Reputation: 4417
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).
Upvotes: 1
Views: 2292
Reputation: 63327
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:
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).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:
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;
}));
Upvotes: 5