在多线程环境中。
我在进度表单中使用ShowDialog来阻止其他表单上的所有用户活动 完成部分过程后,我隐藏了进度表,我做了一些任务,然后再次显示(ShowDialog) 一切都很好,但进展形式闪烁 有没有办法从ShowDialog继续而不隐藏窗口,或者更好的是,有没有办法将Show转换为ShowDialog并再次返回?
修改
我的代码与此类似
class frmProgress : Form
{
// Standard stuff
public void DoSomeWorks()
{
async Task.Run(() => RunWork1());
ShowDialog();
if (_iLikeTheResultOfWork1)
{
async Task.Run(() => RunWork2());
ShowDialog();
}
}
void RunWork1()
{
// Do a lot of things including update UI
Hide();
}
void RunWork2()
{
// Do a lot of things including update UI
Hide();
}
}
编辑2
感谢大家的答案和建议
最后,我采用了解决方案,将一些小作品集中在一个较大的作品中,我在UI中使用,而在单元测试中,我仍然测试小作品。
实际上它不是我想要的解决方案,我希望找到一个基于处理消息泵或做类似事情的解决方案(请参阅从RunDialog(。)开始的System.Windows.Forms.Application源代码以及所有调用的方法,其中在发布这个问题之前我迷路了。)
答案 0 :(得分:4)
我想你有一些方法如下:
void Task1()
{
Debug.WriteLine("Task1 Started");
System.Threading.Thread.Sleep(5000);
Debug.WriteLine("Task1 Finished");
}
void Task2()
{
Debug.WriteLine("Task2 Started");
System.Threading.Thread.Sleep(3000);
Debug.WriteLine("Task2 Finished");
}
如果要并行运行它们:
private async void button1_Click(object sender, EventArgs e)
{
this.Enabled = false;
//Show a loading image
await Task.WhenAll(new Task[] {
Task.Run(()=>Task1()),
Task.Run(()=>Task2()),
});
//Hide the loading image
this.Enabled = true;
}
如果您打算一个接一个地运行它们:
private async void button1_Click(object sender, EventArgs e)
{
this.Enabled = false;
//Show a loading image
await Task.Run(()=>Task1());
await Task.Run(()=>Task2());
//Hide the loading image
this.Enabled = true;
}
答案 1 :(得分:4)
你不能这样做。至少我并没有一种简单的方法。解决这个问题的一种方法是更改代码,如下所示:
//C#-ish pseudocode based on OPs example; doesn't compile
class frmProgress : Form
{
// Standard stuff
public void DoSomeWorks()
{
async Task.Run(() => RunWork1());
ShowDialog();
}
void RunWork1()
{
// Do a lot of things including update UI
if (_iLikeTheResultOfWork1)
{
async Task.Run(() => RunWork2());
}
else
{
Hide();
}
}
void RunWork2()
{
// Do a lot of things including update UI
Hide();
}
}
编辑对于那些抱怨代码无法编译的人来说,他们是对的。但这是OP将使用 代码示例得到的最好的,这就是我猜他被无情地投票的原因。
但为了使答案与其他人更相关,我的建议是不要隐藏2个任务之间的进度表格,当您确定任务已经结束时隐藏它。所有这些都是通过尊重您正在处理的上下文的线程模型,这是OP的代码所做的事情。从最终将在其他线程中运行的方法操作Winforms中的UI将无法工作。
答案 2 :(得分:2)
引用Async/Await - Best Practices in Asynchronous Programming
我建议您利用异步事件处理程序和任务来允许非阻塞调用,而表单是通过显示对话框进行模态的
class frmProgress : Windows.Form {
// Standard stuff
public void DoSomeWorks() {
Work -= OnWork;
Work += OnWork;
Work(this, EventArgs.Empty);//raise the event and do work on other thread
ShowDialog();
}
private CancellationTokenSource cancelSource;
private event EventHandler Work = delegate { };
private async void OnWork(object sender, EventArgs e) {
Work -= OnWork; //unsubscribe
cancelSource = new CancellationTokenSource(); //to allow cancellation.
var _iLikeTheResultOfWork1 = await RunWork1Async(cancelSource.Token);
if (_iLikeTheResultOfWork1) {
await RunWork2Async(cancelSource.Token);
}
DialogResult = DialogResult.OK; //just an example
}
Task<bool> RunWork1Async(CancellationToken cancelToken) {
// Do a lot of things including update UI
//if while working cancel called
if (cancelToken.IsCancellationRequested) {
return Task.FromResult(false);
}
//Do more things
//return true if successful
return Task.FromResult(true);
}
Task<bool> RunWork2Async(CancellationToken cancelToken) {
// Do a lot of things including update UI
//if while working cancel called
if (cancelToken.IsCancellationRequested) {
return Task.FromResult(false);
}
//Do more things
//return true if successful
return Task.FromResult(true);
}
}
请注意使用取消令牌以允许根据需要取消任务。
UI现在已解除阻止,异步功能可以继续工作。没有闪烁,因为表格保持显示而没有任何中断。
答案 3 :(得分:2)
我可能会被误解,在这里我做了什么,用wndProc
处理对话。我创建了一个额外的表单并使用了Show();
方法。在我显示表单后,启动异步任务。在我显示表单后,我通过wndProc
处理了该表单。
protected override void WndProc(ref Message m) {
if((f2.IsDisposed || !f2.Visible)) {
foreach(var control in controlList) {
control.Enabled = true; // enable all controls or other logic
}
}
if(m.Msg == 0x18 && !f2.IsDisposed) { // notify dialog opens and double checks dialog's situation
foreach(var control in controlList.Where(ctrl => ctrl.Name != "button1")) {
control.Enabled = false; // disable except cancel button
}
}
base.WndProc(ref m);
}
希望有所帮助,
答案 4 :(得分:2)
从您发布的代码中,您在RunWork()方法的末尾调用Hide(),然后在ShowDialog()之后调用。如果我理解正确,您需要在RunWork()方法中调用Hide() first ,这样可以在UI更新发生时访问主UI窗口。一切都完成后,ShowDialog()方法发生并再次阻止主UI线程。
class frmProgress : Form
{
public bool _iLikeTheResultOfWork1 = true;
// Note: changed to async method and now awaiting the task
public async void DoSomeWorks()
{
await Task.Run(() => RunWork1());
ShowDialog();
if (_iLikeTheResultOfWork1)
{
await Task.Run(() => RunWork2());
ShowDialog();
}
}
// Hide window and wait 5 seconds. Note that the main window is active during this 5 seconds.
// ShowDialog() is called again right after this, so the dialog becomes modal again.
void RunWork1()
{
Hide();
Task.Delay(5000).Wait();
// Do a lot of things including update UI
}
void RunWork2()
{
Hide();
Task.Delay(5000).Wait();
// Do a lot of things including update UI
}
}
上面的代码将调用RunWork1(),它会将对话框隐藏5秒钟。在此期间,主UI窗口将处于活动状态。然后,该方法返回并调用ShowDialog()。关闭该窗口以调用RunWork2()并重新开始该过程。
这是你想要实现的吗?我刚测试了这段代码,它没有视觉效果,而且没有#34;闪烁&#34;。如果这是你想要的,那么&#34;闪烁&#34;用这种方法发生?
答案 5 :(得分:1)
“当表单离开其模态消息循环时,它将始终被关闭,这是由ShowDialog()启动的。您可以做的最好的是模拟模态,使用Show()显示表单并禁用所有其他窗口。 “
因此,您可以枚举所有应用程序的Windows并在需要时启用/禁用它们,并在设置模拟模式时将对话框窗口设置为始终位于顶部。我认为总是在顶部意味着它超越了操作系统中的所有窗口,因此如果设置为模拟模态,您可能需要一个计时器来将窗口置于前面(在您的应用程序窗口中)