Disabling maximizing form in C# [duplicate] - c#

I'm annoyed that the I'm promised a fixed window that the user can't resize, but then of course they're allowed to double click the title bar to maximize this 'unresizable' window. How can I turn this off? Can I do it with winforms code, or must I go down to Win32?
Thanks!

You could set the MaximizeBox property of the form to false

You can disable the double-click message on a title bar in general (or change the default behavior which is maximizing the window). it works on any FormBorderStyle:
private const int WM_NCLBUTTONDBLCLK = 0x00A3; //double click on a title bar a.k.a. non-client area of the form
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCLBUTTONDBLCLK)
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
MSDN Source
Cheers!

///
/// This is we are overriding base WIN32 window procedure to prevent the form from being moved by the mouse as well as resized by the mouse double click.
///
///
protected override void WndProc(ref Message m)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_MOVE = 0xF010;
const int WM_NCLBUTTONDBLCLK = 0x00A3; //double click on a title bar a.k.a. non-client area of the form
switch (m.Msg)
{
case WM_SYSCOMMAND: //preventing the form from being moved by the mouse.
int command = m.WParam.ToInt32() & 0xfff0;
if (command == SC_MOVE)
return;
break;
}
if(m.Msg== WM_NCLBUTTONDBLCLK) //preventing the form being resized by the mouse double click on the title bar.
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}

I know I'm late to the party, May help someone who is searching for the same.
private const int WM_NCLBUTTONDBLCLK = 0x00A3; //double click on a title bar a.k.a. non-client area of the form
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WM_NCLBUTTONDBLCLK: //preventing the form being resized by the mouse double click on the title bar.
handled = true;
break;
default:
break;
}
return IntPtr.Zero;
}

I just checked it in VB.Net. Below code worked for me.
Private Const Win_FormTitleDoubleClick As Integer = 163
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = Win_FormTitleDoubleClick Then
m.Result = IntPtr.Zero
Return
End If
MyBase.WndProc(m)
End Sub
Note: 163 is the event code

I approached the problem slightly differently. First I removed the minimize and maximize options from the control box via the MaximizeBox and MinimizeBox properties as you'd expect.
Then I added the following OnResizeEnd() event and attached it to the Form's ResizeEnd event handler:
/// <summary>Locks the form to fill the screen that it's placed on and remain in that state as long as it's open.</summary>
private void GuiMain_ResizeEnd( object sender, EventArgs e )
{
this.Location = Screen.WorkingArea.Location;
this.Size = Screen.WorkingArea.Size;
this.MaximizedBounds = Screen.WorkingArea;
this.MinimumSize = Screen.WorkingArea.Size;
this.WindowState = FormWindowState.Normal;
}
This solution necessarily relies on the existence of the following accessor which you can copy, or you can simply replace each instance of Screen. in the above with Screen.FromHandle( this.Handle ).
protected Screen Screen => Screen.FromHandle( this.Handle );
Obviously, if ironically, this actually keeps the form in the FormWindowState.Normal state, but it mimics the effect of maximization, and resets this fullscreen state after any attempt to change it.
Interestingly, due to the use of the Screen.FromHandle() settings (as opposed to hard-coded ones), you can actually drag the form from one display to another, whereupon it immediately "snaps" to fill THAT screen instead. Something I found quite handy, but which may require additional code to correct if you don't want that functionality in your application.

Related

Move window without a mouse drag gesture

I have a window (subclass of System.Windows.Forms.Form) without a border in order to apply a custom style to it. Moving the window when the mouse button is down seems to be easy. Either by sending a WM_NCLBUTTONDOWN HT_CAPTION or a WM_SYSCOMMAND 0xF102 message the window can be dragged to a new location. As soon as the mouse button is up though, it seems to be impossible to move the window.
One could send WM_SYSCOMMAND SC_MOVE message but then the cursor moves at the top center of the window and awaits for the user to press any arrow key in order to hook the window for move -which is awkward at the least. I tried to fake a key press/release sequence but that of course didn't work as I called SendMessage with the current form Handle as argument but I guess the message should not be sent to the current form.
The desired behavior is: click a button (ie the mouse button is released) move the form where cursor goes, click again to release the form. Is that possible with winapi? Unfortunately I am not familiar with it.
Addendum
Sending a key input: I tried to use SendInput as SendMessage is supposed to be bad practice. Still it didn't hook the window. I tried to read and print the winapi error code with Marshal.GetLastWin32Error() and I got a 5 which is access denied. The curious thing was that I received the messages after the move sequence ended (ie I manually pressed a key or mouse button). No idea how to work around this.
Using the IMessageFilter (IVSoftware's answer): this is what I ended up doing but that has 2 issues: moving the window with its Location property has lag compared to the native way (no big deal for now) and also it doesn't receive mouse messages that occur outside the main form. That means it won't work a. for multiscreen environments b. if the cursor moves outside the forms of the application. I could create full screen transparent forms for every monitor that will serve as an "message canvas" but still... why not give the OS way a chance.
As I understand it, the desired behavior is to enable the "Click to Move" (one way or another) and then click anywhere on a multiscreen surface and have the borderless form follow the mouse to the new position. One solution that seems to work in my brief testing is to pinvoke the WinApi SetWindowsHookEx to install a global low level hook for WH_MOUSE_LL in order to intercept WM_LBUTTONDOWN.
*This answer has been modified in order to track updates to the question.
Low-level global mouse hook
public MainForm()
{
InitializeComponent();
using (var process = Process.GetCurrentProcess())
{
using (var module = process.MainModule!)
{
var mname = module.ModuleName!;
var handle = GetModuleHandle(mname);
_hook = SetWindowsHookEx(
HookType.WH_MOUSE_LL,
lpfn: callback,
GetModuleHandle(mname),
0);
}
}
// Unhook when this `Form` disposes.
Disposed += (sender, e) => UnhookWindowsHookEx(_hook);
// A little hack to keep window on top while Click-to-Move is enabled.
checkBoxEnableCTM.CheckedChanged += (sender, e) =>
{
TopMost = checkBoxEnableCTM.Checked;
};
// Compensate move offset with/without the title NC area.
var offset = RectangleToScreen(ClientRectangle);
CLIENT_RECT_OFFSET = offset.Y - Location.Y;
}
readonly int CLIENT_RECT_OFFSET;
IntPtr _hook;
private IntPtr callback(int code, IntPtr wParam, IntPtr lParam)
{
var next = IntPtr.Zero;
if (code >= 0)
{
switch ((int)wParam)
{
case WM_LBUTTONDOWN:
if (checkBoxEnableCTM.Checked)
{
_ = onClickToMove(MousePosition);
// This is a very narrow condition and the window is topmost anyway.
// So probably swallow this mouse click and skip other hooks in the chain.
return (IntPtr)1;
}
break;
}
}
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
}
Perform the move
private async Task onClickToMove(Point mousePosition)
{
// Exempt clicks that occur on the 'Enable Click to Move` button itself.
if (!checkBoxEnableCTM.ClientRectangle.Contains(checkBoxEnableCTM.PointToClient(mousePosition)))
{
// Try this. Offset the new `mousePosition` so that the cursor lands
// in the middle of the button when the move is over. This feels
// like a semi-intuitive motion perhaps. This means we have to
// subtract the relative position of the button from the new loc.
var clientNew = PointToClient(mousePosition);
var centerButton =
new Point(
checkBoxEnableCTM.Location.X + checkBoxEnableCTM.Width / 2,
checkBoxEnableCTM.Location.Y + checkBoxEnableCTM.Height / 2);
var offsetToNow = new Point(
mousePosition.X - centerButton.X,
mousePosition.Y - centerButton.Y - CLIENT_RECT_OFFSET);
// Allow the pending mouse messages to pump.
await Task.Delay(TimeSpan.FromMilliseconds(1));
WindowState = FormWindowState.Normal; // JIC window happens to be maximized.
Location = offsetToNow;
}
checkBoxEnableCTM.Checked = false; // Turn off after each move.
}
In the code I used to test this answer, it seemed intuitive to center the button where the click takes place (this offset is easy to change if it doesn't suit you). Here's the result of the multiscreen test:
WinApi
#region P I N V O K E
public enum HookType : int { WH_MOUSE = 7, WH_MOUSE_LL = 14 }
const int WM_LBUTTONDOWN = 0x0201;
delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
#endregion P I N V O K E
Here is a possible solution that I will go with after all. It's not that IVSoftware's answer doesn't work, it does, I tried it. It's that my solution has some set of advantages relevant to what I am trying to do. The main points are:
Utilizing the IMessageFilter (thanks to SwDevMan81's answer) which reminded me that the correct way to process messages "globally" is not to override WndProc)
Laying out a set of transparent windows on all screens in order to receive mouse move messages everywhere.
Pros
It works without having to make any P/Invokes
It allows more tricks to be done like for example leverage the transparent forms to implement a "move border instead of form" functionality (though I didn't test it, paint might be tricky)
Can be easily applied for resize as well.
Can work with mouse buttons other than the left/primary.
Cons
It has too many "moving parts". At least for my taste. Laying out transparent windows all over the place? Hm.
It has some corner cases. Pressing Alt+F4 while moving the form will close the "canvas form". That can be easily mitigated but there might be others as well.
There must be an OS way to do this...
The code (basic parts; full code on github)
public enum WindowMessage
{
WM_MOUSEMOVE = 0x200,
WM_LBUTTONDOWN = 0x201,
WM_LBUTTONUP = 0x202,
WM_RBUTTONDOWN = 0x204,
//etc. omitted for brevity
}
public class MouseMessageFilter : IMessageFilter
{
public event EventHandler MouseMoved;
public event EventHandler<MouseButtons> MouseDown;
public event EventHandler<MouseButtons> MouseUp;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case (int)WindowMessage.WM_MOUSEMOVE:
MouseMoved?.Invoke(this, EventArgs.Empty);
break;
case (int)WindowMessage.WM_LBUTTONDOWN:
MouseDown?.Invoke(this, MouseButtons.Left);
break;
//etc. omitted for brevity
}
return false;
}
}
public partial class CustomForm : Form
{
private MouseMessageFilter windowMoveHandler = new();
private Point originalLocation;
private Point offset;
private static List<Form> canvases = new(SystemInformation.MonitorCount);
public CustomForm()
{
InitializeComponent();
windowMoveHandler.MouseMoved += (_, _) =>
{
Point position = Cursor.Position;
position.Offset(offset);
Location = position;
};
windowMoveHandler.MouseDown += (_, button) =>
{
switch (button)
{
case MouseButtons.Left:
EndMove();
break;
case MouseButtons.Middle:
CancelMove();
break;
}
};
moveButton.MouseClick += (_, _) =>
{
BeginMove();
};
}
private void BeginMove()
{
Application.AddMessageFilter(windowMoveHandler);
originalLocation = Location;
offset = Invert(PointToClient(Cursor.Position));
ShowCanvases();
}
//Normally an extension method in another library of mine but I didn't want to
//add a dependency just for that
private static Point Invert(Point p) => new Point(-p.X, -p.Y);
private void ShowCanvases()
{
for (int i = 0; i < Screen.AllScreens.Length; i++)
{
Screen screen = Screen.AllScreens[i];
Form form = new TransparentForm
{
Bounds = screen.Bounds,
Owner = Owner
};
canvases.Add(form);
form.Show();
}
}
private void EndMove()
{
DisposeCanvases();
}
private void DisposeCanvases()
{
Application.RemoveMessageFilter(windowMoveHandler);
for (var i = 0; i < canvases.Count; i++)
{
canvases[i].Close();
}
canvases.Clear();
}
private void CancelMove()
{
EndMove();
Location = originalLocation;
}
//The form used as a "message canvas" for moving the form outside the client area.
//It practically helps extend the client area. Without it we won't be able to get
//the events from everywhere
private class TransparentForm : Form
{
public TransparentForm()
{
StartPosition = FormStartPosition.Manual;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//Draws a white border mostly useful for debugging. For example that's
//how I realised I needed Screen.Bounds instead of WorkingArea.
ControlPaint.DrawBorder(e.Graphics, new Rectangle(Point.Empty, Size),
Color.White, 2, ButtonBorderStyle.Solid,
Color.White, 2, ButtonBorderStyle.Solid,
Color.White, 2, ButtonBorderStyle.Solid,
Color.White, 2, ButtonBorderStyle.Solid);
}
}
}

Limit startup behavior of Form

I have a startup screen in my application that I want to show with StartupPosition = CenterScreen and WindowState=Normal. All works fine, but if, for instance, the user creates a shortcut to my application and sets Run=Maximized, then the startup screen becomes maximized instead of normal and I want to disable this behavior. Is there any way to completely disable form from being maximized or override startup state given to application?
I've tried forcing StartupPosition and WindowState, but this doesn't work as the window just ends up on the top-left of the screen.
Doing some tests I found 2 ways to disable the window from being maximized due to Run=Maximized on startup:
A) show a dummy form, close it, then load actual startup form
B) add the following the the Form.Load:
if (WindowState != FormWindowState.Normal)
{
WindowState = FormWindowState.Normal;
Rectangle r = Screen.GetWorkingArea(this);
Location = new Point(r.X + ((r.Width - Width) / 2), r.Y + ((r.Height - Height) / 2));
}
I know there should be an actual method of disabling form from starting maximized because if I show a message box before my startup form the message box shows up normally and then so does my startup form, but without showing the message box my startup screen wants to be maximized.
Thank you #LarsTech! The answer was somewhere between Application stuck in full screen? and #Jimi's solution. The benefit is this disables Run = Maximized without disabling Run = Minimized.
using System.Runtime.InteropServices;
public partial class frmStartup : Form
{
public frmStartup()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x46) // WM_WINDOWPOSCHANGING
{
var wpos = (WINDOWPOS)Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS));
wpos.flags |= 0x0001; // Turn on SWP_NOSIZE
Marshal.StructureToPtr(wpos, m.LParam, false);
}
base.WndProc(ref m);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct WINDOWPOS
{
public IntPtr hwnd, hwndInsertAfter;
public int x, y, cx, cy;
public int flags;
}
private void frmStartup_Load(object sender, EventArgs e)
{
if (WindowState == FormWindowState.Maximized)
{
WindowState = FormWindowState.Normal;
}
}
}

Make MouseWheel event pass from child to parent

I have a Panel where AutoScroll is true. This panel contains many smaller panels that fill all available space like tiles. When there are too many sub-panels to display I get the vertical scroll bar as expected.
Each of these "tiles" have some event handlers tied to them to handle MouseDown / MouseUp / MouseMove as they can be dragged around.
The problem I'm having is that mouse wheel scroll doesn't work on the parent panel as it won't have focus. I can't give it focus because in all probability I'll be scrolling while moving a sub-panel which will have the focus instead, and even then that would require workarounds since panels don't like focus.
I've been trying (and failing) to find a way to propagate only mousewheel events from the children to the parent.
I've read that in Winforms if a control cannot handle a mouse event it will bubble it up to the parent of that control, and then to the parent of that control and so on until it finds a suitable handler.
With this in mind I figured the best solution would be to use WndProc to override all scroll related events on the sub-panel and pass them to the parent while leaving all other events intact but admittedly this isn't my strong suit and I'm lost.
I've tried a few other solutions such as making the sub-panels invisible to all mouse events but as you might have guessed this was bad. I've read about implementing a message filter but didn't understand it.
Here's the code that will give you a very basic example of the panel and its children:
private void Form1_Load(object sender, EventArgs e)
{
Height = 600;
Width = 300;
Color[] colors = new Color[]{ Color.PowderBlue, Color.PeachPuff };
Panel panel = new Panel()
{
Height = this.ClientSize.Height - 20,
Width = 200,
Top = 10,
Left = 10,
BackColor = Color.White,
BorderStyle = BorderStyle.FixedSingle,
AutoScroll = true
};
for (int i = 0; i < 10; i++)
{
Panel subPanel = new Panel()
{
Name = #"SubPanel " + i.ToString(),
Height = 100,
Width = panel.Width - System.Windows.Forms.SystemInformation.VerticalScrollBarWidth - 2,
BackColor = colors[i % 2],
Top = i * 100
};
subPanel.MouseClick += subPanel_MouseClick;
panel.Controls.Add(subPanel);
}
Controls.Add(panel);
}
void subPanel_MouseClick(object sender, MouseEventArgs e)
{
Panel panel = sender as Panel;
Text = panel.Name;
}
Here's my attempt at overriding WndProc in a custom panel:
class NoScrollPanel : Panel
{
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int MOUSEWHEEL = 0x020A;
private const int KEYDOWN = 0x0100;
protected override void WndProc(ref Message m)
{
if ((m.HWnd == Handle) && (m.Msg == MOUSEWHEEL || m.Msg == WM_VSCROLL || (m.Msg == KEYDOWN && (m.WParam == (IntPtr)40 || m.WParam == (IntPtr)35))))
{
PostMessage(Parent.Handle, m.Msg, m.WParam, m.LParam);
}
else
{
base.WndProc(ref m);
}
}
[DllImport("User32.dll")]
private static extern IntPtr PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
}
Any help or alternative approaches are most welcome. Thanks!
At my side #a-clymer's solution does not work, perhaps different environment. Currently there is no direct, clear answer for my problem so I try to combine several thoughts of other professionals and succeed.
In my current project, I have a few input controls contained in a Panel. I managed to make the mouse wheel scroll the panel instead of the ComboBox items by creating a child class of ComboBox and override its WndProc:
public class ComboBoxWithParentMouseWheel : ComboBox
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
const int WM_MOUSEWHEEL = 0x020A;
//thanks to a-clymer's solution
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MOUSEWHEEL)
{
//directly send the message to parent without processing it
//according to https://stackoverflow.com/a/19618100
SendMessage(this.Parent.Handle, m.Msg, m.WParam, m.LParam);
m.Result = IntPtr.Zero;
}else base.WndProc(ref m);
}
}
All credit goes to Hans Passant (again), taken from the thread he suggested: https://stackoverflow.com/a/3562449/17034
Allowing the the containing panel to take focus worked fine. For the demo code above the class needs no changes, just use it for only the containing panel. I had to make some tweaks to my project to call focus when necessary, but it was far from rocket science.
Thanks again.
I could not get any of these solutions to work. I am overriding the WndProc fxn just like you. Finally found the solution! I noticed that if the container holding my TextBox was in focus, then the scroll worked, just not when the TextBox was in focus.
I didn't need to change the transparency of the event, I needed to send the event to a different Control Handle! (It seems so simple now, but I've trying to figure this out for days!)
internal class myTextBox : TextBox
{
const int WM_MOUSEWHEEL = 0x020A;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MOUSEWHEEL)
m.HWnd = this.Parent.Handle; // Change the Handle of the message
base.WndProc(ref m);
}
}
I haven't seen any mention of this method in all my Google searches, if there is some reason NOT to do this I hope someone will reply.
An improved version of #a-clymer version that works better.
const int WM_MOUSEWHEEL = 0x020A;
if (m.Msg == WM_MOUSEWHEEL)
{
// find the first scrollable parent control
Control p = this;
do
{
p = p.Parent;
} while (p != null && !(p is ScrollableControl));
// rewrite the destination handle of the message
if (p != null)
m.HWnd = p.Handle;
}

How to position a form on top of another form without taking the focus

My application needs to display a popup near the cursor position of any active application when certain conditions are met (known by my app).
So I would like to display the form (without stealing the focus from the active application). I tried using ShowWindow with the SW_SHOWNOACTIVATE param but, this way, the my form is displayed under the currently active form.
How can I force my form to be displayed on top of any form active on the screen without stealing the input focus?
Thanks.
What you want is to use the TopMost property of the form you want to stay on top.
You need to add a bit of plumbing to the pop-up form so you can override the WM_ACTIVATE message:
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_ACTIVATE)
{
if (((int)m.WParam & 0xFFFF) != WA_INACTIVE)
{
if (m.LParam != IntPtr.Zero)
{
SetActiveWindow(m.LParam);
}
else
{
// Could not find sender, just in-activate it.
SetActiveWindow(IntPtr.Zero);
}
}
}
base.WndProc(ref m);
}
Make sure you add the following to your pop-up form as well:
[DllImport("user32.dll")]
private extern static IntPtr SetActiveWindow(IntPtr handle);
private const int WM_ACTIVATE = 6;
private const int WA_INACTIVE = 0;
You can use the pop-up form as you would any other, by calling Show() on it. You can make it the top-most window through the TopMost property, as with other forms.

How to make a window Draggable(C# Winforms)?

I have form. I have enabled the transparency on the form and I have removed it's Title Bar and Border. Inside that i have created a Custom UI, Which have the same features like a window. Basically, my idea is to create custom window.
Everything is working as expected but only the windows dragging is not working. I am not sure how to enable it. I googled for this. But i didn't find any useful info for me.
Please help me to implement this window dragging.
I've implemented this behavior by capturing mousedown (uncapture on mouseup), and then mousemove.
Just move the form co-ordinates (left, top), equivalent amounts to the mouse movement (those events have the amount the mouse moved).
This worked fine for me.
class YourForm : Form
{
private const int WM_NCHITTEST = 0x84;
private const int HTCLIENT = 0x1;
private const int HTCAPTION = 0x2;
///
/// Handling the window messages
///
protected override void WndProc(ref Message message)
{
base.WndProc(ref message);
if (message.Msg == WM_NCHITTEST && (int)message.Result == HTCLIENT)
message.Result = (IntPtr)HTCAPTION;
}
}
The easiest way is to process WM_NCHITTEST message and return HTCAPTION for the portions of your custom window which work like the title bar does in a normal window. Windows will do the rest.

Categories

Resources