Удаление пользовательских элементов управления WPF

119

Я создал настраиваемый пользовательский элемент управления WPF, который предназначен для использования третьей стороной. У моего элемента управления есть закрытый член, который является одноразовым, и я хотел бы убедиться, что его метод удаления всегда будет вызываться после закрытия содержащего окно / приложение. Однако UserControl не является одноразовым. Я попытался реализовать интерфейс IDisposable и подписаться на событие Unloaded, но ни один из них не был вызван при закрытии основного приложения. Если это вообще возможно, я не хочу полагаться на то, что потребители моего контроля не забывают вызывать определенный метод Dispose.

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

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

     }
 }

Единственное решение, которое я нашел до сих пор, - это подписаться на событие ShutdownStarted Dispatcher. Это разумный подход?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;
Марк Хит
источник
А как насчет события Unloaded пользовательского элемента управления?
Акджоши 06
2
@akjoshi: MSDN говорит, что: событие Unloaded может вообще не возникать. И он также может запускаться более одного раза, то есть когда пользователь меняет тему.
Dudu
Хотя вы можете реализовать интерфейс IDisposable в своем пользовательском элементе управления, нет гарантии, что ваша третья сторона вызовет метод dispose для реализации шаблона Dispose. Если вы держитесь за собственные ресурсы (например, файловый поток), вам следует рассмотреть возможность использования финализатора.
Филипп

Ответы:

57

Интересный пост в блоге здесь:

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

В нем упоминается подписка на Dispatcher.ShutdownStarted для удаления ваших ресурсов.

Рэй Буйсен
источник
1
ну, я надеялся, что будет более чистый способ, чем этот, но, похоже, на данный момент это лучший способ сделать это.
Марк Хит,
35
Но что, если UserControl умирает раньше, чем приложение? Диспетчер отключится только тогда, когда это сделает приложение, верно?
Роберт Джеппесен,
15
Потому что многие элементы управления повторно используют COM-компоненты или другие неуправляемые ресурсы, которые не были закодированы так, чтобы их можно было оставить на неопределенное время, или завершены в потоке пула потоков, и ожидают / требуют детерминированного освобождения.
Neutrino
1
В приложении Магазина Windows ShutdownStarted не существует.
Cœur
7
Или вам нужно разыменовать обработчики событий, или вам нужно остановить потоки, запущенные в этом элементе управления, ...
DanW
40

Dispatcher.ShutdownStartedсобытие запускается только в конце приложения. Логику утилизации стоит вызывать как раз тогда, когда управление выходит из употребления. В частности, он освобождает ресурсы при многократном использовании элемента управления во время выполнения приложения. Так что решение ioWint предпочтительнее. Вот код:

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
     };
}
Илья Бараховский
источник
В приложении Магазина Windows GetWindow () не существует.
Cœur
Браво, величайший ответ.
Nic
8
Cœur: В приложении Windows Store вы не используете WPF
Алан Бальеу,
3
что, если задействовано больше окон, а главное никогда не закрывается? Или ваш элемент управления размещен на странице, которая загружается / выгружается несколько раз? см .: stackoverflow.com/a/14074116/1345207
L.Trabacchin 01
1
Окно может закрываться не очень часто. Если элемент управления является частью элемента списка, многие из них будут созданы / уничтожены до тех пор, пока его родительское окно не закроется.
ПОТЕРЯН
15

Будьте осторожны при использовании деструктора. Это будет вызвано в потоке GC Finalizer. В некоторых случаях освобождаемые ресурсы могут не понравиться размещению в другом потоке, отличном от того, в котором они были созданы.

Аде Миллер
источник
1
Спасибо за это предупреждение. это был мой случай! Приложение: devenv.exe Версия Framework: v4.0.30319 Описание: Процесс был прерван из-за необработанного исключения. Информация об исключении: System.InvalidOperationException Стек: в MyControl.Finalize () мое решение заключалось в том, чтобы переместить код из финализатора в ShutdownStarted
itsho
10

Я использую следующее поведение интерактивности, чтобы предоставить событие выгрузки для WPF UserControls. Вы можете включить поведение в XAML UserControls. Таким образом, вы можете иметь функциональность, не помещая логику в каждый элемент UserControl.

Объявление XAML:

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

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

Обработчик CodeBehind:

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

Код поведения:

/// <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
источник
5
Я бы поднял здесь флаг ... что, если что-то еще отменяет закрытие окна (возможно, подписано после вашего элемента управления, поэтому e.Cancelвсе еще ложно, когда оно достигает вашего WindowClosingHandlerделегата) ?. Ваш элемент управления будет «выгружен», а окно все равно будет открыто. Я бы определенно сделал это на Closedмероприятии, а не на Closingодном.
Jcl
6

Мой сценарий немного отличается, но намерение такое же, я хотел бы знать, когда родительское окно, в котором размещен мой пользовательский элемент управления, закрывается / закрывается, поскольку представление (то есть мой пользовательский элемент управления) должно вызывать презентаторов oncloseView для выполнения некоторых функций и выполнения очистки. (ну, мы реализуем шаблон MVP в приложении WPF PRISM).

Я только что понял, что в событии Loaded пользовательского элемента управления я могу подключить свой метод ParentWindowClosing к событию закрытия родительских окон. Таким образом мой пользовательский элемент управления может знать, когда родительское окно закрывается, и действовать соответственно!

ioWint
источник
0

Я думаю, что выгрузка вызывается у всех, но в 4.7 существует. Но, если вы играете со старыми версиями .Net, попробуйте сделать это в своем методе загрузки:

e.Handled = true;

Я не думаю, что старые версии выгружаются, пока загрузка не будет обработана. Просто отправляю, потому что я вижу, что другие все еще задают этот вопрос, и не видел в этом предложении решения. Я трогаю .Net только несколько раз в год, и столкнулся с этим несколько лет назад. Но мне интересно, так ли просто, как unload, который не вызывается до завершения загрузки. Похоже, это работает для меня, но опять же в более новой .Net кажется, что всегда вызывает unload, даже если загрузка не помечена как обработанная.

Майкл Т.
источник
-3

У UserControl есть деструктор, почему бы вам не использовать его?

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

источник
Похоже, это не работает. Я только что попробовал этот подход, но он так и не получил ответа.
JasonD
9
Это не деструктор, это финализатор. Вы всегда реализуете финализатор и Dispose как пару, иначе вы рискуете утечками.
Майк Пост,
1
И в финализаторе вы должны очищать только неуправляемые объекты, но не управляемые объекты, потому что финализаторы запускаются в неопределенном порядке в потоках GC, поэтому управляемые объекты могут быть завершены раньше, а их Dispose () может иметь сходство потоков.
Dudu
2
joeduffyblog.com/2005/04/08/… - лучшее объяснение Finalize and Dispose, которое я нашел. Это действительно стоит прочитать.
dss539