【问题标题】:Listbox DataTemplate to be applied to different models应用于不同模型的 Listbox DataTemplate
【发布时间】:2021-04-22 21:57:23
【问题描述】:

我有两个表,其中也只包含一个作为键的值。 我创建了一个列表框来显示和修改它。

  • 表1为TTypes1,字段为Type1String
  • 表2为TTypes2,字段为Type2字符串

我已经写了这个DataTemplate

<DataTemplate x:Key="ListboxItems">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <Label Grid.Column="0" Content="{Binding}" />
        <StackPanel Grid.Column="1" Orientation="Horizontal">
            <!--  edit to swap with save  -->
            <Button
                Content="&#xE74E;"
                ContentTemplate="{StaticResource IconPanelButton}"
                DockPanel.Dock="Right"
                Style="{StaticResource MahApps.Styles.Button.Flat}"
                ToolTipService.ToolTip="Save"
                Visibility="{Binding IsVisible}" />
            <!--  Cancel - visible only on edit  -->
            <Button
                Click="LockUnlock_Click"
                Content="{Binding Icon}"
                ContentTemplate="{StaticResource IconPanelButton}"
                DockPanel.Dock="Right"
                Style="{StaticResource MahApps.Styles.Button.Flat}"
                ToolTipService.ToolTip="{Binding ToolTip}" />
            <!--  Delete  -->
            <Button
                Click="LockUnlock_Click"
                Content="{Binding Icon}"
                ContentTemplate="{StaticResource IconPanelButton}"
                DockPanel.Dock="Right"
                Style="{StaticResource MahApps.Styles.Button.Flat}"
                ToolTipService.ToolTip="{Binding ToolTip}" />
            <!--  Add  -->
            <Button
                Click="LockUnlock_Click"
                Content="{Binding Icon}"
                ContentTemplate="{StaticResource IconPanelButton}"
                DockPanel.Dock="Right"
                Style="{StaticResource MahApps.Styles.Button.Flat}"
                ToolTipService.ToolTip="{Binding ToolTip}" />
        </StackPanel>
    </Grid>
</DataTemplate>

这里是列表框,但我无法让它按我想要的方式工作。

如果我这样离开:

<Label Grid.Column="0" Content="{Binding}" />

我没有看到文字,而是TTypes1TTypes2 类型。

但如果我写:

<Label Grid.Column="0" Content="{Binding Type1}" />

那我就不能在TType2列表框上使用了。

这是我使用它的地方:

<ScrollViewer
    Margin="2"
    HorizontalScrollBarVisibility="Auto"
    VerticalScrollBarVisibility="Auto">
    <ListBox
        Margin="2"
        AlternationCount="2"
        BorderThickness="1"
        ItemsSource="{Binding TTypes1}"
        SelectedIndex="0"
        SelectionMode="Single"
        ItemTemplate="{StaticResource ListboxItems}"
        Style="{StaticResource MahApps.Styles.ListBox.Virtualized}">
    </ListBox>
</ScrollViewer>

第二个是:

<ScrollViewer
    Margin="2"
    HorizontalScrollBarVisibility="Auto"
    VerticalScrollBarVisibility="Auto">
    <ListBox
        Margin="2"
        AlternationCount="2"
        BorderThickness="1"
        ItemsSource="{Binding TTypes2}"
        SelectedIndex="0"
        SelectionMode="Single"
        ItemTemplate="{StaticResource ListboxItems}"
        Style="{StaticResource MahApps.Styles.ListBox.Virtualized}">
    </ListBox>
</ScrollViewer>

我错过了什么?

【问题讨论】:

    标签: wpf xaml .net-core data-binding datatemplate


    【解决方案1】:

    多个数据模板

    处理这种情况的常用方法是为每种类型创建一个不同的数据模板,例如对于TType1TType2

    <DataTemplate x:Key="ListboxItemsTType1"
                  DataType="{x:Type local:TType1}">
       <Grid>
          <Grid.ColumnDefinitions>
             <ColumnDefinition Width="*" />
             <ColumnDefinition Width="Auto" />
          </Grid.ColumnDefinitions>
    
          <Label Grid.Column="0"
                 Content="{Binding Type1}" />
          <!-- ...other markup. -->
       </Grid>
    </DataTemplate>
    
    <DataTemplate x:Key="ListboxItemsTType2"
                  DataType="{x:Type local:TType2}">
       <Grid>
          <Grid.ColumnDefinitions>
             <ColumnDefinition Width="*" />
             <ColumnDefinition Width="Auto" />
          </Grid.ColumnDefinitions>
    
          <Label Grid.Column="0"
                 Content="{Binding Type2}" />
          <!-- ...other markup. -->
       </Grid>
    </DataTemplate>
    

    参考您的ListBoxes 中的特定模板。您还可以从数据模板中删除x:Key,以便它们自动应用于ListBox 中的匹配类型。这也适用于列表中的混合项目。

    <ScrollViewer Grid.Row="0"
                  Margin="2"
                  HorizontalScrollBarVisibility="Auto"
                  VerticalScrollBarVisibility="Auto">
       <ListBox
          ItemTemplate="{StaticResource ListboxItems}"
          ...
       </ListBox>
    </ScrollViewer>
    
    <ScrollViewer Grid.Row="1"
                  Margin="2"
                  HorizontalScrollBarVisibility="Auto"
                  VerticalScrollBarVisibility="Auto">
       <ListBox
          ...
          ItemTemplate="{StaticResource ListboxItems}"
       </ListBox>
    </ScrollViewer>
    

    其他方法

    如果您真的想保留单个数据模板,则必须根据绑定为数据上下文的对象的项目类型来切换绑定。有多种方法可以实现这一目标。

    这是一个使用来自related questionconverter that converts an object to its type 的示例,复制它。 Label 的样式将使用数据触发器根据该类型应用正确的绑定。

    <local:DataTypeConverter x:Key="DataTypeConverter" />
    
    <DataTemplate x:Key="ListboxItems">
       <Grid>
          <Grid.ColumnDefinitions>
             <ColumnDefinition Width="*" />
             <ColumnDefinition Width="Auto" />
          </Grid.ColumnDefinitions>
    
          <Label Grid.Column="0">
             <Label.Style>
                <Style TargetType="{x:Type Label}"
                       BasedOn="{StaticResource {x:Type Label}}">
                   <Setter Property="Content"
                           Value="{x:Null}" />
                   <Style.Triggers>
                      <DataTrigger Binding="{Binding Converter={StaticResource DataTypeConverter}}"
                                   Value="{x:Type local:TType1}">
                         <Setter Property="Content"
                                 Value="{Binding Type1}" />
                      </DataTrigger>
                      <DataTrigger Binding="{Binding Converter={StaticResource DataTypeConverter}}"
                                   Value="{x:Type local:TType2}">
                         <Setter Property="Content"
                                 Value="{Binding Type2}" />
                      </DataTrigger>
                   </Style.Triggers>
                </Style>
             </Label.Style>
          </Label>
          <StackPanel Grid.Column="1"
                      Orientation="Horizontal">
             <!--  edit to swap with save  -->
             <Button Content="&#xE74E;"
                     ContentTemplate="{StaticResource IconPanelButton}"
                     DockPanel.Dock="Right"
                     Style="{StaticResource MahApps.Styles.Button.Flat}"
                     ToolTipService.ToolTip="Save"
                     Visibility="{Binding IsVisible}" />
             <!--  Cancel - visible only on edit  -->
             <Button Click="LockUnlock_Click"
                     Content="{Binding Icon}"
                     ContentTemplate="{StaticResource IconPanelButton}"
                     DockPanel.Dock="Right"
                     Style="{StaticResource MahApps.Styles.Button.Flat}"
                     ToolTipService.ToolTip="{Binding ToolTip}" />
             <!--  Delete  -->
             <Button Click="LockUnlock_Click"
                     Content="{Binding Icon}"
                     ContentTemplate="{StaticResource IconPanelButton}"
                     DockPanel.Dock="Right"
                     Style="{StaticResource MahApps.Styles.Button.Flat}"
                     ToolTipService.ToolTip="{Binding ToolTip}" />
             <!--  Add  -->
             <Button Click="LockUnlock_Click"
                     Content="{Binding Icon}"
                     ContentTemplate="{StaticResource IconPanelButton}"
                     DockPanel.Dock="Right"
                     Style="{StaticResource MahApps.Styles.Button.Flat}"
                     ToolTipService.ToolTip="{Binding ToolTip}" />
          </StackPanel>
       </Grid>
    </DataTemplate>
    

    其他完全依赖代码但更容易重用的选项是:

    • 创建一个与触发器相同的特殊值转换器,返回在代码中创建的绑定,其属性路径基于类型
    • 创建自定义标记扩展,根据类型自动选择属性路径

    我没有提供这些选项的示例,因为它们很复杂并且很大程度上取决于您的要求。此外,我推荐第一种方法来创建多个数据模板,因为我认为从维护和灵活性的角度来看这是最有利的。

    【讨论】:

      【解决方案2】:

      如果您喜欢两个数据模板,我认为最好使用 ItemsControl.ItemTemplateSelector。 首先,您需要一个类继承类“DataTemplateSelector”并覆盖其方法以选择要使用的数据模板。

      public class ModelItemTemplateSelector: DataTemplateSelector
      {
          public DataTemplate Model1Template { get; set; }
          public DataTemplate Model2Template { get; set; }
          public override DataTemplate SelectTemplate(object item, DependencyObject container)
          {
              if(item is Model1)
              {
                  return Model1Template;
              }
              else if(item is Model2)
              {
                  return Model2Template;
              }
              return base.SelectTemplate(item, container);
          }
      }
      

      下面是xaml中的代码

       <ListBox ItemsSource="{Binding Source}">
              <ListBox.ItemTemplateSelector>
                  <local:ModelItemTemplateSelector Model1Template="{StaticResource Model1Template}" Model2Template="{StaticResource Model2Template}" />
              </ListBox.ItemTemplateSelector>
          </ListBox>
      

      还有其他代码:

      两个数据模板

       <DataTemplate x:Key="Model1Template" DataType="{x:Type local:Model1}">
              <TextBlock Text="{Binding Age}" />
          </DataTemplate>
          <DataTemplate x:Key="Model2Template" DataType="{x:Type local:Model2}">
              <TextBlock Text="{Binding Name}" />
          </DataTemplate>
      

      两种类型

      public class BaseModel : INotifyPropertyChanged
      {
      
          public event PropertyChangedEventHandler PropertyChanged;
          public void RaisePropertyChanged(string propertyName)
          {
              this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
          }
      }
      public class Model1 : BaseModel
      {
          private int age;
      
          public int Age
          {
              get { return age; }
              set
              {
                  age = value;
                  this.RaisePropertyChanged(nameof(Age));
              }
          }
      
      }
      
      public class Model2 : BaseModel
      {
          private string name;
      
          public string Name
          {
              get { return name; }
              set
              {
                  name = value;
                  this.RaisePropertyChanged(nameof(Name));
              }
          }
      
      }
      

      vm中的来源

       private ObservableCollection<BaseModel> source;
      
          public ObservableCollection<BaseModel> Source
          {
              get { return source; }
              set
              {
                  source = value;
                  this.RaisePropertyChanged(nameof(Source));
              }
          }
      

      【讨论】:

      • 如果您想为同一项目类的不同实例或其他更复杂的场景使用不同的 DataTemplate,通常只会使用 DataTemplateSelector。对于不同的项目类,通过数据类型自动选择不同的数据模板要简单得多。由于您已经在示例中设置了 DataTemplates 的 DataType,因此您的 ModelItemTemplateSelector 类是完全多余的。它所做的一切都已在 WPF 中实现,只需从 DataTemplates 中删除 x:Key 属性。
      • @Clemens 是的,你是对的,谢谢你的话。
      猜你喜欢
      • 1970-01-01
      • 2016-09-03
      • 2013-01-27
      • 1970-01-01
      • 2011-07-13
      • 2016-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多