WPF TemplateBinding到ObservableCollection

时间:2016-08-19 07:17:29

标签: wpf observablecollection templatebinding

我正在创建包含其他用户控件的内容控件。我们称它们为InnerControl和OuterControl。 InnerControl有一个ObservableCollection类型的依赖项属性,名为" Items。"我试图将它绑定到OuterControl中的相同依赖属性。这是InnerControl代码的存根:

public class InnerControl : UserControl {

    public InnerControl() {
        InnerItems = new ObservableCollection<string>();
    }

    public ObservableCollection<string> InnerItems
    {
        get { return (ObservableCollection<string>)GetValue(InnerItemsProperty); }
        set { SetValue(InnerItemsProperty, value); }
    }
    public static DependencyProperty InnerItemsProperty =
        DependencyProperty.Register("InnerItems",
            typeof(ObservableCollection<string>),
            typeof(InnerControl),
            new PropertyMetadata());
}

外部控件包含相同的Items属性:

public class OuterControl : ContentControl {

    public OuterControl() {
        OuterItems = new ObservableCollection<string>();
    }

    static OuterControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(OuterControl),
                    new FrameworkPropertyMetadata(typeof(OuterControl)));
    }

    public ObservableCollection<string> OuterItems
    {
        get { return (ObservableCollection<string>)GetValue(OuterItemsProperty); }
        set { SetValue(OuterItemsProperty, value); }
    }
    public static DependencyProperty OuterItemsProperty =
        DependencyProperty.Register("OuterItems",
            typeof(ObservableCollection<string>),
            typeof(OuterControl),
            new PropertyMetadata());
}

然后我在generic.xaml文件中定义了OuterControl的外观:

<Style TargetType="{x:Type userControls:OuterControl}">
    <Setter Property="Padding" Value="10" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type userControls:OuterControl}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <local:InnerControl Grid.Row="0" Grid.Column="0"
                            InnerItems="{TemplateBinding OuterItems}"/>
                    <ContentPresenter Grid.Row="1" Grid.Column="0" 
                            Content="{TemplateBinding Content}"
                            Margin="{TemplateBinding Padding}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

我想引起你注意的上述代码中非常重要的部分是:

<local:InnerControl Grid.Row="0" Grid.Column="0" 
InnerItems="{TemplateBinding OuterItems}"/>

我期望发生的事情是,当项目被添加到OuterControl的OuterItems集合中时,这些相同的项目将被添加到InnerControl.InnerItems集合中。但是,这并没有发生,我也无法弄清楚原因。

我还尝试了相对绑定,以便我可以尝试使用TwoWay模式等等。像这样:

InnerItems="{Binding OuterItems, Mode=TwoWay, 
RelativeSource={RelativeSource TemplatedParent}}"

但到目前为止,它还没有发挥作用。

更新

到目前为止,我认为解决此问题的所有内容都只暴露了新问题,因此我删除了之前的更新。我此时所坚持的是:

如果我在构造函数中初始化InnerItems,那么TemplateBinding似乎不起作用(项目永远不会更新)

如果我根本不初始化InnerItems,则TemplateBinding有效。但是,如果InnerControl仅在Designer中使用,则会中断,因为当设计器尝试向其添加项时,InnerItems为null。

更新:已解决(?)

Per Clemen的建议,我需要使用这种方法在构造函数中初始化InnerItem:

SetCurrentValue(InnerItemsProperty, new ObservableCollection<string>());

手指交叉,这似乎正在起作用,让我双管齐下(TemplateBinding有效,控件可以单独使用)。

2 个答案:

答案 0 :(得分:1)

我发现@Clemens评论很可能有正确的答案。无论如何,我使用下面的代码测试了你的解决方案,它对我来说很好。

检查绑定和添加项目的方式。您没有在问题中发布该代码。

OuterControl

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;

namespace TemplateBindingTest.Controls
{
    public class OuterControl : UserControl
    {
        static OuterControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(OuterControl), new FrameworkPropertyMetadata(typeof(OuterControl)));
        }

        public ObservableCollection<string> OuterItems
        {
            get { return (ObservableCollection<string>)GetValue(OuterItemsProperty); }
            set { SetValue(OuterItemsProperty, value); }
        }
        public static DependencyProperty OuterItemsProperty =
            DependencyProperty.Register("OuterItems",
                typeof(ObservableCollection<string>),
                typeof(OuterControl),
                new PropertyMetadata(new ObservableCollection<string>()));
    }
}

InnerControl

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;

namespace TemplateBindingTest.Controls
{
    public class InnerControl : UserControl
    {
        static InnerControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(InnerControl), new FrameworkPropertyMetadata(typeof(InnerControl)));
        }

        public ObservableCollection<string> InnerItems
        {
            get { return (ObservableCollection<string>)GetValue(InnerItemsProperty); }
            set { SetValue(InnerItemsProperty, value); }
        }

        public static DependencyProperty InnerItemsProperty =
            DependencyProperty.Register("InnerItems",
                typeof(ObservableCollection<string>),
                typeof(InnerControl),
                new PropertyMetadata(new ObservableCollection<string>()));
    }
}

Generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:controls="clr-namespace:TemplateBindingTest.Controls">

    <Style TargetType="{x:Type controls:OuterControl}">
        <Setter Property="Padding" Value="10" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:OuterControl}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <ContentPresenter Grid.Row="0" Grid.Column="0" 
                            Content="{TemplateBinding Content}"
                            Margin="{TemplateBinding Padding}"/>
                        <ItemsControl Grid.Row="1" ItemsSource="{TemplateBinding OuterItems}" />
                        <Border Grid.Row="2" BorderThickness="1" BorderBrush="Red">
                            <controls:InnerControl  Grid.Column="0"
                            InnerItems="{TemplateBinding OuterItems}">Inner Control</controls:InnerControl>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style TargetType="{x:Type controls:InnerControl}">
        <Setter Property="Padding" Value="10" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:InnerControl}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <ContentPresenter Grid.Row="0" Grid.Column="0" 
                            Content="{TemplateBinding Content}"
                            Margin="{TemplateBinding Padding}"/>
                        <ItemsControl Grid.Row="1" ItemsSource="{TemplateBinding InnerItems}" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

MainWindow.xaml

<Window x:Class="TemplateBindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:controls="clr-namespace:TemplateBindingTest.Controls"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <controls:OuterControl OuterItems="{Binding OuterItems}">Outer Control</controls:OuterControl>
        <Button Grid.Row="1" Content="Add" Click="Button_Click" HorizontalAlignment="Left" />
    </Grid>
</Window>

MainWindow.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;

namespace TemplateBindingTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private ObservableCollection<string> _OuterItems;

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            _OuterItems = new ObservableCollection<string>(new List<string>()
            {
                "Test 1",
                "Test 2",
                "Test 3",
            });
        }

        public ObservableCollection<string> OuterItems
        {
            get
            {
                return _OuterItems;
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            _OuterItems.Add(System.IO.Path.GetRandomFileName());
        }
    }
}

答案 1 :(得分:1)

如果您有集合类型依赖项属性,则不得将集合类的实例用作属性的默认值。这样做会使拥有该属性的控件的所有实例使用相同的集合实例。

所以你的属性元数据

new PropertyMetadata(new ObservableCollection<string>())

应替换为

new PropertyMetadata(null)

或者您根本没有指定任何元数据

public static DependencyProperty InnerItemsProperty =
    DependencyProperty.Register(
        "InnerItems", typeof(ObservableCollection<string>), typeof(InnerControl));

现在你必须以某种方式初始化属性值。像往常一样,你将在控件的构造函数中执行它,比如

public InnerControl()
{
    InnerItems = new ObservableCollection<string>();
}

现在绑定控件的属性,如

<local:InnerControl InnerItems="{Binding ...}" />

构造函数中设置的值将替换为Binding生成的值。

但是,在样式设置器中创建绑定时不会发生这种情况,因为样式设置器中的值的优先级低于所谓的本地值(请参阅Dependency Property Value Precedence)。

解决方法是通过DependencyObject.SetCurrentValue()方法设置默认值,该方法不设置本地值:

public InnerControl()
{
    SetCurrentValue(InnerItemsProperty, new ObservableCollection<string>()); 
}