Я уверен, что вы не можете найти какое-либо решение в Интернете. Однако я пробовал демо для этого, и он работает довольно хорошо.
В 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; }
}
}
Некоторые снимки экрана:
EDIT: (Это изменение было предложено Hodaya Shalom
, ОП (странно :)
Я нашел решение левого угла м:
перед BeginInvoke я сохранить переменные и в Invoke я поставил локальную переменную:
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;
}));
OMG. Этот ответ так хорош. Спасибо –
@HodayaShalom благодарит за размещение предложения. Это действительно работает. Ну, однако я не думаю, что это из-за использования «BeginInvoke», я использовал «BeginInvoke», потому что я думаю, что нам нужно как можно скорее выполнить обновление окна. Нам не нужно использовать 'BeginInvoke', если это из-за' BeginInvoke', удаление 'BeginInvoke' решило бы проблему, но это не могло. Я думаю, что назначение 'Left' и' Top' ** напрямую ** может вызвать проблему, поэтому сохранение значений в некоторые локальные переменные решает проблему. По крайней мере, благодаря вам, я знаю об этом странном поведении и применяю его в тех же ситуациях. –