在自己的专用UI线程上创build的WPF在窗口上设置所有者

我有下面的代码,它在自己的专用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); 

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

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

你为什么在一个单独的线程中创建一个窗口?

我假设你是这样做的,因为窗口执行长时间运行的代码或需要被长时间运行的代码访问,如果这是真的,当长时间运行的代码正在运行时,窗口将是不响应的(这是不好的,甚至可能冻结在一些情况下,你的整个系统,但让我们暂时忽略这一点) – 而你正在做的全线程的事情,以保持主窗口响应,而第二个窗口被冻结。

这不被WPF所支持 – 但即使它被支持,它也不会起作用 – Windows将相互“相关”的窗口消息队列链接起来(这是保持系统行为可预测性的唯一方法)无响应的窗口将不会处理消息,这将阻塞队列,这反过来将阻止所有者窗口接收消息使其也不响应。

有一个原因,大多数程序只有一个UI线程 – 逻辑很简单:

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

  • 执行长时间运行或阻止操作的窗口将是无响应的,将会影响同一应用程序中的其他窗口(即使它们在不同的线程中),并可能使整个系统不稳定 – 所以我们不希望这样做。

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

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

  • 而且由于同一个线程中的窗口不会互相干扰,多线程增加了复杂性,所以没有理由再多一个UI线程。

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

如果你真的想在另一个线程中设置所有者,则必须使用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(); 

Ps也可以使用SetParent:

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

试图设置一个窗口是另一个线程的窗口的父窗口是不可能的WPF没有冻结窗口(我不知道是可能的),因为每个窗口将无法访问其他数据。

有没有很好的理由来创建独立的线程上的窗口? 大多数情况下,您应该很好地创建所有相同的UI线程上的窗口,并使用后台工作人员处理长时间运行。

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

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

 private MainWindow _main; public AddEditPair(MainWindow main) : base(false) { InitializeComponent(); // Initialise local variables _main = main; } public MainWindow Main { get { return _main; } } 

现在我可以访问主窗口。