Hủy bỏ quyền kiểm soát của người dùng WPF


119

Tôi đã tạo một điều khiển người dùng WPF tùy chỉnh để bên thứ ba sử dụng. Kiểm soát của tôi có một thành viên riêng tư dùng một lần và tôi muốn đảm bảo rằng phương thức xử lý của nó sẽ luôn được gọi khi cửa sổ / ứng dụng chứa đóng. Tuy nhiên, UserControl không dùng một lần. Tôi đã thử triển khai giao diện IDisposable và đăng ký sự kiện Unloaded nhưng không được gọi khi ứng dụng lưu trữ đóng. Nếu có thể, tôi không muốn dựa vào sự kiểm soát của người tiêu dùng khi nhớ gọi một phương pháp Vứt bỏ cụ thể.

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

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

     }
 }

Giải pháp duy nhất tôi đã tìm thấy cho đến nay là đăng ký sự kiện ShutdownStarted của Dispatcher's. Đây có phải là một cách tiếp cận hợp lý?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;

Còn sự kiện Unloaded do người dùng kiểm soát thì sao?
akjoshi

2
@akjoshi: MSDN nói rằng: Sự kiện đã tải lên có thể không được tăng lên chút nào. Và nó cũng có thể được kích hoạt nhiều lần, đó là khi người dùng thay đổi chủ đề.
Dudu

Mặc dù bạn có thể triển khai giao diện IDisposable trên sự kiểm soát của người dùng, nhưng không có gì đảm bảo rằng bên thứ ba của bạn sẽ gọi phương thức xử lý của việc triển khai mẫu Loại bỏ của bạn. Nếu bạn đang giữ các tài nguyên gốc (ví dụ: một luồng tệp), bạn nên cân nhắc sử dụng trình hoàn thiện.
Philippe

Câu trả lời:


57

Bài blog thú vị ở đây:

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

Nó đề cập đến việc đăng ký Dispatcher.ShutdownStarted để loại bỏ tài nguyên của bạn.


1
Tôi đã hy vọng sẽ có một cách sạch hơn cách này, nhưng có vẻ như bây giờ đây là cách tốt nhất để làm điều đó.
Mark Heath

35
Nhưng điều gì sẽ xảy ra nếu UserControl chết trước khi ứng dụng chết? Điều phối viên sẽ chỉ né tránh khi ứng dụng làm vậy, phải không?
Robert Jeppesen

15
Bởi vì nhiều điều khiển sử dụng lại các thành phần COM hoặc các tài nguyên không được quản lý khác không được mã hóa với mục đích bị treo lơ lửng vô thời hạn hoặc được hoàn thiện trên chuỗi nhóm luồng và mong đợi / yêu cầu phân bổ giao dịch xác định.
Neutrino

1
Trong ứng dụng Windows Store, ShutdownStarted không tồn tại.
Cœur

7
Hoặc bạn cần phải xử lý sự kiện dereference, hoặc bạn cần phải dừng lại chủ đề bắt đầu kiểm soát đó, ...
DanW

40

Dispatcher.ShutdownStartedsự kiện chỉ được kích hoạt khi kết thúc ứng dụng. Nó đáng được gọi là logic loại bỏ chỉ khi quyền kiểm soát không được sử dụng. Đặc biệt nó giải phóng tài nguyên khi quyền điều khiển được sử dụng nhiều lần trong thời gian chạy ứng dụng. Vì vậy, giải pháp của ioWint là thích hợp hơn. Đây là mã:

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
     };
}

Trong ứng dụng Windows Store, GetWindow () không tồn tại.
Cœur

Hoan hô, câu trả lời tuyệt vời nhất.
Nic

8
Xin chào: Trong ứng dụng Windows Store, bạn không sử dụng WPF
Alan Baljeu

3
Điều gì sẽ xảy ra nếu có nhiều cửa sổ liên quan và cửa sổ chính không bao giờ đóng lại? Hay điều khiển của bạn được lưu trữ trong trang được tải / tải xuống nhiều lần? xem: stackoverflow.com/a/14074116/1345207
L.Trabacchin

1
Cửa sổ có thể không đóng thường xuyên. Nếu điều khiển là một phần của mục danh sách, nhiều mục sẽ được tạo / hủy cho đến khi cửa sổ mẹ của nó có thể đóng lại.
LOST

15

Bạn phải cẩn thận khi sử dụng hàm hủy. Điều này sẽ được gọi trên luồng GC Finalizer. Trong một số trường hợp, các tài nguyên mà bạn đang giải phóng có thể không thích được phát hành trên một chuỗi khác với chuỗi mà chúng được tạo trên đó.


1
Cảm ơn bạn đã cảnh báo này. đây là trường hợp của tôi chính xác! Ứng dụng: devenv.exe Phiên bản khung: v4.0.30319 Mô tả: Quá trình đã bị chấm dứt do một ngoại lệ chưa được khắc phục. Thông tin ngoại lệ: System.InvalidOperationException Stack: tại MyControl.Finalize () giải pháp của tôi là di chuyển mã từ trình hoàn thiện vào ShutdownStarted
itho

10

Tôi sử dụng Hành vi tương tác sau đây để cung cấp một sự kiện tải xuống WPF UserControls. Bạn có thể bao gồm hành vi trong XAML UserControls. Vì vậy, bạn có thể có chức năng mà không cần đặt logic của nó trong mỗi UserControl.

Khai báo XAML:

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

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

CodeBehind handler:

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

Quy tắc hành vi:

/// <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
Tôi muốn giơ cờ ở đây ... điều gì sẽ xảy ra nếu bất kỳ điều gì khác hủy bỏ việc đóng cửa sổ (có thể được đăng ký sau khi bạn kiểm soát nên e.Cancelvẫn sai khi nó đến tay người được WindowClosingHandlerủy quyền của bạn ) ?. Điều khiển của bạn sẽ được "dỡ bỏ" và cửa sổ vẫn mở. Tôi chắc chắn sẽ làm điều này trong Closedsự kiện, không phải trong Closingmột.
Jcl

6

Kịch bản của tôi hơi khác một chút, nhưng mục đích là giống nhau, tôi muốn biết khi cửa sổ mẹ lưu trữ quyền kiểm soát người dùng của tôi đang đóng / đóng khi Chế độ xem (tức là điều khiển người dùng của tôi) sẽ gọi những người trình bày oncloseView thực thi một số chức năng và thực hiện dọn dẹp. (chúng tôi đang triển khai mẫu MVP trên ứng dụng WPF PRISM).

Tôi vừa tìm ra rằng trong sự kiện Đã tải của usercontrol, tôi có thể kết nối phương thức ParentWindowClosing của mình với sự kiện Đóng cửa sổ cha. Bằng cách này, Usercontrol của tôi có thể nhận biết khi nào cửa sổ Parent đang được đóng và hành động theo đó!


0

Tôi nghĩ rằng dỡ bỏ được gọi là tất cả nhưng tồn tại cứng trong 4.7. Tuy nhiên, nếu bạn đang sử dụng các phiên bản cũ hơn của .Net, hãy thử thực hiện việc này trong phương pháp tải của bạn:

e.Handled = true;

Tôi không nghĩ rằng các phiên bản cũ hơn sẽ không tải cho đến khi quá trình tải được xử lý. Chỉ đăng bài vì tôi thấy những người khác vẫn hỏi câu hỏi này và chưa xem điều này được đề xuất như một giải pháp. Tôi chỉ chạm vào .Net một vài lần một năm và gặp phải vấn đề này vài năm trở lại đây. Nhưng, tôi tự hỏi liệu nó có đơn giản như việc dỡ hàng không được gọi cho đến khi quá trình tải kết thúc hay không. Có vẻ như nó hoạt động đối với tôi, nhưng một lần nữa trong .Net mới hơn, nó dường như luôn gọi dỡ bỏ ngay cả khi tải không được đánh dấu là đã xử lý.


-3

Một UserControl có một Destructor, tại sao bạn không sử dụng nó?

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

Điều này dường như không hoạt động. Tôi chỉ thử cách tiếp cận đó và nó không bao giờ được gọi.
JasonD

9
Đó không phải là trình hủy, mà là trình hoàn thiện. Bạn luôn thực hiện một công cụ hoàn thiện và Vứt bỏ như một cặp nếu không bạn có nguy cơ bị rò rỉ.
Mike Đăng

1
Và, trong trình hoàn thiện, bạn chỉ nên dọn dẹp các đối tượng không được quản lý nhưng không phải đối tượng được quản lý, vì trình hoàn thiện được chạy theo thứ tự không xác định trong các luồng GC do đó các đối tượng được quản lý có thể được hoàn thiện sớm hơn và Dispose () của chúng có thể có mối quan hệ với luồng.
Dudu

2
joeduffyblog.com/2005/04/08/… là lời giải thích tốt nhất về Finalize và Dispose mà tôi tìm thấy. Nó thực sự đáng đọc.
dss539
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.