Question

I have a winform window. When I change the size of the screen, the screen immediately increases or decreases.

I would prefer the Resize behavior of the window will be like Split Container, as long as I drag the mouse I see only line that marks what will be the window size, and only in leaving the Resize operation will be made.

I saw several examples that show that by hiding the frame of the window, and then by clicking on the window itself paint frame.

I want that by clicking on the frame of the window(I don't want to hide the frame) and not on the window.

Is there any way to do this? (May override the behavior of the Resize in any way).

Was it helpful?

Solution

I'm pretty sure that you can't find any solution on the Internet. However I've tried a demo for this and it works pretty well.

In winforms and many other UI technologies, you can't render something outside the window itself. To get the effect we want, we have to render some indicative border outside or inside the window depending on how user resizes it. So looks like we're stuck?

BUT there is a kind of technique to do that (I call it layer technique). We need a transparent, non-focused layer to render the indicative border. This layer will have its Size and Location synchronized with the Size and Location (with just a little offset) of the main window. The layer will also be invisible by default and only shows when user resizes and hides when ending resizing.

That's pretty OK with the technique I mentioned. However how to prevent/discard the default resizing when user resizes the window? It's luckily that Win32 supports 2 messages for this to be done easily:

  • WM_RESIZING : is sent to the window when user starts and keeps resizing. The LParam holds the RECT structure of the current window when resizing. We read this info to render the indicative border correctly. Then we need to modify this RECT to the current Bounds of the window to discard the default resizing effect (the size and location are changed immediately).
  • WM_EXITSIZEMOVE : is sent to the window when the resizing or moving ends. We need to catch this message to assign the Size and Location of the window based on the Size and Location of the transparent layer and of course hide the layer then.

Now the problem is totally solvable. Here is the demo code I've made. Note that there is a very nasty unsolvable and incomprehensible bug here, it happens when you resize the Top-Left corner, the Size is updated correctly after releasing mouse but the Location is set with an offset. I've debugging but no luck. At some point the Top and Left jumps to unexpected values for no clear reason. However, resizing by all the sides (left, top, right, bottom) and other corners is OK. In fact, resizing by the Top-Left corner is hardly done by user so this solution is acceptable, I think.

//Must add using System.Runtime.InteropServices;
public partial class Form1 : Form
{        
    public Form1()
    {
        InitializeComponent();
        //Sizing border initialization
        SizingBorderWidth = 3;
        SizingBorderStyle = DashStyle.Custom;
        SizingBorderColor = Color.Orange;
        //layer initialization
        layer.Owner = this;//especially this one.
        layer.Width = Width + SizingBorderWidth * 2;
        layer.Height = Height + SizingBorderWidth * 2;                         
        //Paint the border when sizing
        layer.Paint += (s, e) => {
            using (Pen p = new Pen(SizingBorderColor) { Width = SizingBorderWidth }) {
                if (Use3DSizingBorder) {
                    ControlPaint.DrawBorder3D(e.Graphics, sizingRect.Left, sizingRect.Top, sizingRect.Width, sizingRect.Height, Border3DStyle.Bump, Border3DSide.All);
                }
                else {
                    p.DashStyle = SizingBorderStyle;
                    p.LineJoin = LineJoin.Round;
                    if(p.DashStyle == DashStyle.Custom)
                       p.DashPattern = new float[] { 8f, 1f, 1f, 1f };//length of each dash from right to left
                    e.Graphics.DrawRectangle(p, sizingRect);
                }
            }
        };
        //Bind the Location of the main form and the layer form together
        LocationChanged += (s, e) => {
            Point p = Location;
            p.Offset(-SizingBorderWidth, -SizingBorderWidth);
            layer.Location = p;
        };
        //Set the intial Location of layer
        Load += (s, e) =>{                
            Point p = Location;
            p.Offset(-SizingBorderWidth, -SizingBorderWidth);
            layer.Location = p;
        };            
    }
    //Set this to true to use 3D indicative/preview border
    public bool Use3DSizingBorder { get; set; }
    //Change the indicative/preview border thickness
    public int SizingBorderWidth { get; set; }
    //Change the indicative/preview border style
    public DashStyle SizingBorderStyle { get; set; }
    //Change the indicative/preview border color
    public Color SizingBorderColor { get; set; }
    //hold the current sizing Rectangle
    Rectangle sizingRect;
    bool startSizing;
    bool suppressSizing;
    //This is a Win32 RECT struct (don't use Rectangle)
    public struct RECT
    {
        public int left, top, right, bottom;
    }
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x214&&!suppressSizing)//WM_SIZING = 0x214
        {                
            RECT rect = (RECT) m.GetLParam(typeof(RECT));
            int w = rect.right - rect.left;
            int h = rect.bottom - rect.top;
            sizingRect = new Rectangle() {X = SizingBorderWidth/2, Y = SizingBorderWidth/2, 
                                          Width = w, Height = h};
            layer.Left = rect.left-SizingBorderWidth;
            layer.Top = rect.top-SizingBorderWidth;
            layer.Width = w+2*SizingBorderWidth;
            layer.Height = h+2*SizingBorderWidth;
            if (!startSizing)
            {
                layer.Show();
                startSizing = true;
            }
            layer.Invalidate();
            //Keep the current position and size fixed
            rect.right = Right;
            rect.bottom = Bottom;
            rect.top = Top;
            rect.left = Left;
            //---------------------------
            Marshal.StructureToPtr(rect, m.LParam, true);
        }
        if (m.Msg == 0x232)//WM_EXITSIZEMOVE = 0x232
        {
            layer.Visible = false;
            BeginInvoke((Action)(() => {
                suppressSizing = true;
                Left = layer.Left + SizingBorderWidth;
                Top = layer.Top + SizingBorderWidth;
                Width = layer.Width - 2 * SizingBorderWidth;
                Height = layer.Height - SizingBorderWidth * 2;
                suppressSizing = false;
            }));
            startSizing = false;
        }
        base.WndProc(ref m);            
    }
    //Here is the layer I mentioned before.
    NoActivationForm layer = new NoActivationForm();
}    
public class NoActivationForm : Form {
    public NoActivationForm() {
        //The following initialization is very important
        TransparencyKey = BackColor;
        FormBorderStyle = FormBorderStyle.None;
        ShowInTaskbar = false;
        StartPosition = FormStartPosition.Manual;            
        //----------------------------------------------                          
    }
    protected override bool ShowWithoutActivation {
        get { return true; }
    }
}

Some screen shots:

enter image description here enter image description here enter image description here enter image description here

EDIT: (This edit was suggested by Hodaya Shalom, the OP (weird :)

I found a solution to the left corner problem :

before the BeginInvoke I save the variables and in the invoke I put the local variable:

int _top = layer.Top + SizingBorderWidth;
int _left = layer.Left + SizingBorderWidth;
int _width = layer.Width - 2 * SizingBorderWidth;
int _height = layer.Height - SizingBorderWidth * 2;
BeginInvoke((Action)(() => {
    suppressSizing = true;
    Left = _left;
    Top = _top;
    Width =_width;
    Height =_height;
    suppressSizing = false;
}));
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top