WPFユーザーコントロールの破棄


119

サードパーティによる使用を目的としたカスタムWPFユーザーコントロールを作成しました。私のコントロールには使い捨てのプライベートメンバーがあり、ウィンドウ/アプリケーションを閉じたときに、そのdisposeメソッドが常に呼び出されるようにしたいと思います。ただし、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イベントをサブスクライブすることです。これは合理的なアプローチですか?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;

ユーザーコントロールのUnloadedイベントについてはどうですか?
akjoshi

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が停止した場合はどうなりますか?ディスパッチャーは、アプリがそうしたときにのみシャイットしますよね?
Robert Jeppesen、

15
多くのコントロールは、無期限にぶら下がったままにしたり、スレッドプールスレッドでファイナライズしたりするためにコーディングされていないCOMコンポーネントまたは他のアンマネージリソースを再利用し、確定的な割り当て解除を期待/要求するためです。
ニュートリノ2013

1
Windowsストアアプリでは、ShutdownStartedは存在しません。
2013

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()は存在しません。
・クール

ブラボー、最高の答え。
2015

8
ツール:Windowsストアアプリでは、WPFを使用していません
Alan Baljeu

3
より多くのウィンドウが関係していて、メインのウィンドウが閉じられない場合はどうなりますか?または、コントロールはページでホストされ、複数回ロード/アンロードされますか?参照:stackoverflow.com/a/14074116/1345207
L.Trabacchin 2017

1
ウィンドウが頻繁に閉じていない可能性があります。コントロールがリストアイテムの一部である場合、親ウィンドウが閉じるまで、多くが作成または破棄されます。
失われた

15

デストラクタの使用には注意が必要です。これは、GC Finalizerスレッドで呼び出されます。解放されたくないリソースが、作成されたスレッドとは別のスレッドで解放されない場合があります。


1
この警告をありがとう。これはまさに私のケースでした! アプリケーション:devenv.exeフレームワークバージョン:v4.0.30319説明:処理されない例外が原因でプロセスが終了しました。例外情報:System.InvalidOperationExceptionスタック:MyControl.Finalize()で の解決策は、ファイナライザからShutdownStartedにコードを移動することでした
itho

10

次の対話動作を使用して、アンロードイベントをWPFユーザーコントロールに提供します。この動作を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.CancelWindowClosingHandlerデリゲートに到達してもfalseのままです)。コントロールは「アンロード」され、ウィンドウはまだ開かれています。私は間違いなく、Closedイベントではなく、イベントでこれを行いClosingます。
Jcl、2014年

6

私のシナリオは少し異なりますが、目的は同じです。ユーザーコントロールをホストしている親ウィンドウが閉じる/閉じるときを知りたいのです。(まあ、WPF PRISMアプリケーションにMVPパターンを実装しています)。

ユーザーコントロールのLoadedイベントで、ParentWindowClosingメソッドをParent windows Closingイベントにフックできることを理解しました。このようにして、私のUsercontrolは、親ウィンドウが閉じられていることを認識し、それに応じて動作することができます!


0

アンロードはすべて呼び出されていると思いますが、4.7には存在しません。しかし、古いバージョンの.Netで遊んでいる場合は、ロード方法でこれを試してください。

e.Handled = true;

ロードが処理されるまで、古いバージョンはアンロードしないと思います。他の人がまだこの質問をしているのを見て、これが解決策として提案されたのを見たことがないので、投稿してください。私は年に数回だけ.Netに触れ、これに数年前に遭遇しました。しかし、ロードが完了するまでunloadが呼び出されないのと同じくらい簡単なのでしょうか。それは私にとってはうまくいくようですが、新しい.NETでは、ロードが処理済みとしてマークされていなくても、常にアンロードを呼び出すようです。


-3

UserControlにはデストラクタがありますが、それを使用しないのはなぜですか?

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

これは機能していないようです。私はそのアプローチを試しましたが、呼び出されることはありません。
JasonD 2010年

9
これはデストラクタではなく、ファイナライザです。ファイナライザとDisposeは常にペアとして実装する必要があります。そうしないと、リークのリスクがあります。
Mike Post

1
また、ファイナライザでは、アンマネージオブジェクトのみをクリーンアップし、マネージオブジェクトはクリーンアップしないでください。ファイナライザは、GCスレッドで不特定の順序で実行されるため、マネージオブジェクトが早期にファイナライズされ、Dispose()にスレッドアフィニティが含まれる場合があるためです。
Dudu

2
joeduffyblog.com/2005/04/08/…は、私が見つけたファイナライズと破棄の最も良い説明です。それは本当に読む価値があります。
dss539 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.