Mattias Mörner
Mattias Mörner

Reputation: 3

C# WinForms - Custom borders of a programmatically created form only shows left and top border

trying to learn programming and been experimenting somewhat. Ran into a little problem that I haven't been able to find a solution for. I have a programmatically created button and a click event that programmatically creates a new form. The form has some created labels, as well as a custom border. The same custom border is used on the main form and works even with auto size on and dynamically created content that changes the size of it. But on this programmatically created form the border only shows on the left and top sides, and I haven't been able to find an answer or solve it by experimentation.

Not sure how much code that is enough, but I made a simplified program showing the problem. It was simplified from the code of my program which uses hundreds of programmatically created buttons and several arrays of information which fills the labels used on the myForm form, so some things might seem strange since I removed the arrays and button creation process.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace StackoverflowSample
{
public partial class Form1 : Form
{
    private Button myButton;

    /// <summary>
    /// Borrowed code snippet for custom border: http://stackoverflow.com/q/5092216. Would have preferred no to do dllimport yet, but will let it pass this time.
    /// </summary>
    /// <param name="nLeftRect"></param>
    /// <param name="nTopRect"></param>
    /// <param name="nRightRect"></param>
    /// <param name="nBottomRect"></param>
    /// <param name="nWidthEllipse"></param>
    /// <param name="nHeightEllipse"></param>
    /// <returns></returns>
    [DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
    private static extern IntPtr CreateRoundRectRgn
    (
        int nLeftRect, // x-coordinate of upper-left corner
        int nTopRect, // y-coordinate of upper-left corner
        int nRightRect, // x-coordinate of lower-right corner
        int nBottomRect, // y-coordinate of lower-right corner
        int nWidthEllipse, // height of ellipse
        int nHeightEllipse // width of ellipse
     );

    public Form1()
    {
        InitializeComponent();

        this.BackColor = Color.White;
        this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;

        Region = System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, 0, 0, 0, 0)); // Since our form is dynamically created and/or filled and auto sized, set starting values here to 0.
        Region.MakeInfinite(); // This one was my own, to solve size problems with my dynamically created forms auto size, further reading required for other ways to do it.
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        Font font = new System.Drawing.Font("Meiryo UI", 16.0f);

        myButton = new Button();
        myButton.Name = "tstButton";
        myButton.Size = new Size(50, 50);
        myButton.Location = new Point(10, 10);
        myButton.Font = font;
        myButton.Text = "Test Button";
        myButton.Click += (sender2, e2) => { ButtonClick(sender, e); };

        this.Controls.Add(myButton);
    }

    private void ButtonClick(object sender, EventArgs e)
    {
        Font font = new System.Drawing.Font("Meiryo UI", 16.0f);

        Button myButton = (sender as Button);

        Form myForm = new Form();
        Label row1Label = new Label();
        Label row2Label = new Label();
        Label row3Label = new Label();
        Label row4Label = new Label();
        Label row5Label = new Label();

        row2Label.AutoSize = true;
        row2Label.Text = "Row 1: ";
        row2Label.Font = font;
        row2Label.Location = new Point(5, 5);

        row1Label.AutoSize = true;
        row1Label.Text = "Row 2: ";
        row1Label.Font = font;
        row1Label.Location = new Point(5, 35);

        row3Label.AutoSize = true;
        row3Label.Text = "Row 3: ";
        row3Label.Font = font;
        row3Label.Location = new Point(5, 65);

        row4Label.AutoSize = true;
        row4Label.Text = "Row 4: ";
        row4Label.Font = font;
        row4Label.Location = new Point(5, 95);

        row5Label.AutoSize = true;
        row5Label.Text = "Row 5: ";
        row5Label.Font = font;
        row5Label.Location = new Point(5, 125);

        myForm.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
        myForm.AutoSize = true;
        myForm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;

        myForm.Controls.Add(row1Label);
        myForm.Controls.Add(row2Label);
        myForm.Controls.Add(row3Label);
        myForm.Controls.Add(row4Label);
        myForm.Controls.Add(row5Label);

        myForm.Font = font;
        myForm.BackColor = Color.White;
        myForm.Padding = new System.Windows.Forms.Padding(10, 10, 10, 10);
        myForm.Show();
        myForm.Paint += (sender4, e4) => { Form1_Paint(sender4, e4); }; // Hijacking the borrowed code for custom borders again for the info boxes.
        myForm.Location = new Point(Cursor.Position.X + 25, Cursor.Position.Y - 100); // Create info box a little to the right and up from the cursors position.
        myForm.LostFocus += (sender3, e3) => { CloseForm(sender3, e3, myForm); }; // If the info box loses focus, for example by clicking another button, close that box (form)
        myForm.MouseEnter += (sender3, e3) => { CloseForm(sender3, e3, myForm); }; // Also, if the mouse enters the box, also close, so we can show the buttons under it.
    }

    /// <summary>
    /// Event for closing my "custom" info boxes. Runs at LostFocus or MouseEnter events. So, clicking another button or entering the info box will close it.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// <param name="myForm">Gets the relevant form that the event will run on. Solved my problem of targeting dynamically created forms from outside its scope.</param>
    private void CloseForm(object sender, EventArgs e, Form myForm)
    {
        myForm.Close();
    }

    /// <summary>
    /// Paint event. Holds some borrowed code for getting custom border. Works so far, but further reading required.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        // Borrowed code snippet for custom border: http://stackoverflow.com/q/5092216
        ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle,
            Color.LightBlue, 5, ButtonBorderStyle.Solid,
            Color.LightBlue, 5, ButtonBorderStyle.Solid,
            Color.LightBlue, 5, ButtonBorderStyle.Solid,
            Color.LightBlue, 5, ButtonBorderStyle.Solid);
    }
}
}

I am a beginner at programming so be somewhat forgiving and show the right way instead of condemning any bad coding.

Kind Regards,

Mattias

Upvotes: 0

Views: 9883

Answers (2)

FrankX
FrankX

Reputation: 177

The davidovic's solution will have a side effect, when there is control dock left/right/top/down, you can't paint in this area, for you are using "ClientRectangle" which is override by the control.

There are two normal solutions of repainting border:

  1. put another form under the form, but will have form dispose issue, may lead to out of memory

  2. catch the nonclient area windows message, repaint the border. But it is very tricky, and may lead to many problems, for example, when form is minimized, when form is resize, some form area is hidden, and you should handle this event.

If you want to use the method 2, I suggest you to take the Lizard.dll for reference, it still has bugs but much stable than other kinds of library.

Upvotes: 0

Nikola Davidovic
Nikola Davidovic

Reputation: 8656

The problem is that you are using the same event handler for both forms. If you want to continue that way you should change the size of the ClientRectangle according to the form for which the Pain event handler has been called (now you are always using the ClientSize of the main form). Change the code of Form1_Paint event handler to this:

private void Form1_Paint(object sender, PaintEventArgs e)
{
        Form frm = (Form)sender;
        ControlPaint.DrawBorder(e.Graphics, frm.ClientRectangle,
        Color.LightBlue, 5, ButtonBorderStyle.Solid,
        Color.LightBlue, 5, ButtonBorderStyle.Solid,
        Color.LightBlue, 5, ButtonBorderStyle.Solid,
        Color.LightBlue, 5, ButtonBorderStyle.Solid);
}

In your previous code, you were passing the ClientRectangle of the main form in cases of the child form redrawing. After you change the code, the DrawBorder method will get the proper ClientRectangle for the form for which it has been called.

Upvotes: 3

Related Questions