0

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

}
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • I can not see what stops you from using `Color.Magena` as `BackColor` and `TransparencyKey`. This way the area around controls will be transparent and will be deleted from region of the form and will not receive clicks, while the controls will remain clickable. Take a look at this post: [Winforms mouse transparent form with clickable controls in it](https://stackoverflow.com/q/32920402/3110834). – Reza Aghaei Feb 22 '18 at 03:18

2 Answers2

1

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
TnTinMn
  • 11,522
  • 3
  • 18
  • 39
  • Thank you. The problem now is if I add the bounds of the titlebar too. For example in a Sizeable normal form, I know the titlebar bounds (including the border size) so I adjust the X and Y properties of each control rectangle bounds, and the resulting region with all the unions made are drawn perfect, however,if with all the rectangle offsets adjusted I also add a new region union for the titlebar bounds,then the region moves a little to the left in all the controls,I don't know why. Maybe you could guide me with a hint of what Im doing wrong, without the need to publish another question?. – ElektroStudios Feb 21 '18 at 23:36
  • Yes I didn't wanted to make more complicated/boring to read my question, for that reason I only specified borderless form, on which is simpler to reproduce the code. Please see this if you would, to understand what I mean (it is a sizeable form): https://i.imgur.com/0wmk8Za.png + https://i.imgur.com/7F0io6f.png – ElektroStudios Feb 21 '18 at 23:44
  • It works except for the right and bottom edges borders of the form, if you put a control near the right edge, the region will include part of the border. I think 'clientRect' width and height needs some calc readjustment before excluding it in the region. But don't worry don't do more, I can't ask you for more, many thanks for all your help! with that code maybe I can follow tomorrow and fix the issue with 'clientRect'. – ElektroStudios Feb 22 '18 at 00:22
  • 1
    @ElektroStudios, don't worry about asking things. You, unlike many people these days, appreciate the help people offer and that is all I can expect. It may appear to be off on a Win 8+ system due to way form borders are draw with a thin line now. When you apply a region, the full border thickness shows. I seem to recall that there is some system metric you can query to get that info, but it eludes me at this moment. I updated the code to simulate a thin border (2 px) its just simple math. – TnTinMn Feb 22 '18 at 01:01
1

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;
    }
Jimi
  • 29,621
  • 8
  • 43
  • 61