简体   繁体   English

C#WinForms:在另一个线程中具有IWin32Window owner参数的Form.ShowDialog()

[英]C# WinForms: Form.ShowDialog() with IWin32Window owner parameter in a different thread

I am creating a C# VSTO addin and am having trouble with setting the owner window parameter in Form.ShowDialog() when the form is shown in a secondary thread and the owner window is on the main thread. 我正在创建一个C#VSTO加载项,并且当表单显示在辅助线程中并且所有者窗口位于主线程上时,无法在Form.ShowDialog()中设置所有者窗口参数。

When using VSTO, Excel only supports changes to the Excel object model on the main thread (it can be done on a separate thread but is dangerous and will throw COM exceptions if Excel is busy). 使用VSTO时,Excel仅支持对主线程上的Excel对象模型进行更改(它可以在单独的线程上完成,但很危险,如果Excel繁忙则将引发COM异常)。 I would like to show a progress form while executing a long operation. 我想在执行长时间操作时显示进度表。 To make the progress form fluid, I show the form on a separate thread and update the progress asynchronously from the main thread using Control.BeginInvoke(). 为了使进度表单流畅,我在单独的线程上显示表单,并使用Control.BeginInvoke()从主线程异步更新进度。 This all works fine, but I seem to only be able to show the form using Form.ShowDialog() with no parameters. 一切正常,但我似乎只能使用不带参数的Form.ShowDialog()来显示表单。 If I pass an IWin32Window or NativeWindow as a parameter to ShowDialog, the form freezes up and does not update the progress. 如果我将IWin32Window或NativeWindow作为参数传递给ShowDialog,则窗体冻结并且不更新进度。 This may be because the owner IWin32Window parameter is a Window that exists on the main thread and not the secondary thread that the progress form is displayed on. 这可能是因为所有者IWin32Window参数是存在于主线程上的窗口,而不是显示进度表的辅助线程。

Is there any trick I can try to pass a IWin32Window to the ShowDialog function when the form is on a separate thread. 当表单位于单独的线程上时,有什么技巧可以尝试将IWin32Window传递给ShowDialog函数。 Technically I don't need to set the form's owner, but rather the form's parent if there is such a difference. 从技术上讲,我不需要设置表单的所有者,但是如果存在这种差异,则需要设置表单的父级。

I'd like my dialog to be linked with the Excel Window so that when Excel is minimized or maximized, the dialog will be hidden or shown accordingly. 我希望对话框与Excel窗口链接,以便在最小化或最大化Excel时,该对话框将被隐藏或相应显示。

Please note that I have already tried going the BackgroundWorker route and it was not successful for what I was trying to accomplish. 请注意,我已经尝试过BackgroundWorker路线,但对于我要完成的任务却没有成功。

----Updated with sample code: ----更新了示例代码:

Below is a trimmed down version of what I am trying to do and how I am trying to do it. 以下是我尝试做的事情以及如何做的精简版。 The MainForm is not actually used in my application, as I am trying to use it to represent the Excel Window in a VSTO application. MainForm实际上并未在我的应用程序中使用,因为我试图使用它来表示VSTO应用程序中的Excel窗口。

Program.cs: Program.cs中:

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}

MainForm.cs: MainForm.cs:

using System;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApplication1
{
    public partial class MainForm : Form
    {
        public ManualResetEvent SignalEvent = new ManualResetEvent(false);
        private ProgressForm _progressForm;
        public volatile bool CancelTask;

        public MainForm()
        {
            InitializeComponent();
            this.Name = "MainForm";
            var button = new Button();
            button.Text = "Run";
            button.Click += Button_Click;
            button.Dock = DockStyle.Fill;
            this.Controls.Add(button);
        }

        private void Button_Click(object sender, EventArgs e)
        {
            CancelTask = false;
            ShowProgressFormInNewThread();
        }

        internal void ShowProgressFormInNewThread()
        {
            var thread = new Thread(new ThreadStart(ShowProgressForm));
            thread.Start();

            //The main thread will block here until the signal event is set in the ProgressForm_Load.
            //this will allow us to do the work load in the main thread (required by VSTO projects that access the Excel object model),
            SignalEvent.WaitOne();
            SignalEvent.Reset();

            ExecuteTask();
        }

        private void ExecuteTask()
        {
            for (int i = 1; i <= 100 && !CancelTask; i++)
            {
                ReportProgress(i);
                Thread.Sleep(100);
            }
        }

        private void ReportProgress(int percent)
        {
            if (CancelTask)
                return;
            _progressForm.BeginInvoke(new Action(() => _progressForm.UpdateProgress(percent)));
        }

        private void ShowProgressForm()
        {
            _progressForm = new ProgressForm(this);
            _progressForm.StartPosition = FormStartPosition.CenterParent;

            //this works, but I want to pass an owner parameter
            _progressForm.ShowDialog();

            /*
             * This gives an exception:
             * An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
             * Additional information: Cross-thread operation not valid: Control 'MainForm' accessed from a thread other than the thread it was created on.
             */
            //var window = new Win32Window(this);
            //_progressForm.ShowDialog(window);

        }

    }
}

ProgressForm.cs: ProgressForm.cs:

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class ProgressForm : Form
    {
        private ProgressBar _progressBar;
        private Label _progressLabel;
        private MainForm _mainForm;

        public ProgressForm(MainForm mainForm)
        {
            InitializeComponent();
            _mainForm = mainForm;
            this.Width = 300;
            this.Height = 150;
            _progressBar = new ProgressBar();
            _progressBar.Dock = DockStyle.Top;
            _progressLabel = new Label();
            _progressLabel.Dock = DockStyle.Bottom;
            this.Controls.Add(_progressBar);
            this.Controls.Add(_progressLabel);
            this.Load += ProgressForm_Load;
            this.Closed += ProgressForm_Close;
        }

        public void UpdateProgress(int percent)
        {
            if(percent >= 100)
                Close();

            _progressBar.Value = percent;
            _progressLabel.Text = percent + "%";
        }

        public void ProgressForm_Load(object sender, EventArgs e)
        {
            _mainForm.SignalEvent.Set();
        }

        public void ProgressForm_Close(object sender, EventArgs e)
        {
            _mainForm.CancelTask = true;
        }

    }
}

Win32Window.cs: Win32Window.cs:

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class Win32Window : IWin32Window
    {
        private readonly IntPtr _handle;

        public Win32Window(IWin32Window window)
        {
            _handle = window.Handle;
        }

        IntPtr IWin32Window.Handle
        {
            get { return _handle; }
        }
    }
}

Adding another answer because although it can be done this way, it's not the recommended way (eg should never have to call Application.DoEvents() ). 添加另一个答案,因为尽管可以通过这种方式完成,但不建议这样做(例如,永远不必调用Application.DoEvents() )。

Use the pinvoke SetWindowLong to set the owner, however doing so then causes DoEvents to be required. 使用pinvoke SetWindowLong设置所有者,但是这样做会导致需要DoEvents

A couple of your requirements don't make sense either. 您的几个要求也没有道理。 You say you want the dialog to minimize and maximize with the Excel window, but your code is locking up the UI thread, which prevents clicking on the Excel window. 您说要用Excel窗口最小化和最大化对话框,但是您的代码锁定了UI线程,从而阻止了单击Excel窗口。 Also, you are using ShowDialog . 另外,您正在使用ShowDialog So if the progress dialog was left open after finishing, the user still cannot minimize the Excel window because ShowDialog is used. 因此,如果进度对话框在完成后仍处于打开状态,则用户仍然无法最小化Excel窗口,因为使用了ShowDialog

public partial class MainForm : UserControl
{
    public ManualResetEvent SignalEvent = new ManualResetEvent(false);
    private ProgressForm2 _progressForm;
    public volatile bool CancelTask;

    public MainForm()
    {
        InitializeComponent();
        this.Name = "MainForm";
        var button = new Button();
        button.Text = "Run";
        //button.Click += button1_Click;
        button.Dock = DockStyle.Fill;
        this.Controls.Add(button);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        CancelTask = false;
        ShowProgressFormInNewThread();
    }

    internal void ShowProgressFormInNewThread()
    {
        var thread = new Thread(new ParameterizedThreadStart(ShowProgressForm));
        thread.Start(Globals.ThisAddIn.Application.Hwnd);

        //The main thread will block here until the signal event is set in the ProgressForm_Load.
        //this will allow us to do the work load in the main thread (required by VSTO projects that access the Excel object model),
        SignalEvent.WaitOne();
        SignalEvent.Reset();

        ExecuteTask();
    }

    private void ExecuteTask()
    {
        for (int i = 1; i <= 100 && !CancelTask; i++)
        {
            ReportProgress(i);
            Thread.Sleep(100);

            // as soon as the Excel window becomes the owner of the progress dialog
            // then DoEvents() is required for the progress bar to update
            Application.DoEvents();
        }
    }

    private void ReportProgress(int percent)
    {
        if (CancelTask)
            return;
        _progressForm.BeginInvoke(new Action(() => _progressForm.UpdateProgress(percent)));
    }

    private void ShowProgressForm(Object o)
    {
        _progressForm = new ProgressForm2(this);
        _progressForm.StartPosition = FormStartPosition.CenterParent;

        SetWindowLong(_progressForm.Handle, -8, (int) o); // <-- set owner
        _progressForm.ShowDialog();
    }

    [DllImport("user32.dll")]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
}

It's unusual to create winform controls on a non-UI thread. 在非UI线程上创建winform控件是不寻常的。 It's better to create the ProgressForm when the button is first clicked, then you don't need the ManualResetEvent . 最好在第一次单击按钮时创建ProgressForm ,然后再不需要ManualResetEvent

Have the ProgressForm implement a simple interface ( IThreadController ) that allows your executing task to update the progress. ProgressForm实现一个简单的接口( IThreadController ),该接口允许您正在执行的任务更新进度。

The owner of the ProgressForm is IntPtr handle = new IntPtr(Globals.ThisAddIn.Application.Hwnd); ProgressForm的所有者是IntPtr handle = new IntPtr(Globals.ThisAddIn.Application.Hwnd); , which causes the ProgressForm to minimize and restore with the Excel window. ,这会导致ProgressForm最小化并使用Excel窗口进行还原。

I don't think you need to use ShowDialog because it will block the UI thread. 我认为您不需要使用ShowDialog因为它将阻塞UI线程。 You can use Show instead. 您可以改用Show

Eg 例如

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;

namespace ExcelAddIn1 {
public partial class UserControl1 : UserControl {

    public UserControl1() {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;

        var pf = new ProgressForm();
        IntPtr handle = new IntPtr(Globals.ThisAddIn.Application.Hwnd);
        pf.Show(new SimpleWindow { Handle = handle });

        Thread t = new Thread(o => {
            ExecuteTask((IThreadController) o);
        });
        t.IsBackground = true;
        t.Start(pf);

        pf.FormClosed += delegate {
            button1.Enabled = true;
        };
    }

    private void ExecuteTask(IThreadController tc)
    {
        for (int i = 1; i <= 100 && !tc.IsStopRequested; i++)
        {
            Thread.Sleep(100);
            tc.SetProgress(i, 100);
        }
    }

    class SimpleWindow : IWin32Window {
        public IntPtr Handle { get; set; }
    }
}

interface IThreadController {
    bool IsStopRequested { get; set; }
    void SetProgress(int value, int max);
}

public partial class ProgressForm : Form, IThreadController {
    private ProgressBar _progressBar;
    private Label _progressLabel;

    public ProgressForm() {
        //InitializeComponent();
        this.Width = 300;
        this.Height = 150;
        _progressBar = new ProgressBar();
        _progressBar.Dock = DockStyle.Top;
        _progressLabel = new Label();
        _progressLabel.Dock = DockStyle.Bottom;
        this.Controls.Add(_progressBar);
        this.Controls.Add(_progressLabel);
    }

    public void UpdateProgress(int percent) {
        if (percent >= 100)
            Close();

        _progressBar.Value = percent;
        _progressLabel.Text = percent + "%";
    }

    protected override void OnClosed(EventArgs e) {
        base.OnClosed(e);
        IsStopRequested = true;

    }

    public void SetProgress(int value, int max) {
        int percent = (int) Math.Round(100.0 * value / max);

        if (InvokeRequired) {
            BeginInvoke((Action) delegate {
                UpdateProgress(percent);
            });
        }
        else
            UpdateProgress(percent);
    }

    public bool IsStopRequested { get; set; }
}


}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 WPF 等效于 Form.ShowDialog(IWin32Window) - WPF equivalent of Form.ShowDialog(IWin32Window) Form.ShowDialog(IWin32Window) 是否应该与任何 window 句柄一起使用? - Should Form.ShowDialog(IWin32Window) work with any window handle? .Owner属性和ShowDialog(IWin32Window所有者)之间的区别? - Difference between .Owner property and ShowDialog(IWin32Window owner)? c#中ShowDialog()和ShowDialog(IWin32Window)有什么区别? - What's difference between ShowDialog() and ShowDialog(IWin32Window) in c#? C# 中的 IWin32Window? - IWin32Window in C#? ShowDialog(所有者)隐藏在/不显示,即使我正在提供所有者IWin32Window - ShowDialog(owner) is hiding behind/not showing even though I am supplying owner IWin32Window WPF中是否有类似Winforms.Show(IWin32Window owner)的方法? - Is there a method like Winforms.Show ( IWin32Window owner ) in WPF? 如何使用反射获取表单的IWin32Window - how to get IWin32Window of form using reflection C#未处理的异常&#39;必须在创建第一个IWin32Window对象之前调用SetCompatibleTextRenderingDefault&#39; - C# Unhandled Exception 'SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created' 从另一个类打开通用表单会导致IWin32Window转换错误 - Opening a generic form from another class causes a IWin32Window conversion error
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM