吊胃口 IV —— 让你的窗口成为“控件”

我记得第二次的时候给大家透露过可以把窗口变成“控件”一样,放到某个窗口“里面”。其实要变成“控件”那样,在某个空间里面其实很简单的,只要在构造函数里面添加一句:SetTopLevel(false); 或者TopLevel = false;。这两句话的作用几乎就是一样的,这两句话的作用都是指定这个空间不是顶层控件。

我们现在假设主窗口类的名称是Form1,我们准备变成“控件”窗口类的名称是frmInner。经过上面的修改,我们还是没有办法在ToolBox上面找到frmInner这个“控件”可以供我们拖到Form1上面,没办法,我们只好手动“拖”这个“控件”到Form1上面。在Form1里面随便找一个地方添加一个定义:
private frmInner fInner;
然后到InitializeComponent()函数里面最后添加下面几句:
fInner = new frmInner();
fInner.Dock = DockStyle.Right;
fInner.Parent = this;
然后在构造函数调用完InitializeComponent()函数之后添加下面这一句话(不要写到InitializeComponent里面):
fInner.Visible = true;

然后我们打开Form1的设计窗口,看:
tmp_nfagen2_proto_04.jpg

现在fInner就乖乖的跑到Form1里面了。不过不要高兴得太早,运行看一下,你就发现有好几个问题存在:
1、标题栏永远都是灰色的。
2、fInner竟然可以移动,甚至还可以改变高度,要知道这个时候他应该是Dock在右边的。
3、如果Form1是MdiContainer,并且fInner.Dock 不是None也不是Fill,按照下面的步骤操作:
   (1) 把fInner变宽。
   (2) 最大化Form1(或者改变它的大小)
   (3) 把fInner变窄。
   你就会发现这个时候Form1里面有一个凹下去方框,这个方框里面应该是MdiChild窗口的区域,现在这个方框的大小没有修正,因此你可以很清晰地看到Form1右边fInner左边有一条很明显的白线。这一来不雅观,二来不符合逻辑。
4、最小化fInner竟然不见掉了!

前面三个问题我已经解决了,就剩下最后一个问题还没有解决。其实还有好多其他的问题没有解决,这里权当给大家抛砖引玉,这里就说说前面三个问题的解决方法。

首先标题栏永远是灰色的问题其实就是要你在适当的时候用Msg = 0x86的消息,调用WndProc函数。那么什么时候是恰当的呢?有两个主要的消息需要我们捕获,一个是WM_MOUSEACTIVATE 0x0021,还有一个是WM_COMMAND 0x111。
当你用鼠标点中了fInner(不是fInner里面的其他任何控件比如TextBox),就会获得WM_MOUSEACTIVATE,这个时候你需要做一件事情:this.Focus();
上面的那个this实际上是fInner,不是Form1,不要理解错了,这么做的作用后面会说到。当我们收到这个消息,并且执行完上面的那句话,就需要用Msg = 0x0086的消息调用base.WndProc函数,引起标题的变化。
而当你点中了fInner里面的其他任何控件比如TextBox,你会获得WM_COMMAND,并且WPARAM的高16位的值是0x0100(或者0x200,如果这个控件失去焦点,并且焦点离开了fInner)。当我们捕获到了这个消息,需要判断WPARAM高16位是否为0x100或者0x200,如果是那么也需要用Msg = 0x0086的消息调用base.WndProc函数,引起标题的变化。

引起标题变化的时候,需要首先判断this.ContainsFocus,如果包含,那么就应该让WPARAM = 1,否则为0。因此当我们接受到WM_MOUSEACTIVATE的时候,一定要首先让自己获得焦点,否则无法正确引起标题变化。到这里,第一个问题解决了,下面解决另外一个问题。

经过测试,我已经确认了改变窗口位置和大小的行为完全受操作系统而不是.NET Framework的掌握,也就是说如果我们截获WM_MOVE、WM_SIZE等消息,并做出相应修改,根本就不起任何作用。这个时候需要看鼠标点击标题栏和边框的时候发生了些什么事情。经过分析,可以知道首先发生的一件事情就是获得WM_NCHITTEST消息,这个消息查MSDN的帮助就可以知道是操作系统希望窗口告诉它,用户鼠标所点击到的地方是什么部位(比如标题栏、可以改变大小的边框部分或者工作区等等)。这个时候我们可以这样写:

None.gif base .WndProc( ref  m);
None.gif
if  ( this .Dock  !=  DockStyle.None)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif   
int mask = 0;
InBlock.gif   
switch(this.Dock)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      
case DockStyle.Left:
InBlock.gif         mask 
= 11// HTRIGHT
InBlock.gif
         break;
InBlock.gif      dot.gif
// 10 = HTLEFT, 15 = HTBOTTOM,  12 = HTTOP
ExpandedSubBlockEnd.gif
   }

InBlock.gif   
int i = m.Result.ToInt32();
InBlock.gif   
if (i == 2 || (i > 9 && i < 18 && i != mask)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif      m.Result 
= new IntPtr(1)
ExpandedSubBlockEnd.gif   }

ExpandedBlockEnd.gif}

None.gif
return //  避免调用WndProc最后的那句base.WndProc

也就是说,如果处于Docking状态,那么无论如何点击标题栏都要视为点击普通的工作区(就是除了标题栏和边框之外的,可以摆放控件的地方),同时根据具体的Docking情况,不应该允许改变大小的边框位置也应该视之为普通区域。当然,你还可以做得更好一点,比如如果是左上角或者左下角,并且Dock右边,则mask=11。到这里,第二个问题也部分解决了,实际上还是有一些其他的Bug,比如说:
(1) 最大化fInner
(2) 最大化Form1
你就会发现不对劲的地方了,具体怎么解决现在我还没有试出来,大家自己思考一下吧。

下面解决第三个问题。
第三个问题其实很好解决,只要在获得WM_SIZING 0x0214 的时候,判断parent是否为一个MdiContainer的Form,如果是,则调用Parent的PerformLayout(this, "Bounds"),具体一点说:
None.gif case   0x214 :
None.gif    
if  (client.Dock  !=  DockStyle.None)
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif {
InBlock.gif        Form host 
= client.Parent as Form;
InBlock.gif        
if (host != null && host.IsMdiContainer)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            host.PerformLayout(client, 
"Size");
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

None.gif    
break ;
None.gif

好了这个问题也解决了。需要我给大家一个完整的代码吗?没问题,待会儿我整理好了就贴出来,不过仍然有各种各样的问题,不过至少给你带来了一个可以Docking的Form“控件”的解决方案了。稍后在这里面给出链接(就在这句话的下一行),敬请关注。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值