2009-02-27 23 views
8

Tôi đang phát triển một ứng dụng trong Silverlight2 và cố gắng theo mẫu Model-View-ViewModel. Tôi ràng buộc tài sản IsEnabled trên một số điều khiển đến một thuộc tính boolean trên ViewModel.Thông báo bất động sảnThay đổi cho các thuộc tính được tính

Tôi đang gặp sự cố khi các thuộc tính đó bắt nguồn từ các thuộc tính khác. Hãy nói rằng tôi có một nút Save mà tôi chỉ muốn được kích hoạt khi nó có thể lưu (dữ liệu đã được tải, và chúng tôi hiện đang không bận rộn làm công cụ trong cơ sở dữ liệu).

Vì vậy, tôi có một vài thuộc tính như thế này:

private bool m_DatabaseBusy; 
    public bool DatabaseBusy 
    { 
     get { return m_DatabaseBusy; } 
     set 
     { 
      if (m_DatabaseBusy != value) 
      { 
       m_DatabaseBusy = value; 
       OnPropertyChanged("DatabaseBusy"); 
      } 
     } 
    } 

    private bool m_IsLoaded; 
    public bool IsLoaded 
    { 
     get { return m_IsLoaded; } 
     set 
     { 
      if (m_IsLoaded != value) 
      { 
       m_IsLoaded = value; 
       OnPropertyChanged("IsLoaded"); 
      } 
     } 
    } 

Bây giờ những gì tôi muốn làm điều này là:

public bool CanSave 
{ 
    get { return this.IsLoaded && !this.DatabaseBusy; } 
} 

Nhưng lưu ý việc thiếu thông báo tài sản thay đổi. Vì vậy, câu hỏi đặt ra là: Cách sạch sẽ để lộ một thuộc tính boolean duy nhất mà tôi có thể liên kết là gì, nhưng được tính toán thay vì được đặt rõ ràng và cung cấp thông báo để giao diện người dùng có thể cập nhật chính xác không? Không.

CHỈNH SỬA:Cảm ơn sự giúp đỡ của mọi người - tôi đã hiểu và có một thuộc tính tùy chỉnh. Tôi đang đăng nguồn ở đây trong trường hợp ai đó quan tâm. Tôi chắc chắn rằng nó có thể được thực hiện một cách sạch hơn, vì vậy nếu bạn thấy bất kỳ sai sót, thêm một bình luận hoặc một câu trả lời.

Về cơ bản những gì tôi đã được thực hiện một giao diện được định nghĩa một danh sách các cặp khóa-giá trị giữ những gì thuộc tính phụ thuộc vào các đặc tính khác:

public interface INotifyDependentPropertyChanged 
{ 
    // key,value = parent_property_name, child_property_name, where child depends on parent. 
    List<KeyValuePair<string, string>> DependentPropertyList{get;} 
} 

sau đó tôi đã thực hiện các thuộc tính đi trên mỗi tài sản:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)] 
public class NotifyDependsOnAttribute : Attribute 
{ 
    public string DependsOn { get; set; } 
    public NotifyDependsOnAttribute(string dependsOn) 
    { 
     this.DependsOn = dependsOn; 
    } 

    public static void BuildDependentPropertyList(object obj) 
    { 
     if (obj == null) 
     { 
      throw new ArgumentNullException("obj"); 
     } 

     var obj_interface = (obj as INotifyDependentPropertyChanged); 

     if (obj_interface == null) 
     { 
      throw new Exception(string.Format("Type {0} does not implement INotifyDependentPropertyChanged.",obj.GetType().Name)); 
     } 

     obj_interface.DependentPropertyList.Clear(); 

     // Build the list of dependent properties. 
     foreach (var property in obj.GetType().GetProperties()) 
     { 
      // Find all of our attributes (may be multiple). 
      var attributeArray = (NotifyDependsOnAttribute[])property.GetCustomAttributes(typeof(NotifyDependsOnAttribute), false); 

      foreach (var attribute in attributeArray) 
      { 
       obj_interface.DependentPropertyList.Add(new KeyValuePair<string, string>(attribute.DependsOn, property.Name)); 
      } 
     } 
    } 
} 

Bản thân thuộc tính chỉ lưu trữ một chuỗi. Bạn có thể xác định nhiều phụ thuộc cho mỗi thuộc tính. Các đường ruột của thuộc tính nằm trong hàm tĩnh BuildDependentPropertyList. Bạn phải gọi điều này trong hàm tạo của lớp của bạn. (Bất cứ ai biết nếu có một cách để làm điều này thông qua một thuộc tính class/constructor?) Trong trường hợp của tôi tất cả điều này được ẩn đi trong một lớp cơ sở, vì vậy trong các lớp con bạn chỉ cần đặt các thuộc tính trên các thuộc tính. Sau đó, bạn sửa đổi tương đương OnPropertyChanged của bạn để tìm bất kỳ phụ thuộc. Đây là lớp cơ sở ViewModel của tôi làm ví dụ:

public class ViewModel : INotifyPropertyChanged, INotifyDependentPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    protected virtual void OnPropertyChanged(string propertyname) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(propertyname)); 

      // fire for dependent properties 
      foreach (var p in this.DependentPropertyList.Where((x) => x.Key.Equals(propertyname))) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(p.Value)); 
      } 
     } 
    } 

    private List<KeyValuePair<string, string>> m_DependentPropertyList = new List<KeyValuePair<string, string>>(); 
    public List<KeyValuePair<string, string>> DependentPropertyList 
    { 
     get { return m_DependentPropertyList; } 
    } 

    public ViewModel() 
    { 
     NotifyDependsOnAttribute.BuildDependentPropertyList(this); 
    } 
} 

Cuối cùng, bạn đặt thuộc tính trên thuộc tính bị ảnh hưởng. Tôi thích cách này bởi vì thuộc tính dẫn xuất giữ các thuộc tính mà nó phụ thuộc vào, chứ không phải là cách khác.

[NotifyDependsOn("Session")] 
    [NotifyDependsOn("DatabaseBusy")] 
    public bool SaveEnabled 
    { 
     get { return !this.Session.IsLocked && !this.DatabaseBusy; } 
    } 

Thông báo lớn ở đây là nó chỉ hoạt động khi các thuộc tính khác là thành viên của lớp hiện tại. Trong ví dụ trên, nếu điều này.Session.IsLocked thay đổi, thông báo không nhận được thông qua. Cách tôi nhận được xung quanh này là để đăng ký này.Session.NotifyPropertyChanged và lửa PropertyChanged cho "Session".(Vâng, điều này sẽ dẫn đến sự kiện bắn nơi họ didnt cần)

Trả lời

6

Cách truyền thống để làm điều này là để thêm một cuộc gọi OnPropertyChanged cho mỗi thuộc tính mà có thể ảnh hưởng đến tính của bạn một, như thế này:

public bool IsLoaded 
{ 
    get { return m_IsLoaded; } 
    set 
    { 
     if (m_IsLoaded != value) 
     { 
      m_IsLoaded = value; 
      OnPropertyChanged("IsLoaded"); 
      OnPropertyChanged("CanSave"); 
     } 
    } 
} 

Điều này có thể nhận được một chút lộn xộn (nếu, ví dụ, tính toán của bạn trong các thay đổi của CanSave).

One (sạch hơn tôi không biết?) Cách để làm được việc này sẽ được ghi đè OnPropertyChanged và thực hiện cuộc gọi có:

protected override void OnPropertyChanged(string propertyName) 
{ 
    base.OnPropertyChanged(propertyName); 
    if (propertyName == "IsLoaded" /* || propertyName == etc */) 
    { 
     base.OnPropertyChanged("CanSave"); 
    } 
} 
+0

Tôi sẽ sử dụng ghi đè OnPropertyChanged. Tôi tự hỏi liệu các phụ thuộc có thể được khai báo là các thuộc tính không? – geofftnz

+0

Bạn có thể thiết lập Danh sách tĩnh để theo dõi các tên thuộc tính mà CanSave tùy thuộc vào. Sau đó, nó sẽ là một "if (_canSaveProperties.Contains (propertyName))". –

+0

Ý tưởng hay, cảm ơn! – geofftnz

2

Bạn cần thêm thông báo cho sự thay đổi sở hữu CanSave ở khắp mọi nơi một trong những thuộc tính nó phụ thuộc thay đổi:

OnPropertyChanged("DatabaseBusy"); 
OnPropertyChanged("CanSave"); 

OnPropertyChanged("IsEnabled"); 
OnPropertyChanged("CanSave"); 
+0

này sẽ làm việc một cách chính xác, nhưng nó có nghĩa là bạn sẽ làm mất hiệu lực CanSave nhiều hơn là cần thiết, có thể làm cho phần còn lại của ứng dụng làm nhiều công việc hơn là bắt buộc. – KeithMahoney

+0

Nếu tôi không có quyền truy cập vào phương pháp đã đặt của thuộc tính thì sao? – geofftnz

1

Còn giải pháp này thì sao?

private bool _previousCanSave; 
private void UpdateCanSave() 
{ 
    if (CanSave != _previousCanSave) 
    { 
     _previousCanSave = CanSave; 
     OnPropertyChanged("CanSave"); 
    } 
} 

Sau đó gọi UpdateCanSave() trong các trình cài đặt của Đã tải và DatabaseBusy?

Nếu bạn không thể sửa đổi các trình cài đặt của IsLoaded và DatabaseBusy vì chúng nằm trong các lớp khác nhau, bạn có thể thử gọi UpdateCanSave() trong trình xử lý sự kiện PropertyChanged cho đối tượng định nghĩa IsLoaded và DatabaseBusy.

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