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...

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  263 随笔 :: 19 文章 :: 3009 评论 :: 74万 阅读
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

我记得第二次的时候给大家透露过可以把窗口变成“控件”一样,放到某个窗口“里面”。其实要变成“控件”那样,在某个空间里面其实很简单的,只要在构造函数里面添加一句: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的设计窗口,看:

现在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的帮助就可以知道是操作系统希望窗口告诉它,用户鼠标所点击到的地方是什么部位(比如标题栏、可以改变大小的边框部分或者工作区等等)。这个时候我们可以这样写:

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

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

}

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

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

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

    }

    
break;

好了这个问题也解决了。需要我给大家一个完整的代码吗?没问题,待会儿我整理好了就贴出来,不过仍然有各种各样的问题,不过至少给你带来了一个可以Docking的Form“控件”的解决方案了。稍后在这里面给出链接(就在这句话的下一行),敬请关注。
posted on   Sumtec  阅读(1808)  评论(2编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
点击右上角即可分享
微信分享提示