zoukankan      html  css  js  c++  java
  • Winforms: 复杂布局改变大小时绘制错误

    一、        问题描述

    当一个Form非常复杂,里面的控件嵌套层次很深时,我们发现在改变Form大小的时候,处于最内层的控件会绘制错误。当我们设置了相应Layout之后,通常内层的控件在外层控件的大小改变时应该也随之改变。当问题出现时,我们期待的内层控件没有变化。

    二、        问题重现

    1. 新建一个Winforms工程;
    2. 在Form上添加一个Button,一个Label和一个Panel;
    3. 在把panel1的Anchor属性设为Top|Bottom|Left|Right;
    4. 在类Form1中添加如下代码:

            public Form1()

            {

                InitializeComponent();

                UpdateLevel();

            }

            int level = 1;

            private void UpdateLevel()

            {

                label1.Text = "Level: " + level.ToString();

            }

            Panel lastPanel;

            private void button1_Click(object sender, EventArgs e)

            {

                Panel panel = new Panel();

                Random random = new Random((int)DateTime.Now.ToBinary());

                panel.BackColor = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256));

                panel.Padding = new Padding(5, 5, 5, 5);

                panel.Location = new Point(5, 5);

                panel.Size = new Size(Math.Max(lastPanel.Width - 10, 0), Math.Max(lastPanel.Height - 10, 0));

                panel.Dock = DockStyle.Fill;

                lastPanel.Controls.Add(panel);

                lastPanel = panel;

                level++;

                UpdateLevel();

            }

    上述代码主要功能是嵌套添加Panel;

    1. 编译运行;
    2. 反复点击Button,同时改变Form的大小,观察绘制的结果。当嵌套的深度到达一定程度时(我的电脑是28),最内层的Panel绘制出现问题。

    三、        原因分析

    当我们改变Form的大小的时候,会调用Form的OnLayout方法,在Form的OnLayout的方法里,会调用最外层控件的OnLayout的方法,外层控件的OnLayout函数会调用内层OnLayout方法。因此,Control.OnLayout是一个递归调用的函数。当我们控件嵌套层次太深的时候,这个调用栈会很深。

    在OnLayout里,我们会调用Windows一个API:SetWindowPos。通常情况下,我们调用SetWindowPos去改变一个Windows窗口的位置和大小的时候,Windows会给该窗口发送WM_WINDOWPOSCHANGED。Winforms在该消息的处理函数里,会去调整窗口的大小并重新绘制。

    当调用栈超过一定限度的时候,我们发现Windows并没有向窗口发送消息WM_WINDOWPOSCHANGED,于是Winforms就不能在其处理函数里图调整窗口的大小并重新绘制。由于消息是由Windows发送出来的,我们站在Winforms的角度不能知道该消息没有发出来的真正原因。

    四、        解决办法

    由于问题的真正原因是Windows没有发出WM_WINDOWPOSCHANGED消息,因此我们不能从根本上解决这个问题。但我们可以想办法去绕过这个问题。

    正如前面分析所提到的,这个问题的出现与调用栈太深有关。如果我们能缩短递归调用栈的深度,也就能绕过这个问题。

    因此我们的第一个建议是减少Form上的控件嵌套层次。经过大量实验,我们发现出现这个问题时,在32位操作系统上嵌套层次超过25层,在64位机器上嵌套层次超过15层(不同机器数据略有不同)。当控件的嵌套层次超过15层时,这个Form会非常复杂。因此我们可以考虑简化Form的设计,减少控件的嵌套层次。

    如果我们确实需要很深的嵌套层次,我们可以尝试另外一个办法:是用异步调用的办法来减少调用栈的深度。我们在一个函数里异步调用另一个函数时,原函数会马上继续,而不用等待被调用函数返回,因此也就减少了调用栈的深度。

    我们可以选择一个控件里面只有少数(最好只有一个)子控件,不设置它子空间的Anchor和Dock属性,而在该控件的Layout事件处理器里自己处理子控件的布局,也就是显式调整子控件的位置和大小。由于我们需要用异步调用的方式去缩短栈的深度,因此我们可以用Control.BeginInvoke来设置Control.Size。下面是一段参考代码:

    1. 添加如下代码:

            private delegate void SetControlSizeDelegate(Control control, Size size);

            private void SetControlSize(Control control, Size size)

            {

                control.Size = size;

            }

            private void panel_Layout(object sender, LayoutEventArgs e)

            {

                Panel panel = sender as Panel;

                if (panel != null && panel.IsHandleCreated && panel.Controls.Count == 1)

                {

                    Control control = panel.Controls[0];

                    Size size = new Size(Math.Max(panel.Width - control.Padding.Left - control.Padding.Right, 0),

                        Math.Max(panel.Height - control.Padding.Top - control.Padding.Bottom, 0));

                    SetControlSizeDelegate myDelegate = new SetControlSizeDelegate(SetControlSize);

                    this.BeginInvoke(myDelegate, new object[] { control, size });

                }

            }

    这段代码的主要功能就是在Layout的事件处理器中用BeginInvoke异步修改子控件的大小。

    2. 在Form1的构造函数里添加代码:

    panel1.Layout += panel_Layout;

    3. 在button1_Click里删除对Panel的Dock设置。我们将在Panel的Layout事件处理器里调整子控件的布局

    4. 在button1_Click里添加代码:

    panel.Layout += panel_Layout;

  • 相关阅读:
    第十二周作业
    第十一周作业
    第十周作业
    第九周作业
    第八周作业
    bzoj3561DZY Loves Math VI
    bzoj3529[Sdoi2014]数表
    bzoj3309DZY Loves Math
    bzoj2823[AHOI2012]信号塔
    bzoj2301[HAOI2011]Problem b
  • 原文地址:https://www.cnblogs.com/rxie/p/1760835.html
Copyright © 2011-2022 走看看