• 获得MDI客户端的“句柄”


    内容 介绍 MdiClient控制 一个参考能做什么 改变背景颜色 一个句柄可以做什么 改变边界样式 窗口消息能做什么 NativeWindow类 隐藏滚动条 先进的绘画 MdiClientController组件 使用组件 结论 介绍 在最近的一个项目,我决定使用一个多文档界面(MDI)将是最好的方法。我惊讶于有多容易创建MDI应用程序在Visual Studio . net平台。简单地设置IsMdiContainer System.Windows.Forms的财产。表单允许托管应用程序中其他形式的工作区。然而,如果你像我一样,你开始想知道,工作区将会看起来像一个不同的颜色,定制绘画,或者不同的边框样式。我很快发现,表单控制接触没有这样的属性来控制这种行为。搜索的网络显示,许多人想要做同样的事情,有各种方法关于如何做到这一点。在使用他们的建议成功在我的应用程序和创建几个我自己的,我决定收集所有这些信息在一个地方,也许开发组件,允许轻松地设置这些属性。 MdiClient控制 事实证明,MDI的Windows®只是另一种控制形式。当IsMdiContainer属性设置为true, System.Windows.Forms类型的控制。MdiClient添加到控件的集合形式。遍历表单的控制加载后将揭示MdiClient控制,也可能是最好的方式参考。MdiClient控制有一个公共构造函数,可以被添加到表单的控制编程集合,但更好的做法是设置表单的IsMdiContainer属性并让它做这项工作。设置参考MdiClient控制、迭代控制直到MdiClient控制发现:隐藏,复制Code

    MdiClient mdiClient = null;
    
    // Get the MdiClient from the parent form.
    for(int i = 0; i < parentForm.Controls.Count; i++)
    {
        // If the form is an MDI container, it will contain an MdiClient control
        // just as it would any other control.
        mdiClient = parentForm.Controls[i] as MdiClient;
        if(mdiClient != null)
        {
            // The MdiClient control was found.
            // ...
            //
    
            break;
        }
    }

    使用关键字是比直接在一个try / catch块或使用关键字,因为如果匹配类型,参考控制将结果或将返回null。这就像两个要求的价格。 注意:在测试中,我发现可以添加多个MdiClient控制表单的控制集合。在这种情况下,只有一个MdiClient控件将会作为主机和这段代码可能会失败,返回一个引用MdiClient不执行托管孩子的形式。一个很好的理由使用IsMdiContainer属性而不是增加手动控制形式。 一个参考能做什么 改变背景颜色 与参考MdiClient控制在手,许多常见的控制属性可以如你所愿。通常要求当然是改变背景颜色。应用程序的默认背景颜色工作区是全球所有的Windows®应用程序,可以在控制面板中改变。. net框架System.Drawing.SystemColors暴露了这种颜色。AppWorkspace静态属性。改变背景颜色是如你所愿,通过背景色属性:隐藏,复制Code

    // Set the color of the application workspace.
    mdiClient.BackColor = value;

    ,以及许多其他控件属性将与MdiClient控制按预期工作。 一个句柄可以做什么 改变边界样式 缺席MdiClient控制,然而,是一个边框样式属性。典型System.Windows.Forms一去不复返了。边框样式的枚举选项Fixed3D FixedSingle,没有。默认情况下,应用程序的工作区MDI形式是插图的3 d边界相当于Fixed3D是什么。只是因为这种行为并不是公开的控制并不意味着它是不可访问的。从这里开始,您将看到MdiClient变成更有价值的处理只是一个参考。 改变边界的外观要求使用Win32函数调用。(更多这方面的信息可以从杰森失去的文章:向用户控件添加可被识别的边界)。每个窗口(例如,控制)在Windows®可以通过使用GetWindowLong检索信息,并设置通过使用SetWindowLong函数。功能都需要一个标志,指定哪些信息我们想获取和设置。在这种情况下,我们感兴趣的是GWL_STYLE GWL_EXSTYLE,获取和设置窗口的风格和扩展窗口风格标志,分别。因为这些更改的非客户区控制,调用控件的无效方法不会导致边界重新粉刷。相反,我们调用SetWindowPos函数导致一个更新的非客户区。这些函数和常量的定义是这样的:隐藏,收缩,复制Code

    // Win32 Constants
    private const int GWL_STYLE   = -16;
    private const int GWL_EXSTYLE = -20;
    
    private const int WS_BORDER        = 0x00800000;
    private const int WS_EX_CLIENTEDGE = 0x00000200;
    
    private const uint SWP_NOSIZE           = 0x0001;
    private const uint SWP_NOMOVE           = 0x0002;
    private const uint SWP_NOZORDER         = 0x0004;
    private const uint SWP_NOREDRAW         = 0x0008;
    private const uint SWP_NOACTIVATE       = 0x0010;
    private const uint SWP_FRAMECHANGED     = 0x0020;
    private const uint SWP_SHOWWINDOW       = 0x0040;
    private const uint SWP_HIDEWINDOW       = 0x0080;
    private const uint SWP_NOCOPYBITS       = 0x0100;
    private const uint SWP_NOOWNERZORDER    = 0x0200;
    private const uint SWP_NOSENDCHANGING   = 0x0400;
    
    
    // Win32 Functions
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern int GetWindowLong(IntPtr hWnd, int Index);
    
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern int SetWindowLong(IntPtr hWnd, int Index, int Value);
    
    [DllImport("user32.dll", ExactSpelling = true)]
    private static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
        int X, int Y, int cx, int cy, uint uFlags);

    注意:在定义这些常数的值h头文件,通常由平台SDK或Visual Studio . net安装。 我们可以根据BorderStyle枚举来调整边框:在扩展的窗口样式(Fixed3D)中包含一个WS_EX_CLIENTEDGE标志,在标准窗口样式(FixedSingle)中包含一个WS_BORDER标志,或者为无边框删除这两个标志(None)。然后调用SetWindowPos函数导致更新。SetWindowPos函数有很多选项,但我们只想重新绘制非客户区,并将传递必要的标志来完成此工作:Hide  收缩,复制Code

    // Get styles using Win32 calls
    int style = GetWindowLong(mdiClient.Handle, GWL_STYLE);
    int exStyle = GetWindowLong(mdiClient.Handle, GWL_EXSTYLE);
    
    // Add or remove style flags as necessary.
    switch(value)
    {
        case BorderStyle.Fixed3D:
            exStyle |= WS_EX_CLIENTEDGE;
            style &= ~WS_BORDER;
            break;
    
        case BorderStyle.FixedSingle:
            exStyle &= ~WS_EX_CLIENTEDGE;
            style |= WS_BORDER;
            break;
    
        case BorderStyle.None:
            style &= ~WS_BORDER;
            exStyle &= ~WS_EX_CLIENTEDGE;
            break;
    }
    
    // Set the styles using Win32 calls
    SetWindowLong(mdiClient.Handle, GWL_STYLE, style);
    SetWindowLong(mdiClient.Handle, GWL_EXSTYLE, exStyle);
    
    // Update the non-client area.
    SetWindowPos(mdiClient.Handle, IntPtr.Zero, 0, 0, 0, 0,
        SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
        SWP_NOOWNERZORDER | SWP_FRAMECHANGED);

    窗口消息能做什么 NativeWindow类 除了更改简单的属性或进行Win32调用外,要进入定制领域,我们需要拦截和处理窗口消息。不幸的是,MdiClient类是密封的,因此不能被子类化,也不能覆盖它的WndProc方法。值得庆幸的是,System.Windows.Forms。NativeWindow类来解决这个问题。NativeWindow类的目的是提供“窗口句柄和窗口过程的低级封装”。换句话说,它允许我们进入控件接收到的窗口消息。要使用NativeWindow,从类继承并覆盖它的WndProc方法。一旦通过AssignHandle方法将控件的句柄分配给NativeWindow, WndProc方法的行为就像它是控件的WndProc方法一样。通过侦听MdiClient控件的窗口消息,可以实现一系列全新的定制。 隐藏滚动条 虽然通过滚动条访问应用程序工作区之外的控件是一个很好的特性,但我个人不记得我使用过的MDI应用程序有同样的功能。在MdiClient中关闭或隐藏滚动条的功能可能比更改其颜色更常见。 MdiClient控件的滚动条是它的非客户区域(client矩形之外的区域)的一部分,它们本身不是MdiClient的父控件。这就排除了更改滚动条可见性的可能性,并给我们留下了影响非客户区大小的窗口消息和Win32函数。当需要计算控件的非客户区域时,将向控件发送WM_NCCALCSIZE消息。为了隐藏滚动条,我们可以告诉Windows®,非客户区比它实际的面积小一点,并盖住滚动条。我的第一个方法是尝试确定非客户区域的大小,但失败了。更好的方法是在使用ShowScrollBar Win32函数计算非客户区时隐藏滚动条。ShowScrollBar函数需要窗口句柄、隐藏的滚动条和指示其可见性的bool:复制Code

    // Win32 Constants
    private const int SB_HORZ = 0;
    private const int SB_VERT = 1;
    private const int SB_CTL  = 2;
    private const int SB_BOTH = 3;
    
    // Win32 Functions
    [DllImport("user32.dll")]
    private static extern int ShowScrollBar(IntPtr hWnd, int wBar, int bShow);
    
    protected override void WndProc(ref Message m)
    {
        switch(m.Msg)
        {
            //
            // ...
            //
    
            case WM_NCCALCSIZE:
                ShowScrollBar(m.HWnd, SB_BOTH, 0 /*false*/);
                break;
        }
    
        base.WndProc(ref m);
    }

    在隐藏了滚动条之后,WM_NCCALCSIZE消息像往常一样被处理并且计算非客户区域减去最近隐藏的滚动条。如果你想知道,通过ShowScrollBar函数隐藏滚动条不会保持滚动条隐藏,它会立即重置为可见。这就是为什么每次计算非客户区时必须隐藏它的原因。 先进的绘画 在web的。net论坛中,我看到的另一个常见请求是,“如何将图像放到MDI表单的应用程序工作区中?”最简单的方法是,一旦有了对MdiClient的引用,就侦听Paint事件。在某些情况下,这可能工作良好,但我注意到一个非常糟糕的闪烁每次MdiClient调整大小。这是绘制没有被双缓冲和绘制调用都在WM_PAINT和WM_ERASEBKGND消息的结果。如果我们能够从MdiClient控件继承,这可以通过使用该控件的受保护方法SetStyle和标志system . window . forms . controlstyles来轻松补救。AllPaintingInWmPaint ControlStyles。DoubleBuffer, ControlStyles.UserPaint。但如前所述,MdiClient类是密封的,这不是一个选项。一个选项是监听WM_PAINT和WM_ERASEBKGND窗口消息并实现我们自己的自定义绘制。(更多信息请参阅Steve McMahon的文章:MDI客户区的绘画)。 我们需要的Win32项目是函数BeginPaint和EndPaint,称为PAINTSTRUCT和RECT的structs,以及一些更多的常量:Hide收缩,复制Code

    // Win32 Constants
    private const int WM_PAINT       = 0x000F;
    private const int WM_ERASEBKGND  = 0x0014;
    private const int WM_PRINTCLIENT = 0x0318;
    
    
    // Win32 Structures
    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    private struct PAINTSTRUCT
    {
        public IntPtr hdc;
        public int fErase;
        public RECT rcPaint;
        public int fRestore;
        public int fIncUpdate;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)] 
        public byte[] rgbReserved;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
    
    // Win32 Functions
    [DllImport("user32.dll")]
    private static extern IntPtr BeginPaint(IntPtr hWnd, 
                                    ref PAINTSTRUCT paintStruct);
    
    [DllImport("user32.dll")]
    private static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT paintStruct);

    双缓冲的典型方法是对图像进行所有绘制,或者从图像中获取图形对象,而不是直接绘制到屏幕上。当绘制图像完成时,图像本身被绘制到屏幕上。这样,所有控件的绘制都是一次显示,而不是可能正在进行的间歇绘制。由于MdiClient控件的图形如此简单,我们可以很容易地自己绘制所有的图形,but更好的做法是不能消除的基本图形绘制,但将它们添加到我们的定制绘画。这样,如果MdiClient改变在某种程度上我们没有预料,这幅画仍应正确显示。这是通过创建自己的窗口消息(WM_PRINTCLIENT),并将其发送给基地控制使用DefWndProc(即违约指向)方法。我们回到原始图形缓冲区是一幅画的画的基本控制。(这可以从J年轻的文章:生成失踪油漆TreeView和ListView控件的事件。)从那里,任何自定义画在上面可以处理:隐藏,收缩,复制Code

    protected override void WndProc(ref Message m)
    {
        switch(m.Msg)
        {
            //Do all painting in WM_PAINT to reduce flicker.
            case WM_ERASEBKGND:
                return;
    
            case WM_PAINT:
    
                // Use Win32 to get a Graphics object.
                PAINTSTRUCT paintStruct = new PAINTSTRUCT();
                IntPtr screenHdc = BeginPaint(m.HWnd, ref paintStruct);
    
                using(Graphics screenGraphics = Graphics.FromHdc(screenHdc)) 
                {
                    // Double-buffer by painting everything to an image and
                    // then drawing the image.
                    int width = (mdiClient.ClientRectangle.Width > 0 ? 
                                       mdiClient.ClientRectangle.Width : 0);
                    int height = (mdiClient.ClientRectangle.Height > 0 ? 
                                      mdiClient.ClientRectangle.Height : 0);
                    using(Image i = new Bitmap(width, height))
                    {
                        using(Graphics g = Graphics.FromImage(i))
                        {
                            // Draw base graphics and raise the base Paint event.
                            IntPtr hdc = g.GetHdc();
                            Message printClientMessage =
                                Message.Create(m.HWnd, WM_PRINTCLIENT, 
                                                     hdc, IntPtr.Zero);  
                            DefWndProc(ref printClientMessage);
                            g.ReleaseHdc(hdc);
    
                            //
                            // Custom painting here...
                            //
                        }
    
                        // Now draw all the graphics at once.
                        screenGraphics.DrawImage(i, mdiClient.ClientRectangle);
                    }
                }
    
                EndPaint(m.HWnd, ref paintStruct);
                return;
        }
    
        base.WndProc(ref m);
    }

    注意:更多信息BeginPaint、EndPaint PAINTSTRUCT,矩形,可以找到WM_PRINTCLIENT平台SDK或MSDN图书馆。 请注意,在这种情况下,我们不让WM_PAINT消息处理失败的基础,因为这会使它指向做其默认画就在我们刚刚做的自己。WM_ERASEBKGND消息被忽略因为我们想做所有的画一次WM_PAINT消息。现在,MdiClient控制油漆事件将不再闪烁和自定义画代码可以放在上面WM_PAINT消息的处理。 MdiClientController组件 而不必把这段代码放到每一个项目都使用多文档界面,我们可以结束这一切System.ComponentModel。组件可以从项目复制到设计图面。源文件是一个组件中包括我叫Slusser MdiClientController和发现。组件名称空间。组件继承自NativeWindow并实现了System.ComponentModel。托管接口给它组件的行为。它包含的所有功能前面讨论的一些属性,这些属性使它容易的地方一个图像在应用程序的工作区。 使用组件与一个MDI形式,只有父窗体必须传递给构造函数或通过ParentForm属性设置。设置MdiClientController组件的ParentForm属性的设计师,我们需要定制站点属性来确定组件放到一个表格。它有助于在这里有一个设计师的知识。如果确实将组件放到表单,我们设置ParentForm属性,它是设计师正确序列化代码:隐藏,复制Code

    public ISite Site
    {
        get { return site; }
        set
        {
            site = value;
    
            if(site == null)
                return;
    
            // If the component is dropped onto a form during design-time,
            // set the ParentForm property.
            IDesignerHost host = 
              (value.GetService(typeof(IDesignerHost)) as IDesignerHost);
            if(host != null)
            {
                Form parent = host.RootComponent as Form;
                if(parent != null)
                    ParentForm = parent;
            }
        }
    }

    创建这个组件的一个挑战是知道什么时候该组件将被初始化。中初始化组件放到设计师InitializeComponent形式的构造函数的方法。如果你检查InitializeComponent方法创建的设计师,你会注意到表单的属性设置的最后一件事。如果MdiClientController扫描MdiClient控制形式的控制收集表单的IsMdiContainer属性设置之前,没有MdiClient控制会被发现。解决方案是创建知道当父窗体的句柄。这肯定会显示所有子控件和变量被初始化,当我们可以开始寻找MdiClient。如果父窗体没有处理ParentForm属性设置时,该组件会听表单的HandleCreated事件然后得到MdiClient:隐藏,收缩,复制Code

    public Form ParentForm
    {
        get { return parentForm; }
        set
        {
            // If the ParentForm has previously been set,
            // unwire events connected to the old parent.
            if(parentForm != null)
                parentForm.HandleCreated -= 
                  new EventHandler(ParentFormHandleCreated);
    
            parentForm = value;
    
            if(parentForm == null)
                return;
    
            // If the parent form has not been created yet,
            // wait to initialize the MDI client until it is.
            if(parentForm.IsHandleCreated)
            {
                InitializeMdiClient();
                RefreshProperties();
            }
            else
                parentForm.HandleCreated += 
                  new EventHandler(ParentFormHandleCreated);
        }
    }
    
    
    private void ParentFormHandleCreated(object sender, EventArgs e)
    {
        // The form has been created, unwire the event,
        // and initialize the MdiClient.
        parentForm.HandleCreated -= 
             new EventHandler(ParentFormHandleCreated);
        InitializeMdiClient();
        RefreshProperties();
    }

    使用组件 一旦MdiClientController被添加到工具箱,直接拖到形式的设计师,或者双击它,它将显示在组件托盘的设计师。MdiClientController不会改变形式的IsMdiContainer财产,所以你必须设置它。所有组件的属性遵循. net命名约定。边框样式功能是包裹在边框样式属性。滚动条的隐藏,我想,是最好的属性放在一个可以自动滚动。背景色和油漆事件现在可以从设计师为您的方便。此外,有三个属性,控制图像的显示在客户区。图像属性集图像的显示,ImageAlign属性将在客户区不同位置,和StretchImage属性将它填满整个客户区。此外,我添加了一个HandleAssigned事件表明当MdiClient被发现和处理分配到NativeWindow。当然,这一切都可以通过编程的方式完成。 结论 和许多项目,成为文章一样,我有我最初需要大约30分钟,但是花了几天准备的东西我可以分享我的程序员。生成的组件应该足够了对于大多数请求关于MDI的外观形式。效果很好,它表现好,让应用程序看起来不错(ly)。还有更多,可以添加到组件,如果需要,我相信一些程序员,它将。或障碍,而有一个特点,我谦卑地承认我并不是能够克服:设计时预览。使用反射器,我发现大量的路障,防止设计时MDI的预览区域。我欢迎任何建议如何克服这个问题。享受。 本文转载于:http://www.diyabc.com/frontweb/news5088.html

  • 相关阅读:
    你眼中的程序员 VS 程序员眼中的自己,是时候打破代沟了
    GaussDB(for openGauss)让数据“存得下、算得快、算得准”
    初识GaussDB(for Cassandra)
    云原生势不可挡,华为云GaussDB加速企业数字化转型
    HDC.Cloud2021|开发者们都在谈的云原生到底长什么样?
    基于深度神经网络的噪声标签学习
    华为云PB级数据库GaussDB(for Redis)揭秘第七期:高斯Redis与强一致
    Delphi 窗体函数GetActiveWindow
    Delphi 窗体函数GetWindowText -获取窗口的标题
    深度学习数据预处理
  • 原文地址:https://www.cnblogs.com/Dincat/p/13462278.html
Copyright © 2020-2023  润新知