【问题标题】:Winform - Continue from a ShowDialog without hiding the windowWinform - 从 ShowDialog 继续而不隐藏窗口
【发布时间】:2018-02-05 06:59:12
【问题描述】:

在多线程环境中。

我在进度表单中使用 ShowDialog 来阻止其他表单上的所有用户活动。
完成部分过程后,我隐藏了进度表,我做了一些任务并再次显示(ShowDialog)它。
一切正常,但进度表闪烁。
有没有办法在不隐藏窗口的情况下从 ShowDialog 继续,或者可能更好,有没有办法将 Show 转换为 ShowDialog 并再次返回?

编辑
我的代码与此类似

class frmProgress : Form
{
    // Standard stuff


    public void DoSomeWorks()
    {
        async Task.Run(() => RunWork1());
        ShowDialog();
        if (_iLikeTheResultOfWork1)
        {
            async Task.Run(() => RunWork2());
            ShowDialog();
        }
    }

    void RunWork1()
    {
        // Do a lot of things including update UI

        Hide();
    }

    void RunWork2()
    {
        // Do a lot of things including update UI

        Hide();
    }
}

编辑 2
感谢大家的回答和建议。
最后,我采用了将一些小作品分组到我在 UI 中使用的更大作品中的解决方案,而在单元测试中我仍然测试小作品。
实际上这不是我正在寻找的解决方案,我希望找到一个基于处理消息泵或做类似事情的解决方案(参见 System.Windows.Forms.Application 源代码从 RunDialog(.) 和所有被调用的方法我在发布这个问题之前迷路了)。

【问题讨论】:

  • 为什么在“完成部分流程后”隐藏进度表?
  • 很难在没有任何重现代码的情况下提供帮助。这在很大程度上取决于你真正做事的方式。
  • ShowDialog 被阻止。忘记显示对话框。这没有意义。事实上,我无法想象ShowDialog 的用途是什么,而您可以禁用表单(或整个表单)的控件并显示一个表单。
  • 这是你想做的吗?如果是这样,我将发布为 C# 代码:stackoverflow.com/questions/13206926/…

标签: c# multithreading winforms


【解决方案1】:

你不能那样做。至少我知道没有一种简单的方法。解决此问题的一种方法是按如下方式更改您的代码:

//C#-ish pseudocode based on OPs example; doesn't compile
class frmProgress : Form
{
    // Standard stuff


    public void DoSomeWorks()
    {
        async Task.Run(() => RunWork1());
        ShowDialog();
    }

    void RunWork1()
    {
        // Do a lot of things including update UI

        if (_iLikeTheResultOfWork1)
        {
            async Task.Run(() => RunWork2());
        }
        else
        {
            Hide();
        }
    }

    void RunWork2()
    {
        // Do a lot of things including update UI

        Hide();
    }
}

编辑 对于那些抱怨代码无法编译的人来说,他们是对的。但这是 OP 将通过 那个 代码示例获得的最好的,这就是我猜他被无情地否决的原因。

但为了让答案与其他人更相关,我的建议是不要隐藏两个任务之间的进度表,当你确定任务已经结束时隐藏它。所有这些都通过尊重您正在处理的上下文的线程模型,这是 OP 的代码所不具备的。从最终将在其他线程中运行的方法操作 Winforms 中的 UI 将不起作用。

【讨论】:

  • 这还能编译吗?
  • @Nkosi 为什么 OPs 示例会编译?使用他在此处显示的代码,这些是他将获得的最佳结果。
  • 因为async/await 不是这样使用的。 OPs 代码也无法编译。
  • @Nkosi 确实,因为我是来帮忙的,而不是 OP 的意思,这是我能提供的最好的。我同意这可能与其他人无关,这就是我进行编辑的原因。
【解决方案2】:

我想你有一些方法如下:

void Task1()
{
    Debug.WriteLine("Task1 Started");
    System.Threading.Thread.Sleep(5000);
    Debug.WriteLine("Task1 Finished");
}
void Task2()
{
    Debug.WriteLine("Task2 Started");
    System.Threading.Thread.Sleep(3000);
    Debug.WriteLine("Task2 Finished");
}

如果您要并行运行它们:

private async void button1_Click(object sender, EventArgs e)
{
    this.Enabled = false;
    //Show a loading image
    await Task.WhenAll(new Task[] {
        Task.Run(()=>Task1()),
        Task.Run(()=>Task2()),
    });
    //Hide the loading image
    this.Enabled = true;
}

如果你要一个一个地运行它们:

private async void button1_Click(object sender, EventArgs e)
{
    this.Enabled = false;
    //Show a loading image
    await Task.Run(()=>Task1());
    await Task.Run(()=>Task2());
    //Hide the loading image
    this.Enabled = true;
}

【讨论】:

  • 对于这个任务,我只想响应 UI 事件。它们不能并行运行。我还需要锁定所有其他窗口,所以我需要一个模态表单。
【解决方案3】:

引用Async/Await - Best Practices in Asynchronous Programming

我建议您利用异步事件处理程序和任务来允许在表单处于模态时通过显示对话框进行非阻塞调用

class frmProgress : Windows.Form {
    // Standard stuff

    public void DoSomeWorks() {
        Work -= OnWork;
        Work += OnWork;

        Work(this, EventArgs.Empty);//raise the event and do work on other thread   
        ShowDialog();
    }

    private CancellationTokenSource cancelSource;
    private event EventHandler Work = delegate { };

    private async void OnWork(object sender, EventArgs e) {
        Work -= OnWork; //unsubscribe

        cancelSource = new CancellationTokenSource(); //to allow cancellation.

        var _iLikeTheResultOfWork1 = await RunWork1Async(cancelSource.Token);
        if (_iLikeTheResultOfWork1) {
            await RunWork2Async(cancelSource.Token);
        }

        DialogResult = DialogResult.OK; //just an example
    }


    Task<bool> RunWork1Async(CancellationToken cancelToken) {
        // Do a lot of things including update UI

        //if while working cancel called
        if (cancelToken.IsCancellationRequested) {
            return Task.FromResult(false);
        }

        //Do more things

        //return true if successful
        return Task.FromResult(true);
    }

    Task<bool> RunWork2Async(CancellationToken cancelToken) {
        // Do a lot of things including update UI

        //if while working cancel called
        if (cancelToken.IsCancellationRequested) {
            return Task.FromResult(false);
        }

        //Do more things

        //return true if successful
        return Task.FromResult(true);
    }
}

注意使用取消令牌以允许根据需要取消任务。

现在 UI 已解锁,异步功能可以继续工作。没有闪烁,因为表格保持显示而没有任何中断。

【讨论】:

    【解决方案4】:

    我可能会被误解,这就是我所做的,使用wndProc 处理对话框。我创建了一个额外的表单并使用了Show(); 方法。在我展示表单之后,异步任务就开始了。在我展示表格后,我通过wndProc 处理了该表格。

    protected override void WndProc(ref Message m) {
                if((f2.IsDisposed || !f2.Visible)) {
                    foreach(var control in controlList) {
                        control.Enabled = true; // enable all controls or other logic
                    }
                }
                if(m.Msg == 0x18 && !f2.IsDisposed) { // notify dialog opens and double checks dialog's situation
                    foreach(var control in controlList.Where(ctrl => ctrl.Name != "button1")) {
                        control.Enabled = false; // disable except cancel button
                    }
                }
                base.WndProc(ref m);
            }
    

    希望有帮助,

    【讨论】:

      【解决方案5】:

      从您发布的代码中,您在 RunWork() 方法的末尾调用 Hide(),然后在之后调用 ShowDialog()。如果我理解正确,您想在 RunWork() 方法中调用 Hide() first,这使得 UI 主窗口可在 UI 更新发生时访问。一切完成后,会出现 ShowDialog() 方法并再次阻塞主 UI 线程。

      class frmProgress : Form
      {
          public bool _iLikeTheResultOfWork1 = true;
      
          // Note: changed to async method and now awaiting the task
          public async void DoSomeWorks()
          {
              await Task.Run(() => RunWork1());
              ShowDialog();
              if (_iLikeTheResultOfWork1)
              {
                  await Task.Run(() => RunWork2());
                  ShowDialog();
              }
          }
      
          // Hide window and wait 5 seconds. Note that the main window is active during this 5 seconds.
          // ShowDialog() is called again right after this, so the dialog becomes modal again.
          void RunWork1()
          {
              Hide();
              Task.Delay(5000).Wait();
      
              // Do a lot of things including update UI
          }
      
          void RunWork2()
          {
              Hide();
              Task.Delay(5000).Wait();
      
              // Do a lot of things including update UI
          }
      }
      

      我上面的代码将调用 RunWork1(),它将对话框隐藏 5 秒。在此期间,主 UI 窗口将处于活动状态。之后,该方法返回并调用 ShowDialog()。关闭该窗口以调用 RunWork2() 并重新开始该过程。

      这是您想要达到的目标吗?我刚刚测试了这段代码,它没有视觉“闪烁”。如果这是您想要的,这种方法是否会出现“闪烁”?

      【讨论】:

        【解决方案6】:

        “当它离开它的模态消息循环时,表单总是会关闭,这个循环是由 ShowDialog() 启动的。你能做的最好的就是模拟模态,用 Show() 显示表单并禁用所有其他窗口。 "

        https://social.msdn.microsoft.com/Forums/sharepoint/en-US/3f6c57a1-92fd-49c5-9f46-9454df80788c/possible-to-change-modality-of-a-dialog-box-at-runtime?forum=winforms

        因此,您可以枚举应用程序的所有窗口并在需要时启用/禁用它们,此外还可以在模拟模态时将对话窗口设置为始终位于顶部。我认为总是在最前面意味着它在操作系统中的所有窗口之上,所以如果它被设置为模拟模式,你可能需要一个计时器而不是把你的窗口带到前面(在你的应用程序窗口中)

        【讨论】:

        • 我可能正在寻找一种方法来设置模式消息循环而不使用 ShowDialog 并返回到非模式消息循环而不隐藏窗口(这可能是退出 ShowDialog 的唯一方法)。
        • 你有没有想过在它下面堆叠一个模态窗口的非模态副本并同步它们的移动(或使其不可移动)以避免闪烁?
        • 我没有。我更喜欢按照上面的建议更改模型。实际上仍在寻找一种无需 ShowDialog 即可更改消息循环的方法。我从 Application.RunDialog 开始(私有但可通过反射调用)然后我迷路了。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-02
        • 2011-03-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多