【问题标题】:In WPF (.net 4.0), how to make a child window block the code while not block the main window?在 WPF(.net 4.0)中,如何让子窗口阻塞代码而不阻塞主窗口?
【发布时间】:2015-08-27 13:14:29
【问题描述】:

我有一个 WPF 应用程序来运行测试用例并收集结果。在主窗口,用户可以选择一些测试用例循环运行。运行案例时,会弹出一个自定义的子窗口,向用户显示一些数据,然后用户单击“Pass”或“Fail”按钮来设置该测试用例的结果并关闭这个子窗口。然后下一个测试用例开始运行。在主窗口上,有一个“停止”按钮。并且用户可以在当前回合结束后单击它来停止循环测试。 代码如下:

while (!stopByUser)
{
   foreach(var caseItem in caseList)
   {
      // on TestWindow UI, caseItem.isPassed will be set by user with clicking buttons;
      caseItem.isPassed = false;
      TestWindow tw = new TestWindow(caseItem);
      tw.ShowDialog();
      if (caseItem.isPassed)
      {
         totalPassed++;
         // update UI ...
      }
   }
}

问题是用户无法单击主窗口上的“停止”按钮,因为我使用 tw.ShowDialog() 来弹出模式窗口。但是,我也不能简单地将其更改为 tw.Show() 以弹出非模态窗口,因为 foreach 循环中的代码必须同步执行。

我发现原生MessageBox有这个能力:阻塞代码而不阻塞主窗口。例如。

var result= MessageBox.Show(message, "Is this test case passed?", MessageBoxButton.YesNo);
// the following line will be executed after the MessageBox is closed
// and meanwhile I can operate my main window when the MessageBox is still visible
var passed = (judgement == MessageBoxResult.Yes);

所以我的问题是如何在 .net 4.0 中使用 WPF Window 来实现此功能? 我的应用程序将在 windows xp 上运行,因此 .net 4.0 是强制性的。

有什么想法吗?提前致谢!

【问题讨论】:

  • 你的设计要求可能有问题,但如果你想坚持下去,你仍然有一些选择:1)为消息框使用单独的 UI 线程,2)使用 p/invoke 和调用本机 MessageBox API,3) 使用 Microsoft.Bcl.Asyncasync/await 让您保持 foreach 循环,同时仍使用无模式 Window.Show/Close,正如 @VMaleev 建议的那样。

标签: wpf modal-dialog window async-await


【解决方案1】:

MessageBox.Show 阻止所有窗口,因为它使用 ShowDialog 方法。你可以做的是写逻辑:

  1. 给出下一项
  2. 为其创建新窗口
  3. 订阅此窗口的关闭事件
  4. OnClosed 事件更新您的用户界面并转到第一步

例子:

    private bool stopByUser {get;set;}
    private int currentItemIndex {get;set;}
    private List<object> caseList {get;set;}]
    private TestWindow tw {get;set;
    public void OnLoaded(object sender, RoutedEventArgs e)
    {
        ShowNext();
    }

    private void ShowNext()
    {
        if (stopByUser) return;

        var caseItem = caseList[currentItemIndex++]
        caseItem.isPassed = false;

        tw = new TestWindow(caseItem);
        tw.Closed += (s, e) => 
        { 
            if (caseItem.isPassed)
            {
                totalPassed++;
                // update UI ...

                ShowNext();
            }
        };
        tw.Show();
    }

【讨论】:

  • MessageBox.Show() 似乎不会阻塞主窗口。我仍然可以与主窗口进行交互。例如。点击“停止”按钮
  • 我不能。您可以共享此行为所在的示例项目。同时,我为您的问题提出了真正的解决方法。
【解决方案2】:

感谢@Noseratio 和@VMaleev 的帮助,我终于找到了使用Microsoft.Bcl.Async 和async/await 的解决方案。它不漂亮,但很有效。

while (!stopByUser)
{
   foreach(var caseItem in caseList)
   {
        caseItem.isPassed = false;
        GlobalVar.isFinished = false; // static variable: isFinished
        TestWindow tw = new TestWindow(caseItem);
        tw.Closed += (s, e) =>
        {
            GlobalVar.isFinished  = true;
        };
        Task task = new Task(() =>
        {
            while (!GlobalVar.isFinished)
            {
                Thread.Sleep(100);
            }
        });
        task.Start();
        tw.Show();
        await task;
        // get the test result here from GlobalVar.isPassed
        var passed = caseItem.isPassed;
        // do other things
   }
}

请记住,要使用 Microsoft.Bcl.Async,必须在 xp/win7 上安装 .NET Framework 4.0(带有 KB2468871)。查看更多here

【讨论】:

  • 你应该在这里使用TaskCompletionSource而不是Task
猜你喜欢
  • 2017-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多