Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/wpf/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# WPF模式进度窗口_C#_Wpf_Dialog_Window_Modal Dialog - Fatal编程技术网

C# WPF模式进度窗口

C# WPF模式进度窗口,c#,wpf,dialog,window,modal-dialog,C#,Wpf,Dialog,Window,Modal Dialog,如果这个问题已经被回答了很多次,我很抱歉,但是我似乎找不到一个适合我的答案。我想创建一个模式窗口,在应用程序执行长时间运行的任务时显示各种进度消息。这些任务在单独的线程上运行,我能够在进程的不同阶段更新进度窗口上的文本。跨线程通信工作得很好。问题是,我无法使窗口仅位于其他应用程序窗口(不是计算机上的所有应用程序)的顶部,保持在顶部,阻止与父窗口的交互,并且仍然允许工作继续 以下是我迄今为止所尝试的: 首先,我的启动窗口是一个自定义类,它扩展了窗口类,并具有更新消息框的方法。我在早期创建了一个sp

如果这个问题已经被回答了很多次,我很抱歉,但是我似乎找不到一个适合我的答案。我想创建一个模式窗口,在应用程序执行长时间运行的任务时显示各种进度消息。这些任务在单独的线程上运行,我能够在进程的不同阶段更新进度窗口上的文本。跨线程通信工作得很好。问题是,我无法使窗口仅位于其他应用程序窗口(不是计算机上的所有应用程序)的顶部,保持在顶部,阻止与父窗口的交互,并且仍然允许工作继续

以下是我迄今为止所尝试的:

首先,我的启动窗口是一个自定义类,它扩展了窗口类,并具有更新消息框的方法。我在早期创建了一个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());
这将正确显示窗口并继续运行代码来处理初始化任务,但它允许我单击父窗口并将其置于最前面

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

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));
这将禁用窗口中的所有元素,但我仍然可以单击主窗口并将其置于初始屏幕前,这不是我想要的

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

然后我找到了关于使用
.ShowDialog()
而不是
.Show()
的帖子。我尝试了这个,它正确地显示了对话框,不允许我单击父窗口,但是调用
.ShowDialog()
会使程序挂起,等待您关闭对话框,然后才能继续运行代码。这显然不是我想要的。我想我可以在另一个线程上调用
ShowDialog()

我还考虑了完全不使用窗口的可能性,而是将一个全尺寸的窗口元素放在页面上所有其他元素的前面。这将工作,除了我有其他窗口,我打开,我想能够使用启动屏幕时,这些都是开放的。如果我使用一个窗口元素,我将不得不在每个窗口上重新创建它,并且我将无法在我的自定义splash类中使用方便的
UpdateSplashText
方法

这就引出了一个问题。正确的处理方法是什么


谢谢您的时间,很抱歉问了这么长的问题,但细节很重要:)

您可以让进度窗口的构造函数执行
任务,然后确保窗口调用
任务。在
已加载事件上启动
。然后使用父窗体中的
ShowDialog
,这将导致进度窗口启动任务

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


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

您可以在启动屏幕运行时使用
窗口上的
可见性
属性隐藏整个窗口

XAML


我找到了一种方法,通过在单独的线程上调用
ShowDialog()
来实现这一点。我在dialog类中创建了自己的
ShowMe()
HideMe()
方法来处理这项工作。我还捕获了
关闭
事件,以防止关闭对话框,从而可以重复使用它

以下是我的闪屏类代码:

public partial class StartupSplash : Window
{
    private Thread _showHideThread;

    public StartupSplash()
    {
        InitializeComponent();

        this.Closing += OnCloseDialog;
    }


    public string Message
    {
        get
        {
            return this.lb_progress.Content.ToString();
        }
        set
        {
            if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
                this.lb_progress.Content = value;
            else
                this.lb_progress.Dispatcher.Invoke(new Action(() => this.lb_progress.Content = value));
        }
    }


    public void ShowMe()
    {
        _showHideThread = new Thread(new ParameterizedThreadStart(doShowHideDialog));
        _showHideThread.Start(true);
    }

    public void HideMe()
    {
        //_showHideThread.Start(false);
        this.doShowHideDialog(false);
    }

    private void doShowHideDialog(object param)
    {
        bool show = (bool)param;

        if (show)
        {
            if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
                this.ShowDialog();
            else
                Application.Current.Dispatcher.Invoke(new Action(() => this.ShowDialog()));
        }
        else
        {
            if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
                this.Close();
            else
                Application.Current.Dispatcher.Invoke(new Action(() => this.Close()));
        }
    }


    private void OnCloseDialog(object sender, CancelEventArgs e)
    {
        e.Cancel = true;
        this.Hide();
    }
}

正确的是,
ShowDialog
为您提供了所需的大部分UI行为

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

您可以在splash类中完成所有的工作,但是由于紧密耦合,这是一个相当糟糕的实践

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

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

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

对话框方法:

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

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

这将起作用,但如果所有UI控件都在同一个线程上,从长远来看,您可能会遇到更少的问题。使用多个UI线程非常困难,由于在现有的UI框架中只存在一个UI线程的基本假设,可能会导致许多微妙而难以理解或解决的问题。我通常同意拥有多个UI线程是复杂的,但在这种情况下,SplashScreen将所有次线程逻辑封装在内部。我所要做的就是调用ShowMe并设置Message属性来更改对话框上的文本。调用方不必知道额外的线程。它很容易使用,我想它会很好用的。我当然会在这里调查其他答案,但现在我不得不继续,因为我的最后期限是上周:(这是否是共同的
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");
    });
}
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();
    }
}
    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");
    });