在 WPF 中,如何在另一个线程(另一个 Dispatcher)上设置 Window 构建的 Window Owner

Eri*_*let 5 c# wpf multithreading dispatcher

我收到以下异常: InvalidOperationException :调用线程无法访问此对象,因为其他线程拥有它。

当我尝试设置一个窗口的所有者时,该窗口的所有者建立在另一个线程上而不是所有者。

我知道我只能从正确的线程更新 UI 对象,但是如果它来自另一个线程,为什么我不能只设置所有者?我可以换一种方式吗?我想让进度窗口成为唯一可以输入条目的窗口。

这是发生错误的代码部分:

    public partial class DlgProgress : Window
{
    // ******************************************************************
    private readonly DlgProgressModel _dlgProgressModel;

    // ******************************************************************
    public static DlgProgress CreateProgressBar(Window owner, DlgProgressModel dlgProgressModel)
    {
        DlgProgress dlgProgressWithProgressStatus = null;
        var listDlgProgressWithProgressStatus = new List<DlgProgress>();
        var manualResetEvent = new ManualResetEvent(false);
        var workerThread = new ThreadEx(() => StartDlgProgress(owner, dlgProgressModel, manualResetEvent, listDlgProgressWithProgressStatus));
        workerThread.Thread.SetApartmentState(ApartmentState.STA);
        workerThread.Start();
        manualResetEvent.WaitOne(10000);
        if (listDlgProgressWithProgressStatus.Count > 0)
        {
            dlgProgressWithProgressStatus = listDlgProgressWithProgressStatus[0];
        }

        return dlgProgressWithProgressStatus;
    }

    // ******************************************************************
    private static void StartDlgProgress(Window owner, DlgProgressModel progressModel, ManualResetEvent manualResetEvent, List<DlgProgress> listDlgProgressWithProgressStatus)
    {
        DlgProgress dlgProgress = new DlgProgress(owner, progressModel);
        listDlgProgressWithProgressStatus.Add(dlgProgress);
        dlgProgress.ShowDialog();
        manualResetEvent.Set();
    }

    // ******************************************************************
    private DlgProgress(Window owner, DlgProgressModel dlgProgressModel)
    {
        if (owner == null)
        {
            throw new ArgumentNullException("Owner cannot be null");
        }

        InitializeComponent();
        this.Owner = owner; // Can't another threads owns it exception
Run Code Online (Sandbox Code Playgroud)

Iev*_*ida 2

上面的答案是正确的。但我会尝试总结一下:

[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

public static void SetOwnerWindowMultithread(IntPtr windowHandleOwned, IntPtr intPtrOwner)
{
    if (windowHandleOwned != IntPtr.Zero && intPtrOwner != IntPtr.Zero)
    {
        SetWindowLong(windowHandleOwned, GWL_HWNDPARENT, intPtrOwner.ToInt32());
    }
}
Run Code Online (Sandbox Code Playgroud)

获取 WPF 处理程序的代码:

public static IntPtr GetHandler(Window window)
{
    var interop = new WindowInteropHelper(window);
    return interop.Handle;
}
Run Code Online (Sandbox Code Playgroud)

请注意,窗口应在设置所有者调用之前初始化!(可以在window.Loaded或者window.SourceInitialized事件中设置)

var handler = User32.GetHandler(ownerForm);

var thread = new Thread(() =>
{
    var window = new DialogHost();
    popupKeyboardForm.Show();
    SetOwnerWindowMultithread(GetHandler(popupKeyboardForm), handler);
    Dispatcher.Run();
});

thread.IsBackground = true;
thread.Start();
Run Code Online (Sandbox Code Playgroud)

也可以使用 SetParent。比您不需要转换处理程序:

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
Run Code Online (Sandbox Code Playgroud)

请注意,父级和所有者具有不同的含义。 Win32 窗口所有者与窗口父级?