在 C# 或 VB.NET 中将窗体区域设置为其子控件的边界?

Set the form region to the bounds of its child controls in C# or VB.NET?

我开始创建一个通用的 usage/reusable 方法,该方法将有助于将窗体的区域设置为其子控件的边界。

但是我发现一个问题,当一个控件的矩形与另一个控件相交时,我的意思是 Z-Order,当一个控件在另一个控件前面并在背景中覆盖另一个控件的一部分时,在这些情况下前面控件的矩形没有画好...

参见:

...其中 Button2 在 Button1 前面。

可能是我使用 GraphicsPath class 绘制区域路径的错误,因为我没有以这种方式使用 GDI+ 的经验,也许我写得不好路径...

如何修复此代码以设置预期区域?

这是代码。在使用之前,将 FormBorderStyle 属性 设置为 None (无边框形式)。

VB.NET:

Public Shared Sub LockFormRegionToControls(ByVal f As Form)
    LockFormRegionToControls(Of Control)(f)
End Sub

Public Shared Sub LokckFormRegionToControls(Of T As Control)(ByVal f As Form)

    Dim rects As Rectangle() =
        (From ctrl As T In f.Controls.OfType(Of T)
         Order By f.Controls.GetChildIndex(ctrl) Ascending
         Select ctrl.Bounds).ToArray()

    Using path As New GraphicsPath()
        path.AddRectangles(rects)
        f.Region = New Region(path)
    End Using

End Sub

C#:

public static void LockFormRegionToControls(Form f) {
    LockFormRegionToControls<Control>(f);
}

public static void LockFormRegionToControls<T>(Form f) where T : Control {

    Rectangle[] rects = (
        from T ctrl in f.Controls.OfType<T>()
        orderby f.Controls.GetChildIndex(ctrl) ascending
        select ctrl.Bounds).ToArray();

    using (GraphicsPath path = new GraphicsPath()) {
        path.AddRectangles(rects);
        f.Region = new Region(path);
    }

}

对于 border-less 表单,它非常简单(没有到客户区的偏移)。只需在控制范围内从空白区域和并集开始。

Public Shared Sub LockFormRegionToControls(ByVal f As Form)
    Dim r As New Region()
    r.MakeEmpty()
    For Each c As Control In f.Controls
        Using r2 As New Region(c.Bounds)
            r.Union(r2)
        End Using
    Next
    Dim oldRegion As Region = f.Region
    f.Region = r
    If oldRegion IsNot Nothing Then oldRegion.Dispose()
End Sub

编辑:处理 non-client 带边框表单区域的方法。

Public Shared Sub LockFormRegionToControls(ByVal f As Form)
    ' determine offset to client rectangle
    Dim zero As Point = f.PointToScreen(Point.Empty) ' top-left of client rectangle in screen coordinates
    Dim offsetX As Int32 = zero.X - f.Location.X
    Dim offsetY As Int32 = zero.Y - f.Location.Y

    ' region for entire form including non-client
    Dim r As New Region(New Rectangle(0, 0, f.Width, f.Height))

    Dim clientRect As Rectangle = f.ClientRectangle
    ' this rect is located at 0,0 so apply the offset
    clientRect.Offset(offsetX, offsetY)

    ' subtract the client rectangle
    r.Exclude(clientRect)

    ' now add in the control bounds
    For Each c As Control In f.Controls
        Dim b As Rectangle = c.Bounds
        ' controlBounds are relative to the client rectangle, so need to offset
        b.Offset(offsetX, offsetY)
        Using r2 As New Region(b)
            r.Union(r2)
        End Using
    Next

    Dim oldRegion As Region = f.Region
    f.Region = r
    If oldRegion IsNot Nothing Then oldRegion.Dispose()
End Sub

编辑 2:细边框调整。

Public Shared Sub LockFormRegionToControls(ByVal f As Form)
    ' determine offset to client rectangle
    Dim zero As Point = f.PointToScreen(Point.Empty) ' top-left of client rectangle in screen coordinates
    Dim offsetX As Int32 = zero.X - f.Location.X
    Dim offsetY As Int32 = zero.Y - f.Location.Y

    ' simulate thin border
    Dim occludedBorderOffset As Int32 = Math.Max(offsetX - 2, 0)
    Dim whAdjustment As Int32 = 2 * occludedBorderOffset

    ' region for entire form including non-client
    Dim mainRect As New Rectangle(occludedBorderOffset, occludedBorderOffset, f.Width - whAdjustment, f.Height - whAdjustment)

    Dim r As New Region(mainRect)

    Dim clientRect As Rectangle = f.ClientRectangle
    ' this rect is located at 0,0 so apply the offset
    clientRect.Offset(offsetX, offsetY)

    ' subtract the client rectangle
    r.Exclude(clientRect)

    ' now add in the control bounds
    For Each c As Control In f.Controls
        Dim b As Rectangle = c.Bounds
        ' ontrolBounds are relative to the client rectangle, so need to offset
        b.Offset(offsetX, offsetY)
        Using r2 As New Region(b)
            r.Union(r2)
        End Using
    Next

    Dim oldRegion As Region = f.Region
    f.Region = r
    If oldRegion IsNot Nothing Then oldRegion.Dispose()
End Sub

这只是 TnTinMn 编码方法的 C# 实现。
(由于此处显示了 C# 和 VB.Net 标记,因此可能会有用)。

调用LockFormRegionToControls(TestForm, [IsBorderless]);判断真假。

//The Form is not Borderless
LockFormRegionToControls(TestForm, false);


    public static void LockFormRegionToControls(Form f, bool IsBorderless) {
            LockBLFormRegionToControls<Control>(f, IsBorderless);
    }

    public static void LockBLFormRegionToControls<T>(Form f, bool Borderless) where T : Control
    {
        Region NewRegion;
        Point OffSet = Point.Empty;

        if (Borderless)
        {
            NewRegion = new Region();
        } else {
            OffSet = new Point((f.Bounds.Width - f.ClientSize.Width) / 2, f.Bounds.Height - f.ClientSize.Height);
            NewRegion = new Region(f.Bounds);
        }

        foreach (T ctrl in f.Controls.OfType<T>()) {
            Point p = new Point(ctrl.Bounds.Left + OffSet.X, ctrl.Bounds.Y + (OffSet.Y - OffSet.X));
            Size s = new Size(ctrl.Bounds.Width, ctrl.Bounds.Height);
            NewRegion.Union(new Region(new Rectangle(p, s)));
        }

        f.Region = NewRegion;
    }