Winform - 从ShowDialog继续而不隐藏窗口

时间:2018-02-05 06:59:12

标签: c# multithreading winforms

在多线程环境中。

我在进度表单中使用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源代码以及所有调用的方法,其中在发布这个问题之前我迷路了。)

6 个答案:

答案 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()显示表单并禁用所有其他窗口。 “

https://social.msdn.microsoft.com/Forums/sharepoint/en-US/3f6c57a1-92fd-49c5-9f46-9454df80788c/possible-to-change-modality-of-a-dialog-box-at-runtime?forum=winforms

因此,您可以枚举所有应用程序的Windows并在需要时启用/禁用它们,并在设置模拟模式时将对话框窗口设置为始终位于顶部。我认为总是在顶部意味着它超越了操作系统中的所有窗口,因此如果设置为模拟模态,您可能需要一个计时器来将窗口置于前面(在您的应用程序窗口中)