WPF: привязка к ObservableCollection в ControlTemplate не обновляется - PullRequest
1 голос
/ 15 июня 2010

Я создал ControlTemplate для своего пользовательского элемента управления MyControl.

MyControl происходит от System.Windows.Controls.Control и определяет следующее свойство public ObservableCollection<MyControl> Children{ get; protected set; }.

Для отображения вложенных дочерних элементов управления я использую ItemsControl (StackPanel), который окружен GroupBox. Если нет дочерних элементов управления, я хочу скрыть GroupBox.

Все отлично работает при запуске приложения: групповой блок и дочерние элементы управления отображаются, если свойство Children изначально содержало хотя бы один элемент. В другом случае он скрыт.

Проблема начинается, когда пользователь добавляет дочерний элемент управления в пустую коллекцию. Видимость GroupBox все еще ухудшена. Та же проблема возникает, когда последний дочерний элемент управления удаляется из коллекции. GroupBox все еще виден. Другим симптомом является то, что преобразователь HideEmptyEnumerationConverter не вызывается. Добавление / удаление дочерних элементов управления в непустые коллекции работает как ожидалось.

Что не так со следующей привязкой? Очевидно, он работает один раз, но не обновляется, хотя коллекция, к которой я привязываю, имеет тип ObservableCollection.

<!-- Converter for hiding empty enumerations -->
<Common:HideEmptyEnumerationConverter x:Key="hideEmptyEnumerationConverter"/>
<!--- ... --->

<ControlTemplate TargetType="{x:Type MyControl}">
  <!-- ... other stuff that works ... -->
  <!-- Child components -->
  <GroupBox Header="Children"
            Visibility="{Binding RelativeSource={RelativeSource TemplatedParent},
              Path=Children, Converter={StaticResource hideEmptyEnumerationConverter}}">
    <ItemsControl ItemsSource="{TemplateBinding Children}"/>
  </GroupBox>
</ControlTemplate>

.

[ValueConversion(typeof (IEnumerable), typeof (Visibility))]
public class HideEmptyEnumerationConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int itemCount = ((IEnumerable) value).Cast<object>().Count();
        return itemCount == 0 ? Visibility.Collapsed : Visibility.Visible;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Другой, более общий вопрос: как вы отлаживаете привязки? Нашел это (http://bea.stollnitz.com/blog/?p=52) но все же мне очень трудно это сделать.

Я рад любой помощи или предложению.

Ответы [ 2 ]

6 голосов
/ 15 июня 2010

Проблема в том, что ваше свойство Children само по себе никогда не изменяется, только его содержимое.Поскольку значение свойства не изменяется, привязка не переоценивается.То, что вам нужно сделать, это связать со свойством Count коллекции.Самый простой способ добиться этого - использовать DataTrigger в вашем шаблоне:

<ControlTemplate TargetType="{x:Type MyControl}">
  <!-- ... other stuff that works ... -->
  <!-- Child components -->
  <GroupBox x:Name="gb" Header="Children">
    <ItemsControl ItemsSource="{TemplateBinding Children}"/>
  </GroupBox>
  <ControlTemplate.Triggers>
      <DataTrigger Binding="{Binding Path=Children.Count, RelativeSource={RelativeSource TemplatedParent}}"
                   Value="0">
        <Setter TargetName="gb" Property="Visibility" Value="Collapsed" />
      </DataTrigger>
  </ControlTemplate.Triggers>
</ControlTemplate>
0 голосов
/ 15 июня 2010

Вам необходимо уведомлять всякий раз, когда изменяется количество элементов в свойстве Children. Вы можете сделать это путем реализации интерфейса INotifyPropertyChanged, зарегистрироваться в событии CollectionChanged коллекции Children и вызвать там PropertyChanged.

Пример:

public class MyControl : Control, INotifyPropertyChanged
{
    static MyControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
    }

    public ObservableCollection<UIElement> Children
    {
        get { return (ObservableCollection<UIElement>)GetValue(ChildrenProperty); }
        set { SetValue(ChildrenProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Children.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ChildrenProperty =
        DependencyProperty.Register("Children", typeof(ObservableCollection<UIElement>), typeof(MyControl), new UIPropertyMetadata(0));

    public MyControl()
    {
        Children = new ObservableCollection<UIElement>();
        Children.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Children_CollectionChanged);
    }

    void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        RaisePropertyChanged("Children");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
...