我有一个问题,要么很容易解决,要么太复杂,我已经为它疯狂了......接下来,我正在 VB.NET (WindowsForms) 中开发一个应用程序来截取屏幕截图并能够选择屏幕的一部分。我正在使用自定义用户控件来选择屏幕的一部分,到目前为止一切顺利,我的问题是,当制作箭头、矩形或手绘图时,这些图画不会出现在所选框架中,因为它位于下方并且不能看到绘图,因为该控件即使具有透明度,并且已捕获图片框的内容,它也不会显示在图片框的 Paint 事件中所做的绘图。我试图实现的一种选择是在控件本身中制作绘图,但是,当我保存剪辑时,绘图没有出现,因为它们在控件中而不是在图片框中......有点令人困惑,对吧?哈哈
这是我使用用户控件捕获图像的代码。
Imports System.Drawing.Drawing2D
Imports Controle_Customizado_PictureBox
Imports FontAwesome.Sharp
Public Class frm_transp_form
Private imgInput As Image
Private EndLocation As Point
Private StartLocation As Point
Private rect As Rectangle
Private IsMouseDown As Boolean = False
Private ctrlResize As New FrameControl()
Private currentBtn As IconButton
Private BottomBoardBtn As Panel
Private ToolTipPersonal As New ToolTip
'desenho linha freehand
Private freehand As Boolean = False
Private linefreehand As New GraphicsPath()
Sub New()
' Esta chamada é requerida pelo designer.
InitializeComponent()
' Adicione qualquer inicialização após a chamada InitializeComponent().
BottomBoardBtn = New Panel With {
.Size = New Size(15, 1)
}
pnl_menu.Controls.Add(BottomBoardBtn)
End Sub
Private Sub frm_transp_form_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.Size = New Size(Screen.AllScreens.Sum(Function(s As Screen) s.Bounds.Width), Screen.AllScreens.Max(Function(s As Screen) s.Bounds.Height))
Me.MaximumSize = New Size(Me.Width, Me.Height)
Me.MinimumSize = Me.MaximumSize
pb_screen.Dock = DockStyle.Fill
pb_screen.SendToBack()
Dim bmp As Bitmap = New Bitmap(Screen.AllScreens.Sum(Function(s As Screen) s.Bounds.Width), Screen.AllScreens.Max(Function(s As Screen) s.Bounds.Height))
Using g As Graphics = Graphics.FromImage(bmp)
g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
g.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
g.CopyFromScreen(SystemInformation.VirtualScreen.X, SystemInformation.VirtualScreen.Y, 0, 0, SystemInformation.VirtualScreen.Size, CopyPixelOperation.SourceCopy)
'Me.BackgroundImage = bmp
pb_screen.BackgroundImage = bmp
End Using
Me.Location = New Point(0, 0)
Me.Cursor = Cursors.Arrow
ctrlResize.DashNodeColor = Color.Gainsboro
ctrlResize.Size = New Size(0, 0)
ctrlResize.Location = New Point(0, 0)
ctrlResize.DrawingcColor = pb_color.BackColor
ctrlResize.BackColor = Color.Transparent
pb_screen.Controls.Add(ctrlResize)
End Sub
Private Sub ActivateButton(senderBtn As Object, customcolor As Color)
If senderBtn IsNot Nothing Then
DisableButton()
currentBtn = CType(senderBtn, IconButton)
currentBtn.IconColor = customcolor
BottomBoardBtn.BackColor = customcolor
BottomBoardBtn.Location = New Point(currentBtn.Location.X + ((pnl_menu.Width - BottomBoardBtn.Width) / 2), currentBtn.Bottom)
BottomBoardBtn.Visible = True
BottomBoardBtn.BringToFront()
End If
End Sub
Private Sub DisableButton()
If currentBtn IsNot Nothing Then
currentBtn.IconColor = Color.FromArgb(78, 78, 78)
End If
End Sub
Private Sub Resetbutton()
DisableButton()
BottomBoardBtn.Visible = False
End Sub
Private Sub pb_screen_MouseDown(sender As Object, e As MouseEventArgs) Handles pb_screen.MouseDown
IsMouseDown = True
StartLocation = e.Location
With ctrlResize
.CanMove = True
.DrawArrow = False
.DrawHand = False
.DrawRect = False
End With
pnl_menu.Visible = False
If freehand Then
linefreehand.StartFigure()
End If
Console.WriteLine("Startlocation: " & StartLocation.ToString)
End Sub
Private Sub pb_screen_MouseMove(sender As Object, e As MouseEventArgs) Handles pb_screen.MouseMove
If IsMouseDown = True Then
EndLocation = e.Location
GetRectangle()
Application.DoEvents()
ctrlResize.Size = New Size(rect.Size)
ctrlResize.Location = New Point((StartLocation.X), (StartLocation.Y))
lbl_position.Text = ctrlResize.Width & "x" & ctrlResize.Height
pnl_menu.Location = New Point(EndLocation.X + 5, EndLocation.Y - pnl_menu.Height)
Application.DoEvents()
If freehand Then
linefreehand.AddLine(e.X, e.Y, e.X, e.Y)
End If
pb_screen.Controls(0).Invalidate()
pb_screen.Invalidate()
End If
End Sub
Private Sub pb_screen_Paint(sender As Object, e As PaintEventArgs) Handles pb_screen.Paint
e.Graphics.ExcludeClip(ctrlResize.Bounds)
Using b = New SolidBrush(Color.FromArgb(100, Color.Black))
e.Graphics.FillRectangle(b, pb_screen.ClientRectangle)
End Using
If freehand Then
DrawShapes(e.Graphics)
End If
End Sub
Private Sub pb_screen_MouseUp(sender As Object, e As MouseEventArgs) Handles pb_screen.MouseUp
If IsMouseDown = True Then
EndLocation = e.Location
IsMouseDown = False
Console.WriteLine("Retangulo: " & rect.Size.ToString)
pnl_menu.Visible = True
End If
End Sub
Private Sub DrawShapes(ByVal g As Graphics)
Try
g.SmoothingMode = SmoothingMode.AntiAlias
g.SmoothingMode = SmoothingMode.HighQuality
g.SmoothingMode = SmoothingMode.HighSpeed
Dim linecolor As Color = Color.Red
Dim linepen As Single = 0.3F
''DESENHO FREEHAND
Using handpen As Pen = New Pen(linecolor, linepen)
g.DrawPath(handpen, linefreehand)
End Using
Catch ex As Exception
Throw ex
End Try
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If freehand = False Then
With ctrlResize
.CanMove = False
.DrawArrow = False
.DrawHand = False
.DrawRect = False
End With
freehand = True
End If
End Sub
Private Function GetRectangle() As Rectangle
rect = New Rectangle With {
.X = Math.Min(StartLocation.X, EndLocation.X),
.Y = Math.Min(StartLocation.Y, EndLocation.Y),
.Width = Math.Abs(StartLocation.X - EndLocation.X),
.Height = Math.Abs(StartLocation.Y - EndLocation.Y)
}
Return rect
End Function
Protected Overrides ReadOnly Property CreateParams As CreateParams
Get
Dim cp As CreateParams = MyBase.CreateParams
cp.ExStyle = cp.ExStyle Or &H2000000
Return cp
End Get
End Property
Private Sub pb_color_Click(sender As Object, e As EventArgs) Handles pb_color.Click
'ColorDialog1.ShowDialog()
If ColorDialog1.ShowDialog <> DialogResult.Cancel Then
pb_color.BackColor = ColorDialog1.Color
ctrlResize.DrawingcColor = ColorDialog1.Color
End If
End Sub
Private Sub btn_arrow_Click(sender As Object, e As EventArgs) Handles btn_arrow.Click
ActivateButton(sender, Color.FromArgb(78, 78, 78))
With ctrlResize
.CanMove = False
.DrawArrow = True
.DrawHand = False
.DrawRect = False
End With
End Sub
Private Sub btn_freehand_Click(sender As Object, e As EventArgs) Handles btn_freehand.Click
ActivateButton(sender, Color.FromArgb(78, 78, 78))
With ctrlResize
.CanMove = False
.DrawArrow = False
.DrawHand = True
.DrawRect = False
End With
End Sub
Private Sub btn_rect_Click(sender As Object, e As EventArgs) Handles btn_rect.Click
ActivateButton(sender, Color.FromArgb(78, 78, 78))
With ctrlResize
.CanMove = False
.DrawArrow = False
.DrawHand = False
.DrawRect = True
End With
End Sub
Private Sub btn_copy_Click(sender As Object, e As EventArgs) Handles btn_copy.Click
ActivateButton(sender, Color.FromArgb(78, 78, 78))
Dim selectedRectangle = New Rectangle(StartLocation.X, StartLocation.Y, ctrlResize.ClientRectangle.Width, ctrlResize.ClientRectangle.Height)
Dim result = GetRectangeOnImage(pb_screen, selectedRectangle)
Console.WriteLine("VALORES DO RETANGULO: " & selectedRectangle.ToString)
Using bm = New Bitmap(CInt(result.Width), CInt(result.Height))
Using g = Graphics.FromImage(bm)
g.SmoothingMode = SmoothingMode.HighQuality
g.CompositingQuality = CompositingQuality.HighQuality
g.DrawImage(pb_screen.Image, 0, 0, result, GraphicsUnit.Pixel)
End Using
Dim img As Image = CType(bm.Clone(), Image)
PictureBox1.Size = New Size(ctrlResize.Size)
PictureBox1.Image = img
PictureBox1.Visible = True
End Using
End Sub
Public Function GetRectangeOnImage(ByVal p As PictureBox, ByVal selectionRect As Rectangle) As RectangleF
Dim method = GetType(PictureBox).GetMethod("ImageRectangleFromSizeMode", System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance)
Dim imageRect = CType(method.Invoke(p, New Object() {p.SizeMode}), Rectangle)
If p.Image Is Nothing Then Return selectionRect
Dim cx = CSng(p.Image.Width) / CSng(imageRect.Width)
Dim cy = CSng(p.Image.Height) / CSng(imageRect.Height)
Dim r2 = Rectangle.Intersect(imageRect, selectionRect)
r2.Offset(-imageRect.X, -imageRect.Y)
Return New RectangleF(r2.X * cx, r2.Y * cy, r2.Width * cx, r2.Height * cy)
End Function
Private Sub btn_fechar_Click(sender As Object, e As EventArgs) Handles btn_fechar.Click
ActivateButton(sender, Color.FromArgb(78, 78, 78))
End
End Sub
End Class
这是我的用户控制代码
Imports System.ComponentModel
Imports System.Drawing.Drawing2D
Imports System.Windows
Partial Public Class FrameControl
Inherits UserControl
<DefaultValue(True)>
Public Property CanMove As Boolean = True
<DefaultValue(False)>
Public Property DrawArrow As Boolean = False
<DefaultValue(False)>
Public Property DrawHand As Boolean = False
<DefaultValue(False)>
Public Property DrawRect As Boolean = False
Public Property DrawingcColor As Color = Color.LightGreen
Public Property PenSize As Single = 3.0F
Public Property DashNodeColor As Color = Color.Black
Public Event DrawUndoClicked As EventHandler
Dim btn As Button
Const WM_NCHITTEST As Integer = 132
Const WM_SETCURSOR As Integer = 32
Const WM_MOUSEMOVE As Integer = 32
Const WM_NCLBUTTONDBLCLK As Integer = 163
Private isMouseDown As Boolean
'desenho flecha
Private m_StartX, m_StartY, m_CurX, m_CurY As Single
Dim MyLineArrows As New List(Of LineList)
'desenho retangulo
Private SelectedColor As Color = Color.LightGreen
Private drawingRects As List(Of DrawingRectangle) = New List(Of DrawingRectangle)()
'desenho linha freehand
Private linefreehand As New GraphicsPath()
Private sizeNodeRect As Integer = 7
Private nodeSelected As PosSizableRect = PosSizableRect.None
Public rect As Rectangle
Private Enum PosSizableRect
UpMiddle
LeftMiddle
LeftBottom
LeftUp
RightUp
RightMiddle
RightBottom
BottomMiddle
None
End Enum
Public Sub New()
InitializeComponent()
rect = ClientRectangle
SetStyle(ControlStyles.SupportsTransparentBackColor, True)
DoubleBuffered = True
ResizeRedraw = True
BackColor = Color.Transparent
Timer1.Enabled = True
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
Try
Using p = New Pen(DashNodeColor, 2)
Using b = New SolidBrush(DashNodeColor)
p.DashStyle = Drawing2D.DashStyle.Dash
e.Graphics.DrawRectangle(p, 0, 0, Width - 1, Height - 1)
For Each pos As PosSizableRect In [Enum].GetValues(GetType(PosSizableRect))
e.Graphics.DrawRectangle(p, GetRect(pos))
e.Graphics.FillRectangle(b, GetRect(pos))
Next
End Using
End Using
If CanMove = False Then
DrawShapes(e.Graphics)
End If
Catch ex As Exception
Throw ex
End Try
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
Try
Dim borderWidth As Integer = 10
If m.Msg = WM_SETCURSOR Then
If (m.LParam.ToInt32() And 65535) = 2 Then
Cursor.Current = If(CanMove, Cursors.SizeAll, Cursors.Arrow)
m.Result = CType(1, IntPtr)
Return
End If
End If
If m.Msg = WM_MOUSEMOVE Then
If (m.LParam.ToInt32() And 65535) = 2 Then
Cursor.Current = Cursors.Arrow
m.Result = CType(1, IntPtr)
Return
End If
End If
If m.Msg = WM_NCLBUTTONDBLCLK Then
m.Result = CType(1, IntPtr)
Return
End If
MyBase.WndProc(m)
If m.Msg = WM_NCHITTEST Then
Dim pos = PointToClient(New Point(m.LParam.ToInt32() And 65535, m.LParam.ToInt32() >> 16))
If (pos.X <= (ClientRectangle.Left + borderWidth)) AndAlso (pos.Y <= (ClientRectangle.Top + borderWidth)) Then
m.Result = New IntPtr(13)
Else
If (pos.X >= (ClientRectangle.Right - borderWidth)) AndAlso (pos.Y <= (ClientRectangle.Top + borderWidth)) Then
m.Result = New IntPtr(14)
Else
If (pos.X <= (ClientRectangle.Left + borderWidth)) AndAlso (pos.Y >= (ClientRectangle.Bottom - borderWidth)) Then
m.Result = New IntPtr(16)
Else
If (pos.X >= (ClientRectangle.Right - borderWidth)) AndAlso (pos.Y >= (ClientRectangle.Bottom - borderWidth)) Then
m.Result = New IntPtr(17)
Else
If pos.X <= (ClientRectangle.Left + borderWidth) Then
m.Result = New IntPtr(10)
Else
If pos.Y <= (ClientRectangle.Top + borderWidth) Then
m.Result = New IntPtr(12)
Else
If pos.X >= (ClientRectangle.Right - borderWidth) Then
m.Result = New IntPtr(11)
Else
If pos.Y >= (ClientRectangle.Bottom - borderWidth) Then
m.Result = New IntPtr(15)
Else
m.Result = (If(CanMove, New IntPtr(2), IntPtr.Zero))
End If
End If
End If
End If
End If
End If
End If
End If
End If
Catch ex As Exception
Throw ex
End Try
End Sub
Private Sub FrameControl_MouseDown(sender As Object, e As MouseEventArgs) Handles MyBase.MouseDown
Try
isMouseDown = True
If DrawArrow Then
m_StartX = e.X
m_StartY = e.Y
m_CurX = e.X
m_CurY = e.Y
ElseIf DrawRect Then
drawingRects.Add(New DrawingRectangle() With {
.Location = e.Location,
.Size = Size.Empty,
.StartPosition = e.Location,
.DrawingcRectColor = DrawingcColor,
.PenRectSize = PenSize
})
ElseIf DrawHand Then
linefreehand.StartFigure()
End If
Catch ex As Exception
Throw ex
End Try
'.Owner = CType(sender, Control),
End Sub
Private Sub FrameControl_MouseMove(sender As Object, e As MouseEventArgs) Handles MyBase.MouseMove
Try
If isMouseDown Then
If DrawArrow Then
m_CurX = e.X
m_CurY = e.Y
ElseIf DrawRect Then
Dim dr = drawingRects(drawingRects.Count - 1)
If e.Y < dr.StartPosition.Y Then
dr.Location = New Point(dr.Rect.Location.X, e.Y)
End If
If e.X < dr.StartPosition.X Then
dr.Location = New Point(e.X, dr.Rect.Location.Y)
End If
dr.Size = New Size(Math.Abs(dr.StartPosition.X - e.X), Math.Abs(dr.StartPosition.Y - e.Y))
ElseIf DrawHand Then
linefreehand.AddLine(e.X, e.Y, e.X, e.Y)
End If
Me.Invalidate()
End If
Catch ex As Exception
Throw ex
End Try
End Sub
Private Sub FrameControl_Load(sender As Object, e As EventArgs) Handles MyBase.Load
UseDoubleBuffer()
End Sub
Private Sub FrameControl_MouseUp(sender As Object, e As MouseEventArgs) Handles MyBase.MouseUp
Try
isMouseDown = False
If DrawArrow Then
Dim DrawLine As New LineList
DrawLine.X1 = m_StartX
DrawLine.Y1 = m_StartY
DrawLine.X2 = m_CurX
DrawLine.Y2 = m_CurY
MyLineArrows.Add(DrawLine)
ElseIf DrawRect Then
Dim dr = drawingRects.Last()
End If
Catch ex As Exception
Throw ex
End Try
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Try
If CanMove Then
linefreehand.Reset()
drawingRects.Clear()
MyLineArrows.Clear()
Me.Invalidate()
End If
Catch ex As Exception
Throw ex
End Try
End Sub
Private Sub DrawShapes(ByVal g As Graphics)
Try
g.SmoothingMode = SmoothingMode.AntiAlias
g.SmoothingMode = SmoothingMode.HighQuality
g.SmoothingMode = SmoothingMode.HighSpeed
'DESENHO DO RETANGULO
For Each dr In drawingRects
Using pen As Pen = New Pen(DrawingcColor, PenSize)
g.DrawRectangle(pen, dr.Rect)
End Using
Next
'DESENHO DA FLECHA
Dim x1, y1, x2, y2 As Integer
Dim linecolor As Color = DrawingcColor
Dim linepen As Single = PenSize
For Each line In MyLineArrows
x1 = line.X1 : x2 = line.X2
y1 = line.Y1 : y2 = line.Y2
Using Arrow1 As New Pen(linecolor, linepen)
Arrow1.EndCap = LineCap.Custom
Arrow1.CustomEndCap = New AdjustableArrowCap(2, 2, False)
g.DrawLine(Arrow1, x1, y1, x2, y2)
End Using
Next
If isMouseDown Then
Using Arrow As Pen = New Pen(linecolor, linepen)
Arrow.EndCap = LineCap.Custom
Arrow.CustomEndCap = New AdjustableArrowCap(2, 2, False)
g.DrawLine(Arrow, m_StartX, m_StartY, m_CurX, m_CurY)
End Using
End If
'DESENHO FREEHAND
Using handpen As Pen = New Pen(linecolor, linepen)
g.DrawPath(handpen, linefreehand)
End Using
Catch ex As Exception
Throw ex
End Try
End Sub
Private Sub UseDoubleBuffer()
Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or
ControlStyles.UserPaint Or
ControlStyles.OptimizedDoubleBuffer, True)
End Sub
Private Function CreateRectSizableNode(ByVal x As Integer, ByVal y As Integer) As Rectangle
Return New Rectangle(x - sizeNodeRect / 2, y - sizeNodeRect / 2, sizeNodeRect, sizeNodeRect)
End Function
Private Function GetRect(ByVal p As PosSizableRect) As Rectangle
Select Case p
Case PosSizableRect.LeftUp
Return CreateRectSizableNode(ClientRectangle.X, ClientRectangle.Y)
Case PosSizableRect.LeftMiddle
Return CreateRectSizableNode(ClientRectangle.X, ClientRectangle.Y + +ClientRectangle.Height / 2)
Case PosSizableRect.LeftBottom
Return CreateRectSizableNode(ClientRectangle.X, ClientRectangle.Y + ClientRectangle.Height)
Case PosSizableRect.BottomMiddle
Return CreateRectSizableNode(ClientRectangle.X + ClientRectangle.Width / 2, ClientRectangle.Y + ClientRectangle.Height)
Case PosSizableRect.RightUp
Return CreateRectSizableNode(ClientRectangle.X + ClientRectangle.Width, ClientRectangle.Y)
Case PosSizableRect.RightBottom
Return CreateRectSizableNode(ClientRectangle.X + ClientRectangle.Width, ClientRectangle.Y + ClientRectangle.Height)
Case PosSizableRect.RightMiddle
Return CreateRectSizableNode(ClientRectangle.X + ClientRectangle.Width, ClientRectangle.Y + ClientRectangle.Height / 2)
Case PosSizableRect.UpMiddle
Return CreateRectSizableNode(ClientRectangle.X + ClientRectangle.Width / 2, ClientRectangle.Y)
Case Else
Return New Rectangle()
End Select
End Function
Private Function GetNodeSelectable(ByVal p As Point) As PosSizableRect
For Each r As PosSizableRect In [Enum].GetValues(GetType(PosSizableRect))
If GetRect(r).Contains(p) Then
Return r
End If
Next
Return PosSizableRect.None
End Function
End Class
我接受 C# 和 VB.net 中的建议
不是完整的答案,所以只是社区维基,但我想建议对用户控件的深层嵌套
If
/Else
部分(WndProc()
方法)进行改进:
Protected Overrides Sub WndProc(ByRef m As Message)
Dim borderWidth As Integer = 10
If m.Msg = WM_SETCURSOR AndAlso (m.LParam.ToInt32() And 65535) = 2 Then
Cursor.Current = If(CanMove, Cursors.SizeAll, Cursors.Arrow)
m.Result = CType(1, IntPtr)
Return
End If
If m.Msg = WM_MOUSEMOVE AndAlso (m.LParam.ToInt32() And 65535) = 2 Then
Cursor.Current = Cursors.Arrow
m.Result = CType(1, IntPtr)
Return
End If
If m.Msg = WM_NCLBUTTONDBLCLK Then
m.Result = CType(1, IntPtr)
Return
End If
MyBase.WndProc(m)
If m.Msg <> WM_NCHITTEST Then Return
Dim pos = PointToClient(New Point(m.LParam.ToInt32() And 65535, m.LParam.ToInt32() >> 16))
If pos.X <= (ClientRectangle.Left + borderWidth) AndAlso pos.Y <= (ClientRectangle.Top + borderWidth) Then
m.Result = New IntPtr(13)
Else If pos.X >= (ClientRectangle.Right - borderWidth) AndAlso pos.Y <= (ClientRectangle.Top + borderWidth) Then
m.Result = New IntPtr(14)
Else If pos.X <= (ClientRectangle.Left + borderWidth) AndAlso pos.Y >= (ClientRectangle.Bottom - borderWidth) Then
m.Result = New IntPtr(16)
Else If pos.X >= (ClientRectangle.Right - borderWidth) AndAlso pos.Y >= (ClientRectangle.Bottom - borderWidth) Then
m.Result = New IntPtr(17)
Else If pos.X <= (ClientRectangle.Left + borderWidth) Then
m.Result = New IntPtr(10)
Else If pos.Y <= (ClientRectangle.Top + borderWidth) Then
m.Result = New IntPtr(12)
Else If pos.X >= (ClientRectangle.Right - borderWidth) Then
m.Result = New IntPtr(11)
Else If pos.Y >= (ClientRectangle.Bottom - borderWidth) Then
m.Result = New IntPtr(15)
Else
m.Result = (If(CanMove, New IntPtr(2), IntPtr.Zero))
End If
End Sub
再次,正如评论中所指出的,原始方法中的
Try
/Catch
不仅没有帮助,而且主动让事情变得更糟,所以我只是将其删除。这也大大减少了嵌套/缩进,从而可以删除一堆End If
行,并使代码MUCH更易于理解。
我怀疑也可以将其转换为通过字典查找为
11
调用找到正确的整数值(从 17
到 IntPtr()
),这会更好。但由于有两组输入(X 与 Y),这可能比乍看起来更棘手。