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