ElektroStudios
ElektroStudios

Reputation: 20464

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

I started to create a generic usage/reusable method that will help to set the region of a Form to the bounds of its child controls.

But I found a problem when the rectangle of a control intersects with another, I mean the Z-Order, when a control is in front of other and covers a part of the other control in the background, in these circunstances the rectangle of the control in the front is not properly drawn...

See:

enter image description here

...where Button2 is in front of Button1.

Probably is my fault with the usage of the GraphicsPath class to draw the region path, because I'm not experienced using GDI+ in this way, and maybe I'm writting bad the path...

How can I fix this code to set the expected region?.

Here is the code. Before use it, set the FormBorderStyle property to None (a borderless form).

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);
    }

}

Upvotes: 0

Views: 789

Answers (2)

Jimi
Jimi

Reputation: 32223

This is just a C# implementation of the method TnTinMn coded.
(Since both C# and VB.Net tags are shown here, it may be useful).

Call LockFormRegionToControls(TestForm, [IsBorderless]); with true or false.

//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;
    }

Upvotes: 1

TnTinMn
TnTinMn

Reputation: 11791

For a border-less form it is pretty simple (no offset to client area). Just start with an empty region and union in the control bounds.

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

Edit: Method to handle non-client areas of form with border.

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

Edit 2: Thin border adjustment.

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

Upvotes: 1

Related Questions