Prevent Form resize on SW_MAXIMIZE - c#

I want to simulate Aero Snap functionality by my own. And for stick Form to a screen's side I'm using solution from this question.
The problem is that after I call:
ShowWindow(Handle, SW_MAXIMIZE);
Form immediately maximizes and after call MoveWindow changes it's size to needed size. And this jump of the Form is visible and annoying. To prevent it I tried to disable handling of messages WM_GETMINMAXINFO, WM_SIZE, WM_MOVE, WM_NCCALCSIZE, WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED in WndProc. It helps but not completely. Is there any analog SuspendLayout()/ResumeLayout() for Form resizing?

Disabling message handling in WndProc helped me to reduce flickering (all except WM_NCPAINT):
bool ignoreMessages = false;
public void DockWindow()
{
ignoreMessages = true;
ShowWindow(handle, SW_MAXIMIZE);
ignoreMessages = false;
MoveWindow(handle, 0, 0, Screen.PrimaryScreen.WorkingArea.Width / 2, Screen.PrimaryScreen.WorkingArea.Height, true);
}
protected override void WndProc(ref Message message)
{
if (ignoreMessages &&
message.Msg != WM_NCPAINT)
return;
base.WndProc(ref message);
}

Related

Application.UseWaitCursor is delayed sometimes [duplicate]

Is there a difference between Cursor.Current and this.Cursor (where this is a WinForm) in .Net? I've always used this.Cursor and have had very good luck with it but I've recently started using CodeRush and just embedded some code in a "Wait Cursor" block and CodeRush used the Cursor.Current property. I've seen on the Internet and at work where other programmers have had some problems with the Cursor.Current property. It just got me to wondering if there is a difference in the two. Thanks in advance.
I did a little test. I have two winforms. I click a button on form1, set the Cursor.Current property to Cursors.WaitCursor and then show form2. The cursor doesn't change on either form. It remains Cursors.Default (pointer) cursor.
If I set this.Cursor to Cursors.WaitCursor in the button click event on form1 and show form2, the wait cursor only shows on form1 and the default cursor is on form2 which is expected. So, I still don't know what Cursor.Current does.
Windows sends the window that contains the mouse cursor the WM_SETCURSOR message, giving it an opportunity to change the cursor shape. A control like TextBox takes advantage of that, changing the cursor into a I-bar. The Control.Cursor property determines what shape will be used.
The Cursor.Current property changes the shape directly, without waiting for a WM_SETCURSOR response. In most cases, that shape is unlikely to survive for long. As soon as the user moves the mouse, WM_SETCURSOR changes it back to Control.Cursor.
The UseWaitCursor property was added in .NET 2.0 to make it easier to display an hourglass. Unfortunately, it doesn't work very well. It requires a WM_SETCURSOR message to change the shape and that won't happen when you set the property to true and then do something that takes a while. Try this code for example:
private void button1_Click(object sender, EventArgs e) {
this.UseWaitCursor = true;
System.Threading.Thread.Sleep(3000);
this.UseWaitCursor = false;
}
The cursor never changes. To whack that into shape, you'll need to use Cursor.Current as well. Here is a little helper class to make it easy:
using System;
using System.Windows.Forms;
public class HourGlass : IDisposable {
public HourGlass() {
Enabled = true;
}
public void Dispose() {
Enabled = false;
}
public static bool Enabled {
get { return Application.UseWaitCursor; }
set {
if (value == Application.UseWaitCursor) return;
Application.UseWaitCursor = value;
Form f = Form.ActiveForm;
if (f != null && f.Handle != IntPtr.Zero) // Send WM_SETCURSOR
SendMessage(f.Handle, 0x20, f.Handle, (IntPtr)1);
}
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
And use it like this:
private void button1_Click(object sender, EventArgs e) {
using (new HourGlass()) {
System.Threading.Thread.Sleep(3000);
}
}
I believe that Cursor.Current is the mouse cursor currently being used (regardless of where it is on the screen), while this.Cursor is the cursor it will be set to, when the mouse passes over your window.
this.Cursor is the cursor that will be used when the mouse is over the window referred to by this. Cursor.Current is the current mouse cursor, which might be different from this.Cursor if the mouse is over a different window.
Actually if you would like to use HourGlass from another thread that will give you back cross-threading exception because you are trying to access f.Handle from different thread than form was originally created. Use GetForegroundWindow() instead from user32.dll.
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
and then
public static bool Enabled
{
get
{
return Application.UseWaitCursor;
}
set
{
if (value == Application.UseWaitCursor)
{
return;
}
Application.UseWaitCursor = value;
var handle = GetForegroundWindow();
SendMessage(handle, 0x20, handle, (IntPtr)1);
}
}
I have noticed an interesting thing about setting cursors, so I would like to clear some misunderstandings that I myself had before and I hope it may help others too:
When you try to set a form's cursor by using
this.cursor = Cursors.Waitcursor
you actually set the cursor for the control and not the whole form since cursor is property of the Control class.
Also of course the cursor will only be changed to the given cursor when the mouse is actually over the actual control (explicitly the form's area)
As Hans Passant has already stated that:
Windows sends the window that contains the mouse cursor the
WM_SETCURSOR message, giving it an opportunity to change the cursor
shape
I don't know if windows sends messages directly to controls or if the form relays those messages to it's child controls based on mouse position, I'd most likely guess on the first method since when i fetched the messages with overriding WndProc of the form control, when i was over the textbox for example, the form didn't process any messages. (please someone give clarity on this)
Basically my suggestion would be to reside from using this.cursor also and stick to this.usewaitcursor, since that changes the cursor property to waitcursor for all child controls.
The problem with this is also the same as with the application level Application.usewaitcursor, while you are not over the form/forms with your cursor no WM_SETCURSOR message is being sent by windows, so if you start a time consuming synchronous operation before moving your mouse over the form's area, the form can only process such message when the time consuming synchronous operation finishes.
(I would not suggest running time consuming tasks in the UI thread at all, mainly this is what is causing the issue here)
I made a little improvement on Hans Passant's answer, so the hourglass can be either set on application level or form level, also avoiding InvalidOperationException from cross threaded operation calls:
using System;
using System.Windows.Forms;
public class HourGlass : IDisposable
{
public static bool ApplicationEnabled
{
get{ return Application.UseWaitCursor; }
set
{
Form activeFrom = Form.ActiveForm;
if (activeFrom == null || ApplicationEnabled == value) return;
if (ApplicationEnabled == value)return;
Application.UseWaitCursor = (bool)value;
if (activeFrom.InvokeRequired)
{
activeFrom.BeginInvoke(new Action(() =>
{
if (activeFrom.Handle != IntPtr.Zero)
SendMessage(activeFrom.Handle, 0x20, activeFrom.Handle, (IntPtr)1); // Send WM_SETCURSOR
}));
}
else
{
if (activeFrom.Handle != IntPtr.Zero)
SendMessage(activeFrom.Handle, 0x20, activeFrom.Handle, (IntPtr)1); // Send WM_SETCURSOR
}
}
}
private Form f;
public HourGlass()
{
this.f = Form.ActiveForm;
if (f == null)
{
throw new ArgumentException();
}
Enabled = true;
}
public HourGlass(bool enabled)
{
this.f = Form.ActiveForm;
if (f == null)
{
throw new ArgumentException();
}
Enabled = enabled;
}
public HourGlass(Form f, bool enabled)
{
this.f = f;
if (f == null)
{
throw new ArgumentException();
}
Enabled = enabled;
}
public HourGlass(Form f)
{
this.f = f;
if (f == null)
{
throw new ArgumentException();
}
Enabled = true;
}
public void Dispose()
{
Enabled = false;
}
public bool Enabled
{
get { return f.UseWaitCursor; }
set
{
if (f == null || Enabled == value) return;
if (Application.UseWaitCursor == true && value == false) return;
f.UseWaitCursor = (bool)value;
if(f.InvokeRequired)
{
f.BeginInvoke(new Action(()=>
{
if (f.Handle != IntPtr.Zero)
SendMessage(f.Handle, 0x20, f.Handle, (IntPtr)1); // Send WM_SETCURSOR
}));
}
else
{
if (f.Handle != IntPtr.Zero)
SendMessage(f.Handle, 0x20, f.Handle, (IntPtr)1); // Send WM_SETCURSOR
}
}
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
To use it on application level:
try
{
HourGlass.ApplicationEnabled = true;
//time consuming synchronous task
}
finally
{
HourGlass.ApplicationEnabled = false;
}
For using it on form level you can either use for the current active form:
using (new HourGlass())
{
//time consuming synchronous task
}
or you can initialize a local variable in the form like this:
public readonly HourGlass hourglass;
public Form1()
{
InitializeComponent();
hourglass = new HourGlass(this, false);
}
and use it later in a try catch finally block
This works great for me when the LongRunningOperation() is processing messages.
private void btnDoLongRunningOperation_Click(object sender, System.EventArgs e)
{
this.Cursor = Cursors.WaitCursor;
LongRunningOperation();
this.Cursor = Cursors.Arrow;
}
From VB.net VS 2012
Windows.Forms.Cursor.Current = Cursors.Default

Custom window frame with DWM: how to handle WM_NCCALCSIZE correctly

I'm trying to make a custom window frame for my form using DWM.
The platform is C# WinForms, Pinvoking DWM.
Following the MSDN article on making custom window frame with DWM, the main steps are next:
Remove standard frame (non-client area), returning 0 in answer to WM_NCCALCSIZE message
Extend the frame into client area using DwmExtendFrameIntoClientArea function
I handle WM_NCCALCSIZE message in the next way:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCCALCSIZE:
if (isDwmWindowFramePaintEnabled() && m.WParam != IntPtr.Zero)
{
m.Result = IntPtr.Zero;
}
else
{
base.WndProc(ref m);
}
return;
}
}
According to MSDN documentation on WM_NCCALCSIZE,
When wParam is TRUE, simply returning 0 without processing the
NCCALCSIZE_PARAMS rectangles will cause the client area to resize to
the size of the window, including the window frame. This will remove
the window frame and caption items from your window, leaving only the
client area displayed.
Everything is fine and works for me except one issue.
When I maximize/restore window, it's always growing a little bit when it gets restored.
I think, the issue is something like this:
When window gets restored, it contains client area only
Windows tries to give some non-client area to the window
In WM_NCCALCSIZE client area grows to contain non-client area
So, like this window grows a little bit each time I maximize/restore it.
I need to remove non-client area to paint custom form frame with DWM.
I can't simply set window border style to none as then DWM will not paint the window caption and borders.
Please, help to solve the issue and happily have a custom window frame.
This is actually a bug in Windows Forms and there's a workaround. In function Form.SizeFromClientSize(int, int) the AdjustWindowRectEx function is used to translate the size and it always uses the default measurements and can't be overridden. This function is called from two places:
RestoreWindowBoundsIfNecessary in WM_WINDOWPOSCHANGED window message handler
SetClientSizeCore
The workaround is the following:
Override CreateParams in the Form:
private bool createParamsHack;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
// Remove styles that affect the border size
if (createParamsHack)
cp.Style &= ~(int)(WS_BORDER | WS_CAPTION | WS_DLGFRAME | WS_THICKFRAME);
return cp;
}
}
Override WndProc and insert the following code to handle WM_WINDOWPOSCHANGED:
if (m.Msg == WM_WINDOWPOSCHANGED)
{
createParamsHack = true;
base.WndProc(ref m);
createParamsHack = false;
}
Override SetClientSizeCore:
protected override void SetClientSizeCore(int x, int y)
{
createParamsHack = true;
base.SetClientSizeCore(x, y);
createParamsHack = false;
}
It may also be good idea to override SizeFromClientSize(Size) to return the correct measurements, but it is not strictly necessary.

SendKeys not stopping screensaver coming on [duplicate]

I want to disable the screensaver and monitor power off. At this stage there's no windows form, which I could youse. Thus I wan't to use NativeWindow.
Here's my code
sealed class ObserverWindow : NativeWindow, IDisposable
{
internal ObserverWindow()
{
this.CreateHandle(new CreateParams()
{
Parent= IntPtr.Zero
});
}
public void Dispose()
{
DestroyHandle();
}
protected override void WndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND &&
((((long)msg.WParam & 0xFFF0) == SC_SCREENSAVE) ||
((long)msg.WParam & 0xFFF0) == SC_MONITORPOWER))
{
msg.Msg = 0;
msg.HWnd = IntPtr.Zero;
}
base.WndProc(ref msg);
}
}
The Problem is, that the WndProc is not called with WM_SYSCOMMAND. Actualy the WndProc is called 4 times. At the last call there's msg.Msg == WM_CREATE.
I think I'm missing some create parameter. Does anyone have advise?
Regards Michael
UPDATE
I was running the code in a non STA thread. Thus the window did not reveive any messages exept the initial ones. Now I'm receiving WM_SYSCOMMAND messages. But when the screensaver is activated, there's no message.
I also tried to overwrite a Form's WndProc with the same result. But this used to work in Windows XP. Is there a change in Windows 7?
OS: Windows 7 64bit.
SOLUTION
As a comment in this Question states, only the foreground window can cancel the screensaver. Thus the above code can't work. The NativeWindow is great for receiving messages, but not for canceling a screensaver. For latter I recommend the answer to this question.
The proper way to do this is by telling Windows that your thread needs to have the display active. Commonly used by video players. P/Invoke the SetThreadExecutionState() API function, pass ES_DISPLAY_REQUIRED. And ES_SYSTEM_REQUIRED to keep the machine from shutting down automatically. Visit pinvoke.net for the required declarations.
Disabling the screen saver is much easier, according to this KB article:
This can be done easily using:
SystemParametersInfo( SPI_SETSCREENSAVEACTIVE,
FALSE,
0,
SPIF_SENDWININICHANGE
);
[...]
If you need the screen saver to start up again, you'll need to reinitialize the time-out period. Do this by [c]alling SystemParametersInfo (SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_SENDWININICHANGE).
You could try overriding DefWndProc instead.
public override void DefWndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND &&
((((long)msg.WParam & 0xFFF0) == SC_SCREENSAVE) ||
((long)msg.WParam & 0xFFF0) == SC_MONITORPOWER))
{
msg.Msg = 0;
msg.HWnd = IntPtr.Zero;
}
base.DefWndProc(ref msg);
}
I'm not on a Windows box right now, so I cannot test this. Let me know if it works.

Use NativeWindow to disable screensaver

I want to disable the screensaver and monitor power off. At this stage there's no windows form, which I could youse. Thus I wan't to use NativeWindow.
Here's my code
sealed class ObserverWindow : NativeWindow, IDisposable
{
internal ObserverWindow()
{
this.CreateHandle(new CreateParams()
{
Parent= IntPtr.Zero
});
}
public void Dispose()
{
DestroyHandle();
}
protected override void WndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND &&
((((long)msg.WParam & 0xFFF0) == SC_SCREENSAVE) ||
((long)msg.WParam & 0xFFF0) == SC_MONITORPOWER))
{
msg.Msg = 0;
msg.HWnd = IntPtr.Zero;
}
base.WndProc(ref msg);
}
}
The Problem is, that the WndProc is not called with WM_SYSCOMMAND. Actualy the WndProc is called 4 times. At the last call there's msg.Msg == WM_CREATE.
I think I'm missing some create parameter. Does anyone have advise?
Regards Michael
UPDATE
I was running the code in a non STA thread. Thus the window did not reveive any messages exept the initial ones. Now I'm receiving WM_SYSCOMMAND messages. But when the screensaver is activated, there's no message.
I also tried to overwrite a Form's WndProc with the same result. But this used to work in Windows XP. Is there a change in Windows 7?
OS: Windows 7 64bit.
SOLUTION
As a comment in this Question states, only the foreground window can cancel the screensaver. Thus the above code can't work. The NativeWindow is great for receiving messages, but not for canceling a screensaver. For latter I recommend the answer to this question.
The proper way to do this is by telling Windows that your thread needs to have the display active. Commonly used by video players. P/Invoke the SetThreadExecutionState() API function, pass ES_DISPLAY_REQUIRED. And ES_SYSTEM_REQUIRED to keep the machine from shutting down automatically. Visit pinvoke.net for the required declarations.
Disabling the screen saver is much easier, according to this KB article:
This can be done easily using:
SystemParametersInfo( SPI_SETSCREENSAVEACTIVE,
FALSE,
0,
SPIF_SENDWININICHANGE
);
[...]
If you need the screen saver to start up again, you'll need to reinitialize the time-out period. Do this by [c]alling SystemParametersInfo (SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_SENDWININICHANGE).
You could try overriding DefWndProc instead.
public override void DefWndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND &&
((((long)msg.WParam & 0xFFF0) == SC_SCREENSAVE) ||
((long)msg.WParam & 0xFFF0) == SC_MONITORPOWER))
{
msg.Msg = 0;
msg.HWnd = IntPtr.Zero;
}
base.DefWndProc(ref msg);
}
I'm not on a Windows box right now, so I cannot test this. Let me know if it works.

How to disable the minimize button in C#?

In my application I need to temporarily gray out the minimize button of the main form. Any ideas how this can be achieved? I don't mind doing p/invokes to Win32 dlls.
Edit: Graying out the minimize button would be the preferred solution, but is there any other way of preventing the form from becoming minimized?
I read your comment in regards to my response and was able to drum up a more complete solution for you. I ran this quickly and it seemed to have the behavior that you wanted. Instead of deriving your winforms from Form, derive from this class:
using System;
using System.Windows.Forms;
using System.ComponentModel;
namespace NoMinimizeTest
{
public class MinimizeControlForm : Form
{
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MINIMIZE = 0xf020;
protected MinimizeControlForm()
{
AllowMinimize = true;
}
protected override void WndProc(ref Message m)
{
if (!AllowMinimize)
{
if (m.Msg == WM_SYSCOMMAND)
{
if (m.WParam.ToInt32() == SC_MINIMIZE)
{
m.Result = IntPtr.Zero;
return;
}
}
}
base.WndProc(ref m);
}
[Browsable(true)]
[Category("Behavior")]
[Description("Specifies whether to allow the window to minimize when the minimize button and command are enabled.")]
[DefaultValue(true)]
public bool AllowMinimize
{
get;
set;
}
}
}
You could do a bit more if you wanted to be able to decide whether to allow minimizing at the time the click is sent, for instance:
using System;
using System.Windows.Forms;
using System.ComponentModel;
namespace NoMinimizeTest
{
public class MinimizeControlForm : Form
{
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MINIMIZE = 0xf020;
protected MinimizeControlForm()
{
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_SYSCOMMAND)
{
if (m.WParam.ToInt32() == SC_MINIMIZE && !CheckMinimizingAllowed())
{
m.Result = IntPtr.Zero;
return;
}
}
base.WndProc(ref m);
}
private bool CheckMinimizingAllowed()
{
CancelEventArgs args = new CancelEventArgs(false);
OnMinimizing(args);
return !args.Cancel;
}
[Browsable(true)]
[Category("Behavior")]
[Description("Allows a listener to prevent a window from being minimized.")]
public event CancelEventHandler Minimizing;
protected virtual void OnMinimizing(CancelEventArgs e)
{
if (Minimizing != null)
Minimizing(this, e);
}
}
}
For more information about this window notification, see the MSDN article about it.
form.MinimizeBox = false;
or if in the form scope
MinimizeBox = false;
Just do MinimizeBox = false; in your form's code.
Put this code in your form's Resize event:
if (this.WindowState == FormWindowState.Minimized)
{
this.WindowState = FormWindowState.Normal;
}
This will make your form un-minimizable (DISCLAIMER: I do not advocate altering the standard behavior of windows in this way).
You can also implement handle to the Minimize event to cancel the command
Don't. Don't mess with my windows. They are mine, not yours. It is my computer and if I want to minimize, I should be able to. I can't think of, and have never been given, a good reason for doing this.
Coincoin's answer is correct. MinimizeBox is also available as a property in the designer properties window.
#Kevin: While I appreciate the sentiment, that's not always a valid answer. If the application displays a modal dialog box by creating a new instance of a Form and then calling .ShowDialog() on it, you don't want the user to minimize that Form, because then all input on the main UI thread is blocked until that Form's modal status is satisfied. The user could potentially click on the main form and just get the "ding ding ding" unresponsive sound from Windows and not know what to do.
just set the MinimizeBox property of your form to false.
this will disable the minimize button but other buttons will remain functional.

Categories

Resources