I want this:
Pretty much, my form is resizeable if BorderStyle is set to None and isMDIContainer = false;
But, how do I get my form resizeable if BordeStyle is set to None and isMDICOntainer = true?
https://gyazo.com/6fe87f127a3b2768c152e64d372593c1
This is an example. You can see the form is resizeable just fine. But as soon as the MDI comes in play, it doesn't work anymore.
Here is the current code:
private const int cCaption = 62;
private const int cGrip = 16;
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip, this.ClientSize.Height - cGrip, cGrip, cGrip);
ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
rc = new Rectangle(0, 0, this.ClientSize.Width, cCaption);
// e.Graphics.FillRectangle(Brushes.Blue, rc);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x84)
{ // Trap WM_NCHITTEST
Point pos = new Point(m.LParam.ToInt32());
pos = this.PointToClient(pos);
if (pos.Y < cCaption)
{
m.Result = (IntPtr)2; // HTCAPTION
return;
}
if (pos.X >= this.ClientSize.Width - cGrip && pos.Y >= this.ClientSize.Height - cGrip)
{
m.Result = (IntPtr)17; // HTBOTTOMRIGHT
return;
}
}
base.WndProc(ref m);
}
The explanation from Jimi was right. The OnPaint event only draws your Rectangle on the MainForm, while what you see is the MdiClient control. This control covers the background of the MainForm (like you set a panel control and set Dock = fill), so you cannot see and click the rectangle at bottom right to resize.
One way for you to be able to see and click on the rectangle for resizing is set the padding for MainForm, like this:
protected override void OnClientSizeChanged(EventArgs e)
{
if (this.WindowState != lastState || lastState == FormWindowState.Normal)
{
lastState = this.WindowState;
OnWindowStateChange(e);
}
base.OnClientSizeChanged(e);
}
private void OnWindowStateChange(EventArgs e)
{
if (WindowState == FormWindowState.Maximized)
{
Padding = new Padding(0);
}
else
{
Padding = new Padding(7);
}
}
So at normal window state (not fullscreen), the MdiClient will not cover all surface of mainform.
I find the color of this is not good. So you may want to change the background of the main form that fit the background of MdiClient, or use this approach to draw rectangles around your form for resizing like normal.
How to resize borderless form from all edges
Related
Recently I made (mostly out of curiosity) a borderless form. After making my own title bar which includes the title, and the three buttons(minimize, maximize and close), just like every normal Windows program. I also made the code for these buttons (just ask if you want to see the code).
However, I've noticed that there are no animations. What I mean is that, e.g. if I click the minimize button, there is no animation, the program instantly disappears (it doesn't close, the button works, but without an animation). This happens in all cases: When I open the program it instantly appears, when I close it, it instantly disappears.
Is there some sort of way to use these animations that standard Windows programs use?
It doesn't seem possible to have the animation effect on a borderless form. However, there are two possible workarounds.
Set the FormBorderStyle back to Sizable just before a Minimize or Restore, and then back to none aftewards.
Use the AnimateWindow function instead. The animations tend to happen where the window is currently located. The functions can be applied to any Control, not just top level windows.
Here is some sample code:
class FormA : Form {
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MINIMIZE = 0xF020;
private const int SC_RESTORE = 0xF120;
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case WM_SYSCOMMAND:
int command = m.WParam.ToInt32();
if (command == SC_RESTORE) {
this.FormBorderStyle = FormBorderStyle.Sizable;
this.ControlBox = true;
}
break;
}
base.WndProc(ref m);
}
}
[DllImport("user32.dll")]
static extern bool AnimateWindow(IntPtr hwnd, int dwTime, int dwFlags);
private const int AW_VER_POSITIVE = 0x00000004;
private const int AW_VER_NEGATIVE = 0x00000008;
private const int AW_SLIDE = 0x00040000;
private const int AW_HIDE = 0x00010000;
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Form f = new FormA();
f.ControlBox = false;
f.FormBorderStyle = FormBorderStyle.None;
bool isMinimizing = false;
var mb = new Button { Text = "Min" };
mb.Click += delegate {
isMinimizing = true;
f.FormBorderStyle = FormBorderStyle.Sizable;
f.ControlBox = true;
f.WindowState = FormWindowState.Minimized;
f.FormBorderStyle = FormBorderStyle.None;
isMinimizing = false;
//AnimateWindow(f.Handle, 300, AW_SLIDE | AW_VER_POSITIVE | AW_HIDE);
};
f.SizeChanged += delegate {
if (isMinimizing)
return;
if (f.WindowState != FormWindowState.Minimized)
f.FormBorderStyle = FormBorderStyle.None;
};
f.Controls.Add(mb);
Application.Run(f);
}
I know that this question has been asked over a year ago, but i had the same problem and found a very nice solution.
Look at this repo at Github.
Add FormBase.cs and Native.cs to your project.
What you have to do is basically to create a Form, f.e. Main.cs and derive it from FormBase
Main.cs
public Main()
{
InitializeComponent();
// Redraw gripper on resize
this.SetStyle(ControlStyles.ResizeRedraw, true);
// Ability to minimize/restore the form with animation
this.FormBorderStyle = FormBorderStyle.Sizable;
}
// Draw the gripper on the bottom right corner
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip, this.ClientSize.Height - cGrip, cGrip, cGrip);
ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
rc = new Rectangle(0, 0, this.ClientSize.Width, cCaption);
e.Graphics.FillRectangle(Brushes.DarkBlue, rc);
SizeGripStyle = SizeGripStyle.Hide;
}
// Override WndProc to add resize ability -> Cursor
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x84)
{ // Trap WM_NCHITTEST
Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
pos = this.PointToClient(pos);
if (pos.X >= this.ClientSize.Width - cGrip && pos.Y >= this.ClientSize.Height - cGrip)
{
m.Result = (IntPtr)17; // HTBOTTOMRIGHT
return;
}
}
base.WndProc(ref m);
}
I also removed line 147 in FormBase.cs, because my Form had rounded edges
//SetWindowRegion(m.HWnd, 0, 0, pos.cx, pos.cy);
I have a custom user control in my program. It is a panel which needs to be resizable from the left-hand side. Here is my code:
private void ResizePanel_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && e.X == ClientRectangle.Left)
{
resizeMode = true;
}
}
private void ResizePanel_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
resizeMode = false;
}
}
private void ResizePanel_MouseMove(object sender, MouseEventArgs e)
{
if (resizeMode == true)
{
Size newSize = new Size();
newSize.Height = Height;
newSize.Width = Math.Abs(e.X - ClientRectangle.Left); // Distance between the mouse position and
// left side of the panel
if (e.X < this.ClientRectangle.Left)
{
Width = newSize.Width;
Left -= newSize.Width;
}
}
}
In theory, the solution would be to move the panel to the left by the new width as the width increases. That's what this code is supposed to do. The problem with it at the moment is that as the panel moves to the left, the width stays the same and doesn't increase. Is there a way to do this so I can grab the control on the left side and drag to the left, so the size increases and it appears to stay in place?
Here's a quick and dirty panel that can only be dragged from the left side:
class LeftSideResizePanel : Panel
{
private const int HTLEFT = 10;
private const int HTCLIENT = 1;
private const int WM_NCHITTEST = 0x0084;
private const int WS_THICKFRAME = 0x40000;
protected override CreateParams CreateParams
{
get
{
var cp = base.CreateParams;
cp.Style |= WS_THICKFRAME;
return cp;
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCHITTEST:
Point pt = this.PointToClient(new Point(m.LParam.ToInt32()));
Rectangle hit = new Rectangle(new Point(-3, 0), new Size(6, this.Height));
m.Result = hit.Contains(pt) ? (IntPtr)HTLEFT : (IntPtr)HTCLIENT;
return;
}
base.WndProc(ref m);
}
}
If you don't like the look that WS_THICKFRAME gives, you'll have to go for finer control using a method as described in Jimi's comment.
my Problem is, that i want to draw a rectangle over an existing Textbox.
I have a solution now, but the Textbox is always redrawing, which is a behavior i do not want.
here is the code
private bool isDragging = false;
void Form2_MouseMove(object sender, MouseEventArgs e)
{
if (isDragging)
{
endPos = e.Location;
Rectangle rect;
if (endPos.Y > startPos.Y)
{
rect = new Rectangle(startPos.X, startPos.Y,
endPos.X - startPos.X, endPos.Y - startPos.Y);
}
else
{
rect = new Rectangle(endPos.X, endPos.Y,
startPos.X - endPos.X, startPos.Y - endPos.Y);
}
Region dragRegion = new Region(rect);
this.Invalidate();
}
}
void Form2_MouseUp(object sender, MouseEventArgs e)
{
isDragging = false;
this.Invalidate();
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp;
cp = base.CreateParams;
cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
return cp;
}
}
void Form2_MouseDown(object sender, MouseEventArgs e)
{
isDragging = true;
startPos = e.Location;
}
// this is where we intercept the Paint event for the TextBox at the OS level
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 15: // this is the WM_PAINT message
// invalidate the TextBox so that it gets refreshed properly
Input.Invalidate();
// call the default win32 Paint method for the TextBox first
base.WndProc(ref m);
// now use our code to draw extra stuff over the TextBox
break;
default:
base.WndProc(ref m);
break;
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (isDragging)
{
using (Pen p = new Pen(Color.Gray))
{
p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawRectangle(p,
startPos.X, startPos.Y,
endPos.X - startPos.X, endPos.Y - startPos.Y);
}
}
base.OnPaint(e);
}
the problem here is, the textbox is flickering, and the Rectangle is not set in front of the textbox after finish dragging.
How do i solve this ?
You are turning off the flag: WS_CLIPCHILDREN in WndProc.
WS_CLIPCHILDREN value is 0x02000000 and you are turning it off in your code:
cp.Style &= 0x7DFFFFFF; //WS_CLIPCHILDREN
WS_CLIPCHILDREN flag is set by default, it prevents flickering of child controls. If you will not turn off the flag, the flickering will stop.
According to the documentation on WS_CLIPCHILDREN: Excludes the area occupied by child windows when drawing occurs within the parent window. This style is used when creating the parent window.
Side Note: when turning off a flag, it is more clear to use the flag value you want to turn off:
cp.Style &= ~0x02000000; //WS_CLIPCHILDREN
I just wanted to know if in windows forms I can create a red line around the border of a combobox when its changed? Like just a flash of red and then gone again just to show that it was changed. Catch the user's eye or something. I will provide screens to represent what i would like.
If it is possible, please tell me where I can look it up to gain some information on it.
No border
Border flash on change
Border gone again after a second or two
Anytime the combobox changes, I want to flash a border to indicate it has changed.
The main idea is using a timer and drawing a border for some times. You can draw the border using different solutions. For example you can (1) draw the border on ComboBox or (2) you can draw border on Parent of ComboBox.
In the answer which I posed, I created a MyComboBox and added a FlashHotBorder method which can be called to flash border. Also I added a HotBorderColor property which can be used to set border color.
Flashing Border of ComboBox
To draw a border for ComboBox you can handle WM_Paint message of ComboBox and draw a border for control. Then to flash the border, you need to use a timer and turn on and turn off border for some times:
MyComboBox Code
I've created a FlashHotBorder method which you can call in SelectedIndexChanged event. Also if always you want to flash border when selected index changes, you can call it in OnSelectedIndexChanged. I prefer to call it in event handler. Here is the implementation:
using System.Drawing;
using System.Windows.Forms;
public class MyComboBox : ComboBox
{
int flash = 0;
private const int WM_PAINT = 0xF;
private int buttonWidth = SystemInformation.HorizontalScrollBarArrowWidth;
public Color HotBorderColor { get; set; }
private bool DrawBorder { get; set; }
Timer timer;
public MyComboBox()
{
this.HotBorderColor = Color.Red;
timer = new Timer() { Interval = 100 };
timer.Tick += new System.EventHandler(timer_Tick);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT && this.DrawBorder)
using (var g = Graphics.FromHwnd(this.Handle))
using (var p = new Pen(this.HotBorderColor))
g.DrawRectangle(p, 0, 0, this.Width - 1, this.Height - 1);
}
public void FlashHotBorder()
{
flash = 0;
timer.Start();
}
void timer_Tick(object sender, System.EventArgs e)
{
if (flash < 10)
{
flash++;
this.DrawBorder = !this.DrawBorder;
this.Invalidate();
}
else
{
timer.Stop();
flash = 0;
DrawBorder = false;
}
}
protected override void Dispose(bool disposing)
{
if (disposing) { timer.Dispose(); }
base.Dispose(disposing);
}
}
Then it's enough to use this event handler for SelectedIndexChanged event of eeach combo which you want to flash:
private void myComboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
var combo = sender as FlatCombo;
if (combo != null)
combo.FlashHotBorder();
}
You can create an outline/draw a border outside a comboBox or any other control using the DrawRectangle method.
The border will be drawn outside the comboBox if the SelectedIndex range condition satisfies else it'll revert to it's original state with no outline.
bool changed = false;
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (changed)
{
Pen p = new Pen(Color.Red);
Graphics g = e.Graphics;
int diff = 1;
g.DrawRectangle(p, new Rectangle(comboBox1.Location.X - diff, comboBox1.Location.Y - diff, comboBox1.Width + diff, comboBox1.Height + diff));
}
}
And, I am calling the Form1_Paint event on SelectedIndexChanged event of the comboBox.
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex >= 1 && comboBox1.SelectedIndex <= 9)
{
changed = true;
this.Refresh();
}
else
{
changed = false;
this.Refresh();
}
}
Outline Without Outline
So I came up with this. It's the shortest and easiest way to do it I think. If you have any recommendation, feel free to post them or comment it. thanx for all the help :).
public partial class Form1 : Form
{
private int tick = 0;
public Form1()
{
InitializeComponent();
}
bool changed = false;
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (changed == true)
{
changed = false;
this.Refresh();
}
else
{
if(tick<3)
{
timer1.Enabled = true;
timer1.Start();
}
changed = true;
this.Refresh();
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (changed)
{
Graphics g1 = e.Graphics;
int diff = 1;
Rectangle rect2 = new Rectangle(comboBox1.Location.X - diff, comboBox1.Location.Y - diff, comboBox1.Width + diff, comboBox1.Height + diff);
using (LinearGradientBrush br = new LinearGradientBrush(rect2,Color.Red,Color.Blue,LinearGradientMode.Horizontal))
{
ColorBlend color_blend = new ColorBlend();
color_blend.Colors = new Color[] { Color.Red, Color.Orange, Color.Yellow, Color.Lime, Color.Blue, Color.Indigo, Color.DarkViolet};
color_blend.Positions = new float[] { 0 / 6f, 1 / 6f, 2 / 6f, 3 / 6f, 4 / 6f, 5 / 6f, 6 / 6f };
br.InterpolationColors = color_blend;
Pen p = new Pen(br, 10);
e.Graphics.DrawRectangle(p, rect2);
}
}
else
{
Pen p = new Pen(Color.Transparent);
Graphics g = e.Graphics;
int diff = 1;
g.DrawRectangle(p, new Rectangle(comboBox1.Location.X - diff, comboBox1.Location.Y - diff, comboBox1.Width + diff, comboBox1.Height + diff));
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if(tick<3)
{
comboBox1_SelectedIndexChanged(null, null);
tick++;
}
else
{
timer1.Stop();
tick = 0;
}
}
}
Recently I made (mostly out of curiosity) a borderless form. After making my own title bar which includes the title, and the three buttons(minimize, maximize and close), just like every normal Windows program. I also made the code for these buttons (just ask if you want to see the code).
However, I've noticed that there are no animations. What I mean is that, e.g. if I click the minimize button, there is no animation, the program instantly disappears (it doesn't close, the button works, but without an animation). This happens in all cases: When I open the program it instantly appears, when I close it, it instantly disappears.
Is there some sort of way to use these animations that standard Windows programs use?
It doesn't seem possible to have the animation effect on a borderless form. However, there are two possible workarounds.
Set the FormBorderStyle back to Sizable just before a Minimize or Restore, and then back to none aftewards.
Use the AnimateWindow function instead. The animations tend to happen where the window is currently located. The functions can be applied to any Control, not just top level windows.
Here is some sample code:
class FormA : Form {
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MINIMIZE = 0xF020;
private const int SC_RESTORE = 0xF120;
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case WM_SYSCOMMAND:
int command = m.WParam.ToInt32();
if (command == SC_RESTORE) {
this.FormBorderStyle = FormBorderStyle.Sizable;
this.ControlBox = true;
}
break;
}
base.WndProc(ref m);
}
}
[DllImport("user32.dll")]
static extern bool AnimateWindow(IntPtr hwnd, int dwTime, int dwFlags);
private const int AW_VER_POSITIVE = 0x00000004;
private const int AW_VER_NEGATIVE = 0x00000008;
private const int AW_SLIDE = 0x00040000;
private const int AW_HIDE = 0x00010000;
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Form f = new FormA();
f.ControlBox = false;
f.FormBorderStyle = FormBorderStyle.None;
bool isMinimizing = false;
var mb = new Button { Text = "Min" };
mb.Click += delegate {
isMinimizing = true;
f.FormBorderStyle = FormBorderStyle.Sizable;
f.ControlBox = true;
f.WindowState = FormWindowState.Minimized;
f.FormBorderStyle = FormBorderStyle.None;
isMinimizing = false;
//AnimateWindow(f.Handle, 300, AW_SLIDE | AW_VER_POSITIVE | AW_HIDE);
};
f.SizeChanged += delegate {
if (isMinimizing)
return;
if (f.WindowState != FormWindowState.Minimized)
f.FormBorderStyle = FormBorderStyle.None;
};
f.Controls.Add(mb);
Application.Run(f);
}
I know that this question has been asked over a year ago, but i had the same problem and found a very nice solution.
Look at this repo at Github.
Add FormBase.cs and Native.cs to your project.
What you have to do is basically to create a Form, f.e. Main.cs and derive it from FormBase
Main.cs
public Main()
{
InitializeComponent();
// Redraw gripper on resize
this.SetStyle(ControlStyles.ResizeRedraw, true);
// Ability to minimize/restore the form with animation
this.FormBorderStyle = FormBorderStyle.Sizable;
}
// Draw the gripper on the bottom right corner
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rc = new Rectangle(this.ClientSize.Width - cGrip, this.ClientSize.Height - cGrip, cGrip, cGrip);
ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, rc);
rc = new Rectangle(0, 0, this.ClientSize.Width, cCaption);
e.Graphics.FillRectangle(Brushes.DarkBlue, rc);
SizeGripStyle = SizeGripStyle.Hide;
}
// Override WndProc to add resize ability -> Cursor
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x84)
{ // Trap WM_NCHITTEST
Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
pos = this.PointToClient(pos);
if (pos.X >= this.ClientSize.Width - cGrip && pos.Y >= this.ClientSize.Height - cGrip)
{
m.Result = (IntPtr)17; // HTBOTTOMRIGHT
return;
}
}
base.WndProc(ref m);
}
I also removed line 147 in FormBase.cs, because my Form had rounded edges
//SetWindowRegion(m.HWnd, 0, 0, pos.cx, pos.cy);