Розпорядження керування користувачами WPF


119

Я створив користувацьке управління користувачем WPF, яке призначене для використання третьою стороною. У моєму управлінні є приватний член, який є одноразовим, і я хотів би переконатися, що його метод розпорядження завжди буде викликаний, коли вікно / додаток, що містить міститься, закривається. Однак UserControl не є одноразовим. Я спробував реалізувати інтерфейс IDisposable та підписатись на подію Unloaded, але не дзвонив, коли хост-програма закривається. Якщо це взагалі можливо, я не хочу покладатися на споживачів мого контролю, які пам’ятають викликати конкретний метод утилізації.

 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;

А як щодо нерозвантаженої події управління користувачем?
akjoshi

2
@akjoshi: MSDN каже, що: "Не завантажена подія може взагалі не підніматися. І це також може спрацьовувати не раз, тобто коли користувач змінює тему.
Дуду

Незважаючи на те, що ви могли реалізувати інтерфейс ID для одноразового використання під управлінням користувача, немає гарантії, що ваша третя сторона зателефонує за способом розпорядження вашої реалізації схеми розпорядження. Якщо ви тримаєтесь на власних ресурсах (наприклад, файловий потік), варто подумати про використання фіналізатора.
Філіп

Відповіді:


57

Цікава публікація в блозі тут:

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

Тут згадується підписка на Dispatcher.ShutdownStarted для розпорядження вашими ресурсами.


1
добре, я сподівався, що буде більш чистий спосіб, ніж це, але схоже, що зараз це найкраще зробити.
Марк Хіт

35
Але що робити, якщо UserControl вмирає до того, як програма вмирає? Диспетчер ухилиться лише тоді, коли додаток зробить це, правда?
Роберт Джеппесен

15
Оскільки багато елементів управління повторно використовують компоненти COM або інші некеровані ресурси, які не були кодовані з метою залишити їх на невизначений термін або доопрацювати на потоці пулу потоків, і очікують / вимагають детермінованої розстановки.
Нейтрино

1
У додатку Windows Store 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 Store GetWindow () не існує.
Cœur

Браво, найбільша відповідь.
Нік

8
Cœur: У додатку Windows Store ви не використовуєте WPF
Alan Baljeu

3
що робити, якщо задіяно більше вікон, і головне ніколи не закриється? Або ваш контроль розміщений на сторінці, яку завантажують / завантажують кілька разів? см: stackoverflow.com/a/14074116/1345207
L.Trabacchin

1
Вікно може закриватися не дуже часто. Якщо елемент керування є частиною списку, багато буде створено / знищено, поки його батьківське вікно не закриється.
ВЗАЄМО

15

Ви повинні бути обережними, використовуючи деструктор. Це буде викликано на потоці GC Finalizer. У деяких випадках ресурси, які можуть вам не сподобатися, можуть бути випущені на іншій темі від тієї, на якій вони були створені.


1
Дякую за це попередження це був саме мій випадок! Застосування: devenv.exe Framework Version: v4.0.30319 Опис: Процес було припинено через необроблений виняток. Інформація про виняток: System.InvalidOperationException: у MyControl.Finalize () моє рішення полягало в переміщенні коду з фіналізатора в ShutdownStarted
itsho

10

Я використовую наступну інтерактивну поведінку, щоб забезпечити подію розвантаження WPF UserControls. Ви можете включити поведінку в UserControls XAML. Таким чином, ви можете мати функціональні можливості, не розміщуючи логіку в кожному окремому 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);
    }
}

5
Я б підняв тут прапор ... що робити, якщо що-небудь ще скасує закриття вікна (можливо, підписується після того, як ваш контроль так e.Cancelі залишається помилковим, коли він доходить до вашого WindowClosingHandlerделегата). Ваш контроль буде "вивантажений", і вікно все ще відкриється. Я б точно це зробив на Closedзаході, а не на Closingодному.
Jcl

6

Мій сценарій трохи інший, але намір такий же, я хотів би знати, коли батьківське вікно, в якому розміщено моє управління користувача, закривається / закривається, оскільки представлення (тобто мій usercontrol) повинно викликати презентаторів oncloseView, щоб виконати певну функціональність і виконати очищення. (добре, ми реалізуємо схему MVP у додатку ПРИЗМ ПРЗМ).

Я просто зрозумів, що у випадку Loaded Usercontrol я можу підключити свій метод ParentWindowClosing до події закриття батьківських вікон. Таким чином мій Usercontrol може знати про закриття вікна Батьків і діяти відповідно!


0

Я думаю, що вивантаження називається всім, але важко існує в 4.7. Але якщо ви граєте зі старими версіями .Net, спробуйте зробити це у вашому методі завантаження:

e.Handled = true;

Я не думаю, що старіші версії не завантажуватимуться, поки не буде оброблено завантаження. Лише розміщую повідомлення, тому що я бачу, що інші все ще задають це питання, і не бачили цього запропонованого рішення. Я торкаюсь лише декількох разів на рік, і натрапив на це кілька років тому. Але, мені цікаво, чи це не так просто, як вивантаження не викликається, поки завантаження не закінчиться. Схоже, це працює для мене, але знову ж таки в нових версіях. Здається, завжди викликає розвантаження, навіть якщо завантаження не позначено як оброблене.


-3

У UserControl є Destructor, чому б вам не скористатися цим?

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

Це, здається, не працює. Я просто спробував такий підхід, і він ніколи не називається.
JasonD

9
Це не деструктор, це фіналізатор. Ви завжди впроваджуєте фіналізатор і розпоряджаєтесь парою, інакше ви ризикуєте протікати.
Майк Пост

1
І, у фіналізаторі слід очищати лише некеровані об'єкти, але не керовані об'єкти, оскільки фіналізатори запускаються у визначеному порядку в потоках GC, таким чином керовані об'єкти можуть бути завершені раніше, а їх Dispose () може мати спорідненість з потоком.
Дуду

2
joeduffyblog.com/2005/04/08/… - найкраще пояснення, яке я знайшов у фіналізованому та розпорядженні. Це дійсно варто прочитати.
dss539
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.