上一次提到了一个浮动的ListBox,实现得到方法是创建另外一个窗口。这一次呢,我就来给大家带来我的代码,希望大家喜欢。
不过在这之前,我先说明一下原理。一般情况下,我的原则是能够利用.NET本身提供的东西就尽量少使用API,原因见仁见智,我不罗嗦了。也就是说呢,这里并没有用到任何的API。
有人说了,得到Desktop的hWnd然后在上面画——我对于这个想法感到恐惧,因为你要手动画ListBox,手动处理一切的鼠标键盘事件。还有人说了,parent指向桌面,这个想法没有验证过,只是觉得如果这样仍然无法避免点击到这个ListBox的时候,“激活”转移到“桌面”(相当于你点击到桌面的任何一部分),从而使你的窗口失去“激活”,标题就变成绘画的了。当然,这也只是猜想。还有人说,获得WM_NCACTIVATE之后再创建相应的窗口……这个不好理解,不过似乎也没有解决“激活窗口”被转移造成的标题变色问题。
看起来这个问题很复杂,其实很简单,给大家看看重点部分的代码是什么样的:
public Form1()
{
InitializeComponent();
fcManager = new FloatingClientManager(this);
fcManager.AddClient(fLs);
}
private frmList fLs = new frmList();
private FloatingClientManager fcManager;
protected override void WndProc(ref Message m)
{
Message tm = fcManager.WinProc(ref m);
if (tm.Msg != m.Msg)
{
base.WndProc(ref tm);
}
base.WndProc(ref m);
}
{
InitializeComponent();
fcManager = new FloatingClientManager(this);
fcManager.AddClient(fLs);
}
private frmList fLs = new frmList();
private FloatingClientManager fcManager;
protected override void WndProc(ref Message m)
{
Message tm = fcManager.WinProc(ref m);
if (tm.Msg != m.Msg)
{
base.WndProc(ref tm);
}
base.WndProc(ref m);
}
也就是说,首先你需要一个FloatingClientManager(这个东西后面介绍,先吊一下胃口),构造的时侯指明“宿主”是哪一个Form,然后把所有“客户”窗口通过AddClient注册一下,最后在WndProc里面添加一小段代码,那么FloatingClientManager就会自动帮你处理所有事情了。很简单吧?
FloatingClientManager的原理是,通过截获WM_NCACTIVATE消息阻止“宿主窗口”的标题变色。大家如果看一下WM_NCACTIVATE的描述,就知道这个消息是用来通知这个窗口ACTIVATE状态改变,需要重绘窗口的标题(以及ICON)。看MSDN的描述,如果在DEACTIVATE(失去“激活”状态)的时候,返回FALSE就可以阻止标题重画,然而事实上并不是这样的,.NET在这里搞了点鬼——至于什么鬼不知道,那些是Native的代码,用Reflector看不到。如果你真的只是打算再调用完毕之后让m.Result = IntPtr.Zero,那么你会发现标题的颜色变了,并且还有一个更坏的消息:那个ListBox无法获得焦点了(甚至那个客户窗口也没办法获得焦点)。
那怎么做呢?呵呵,我的办法很奇特,就是在DEACTIVATE的时候,让.NET Framework误以为实际上是ACTIVATE,让他把标题画成“激活状态”的样子。其实我也说不清楚为什么这样能够成功,我也是在前面一个尝试失败的情况下,胡乱试试的——死马且当活马医,没想到还真的医活了!在看FloatingClientManager的代码之前,可以看看下面这个“另类”的效果图:
好了,废话不多说,赶紧看代码吧:
using System;
using System.Collections;
using System.Windows.Forms;
namespace NFAGEN2
{
/// <summary>
/// FloatingClientManager 浮动客户窗口管理器
/// </summary>
public class FloatingClientManager
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="HostForm">宿主窗口</param>
public FloatingClientManager(Form HostForm)
{
hostForm = HostForm;
if (HostForm == null)
{
throw new ArgumentException("HostForm must not be null");
}
}
/// <summary>
/// 宿主窗口
/// </summary>
private Form hostForm;
/// <summary>
/// 客户窗口 哈希表
/// </summary>
private Hashtable ht = new Hashtable();
/// <summary>
/// 整个程序程序是否处于“激活”状态
/// </summary>
private bool appActive;
private readonly IntPtr pFalse = IntPtr.Zero;
private readonly IntPtr pTrue = new IntPtr(1);
private const int WM_ACTIVATEAPP = 0x1C;
private const int WM_NCPAINT = 0x85;
private const int WM_NCACTIVATE = 0x86;
/// <summary>
/// 添加在这个宿主窗口上浮动的客户窗口
/// </summary>
/// <param name="Client">客户窗口</param>
public void AddClient(Form Client)
{
if (Client.TopLevel == false)
{
throw new ArgumentException("Client.TopLevel must be true");
}
ht.Add(Client, Client.Owner);
Client.Owner = hostForm;
}
/// <summary>
/// 删除客户窗口
/// </summary>
/// <param name="Client">客户窗口</param>
public void RemoveClient(Form Client)
{
Client.Owner = (Form) ht[Client];
ht.Remove(Client);
}
/// <summary>
/// 检测是否已经注册客户窗口
/// </summary>
/// <param name="Client">客户窗口</param>
/// <returns>包含则返回true</returns>
public bool ContainsClient(Form Client)
{
return ht.Contains(Client);
}
/// <summary>
/// 获得宿主窗口
/// </summary>
public Form HostForm
{
get
{
return hostForm;
}
}
/// <summary>
/// 获得客户窗口数组
/// </summary>
public Form[] Clients
{
get
{
Form[] clients;
clients = new Form[ht.Count];
ht.Keys.CopyTo(clients, 0);
return clients;
}
}
/// <summary>
/// 辅助处理函数,用于简化宿主窗口的WndProc函数的代码。
/// </summary>
/// <param name="m">消息m,可能会被部分修改</param>
/// <returns>如果需要首先“处理”另外一个消息,则返回需要首先处理的消息,否则返回消息m。</returns>
public Message WinProc(ref Message m)
{
// 不是宿主窗口,避免错误处理情况
if (m.HWnd != hostForm.Handle)
{
return m;
}
// 程序激活状态改变
if (m.Msg == WM_ACTIVATEAPP)
{
appActive = (m.WParam != pFalse);
Message tm = m;
tm.Msg = WM_NCACTIVATE;
return tm;
}
else if (appActive) // 如果应用程序处于激活状态
{
switch (m.Msg)
{
case WM_NCPAINT: // NC区域需要重绘,此时需要手动重绘标题,标题状态错误。
Message tm = m;
tm.Msg = WM_NCACTIVATE;
if ( Form.ActiveForm == hostForm ||
(Form.ActiveForm != null && ht.Contains(Form.ActiveForm)) )
{
tm.WParam = pTrue;
}
else
{
tm.WParam = pFalse;
}
return tm;
case WM_NCACTIVATE: // NC标题需要重绘,
// 让.NET Framework 以为宿主窗口是激活的(如果需要的话)。
if ( Form.ActiveForm == hostForm ||
(Form.ActiveForm != null && ht.Contains(Form.ActiveForm)) )
{
m.WParam = pTrue;
}
else
{
m.WParam = pFalse;
}
break;
}
}
return m;
}
}
}
using System.Collections;
using System.Windows.Forms;
namespace NFAGEN2
{
/// <summary>
/// FloatingClientManager 浮动客户窗口管理器
/// </summary>
public class FloatingClientManager
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="HostForm">宿主窗口</param>
public FloatingClientManager(Form HostForm)
{
hostForm = HostForm;
if (HostForm == null)
{
throw new ArgumentException("HostForm must not be null");
}
}
/// <summary>
/// 宿主窗口
/// </summary>
private Form hostForm;
/// <summary>
/// 客户窗口 哈希表
/// </summary>
private Hashtable ht = new Hashtable();
/// <summary>
/// 整个程序程序是否处于“激活”状态
/// </summary>
private bool appActive;
private readonly IntPtr pFalse = IntPtr.Zero;
private readonly IntPtr pTrue = new IntPtr(1);
private const int WM_ACTIVATEAPP = 0x1C;
private const int WM_NCPAINT = 0x85;
private const int WM_NCACTIVATE = 0x86;
/// <summary>
/// 添加在这个宿主窗口上浮动的客户窗口
/// </summary>
/// <param name="Client">客户窗口</param>
public void AddClient(Form Client)
{
if (Client.TopLevel == false)
{
throw new ArgumentException("Client.TopLevel must be true");
}
ht.Add(Client, Client.Owner);
Client.Owner = hostForm;
}
/// <summary>
/// 删除客户窗口
/// </summary>
/// <param name="Client">客户窗口</param>
public void RemoveClient(Form Client)
{
Client.Owner = (Form) ht[Client];
ht.Remove(Client);
}
/// <summary>
/// 检测是否已经注册客户窗口
/// </summary>
/// <param name="Client">客户窗口</param>
/// <returns>包含则返回true</returns>
public bool ContainsClient(Form Client)
{
return ht.Contains(Client);
}
/// <summary>
/// 获得宿主窗口
/// </summary>
public Form HostForm
{
get
{
return hostForm;
}
}
/// <summary>
/// 获得客户窗口数组
/// </summary>
public Form[] Clients
{
get
{
Form[] clients;
clients = new Form[ht.Count];
ht.Keys.CopyTo(clients, 0);
return clients;
}
}
/// <summary>
/// 辅助处理函数,用于简化宿主窗口的WndProc函数的代码。
/// </summary>
/// <param name="m">消息m,可能会被部分修改</param>
/// <returns>如果需要首先“处理”另外一个消息,则返回需要首先处理的消息,否则返回消息m。</returns>
public Message WinProc(ref Message m)
{
// 不是宿主窗口,避免错误处理情况
if (m.HWnd != hostForm.Handle)
{
return m;
}
// 程序激活状态改变
if (m.Msg == WM_ACTIVATEAPP)
{
appActive = (m.WParam != pFalse);
Message tm = m;
tm.Msg = WM_NCACTIVATE;
return tm;
}
else if (appActive) // 如果应用程序处于激活状态
{
switch (m.Msg)
{
case WM_NCPAINT: // NC区域需要重绘,此时需要手动重绘标题,标题状态错误。
Message tm = m;
tm.Msg = WM_NCACTIVATE;
if ( Form.ActiveForm == hostForm ||
(Form.ActiveForm != null && ht.Contains(Form.ActiveForm)) )
{
tm.WParam = pTrue;
}
else
{
tm.WParam = pFalse;
}
return tm;
case WM_NCACTIVATE: // NC标题需要重绘,
// 让.NET Framework 以为宿主窗口是激活的(如果需要的话)。
if ( Form.ActiveForm == hostForm ||
(Form.ActiveForm != null && ht.Contains(Form.ActiveForm)) )
{
m.WParam = pTrue;
}
else
{
m.WParam = pFalse;
}
break;
}
}
return m;
}
}
}
呵呵,这里的代码有点过于“简陋”了,可以说不是很完美,很安全。如果你觉得不爽,尽管修改好了。最后再来发扬一下“吊胃口”精神,给大家出示另外一个图片:
需要说明一下,这个“frmI...”窗口实际上是Form1上面的一个“控件”,Dock = DockStyle.Right。让一个窗口能够放到另外一个窗口上面,就像Label/TextBox一样,其实很简单,就是在构造函数里面添加一句:
SetTopLevel(false);
但是你会发现标题栏是灰色的!
我这里的标题栏可使蓝色的哦!
现在我可以非常简单的模拟VS.NET的那些能够Docking的窗口了!下一次再来公布这部分的代码,如果感兴趣的人比较多的话,感兴趣就回复哦!谢谢支持!