将窗体限制在屏幕的工作区域,不闪烁

Restrain form to the working area of the screen without flickering

我有工作代码,但是当将表单移动到屏幕边界时,我有时会出现表单闪烁。不好看。

有更好的解决方案吗?

Private Sub Form1_Move(sender As Object, e As EventArgs) Handles Me.Move
    Dim p As Point
    p = Me.Location
    Dim screenWidth As Integer = Screen.PrimaryScreen.Bounds.Width
    Dim screenHeight As Integer = Screen.PrimaryScreen.Bounds.Height
    Dim TaskBarH As Integer = screenHeight - Screen.PrimaryScreen.WorkingArea.Height

    If p.X < 0 Then
        Me.Location = New System.Drawing.Point(0, p.Y)
    ElseIf p.X > screenWidth - Me.Size.Width Then
        Me.Location = New System.Drawing.Point(screenWidth - Me.Size.Width, p.Y)
    End If

    If p.Y < 0 Then
        Me.Location = New System.Drawing.Point(p.X, 0)
    ElseIf p.Y > screenHeight - Me.Size.Height - TaskBarH Then
        Me.Location = New System.Drawing.Point(p.X, screenHeight - Me.Size.Height - TaskBarH)
    End If

End Sub

表单在移动时闪烁的原因是每次在不同位置重新绘制表单时,都会触发 Move 事件。要查看多少,试试这个:

Option Strict On
Option Explicit On

Public Class Form1
    Dim counter As Int64
    Private Sub Form1_Move(sender As Object, e As EventArgs) Handles Me.Move
        counter += 1
        Console.WriteLine(counter)
    End Sub
End Class

您会注意到,只要表单移动了一个像素,事件就会被触发。您将窗体向左移动 100 个像素,该事件将触发 100 次。在每次重绘表单时检查位置,然后计算每个点是否在您想要的范围内,这需要大量工作。所有这些点和矩形等的持续计算导致巨大的开销,结果是没有足够的资源时间来重绘表单中的所有控件,因此表单闪烁。

更好的选择是在表单完成移动时只执行一次所有这些数学运算,如果表单超出屏幕边界则更正表单。有一个 Form.ResizeEnd 事件会在用户完成调整大小或移动表单时触发。通过利用这个事件,代码只会在用户完成他们的操作并且表单在屏幕上重新绘制后被调用一次。然后,如果表单在您想要的范围之外,您可以将表单移动到一个位置。

我没有对 Screen.PrimaryScreen 的使用进行硬编码,而是动态获取表单所在的屏幕。这允许用户将表单移动到多显示器环境中的不同屏幕。

Option Strict On
Option Explicit On

Public Class Form1
    Private Sub Form1_resizeEnd(sender As Object, e As EventArgs) Handles Me.ResizeEnd
        Dim screenArea As Rectangle = Screen.GetWorkingArea(Me.Location)
        Dim formArea As Rectangle = Me.DesktopBounds

        If Me.WindowState = FormWindowState.Normal AndAlso Not screenArea.Contains(formArea) Then
            If formArea.Top < screenArea.Top Then Me.Top = 0
            If formArea.Left < screenArea.Left Then Me.Left = 0
            If formArea.Right > screenArea.Right Then Me.Left = Me.Left - (formArea.Right - screenArea.Right)
            If formArea.Bottom > screenArea.Bottom Then Me.Top = Me.Top - (formArea.Bottom - screenArea.Bottom)
        End If
    End Sub
End Class

你可以试试像这样限制鼠标拖动表单的位置。

Option Strict On
Option Explicit On
Option Infer Off
Imports System.Runtime.InteropServices
Public Class Form1
    Public Declare Function SetCursorPos Lib "user32.dll" (X As Integer, Y As Integer) As Boolean
    Public Function AbsoluteFormMousePosition() As Point
        Dim mousePos As Point = PointToClient(MousePosition)
        Dim hBorderWidth As Integer = (Me.Width - Me.ClientRectangle.Width) \ 2
        Dim vBorderWidth As Integer = hBorderWidth
        Dim TitleHeight As Integer = (Me.Height - Me.ClientRectangle.Height) - vBorderWidth
        Dim newPos As New Point(mousePos.X + hBorderWidth, mousePos.Y + TitleHeight)
        Return newPos
    End Function
    Function TryOffest(range As Range, find As Integer, ByRef newValue As Integer) As Boolean
        If Not range.Contains(find) Then
            Dim diff As Integer
            diff = If(find > range.Upper, find - range.Upper, range.Lower - find)
            newValue += If(find > range.Upper, -(diff), diff)
            Return True
        End If
        Return False
    End Function
    Private Sub Form1_Move(sender As Object, e As EventArgs) Handles Me.Move
        Dim mousePos As Point = MousePosition
        Dim newX As Integer = mousePos.X
        Dim newY As Integer = mousePos.Y
        Dim vChanged, hChanged As Boolean
        Dim hBorder As Integer = Me.Width - Me.ClientRectangle.Width
        Dim vBorder As Integer = Me.Height - Me.ClientRectangle.Height
        Dim adjX As Integer = Me.PointToClient(mousePos).X
        Dim adjY As Integer = Me.PointToClient(mousePos).Y
        Dim titleMousePosition As Integer = hBorder - Me.PointToClient(mousePos).Y
        Dim verticalBounds As New Range(AbsoluteFormMousePosition.Y + 2, Screen.PrimaryScreen.WorkingArea.Height - (Me.Height - vBorder - adjY + 2))
        Dim horizontalBounds As New Range(adjX + 2, Screen.PrimaryScreen.WorkingArea.Width - (Me.Width - hBorder - adjX + 2))
        vChanged = TryOffest(verticalBounds, mousePos.Y, newY)
        hChanged = TryOffest(horizontalBounds, mousePos.X, newX)
        If vChanged OrElse hChanged Then SetCursorPos(newX, newY)
    End Sub
End Class
Public Class Range
    Public Lower, Upper As Integer
    Sub New(lower As Integer, upper As Integer)
        Me.Lower = lower : Me.Upper = upper
    End Sub
    Public Function Contains(number As Integer) As Boolean
        If number >= Lower AndAlso number <= Upper Then Return True Else Return False
    End Function
End Class