Data绑定DataTemplate中的ItemsControl

时间:2016-06-27 13:35:58

标签: c# wpf data-binding datatemplate

我似乎有一个简单的数据绑定问题,但无法找到正确的方法。有TabControl定义了两个DataTemplate,一个用于标题页,一个用于标签内容。

内容模板包含ItemsControl。 ItemsControl尝试绑定到动态创建的ViewModel(ConnectionInfoVM)。

当我显示UI时,绑定失败,但输出中没有关于它的错误消息。

如何设置DataContext和绑定以便绑定正常工作并实际显示DataBuffer?任何帮助非常感谢。

ConnectionsControl:

<UserControl x:Class="XXXViewer.Views.ConnectionsControl"
             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"
             xmlns:viewModels="clr-namespace:XXXViewer.ViewModels"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid> 
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TabControl Grid.Row="0" Name="TabDynamic" SelectionChanged="tabDynamic_SelectionChanged">
            <TabControl.Resources>
                <DataTemplate x:Key="TabHeader" DataType="TabItem">
                    <DockPanel>
                        <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
                        <Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnTabDelete_Click" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
                            <Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
                        </Button>
                    </DockPanel>
                </DataTemplate>

                <DataTemplate x:Key="TabContent" DataType="viewModels:ConnectionInfoVM">
                    <StackPanel>
                        <ScrollViewer Name="Scroller" Background="Black">
                            <StackPanel>
                                <TextBlock Text="This line gets printed" Foreground="White" FontFamily="Consolas"/>
                                <ItemsControl Name="ItemCtrl" ItemsSource="{Binding DataBuffer}">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </StackPanel>
                        </ScrollViewer>
                    </StackPanel>
                </DataTemplate>

            </TabControl.Resources>
        </TabControl>
    </Grid>
</UserControl>

后面的ConnectionsControl代码:

namespace XXXViewer.Views
{
    public partial class ConnectionsControl : UserControl
    {
        private readonly ObservableCollection<TabItem> _tabItems = new ObservableCollection<TabItem>();

        public ConnectionsControl()
        {
            InitializeComponent();

            // bindings
            TabDynamic.ItemsSource = _tabItems;
            TabDynamic.DataContext = this;
        }

        // assume this gets called
        private void AddTabItem(ConnectionInfoVM ci)
        {
            DataTemplate headerTemplate = TabDynamic.FindResource("TabHeader") as DataTemplate;
            DataTemplate contentTemplate = TabDynamic.FindResource("TabContent") as DataTemplate;

            // create new tab item
            TabItem tab = new TabItem
            {
                Header = $"Tab {ci.ConnectionID}",
                Name = $"T{ci.ConnectionID}",
                HeaderTemplate = headerTemplate,
                ContentTemplate = contentTemplate,
                DataContext = ci
            };

            _tabItems.Insert(0, tab);

            // set the new tab as active tab
            TabDynamic.SelectedItem = tab;
        }
    }
}

ConnectionInfoVM:

namespace XXXViewer.ViewModels
{
    public class ConnectionInfoVM : ViewModelBase
    {
        private readonly ObservableQueue<string> _dataBuffer = new ObservableQueue<string>();
        public ObservableQueue<string> DataBuffer => _dataBuffer;
    }
}

创建的标签的屏幕截图: resulting tab

2 个答案:

答案 0 :(得分:0)

根据您的编码,Tabcontrol不包含DataContext视图模型,而是控件;所以我们需要找到一个控件或其他容纳VM的东西。页面似乎不会将VM保留在DataContext中。

我建议一条路由是使用TabControl的Tag属性来保存VM,例如在后面的代码中指定它:

TabDynamic.ItemsSource = _tabItems;
TabDynamic.DataContext = this;
TabDynamic.Tag = {Wherever you are keeping your VM at this time its not clear in your code example};

然后,您可以通过指定TabControls的名称从模板绑定中指定Tag

 <ItemsControl Name="ItemCtrl" 
                     ItemsSource="{Binding Tag.DataBuffer, 
                                           ElementName=TabDynamic}">

答案 1 :(得分:0)

您设置了ContentTemplate但从不设置Content,因此从不应用ContentTemplate,因为它仅在设置了内容时才应用。而不是DataContext = ciContent = ci

顺便说一句DataContext = ci没用,因为DataContext已经隐含了应用DataTemplate的对象。

修改

当您使用WPF时,请使用和滥用其核心功能:绑定。

我如何编写代码(如果我没有使用完整的MVVM兼容代码):

您的XAML:

<TabControl Grid.Row="0" Name="TabDynamic" 
            ItemsSource="{Binding TabItems, Mode=OneWay}" 
            SelectionChanged="tabDynamic_SelectionChanged">
    <TabControl.Resources>
        <DataTemplate x:Key="TabHeader" DataType="TabItem">
            <DockPanel>
                <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
                <Button Name="btnDelete" DockPanel.Dock="Right" Margin="5,0,0,0" Padding="0" Click="btnTabDelete_Click" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Name}">
                    <Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
                </Button>
            </DockPanel>
        </DataTemplate>
    </TabControl.Resources>
    <TabControl.ItemTemplate>
        <DataTemplate DataType="viewModels:ConnectionInfoVM">
            <TabItem Header="{Binding ConnectionID, Mode=OneWay}"
                     Name="{Binding ConnectionID, Mode=OneWay}"
                     HeaderTemplate="{StaticResources TabHeader}">
                <StackPanel>
                    <ScrollViewer Name="Scroller" Background="Black">
                        <StackPanel>
                            <TextBlock Text="This line gets printed" Foreground="White" FontFamily="Consolas"/>
                            <ItemsControl Name="ItemCtrl" ItemsSource="{Binding DataBuffer}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </StackPanel>
                    </ScrollViewer>
                </StackPanel>
            </TabItem>

        </DataTemplate>
    </TabControl.ItemTemplate>
</TabControl>

你的代码变得更加简单:

namespace XXXViewer.Views
{
    public partial class ConnectionsControl : UserControl
    {
        private readonly ObservableCollection<ConnectionInfoVM> _tabItems = new ObservableCollection<ConnectionInfoVM>();
        public ObservableCollection<ConnectionInfoVM> TabItems {get {return _tabItems;}}

        public ConnectionsControl()
        {
            InitializeComponent();

            // bindings
            //TabDynamic.ItemsSource = _tabItems;
            TabDynamic.DataContext = this;
        }

        // assume this gets called
        private void AddTabItem(ConnectionInfoVM ci)
        {
            TabItems.Add(ci);
        }
    }
}

我在重新阅读您的代码时注意到您可能对代码隐藏中的绑定感到困惑。

您的代码TabDynamic.ItemsSource = _tabItems;不是绑定,只会设置一次。

无论如何,我建议你阅读一些关于MVVM的内容。 TabItems应该在ViewModel类中,而不是在代码隐藏中。