WPF Set Owner sur Window créé sur un thread d’interface utilisateur dédié

J’ai le code suivant, qui exécute une fenêtre WPF sur son propre thread d’interface utilisateur dédié:

// 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(addEditPair_PairRecordAdded); addEditPair.PairRecordEdited += new EventHandler(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(); 

Cela fonctionne très bien sauf lorsque j’essaie de définir le propriétaire de cette fenêtre sur une fenêtre qui s’exécute sur le thread principal de l’interface utilisateur.

L’exception que je reçois est:

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

Je comprends ce que signifie l’erreur et pourquoi cela se produit, alors j’ai implémenté ce qui suit:

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

Mais j’ai toujours la même erreur. Maintenant, je suis confus!

Des idées quelqu’un? Toute aide serait appréciée.

Pourquoi créez-vous une fenêtre dans un thread séparé?

Je suppose que vous le faites parce que la fenêtre exécute le long code en cours d’exécution ou doit être accédé par le long code d’exécution, si ceci est vrai que la fenêtre sera non-réactive quand le long code fonctionnant est en cours d’exécution votre système entier dans certaines circonstances, mais ignorons cela pendant un moment) – et vous faites tout le truc pour garder la fenêtre principale réactive pendant que la deuxième fenêtre est gelée.

Ceci n’est pas supporté par WPF – mais même s’il était supporté, cela n’aurait pas fonctionné – Windows lie les files de messages des fenêtres “liées” entre elles (c’est le seul moyen de maintenir le comportement du système prévisible) la fenêtre non réactive ne traitera pas les messages et bloquera la queue, ce qui empêcherait la fenêtre propriétaire de recevoir des messages, ce qui la rendrait également non réactive.

Il y a une raison pour laquelle la plupart des programmes n’ont qu’un seul thread d’interface utilisateur – la logique est simple:

  • La seule raison d’exécuter quelque chose dans un thread différent est que vous ayez deux ou plusieurs longues opérations d’exécution ou de blocage et que vous ne souhaitez pas qu’elles se bloquent.

  • Une fenêtre qui exécute une longue opération d’exécution ou de blocage ne répondra pas, affectera les autres fenêtres de la même application (même si elles se trouvent sur des threads différents) et risque de déstabiliser l’ensemble du système. Nous ne le souhaitons donc pas.

  • Donc, je ne dois pas exécuter de blocages ou d’opérations longues à partir d’une fenêtre, mais utiliser un thread d’arrière-plan.

  • Si une fenêtre n’exécute pas de longues opérations d’exécution ou de blocage, elle ne bloquera pas le thread et pourra donc s’exécuter sans problème sur le même thread avec d’autres fenêtres bien comscopes.

  • Et comme les fenêtres sur le même thread n’interfèrent pas entre elles et que le multithread ajoute de la complexité, il n’y a aucune raison d’avoir plus d’un thread d’interface utilisateur.

Remarque: un seul thread d’interface utilisateur affichant l’interface utilisateur, il est parfaitement correct d’avoir des threads d’arrière-plan utilisant WPF qui n’ouvrent jamais une fenêtre (exemple: créer un fichier FixedDocument volumineux en arrière-plan) Ne dites rien sur le nombre de threads d’arrière-plan.

Si vous voulez vraiment définir le propriétaire dans l’autre thread, vous devez utiliser la fonction 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()); } } 

Code pour obtenir le gestionnaire WPF:

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

Notez que cette fenêtre doit être initialisée avant l’appel propriétaire multithread!

  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 aussi SetParent peut être utilisé:

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

Essayer de définir une fenêtre comme étant le parent d’une fenêtre sur un autre thread n’est pas possible dans WPF sans que les fenêtres ne soient gelées (ce qui n’est pas possible) car chacune des fenêtres ne pourra pas accéder aux autres données .

Y a-t-il une bonne raison de créer des fenêtres sur des threads distincts? La plupart du temps, vous devriez bien créer des fenêtres sur les mêmes threads d’interface utilisateur et utiliser des agents d’arrière-plan pour traiter les tâches longues.

Une sorte de validation semble se produire sur la propriété Owner d’une fenêtre, qui vérifie si les fenêtres ont été créées sur le même thread.

Ma solution pour y remédier est d’implémenter ma propre propriété de type Fenêtre principale et d’y stocker une référence du constructeur, comme suit:

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

Maintenant, j’ai access à la fenêtre principale.