Search code examples
c#wpf

How to add a placeholder item to an ItemsControl?


How can I add a custom text at the end to a listbox without adding it to the apples collection using ItemsSource?

e.g.
Listbox:
Listbox Item1-Apple
Listbox Item2-Apple
Listbox Item3-Apple.. Could be more or less Apple the last item should say "ADD NEW..."
Listbox Item4-ADD NEW...

XAML:

<Grid>
    <ListBox Name="lbxFruits" Margin="0,0,70,52">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation = "vertical" Background="Green">
                    <Label>Hello</Label>
                    <TextBlock Text = "{Binding Price, ElementName=lbxFruits}" Width = "14" />

                    <TextBlock Text = "{Binding Name, ElementName=lbxFruits}" />

                </StackPanel >
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <Button Content="Button" HorizontalAlignment="Left" Margin="690,404,0,0" VerticalAlignment="Top" Click="Button_Click"/>
</Grid>

C#:

private void RebuildList()
{
    ListBoxItem addItem = new ListBoxItem() { Content = "ADD NEW ..." };
    lbxFruits.ItemsSource = apples;
}

private ObservableCollection<Fruit> apples ;
public ObservableCollection<Fruit> Apples
{
    get
    {
        return this.apples;
    }

    set
    {
        if (value != this.apples)
        {
            this.apples = value;
            NotifyPropertyChanged();
        }
    }
}

Solution

  • You can use the placeholder feature of the CollectionView: it will handle the positioning of the placeholder item automatically (for example pin it at the end or beginning). When iterating over the collection, this placeholder item won't appear, thus won't pollute your data structure.

    The big advantage is that since focusing on the collection view, you don't have to modify existing data models and their related logic.

    You enable the placeholder item by setting the IEditableCollectionView.NewItemPlaceholderPosition property to either NewItemPlaceholderPosition.AtBeginning or NewItemPlaceholderPosition.AtEnd. Common collections that implement IList (for example ObservableCollection) are represented by the ListCollectionView which implements IEditableCollectionView.
    After enabling the placeholder feature, the collection view of the underlying source collection will now contain the static CollectionView.PlaceholderItem.

    You can then create a dedicated DataTemplate for the CollectionView.NewItemPlaceholder which is of type object (the underlying type is defined internal and therefore not accessible for client code of the .NET library).

    A custom DataTemplateSelector will then identify this placeholder item to return the appropriate DataTemplate.

    The DataTemplate for the placeholder item contains a Button that allows to add a new item on click (using an ICommand or event handler) and to display the placeholder item's text.

    FruitTemplateSelector.cs

    public class FruitTemplateSelector : DataTemplateSelector
    {
      /* Add more template properties in case you have more data types */
    
      public DataTemplate AppleTemplate { get; set; }
      public DataTemplate PlaceholderTemplate { get; set; }
    
      public override DataTemplate SelectTemplate(object item, DependencyObject container) => item switch
      {
        var dataItem when dataItem == CollectionView.NewItemPlaceholder => this.PlaceholderTemplate,
        Apple _ => this.AppleTemplate,
        _ => base.SelectTemplate(item, container),
      };
    }
    

    MainWindow.xaml.cs

    partial class MainWindow : Window
    {
      public ObservableCollection<string> TextItems { get; }
    
      public MainWindow()
      {
        InitializeComponent();
        this.DataContext = this;
     
        this.TextItems = new ObservableCollection<string>
        {
          "Item #1",
          "Item #2",
          "Item #3"
        };
    
        // Get the default collection view of the source collection
        ICollectionView textItemsView = CollectionViewSource.GetDefaultView(this.TextItems);
    
        // Enable the placeholder item 
        // and place it at the end of the collection view
        IEditableCollectionView editableCollectionView = textItemsView as IEditableCollectionView;
        editableCollectionView.NewItemPlaceholderPosition = NewItemPlaceholderPosition.AtEnd;
      }
    
      private void AddNewItem_OnClick(object sender, EventArgs e)
        => this.TextItems.Add($"Item #{this.TextItems.Count + 1}";
    }
    

    MainWindow.xaml

    <Window xmlns:sys="clr-namespace:System;assembly=mscorlib">
      <ListBox ItemsSource="{Binding TextItems}"
               HorizontalContentAlignment="Stretch">
        <ListBox.ItemTemplateSelector>
          <local:FruitTemplateSelector>
            <local:FruitTemplateSelector.AppleTemplate>
              <DataTemplate DataType="{x:Type sys:String}">
                <TextBlock Text="{Binding}" />
              </DataTemplate>
            </local:FruitTemplateSelector.AppleTemplate>
    
            <local:FruitTemplateSelector.PlaceholderTemplate>
              <DataTemplate>
                <Button Content="Add New Item..."
                        Click="AddNewItem_OnClick"
                        Background="Orange" />
              </DataTemplate>
            </local:FruitTemplateSelector.PlaceholderTemplate>
          </local:FruitTemplateSelector>
        </ListBox.ItemTemplateSelector>
      </ListBox>
    </Window>