Use of ItemsControl in WPF C# for Flexible UI Design

Introduction

ItemsControl is a control within the Windows Presentation Foundation (WPF) framework that facilitates the display of a collection of items, which may include data objects or user interface elements. It acts as a foundational class for various other item-oriented controls, such as ListBox, ListView, ComboBox, TreeView, and Menu. Notably, ItemsControl is a more versatile control that does not inherently include specific features for selection, scrolling, or other interactions. Rather, it offers a customizable approach to organizing items through tailored layouts and templates.

Key Properties

  1. ItemsSource: Establishes a connection between the control and a collection of items, which may consist of strings, objects, or any other data source.
  2. ItemTemplate: Determines the visual representation of each item within the collection by utilizing a DataTemplate. For instance, when dealing with custom objects, ItemTemplate can be employed to specify how their properties should be visually represented.
  3. ItemTemplateSelector: Allows for the selection of distinct templates for individual items based on specific criteria. This feature is particularly beneficial when items possess varying layouts or types of content.
  4. ItemsPanel: Specifies the layout panel responsible for organizing the items. Options include using a StackPanel, WrapPanel, or a custom panel to manage the arrangement of items within the ItemsControl.

Key Characteristics of ItemsControl

  1. Versatile Layout: ItemsControl allows for the specification of various layout containers (ItemsPanel), enabling adaptability to different arrangements such as grids, stacks, or bespoke configurations.
  2. Tailored Presentation: Utilizing ItemTemplate and ItemTemplateSelector, you can manage the representation of each item according to its associated data.
  3. Foundation for Specialized Controls: Controls such as ListBox and TreeView derive from ItemsControl, incorporating additional specialized functionalities like item selection and hierarchical organization. ItemsControl is a control within the Windows Presentation Foundation (WPF) framework that facilitates the display of a collection of items, which may include data objects or user interface elements. It acts as a foundational class for various other item-oriented controls, such as ListBox, ListView, ComboBox, TreeView, and Menu. Notably, ItemsControl is a more versatile control that does not inherently include specific features for selection, scrolling, or other interactions. Rather, it offers a customizable approach to organizing items through tailored layouts and templates.

Implementation of ItemsControl

Step 1. Create a Model Class like the one below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TemplateExample
{
    public class DataModel
    {
        public string Label { get; set; }
        public string PropertyType { get; set; } // This will help us select the template
        public string Input { get; set; }
    }
}

Step 2. Create a ViewModel like the one below.

using System.Collections.ObjectModel;

namespace TemplateExample
{
    internal class MainWindowViewModel
    {
        public ObservableCollection<DataModel> ItemsTemplateSourceDataList { get; set; }

        public MainWindowViewModel()
        {
            ItemsTemplateSourceDataList = new ObservableCollection<DataModel>
            {
                new DataModel { Label = "Name", PropertyType = "Text" },
                new DataModel { Label = "Email", PropertyType = "Email" },
                new DataModel { Label = "Phone", PropertyType = "Phone" },
                new DataModel { Label = "Address", PropertyType = "Text" },
                new DataModel { Label = "Comments", PropertyType = "Comments" },
                new DataModel { Label = "Additional Information", PropertyType = "Text" },
                new DataModel { Label = "Feedback", PropertyType = "Comments" }
            };
        }
    }
}

Step 3. Create a template selector like below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows;

namespace TemplateExample
{
    public class PropertyTemplateSelector : DataTemplateSelector
    {
        public DataTemplate TextTemplate { get; set; }
        public DataTemplate EmailTemplate { get; set; }
        public DataTemplate PhoneTemplate { get; set; }
        public DataTemplate CommentsTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            if (item is DataModel DataModel)
            {
                switch (DataModel.PropertyType)
                {
                    case "Text":
                        return TextTemplate;
                    case "Email":
                        return EmailTemplate;
                    case "Phone":
                        return PhoneTemplate;
                    case "Comments":
                        return CommentsTemplate;
                    default:
                        return base.SelectTemplate(item, container);
                }
            }
            return base.SelectTemplate(item, container);
        }
    }
}

Step 4. Define the DataTemplate in XAML.

<Window x:Class="TemplateExample.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:TemplateExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>
        <!-- Template for Text Property -->
        <DataTemplate x:Key="TextTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="LabelGroup" />
                    <ColumnDefinition Width="5" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="30" />
                </Grid.RowDefinitions>
                <TextBlock Text="{Binding Label}" FontSize="14" Margin="5" Grid.Column="0" Grid.Row="0" />
                <GridSplitter Width="5" Background="DarkGray" HorizontalAlignment="Stretch" Grid.Column="1" ResizeDirection="Columns" Grid.Row="0" />
                <TextBox Text="{Binding Input}" Margin="5" Grid.Column="2" HorizontalAlignment="Stretch" Grid.Row="0" />
            </Grid>
        </DataTemplate>

        <!-- Template for Email Property -->
        <DataTemplate x:Key="EmailTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="LabelGroup" />
                    <ColumnDefinition Width="5" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Label}" FontSize="14" Margin="5" Grid.Column="0" />
                <GridSplitter Width="5" Background="DarkGray" HorizontalAlignment="Stretch" Grid.Column="1" ResizeDirection="Columns" />
                <TextBox Text="{Binding Input}" Margin="5" Grid.Column="2" HorizontalAlignment="Stretch" />
            </Grid>
        </DataTemplate>

        <!-- Template for Phone Property -->
        <DataTemplate x:Key="PhoneTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="LabelGroup" />
                    <ColumnDefinition Width="5" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Label}" FontSize="14" Margin="5" Grid.Column="0" />
                <GridSplitter Width="5" Background="DarkGray" HorizontalAlignment="Stretch" Grid.Column="1" ResizeDirection="Columns" />
                <TextBox Text="{Binding Input}" Margin="5" Grid.Column="2" HorizontalAlignment="Stretch" />
            </Grid>
        </DataTemplate>

        <!-- Template for Comments (Multi-line Text) Property -->
        <DataTemplate x:Key="CommentsTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="LabelGroup" />
                    <ColumnDefinition Width="5" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Label}" FontSize="14" Margin="5" Grid.Column="0" />
                <GridSplitter Width="5" Background="DarkGray" HorizontalAlignment="Stretch" Grid.Column="1" ResizeDirection="Columns" />
                <TextBox Text="{Binding Input}" Margin="5" Grid.Column="2" AcceptsReturn="True" Height="100" HorizontalAlignment="Stretch" />
            </Grid>
        </DataTemplate>

        <!-- DataTemplateSelector for Choosing Template Based on PropertyType -->
        <local:PropertyTemplateSelector x:Key="SettingTemplateSelector"
                                        TextTemplate="{StaticResource TextTemplate}"
                                        EmailTemplate="{StaticResource EmailTemplate}"
                                        PhoneTemplate="{StaticResource PhoneTemplate}"
                                        CommentsTemplate="{StaticResource CommentsTemplate}" />
    </Window.Resources>

    <!-- ItemsControl with ScrollViewer -->
    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
        <Grid IsSharedSizeScope="True" HorizontalAlignment="Stretch">
            <ItemsControl x:Name="SettingsItemsPanel"
                          ItemsSource="{Binding ItemsTemplateSourceDataList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          ItemTemplateSelector="{StaticResource SettingTemplateSelector}">
            </ItemsControl>
        </Grid>
    </ScrollViewer>
</Window>

Step 5. Set the Window's DataContext (in Code-Behind).

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace TemplateExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
        }
    }
}

Step 6. The output will appear as follows.

Output