2010-09-08 32 views
7

Đã có rất nhiều bài viết về cách sử dụng phản chiếu và LINQ để tăng các sự kiện PropertyChanged theo cách an toàn, không sử dụng chuỗi.Xử lý tài sảnThay đổi theo cách an toàn loại

Nhưng có cách nào để tiêu thụ Thuộc tínhThay đổi sự kiện theo cách an toàn không? Hiện tại, tôi đang thực hiện điều này

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case "Property1": 
      ... 
     case "Property2": 
      ... 

     ....    
    } 
} 

Có cách nào để tránh các chuỗi mã hóa cứng trong câu lệnh chuyển đổi để xử lý các thuộc tính khác nhau không? Một số cách tiếp cận dựa trên phản chiếu LINQ hoặc tương tự?

Trả lời

3

Hãy khai báo một phương pháp mà có thể biến một biểu thức lambda vào một đối tượng Reflection PropertyInfo (taken from my answer here):

public static PropertyInfo GetProperty<T>(Expression<Func<T>> expr) 
{ 
    var member = expr.Body as MemberExpression; 
    if (member == null) 
     throw new InvalidOperationException("Expression is not a member access expression."); 
    var property = member.Member as PropertyInfo; 
    if (property == null) 
     throw new InvalidOperationException("Member in expression is not a property."); 
    return property; 
} 

Và sau đó chúng ta hãy sử dụng nó để có được tên của các thuộc tính:

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    if (e.PropertyName == GetProperty(() => Property1).Name) 
    { 
     // ... 
    } 
    else if (e.PropertyName == GetProperty(() => Property2).Name) 
    { 
     // ... 
    } 
} 

Rất tiếc, bạn không thể sử dụng câu lệnh switch vì tên thuộc tính không còn là hằng số biên dịch.

+0

(Mặc dù thẳng thắn, nếu bạn thực sự muốn an toàn loại thích hợp, có lẽ bạn nên xem xét không sử dụng 'PropertyChangedEventArgs' ở tất cả và thay vào đó tuyên bố một trong những của riêng bạn mà chứa các 'đối tượng PropertyInfo' thay vì một chuỗi.) – Timwi

1

Josh Smith's MVVM Foundation bao gồm lớp PropertyObserver thực hiện những gì bạn muốn.

+0

Mã của anh ta vẫn sử dụng chuỗi để chuyển tên thuộc tính, anh ta chỉ thêm xác minh để kiểm tra xem đó là tên thuộc tính hợp lệ: nếu (TypeDescriptor.GetProperties (this) [propertyName] == null) –

+0

+ 1 để trỏ đến tập hợp các bài viết tuyệt vời - mã tuyệt vời và đưa ra các giải thích hợp lý –

1

Tôi tránh chuyển đổi bằng cách kết hợp mẫu lệnh và một số logic biểu thức. Bạn đóng gói case-action trong một lệnh. Tôi sẽ minh họa điều này bằng cách sử dụng cấu trúc Bộ điều khiển chế độ xem mô hình. mã thế giới thực - WinForms, nhưng ý tưởng tương tự

ví dụ sẽ tải một cây trong chế độ xem, khi thuộc tính Cây được đặt trong mô hình.

một tùy chỉnh ICommand

void Execute(); 
string PropertyName { get; } 

bê tông lệnh

public TreeChangedCommand(TreeModel model, ISelectTreeView selectTreeView,Expression<Func<object>> propertyExpression) 
    { 
     _model = model; 
     _selectTreeView = selectTreeView; 

     var body = propertyExpression.Body as MemberExpression; 
     _propertyName = body.Member.Name; 

    } 

khiển constructor

//handle notify changed event from model 
    _model.PropertyChanged += _model_PropertyChanged; 
    //init commands 
    commands = new List<ICommand>(); 
    commands.Add(new TreeChangedCommand(_model,_mainView,()=>_model.Tree)); 

PropertyChanged handler

void _model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 
{ 
    //find the corresponding command and execute it. (instead of the switch) 
    commands.FirstOrDefault(cmd=>cmd.PropertyName.Equals(e.PropertyName)).Execute(); 
} 
1

Một giải pháp gần đây tôi đã đưa ra là để đóng gói logic sự kiện văn vào một lớp chuyên dụng.

Lớp có một phương pháp nào gọi là Handle trong đó có chữ ký giống như PropertyChangedEventHandler đại biểu có nghĩa là nó có thể được đăng ký sự kiện PropertyChanged của bất kỳ lớp mà thực hiện giao diện INotifyPropertyChanged.

Lớp này chấp nhận các đại biểu như thường được sử dụng DelegateCommand được sử dụng bởi hầu hết các triển khai WPF có nghĩa là nó có thể được sử dụng mà không phải tạo các lớp con.

Lớp trông như thế này:

public class PropertyChangedHandler 
{ 
    private readonly Action<string> handler; 
    private readonly Predicate<string> condition; 
    private readonly IEnumerable<string> properties; 

    public PropertyChangedHandler(Action<string> handler, 
     Predicate<string> condition, IEnumerable<string> properties) 
    { 
     this.handler = handler; 
     this.condition = condition; 
     this.properties = properties; 
    } 

    public void Handle(object sender, PropertyChangedEventArgs e) 
    { 
     string property = e.PropertyName ?? string.Empty; 

     if (this.Observes(property) && this.ShouldHandle(property)) 
     { 
      handler(property); 
     } 
    } 

    private bool ShouldHandle(string property) 
    { 
     return condition == null ? true : condition(property); 
    } 

    private bool Observes(string property) 
    { 
     return string.IsNullOrEmpty(property) ? true : 
      !properties.Any() ? true : properties.Contains(property); 
    } 
} 

Sau đó bạn có thể đăng ký một tài sản thay đổi xử lý sự kiện như thế này:

var eventHandler = new PropertyChangedHandler(
    handler: p => { /* event handler logic... */ }, 
    condition: p => { /* determine if handler is invoked... */ }, 
    properties: new string[] { "Foo", "Bar" } 
); 

aViewModel.PropertyChanged += eventHandler.Handle; 

Các PropertyChangedHandler chăm sóc kiểm tra PropertyName của PropertyChangedEventArgs và đảm bảo rằng handler được gọi bởi các thay đổi thuộc tính phù hợp.

Lưu ý rằng PropertyChangedHandler cũng chấp nhận một vị từ để đại biểu xử lý có thể được gửi đi có điều kiện. Lớp này cũng cho phép bạn chỉ định nhiều thuộc tính để một trình xử lý đơn lẻ có thể được liên kết với nhiều thuộc tính trong một lần. Điều này có thể dễ dàng được mở rộng bằng cách sử dụng một số phương pháp mở rộng để đăng ký trình xử lý thuận tiện hơn cho phép bạn tạo trình xử lý sự kiện và đăng ký sự kiện PropertyChanged trong một cuộc gọi phương thức duy nhất và chỉ định các thuộc tính sử dụng biểu thức thay cho chuỗi trông như thế này:

aViewModel.OnPropertyChanged(
    handler: p => handlerMethod(), 
    condition: p => handlerCondition, 
    properties: aViewModel.GetProperties(
     p => p.Foo, 
     p => p.Bar, 
     p => p.Baz 
    ) 
); 

này về cơ bản nói rằng khi một trong hai Foo, Bar hoặc Baz tính thay đổi handlerMethod sẽ được gọi nếu handlerCondition là đúng.

Quá tải phương thức OnPropertychanged được cung cấp để đáp ứng các yêu cầu đăng ký sự kiện khác nhau.

Nếu, ví dụ, bạn muốn đăng ký một handler mà được gọi là cho bất kỳ thay đổi sự kiện bất động sản và luôn luôn thực hiện bạn chỉ có thể làm như sau:

aViewModel.OnPropertyChanged(p => handlerMethod()); 

Nếu, ví dụ, bạn muốn đăng ký một handler đó là luôn luôn thực hiện nhưng chỉ cho một sự thay đổi tài sản cụ thể duy nhất bạn có thể làm như sau:

aViewModel.OnPropertyChanged(
    handler: p => handlerMethod(), 
    properties: aViewModel.GetProperties(p => p.Foo) 
); 

tôi đã tìm thấy phương pháp này rất hữu ích khi viết các ứng dụng WPF MVVM. Hãy tưởng tượng bạn có một kịch bản mà bạn muốn vô hiệu hóa một lệnh khi bất kỳ thuộc tính nào thay đổi. Sử dụng các phương pháp thông thường bạn sẽ phải làm một cái gì đó như thế này:

void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case "Foo": 
     case "Bar": 
     case "Baz": 
      FooBarBazCommand.Invalidate(); 
      break; 
     ....    
    } 
} 

Nếu bạn thay đổi tên của bất kỳ thuộc tính ViewModel bạn sẽ cần phải nhớ để cập nhật các xử lý sự kiện để chọn các thuộc tính chính xác.

Sử dụng lớp PropertyChangedHandler đã nêu ở trên, bạn có thể đạt được kết quả tương tự với những điều sau:

aViewModel.OnPropertyChanged(
    handler: p => FooBarBazCommand.Invalidate(), 
    properties: aViewModel.GetProperties(
     p => p.Foo, 
     p => p.Bar, 
     p => p.Baz 
    ) 
); 

này bây giờ có thời gian biên dịch an toàn nên nếu bất kỳ thuộc tính ViewModel được đổi tên chương trình sẽ không biên dịch.

2

Với C# 6.0, bạn có thể sử dụng nameof. Bạn cũng có thể tham chiếu thuộc tính của lớp mà không cần tạo một thể hiện của lớp đó.

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case nameof(ClassName.Property1): 
      ... 
     case nameof(ClassName.Property2): 
      ... 

     ....    
    } 
} 
Các vấn đề liên quan