Élimination des contrôles utilisateur WPF


119

J'ai créé un contrôle utilisateur WPF personnalisé destiné à être utilisé par un tiers. Mon contrôle a un membre privé qui est jetable, et je voudrais m'assurer que sa méthode dispose sera toujours appelée une fois que la fenêtre / l'application contenant est fermée. Cependant, UserControl n'est pas jetable. J'ai essayé d'implémenter l'interface IDisposable et de m'abonner à l'événement Unloaded, mais je ne suis pas appelé lorsque l'application hôte se ferme. Dans la mesure du possible, je ne veux pas que les consommateurs de mon contrôle se souviennent d'appeler une méthode Dispose spécifique.

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

     // where does this code go?
     void Somewhere() 
     {
         if (x != null)
         {
             x.Dispose();
             x = null;
         }

     }
 }

La seule solution que j'ai trouvée jusqu'à présent est de souscrire à l'événement ShutdownStarted du Dispatcher. Est-ce une approche raisonnable?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;

Qu'en est-il de l'événement Unloaded du contrôle utilisateur?
akjoshi

2
@akjoshi: MSDN dit que: L'événement déchargé peut ne pas être déclenché du tout. Et il peut également être déclenché plus d'une fois, c'est-à-dire lorsque l'utilisateur change de thème.
Dudu

Bien que vous puissiez implémenter l'interface IDisposable sur votre contrôle utilisateur, il n'y a aucune garantie que votre tiers appellera la méthode dispose de votre implémentation de modèle Dispose. Si vous vous en tenez à des ressources natives (par exemple, un flux de fichiers), vous devriez envisager d'utiliser un finaliseur.
Philippe

Réponses:


57

Article de blog intéressant ici:

http://geekswithblogs.net/cskardon/archive/2008/06/23/dispose-of-a-wpf-usercontrol-ish.aspx

Il mentionne l'abonnement à Dispatcher.ShutdownStarted pour disposer de vos ressources.


1
eh bien, j'espérais qu'il y aurait un moyen plus propre que celui-ci, mais il semble que pour l'instant c'est le meilleur pour le faire.
Mark Heath

35
Mais que se passe-t-il si l'UserControl meurt avant la mort de l'application? Le répartiteur ne s'arrêtera que lorsque l'application le fera, n'est-ce pas?
Robert Jeppesen

15
Parce que de nombreux contrôles réutilisent des composants COM ou d'autres ressources non managées qui n'ont pas été codées dans le but d'être laissées en suspens indéfiniment, ou finalisées sur un thread de pool de threads, et attendent / nécessitent une désallocation déterministe.
Neutrino

1
Dans une application Windows Store, ShutdownStarted n'existe pas.
Cœur

7
Ou vous devez déréférencer les gestionnaires d'événements, ou vous devez arrêter les threads démarrés dans ce contrôle, ...
DanW

40

Dispatcher.ShutdownStartedL'événement est déclenché uniquement à la fin de l'application. Cela vaut la peine d'appeler la logique d'élimination au moment où le contrôle devient inutilisable. En particulier, il libère des ressources lorsque le contrôle est utilisé plusieurs fois pendant l'exécution de l'application. La solution d' ioWint est donc préférable. Voici le code:

public MyWpfControl()
{
     InitializeComponent();
     Loaded += (s, e) => { // only at this point the control is ready
         Window.GetWindow(this) // get the parent window
               .Closing += (s1, e1) => Somewhere(); //disposing logic here
     };
}

Dans une application Windows Store, GetWindow () n'existe pas.
Cœur

Bravo, meilleure réponse.
Nic

8
Cœur: Dans une application Windows Store, vous n'utilisez pas WPF
Alan Baljeu

3
Et s'il y a plus de fenêtres impliquées et que la principale ne se ferme jamais? Ou votre contrôle est hébergé dans une page qui est chargée / déchargée plusieurs fois? voir: stackoverflow.com/a/14074116/1345207
L.Trabacchin

1
La fenêtre peut ne pas se fermer très souvent. Si le contrôle fait partie d'un élément de liste, beaucoup seront créés / détruits jusqu'à ce que sa fenêtre parente puisse se fermer.
PERDU le

15

Vous devez être prudent en utilisant le destructeur. Cela sera appelé sur le thread GC Finalizer. Dans certains cas, les ressources que vous libérez peuvent ne pas aimer être publiées sur un thread différent de celui sur lequel elles ont été créées.


1
Merci pour cet avertissement. c'était mon cas exactement! Application: devenv.exe Version du framework: v4.0.30319 Description: Le processus a été interrompu en raison d'une exception non gérée. Informations d'exception: System.InvalidOperationException Stack: chez MyControl.Finalize (), ma solution consistait à déplacer le code du finaliseur vers ShutdownStarted
itsho

10

J'utilise le comportement d'interactivité suivant pour fournir un événement de déchargement à WPF UserControls. Vous pouvez inclure le comportement dans le UserControls XAML. Ainsi, vous pouvez avoir la fonctionnalité sans placer la logique dans chaque UserControl.

Déclaration XAML:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

<i:Interaction.Behaviors>
    <behaviors:UserControlSupportsUnloadingEventBehavior UserControlClosing="UserControlClosingHandler" />
</i:Interaction.Behaviors>

Gestionnaire CodeBehind:

private void UserControlClosingHandler(object sender, EventArgs e)
{
    // to unloading stuff here
}

Code de comportement:

/// <summary>
/// This behavior raises an event when the containing window of a <see cref="UserControl"/> is closing.
/// </summary>
public class UserControlSupportsUnloadingEventBehavior : System.Windows.Interactivity.Behavior<UserControl>
{
    protected override void OnAttached()
    {
        AssociatedObject.Loaded += UserControlLoadedHandler;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= UserControlLoadedHandler;
        var window = Window.GetWindow(AssociatedObject);
        if (window != null)
            window.Closing -= WindowClosingHandler;
    }

    /// <summary>
    /// Registers to the containing windows Closing event when the UserControl is loaded.
    /// </summary>
    private void UserControlLoadedHandler(object sender, RoutedEventArgs e)
    {
        var window = Window.GetWindow(AssociatedObject);
        if (window == null)
            throw new Exception(
                "The UserControl {0} is not contained within a Window. The UserControlSupportsUnloadingEventBehavior cannot be used."
                    .FormatWith(AssociatedObject.GetType().Name));

        window.Closing += WindowClosingHandler;
    }

    /// <summary>
    /// The containing window is closing, raise the UserControlClosing event.
    /// </summary>
    private void WindowClosingHandler(object sender, CancelEventArgs e)
    {
        OnUserControlClosing();
    }

    /// <summary>
    /// This event will be raised when the containing window of the associated <see cref="UserControl"/> is closing.
    /// </summary>
    public event EventHandler UserControlClosing;

    protected virtual void OnUserControlClosing()
    {
        var handler = UserControlClosing;
        if (handler != null) 
            handler(this, EventArgs.Empty);
    }
}

5
Je lèverais un drapeau ici ... et si quelque chose d'autre annule la fermeture de la fenêtre (peut-être souscrit après votre contrôle donc e.Cancelest toujours faux quand il atteint votre WindowClosingHandlerdélégué)?. Votre contrôle serait "déchargé" et la fenêtre encore ouverte. Je ferais certainement ça sur l' Closedévénement, pas sur celui- Closinglà.
Jcl

6

Mon scénario est un peu différent, mais l'intention est la même.Je voudrais savoir quand la fenêtre parent hébergeant mon contrôle utilisateur se ferme / se ferme car la vue (c'est-à-dire mon contrôle utilisateur) devrait invoquer les présentateurs oncloseView pour exécuter certaines fonctionnalités et effectuer le nettoyage. (Eh bien, nous implémentons un modèle MVP sur une application WPF PRISM).

Je viens de comprendre que dans l'événement Loaded du contrôle utilisateur, je peux connecter ma méthode ParentWindowClosing à l'événement Parent windows Closing. De cette façon, mon Usercontrol peut être conscient de la fermeture de la fenêtre Parent et agir en conséquence!


0

Je pense que le déchargement est appelé tout, mais il existe vraiment en 4.7. Mais, si vous jouez avec des versions plus anciennes de .Net, essayez de le faire dans votre méthode de chargement:

e.Handled = true;

Je ne pense pas que les anciennes versions se déchargeront tant que le chargement ne sera pas géré. Je publie simplement parce que je vois que d'autres posent encore cette question et que je n'ai pas vu cela proposé comme une solution. Je ne touche .Net que quelques fois par an, et je suis tombé dessus il y a quelques années. Mais, je me demande si c'est aussi simple que déchargement de ne pas être appelé tant que le chargement n'est pas terminé. On dirait que cela fonctionne pour moi, mais encore une fois dans .Net plus récent, il semble toujours appeler unload même si le chargement n'est pas marqué comme géré.


-3

Un UserControl a un Destructor, pourquoi ne l'utilisez-vous pas?

~MyWpfControl()
    {
        // Dispose of any Disposable items here
    }

Cela ne semble pas fonctionner. J'ai juste essayé cette approche et elle n'est jamais appelée.
JasonD

9
Ce n'est pas un destructeur, c'est un finaliseur. Vous implémentez toujours un finaliseur et éliminez par paire, sinon vous risquez des fuites.
Mike Post

1
Et, dans le finaliseur, vous ne devez nettoyer que les objets non gérés mais pas les objets gérés, car les finaliseurs sont exécutés dans un ordre non spécifié dans les threads GC, donc les objets gérés peuvent être finalisés plus tôt et leur Dispose () peut avoir une affinité de thread.
Dudu

2
joeduffyblog.com/2005/04/08/… est la meilleure explication de Finalize and Dispose que j'ai trouvée. Cela vaut vraiment la peine d'être lu.
dss539
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.