ListBox中的DataTemplate作为UserControl

27

我希望可以像DataTemplates一样在其他UserControls中重复使用我的UserControl,比如在页面或窗口中。这里的例子是在一个ListBox中。整个应用程序都遵循MVVM模式。

我有一个名为“CardControl”的UserControl来显示一个简单的对象“Card”。Card有两个属性:“ID”和“CardImage”。控件的DataContext是通过XAML设置的。如果我在VS或Blend中打开此UserControl,它会显示我在对应的ViewModel中定义的虚拟Card。

现在我有另一个名为“CardSetControl”的UserControl,它应该显示一组Cards。因此,ViewModel有一个类型为ObservableCollection<Card>的属性,名为“Cards”。

以下是代码:

<ListBox x:Name="MyList" ItemsSource="{Binding CardSet.Cards}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel>

        <!-- WORKING, but not what i want -->
        <TextBlock Text="{Binding ID}" /> // would display ID of Card
        <Image Source="{Binding Image}" /> // would display Image of Card

        <!-- NOT WORKING, but this is how i want it to work -->
        <UserControls:CardControl DataContext="{Binding "Current listbox item as DataContext of CardControl???"}" />

      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

在阅读了大量关于MVVM、DataContext/Binding的文章后,我仍然无法使其工作。如何以最好的清晰方式完成整个层次结构的UserControls/DataContexts?

4个回答

24
在你的例子中,UserControlDataContext 将是当前选定的卡片。它像其他任何UIElement 一样流入UserControl 及其子控件,并接收其父控件的DataContext
这将起作用:
<ListBox x:Name="MyList" ItemsSource="{Binding CardSet.Cards}">
  <ListBox.ItemTemplate>
    <DataTemplate>
        <UserControls:CardControl />
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

CardControl 是什么:

<UserControl x:Class="MySolution.CardControl" 
             OtherProperties="Not shown to keep this example small">
      <StackPanel>
        <TextBlock Text="{Binding ID}" /> 
        <Image Source="{Binding Image}" />
      </StackPanel>
</UserControl>

Will,抱歉,我基本上给了和你完全相同的答案。下次在回答之前我应该刷新页面 ;) - Michael Brown
1
这样,一个模型(Card)作为DataContext传递给用户控件。如果我想让该用户控件使用其视图模型怎么办?我应该如何将接收到的模型传递给视图模型并将视图模型绑定到控件的视图? - Ondrej Janacek
1
@OndrejJanacek:UserControls 不应该被设计为拥有自己的视图模型。它们应该在其表面上具有公共可绑定属性,用户将绑定到自己的视图模型。这个答案更多地是针对 OP 而不是最佳实践 :/ - user1228
1
@Will 非常有见地的信息,谢谢。 - Ondrej Janacek

19

对于ListBox控件,每个项源中的项都会创建一个推断的ListBoxItem。该项被设置为数据上下文(DataContext),您的ItemTemplate则被设置为模板。由于DataContext具有继承性,因此您不必显式设置它,因为它已经是DataTemplate中Card实例的数据上下文。

对于这种情况,您不需要在CardControl上设置DC,因为它已经为您设置好了。

<ListBox x:Name="MyList" ItemsSource="{Binding CardSet.Cards}"> 
  <ListBox.ItemTemplate> 
    <DataTemplate> 
      <StackPanel> 

        <!-- WORKING, but not what i want --> 
        <TextBlock Text="{Binding ID}" /> // would display ID of Card 
        <Image Source="{Binding Image}" /> // would display Image of Card 

        <!-- NOT WORKING, but this is how i want it to work --> 
        <UserControls:CardControl /> 

      </StackPanel> 
    </DataTemplate> 
  </ListBox.ItemTemplate> 
</ListBox> 

应该适用于你。


1

谢谢你的回答,但我发现我的问题与我提到的不同。如果按照你描述的方法操作,我可以看到我的用户控件(CardControl)被用作ListBox项目的模板,并且ID和图像显示正确。

除此之外,我一直想知道为什么可以显示ID和图像,而我无法绑定到ViewModel中定义的其他属性。

今天我发现了一篇有关DataContext层次结构的有趣文章。在那里它说,在ListBox中的DataContext与ListBox所在的页面上的DataContext不是相同的。我以前没有注意到这一点,所以我认为我必须以某种方式设置DataContext,就像我在问题中提到的那样。现在我可以绑定到所有属性了。

以下是该文章链接: http://blog.thekieners.com/2010/09/08/relativesource-binding-with-findancestor-mode-in-silverlight/


遗憾的是,你提到的网站可能并不出乎意料地已经不存在了。 - naskew

0

我曾经遇到类似的问题,我使用以下代码解决了它。我在这里发布,希望能对其他人有所帮助。

如果我的回答对你有帮助,请不要忘记点赞。

EmpUserControl.xaml文件

<UserControl x:Class="Sample.UserControls.EmpUserControl"
             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:uc="clr-namespace:Sample.UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"></RowDefinition>
            <RowDefinition Height="20"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0">First Name: </Label>
        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding FirstName}" />
        <Label Grid.Row="1" Grid.Column="0">Last Name: </Label>
        <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding LastName}" />
    </Grid>
</UserControl>

EmpUserControl.xaml.cs

namespace Sample.UserControls
{
    /// <summary>
    /// Interaction logic for EmpUserControl.xaml
    /// </summary>
    public partial class EmpUserControl : UserControl
    {
        public EmpUserControl()
        {
            InitializeComponent();
        }
    }
}

MainWindow.xaml 文件

<Window x:Class="Sample.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:local="clr-namespace:Sample"
        xmlns:uc="clr-namespace:Sample.UserControls" 
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="900" WindowStyle="SingleBorderWindow" 
        ResizeMode="NoResize">
    <Grid>
        <ListBox Name="LbEmp" Width="220" Height="220" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <uc:EmpUserControl DataContext="{Binding}"></uc:EmpUserControl>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

MainWindow.xaml.cs 文件

namespace Sample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void LoadEmpListBox(){
            var empList = new List<EmpViewModel>{
                new EmpViewModel { FirstName = "Rajeev", LastName = "Kumar" },
                new EmpViewModel { FirstName = "Sita", LastName = "Hedge" },
                new EmpViewModel { FirstName = "Deepika", LastName = "PL" }
            };
            LbEmp.ItemsSource = empList;
        }
    }
}

EmpViewModel 模型

public class EmpViewModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

工作正常,关键是在DataTemplate中为UserControl设置DataContext。 - CodingNinja
p2: 但这会带来另一个问题,解决方案在这里:https://dev59.com/T2445IYBdhLWcg3wBVzz - CodingNinja

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接