WPF模态进度窗口

Bri*_*VPS 10 c# wpf dialog window modal-dialog

如果这个问题得到了很多次回答,我很抱歉,但我似乎无法找到适合我的答案.我想创建一个模态窗口,在我的应用程序执行长时间运行的任务时显示各种进度消息.这些任务在一个单独的线程上运行,我可以在进程的不同阶段更新进度窗口上的文本.跨线程通信都很好地工作.问题是我不能让窗口只在其他应用程序窗口(不是计算机上的每个应用程序)之上,保持在最顶层,阻止与父窗口的交互,并仍然允许工作继续.

这是我到目前为止所尝试的:

首先,我的启动窗口是一个自定义类,它扩展了Window类,并具有更新消息框的方法.我在早期创建了一个新的splash类实例,并根据需要显示/隐藏它.

在最简单的情况下,我实例化窗口并调用.Show()它:

//from inside my secondary thread
this._splash.Dispatcher.Invoke(new Action(() => this._splash.Show());

//Do things
//update splash text
//Do more things

//close the splash when done
this._splash.Dispatcher.Invoke(new Action(() => this._splash.Hide());
Run Code Online (Sandbox Code Playgroud)

这会正确显示窗口并继续运行我的代码来处理初始化任务,但它允许我单击父窗口并将其带到前面.

接下来我尝试禁用主窗口并稍后重新启用:

Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = false));

//show splash, do things, etc

Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = true));
Run Code Online (Sandbox Code Playgroud)

这会禁用窗口中的所有元素,但我仍然可以单击主窗口并将其放在启动屏幕前面,这不是我想要的.

接下来,我尝试使用启动窗口中的最顶层属性.这使得它保持在所有内容之前,并且与设置主窗口IsEnabled属性一起,我可以阻止交互,但这使得启动屏幕出现在一切,包括其他应用程序.我也不想要那个.我只是希望它成为这个应用程序中最顶层的窗口.

然后我发现了关于使用的帖子.ShowDialog()而不是.Show().我试过这个,它正确地显示了对话框并且不允许我点击父窗口,但调用.ShowDialog()使程序挂起等待你关闭对话框,然后它将继续运行代码.这显然不是我想要的.我想我可以调用ShowDialog()一个不同的线程,以便该线程可以挂起但是执行该工作的线程不会......是推荐的方法吗?

我还考虑过根本不使用窗口的可能性,而是将一个完整大小的窗口元素放在页面上的其他内容之前.这可以工作,除了我打开其他窗口,我希望能够在那些打开的时候使用启动画面.如果我使用了一个窗口元素,我将不得不在每个窗口上重新创建它,我将无法UpdateSplashText在自定义的splash类中使用我的方法.

所以这让我想到了这个问题.处理这个问题的正确方法是什么?

感谢您的时间和对不起的长期问题,但细节很重要:)

Ser*_*rvy 16

您是正确的,ShowDialog它可以为您提供所需的大部分UI行为.

它确实存在这样的问题,即只要你调用它就可以阻止执行.如何在显示表单后运行一些代码,但在显示之前定义它应该是什么?那是你的问题.

您可以在splash类中完成所有工作,但由于紧耦合,这种做法相当糟糕.

您可以做的是利用Loaded事件Window来定义应该在显示窗口后运行的代码,但是在显示窗口之前定义它的位置.

public static void DoWorkWithModal(Action<IProgress<string>> work)
{
    SplashWindow splash = new SplashWindow();

    splash.Loaded += (_, args) =>
    {
        BackgroundWorker worker = new BackgroundWorker();

        Progress<string> progress = new Progress<string>(
            data => splash.Text = data);

        worker.DoWork += (s, workerArgs) => work(progress);

        worker.RunWorkerCompleted +=
            (s, workerArgs) => splash.Close();

        worker.RunWorkerAsync();
    };

    splash.ShowDialog();
}
Run Code Online (Sandbox Code Playgroud)

请注意,此方法旨在将样板代码封装在此处,以便您可以传入任何接受进度指示器的工作方法,并且它将在后台线程中执行该工作,同时显示具有从工作人员指示的进度的通用启动屏幕.

然后可以这样调用:

public void Foo()
{
    DoWorkWithModal(progress =>
    {
        Thread.Sleep(5000);//placeholder for real work;
        progress.Report("Finished First Task");

        Thread.Sleep(5000);//placeholder for real work;
        progress.Report("Finished Second Task");

        Thread.Sleep(5000);//placeholder for real work;
        progress.Report("Finished Third Task");
    });
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*der 5

@Servy 接受的答案对我帮助很大!我想与asyncMVVM 方法分享我的版本。它还包含一个小的延迟,以避免操作太快而出现“窗口闪烁”。

对话方法:

public static async void ShowModal(Func<IProgress<string>, Task> workAsync, string title = null, TimeSpan? waitTimeDialogShow = null)
{
    if (!waitTimeDialogShow.HasValue)
    {
        waitTimeDialogShow = TimeSpan.FromMilliseconds(300);
    }

    var progressWindow = new ProgressWindow();
    progressWindow.Owner = Application.Current.MainWindow;

    var viewModel = progressWindow.DataContext as ProgressWindowViewModel;
    Progress<string> progress = new Progress<string>(text => viewModel.Text = text);

    if(!string.IsNullOrEmpty(title))
    {
        viewModel.Title = title;
    }

    var workingTask = workAsync(progress);

    progressWindow.Loaded += async (s, e) =>
    {
        await workingTask;

        progressWindow.Close();
    };

    await Task.Delay((int)waitTimeDialogShow.Value.TotalMilliseconds);

    if (!workingTask.IsCompleted && !workingTask.IsFaulted)
    {
        progressWindow.ShowDialog();
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

    ShowModal(async progress =>
    {
        await Task.Delay(5000); // Task 1
        progress.Report("Finished first task");

        await Task.Delay(5000); // Task 2
        progress.Report("Finished second task");
    }); 
Run Code Online (Sandbox Code Playgroud)

再次感谢@Servy,节省了我很多时间。


Dax*_*ohl 0

您可以让进度窗口的构造函数接受 a Task,然后确保窗口调用task.StartOnLoaded事件。然后您从父窗体中使用ShowDialog,这将导致进度窗口启动任务。

请注意,您还可以task.Start在构造函数中调用,或者在调用 之前在父窗体中的任何位置调用ShowDialog。无论哪个对您来说最有意义。

另一种选择是在主窗口的状态条中使用进度条,并摆脱弹出窗口。如今,这种选择似乎越来越常见。