如何处理包含列表的WPF用户控件中的嵌套绑定?

时间:2017-03-01 15:05:44

标签: c# wpf xaml data-binding binding

我正在尝试在WPF中创建一个包含内容集合的usercontrol,并努力使绑定在子元素上正常工作。我已经看过这个例子了,但我更深层次地嵌套了这个,所以这个解决方案没有帮助:Data Binding in WPF User Controls

这是我的UserControl:

<UserControl x:Class="BindingTest.MyList"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="uc">
    <Grid>
        <ListView Grid.Row="1" ItemsSource="{Binding Items, ElementName=uc}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Label Content="{Binding Text}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</UserControl>

代码隐藏:

[ContentProperty("Items")]
public partial class MyList : UserControl {
    public MyList() {
        InitializeComponent();
    }

    public ObservableCollection<MyItem> Items { get; set; } = new ObservableCollection<MyItem>();
}

public class MyItem : DependencyObject {
    public string Text {
        get {
            return (string)this.GetValue(TextProperty);
        }
        set {
            this.SetValue(TextProperty, value);
        }
    }
    public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MyItem), new PropertyMetadata());
}

然后我在应用程序中如何使用它:

<Window x:Class="BindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:BindingTest"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TextBox Grid.Row="0" Text="{Binding Val1, Mode=TwoWay}" />

        <local:MyList Grid.Row="1">
            <local:MyItem Text="{Binding Val1}" />
            <local:MyItem Text="Two" />
            <local:MyItem Text="Three" />
        </local:MyList>

        <ListView Grid.Row="2">
            <ListViewItem Content="{Binding Val1}" />
            <ListViewItem Content="Bravo" />
            <ListViewItem Content="Charlie" />
        </ListView>
    </Grid>
</Window>

最后我的窗口:

public partial class MainWindow : Window, INotifyPropertyChanged {
    public MainWindow() {
        InitializeComponent();
    }

    string _val1 = "Foo";
    public string Val1 {
        get { return _val1; }
        set {
            _val1 = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Val1"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

我遇到的问题是用户控件中的第一个“MyItem”最终显示空白文本,而库存控件中的第一个ListViewItem显示Foo,如你所料。

我理解这是因为在MyList控件中,DataTemplate为集合中的每个项切换DataContext,但无论我在绑定表达式中尝试什么,无论是在窗口还是用户控件中,我都无法得到它正确绑定到窗口上的Val1。

我做错了什么?

2 个答案:

答案 0 :(得分:0)

你的MyItem对象的DataContext没有要绑定的Val1值以及绑定中断的原因是正确的。这是您在DataGrids中常见的问题,其中内部项(MyItem)的上下文设置为ItemSource的元素(在您的情况下是ObservableCollection项)。

我总是遇到问题的方法是在DataGrid或Control的资源中包含一个对象,该对象具有对控件的DataContext的引用。这样您就可以将该对象绑定为StaticResource,并且不必更改Control中的XAML。

包含数据上下文的类:

 public class BindingProxy : Freezable
{
    protected override Freezable CreateInstanceCore() { return new BindingProxy(); }

    public static readonly DependencyProperty ContextProperty =
        DependencyProperty.Register("Context", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));

    public object Context
    {
        get { return (object)GetValue(ContextProperty); }
        set { SetValue(ContextProperty, value); }
    }
}

然后将BindingProxy的实例添加到MyList的资源中,并将Context属性绑定到MyList的绑定。然后,您可以按名称绑定到BindingProxy作为StaticResource。 Path是资源的Context属性,然后是要绑定到的值的正常路径。

<local:MyList Grid.Row="1">
        <local:MyList.Resources>
            <local:BindingProxy x:Key="VMProxy" Context="{Binding}"/>
        </local:MyList.Resources>
        <local:MyItem Text="{Binding Path=Context.Val1, Source={StaticResource VMProxy}}"/>
        <local:MyItem Text="Two" />
        <local:MyItem Text="Three" />
    </local:MyList>

我用你拥有的控件和窗口代码测试它,它就像一个魅力。 我不能完全赞同这个解决方案,因为我在这里找到了它,但对于我的生活,我找不到具有DataGrids解决方案的旧答案。

答案 1 :(得分:0)

实际上,我找到了一种方法来使这项工作正常进行。我没有进行UserControl,而是切换到ItemsControl,并将“MyItem”类更改为子类ContentControl。然后WPF负责为您设置数据上下文。您可以为ItemsControl指定新模板,以避免Grid在列表中显示为自己的项目。

<ItemsControl x:Class="BindingTest.MyList"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300"
                 x:Name="uc">
    <ItemsControl.Template>
        <ControlTemplate>
            <Grid>
                <ListView ItemsSource="{Binding Items, ElementName=uc}">
                    <ListView.Resources>
                        <Style TargetType="ListViewItem">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="ListViewItem">
                                        <Label Foreground="Blue" Content="{Binding Path=Text}" />
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </ListView.Resources>
                </ListView>
            </Grid>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>

public partial class MyList : ItemsControl {
    public MyList() {
        InitializeComponent();
    }
}

public class MyItem : ContentControl {
    public string Text {
        get {
            return (string)this.GetValue(TextProperty);
        }
        set {
            this.SetValue(TextProperty, value);
        }
    }
    public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MyItem), new PropertyMetadata());
}