• 立即显示WinForms使用如图所示()事件


    介绍 我最讨厌的事情之一就是表单不能立即出现。您一直可以看到:用户单击一个按钮,然后等待几秒钟,直到预期的UI出现。这通常是由于新创建的表单在其Load()事件处理程序中执行一些耗时的(阻塞的)任务(或者在非-中执行)。NET世界,WM_INITDIALOG消息处理程序)。除了糟糕的UI设计(永远不要让用户疑惑发生了什么!)之外,它还会带来一些真正不希望看到的后果:单击按钮不会立即产生效果,用户会倾向于再次单击按钮,这可能会导致操作被调用两次。 从我最早的Windows编程开始,我总是在我的表单(对话框)中实现“即时反馈”。我的技术随着Windows API的发展而发展;我将讨论我的旧技术(仍可在. net中使用),并以使用. net WinForms api的当前实现结束。 我们的目标 创建一个简单、可靠的机制来进行后加载处理,以确保表单在后加载处理开始之前被“完全呈现”。这意味着表单本身中的所有控件以及所有子控件(在对话框模板中)都按照用户预期的方式绘制。 背景 这是一个非常基本的技巧;WinForms编程新手应该能够实现它。 了解表单(又称窗口、对话框)的启动顺序非常有用:http://msdn.microsoft.com/en-us/library/86faxx0d%28VS.80%29.aspx。 大多数这些事件都有直接的Windows message (WM_*)类似物。但是,. net添加了System.Windows.Forms.Form。所显示的事件——没有相应的Windows消息——并且该事件是执行post-Load()处理的一种相对干净的方式的基础。(MSDN文档关于show()事件:在这里阅读)。 使问题复杂化的是消息的异步性、不可完全预测的特性。在创建一个窗口(以及它的子窗口)时,各个窗口消息的确切顺序在实例之间是不同的。当然,每个窗口的消息每次都以相同的顺序出现,但是父/子消息的顺序是不可预测的,从而创建了竞争条件。最不幸的结果是,有时对话框会正确呈现,有时则不能。消除这种不确定性——确保可预测性——是这一解决方案的重要组成部分。 最后,在表单和所有子元素完全呈现之后执行阻塞处理非常重要。在渲染过程中阻塞UI线程(和消息队列),会导致一些相当难看的UI,看起来就像你的应用崩溃了! 请注意表单的框架和未剪切的客户区域是如何呈现的,但是某些子控件的客户区域仍然在表单“下面”显示UI。还要注意,listbox是如何呈现的,而其他控件(groupbox、combobox)没有呈现(我还没有研究为什么会这样)。 下面是它完全渲染后的样子: 解决方案:在。net之前 (如果你在。net之前写Windows代码——不管有没有MFC——这看起来应该很熟悉。) 以下是创建窗口时生成的消息的简化顺序: 隐藏,复制Code

    WM_CREATE (window) or WM_INITDIALOG (dialogbox)
    ...
    WM_SHOWWINDOW
    ...
    WM_ACTIVATE (wParam has WA_* state)

    在那些日子里,我做了这样的事情: 隐藏,复制Code

    #define WMUSER_FIRSTACTIVE  (WM_USER+1)
    bool m_bSeenFirstActivation = false;
    virtual void DoPostLoadProcessing();
    
    LRESULT WndProc(msg, wparam, lparam)
    {
        switch(msg)
        {
            case WM_ACTIVATE:
                if((wparam==WA_ACTIVE) && !m_bSeenFirstActivation)
                {
                    m_bSeenFirstActivation = true;
                    PostMessage(m_hWnd, WMUSER_FIRSTACTIVE);
                }
                break;
            case WMUSER_FIRSTACTIVE:
                DoPostLoadProcessing(); // Derived classes override this
                break;
        }
    }

    其原理是,在收到初始激活信息后,通过发信息给自己,我可以确信: 对话框已完全呈现,后加载处理在对话框的UI线程上异步执行: 使用PostMessage()可以确保它是异步的。发布的消息被添加到队列的末尾,并在所有挂起的(与ui相关的)消息被处理之后被处理。在对话框的UI线程中做一些事情是很重要的——如果你试图从UI线程以外的线程操作许多控件,它们会变得非常不舒服。 有时我会这样做: 隐藏,复制Code

    #define IDT_FIRSTACTIVE  0x100
    bool m_bSeenFirstActivation = false;
    virtual void DoPostLoadProcessing();
    
    LRESULT WndProc(msg, wparam, lparam)
    {
        switch(msg)
        {
            case WM_ACTIVATE:
                if((wparam==WA_ACTIVE) && !m_bSeenFirstActivation)
                {
                    m_bSeenFirstActivation = true;
                    SetTimer(m_hWnd, IDT_FIRSTACTIVE, 50, NULL);
                    PostMessage(m_hWnd, WMUSER_FIRSTACTIVE);
                }
                break;
            case WM_TIMER:
                if(wParam == IDT_FIRSTACTIVE)
                {
                    KillTimer(m_hWnd, IDT_FIRSTACTIVE);
                    DoPostLoadProcessing(); // Derived classes override this
                }
                break;
        }
    }

    这里的原理本质上是一样的:在初始激活时设置一个快速(50ms)计时器。WM_TIMER消息异步发生并且有一些额外的延迟。计时器会立即终止(我们只需要第一个计时器消息),然后调用派生类的DoPostLoadProcessing()。(是的,我知道计时器<55毫秒对个人电脑没用。) 这两种技术都工作得很好,并且仍然可以在。net中使用,尽管它们很混乱。 解决方案:在。net中完成 最基本的 . net添加的show()事件大大简化了事情。现在,从理论上讲,您所需要做的就是处理show()事件并在那里进行处理: 隐藏,复制Code

    void MyForm_Shown(object sender, EventArgs e)
    {
        // Do blocking stuff here
    }

    简单,是吧?嗯,差不多。结果显示,show()事件在表单的所有子控件完全呈现之前被触发,因此您仍然有竞争条件。我的解决方案是调用Application.DoEvents()在做任何额外的后处理之前清除消息队列: 隐藏,本编解码器cdccc4 更完整的解决方案 上面基本解决方案的问题是必须记住在每个表单的show()事件处理程序中调用Application.DoEvents()。虽然这确实是一个小麻烦,但是我选择了更进一步的解决方案,实现了一个基本对话框类,它处理show()事件,调用DoEvents(),然后调用它自己的事件: 隐藏,复制Code

    public class BaseForm : Form
    {
        public delegate void LoadCompletedEventHandler();
        public event LoadCompletedEventHandler LoadCompleted;
    
        public BaseForm()
        {
            this.Shown += new EventHandler(BaseForm_Shown);
        }
    
        void BaseForm_Shown(object sender, EventArgs e)
        {
            Application.DoEvents();
            if (LoadCompleted != null)
                LoadCompleted();
        }
    }

    并且,派生窗体仅仅实现了一个LoadCompleted()处理程序: 隐藏,复制Code

    this.LoadCompleted += new 
          FormLoadCompletedDemo.BaseForm.LoadCompletedEventHandler(
          this.MyFormUsingBase_LoadCompleted);
        
        ...
    
    private void MyFormUsingBase_LoadCompleted()
    {
        // Do blocking stuff here
    }

    和…奖金!: LoadCompleted()出现在Visual Studio的属性/事件窗格中: (我将事件命名为LoadCompleted,以便它会立即出现在事件窗格中的Load事件之后,使其更容易查找。) 有了它,您就可以在LoadCompleted()中完成所有的长期操作,并且确信在用户等待时基本UI将被完全呈现。当然,你仍应遵循好的UI实践,如显示WaitCursor也许禁用控制直到他们可用的(一个有用的视觉提示用户),甚至显示progressbar真的长时间等待(例如:显示一个选框progressbar在SQL调用,5 +秒)。 演示项目 附加的VS2008/ c#项目演示了这种行为和解决方案。它实现了一个带有四个按钮的表单: 这四个按钮都做了同样的事情,这是弹出一个子对话框,其中包含: 一个填充了50K项的列表框。这个填充会阻塞UI线程几秒钟,这允许应用程序演示问题的解决方案。其他一些控件,主要是组合框。不管出于什么原因,组合框特别容易由于阻塞代码而呈现不完整。 每个子窗体在其Load()事件中将WaitCursor设置为正在发生的事情的可视化反馈。 但是,每个按钮都会导致不同的表单加载行为: 在加载事件中执行处理——listbox在表单的Load()事件处理程序中被加载,导致表单直到listbox完全加载(错误)才出现。打开没有DoEvents的表单——listbox在表单的show()事件处理程序中被加载,但没有调用Application.DoEvents(),导致表单出现部分呈现,直到listbox加载完成(非常糟糕)。打开Form *with* DoEvents——listbox在表单的show()事件处理程序中被加载,并调用Application.DoEvents(),导致表单显示为完全呈现,直到listbox加载完成(很好!!)Open Form派生自BaseForm -与Open Form *相同,但使用基类和自定义事件实现。 结论 就像Windows(以及生活中!)中的许多东西一样,这个问题的解决方案非常简单,但要想弄清楚它需要一些尝试和错误。我希望上面概述的解决方案可以节省您的时间,并帮助您创建响应性更好的ui。 历史 2010年2月12日:初始版本 本文转载于:http://www.diyabc.com/frontweb/news3687.html

  • 相关阅读:
    python3----练习......
    python3----练习题(....)
    python3----requests
    python3----练习题(爬取电影天堂资源,大学排名,淘宝商品比价)
    python3----练习题(图片转字符画)
    python3----练习题(....)
    ConfigParser 读写配置文件
    数据驱动ddt+excel数据读取
    数据驱动ddt
    expected_conditions判断页面元素
  • 原文地址:https://www.cnblogs.com/Dincat/p/13461276.html
Copyright © 2020-2023  润新知