0

I have two tables that contain just one value that is the key, too. I created a list box to show it and modify it.

  • Table 1 is TTypes1 and the field is Type1 String
  • Table 2 is TTypes2 and the field is Type2 String

I've written this 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>

Here are the list boxes but I'm not able to get it working as I want.

If I leave it like this:

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

I do not see the text but the type TTypes1 or TTypes2.

But if I write:

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

Then I cannot use it on TType2 list box.

Here is where I use it:

<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>

and the second one is:

<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>

What am I missing?

thatguy
  • 21,059
  • 6
  • 30
  • 40

2 Answers2

1

Multiple Data Templates

The usual way to handle this is to create one distinct data template per type, e.g. for TType1 and TType2.

<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>

Reference the specific templates in your ListBoxes. You can also remove the x:Key from the data templates, so they are automatically applied to a matching type in the ListBox. This also works with mixed items in a list.

<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>

Other Methods

If you really want to keep a single data template, you will have to switch the binding depending on the item type of the object bound as data context. There are multiple ways to achieve this.

Here is an example that uses a converter that converts an object to its type from a related question, copy it. A style for Label will use data triggers to apply the correct binding based on that type.

<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>

Other options that completely rely on code, but are easier to reuse are:

  • Create a special value converter that does the same as the triggers, return a binding created in code with a property path that is based on type
  • Create a custom markup extension that automatically chooses the property path based on type

I do not provide examples on these options as they are complex and heavily depend on your requirements. Furthermore, I recommend the first approach to create multiple data templates, as this is the most favorable from a perspective of maintenance and flexibility in my opinion.

thatguy
  • 21,059
  • 6
  • 30
  • 40
0

I think it is better to use ItemsControl.ItemTemplateSelector if you like two datatemplates. First you need one class inherit class "DataTemplateSelector" and override its method to select which datatemplate to use.

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);
    }
}

Then code in xaml is below

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

And the other code:

Two datatemplates

 <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>

Two types

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));
        }
    }

}

Source in vm

 private ObservableCollection<BaseModel> source;

    public ObservableCollection<BaseModel> Source
    {
        get { return source; }
        set
        {
            source = value;
            this.RaisePropertyChanged(nameof(Source));
        }
    }
Kun Ma
  • 299
  • 1
  • 6
  • 1
    You would usually only use a DataTemplateSelector if you would want to use different DataTemplates for different instances of the same item class, or other, more complex scenarios. For different item classes it is much simpler to auto-select different DataTemplates by their DataType. Since you are already setting the DataType of the DataTemplates in your example, your ModelItemTemplateSelector class is entirely redundant. Everything it does is already implemented in WPF, just remove the x:Key attributes from the DataTemplates. – Clemens Jan 18 '21 at 21:34
  • @Clemens Yes, you are right, thanks for your words. – Kun Ma Jan 19 '21 at 04:31