Search code examples
c#wpfmultithreadingdispatcher

In WPF, how to set a Window Owner of a Window build on another thread (another Dispatcher)


I got the following exception: InvalidOperationException : The calling thread cannot access this object because a different thread owns it.

when I try to set the Owner of a window that is build on another thread than the Owner.

I know that I can only update UI object from the proper thread, but why I can't just set the owner if it come from another thread? Can I do it on another way ? I want to make the progress window the only one which can have input entries.

This is the portion of code where bug occurs:

    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

Solution

  • Answer above was correct. But I will try to summarize:

    [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());
        }
    }
    

    Code to get WPF handler:

    public static IntPtr GetHandler(Window window)
    {
        var interop = new WindowInteropHelper(window);
        return interop.Handle;
    }
    

    Note that window should be initialized before set owner call! (can be set in window.Loaded or window.SourceInitialized event)

    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();
    

    Also SetParent can be used. Than you dont need to convert handlers:

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

    Note that parent and owner have different meanings. Win32 window Owner vs window Parent?