【发布时间】:2014-05-15 09:52:47
【问题描述】:
我正在使用半透明表单来捕获鼠标事件,例如 LeftButtonDown、LeftButtonUp 和 MouseMove 以便能够选择屏幕上的一个区域在该区域上绘制一个矩形,问题是每次我移动鼠标时都会绘制一个新的矩形,从而产生如下烦人的结果:
当我将鼠标移动到新的鼠标位置时,我只想更新绘制的矩形以期望得到类似这样的结果:
我尝试处置、清除和重新实例化 Graphics 对象,但没有运气,我也见过 this S.O.讨论这个的问题。
这是我正在使用的代码的相关部分:
''' <summary>
''' The Graphics object to draw on the screen.
''' </summary>
Dim ScreenGraphic As Graphics = Graphics.FromHwnd(IntPtr.Zero)
Private Sub MouseEvents_MouseMove(ByVal MouseLocation As Point) Handles MouseEvents.MouseMove
' If left mouse button is hold then set the rectangle area...
If IsMouseLeftDown Then
' ... blah blah blah
' ... more code here
' Draw the rectangle area.
Me.DrawRectangle()
End If
''' <summary>
''' Draws the rectangle on the selected area.
''' </summary>
Private Sub DrawRectangle()
' Call the "EraseRectanglehere" method here before re-drawing ?
' Me.EraseRectangle
Using pen As New Pen(Me.BorderColor, Me.BorderSize)
ScreenGraphic.DrawRectangle(pen, SelectionRectangle)
End Using
End Sub
''' <summary>
''' Erases the last drawn rectangle.
''' </summary>
Private Sub EraseRectangle()
End Sub
如果有人需要更好地检查它,这里是完整的代码:
注意:我已经更新了我在上一个问题编辑中使用的代码。
Imports System.Runtime.InteropServices
Public Class RangeSelector : Inherits Form
#Region " Properties "
''' <summary>
''' Gets or sets the border size of the range selector.
''' </summary>
''' <value>The size of the border.</value>
Public Property BorderSize As Integer = 2
''' <summary>
''' Gets or sets the border color of the range selector.
''' </summary>
''' <value>The color of the border.</value>
Public Property BorderColor As Color = Color.Red
#End Region
#Region " Objects "
''' <summary>
''' Indicates the initial location when the mouse left button is clicked.
''' </summary>
Private InitialLocation As Point = Point.Empty
''' <summary>
''' Indicates the rectangle that contains the selected area.
''' </summary>
Private SelectionRectangle As Rectangle = Rectangle.Empty
''' <summary>
''' The Graphics object to draw on the screen.
''' </summary>
Private ScreenGraphic As Graphics = Graphics.FromHwnd(IntPtr.Zero)
#End Region
#Region " Constructors "
''' <summary>
''' Initializes a new instance of the <see cref="RangeSelector"/> class.
''' </summary>
Public Sub New()
InitializeComponent()
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="RangeSelector" /> class.
''' </summary>
''' <param name="BorderSize">Indicates the border size of the range selector.</param>
''' <param name="BorderColor">Indicates the border color of the range selector.</param>
Public Sub New(ByVal BorderSize As Integer, ByVal BorderColor As Color)
Me.BorderSize = BorderSize
Me.BorderColor = BorderColor
InitializeComponent()
End Sub
#End Region
#Region " Event Handlers "
Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
' MyBase.OnMouseDown(e)
InitialLocation = e.Location
SelectionRectangle = New Rectangle(InitialLocation.X, InitialLocation.Y, 0, 0)
End Sub
Protected Overrides Sub OnMouseUp(e As MouseEventArgs)
' Make the Form transparent to take the region screenshot.
Me.Opacity = 0.0R
' ToDo:
' take the screenshot.
' Return the selected rectangle area and save it.
Me.Close()
End Sub
Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
' If left mouse button is hold then set the rectangle area...
If e.Button = MouseButtons.Left Then
If (e.Location.X < Me.InitialLocation.X) _
AndAlso (e.Location.Y < Me.InitialLocation.Y) Then ' Top-Left
Me.SelectionRectangle = New Rectangle(e.Location.X,
e.Location.Y,
Me.InitialLocation.X - e.Location.X,
Me.InitialLocation.Y - e.Location.Y)
ElseIf (e.Location.X > Me.InitialLocation.X) _
AndAlso (e.Location.Y < Me.InitialLocation.Y) Then ' Top-Right
Me.SelectionRectangle = New Rectangle(Me.InitialLocation.X,
e.Location.Y,
e.Location.X - Me.InitialLocation.X,
Me.InitialLocation.Y - e.Location.Y)
ElseIf (e.Location.X < Me.InitialLocation.X) _
AndAlso (e.Location.Y > Me.InitialLocation.Y) Then ' Bottom-Left
Me.SelectionRectangle = New Rectangle(e.Location.X,
Me.InitialLocation.Y,
Me.InitialLocation.X - e.Location.X,
e.Location.Y - Me.InitialLocation.Y)
ElseIf (e.Location.X > Me.InitialLocation.X) _
AndAlso (e.Location.Y > Me.InitialLocation.Y) Then ' Bottom-Right
Me.SelectionRectangle = New Rectangle(Me.InitialLocation.X,
Me.InitialLocation.Y,
e.Location.X - Me.InitialLocation.X,
e.Location.Y - Me.InitialLocation.Y)
End If
' Draw the rectangle area.
Me.DrawRectangle()
End If
End Sub
#End Region
#Region " Private Methods "
Private Sub InitializeComponent()
Me.SuspendLayout()
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None
Me.BackColor = System.Drawing.Color.Black
Me.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None
Me.CausesValidation = False
Me.ClientSize = New System.Drawing.Size(100, 100)
Me.ControlBox = False
Me.Cursor = System.Windows.Forms.Cursors.Cross
Me.DoubleBuffered = True
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Name = "RangeSelector"
Me.Opacity = 0.01R
Me.ShowIcon = False
Me.ShowInTaskbar = False
Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide
Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
Me.TopMost = True
Me.WindowState = System.Windows.Forms.FormWindowState.Maximized
Me.ResumeLayout(False)
End Sub
''' <summary>
''' Draws the rectangle on the selected area.
''' </summary>
Private Sub DrawRectangle()
' Just a weird trick to refresh the painting.
' Me.Opacity = 0.0R
' Me.Opacity = 0.01R
' Using g As Graphics = Graphics.FromHwnd(IntPtr.Zero)
Using pen As New Pen(Me.BorderColor, Me.BorderSize)
ScreenGraphic.DrawRectangle(pen, Me.SelectionRectangle)
End Using
' End Using
End Sub
#End Region
End Class
更新 1
我已经翻译了所有代码以将其用作Form 对话框,以便在选择区域时具有更大的灵活性,我已经替换了上面的整个代码来更新我的问题,代码并没有太大变化使用 LL Hook 捕获鼠标事件我正在处理半透明最大化窗体的鼠标事件,我仍然在 Desktop Screen Graphics 上绘制矩形(而不是在 OnPaint Form 事件上) 那部分代码和你在上面的代码中看到的一样:
Private ScreenGraphic As Graphics = Graphics.FromHwnd(IntPtr.Zero)
...因为我说过表单是半透明的,所以如果我在表单中绘制一个矩形,它也会是半透明的(或者至少我不知道如何避免这种情况) .
然后我发现了一个奇怪的技巧,通过在新坐标中绘制矩形之前更改窗体的不透明度来解决矩形问题:
Me.Opacity = 0.0R
Me.Opacity = 0.01R
Using pen As New Pen(Me.BorderColor, Me.BorderSize)
ScreenGraphic.DrawRectangle(pen, Me.SelectionRectangle)
End Using
问题? ...并不完美,它会产生非常烦人的效果,因为我在绘制矩形时会出现很多闪烁(是的,我有 Form doubleBuffered 并且我正在使用 CreateParams 技巧来避免闪烁,但什么都没有)。
更新 2
我已经尝试使用 @Plutonix 在他的评论中指出的 InvalidateRect 函数和这个 API 声明:
<DllImport("user32.dll")>
Private Shared Function InvalidateRect(
ByVal hWnd As Integer,
ByRef lpRect As Rectangle,
ByVal bErase As Boolean) As Boolean
End Function
我尝试将它与False/True 标志一起使用。
问题?问题与我在第一次更新中指出的问题相同:
'并不完美,它会产生非常烦人的效果'因为在绘制矩形时我会出现很多闪烁(是的,我有 Form doubleBuffered,而且我正在使用CreateParams 技巧来避免闪烁,但没有)。'
更新 3
我正在尝试使用 RedrawWindow 函数解决此问题,正如我在 this SO answer 中看到的那样,它可以用来做与 InvalidateRect 函数相同的事情,但也具有更大的灵活性,也许没有我使用InvalidateRect 函数得到的烦人效果,我只需要尝试一下。
RedrawWindow 函数更新指定的矩形或区域 窗口的客户区。
这是 API 声明:
<DllImport("user32.dll")>
Private Shared Function RedrawWindow(
ByVal hWnd As IntPtr,
<[In]> ByRef lprcUpdate As Rectangle,
ByVal hrgnUpdate As IntPtr,
ByVal flags As RedrawWindowFlags) As Boolean
End Function
<Flags()>
Private Enum RedrawWindowFlags As UInteger
''' <summary>
''' Invalidates the rectangle or region that you specify in lprcUpdate or hrgnUpdate.
''' You can set only one of these parameters to a non-NULL value. If both are NULL, RDW_INVALIDATE invalidates the entire window.
''' </summary>
Invalidate = &H1
''' <summary>Causes the OS to post a WM_PAINT message to the window regardless of whether a portion of the window is invalid.</summary>
InternalPaint = &H2
''' <summary>
''' Causes the window to receive a WM_ERASEBKGND message when the window is repainted.
''' Specify this value in combination with the RDW_INVALIDATE value; otherwise, RDW_ERASE has no effect.
''' </summary>
[Erase] = &H4
''' <summary>
''' Validates the rectangle or region that you specify in lprcUpdate or hrgnUpdate.
''' You can set only one of these parameters to a non-NULL value. If both are NULL, RDW_VALIDATE validates the entire window.
''' This value does not affect internal WM_PAINT messages.
''' </summary>
Validate = &H8
NoInternalPaint = &H10
''' <summary>Suppresses any pending WM_ERASEBKGND messages.</summary>
NoErase = &H20
''' <summary>Excludes child windows, if any, from the repainting operation.</summary>
NoChildren = &H40
''' <summary>Includes child windows, if any, in the repainting operation.</summary>
AllChildren = &H80
''' <summary>Causes the affected windows, which you specify by setting the RDW_ALLCHILDREN and RDW_NOCHILDREN values, to receive WM_ERASEBKGND and WM_PAINT messages before the RedrawWindow returns, if necessary.</summary>
UpdateNow = &H100
''' <summary>
''' Causes the affected windows, which you specify by setting the RDW_ALLCHILDREN and RDW_NOCHILDREN values, to receive WM_ERASEBKGND messages before RedrawWindow returns, if necessary.
''' The affected windows receive WM_PAINT messages at the ordinary time.
''' </summary>
EraseNow = &H200
Frame = &H400
NoFrame = &H800
End Enum
我已经尝试使用带有这些参数的函数:
RedrawWindow(IntPtr.Zero, Me.SelectionRectangle, IntPtr.Zero, RedrawWindowFlags.Invalidate)
...我想正如 MSDN 文档所述,如果第一个参数为 NULL,则表示桌面屏幕,第二个参数表示要更新的矩形,如果我指定了第三个参数,则需要为空第二个参数中的矩形,最后一个参数表示一个标志,指示要执行的操作(在这种情况下,像@Plutonix所说的那样使矩形无效?)
我尝试在绘制矩形之后和绘制它之前使用该指令,我的意思是在 OnMouseMove 事件中,或者在我的代码中的 DrawRectangle 方法中,但我看不出有任何区别屏幕,我在绘制矩形时仍然遇到上图中显示的相同问题我的意思是当我移动鼠标时会绘制多个矩形并且此函数会擦除任何矩形,也许我使用了错误的参数? .
【问题讨论】:
-
@Epistemex 感谢您的评论,但我真的不明白那里发布的“ClearRegion”方法,我需要使用从 C# 到 VB.NET 的在线转换器。反正我要试试,再次感谢。
-
看答案底部,有完整的VB源码
-
@ElektroStudios google.no/search?q=csharp+to+vb
-
@Epistemex,对不起我的错,解雇了。
标签: .net vb.net graphics gdi+ rectangles