none
drawreversibleframe draws in the "wrong" place if the display scaling is other than 100% RRS feed

  • Question

  • I've an application that lets the the user drag a rectangle over an image. I'm using DrawReversibleFrame for this. With the display scaling set to 100%, everything works but if I set the display scaling to anything else, the frame is drawn the correct size (as far as I can judge) but in the wrong place. I've written a demo application to show the problem:

    Public Class Form1

        Dim StartPt As Point
        Dim HavePrevRect As Boolean = False
        Dim PrevRect As Rectangle

        Private Sub Form1_MouseDown(sender As Object, e As MouseEventArgs) Handles MyBase.MouseDown
            StartPt = Me.PointToScreen(e.Location)
            HavePrevRect = False
        End Sub

        Private Sub Form1_MouseMove(sender As Object, e As MouseEventArgs) Handles MyBase.MouseMove
            If e.Button <> MouseButtons.Left Then
                Exit Sub
            End If
            If HavePrevRect Then
                ControlPaint.DrawReversibleFrame(PrevRect, Color.Black, FrameStyle.Dashed) ' remove previous frame
            End If
            Dim NewPos As Point = Me.PointToScreen(e.Location)
            Dim Rect As New Rectangle(StartPt.X, StartPt.Y, NewPos.X - StartPt.X, NewPos.Y - StartPt.Y)
            ControlPaint.DrawReversibleFrame(Rect, Color.Black, FrameStyle.Dashed)
            PrevRect = Rect
            HavePrevRect = True
        End Sub

        Private Sub Form1_MouseUp(sender As Object, e As MouseEventArgs) Handles MyBase.MouseUp
            If HavePrevRect Then
                ControlPaint.DrawReversibleFrame(PrevRect, Color.Black, FrameStyle.Dashed)
            End If
            HavePrevRect = False
        End Sub
    End Class



    AndrewFromEssex

    Sunday, June 3, 2018 5:30 AM

Answers

  • Andrew,

    I dont seem to be able to reproduce the problem using your example 125 percent scaling windows 7 64bit. Can you?

    How do you know if it draws right? What is drawn for the rect does not match the mouse down coordinates visually? I cant duplicate that in your example.

    I put an image in the form backgroundimage and I can draw a rect over it correctly?

    However I know exactly what you describe. What is your video resolution and what version of windows. Describe exactly how to reproduce the problem and the settings you are using, windows version, computer (ie ms surface).

    I think the easiest thing first of all is to draw in the paint event using e.graphics instead of in the mouse event using drawreversibleline. The drawresversible is old and really meant to draw on controls. Its not really for graphics like you are doing.

    Here is an example that draws in the paint event. See if this works for you.

    The example creates a picturebox docked fill to the form and draws on that. All you have to do to create the example is paste the code into an empty form and change the path to the image file.

    'draw rectangle move with mouse 
    Option Strict On
    Public Class CADMouseRectangle
        Private WithEvents PictureBox1 As New PictureBox With {.Parent = Me, .Dock = DockStyle.Fill, .BackColor = Color.Black}
        Private MouseDownStage As Integer
        Private MouseDownPt, MouseMovePt, MouseDownOffsetPt As PointF
        Private FenceRect As RectangleF
    
    
        Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
            MouseDownStage = GetMouseOverType(e.X, e.Y)
            Select Case MouseDownStage
                Case 1      'nothing under mouse down begin drawing tracer
                    MouseDownPt = e.Location
                    MouseMovePt = e.Location
                Case 2, 3   'mouse down on rectangle or handle begine moving
                    MouseDownPt = FenceRect.Location
                    MouseDownOffsetPt.X = e.X - FenceRect.X
                    MouseDownOffsetPt.Y = e.Y - FenceRect.Y
            End Select
        End Sub
    
        Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
            MouseMovePt = e.Location
    
            If MouseDownStage > 0 Then
                Dim dx, dy As Single
    
                Select Case MouseDownStage
                    Case 1                      'drawing rectangle 
                        FenceRect = GetFenceRect(MouseDownPt, New SizeF(MouseMovePt.X - MouseDownPt.X,
                                                                        MouseMovePt.Y - MouseDownPt.Y))
    
                    Case 2                      'moving fence
                        dx = e.X - MouseDownPt.X
                        dy = e.Y - MouseDownPt.Y
                        FenceRect.X = MouseDownPt.X + dx - MouseDownOffsetPt.X
                        FenceRect.Y = MouseDownPt.Y + dy - MouseDownOffsetPt.Y
                    Case 3                      'moving handle
                        FenceRect.Width = Math.Abs(FenceRect.X - e.X)
                        FenceRect.Height = Math.Abs(FenceRect.Y - e.Y)
                End Select
                PictureBox1.Invalidate()
            Else
                Select Case GetMouseOverType(e.X, e.Y)
                    Case 2  'fence
                        Cursor = Cursors.Hand
                    Case 3 'handle
                        Cursor = Cursors.SizeNESW
                    Case Else
                        Cursor = Cursors.Default
                End Select
            End If
        End Sub
    
        Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
    
            Select Case MouseDownStage
                Case 1      'save the rectangle
                    FenceRect = GetFenceRect(MouseDownPt, New SizeF(MouseMovePt.X - MouseDownPt.X,
                                                                        MouseMovePt.Y - MouseDownPt.Y))
    
            End Select
    
            MouseDownStage = 0
            PictureBox1.Invalidate()
        End Sub
    
        Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
    
            With e.Graphics
                Using p As New Pen(Color.White, 1)
                    'draw the saved rectangle
                    If FenceRect.Width > 0 Then
                        'rectangle outline dashed white
                        p.DashStyle = Drawing2D.DashStyle.Dash
                        .DrawRectangle(p, Rectangle.Round(FenceRect))
    
                        'draw the handle solid green
                        Dim h As Integer = 5
                        Dim rect2 As New Rectangle(CInt(FenceRect.X + FenceRect.Width - h), CInt(FenceRect.Y + FenceRect.Height - h), 2 * h, 2 * h)
                        p.Width = 2
                        p.Color = Color.LimeGreen
                        p.DashStyle = Drawing2D.DashStyle.Solid
                        .DrawRectangle(p, rect2)
                    End If
                End Using
            End With
        End Sub
    
        Private Function GetMouseOverType(x As Integer, y As Integer) As Integer
            'determine if the mouse pointer is over the handle or fence
            Dim h As Integer = 5
            Dim handleRect As New Rectangle(CInt(fenceRect.X + fenceRect.Width - h), CInt(fenceRect.Y + fenceRect.Height - h), 2 * h, 2 * h)
    
            If handleRect.Contains(x, y) Then
                GetMouseOverType = 3              'mouse over handle
            ElseIf fenceRect.Contains(x, y) Then
                GetMouseOverType = 2              'mouse over fence FenceRect
            Else
                GetMouseOverType = 1              'mouse not over anything
            End If
        End Function
    
        Private Function GetFenceRect(locPt As PointF, sz As SizeF) As RectangleF
            'creates proper FenceRect from any direction for point and size 
            Dim X As Single = locPt.X
            Dim X1 As Single = sz.Width
    
            If X1 < 0 Then
                X1 = Math.Abs(X1)
                X -= X1
            End If
    
            Dim Y As Single = locPt.Y
            Dim Y1 As Single = sz.Height
    
            If Y1 < 0 Then
                Y1 = Math.Abs(Y1)
                Y -= Y1
            End If
    
            GetFenceRect = New RectangleF(X, Y, X1, Y1)
    
        End Function
    
        Private Sub CADMouseRectangle_Load(sender As Object, e As EventArgs) Handles Me.Load
            PictureBox1.BackgroundImage = Image.FromFile("c:\bitmaps\rusty.jpg")
            PictureBox1.BackgroundImageLayout = ImageLayout.None
    
        End Sub
    End Class



    Sunday, June 3, 2018 11:48 AM

All replies

  • Andrew,

    I dont seem to be able to reproduce the problem using your example 125 percent scaling windows 7 64bit. Can you?

    How do you know if it draws right? What is drawn for the rect does not match the mouse down coordinates visually? I cant duplicate that in your example.

    I put an image in the form backgroundimage and I can draw a rect over it correctly?

    However I know exactly what you describe. What is your video resolution and what version of windows. Describe exactly how to reproduce the problem and the settings you are using, windows version, computer (ie ms surface).

    I think the easiest thing first of all is to draw in the paint event using e.graphics instead of in the mouse event using drawreversibleline. The drawresversible is old and really meant to draw on controls. Its not really for graphics like you are doing.

    Here is an example that draws in the paint event. See if this works for you.

    The example creates a picturebox docked fill to the form and draws on that. All you have to do to create the example is paste the code into an empty form and change the path to the image file.

    'draw rectangle move with mouse 
    Option Strict On
    Public Class CADMouseRectangle
        Private WithEvents PictureBox1 As New PictureBox With {.Parent = Me, .Dock = DockStyle.Fill, .BackColor = Color.Black}
        Private MouseDownStage As Integer
        Private MouseDownPt, MouseMovePt, MouseDownOffsetPt As PointF
        Private FenceRect As RectangleF
    
    
        Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
            MouseDownStage = GetMouseOverType(e.X, e.Y)
            Select Case MouseDownStage
                Case 1      'nothing under mouse down begin drawing tracer
                    MouseDownPt = e.Location
                    MouseMovePt = e.Location
                Case 2, 3   'mouse down on rectangle or handle begine moving
                    MouseDownPt = FenceRect.Location
                    MouseDownOffsetPt.X = e.X - FenceRect.X
                    MouseDownOffsetPt.Y = e.Y - FenceRect.Y
            End Select
        End Sub
    
        Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
            MouseMovePt = e.Location
    
            If MouseDownStage > 0 Then
                Dim dx, dy As Single
    
                Select Case MouseDownStage
                    Case 1                      'drawing rectangle 
                        FenceRect = GetFenceRect(MouseDownPt, New SizeF(MouseMovePt.X - MouseDownPt.X,
                                                                        MouseMovePt.Y - MouseDownPt.Y))
    
                    Case 2                      'moving fence
                        dx = e.X - MouseDownPt.X
                        dy = e.Y - MouseDownPt.Y
                        FenceRect.X = MouseDownPt.X + dx - MouseDownOffsetPt.X
                        FenceRect.Y = MouseDownPt.Y + dy - MouseDownOffsetPt.Y
                    Case 3                      'moving handle
                        FenceRect.Width = Math.Abs(FenceRect.X - e.X)
                        FenceRect.Height = Math.Abs(FenceRect.Y - e.Y)
                End Select
                PictureBox1.Invalidate()
            Else
                Select Case GetMouseOverType(e.X, e.Y)
                    Case 2  'fence
                        Cursor = Cursors.Hand
                    Case 3 'handle
                        Cursor = Cursors.SizeNESW
                    Case Else
                        Cursor = Cursors.Default
                End Select
            End If
        End Sub
    
        Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
    
            Select Case MouseDownStage
                Case 1      'save the rectangle
                    FenceRect = GetFenceRect(MouseDownPt, New SizeF(MouseMovePt.X - MouseDownPt.X,
                                                                        MouseMovePt.Y - MouseDownPt.Y))
    
            End Select
    
            MouseDownStage = 0
            PictureBox1.Invalidate()
        End Sub
    
        Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
    
            With e.Graphics
                Using p As New Pen(Color.White, 1)
                    'draw the saved rectangle
                    If FenceRect.Width > 0 Then
                        'rectangle outline dashed white
                        p.DashStyle = Drawing2D.DashStyle.Dash
                        .DrawRectangle(p, Rectangle.Round(FenceRect))
    
                        'draw the handle solid green
                        Dim h As Integer = 5
                        Dim rect2 As New Rectangle(CInt(FenceRect.X + FenceRect.Width - h), CInt(FenceRect.Y + FenceRect.Height - h), 2 * h, 2 * h)
                        p.Width = 2
                        p.Color = Color.LimeGreen
                        p.DashStyle = Drawing2D.DashStyle.Solid
                        .DrawRectangle(p, rect2)
                    End If
                End Using
            End With
        End Sub
    
        Private Function GetMouseOverType(x As Integer, y As Integer) As Integer
            'determine if the mouse pointer is over the handle or fence
            Dim h As Integer = 5
            Dim handleRect As New Rectangle(CInt(fenceRect.X + fenceRect.Width - h), CInt(fenceRect.Y + fenceRect.Height - h), 2 * h, 2 * h)
    
            If handleRect.Contains(x, y) Then
                GetMouseOverType = 3              'mouse over handle
            ElseIf fenceRect.Contains(x, y) Then
                GetMouseOverType = 2              'mouse over fence FenceRect
            Else
                GetMouseOverType = 1              'mouse not over anything
            End If
        End Function
    
        Private Function GetFenceRect(locPt As PointF, sz As SizeF) As RectangleF
            'creates proper FenceRect from any direction for point and size 
            Dim X As Single = locPt.X
            Dim X1 As Single = sz.Width
    
            If X1 < 0 Then
                X1 = Math.Abs(X1)
                X -= X1
            End If
    
            Dim Y As Single = locPt.Y
            Dim Y1 As Single = sz.Height
    
            If Y1 < 0 Then
                Y1 = Math.Abs(Y1)
                Y -= Y1
            End If
    
            GetFenceRect = New RectangleF(X, Y, X1, Y1)
    
        End Function
    
        Private Sub CADMouseRectangle_Load(sender As Object, e As EventArgs) Handles Me.Load
            PictureBox1.BackgroundImage = Image.FromFile("c:\bitmaps\rusty.jpg")
            PictureBox1.BackgroundImageLayout = ImageLayout.None
    
        End Sub
    End Class



    Sunday, June 3, 2018 11:48 AM
  •  This is because your application is not Dpi Aware.  You can fix that by opening your application in Visual Studio,  then Click (Project) on the VS menu.  Now select (YourApplicationName Properties) to open the application's properties.

     When the properties open,  select the (Application) tab and then click the (View Windows Settings) button on that tab.  It will open the app.manifest file.  Find the part shown below which could look a little different than mine since I am using VS2015 and you might be using a newer version...

      <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
           DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need 
           to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should 
           also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
      <!--
      <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
          <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        </windowsSettings>
      </application>
      -->
     

     You can enable the dpiAware setting by simply un-commenting the setting as shown below.

      <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
           DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need 
           to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should 
           also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
    
      <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
          <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        </windowsSettings>
      </application>
     

     Now just Build your application and run it.

     

     Hi Tom.

     Try it at 150%,  that will cause the problem.  That is due to the scaling using the same 96dpi for 125% as it does for 100% but,  120dpi for 150%.  8)


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Sunday, June 3, 2018 12:23 PM
    Sunday, June 3, 2018 12:19 PM
  •  Hi Tom.

     Try it at 150%,  that will cause the problem.  That is due to the scaling using the same 96dpi for 125% as it does for 100% but,  120dpi for 150%.  8)


    If you say it can`t be done then i`ll try it

    Hi Razerz!

    Good to see you.

    Yeah, deja vu again.

    Not the same for text. It is scaled at 125 etc. Dpi aware in the manifest does not correct everything. Depends what it is exactly and which dpi scaling and all the other settings now available for the form and etc. Not sure about pointtoscreen.

    Examples with text vs bitmap scaling.

    Anyway, the drawreversible line and using pointtoscreen and etc is all wrong. Wrong Wrong Wrong Wrong I tell you!

    :)

    PS the other thing to consider with gdi drawing is that if you are scaling the view port in some way like with scaletransform, then none of this applies anymore. Because we are drawing and scaling it all to fit the viewport. So that is the direction we want to go if doing scaled drawing.

    Sunday, June 3, 2018 12:30 PM
  • Andrew,

    If setting dpi aware as Razerz shows does not help then please describe more about what you want to do exactly with the application.

    "lets the the user drag a rectangle over an image"

    Is the image on the application form or on the desktop screen or other application?




    Sunday, June 3, 2018 1:07 PM
  • Hi Razerz!

    Good to see you.

    Yeah, deja vu again.

    Not the same for text. It is scaled at 125 etc. Dpi aware in the manifest does not correct everything. Depends what it is exactly and which dpi scaling and all the other settings now available for the form and etc. Not sure about pointtoscreen.

    Examples with text vs bitmap scaling.

    Anyway, the drawreversible line and using pointtoscreen and etc is all wrong. Wrong Wrong Wrong Wrong I tell you!

    :)

    haha!!!

     Correct,  setting the dpiAware setting will not correct all problems that can arise from winform applications not being dpi aware but,  it does fix this problem when using the DrawReversableFrame method.

     If Andrew only needs to select images withing the form,  then I agree that it can be done better using a few different ways,  including the way you have shown.

     OT - I have been busy, busy, busy.  I do manage to get on the computer at least once or twice a week for at least a few minutes lately.  Hope all is good with you.  8)


    If you say it can`t be done then i`ll try it

    Sunday, June 3, 2018 1:12 PM
  • I see.

    Well busy is good I think.

    Sunday, June 3, 2018 1:23 PM
  • Thanks to all - and thanks for the sample code; I'll try switching to that - I think that will fix that for me.

    -Andrew


    AndrewFromEssex

    Sunday, June 3, 2018 6:18 PM
  • Hi AndrewFromEssex,

    If the above reply can resolve your issue, please remember to close your thread by marking the helpful post as answer, it is beneficial to other community members who face the same issue.

    Thanks for your understanding.

    Best Regards,

    Cherry


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, June 4, 2018 7:30 AM
    Moderator
  • Hi Tom, I've modified my code as you suggested and that fixed the problem - I was previously using a Panel, but that flickered like crazy because of all the paint events, I switched that to PictureBox and all is working.

    Thanks again!


    AndrewFromEssex

    Monday, June 4, 2018 2:04 PM
  • Hi Tom, I've modified my code as you suggested and that fixed the problem - I was previously using a Panel, but that flickered like crazy because of all the paint events, I switched that to PictureBox and all is working.

    Thanks again!


    AndrewFromEssex


    Oh, ok. Good.

    Yes the Panel does not have doublebuffered unless you add it special. Doublebuffer is built into and on by default in a picturebox. In a form you can option it on or off etc.

    Now we are curious, did you try setting the app.manifest as Razerz shows? Its not hard but seems hard if you have never done it.

    Do you understand what was happening and why you had a problem? What was giving the offset problem first mentioned? The pointtoscreen offset? Again just curious so we all know.

    It is good to understand why things are changing size and then pick the "best way" to deal with it.

    I think app manifest dpi aware is mostly for auto-sizing controls and text. It does not affect all gdi drawing in the same way exactly and is more complicated. I am not claiming to understand it completely.

    So a hangman game using labels and textboxes is good for appmanifest dpi aware but a CAD drawing program needs to have:

        programmer dpi aware = true


    Monday, June 4, 2018 2:38 PM
  • Hi, I did try making the app DPI aware but hit some problems - it was a while ago, so I can't remember all the details but one ISTR was on multiple screens - and the application is designed to be used with a two screen set-up - then the wrong DPI was reported when the application was moved from one screen to another.

    I think the problem is to do with the drawreversibleframe code; I did try to compensate by finding out the display zoom factor, but that seems to be pretty much impossible!


    AndrewFromEssex

    Monday, June 4, 2018 2:55 PM
  • Hi, I did try making the app DPI aware but hit some problems - it was a while ago, so I can't remember all the details but one ISTR was on multiple screens - and the application is designed to be used with a two screen set-up - then the wrong DPI was reported when the application was moved from one screen to another.

    I think the problem is to do with the drawreversibleframe code; I did try to compensate by finding out the display zoom factor, but that seems to be pretty much impossible!


    AndrewFromEssex


    "finding out the display zoom factor"

    Well it depends what you mean exactly again. There are several things that come to play. It is really two different things.

    There is the control panel scaling setting ie 100, 125, 150 percent.

    Or there is the actual device resolution. For our standard window monitor that is 96 dpi.

    And then some new things like "hi resolution" and the Mac "Retina" thing etc.

    Agian not that I understand it completely.

    But if you are doing gdi drawing the 96 dpi comes in and if you are using text and pictures in labels and pictureboxes then its the other control panel scaling.

    So for example our old monitor video is 96 dpi but our printer is 600 dpi and we want to use the same code to draw on both devices then we can e.graphics.dpix to get the device resolution. Or we can scale the view port with scaletransform.

    But for the most part we don't need drawreversible these days IMHO. However it does have a nice effect (or limitation) in it is XOR pen color so you always get inverse of the screen. What I show using a normal pen you do not. So sometimes the background is the color of the pen but one does not know that and XOR is good inverse colors...and on and on...

    Razerz?

    Monday, June 4, 2018 3:35 PM
  • It was on the display settings, I couldn't find out the display zoom settings from within the program. Yes, I have thought of a way around the missing XOR yet maybe drawing two rectangles, one black one white and with one inside the other one.

    AndrewFromEssex

    Monday, June 4, 2018 4:25 PM
  • I tried two rectangle technique; outer black, inner white; it worked quite well:

    AndrewFromEssex

    Monday, June 4, 2018 6:03 PM
  • Andrew,

    Yes that is what I use, the dotted line style etc. Normally not mission critical for me. You can still use bitblt functions via apis and etc.

    There are apis to get the windows scaling percentage as set in control panel. Razerz showed an example in this thread Examples with text vs bitmap scaling. For gdi drawing I don't think you need it if you scale the viewport. But as usual it depends ...

    Then you know about pen.dashstyle and pen.width.

    Monday, June 4, 2018 7:50 PM