WPF:绑定到 ControlTemplate 中的 ObservableCollection 未更新

发布于 2024-09-05 22:22:40 字数 2203 浏览 3 评论 0原文

我为自定义控件 MyControl 创建了一个 ControlTemplate

MyControl 派生自 System.Windows.Controls.Control 并定义以下属性 public ObservableCollection孩子们{得到;受保护集; }

为了显示嵌套的子控件,我使用了一个被 GroupBox 包围的 ItemsControl (StackPanel)。如果没有子控件,我想隐藏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) 但我仍然觉得很难做到。

我很高兴获得任何帮助或建议。

I created a ControlTemplate for my custom control MyControl.

MyControl derives from System.Windows.Controls.Control and defines the following property public ObservableCollection<MyControl> Children{ get; protected set; }.

To display the nested child controls I am using an ItemsControl (StackPanel) which is surrounded by a GroupBox. If there are no child controls, I want to hide the GroupBox.

Everything works fine on application startup: The group box and child controls are shown if the Children property initially contained at least one element. In the other case it is hidden.

The problem starts when the user adds a child control to an empty collection. The GroupBox's visibility is still collapsed. The same problem occurs when the last child control is removed from the collection. The GroupBox is still visible.
Another symptom is that the HideEmptyEnumerationConverter converter does not get called.
Adding/removing child controls to non empty collections works as expected.

Whats wrong with the following binding? Obviously it works once but does not get updated, although the collection I am binding to is of type 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
}

Another, more general question: How do you guys debug bindings? Found this (http://bea.stollnitz.com/blog/?p=52) but still I find it very hard to do.

I am glad for any help or suggestion.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

折戟 2024-09-12 22:22:40

问题是您的 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>

The problem is that your Children property itself never changes, just its content. Since the property value doesn't change, the binding isn't reevaluated. What you need to do is bind to the Count property of the collection. The easiest way you can achieve this is with a DataTrigger in your template:

<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>

每当您的儿童财产中的物品数量发生变化时,您都需要通知。您可以通过实现 INotifyPropertyChanged 接口、注册到 Children 集合的 CollectionChanged 事件并从那里引发 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));
        }
    }
}

You need to notify whenever the number of items in your Children property changes. You can do that by implementing INotifyPropertyChanged interface, register to Children collection's CollectionChanged event and raise PropertyChanged from there.

Example:

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));
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文