Descartando controles de usuário WPF

119

Eu criei um controle de usuário WPF personalizado que se destina a ser usado por terceiros. Meu controle tem um membro privado que é descartável e eu gostaria de garantir que seu método de descarte sempre seja chamado assim que a janela / aplicativo contêiner for fechada. No entanto, UserControl não é descartável. Tentei implementar a interface IDisposable e assinar o evento Unloaded, mas não fui chamado quando o aplicativo host é fechado. Se possível, não quero confiar nos consumidores do meu controle lembrando-se de chamar um método Dispose específico.

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

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

     }
 }

A única solução que eu encontrei até agora é assinar o evento ShutdownStarted do Dispatcher's. Essa é uma abordagem razoável?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;
Mark Heath
fonte
E o evento Unloaded do controle do usuário?
akjoshi
2
@akjoshi: O MSDN diz que: O evento descarregado pode não ser gerado. E também pode ser acionado mais de uma vez, ou seja, quando o usuário altera o tema.
Dudu
Embora você possa implementar a interface IDisposable no seu controle de usuário, não há garantia de que terceiros chamem o método de descarte da implementação do padrão Dispose. Se você estiver usando recursos nativos (por exemplo, um fluxo de arquivos), considere usar um finalizador.
Philippe

Respostas:

57

Postagem interessante no blog aqui:

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

Menciona assinar Dispatcher.ShutdownStarted para descartar seus recursos.

Ray Booysen
fonte
1
bem, eu esperava que houvesse uma maneira mais limpa do que isso, mas parece que por enquanto é o melhor para fazê-lo.
Mark Heath
35
Mas e se o UserControl morrer antes que o aplicativo morra? O Dispatcher só diminui quando o aplicativo faz, certo?
Robert Jeppesen
15
Como muitos controles reutilizam componentes COM ou outros recursos não gerenciados que não foram codificados com o objetivo de ficar pendentes indefinidamente ou finalizados em um thread do conjunto de encadeamentos, e esperam / exigem uma desalocação determinística.
Neutrino
1
Em um aplicativo da Windows Store, o ShutdownStarted não existe.
Cœur
7
Ou você precisa para manipuladores de eventos dereference, ou você precisa parar de tópicos iniciados nesse controlo, ...
DanW
40

Dispatcher.ShutdownStartedO evento é acionado apenas no final do aplicativo. Vale a pena chamar a lógica de descarte justamente quando o controle fica fora de uso. Em particular, ele libera recursos quando o controle é usado várias vezes durante o tempo de execução do aplicativo. Portanto , a solução da ioWint é preferível. Aqui está o código:

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
     };
}
Ilia Barahovski
fonte
Em um aplicativo da Windows Store, GetWindow () não existe.
Cœur
Bravo, a melhor resposta.
Nic
8
Cliente: Em um aplicativo da Windows Store, você não está usando o WPF
Alan Baljeu
3
e se houver mais janelas envolvidas e a principal nunca for fechada? Ou seu controle está hospedado em uma página que é carregada / descarregada várias vezes? consulte: stackoverflow.com/a/14074116/1345207
L.Trabacchin
1
A janela pode não estar sendo fechada com muita frequência. Se o controle fizer parte de um item da lista, muitos serão criados / destruídos até que a janela pai possa fechar.
PERDIDO
15

Você precisa ter cuidado ao usar o destruidor. Isso será chamado no thread do Finalizador do GC. Em alguns casos, os recursos em que sua liberação pode não gostar de serem liberados em um encadeamento diferente daquele em que foram criados.

Ade Miller
fonte
1
Obrigado por este aviso. esse foi exatamente o meu caso! Aplicativo: devenv.exe Versão da estrutura: v4.0.30319 Descrição: o processo foi encerrado devido a uma exceção não tratada. Informações de exceção: System.InvalidOperationException Stack: at MyControl.Finalize () minha solução foi mover o código do finalizador para ShutdownStarted
itsho
10

Eu uso o seguinte comportamento de interatividade para fornecer um evento de descarregamento ao WPF UserControls. Você pode incluir o comportamento no UserControls XAML. Assim, você pode ter a funcionalidade sem colocar a lógica em cada UserControl.

Declaração XAML:

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

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

Manipulador CodeBehind:

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

Código de comportamento:

/// <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);
    }
}
alex.enjoy
fonte
5
Eu levantaria uma bandeira aqui ... e se alguma outra coisa cancelar a janela que se fecha (talvez assinada após o seu controle, e.Cancelainda seja falsa quando chegar ao seu WindowClosingHandlerrepresentante) ?. Seu controle seria "descarregado" e a janela ainda aberta. Eu definitivamente faria isso no Closedevento, não Closingnaquele.
Jcl
6

Meu cenário é um pouco diferente, mas a intenção é a mesma que eu gostaria de saber quando a janela pai que hospeda o meu controle de usuário está sendo fechada / fechada. (bem, estamos implementando um padrão MVP em um aplicativo WPF PRISM).

Acabei de descobrir que, no evento Loaded do usercontrol, posso conectar meu método ParentWindowClosing ao evento Closing do Windows Parent. Dessa forma, meu controle de usuário pode estar ciente quando a janela principal está sendo fechada e agir em conformidade!

ioWint
fonte
0

Eu estou pensando que descarregar é chamado tudo, mas dificilmente existe no 4.7. Mas, se você estiver brincando com versões mais antigas do .Net, tente fazer isso no seu método de carregamento:

e.Handled = true;

Eu não acho que as versões mais antigas serão descarregadas até que o carregamento seja tratado. Estou apenas postando porque vejo outras pessoas ainda fazendo essa pergunta, e não a vi proposta como uma solução. Eu toco apenas .Net algumas vezes por ano e me deparei com isso alguns anos atrás. Mas, eu me pergunto se é tão simples quanto descarregar não ser chamado até o carregamento terminar. Parece que funciona para mim, mas novamente no .Net mais recente parece sempre chamar descarregar, mesmo que o carregamento não esteja marcado como tratado.

Michael T.
fonte
-3

Um UserControl tem um destruidor, por que você não usa isso?

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

fonte
Isso não parece funcionar. Eu apenas tentei essa abordagem e ela nunca é chamada.
JasonD
9
Isso não é um destruidor, é um finalizador. Você sempre implementa um finalizador e Dispose como um par, caso contrário corre o risco de vazar.
Mike Post
1
E, no finalizador, você deve limpar apenas objetos não gerenciados, mas não objetos gerenciados, porque os finalizadores são executados em ordem não especificada nos encadeamentos do GC; portanto, os objetos gerenciados podem ser finalizados anteriormente e seu Dispose () pode ter afinidade de encadeamento.
Dudu
2
joeduffyblog.com/2005/04/08/… é a melhor explicação de Finalize and Dispose que eu encontrei. Realmente vale a pena ler.
dss539