scprogs
asked on
Stretch and Rotate Text Control
I'm creating a label designer with a GUI. A user will be able to create shape and text objects, move them, rotate them, and resize them as shown in the screen shot below.
Each object is an instance of a custom user control (rectangle class, text class, etc.) that the user can interact with.
I'm not having any issues with the shapes, but I'm having problems rotating and stretching the text. I've been messing with the code for quite a while and I've been getting all kinds of funky results.
This is the code that creates the object:
This is some of the code that is used when the text object is created:
SetPoints is called after the object is created:
RecomputeRegion is the code I've been struggling with. It's called when a property of the text object is changed (it could be called after SetPoints for testing, just hard-code the text height and width).
All the necessary code should be posted, so hopefully you can at least guess what I'm trying to accomplish. Let me know if any other info is needed.
Thanks in advance!
Each object is an instance of a custom user control (rectangle class, text class, etc.) that the user can interact with.
I'm not having any issues with the shapes, but I'm having problems rotating and stretching the text. I've been messing with the code for quite a while and I've been getting all kinds of funky results.
This is the code that creates the object:
TextObj = New TextObject(GetObjectName("Text"), _Unit, _Zoom)
AddHandler TextObj.PropertiesChanged, AddressOf ObjectPropertiesChanged
AddHandler TextObj.DeleteObject, AddressOf DeleteObject
Me.Controls.Add(TextObj)
TextObj.SetPoints(Cursor.Position, New Point(Cursor.Position.X + 1000, Cursor.Position.Y + 25))
This is some of the code that is used when the text object is created:
Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.UserPaint, True)
Me.UpdateStyles()
SetPoints is called after the object is created:
Public Sub SetPoints(ByVal ptA As Point, ByVal ptB As Point) ' <-- Screen Coordinates
If Not (Me.Parent Is Nothing) Then
Dim rc As Rectangle = NormalizedRC(ptA, ptB)
Dim rcClient As Rectangle = Me.Parent.RectangleToClient(rc)
Me.Location = New Point(rcClient.X, rcClient.Y)
PropertiesForm = New TextPropertiesForm(Me)
If PropertiesForm.ShowDialog() = DialogResult.Cancel Then
RaiseEvent DeleteObject(Me)
End If
PropertiesForm.Dispose()
PropertiesForm = Nothing
End If
End Sub
Private Function NormalizedRC(ByVal ptA As Point, ByVal ptB As Point) As Rectangle
Return New Rectangle(New Point(Math.Min(ptA.X, ptB.X), Math.Min(ptA.Y, ptB.Y)), New Size(Math.Abs(ptB.X - ptA.X), Math.Abs(ptB.Y - ptA.Y)))
End Function
RecomputeRegion is the code I've been struggling with. It's called when a property of the text object is changed (it could be called after SetPoints for testing, just hard-code the text height and width).
Private Sub RecomputeRegion()
Try
If _DisplayText = "" Then Exit Sub
Dim g As Graphics = CreateGraphics()
Dim ChRanges(DisplayText.Length - 1) As CharacterRange
For i As Integer = 0 To DisplayText.Length - 1
ChRanges(i) = New CharacterRange(i, 1)
Next
Dim StrFormat As New StringFormat
StrFormat.SetMeasurableCharacterRanges(ChRanges)
Dim f As New Font("Microsoft Sans Serif", _TextHeight, FontStyle.Regular, GraphicsUnit.Pixel)
Dim LayoutRect As Rectangle = New Rectangle(Left, Top, 1000, _TextHeight)
Dim ChRegions() As Region = g.MeasureCharacterRanges(DisplayText, f, LayoutRect, StrFormat)
Dim StrWidth As Integer = 0
Dim ChBounds As RectangleF
Dim ChRect As Rectangle
For Each rgn As Region In ChRegions
ChBounds = rgn.GetBounds(g)
ChRect = Rectangle.Round(ChBounds)
StrWidth += ChRect.Width
Next
StrWidth *= (_TextWidth / _TextHeight)
Dim gp As New GraphicsPath
gp.AddString(DisplayText, New FontFamily("Microsoft Sans Serif"), CInt(FontStyle.Regular), _TextHeight, New Point(0, 0), New StringFormat)
'Dim ClientRect As Rectangle = ClientRectangle
Dim GpBounds As RectangleF = gp.GetBounds
Dim pts(2) As PointF
Dim m As Matrix
Select Case _Rotation
Case Ruler.Rotation.Zero
Size = New Size(StrWidth, _TextHeight)
pts(0) = New PointF(0, 0)
pts(1) = New PointF(Width, 0)
pts(2) = New PointF(0, _TextHeight - GpBounds.Y)
m = New Matrix(GpBounds, pts)
m.RotateAt(0, New PointF(0, 0))
Case Ruler.Rotation.Ninety
Size = New Size(_TextHeight, StrWidth * 2)
pts(0) = New PointF(0 - _TextHeight, 0)
pts(1) = New PointF(StrWidth / (_TextWidth / _TextHeight), 0)
pts(2) = New PointF((0 - _TextHeight + (GpBounds.X * (_TextHeight / _TextWidth))) * (_TextWidth / _TextHeight) + _TextWidth - _TextHeight, (_TextHeight - GpBounds.Y) * (_TextWidth / _TextHeight))
pts(2) = New PointF(0 - _TextHeight, (_TextHeight - GpBounds.Y) * (_TextWidth / _TextHeight))
m = New Matrix(GpBounds, pts)
m.RotateAt(90, New PointF(_TextHeight, _TextHeight))
Location = New Point(Location.X, Location.Y + _TextHeight)
'Case Ruler.Rotation.OneEighty
' Dim pts() As PointF = {New PointF(rect.Width + rect.X, rect.Bottom + rect.Y), New PointF(rect.Width + rect.X + target.Width, rect.Bottom + rect.Y), New PointF(rect.Width + rect.X, rect.Bottom + rect.Height + rect.Y)}
' Dim m As New Matrix(rect, pts)
' m.Rotate(180)
' gp.Transform(m)
'Case Ruler.Rotation.TwoSeventy
' Dim pts() As PointF = {New PointF(target.Left, target.Top), New PointF(target.Right, target.Top), New PointF(target.Left, target.Bottom)}
' Dim m As New Matrix(rect, pts)
' m.Rotate(270)
' gp.Transform(m)
End Select
gp.Transform(m)
Me.Region = New Region(gp)
If Me.Parent IsNot Nothing Then Me.Parent.Refresh()
Catch ex As Exception
End Try
End Sub
All the necessary code should be posted, so hopefully you can at least guess what I'm trying to accomplish. Let me know if any other info is needed.
Thanks in advance!
ASKER
Thanks, SStory.
I'll give it a good look and see if I can use it. The hard part is that I'm not just putting the string on the form. I'm trying to fit it within the bounds of a custom user control. The user needs the ability to interact with each individual object (to move it, resize it, edit the text, etc.).
I'll give it a good look and see if I can use it. The hard part is that I'm not just putting the string on the form. I'm trying to fit it within the bounds of a custom user control. The user needs the ability to interact with each individual object (to move it, resize it, edit the text, etc.).
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Yes, that margin has been a major headache for me. I didn't get a chance to read that GDI+ FAQ yesterday. Hopefully I'll get to check out those links soon.
Thanks again.
Thanks again.
ASKER
I figured out this particular problem. First, I changed my font graphics unit from pixel to point (I could slap myself for that one...)
Turns out I was over-complicating the rest of it (I figured that was the case). It's a little tough to explain, so bear with me.
When I'm declaring a matrix, I'm passing to it a rectangle (graphicspath.bounds) and an array of 3 points.
When I want the text at 0 degrees, the array looks like this:
point 0 = upper-left corner
point 1 = upper-right corner
point 2 = lower-left corner
The lower-right corner is derived automatically in the matrix.
Here's where I began to understand this better (thanks to the image rotation article on codeproject). When I want the text at 90 degrees, I shift all 3 points 90 degrees, so the array looks like this:
point 0 = upper-right corner
point 1 = lower-right corner
point 2 = upper-left corner
The lower-left corner is derived automatically.
Defining the points this way eliminates the need for matrix.RotateAt, which I was previously calling. I also had to switch the width and height.
Thanks, SStory, for helping me look into this.
Turns out I was over-complicating the rest of it (I figured that was the case). It's a little tough to explain, so bear with me.
When I'm declaring a matrix, I'm passing to it a rectangle (graphicspath.bounds) and an array of 3 points.
When I want the text at 0 degrees, the array looks like this:
point 0 = upper-left corner
point 1 = upper-right corner
point 2 = lower-left corner
The lower-right corner is derived automatically in the matrix.
Here's where I began to understand this better (thanks to the image rotation article on codeproject). When I want the text at 90 degrees, I shift all 3 points 90 degrees, so the array looks like this:
point 0 = upper-right corner
point 1 = lower-right corner
point 2 = upper-left corner
The lower-left corner is derived automatically.
Defining the points this way eliminates the need for matrix.RotateAt, which I was previously calling. I also had to switch the width and height.
Thanks, SStory, for helping me look into this.
http://bobpowell.net/angletext.aspx