有没有什么方法可以在 WinForms 中创建一个工具窗口,只要托管表单有焦点,工具窗口也可以吗?Paint.NET 中就是一个例子:
我在 .Net 4.0 下使用 C# 作为后端应用程序语言。
Paint.NET 中的工具窗口就是这样的工具窗口。在 Win32 术语中,您可以通过创建具有WS_EX_TOOLWINDOW
扩展窗口样式的窗口来实现这一点:
该窗口旨在用作浮动工具栏。工具窗口的标题栏比普通的标题栏短,并且窗口标题使用较小的字体绘制。工具窗口不会出现在任务栏或用户按 ALT+TAB 时出现的对话框中。
在 WinForms 中,这是由FormBorderStyle
属性控制的。将其设置为FormBorderStyle.FixedToolWindow
或FormBorderStyle.SizableToolWindow
在表单的构造函数中。
您还需要确保为工具窗口指定所有者窗口。它的所有者应该是您的主要表单,它用作工具调色板。您通常在显示表单时执行此操作,使用Show
允许您指定所有者窗口的方法的重载。
最后,Paint.NET 的另一个很酷的效果(我认为,如果我没记错的话)是工具窗口永远无法真正获得焦点。您可以与它们进行交互,单击按钮来选择工具,但您实际上不能将焦点设置到浮动调色板。它总是回到主窗口。模拟这种行为的幼稚尝试可能是在一个焦点更改通知(例如,Activate
事件)中重置焦点,但这不是一个好主意,原因有很多。更好的解决方案是添加WS_EX_NOACTIVATE
扩展窗口样式。我不知道在 WinForms 中公开此功能的任何属性,但您可以在创建窗口期间通过覆盖该CreateParams
属性手动设置它。例如:
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;
}
}
}
Paint.Net 旧版本的源代码可在Openpdn Fork of Paint.NET 3.36.7 中获得
我试图从源代码中提取他们的方法到我能收集到的最简洁的工作示例中:
Pinvoking类:
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);
}
基本形式:
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);
}
}
始终处于活动状态的 Form2(除非应用程序未处于活动状态):
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);
}
}
}
}
无需将 Form2 的 TopMost 设置为 true,因为它在显示时应该归主窗体所有。此外,Form2不是MDI 子窗体。
我不知道 Windows 窗体是否对此具有内置功能,但您可以使用以下代码实现您想要的功能:
对于主要形式:
private ToolForm m_toolForm;
private void MainForm_Load(object sender, EventArgs e)
{
m_toolForm = new ToolForm ();
m_toolForm.Show();
}
private void MainForm_Resize(object sender, EventArgs e)
{
switch (WindowState)
{
case FormWindowState.Minimized:
m_toolForm.Hide();
break;
case FormWindowState.Maximized:
m_toolForm.Show();
break;
}
}
对于工具形式: 您不需要任何代码,只需将属性“TopMost”设置为 true。
对于使用 DevExpress RibbonForm 的用户,请使用以下链接中的解决方案来解决焦点问题。zip 文件位于解决方案的注释中,而不是解决方案本身:
http://www.devexpress.com/Support/Center/Question/Details/Q498321