2010-11-10 43 views
26

Cập nhật: Đã giải quyết được vấn đề, xem my Answer.Đăng ký INotifyPropertyChanged cho các đối tượng lồng nhau (con)

Tôi đang tìm kiếm một sạch và giải pháp thanh lịch để xử lý các sự kiện INotifyPropertyChanged của lồng đối tượng (trẻ em). Mã ví dụ:

public class Person : INotifyPropertyChanged { 

    private string _firstName; 
    private int _age; 
    private Person _bestFriend; 

    public string FirstName { 
    get { return _firstName; } 
    set { 
     // Short implementation for simplicity reasons 
     _firstName = value; 
     RaisePropertyChanged("FirstName"); 
    } 
    } 

    public int Age { 
    get { return _age; } 
    set { 
     // Short implementation for simplicity reasons 
     _age = value; 
     RaisePropertyChanged("Age"); 
    } 
    } 

    public Person BestFriend { 
    get { return _bestFriend; } 
    set { 
     // - Unsubscribe from _bestFriend's INotifyPropertyChanged Event 
     // if not null 

     _bestFriend = value; 
     RaisePropertyChanged("BestFriend"); 

     // - Subscribe to _bestFriend's INotifyPropertyChanged Event if not null 
     // - When _bestFriend's INotifyPropertyChanged Event is fired, i'd like 
     // to have the RaisePropertyChanged("BestFriend") method invoked 
     // - Also, I guess some kind of *weak* event handler is required 
     // if a Person instance i beeing destroyed 
    } 
    } 

    // **INotifyPropertyChanged implementation** 
    // Implementation of RaisePropertyChanged method 

} 

Tập trung vào số BestFriend Thuộc tính và giá trị của nó là setter. Bây giờ Tôi biết rằng tôi có thể thực hiện việc này theo cách thủ công, thực hiện tất cả các bước được mô tả trong các nhận xét. Nhưng điều này sẽ là rất nhiều mã, đặc biệt là khi tôi đang có kế hoạch để có nhiều tài sản con thực hiện INotifyPropertyChanged như thế này. Tất nhiên họ sẽ không phải luôn luôn cùng loại, điều duy nhất họ có điểm chung là giao diện INotifyPropertyChanged.

Lý do là, trong kịch bản thực tế của tôi, tôi có một đối tượng "Item" (trong giỏ hàng) phức tạp có các thuộc tính lồng nhau trên nhiều lớp (Item có đối tượng "License"). một lần nữa) và tôi cần được thông báo về bất kỳ thay đổi nào của "Mục" để có thể tính toán lại giá.

Bạn có một số mẹo hay hoặc thậm chí một số việc thực hiện để giúp tôi giải quyết điều này?

Thật không may, tôi không thể/được phép sử dụng các bước xây dựng sau như PostSharp để hoàn thành mục tiêu của mình.

Cảm ơn bạn rất nhiều trước,
- Thomas

+0

AFAIK, hầu hết các triển khai ràng buộc không * mong đợi * sự kiện tuyên truyền trong thời trang đó. Bạn * không * thay đổi giá trị của 'BestFriend', sau khi tất cả. –

Trả lời

19

vì tôi không thể tìm thấy giải pháp sẵn sàng để sử dụng, tôi đã thực hiện triển khai tùy chỉnh dựa trên đề xuất của Pieters (và Mark) (cảm ơn!).

Sử dụng các lớp học, bạn sẽ được thông báo về bất kỳ sự thay đổi trong một cây đối tượng sâu, công trình này cho bất kỳ INotifyPropertyChanged loại thực hiện và INotifyCollectionChanged bộ sưu tập * thực hiện (Rõ ràng, tôi đang sử dụng ObservableCollection cho điều đó).

Tôi hy vọng điều này hóa ra là một giải pháp khá sạch sẽ và thanh lịch, nó không được kiểm tra đầy đủ mặc dù và có chỗ cho cải tiến. Nó khá dễ sử dụng, chỉ cần tạo một thể hiện của ChangeListener sử dụng nó là tĩnh Create phương pháp và thông qua INotifyPropertyChanged của bạn:

var listener = ChangeListener.Create(myViewModel); 
listener.PropertyChanged += 
    new PropertyChangedEventHandler(listener_PropertyChanged); 

các PropertyChangedEventArgs cung cấp một PropertyName mà sẽ luôn là "con đường" đầy Đối tượng của bạn. Ví dụ: nếu bạn thay đổi Tên "BestFriend" của Persons, thì PropertyName sẽ là "BestFriend.Name", nếu BestFriend có bộ sưu tập Trẻ em và bạn thay đổi Độ tuổi, giá trị sẽ là "BestFriend.Children []. Age" và vân vân. Đừng quên để Dispose khi đối tượng của bạn bị phá hủy, sau đó nó sẽ (hy vọng) hoàn toàn hủy đăng ký từ tất cả các thính giả sự kiện.

Nó biên dịch trong .NET (Thử nghiệm ở 4) và Silverlight (Thử nghiệm trong 4). Bởi vì mã trong tách trong ba lớp, tôi đã đăng mã để gist 705450 nơi bạn có thể lấy nó tất cả: https://gist.github.com/705450 **

*) Một lý do rằng mã đang làm việc là ObservableCollection cũng thực hiện INotifyPropertyChanged, nếu không nó sẽ không hoạt động như mong muốn, đây là một caveat biết

**) Sử dụng miễn phí, phát hành dưới MIT License

+0

rất hữu ích. thx :) – Marco

+1

Tôi lấy một quyền tự do để bọc nắm tay của bạn thành một gói nuget: https://www.nuget.org/packages/RecursiveChangeNotifier/ – LOST

+0

Cảm ơn @LOST, hy vọng nó sẽ giúp ai đó – thmshd

16

Tôi nghĩ rằng những gì bạn đang tìm kiếm là một cái gì đó giống như WPF ràng buộc.

Làm thế nào INotifyPropertyChanged công việc là vì các RaisePropertyChanged("BestFriend"); phải chỉ được fored khi tài sản BestFriend thay đổi. Không phải khi bất cứ điều gì trên bản thân đối tượng thay đổi.

Cách bạn thực hiện điều này bằng cách xử lý sự kiện hai bước INotifyPropertyChanged. Người nghe của bạn sẽ đăng ký vào sự kiện đã thay đổi của Person. Khi số BestFriend được thiết lập/thay đổi, bạn đăng ký vào sự kiện đã thay đổi của BestFriendPerson. Sau đó, bạn bắt đầu lắng nghe các sự kiện đã thay đổi của đối tượng đó.

Đây chính xác là cách thức ràng buộc WPF thực hiện việc này. Việc nghe các thay đổi của các đối tượng lồng nhau được thực hiện thông qua hệ thống đó.

Lý do điều này sẽ không hoạt động khi bạn triển khai trong Person là mức có thể trở nên rất sâu và sự kiện đã thay đổi BestFriend không có nghĩa gì nữa ("điều gì đã thay đổi?"). Vấn đề này trở nên lớn hơn khi bạn có quan hệ vòng tròn, ví dụ: người bạn tốt nhất của monther của bạn là mẹ của người bạn tốt nhất của bạn. Sau đó, khi một trong các thuộc tính thay đổi, bạn sẽ bị tràn ngăn xếp.

Vì vậy, cách bạn giải quyết điều này là tạo một lớp học mà bạn có thể xây dựng người nghe. Ví dụ, bạn sẽ xây dựng một người nghe trên BestFriend.FirstName.Lớp đó sau đó sẽ đưa một trình xử lý sự kiện vào sự kiện đã thay đổi của Person và lắng nghe các thay đổi trên BestFriend. Sau đó, khi điều đó thay đổi, nó sẽ đặt người nghe theo số BestFriend và lắng nghe những thay đổi của FirstName. Sau đó, khi điều đó thay đổi, nó sẽ gửi một sự kiện và sau đó bạn có thể nghe điều đó. Đó là cơ bản cách WPF ràng buộc hoạt động.

Xem http://msdn.microsoft.com/en-us/library/ms750413.aspx để biết thêm thông tin về ràng buộc WPF.

+0

Cảm ơn câu trả lời của bạn, tôi đã không thực sự nhận thức được một số vấn đề bạn đã mô tả. Hiện tại, chỉ ** hủy đăng ký ** khỏi Sự kiện đang gây ra một số nhức đầu. Ví dụ, 'BestFriend' có thể được đặt thành' null'. Có thực sự có thể hủy đăng ký theo cách đó, mà không có loại 'INotifyPropertyChanging' được triển khai? – thmshd

+0

Khi trình lắng nghe (đối tượng tôi mô tả) nhận tham chiếu đầu tiên tới 'Person', nó sao chép tham chiếu đến' BestFriend' và đăng ký một người nghe đến tham chiếu đó. Nếu 'BestFriend' thay đổi (ví dụ: thành' null'), trước tiên nó ngắt kết nối sự kiện khỏi tham chiếu đã sao chép, sao chép tham chiếu mới (có thể là 'null') và đăng ký trình xử lý sự kiện trên đó (nếu nó không phải là' null'). Bí quyết ở đây là bạn hoàn toàn cần phải sao chép tham chiếu đến người nghe của bạn thay vì sử dụng thuộc tính 'BestFriend' của' Person'. Điều đó sẽ giải quyết vấn đề của bạn. –

+0

Rất đẹp, tôi sẽ cố gắng triển khai giải pháp này và đăng bài khi tôi hoàn thành. Phiếu bầu +1 ít nhất cho bạn =) – thmshd

3

giải pháp Thú Thomas.

Tôi đã tìm thấy một giải pháp khác. Nó được gọi là mẫu thiết kế Propagator. Bạn có thể tìm thêm thông tin trên web (ví dụ: CodeProject: Propagator in C# - An Alternative to the Observer Design Pattern).

Về cơ bản, đó là mẫu để cập nhật các đối tượng trong mạng phụ thuộc. Nó rất hữu ích khi thay đổi trạng thái cần phải được đẩy qua một mạng lưới các đối tượng. Một sự thay đổi trạng thái được đại diện bởi một đối tượng mà nó di chuyển qua mạng của các Propagator. Bằng cách đóng gói sự thay đổi trạng thái như một đối tượng, các Propagators trở nên lỏng lẻo cùng với nhau.

Một sơ đồ lớp của các lớp truyền bá tái sử dụng:

A class diagram of the re-usable Propagator classes

Tìm hiểu thêm về CodeProject.

+0

+1 Cảm ơn sự đóng góp của bạn, tôi ' chắc chắn sẽ có một cái nhìn gần hơn – thmshd

0

Tôi đã viết một trợ giúp dễ dàng để thực hiện việc này. Bạn chỉ cần gọi BubblePropertyChanged (x => x.BestFriend) trong mô hình xem cha mẹ của bạn. n.b.có một giả định rằng bạn có một phương thức được gọi là NotifyPropertyChagned trong cha mẹ của bạn, nhưng bạn có thể điều chỉnh nó.

 /// <summary> 
    /// Bubbles up property changed events from a child viewmodel that implements {INotifyPropertyChanged} to the parent keeping 
    /// the naming hierarchy in place. 
    /// This is useful for nested view models. 
    /// </summary> 
    /// <param name="property">Child property that is a viewmodel implementing INotifyPropertyChanged.</param> 
    /// <returns></returns> 
    public IDisposable BubblePropertyChanged(Expression<Func<INotifyPropertyChanged>> property) 
    { 
     // This step is relatively expensive but only called once during setup. 
     MemberExpression body = (MemberExpression)property.Body; 
     var prefix = body.Member.Name + "."; 

     INotifyPropertyChanged child = property.Compile().Invoke(); 

     PropertyChangedEventHandler handler = (sender, e) => 
     { 
      this.NotifyPropertyChanged(prefix + e.PropertyName); 
     }; 

     child.PropertyChanged += handler; 

     return Disposable.Create(() => { child.PropertyChanged -= handler; }); 
    } 
+0

Tôi đã thử thêm này nhưng tôi nhận được Tên 'Dùng một lần' không tồn tại trong ngữ cảnh này. Dùng một lần là gì? –

+0

Dùng một lần là lớp trợ giúp cụ thể trong phần mở rộng Phản ứng, tạo ra các đối tượng cụ thể triển khai IDisposable với nhiều hành vi khác nhau. Bạn có thể loại bỏ mã đó mặc dù và xử lý các sự kiện sẽ diễn ra một cách rõ ràng nếu bạn không muốn tìm hiểu những niềm vui nếu IDisposable ngay bây giờ (mặc dù giá trị của nó nỗ lực). – DanH

0

Tôi đã được tìm kiếm trên web cho một ngày bây giờ và tôi tìm thấy một giải pháp tốt đẹp từ Sacha Barber:

http://www.codeproject.com/Articles/166530/A-Chained-Property-Observer

Ông đã tạo ra tài liệu tham khảo yếu trong một Chained Observer tài sản. Kiểm tra bài viết nếu bạn muốn xem một cách tuyệt vời khác để triển khai tính năng này.

Và tôi cũng muốn đề cập đến một thực hiện tốt đẹp với các Extensions Reactive @ http://www.rowanbeach.com/rowan-beach-blog/a-system-reactive-property-change-observer/

việc Giải pháp này chỉ dành cho một Mức độ Observer, không phải là một chuỗi đầy đủ các nhà quan sát.

+0

Cảm ơn bạn đã cập nhật. Giải pháp của Sacha rõ ràng là tiên tiến nhất, trong khi tôi có thể nhớ tôi cũng làm việc tốt, dù sao nó là một chủ đề mà tôi đã không xúc động trong một thời bây giờ :) – thmshd

0

Kiểm tra-out giải pháp của tôi trên CodeProject: http://www.codeproject.com/Articles/775831/INotifyPropertyChanged-propagator Nó chính xác những gì bạn cần - giúp tuyên truyền (theo cách thanh lịch) thuộc tính phụ thuộc khi phụ thuộc có liên quan trong việc này hoặc bất kỳ mô hình điểm lồng nhau thay đổi:

public decimal ExchTotalPrice 
{ 
    get 
    { 
     RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice)); 
     RaiseMeWhen(ExchangeRate, has => has.Changed(_ => _.Rate)); 
     return TotalPrice * ExchangeRate.Rate; 
    } 
} 
Các vấn đề liên quan