【问题标题】:Displaying a "User control is loading" message while loading a User Control加载用户控件时显示“正在加载用户控件”消息
【发布时间】:2013-03-28 16:32:07
【问题描述】:

我有一个带有 TabStrip 控件的 Winforms 应用程序。在运行时,用户控件将被动态加载到不同的选项卡中。

我想在加载 UserControl 之前向用户显示“用户控件 xyz 正在加载”消息(将现有标签设置为可见并更改其文本),直到加载完全完成。

到目前为止我的方法:

  1. 试图在 BackgroundWorker 线程中加载用户控件。这失败了,因为我必须在加载 UserControl 期间访问 Gui-Controls
  2. 试图在 BackgroundWorker 线程中显示消息。这显然失败了,因为 BackgroundWorker 线程不是 UI 线程;-)
  3. 显示消息,调用 DoEvents(),加载 UserControl。这会导致每次加载 UserControl 时出现不同的行为(闪烁、...),并且我无法控制何时以及如何将其再次设置为不可见。

总结一下,我有两个问题:

  1. 如何在加载用户控件之前确保消息直接可见
  2. 如何确保消息再次设置为不可见,就在 UserControl 完全加载的那一刻(包括所有 DataBinding、网格格式等)

【问题讨论】:

    标签: c# winforms multithreading


    【解决方案1】:

    我们使用的类似这样:

    1. 创建一个新表单,其中包含您想向用户显示的任何内容,
    2. 实现一个静态方法,您可以在其中调用此表单以在其内部创建,以防止内存泄漏
    3. 在此表单中创建一个新线程,以便表单在单独的线程中运行并保持响应;我们使用一个显示进度条填充的ajax 控件。

    在您用于启动线程的方法中,将其属性设置为 topmost true 以确保它保持在顶部。

    例如,在您的主窗体中执行此操作:

    loadingForm.ShowLoadingScreen("usercontrollname");
    //do something
    loadingform.CloseLoadingScreen();
    

    在加载表单类中;

    public LoadingScreen()
    {
      InitializeComponent();
    }
    
    public static void ShowLoadingScreen(string usercontrollname)
    {
      // do something with the usercontroll name if desired
      if (_LoadingScreenThread == null)
      {
        _LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
        _LoadingScreenThread.IsBackground = true;
        _LoadingScreenThread.Start();
      }
    }
    
    public static void CloseLoadingScreen()
    {
      if (_ls.InvokeRequired)
      {
        _ls.Invoke(new MethodInvoker(CloseLoadingScreen));
      }
      else
      {
        Application.ExitThread();
        _ls.Dispose();
        _LoadingScreenThread = null;
      }
    }
    
    private static void DoShowLoadingScreen()
    {
        _ls = new LoadingScreen();
        _ls.FormBorderStyle = FormBorderStyle.None;
        _ls.MinimizeBox = false;
        _ls.ControlBox = false;
        _ls.MaximizeBox = false;
        _ls.TopMost = true;
        _ls.StartPosition = FormStartPosition.CenterScreen;
    
      Application.Run(_ls);
    }
    

    【讨论】:

    • 谢谢!就 ShowLoadingScreen 部分而言,这非常有效。我唯一想念的是在 UserControl 上找到正确的事件,我可以确定它已完全加载(和“UI 渲染”)以调用 CloseLoadingScreen()-part。
    • 您可以订阅用户控件抛出的事件,但我不确定是哪一个。我知道在控件第一次可见之前触发了加载事件,所以也许您可以订阅可见的更改事件来执行此操作?但是,这不会检查您的数据是否已加载。您可以编写自己的事件处理程序并在完成加载部分所需的所有内容时触发事件并将其用作解决方法。
    【解决方案2】:

    再次尝试第二种方法:

    试图在 BackgroundWorker 线程中显示消息。这显然失败了,因为 BackgroundWorker 线程不是 UI 线程;-)

    但这一次,请在后台线程中使用以下代码来更新您的标签:

    label.Invoke((MethodInvoker) delegate {
        label.Text = "User Control xyz is loading";
        label.Visible = true;
    });
    // Load your user control
    // ...
    label.Invoke((MethodInvoker) delegate {
        label.Visible = false;
    });
    

    Invoke 允许您在另一个线程中更新您的 UI。

    【讨论】:

      【解决方案3】:

      根据@wterbeek 的示例,我出于自己的目的修改了该类:

      • 将其置于加载表单的中心
      • 修改其不透明度
      • 将其调整为父大小
      • 将其显示为对话框并阻止所有用户交互
      • 我被要求展示一个悸动者

      我在线收到一个空错误:

      if (_ls.InvokeRequired)
      

      所以我添加了一个 _shown 条件(如果动作完成得如此之快以至于 _LoadingScreenThread 线程甚至没有运行)来检查表单是否存在。

      另外,如果_LoadingScreenThread 没有启动,Application.Exit 会关闭主线程。

      我想发布它可能会帮助其他人。代码中的注释会解释更多。

      public partial class LoadingScreen : Form {
      
          private static Thread _LoadingScreenThread;
      
          private static LoadingScreen _ls;
      
          //condition required to check if the form has been loaded
          private static bool _shown = false;
      
          private static Form _parent;
      
          public LoadingScreen() {
              InitializeComponent();
          }
          //added the parent to the initializer
          //CHECKS FOR NULL HAVE NOT BEEN IMPLEMENTED
          public static void ShowLoadingScreen(string usercontrollname, Form parent) {
              // do something with the usercontroll name if desired
              _parent = parent;
              if (_LoadingScreenThread == null) {
                  _LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
                  _LoadingScreenThread.SetApartmentState(ApartmentState.STA);
                  _LoadingScreenThread.IsBackground = true;
                  _LoadingScreenThread.Start();
              }
          }
      
          public static void CloseLoadingScreen() {
              //if the operation is too short, the _ls is not correctly initialized and it throws 
              //a null error
              if (_ls!=null && _ls.InvokeRequired) {
                  _ls.Invoke(new MethodInvoker(CloseLoadingScreen));
              } else {
                  if (_shown)
                  {
                      //if the operation is too short and the thread is not started
                      //this would close the main thread
                      _shown = false;
                      Application.ExitThread();
                  }
                  if (_LoadingScreenThread != null)
                      _LoadingScreenThread.Interrupt();
                  //this check prevents the appearance of the loader
                  //or its closing/disposing if shown
                  //have not found the answer
                  //if (_ls !=null)
                  //{
                     _ls.Close();
                     _ls.Dispose();
                  //}
                  _LoadingScreenThread = null;
              }
          }
      
          private static void DoShowLoadingScreen() {
              _ls = new LoadingScreen();
              _ls.FormBorderStyle = FormBorderStyle.None;
              _ls.MinimizeBox = false;
              _ls.ControlBox = false;
              _ls.MaximizeBox = false;
              _ls.TopMost = true;
              //get the parent size
              _ls.Size = _parent.Size; 
              //get the location of the parent in order to show the form over the 
              //target form
              _ls.Location = _parent.Location;     
              //in order to use the size and the location specified above
              //we need to set the start position to "Manual"
              _ls.StartPosition =FormStartPosition.Manual;
              //set the opacity
              _ls.Opacity = 0.5;
              _shown = true;
              //Replaced Application.Run with ShowDialog to show as dialog
              //Application.Run(_ls);
              _ls.ShowDialog();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-17
        • 2023-04-05
        • 1970-01-01
        相关资源
        最近更新 更多