Solved

Stretch and Rotate Text Control

Posted on 2014-01-30
5
266 Views
Last Modified: 2014-02-05
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.

Screen Shot 1
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))

Open in new window


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()

Open in new window


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

Open in new window


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

Open in new window


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!
0
Comment
Question by:scprogs
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 3
  • 2
5 Comments
 
LVL 25

Expert Comment

by:SStory
ID: 39830867
Have you looked at the examples on Bob Powell's GDI+ FAQ?
http://bobpowell.net/angletext.aspx
0
 

Author Comment

by:scprogs
ID: 39830880
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.).
0
 
LVL 25

Accepted Solution

by:
SStory earned 500 total points
ID: 39832359
Text in Dotnet isn't as precise as I'd like. Seems that if I remember correctly they force a margin around it with no way to measure that margin.  That being said, there are several articles on codeproject.com that may help. One is this:
http://www.codeproject.com/Articles/3319/Image-Rotation-in-NET
The author uses trig functions to calculate the triangles around the rectangle and get the theta rotation angle and then draw a bitmap. I'm not sure if it will help you or not.
Also an article by SSDiver2112, http://www.codeproject.com/Articles/95399/gLabel-Custom-Label-with-Special-Effects-VB-NET, shows how he made the text fit horizontally and vertically in the box, however being rotated as yours is it may not help.
0
 

Author Comment

by:scprogs
ID: 39832436
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.
0
 

Author Closing Comment

by:scprogs
ID: 39837270
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.
0

Featured Post

On Demand Webinar - Networking for the Cloud Era

This webinar discusses:
-Common barriers companies experience when moving to the cloud
-How SD-WAN changes the way we look at networks
-Best practices customers should employ moving forward with cloud migration
-What happens behind the scenes of SteelConnect’s one-click button

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In my previous two articles we discussed Binary Serialization (http://www.experts-exchange.com/A_4362.html) and XML Serialization (http://www.experts-exchange.com/A_4425.html). In this article we will try to know more about SOAP (Simple Object Acces…
A long time ago (May 2011), I have written an article showing you how to create a DLL using Visual Studio 2005 to be hosted in SQL Server 2005. That was valid at that time and it is still valid if you are still using these versions. You can still re…
Monitoring a network: why having a policy is the best policy? Michael Kulchisky, MCSE, MCSA, MCP, VTSP, VSP, CCSP outlines the enormous benefits of having a policy-based approach when monitoring medium and large networks. Software utilized in this v…
Do you want to know how to make a graph with Microsoft Access? First, create a query with the data for the chart. Then make a blank form and add a chart control. This video also shows how to change what data is displayed on the graph as well as form…

626 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question