在自己的专用UI线程上创建的窗口上的WPF集所有者

时间:2010-10-06 07:48:47

标签: .net wpf windows user-interface thread-safety

我有以下代码,它在自己的专用UI线程上运行WPF窗口:

// Create the dedicated UI thread for AddEditPair window
Thread addEditPairThread = new Thread(() =>
{
    // Initialise the add edit pair window
    addEditPair = new AddEditPair(this);
    addEditPair.PairRecordAdded += new EventHandler<PairRecordEventArgs>(addEditPair_PairRecordAdded);
    addEditPair.PairRecordEdited += new EventHandler<PairRecordEventArgs>(addEditPair_PairRecordEdited);

    // Force AddEditPair to run on own UI thread
    System.Windows.Threading.Dispatcher.Run();
});
addEditPairThread.IsBackground = true;
addEditPairThread.Name = "AddEditPair";
addEditPairThread.SetApartmentState(ApartmentState.STA);
addEditPairThread.Start();

除非我尝试将此窗口的所有者设置为在主Ui线程上运行的窗口,否则这很有效。

我得到的例外是:

The calling thread cannot access this object because a different thread owns it.

我理解错误的含义及其发生的原因,然后我实现了以下内容:

// If invoke is not required - direct call
if (addEditPair.Dispatcher.CheckAccess())
    method();
// Else if invoke is required - invoke
else
    addEditPair.Dispatcher.BeginInvoke(dispatcherPriority, method);

但我仍然得到同样的错误。现在我很困惑!

任何想法?任何帮助将不胜感激。

4 个答案:

答案 0 :(得分:4)

为什么要在单独的线程中创建窗口?

我假设您正在这样做,因为窗口执行长时间运行的代码或需要长时间运行的代码访问,如果这是真的,那么当长时间运行的代码运行时窗口将无响应(这很糟糕,甚至可以在某些情况下冻结整个系统,但我们暂时忽略它 - 并且你正在做整个线程的事情,以便在第二个窗口被冻结时保持主窗口的响应。

WPF不支持这一点 - 但即使支持它也不会有效--Windows链接彼此“相关”的窗口的消息队列(这是保持系统行为的唯一方法)因此,非响应窗口不会处理消息,这将阻止队列,反过来会阻止所有者窗口接收消息,使其也无响应。

大多数程序只有一个UI线程是有原因的 - 逻辑很简单:

  • 在不同的线程中运行某些内容的唯一原因是,如果您有两个或更多长时间运行或阻塞操作,并且您不希望它们相互阻塞。

  • 执行长时间运行或阻止操作的窗口将无响应,这将影响同一应用程序中的其他窗口(即使它们位于不同的线程上)并可能会破坏整个系统的稳定性 - 所以我们不要我想要那个。

  • 所以我不能从窗口执行阻止或长时间运行的操作,而是使用后台线程。

  • 如果一个窗口没有执行长时间运行或阻塞操作,它将不会阻塞该线程,因此可以在与其他表现良好的窗口相同的线程上运行而没有任何问题。

  • 由于同一个线程上的窗口不会互相干扰,多线程会增加复杂性 - 因此没有理由拥有多个UI线程。

注意:只有一个实际显示UI的UI线程,使用永远不会打开窗口的WPF的后台线程完全没问题(例如:在后台创建一个大的FixedDocument)我没有调用那些UI线程,我也没有说过后台线程的数量。

答案 1 :(得分:1)

如果你真的想在另一个线程中设置所有者而不是必须使用user32函数。

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

获取WPF处理程序的代码:

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

请注意,应在多线程所有者调用之前初始化窗口!

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

P.S。也可以使用SetParent:

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

答案 2 :(得分:0)

如果没有窗口被冻结(我不确定是否可能),在WPF中尝试将一个窗口设置为另一个窗口上的窗口的父窗口是不可能的,因为每个窗口都无法访问其他数据。

是否有充分的理由在单独的线程上创建窗口?大多数情况下,您可以在相同的ui线程上创建窗口,并使用后台工作程序处理长时间运行的tak。

答案 3 :(得分:-1)

某个窗口的Owner属性似乎正在进行某种验证,它检查窗口是否在同一个线程上创建。

我的解决方法是,实现我自己的Main Window类型的属性,并从构造函数中存储对this的引用,如下所示:

private MainWindow _main;

public AddEditPair(MainWindow main)
    : base(false)
{
    InitializeComponent();

    // Initialise local variables
    _main = main;
}

public MainWindow Main
{
    get
    {
        return _main;
    }
}

现在我可以访问主窗口。