2017-10-06 1 views
1

bool 속성에 바인딩해야합니다. 컬렉션의 속성 중 하나가 true 일 때만 true입니다. 컬렉션의 모든 항목에 대한 WPF 바인딩

은 바인딩 :

<tk:BusyIndicator IsBusy="{Binding Tabs, Converter={StaticResource BusyTabsToStateConverter}}"> 

그리고 뷰 모델을 :

public class MainWindowViewModel : INotifyPropertyChanged 
{ 
    private ObservableCollection<Tab> _tabs; 

    public ObservableCollection<Tab> Tabs 
    { 
     get 
     { return _tabs; } 
     set 
     { 
      if (value != _tabs) 
      { 
       _tabs = value; 
       NotifyPropertyChanged(); 
      } 
     } 
    } 

Tab 클래스는 속성 변경 통지가 :이 컨버터는

public class Tab : INotifyPropertyChanged 
{ 
    public bool IsBusy { get{...} set{...NotifyPropertyChanged();} } 

:

public class BusyTabsToStateConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     var tabs = value as ObservableCollection<Tab>; 
     return tabs.Any(tab => tab.IsBusy); 
    } 
} 

Tab.IsBusy이 변경되면 바인딩 소스가 관찰 가능한 컬렉션에 바인딩되고 IsBusy 속성에 바인딩되어 있지 않기 때문에 문제가 발생합니다.

컬렉션의 항목 중 하나의 IsBusy 속성이 변경되면 알림 트리거를 올바르게 수행 할 수 있습니까?

답변

4

Binding Converter 대신 MainWindowViewModel에 AnyTabBusy 속성이있을 수 있습니다. 변경 알림은 PropertyChanged 이벤트 처리기에 의해 발생합니다.이 이벤트 처리기는 Tabs 컬렉션에 추가 될 때 Tabs 컬렉션의 개별 요소에 첨부되거나 분리됩니다 컬렉션에서 제거되었습니다.

아래 예제에서는 Tabs 속성이 읽기 전용입니다. 쓰기 가능해야하는 경우 Tabs 설정자에서 TabsCollectionChanged 처리기를 연결하고 분리해야합니다. 이 코드의 재사용을 확인하려면

public class MainWindowViewModel : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    public ObservableCollection<Tab> Tabs { get; } = new ObservableCollection<Tab>(); 

    public bool AnyTabBusy 
    { 
     get { return Tabs.Any(t => t.IsBusy); } 
    } 

    public MainWindowViewModel() 
    { 
     Tabs.CollectionChanged += TabsCollectionChanged; 
    } 

    private void TabsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     switch (e.Action) 
     { 
      case NotifyCollectionChangedAction.Add: 
       foreach (Tab tab in e.NewItems) 
       { 
        tab.PropertyChanged += TabPropertyChanged; 
       } 
       break; 
      case NotifyCollectionChangedAction.Remove: 
       foreach (Tab tab in e.OldItems) 
       { 
        tab.PropertyChanged -= TabPropertyChanged; 
       } 
       break; 
      default: 
       break; 
     } 
    } 

    private void TabPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (e.PropertyName == nameof(Tab.IsBusy)) 
     { 
      PropertyChanged?.Invoke(this, 
       new PropertyChangedEventArgs(nameof(AnyTabBusy))); 
     } 
    } 
} 

, 당신은 당신이 ItemPropertyChanged 이벤트에 대한 핸들러를 첨부 할 수처럼 아래 그림과 파생 컬렉션 클래스로 둘 수 있었다.

public class MainWindowViewModel : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    public ObservableItemCollection<Tab> Tabs { get; } 
     = new ObservableItemCollection<Tab>(); 

    public bool AnyTabBusy 
    { 
     get { return Tabs.Any(t => t.IsBusy); } 
    } 

    public MainWindowViewModel() 
    { 
     Tabs.ItemPropertyChanged += TabPropertyChanged; 
    } 

    private void TabPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (e.PropertyName == nameof(Tab.IsBusy)) 
     { 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AnyTabBusy))); 
     } 
    } 
} 
+0

예제는 훌륭합니다. 이러한 작은 기능에 대한 많은 코드가 있지만 솔루션은 훌륭하게 작동합니다. 감사! – Matthias

+0

@Clemens 나는 당신이 괜찮기를 희망하지만, 나는 당신의 대답과 그것을 사용하기 쉬운 확장 방법으로 (https://stackoverflow.com/a/46606925/526724) 가져 갔다. –

+1

@ 브래들리 당연히 아니지만 좀 더 일반적인 사용법을 달성하기위한 나의 접근법을 참조하십시오. – Clemens

-1

모델에서 모델의 컬렉션으로 알림을 전파하려면 Collection 자체에 Notifiable 속성이 있어야합니다.

아마 당신은 ObservableCollection에를 확장하고 불행하게도 무료로이를 얻을 수있는 방법은없는 UI

+0

Downvoter는 문제를 해결할 수 있도록 이유를 지정해야합니다. –

-1

을 알릴 수 있다는 점에서 재산권을 가질 수 있습니다. MainWindowViewModel에 IsBusy 속성을 만들 것입니다. 탭이 설정된 경우 컬렉션 변경에 대한 수신기를 추가하고 해당 업데이트를 IsBusy 속성으로 업데이트하십시오.

1

내가 @Clemens 'answer을 촬영했습니다, 그리고 여러 컬렉션에 사용하기 쉽게 만들 수있는 확장 방법에 변환 :

public class ObservableItemCollection<T> 
    : ObservableCollection<T> where T : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler ItemPropertyChanged; 

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
    { 
     base.OnCollectionChanged(e); 

     switch (e.Action) 
     { 
      case NotifyCollectionChangedAction.Add: 
       foreach (INotifyPropertyChanged item in e.NewItems) 
       { 
        item.PropertyChanged += OnItemPropertyChanged; 
       } 
       break; 
      case NotifyCollectionChangedAction.Remove: 
       foreach (INotifyPropertyChanged item in e.OldItems) 
       { 
        item.PropertyChanged -= OnItemPropertyChanged; 
       } 
       break; 
      default: 
       break; 
     } 
    } 

    private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     ItemPropertyChanged?.Invoke(this, e); 
    } 
} 

뷰 모델은 이제이 감소 될 수있다 . PropertyChangedEventHandler을 가져 와서 추가 및 제거 할 때 컬렉션의 항목에서 자동으로 추가 및 제거합니다. 만약 당신이 이것을 찬성표로 올리면 @ Clemens의 답변도 올려주세요.

익명 메서드를 PropertyChanged 처리기로 사용하지 마십시오 (이 해결 방법이 아닌 일반적인 모든 이벤트 처리기에서 사용됨). 제거하기 어려울 수 있으므로 특별한 precautions을 사용하지 마십시오.

(주 :이 CollectionChanged 처리기의 위임 쉽게 처리하기 위해 로컬 함수를 사용하기 때문에이, C# 7 필요합니다.)

public static class ObservableCollectionExtensions 
{ 
    public static Hook<TList> RegisterPropertyChangeHook<TList>(this ObservableCollection<TList> collection, PropertyChangedEventHandler handler) where TList : INotifyPropertyChanged 
    { 
     void Collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
     { 
      switch (e.Action) 
      { 
       case NotifyCollectionChangedAction.Add: 
        foreach (TList item in e.NewItems) 
        { 
         item.PropertyChanged += handler; 
        } 
        break; 
       case NotifyCollectionChangedAction.Remove: 
        foreach (TList item in e.OldItems) 
        { 
         item.PropertyChanged -= handler; 
        } 
        break; 
       default: 
        break; 
      } 
     } 

     return new Hook<TList>(collection, Collection_CollectionChanged); 
    } 

    public class Hook<TList> where TList : INotifyPropertyChanged 
    { 
     internal Hook(ObservableCollection<TList> collection, NotifyCollectionChangedEventHandler handler) 
     { 
      _handler = handler; 
      _collection = collection; 

      collection.CollectionChanged += handler; 
     } 

     private NotifyCollectionChangedEventHandler _handler; 
     private ObservableCollection<TList> _collection; 

     public void Unregister() 
     { 
      _collection.CollectionChanged -= _handler; 
     } 
    } 
} 

당신은 다음과 같이 사용할 수 있습니다 :

void Main() 
{ 
    var list = new ObservableCollection<Animal>(); 

    list.RegisterPropertyChangeHook(OnPropertyChange); 

    var animal = new Animal(); // Has a "Name" property that raises PropertyChanged 
    list.Add(animal); 
    animal.Name="Charlie"; // OnPropertyChange called 

    list.Remove(animal); 
    animal.Name="Sam"; // OnPropertyChange not called 
} 

private void OnPropertyChange(object sender, PropertyChangedEventArgs e) 
{ 
    Console.WriteLine($"property changed: {e.PropertyName}"); 
} 

후크 등록을 취소하려면 다음을 수행하십시오.

var hook = list.RegisterPropertyChangeHook(OnPropertyChange); 
hook.Unregister(); 

등록 해제 제네릭을 지원하지 않는 확장 메서드 클래스로 인해 예상보다 까다로워졌습니다. "memento"패턴을 사용하여 나중에 등록 취소하는 데 사용할 수있는 객체를 반환합니다.

관련 문제