C# 覆盖winform窗口的调整大小行为

C# 覆盖winform窗口的调整大小行为,c#,winforms,forms,resize,splitcontainer,C#,Winforms,Forms,Resize,Splitcontainer,我有一个winform窗口。当我改变屏幕大小时,屏幕会立即增大或减小 我更喜欢窗口的调整大小行为将像拆分容器一样,只要我拖动鼠标,我只看到标记窗口大小的线条,并且只有在离开时才会进行调整大小操作 我看到了几个例子,通过隐藏窗口的框架,然后单击窗口本身来绘制框架 我想通过点击窗口的框架来实现这一点,我不想隐藏框架,而不是窗口 有没有办法做到这一点?可能会以任何方式覆盖调整大小的行为。我很确定您在Internet上找不到任何解决方案。然而,我已经尝试了一个演示,它的工作非常好 在winforms和许

我有一个winform窗口。当我改变屏幕大小时,屏幕会立即增大或减小

我更喜欢窗口的调整大小行为将像拆分容器一样,只要我拖动鼠标,我只看到标记窗口大小的线条,并且只有在离开时才会进行调整大小操作

我看到了几个例子,通过隐藏窗口的框架,然后单击窗口本身来绘制框架

我想通过点击窗口的框架来实现这一点,我不想隐藏框架,而不是窗口


有没有办法做到这一点?可能会以任何方式覆盖调整大小的行为。

我很确定您在Internet上找不到任何解决方案。然而,我已经尝试了一个演示,它的工作非常好

在winforms和许多其他UI技术中,您不能在窗口本身之外呈现某些内容。为了获得我们想要的效果,我们必须根据用户调整大小的方式在窗口外部或内部呈现一些指示边框。看来我们被卡住了

但是有一种技术,我称之为层技术。我们需要一个透明的非聚焦层来渲染指示边界。该层的大小和位置将与主窗口的大小和位置同步,只需稍微偏移主窗口。默认情况下,该层也将不可见,仅在用户调整大小时显示,并在结束调整大小时隐藏

对于我提到的技术来说,这很好。但是,当用户调整窗口大小时,如何防止/放弃默认的大小调整?幸运的是,Win32支持2条消息,以便轻松完成此操作:

:在用户启动并不断调整大小时发送到窗口。在调整大小时,LPRAM保存当前窗口的RECT结构。我们阅读此信息以正确呈现指示边框。然后我们需要将这个矩形修改为窗口的当前边界,以放弃默认的大小调整效果,大小和位置会立即更改。 :在调整大小或移动结束时发送到窗口。我们需要捕捉这个消息,根据透明层的大小和位置分配窗口的大小和位置,当然,然后隐藏该层。 现在问题完全可以解决了。这是我制作的演示代码。请注意,这里有一个非常严重的无法解决和无法理解的错误,它发生在您调整左上角的大小时,释放鼠标后大小会正确更新,但位置设置为偏移。我很幸运,但运气不好。在某个点上,顶部和左侧会毫无原因地跳转到意外值。但是,通过所有边(左、上、右、下和其他角)调整大小是可以的。事实上,用户很难通过左上角调整大小,所以我认为这种解决方案是可以接受的

//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; }
    }
}
一些屏幕截图:

编辑:这个编辑是由Hodaya Shalom建议的,他是一个古怪的人:

我找到了解决左角问题的方法:

在开始调用之前,我保存变量,并在调用中放入局部变量:

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

我很确定你在互联网上找不到任何解决方案。然而,我已经尝试了一个演示,它的工作非常好

在winforms和许多其他UI技术中,您不能在窗口本身之外呈现某些内容。为了获得我们想要的效果,我们必须根据用户调整大小的方式在窗口外部或内部呈现一些指示边框。看来我们被卡住了

但是有一种技术,我称之为层技术。我们需要一个透明的非聚焦层来渲染指示边界。该层的大小和位置将与主窗口的大小和位置同步,只需稍微偏移主窗口。默认情况下,该层也将不可见,仅在用户调整大小时显示,并在结束调整大小时隐藏

对于我提到的技术来说,这很好。但是,当用户调整窗口大小时,如何防止/放弃默认的大小调整?幸运的是,Win32支持2条消息,以便轻松完成此操作:

:在用户启动并不断调整大小时发送到窗口。在调整大小时,LPRAM保存当前窗口的RECT结构。我们阅读此信息以正确呈现指示边框。然后我们需要将这个矩形修改为窗口的当前边界,以放弃默认的大小调整效果,大小和位置会立即更改。 :在调整大小或移动结束时发送到窗口。我们需要捕捉这个消息,根据透明层的大小和位置分配窗口的大小和位置,当然,然后隐藏该层。 现在问题完全可以解决了。这是我制作的演示代码。请注意,这里有一个非常严重的无法解决和无法理解的错误,它发生在您调整左上角的大小时,释放鼠标后大小会正确更新,但位置设置为偏移。我 我很幸运,但运气不好。在某个点上,顶部和左侧会毫无原因地跳转到意外值。但是,通过所有边(左、上、右、下和其他角)调整大小是可以的。事实上,用户很难通过左上角调整大小,所以我认为这种解决方案是可以接受的

//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; }
    }
}
一些屏幕截图:

编辑:这个编辑是由Hodaya Shalom建议的,他是一个古怪的人:

我找到了解决左角问题的方法:

在开始调用之前,我保存变量,并在调用中放入局部变量:

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

@HodayaShalom感谢您发布此建议。它确实有效。不过,我不认为这是因为使用了BeginInvoke,我使用了BeginInvoke,因为我认为我们需要尽快执行窗口更新。我们不需要使用BeginInvoke,如果这是因为BeginInvoke,那么删除BeginInvoke可以解决问题,但它不能。我认为直接指定Left和Top可能会导致问题,因此将值保存到一些局部变量中可以解决问题。至少多亏了你,我知道了这种奇怪的行为,并将在同样的情况下应用它。@HodayaShalom感谢你的建议。它确实有效。不过,我不认为这是因为使用了BeginInvoke,我使用了BeginInvoke,因为我认为我们需要尽快执行窗口更新。我们不需要使用BeginInvoke,如果这是因为BeginInvoke,那么删除BeginInvoke可以解决问题,但它不能。我认为直接指定Left和Top可能会导致问题,因此将值保存到一些局部变量中可以解决问题。至少多亏了你,我知道这种奇怪的行为,并将在同样的情况下应用它。