SUMTEC -- There's a thing in my bloglet.

But it's not only one. It's many. It's the same as other things but it exactly likes nothing else...

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

上一次提到了一个浮动的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);
        }


也就是说,首先你需要一个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;
        }


    }

}


呵呵,这里的代码有点过于“简陋”了,可以说不是很完美,很安全。如果你觉得不爽,尽管修改好了。最后再来发扬一下“吊胃口”精神,给大家出示另外一个图片:


需要说明一下,这个“frmI...”窗口实际上是Form1上面的一个“控件”,Dock = DockStyle.Right。让一个窗口能够放到另外一个窗口上面,就像Label/TextBox一样,其实很简单,就是在构造函数里面添加一句:
SetTopLevel(false);

但是你会发现标题栏是灰色的!

我这里的标题栏可使蓝色的哦!

现在我可以非常简单的模拟VS.NET的那些能够Docking的窗口了!下一次再来公布这部分的代码,如果感兴趣的人比较多的话,感兴趣就回复哦!谢谢支持!
posted on 2004-07-19 16:35  Sumtec  阅读(2711)  评论(7编辑  收藏  举报