5

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
Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119
  • you can't. You can't put together `DispatcherObject`s belonging to different `Dispatcher`s – Federico Berasategui Feb 15 '13 at 13:51
  • You really don't want to be creating multilpe UI threads, which is what it appears that you're doing. You *really* want to keep a single UI thread for your application if at all possible. – Servy Feb 15 '13 at 14:34
  • Why don't you create your `dlgProgress` in your owner's thread and then pass it to your `workerThread`, instead of passing the `owner` parameter? – Colin Feb 15 '13 at 14:36
  • Technically this is supported by Windows, you'd have to pinvoke SetParent(). To what degree that's going to gum-up the WPF plumbing is something you definitely have to worry about, it certainly wasn't written to support it. There's just no point in writing code like this, have the UI thread display the dialog, do the actual heavy lifting in a worker thread. BackgroundWorker is always good for that. – Hans Passant Feb 15 '13 at 14:40
  • I want to have a ProgressWindow into a different messageLoop because my UI update is very intensive (OCX STA) and take a while. I want to be able to cancel and have the cancel button responsive. – Eric Ouellet Feb 15 '13 at 14:55
  • To Hans, I'm not sure but I think that BackGroundWorker will run on the main message loop and will have the save problem... My cancel button on my progress will not be responsive due to very intensive drawing on UI. – Eric Ouellet Feb 15 '13 at 14:57
  • To Hans, I think that parent is not the owner and I think the owner should be set at creation. Parent is more the relation between Window and its controls. – Eric Ouellet Feb 15 '13 at 15:01
  • To Hans, Thanks, your suggestion brings me to the proper way. My code is a little bit twisted but seems to work fine. Note: you should use SetWindowLong instead of SetParent to set Owner. – Eric Ouellet Feb 15 '13 at 16:08

4 Answers4

2

I made it according based mainly on Hans Passant suggestion. Important, I suspect that this code should only work on 32 bits because I use "ToInt32" on IntPtr.

This is the code:

WindowHelper function:

        // ******************************************************************
    private const int GWL_HWNDPARENT = -8; // Owner --> not the parent

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

    // ******************************************************************
    public static void SetOwnerWindow(Window owned, IntPtr intPtrOwner)
    {
        try
        {
            IntPtr windowHandleOwned = new WindowInteropHelper(owned).Handle;
            if (windowHandleOwned != IntPtr.Zero && intPtrOwner != IntPtr.Zero)
            {
                SetWindowLong(windowHandleOwned, GWL_HWNDPARENT, intPtrOwner.ToInt32());
            }
        }
        catch (Exception ex)
        {
            Debug.Print(ex.Message);
        }
    }

    // ******************************************************************

Calling function:

  using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
using HQ.Util.General.Threading;
using HQ.Util.Unmanaged;

namespace HQ.Wpf.Util.Dialog
{
    /// <summary>
    /// Interaction logic for DlgProgressWithProgressStatus.xaml
    /// </summary>
    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 resetEvent = new ManualResetEvent(false);

            IntPtr windowHandleOwner = new WindowInteropHelper(owner).Handle;
            dlgProgressModel.Owner = owner;
            dlgProgressModel.IntPtrOwner = windowHandleOwner;

            var workerThread = new ThreadEx(() => StartDlgProgress(dlgProgressModel, resetEvent, listDlgProgressWithProgressStatus));
            workerThread.Thread.SetApartmentState(ApartmentState.STA);
            workerThread.Start();
            resetEvent.WaitOne(10000);
            if (listDlgProgressWithProgressStatus.Count > 0)
            {
                dlgProgressWithProgressStatus = listDlgProgressWithProgressStatus[0];
            }

            return dlgProgressWithProgressStatus;
        }

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

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

            InitializeComponent();
            // this.Owner = owner; // Can't another threads owns it exception

            if (dlgProgressModel == null)
            {
                throw new ArgumentNullException("dlgProgressModel");
            }

            _dlgProgressModel = dlgProgressModel;
            _dlgProgressModel.Dispatcher = this.Dispatcher;
            _dlgProgressModel.PropertyChanged += _dlgProgressModel_PropertyChanged;
            DataContext = _dlgProgressModel;
        }

        // ******************************************************************
        // Should be call as a modal dialog
        private new void Show()
        {
            throw new Exception("Should only be used as modal dialog");
        }

        // ******************************************************************
        void _dlgProgressModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {

            //          if (e.PropertyName == "IsJobCanceled" || e.PropertyName == "IsJobCompleted" || e.PropertyName == "IsProgressCompleted")
            // Faster if we don't check strings and check condition directly 
            {
                if (_dlgProgressModel.HaveConditionToClose())
                {
                    if (_dlgProgressModel.IsJobCanceled == true)
                    {
                        SetDialogResult(false);
                    }
                    else
                    {
                        SetDialogResult(true);
                    }
                }
            }
        }

        // ******************************************************************
        private void SetDialogResult(bool result)
        {
            this._dlgProgressModel.Dispatcher.BeginInvoke(new Action(() =>
                {
                    this.DialogResult = result;
                }), DispatcherPriority.Background);
        }

        // ******************************************************************
        private bool _isFirstTimeLoaded = true;

        private Timer _timer = null;
        // ******************************************************************
        private void WindowLoaded(object sender, RoutedEventArgs e)
        {
            if (_isFirstTimeLoaded)
            {
                WindowHelper.SetOwnerWindow(this, _dlgProgressModel.IntPtrOwner);
                Dispatcher.BeginInvoke(new Action(ExecuteDelayedAfterWindowDisplayed), DispatcherPriority.Background);
                _isFirstTimeLoaded = false;

                if (_dlgProgressModel.FuncGetProgressPercentageValue != null)
                {
                    TimerCallback(null);
                    _timer = new Timer(TimerCallback, null, _dlgProgressModel.MilliSecDelayBetweenCall, _dlgProgressModel.MilliSecDelayBetweenCall);
                }
            }
        }

        // ******************************************************************
        private void TimerCallback(Object state)
        {
            Dispatcher.BeginInvoke(new Action(() =>
                {
                    _dlgProgressModel.ValueCurrent = _dlgProgressModel.FuncGetProgressPercentageValue();
                }));
        }

        // ******************************************************************
        private void ExecuteDelayedAfterWindowDisplayed()
        {
            if (_dlgProgressModel._actionStarted == false)
            {
                _dlgProgressModel._actionStarted = true;
                Task.Factory.StartNew(ExecuteAction);
            }
        }

        // ******************************************************************
        private void ExecuteAction()
        {
            _dlgProgressModel.ExecuteAction();
            _dlgProgressModel._actionTerminated = true;
            _dlgProgressModel.IsJobCompleted = true;
        }

        // ******************************************************************
        private void CmdCancel_Click(object sender, RoutedEventArgs e)
        {
            this._dlgProgressModel.IsJobCanceled = true;
        }

        // ******************************************************************
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (! _dlgProgressModel.HaveConditionToClose())
            {
                e.Cancel = true;
                return;
            }

            WindowHelper.SetOwnerWindow(this, 0);

            this.CmdCancel.IsEnabled = false;
            this.CmdCancel.Content = "Canceling...";
            this._dlgProgressModel.Dispose();
        }

        // ******************************************************************
    }
}
Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119
2

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?

Gudarzi
  • 486
  • 3
  • 7
  • 22
Ievgen
  • 4,261
  • 7
  • 75
  • 124
  • Thanks a lots for the information. I'm really happy that you clarify it. If I ever need it, it will be easier for me to do it. – Eric Ouellet Feb 25 '16 at 16:52
0
internal class WindowHelp
{
    private const int GWL_HWNDPARENT = -8;
    [DllImport("user32.dll")]
    private static extern IntPtr SetWindowLong(IntPtr hwnd, int index, int newStyle);
    public static void SetOwnerWindow(IntPtr hwndOwned, IntPtr intPtrOwner)
    {
        try
        {
            if (hwndOwned != IntPtr.Zero && intPtrOwner != IntPtr.Zero)
            {
                SetWindowLong(hwndOwned, GWL_HWNDPARENT, intPtrOwner.ToInt32());
            }
        }
        catch { }
    }
}
WindowInteropHelper helper = new WindowInteropHelper(owner);
_messageBox.Loaded += (sender, e) =>
{
    IntPtr windowHandleOwned = new WindowInteropHelper(_messageBox).Handle;
    owner.Dispatcher.Invoke(new Action(() =>
    {
        WindowHelp.SetOwnerWindow(windowHandleOwned, helper.Handle);
    }));
};

A problem this has is that when the application is shutting down and the owned window is still open it will try to execute something that can fail, my guess is that it's trying to close all owned windows.

System.ComponentModel.Win32Exception
  HResult=0x80004005
  Message=Invalid window handle
  Source=WindowsBase
  StackTrace:
   at MS.Win32.ManagedWndProcTracker.HookUpDefWindowProc(IntPtr hwnd)
   at MS.Win32.ManagedWndProcTracker.OnAppDomainProcessExit()
   at MS.Win32.ManagedWndProcTracker.ManagedWndProcTrackerShutDownListener.OnShutDown(Object target, Object sender, EventArgs e)
   at MS.Internal.ShutDownListener.HandleShutDown(Object sender, EventArgs e)

A disadvantage of giving it its own thread is that you have to keep track of the child window and close it when the main window is closing before the application reaches the later stages of shut down:

private void View_Closing(object sender, CancelEventArgs e)
{
    UIGlobal.SelfThreadedDialogs.ForEach(k =>
    {
        try
        {
            if (k != null && !k.Dispatcher.HasShutdownStarted)
            {
                k.Dispatcher.InvokeShutdown();
                //k.Dispatcher.Invoke(new Action(() => { k.Close(); }));
            }
        }
        catch { }
    });
}

The price of having this kind of "multi-threaded and related" behavior.

Sometimes the tracking and/or the owner's View_Closing code is not needed. Sometimes you only need tracking to keep a reference to the owned windows so that they aren't garbage collected before application shutdown occurs. It depends. See what works for your situation.

fartwhif
  • 321
  • 3
  • 12
-1

It's not about setting the owner. If you want to manipulate a control in WPF from another thread, you'll need to create a delegate and pass this to the dispatcher of the control.

if(Control.Dispatcher.CheckAccess())
{
    //The control can be accessed without using the dispatcher.
    Control.DoSomething();
}
else{
     //The dispatcher of the control needs to be informed
     MyDelegate md = new MyDelegate( delegate() { Control.DoSomething(); });
     Control.Dispatcher.Invoke(md, null);
}

See this post.

Community
  • 1
  • 1
bash.d
  • 13,029
  • 3
  • 29
  • 42
  • 1
    Thanks bash, I know I have to use the Dispatcher to do so. My problem is that I want to have same behavior as modal window over another one. But due to have 2 window on 2 differents threads cause me a real headache. – Eric Ouellet Feb 15 '13 at 14:59
  • This one not working when you try to set owner from other thread. Exception will be thrown. – Ievgen Feb 25 '16 at 13:35