Переопределить поведение изменения размера окна winform

У меня есть окно winform. Когда я меняю размер экрана, экран сразу увеличивается или уменьшается.

Я бы предпочел, чтобы поведение Resize окна было похоже на Split Container, пока я перетаскиваю мышь, я вижу только строку, которая отмечает размер окна, и только при выходе из операции Resize будет выполняться.

Я видел несколько примеров, которые показывают, что, скрывая рамку окна, а затем нажимая на само окно, рисует рамку.

Я хочу это, нажав на рамку окна (я не хочу скрывать рамку), а не на окне.

Есть какой-либо способ сделать это? (Может изменить поведение Resize любым способом).

1 ответ

Решение

Я уверен, что вы не можете найти никакого решения в Интернете. Однако я попробовал демо для этого, и это работает довольно хорошо.

В winforms и многие другие технологии пользовательского интерфейса, вы не можете визуализировать что-либо за пределами самого окна. Чтобы получить желаемый эффект, мы должны визуализировать некоторую ориентировочную рамку снаружи или внутри окна, в зависимости от того, как пользователь ее изменяет. Похоже, мы застряли?

НО есть своего рода техника для этого (я называю это техникой слоя). Нам нужен прозрачный, не сфокусированный слой для визуализации ориентировочной границы. Этот слой будет иметь свой Size а также Location синхронизируется с Size а также Location (с небольшим смещением) главного окна. Слой также будет invisible по умолчанию и отображается только при изменении размера пользователя и скрывается при завершении изменения размера.

Это довольно хорошо с техникой, которую я упомянул. Однако как предотвратить / отменить изменение размера по умолчанию, когда пользователь изменяет размер окна? К счастью, Win32 поддерживает 2 сообщения, чтобы сделать это легко:

  • WM_RESIZING: отправляется в окно, когда пользователь запускает и продолжает изменять размер. LParam держит RECT структура текущего окна при изменении размера. Мы читаем эту информацию, чтобы правильно отобразить ориентировочную границу. Тогда нам нужно изменить это RECT к текущему Bounds окна, чтобы отменить эффект изменения размера по умолчанию (размер и местоположение изменяются немедленно).
  • WM_EXITSIZEMOVE: отправляется окну, когда заканчивается изменение размера или перемещение. Нам нужно поймать это сообщение, чтобы назначить Size а также Location окна на основе Size а также Location прозрачного слоя и, конечно, скрыть слой тогда.

Теперь проблема полностью решаема. Вот демонстрационный код, который я сделал. Обратите внимание, что здесь есть очень неприятная неразрешимая и непонятная ошибка, это происходит, когда вы изменяете размер Top-Left угол Size обновляется правильно после отпускания мыши, но Location устанавливается со смещением. Я отлаживаю, но не повезло. В какой-то момент Top а также Left переходит к неожиданным значениям без явной причины. Тем не менее, изменение размера по всем сторонам (слева, сверху, справа, снизу) и другим углам в порядке. На самом деле, изменение размера с помощью Top-Left corner вряд ли это сделано пользователем, так что это решение приемлемо, я думаю.

//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 ОП (странно:)

Я нашел решение проблемы левого угла:

перед BeginInvoke я сохраняю переменные и в вызове помещаю локальную переменную:

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;
}));
Другие вопросы по тегам