WPF 模态进度窗口 [英] WPF modal progress window

查看:23
本文介绍了WPF 模态进度窗口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果这个问题已被多次回答,我深表歉意,但我似乎找不到适合我的答案.我想创建一个模式窗口,在我的应用程序执行长时间运行的任务时显示各种进度消息.这些任务在单独的线程上运行,我能够在流程的不同阶段更新进度窗口上的文本.跨线程通信一切正常.问题是我不能让窗口仅位于其他应用程序窗口(不是计算机上的每个应用程序)之上,保持在最上面,阻止与父窗口交互,并且仍然允许工作继续.

I apologize if this question has been answered tons of times, but I can't seem to find an answer that works for me. I would like to create a modal window that shows various progress messages while my application performs long running tasks. These tasks are run on a separate thread and I am able to update the text on the progress window at different stages of the process. The cross-thread communication is all working nicely. The problem is that I can't get the window to be on top of only other application windows (not every application on the computer), stay on top, prevent interaction with the parent window, and still allow the work to continue.

这是我迄今为止尝试过的:

Here's what I've tried so far:

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

First, my splash window is a custom class that extends the Window class and has methods to update the message box. I create a new instance of the splash class early on and Show/Hide it as needed.

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

In the simplest of cases, I instantiate the window and call .Show() on it:

//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());

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

This correctly displays the window and continues running my code to handle the initialization tasks, but it allows me to click on the parent window and bring that to the front.

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

Next I tried disabling the main window and re-enabling later:

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));

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

This disables all the elements in the window, but I can still click the main window and bring it in front of the splash screen, which is not what I want.

接下来我尝试使用启动窗口中的 topmost 属性.这使它保持在所有事物的前面,并且结合设置主窗口的 IsEnabled 属性,我可以阻止交互,但这会使启动屏幕出现在所有事物的前面,包括其他应用程序.我也不想要那个.我只想让它成为这个应用程序中最顶层的窗口.

Next I tried using the topmost property on the splash window. This keeps it in front of everything, and in conjunction with setting the main window IsEnabled property I could prevent interaction, but this makes the splash screen appear in front of EVERYTHING, including other applications. I don't want that either. I just want it to be the topmost window within THIS application.

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

Then I found posts about using .ShowDialog() instead of .Show(). I tried this, and it correctly showed the dialog and did not allow me to click on the parent window, but calling .ShowDialog() makes the program hang waiting for you to close the dialog before it will continue running code. This is obviously, not what I want either. I suppose I could call ShowDialog() on a different thread so that that thread would hang but the thread doing the work would not...is that the recommended method?

我还考虑过根本不使用窗口的可能性,而是将一个全尺寸的窗口元素放在页面上其他所有内容的前面.除了我打开了其他窗口并且我希望能够在这些窗口也打开时使用启动画面之外,这将起作用.如果我使用一个窗口元素,我将不得不在每个窗口上重新创建它,并且我将无法在我的自定义启动类中使用我方便的 UpdateSplashText 方法.

I have also considered the possibility of not using a window at all and instead putting a full-sized window element in front of everything else on the page. This would work except that I have other windows I open and I'd like to be able to use the splash screen when those are open too. If I used a window element I would have to re-create it on every window and I wouldn't be able to use my handy UpdateSplashText method in my custom splash class.

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

So this brings me to the question. What is the right way to handle this?

感谢您抽出宝贵时间,很抱歉这个问题很长,但细节很重要 :)

Thanks for your time and sorry for the long question but details are important :)

推荐答案

您是正确的,ShowDialog 为您提供了您想要的大部分 UI 行为.

You are correct that ShowDialog gives you most of the UI behavior that you want.

它确实有一个问题,即一旦你调用它就会阻止执行.你怎么可能在显示表单之后运行一些代码,但在显示之前定义它应该是什么?那是你的问题.

It does have the problem that as soon as you call it you block execution though. How could you possibly run some code after you show the form, but define what it should be before it's shown? That's your problem.

您可以在启动类中完成所有工作,但由于紧密耦合,这是相当糟糕的做法.

You could just do all of the work within the splash class, but that's rather poor practice due to tight coupling.

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

What you can do is leverage the Loaded event of Window to define code that should run after the window is shown, but where it is defined before you show it.

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();
}

请注意,此方法旨在在此处封装样板代码,以便您可以传入任何接受进度指示器的工作方法,它会在后台线程中执行该工作,同时显示一个已指示进度的通用初始屏幕来自工人.

Note that this method is designed to encapsulate the boilerplate code here, so that you can pass in any worker method that accepts the progress indicator and it will do that work in a background thread while showing a generic splash screen that has progress indicated from the worker.

然后可以这样称呼它:

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");
    });
}

这篇关于WPF 模态进度窗口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆