Is there any way of creating a tool window in WinForms that as long as the hosting form has focus, the tool window does as well? An example of this is in Paint.NET:
I'm using C# as the backend application language, under .Net 4.0.
The source code for an old version of Paint.Net is available at openpdn Fork of Paint.NET 3.36.7
I tried to extract their methods from that source code into the most concise working example I could muster:
Pinvoking class:
internal static class Win32 {
public const int WM_ACTIVATE = 0x006;
public const int WM_ACTIVATEAPP = 0x01C;
public const int WM_NCACTIVATE = 0x086;
[DllImport("user32.dll", SetLastError = false)]
internal static extern IntPtr SendMessageW(IntPtr hWnd, uint msg,
IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal extern static bool PostMessageW(IntPtr handle, uint msg,
IntPtr wParam, IntPtr lParam);
}
Base Form:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private bool ignoreNcActivate = false;
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
switch (m.Msg) {
case Win32.WM_NCACTIVATE:
if (m.WParam == IntPtr.Zero) {
if (ignoreNcActivate) {
ignoreNcActivate = false;
} else {
Win32.SendMessageW(this.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
}
}
break;
case Win32.WM_ACTIVATEAPP:
if (m.WParam == IntPtr.Zero) {
Win32.PostMessageW(this.Handle, Win32.WM_NCACTIVATE, IntPtr.Zero, IntPtr.Zero);
foreach (Form2 f in this.OwnedForms.OfType<Form2>()) {
f.ForceActiveBar = false;
Win32.PostMessageW(f.Handle, Win32.WM_NCACTIVATE, IntPtr.Zero, IntPtr.Zero);
}
ignoreNcActivate = true;
} else if (m.WParam == new IntPtr(1)) {
Win32.SendMessageW(this.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
foreach (Form2 f in this.OwnedForms.OfType<Form2>()) {
f.ForceActiveBar = true;
Win32.SendMessageW(f.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
}
}
break;
}
}
protected override void OnShown(EventArgs e) {
base.OnShown(e);
Form2 f = new Form2();
f.Show(this);
}
}
Always active Form2 (unless app is not active):
public partial class Form2 : Form {
internal bool ForceActiveBar { get; set; }
public Form2() {
InitializeComponent();
this.ShowInTaskbar = false;
this.ForceActiveBar = true;
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (m.Msg == Win32.WM_NCACTIVATE) {
if (this.ForceActiveBar && m.WParam == IntPtr.Zero) {
Win32.SendMessageW(this.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
}
}
}
}
There is no need to set TopMost to true for Form2 since it should be owned by the main form when it gets displayed. Also, Form2 is not an MDI child form.
The tool windows in Paint.NET are just that—tool windows. In Win32 terms, you achieve this by creating the window with the WS_EX_TOOLWINDOW
extended window style:
The window is intended to be used as a floating toolbar. A tool window has a title bar that is shorter than a normal title bar, and the window title is drawn using a smaller font. A tool window does not appear in the taskbar or in the dialog that appears when the user presses ALT+TAB.
In WinForms, this is controlled by the FormBorderStyle
property. Set it to either FormBorderStyle.FixedToolWindow
or FormBorderStyle.SizableToolWindow
in your form's constructor.
You also need to make sure that you specify an owner window for the tool window. Its owner should be your main form, the one for which it serves as a tool palette. You generally do this when showing the form, using the overload of the Show
method that allows you to specify an owner window.
Finally, another cool effect that Paint.NET has (I think, if I remember correctly) is that the tool windows can never actually receive the focus. You can interact with them, clicking on buttons to select tools, but you can't actually set the focus to a floating palette. It always goes back to the main window. A naive attempt to emulate this behavior might be to reset the focus in one of the focus-changing notifications (e.g., the Activate
event), but that's not a good idea for numerous reasons. A better solution would be to add the WS_EX_NOACTIVATE
extended window style. I'm not aware of any property that exposes this functionality in WinForms, but you can set it manually during the window's creation by overriding the CreateParams
property. For example:
public class MyForm : Form
{
// ... other code ...
protected override CreateParams CreateParams {
get {
const int WS_EX_NOACTIVATE = 0x08000000;
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_NOACTIVATE;
return cp;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With