C# WPF Lifecycle User Control

Print Friendly, PDF & Email

C# WPF Lifecycle User Control

In this quick post were going to create a user control that allows us to create a lifecycle graph like the one below.

Lifecycle Graph

Datamodel

Were going to start with a very simple data model, which contains Status State to allow us to display which node is active. Status Title so we can change the displayed text, and Status Arrow Visibility so we can turn of the last arrow.

 public class StatusModel
 {
     public bool StatusState { get; set; } = false;
     public string StatusTitle { get; set; } = "Frozen";      
     public Visibility StatusArrowVisibility { get; set; } = Visibility.Visible;
 }

Status User Control

Within the user control were going to create a very simple layout with a few bindings and data triggers to control the style of the user control.

Status User Control

Status User Control XAML

<UserControl x:Class="User_Controls.UserControls.StateUC"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="100"
             d:DesignWidth="300"
             x:Name="stateUC">
    <StackPanel 
        Orientation="Horizontal">
        <Border
            x:Name="stateUCBorder"
            CornerRadius="10"
            BorderThickness="0"
            BorderBrush="Transparent"
            Height="{Binding ElementName=stateUC, Path=StatusHeight,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
            Width="{Binding ElementName=stateUC, Path=StatusWidth,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
            Margin="0 0 20 0">
            <Border.Style>
                <Style TargetType="Border">
                    <Setter Property="Background" Value="Transparent"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="True">
                            <Setter Property="Background" Value="LightBlue"/>
                            <Setter Property="BorderBrush" Value="Blue"/>
                            <Setter Property="BorderThickness" Value="2"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="False">
                            <Setter Property="Background" Value="White"/>
                            <Setter Property="BorderBrush" Value="Black"/>
                            <Setter Property="BorderThickness" Value="1"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Border.Style>
            <TextBlock
                x:Name="stateUCText"
                Text="{Binding ElementName=stateUC, Path=StatusTitle,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontSize="20"/>
        </Border>
        <Viewbox Width="10" Height="30" Margin="0 5 0 0">
            <Canvas
                Width="10"
                Height="30">
                <Path Visibility="{Binding ElementName=stateUC,Path=StatusArrowVisibility, UpdateSourceTrigger=PropertyChanged}">
                    <Path.Style>
                        <Style TargetType="Path">
                            <Setter Property="Fill" Value="Transparent"/>
                            <Setter Property="Stroke" Value="Transparent"/>
                            <Setter Property="StrokeThickness" Value="2"/>
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="True">
                                    <Setter Property="Visibility" Value="Visible"/>
                                    <Setter Property="Fill" Value="LightBlue"/>
                                    <Setter Property="Stroke" Value="Blue"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="False">
                                    <Setter Property="Visibility" Value="Visible"/>
                                    <Setter Property="Fill" Value="White"/>
                                    <Setter Property="Stroke" Value="Black"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Path.Style>
                    <Path.Data>
                        <PathGeometry>
                            <PathGeometry.Figures>
                                <PathFigure StartPoint="60,10">
                                    <PathFigure.Segments>
                                        <LineSegment Point="30,0"/>
                                        <LineSegment Point="30,5"/>
                                        <LineSegment Point="0,5"/>
                                        <LineSegment Point="0,15"/>
                                        <LineSegment Point="30,15"/>
                                        <LineSegment Point="30,20"/>
                                        <LineSegment Point="60,10"/>
                                    </PathFigure.Segments>
                                </PathFigure>
                            </PathGeometry.Figures>
                        </PathGeometry>
                    </Path.Data>
                </Path>
            </Canvas>
        </Viewbox>
    </StackPanel>
</UserControl>

Status User Control XAML.cs

In the code behind we need to expose several dependency properties. To really customize the user control we could add more, i.e. Brushes to control color etc.

using System.Windows;
using System.Windows.Controls;

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

        public bool StatusState
        {
            get { return (bool)GetValue(StatusStateProperty); }
            set { SetValue(StatusStateProperty, value); }
        }

        public static readonly DependencyProperty StatusStateProperty =
            DependencyProperty.Register("StatusState", 
                typeof(bool), 
                typeof(StateUC), 
                new PropertyMetadata(false));

        public string StatusTitle
        {
            get { return (string)GetValue(StatusTitleProperty); }
            set { SetValue(StatusTitleProperty, value); }
        }

        public static readonly DependencyProperty StatusTitleProperty =
            DependencyProperty.Register("StatusTitle", 
                typeof(string), 
                typeof(StateUC), 
                new PropertyMetadata("Title"));

        public int StatusHeight
        {
            get { return (int)GetValue(StatusHeightProperty); }
            set { SetValue(StatusHeightProperty, value); }
        }

        public static readonly DependencyProperty StatusHeightProperty =
            DependencyProperty.Register("StatusHeight", 
                typeof(int), 
                typeof(StateUC), 
                new PropertyMetadata(100));

        public int StatusWidth
        {
            get { return (int)GetValue(StatusWidthProperty); }
            set { SetValue(StatusWidthProperty, value); }
        }

        public static readonly DependencyProperty StatusWidthProperty =
            DependencyProperty.Register("StatusWidth", 
                typeof(int), 
                typeof(StateUC), 
                new PropertyMetadata(300));

        public Visibility StatusArrowVisibility
        {
            get { return (Visibility)GetValue(StatusArrowVisibilityProperty); }
            set { SetValue(StatusArrowVisibilityProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowVisibilityProperty =
            DependencyProperty.Register("StatusArrowVisibility", 
                typeof(Visibility), 
                typeof(StateUC), 
                new PropertyMetadata(null));
    }
}

View Model

In the view model that’s going to be tied to the view that contains the Lifecycle Graph we need to create an observable collection of the Status Models. Notice in the last one were hiding the arrow.

private ObservableCollection<StatusModel> _statusModels = new ObservableCollection<StatusModel>()
{
    new StatusModel(){StatusTitle = "Frozen",StatusState = false},
    new StatusModel(){StatusTitle = "Online",StatusState = false},
    new StatusModel(){StatusTitle = "Offline",StatusState = true},
    new StatusModel(){StatusTitle = "Busy",StatusState = false,StatusArrowVisibility=Visibility.Hidden}
};
public ObservableCollection<StatusModel> StatusModels
{
    get => _statusModels;
    set
    {
        _statusModels = value;
        OnPropertyChanged();
    }
}

View

Within the XAML of the view we can use the Items Control tag and bind it to the Status Models observable collection. We can then define the template we want to use, in this case a stack panel. Then we can use the Status User Control within the Data template and define its bindings tot he Status Model.

<ItemsControl ItemsSource="{Binding StatusModels}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="20 0 0 20"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <uc:StateUC 
                StatusTitle="{Binding StatusTitle}"
                StatusState="{Binding StatusState}"
                StatusArrowVisibility="{Binding StatusArrowVisibility}"
                StatusHeight="50"
                StatusWidth="100"
                Margin="0 0 50 0"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

This will produce the lifecycle graph;

Extra Dependency Properties

I added a few more bindings and dependency property’s so you can see how to fully define a user control.

Status User Control XAML

<UserControl x:Class="User_Controls.UserControls.StateUC"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="100"
             d:DesignWidth="300"
             x:Name="stateUC">
    <StackPanel 
        Orientation="Horizontal">
        <Border
            x:Name="stateUCBorder"
            CornerRadius="{Binding ElementName=stateUC,Path=StatusCornerRadius,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
            BorderThickness="0"
            BorderBrush="Transparent"
            Height="{Binding ElementName=stateUC, Path=StatusHeight,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
            Width="{Binding ElementName=stateUC, Path=StatusWidth,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
            Margin="0 0 20 0">
            <Border.Style>
                <Style TargetType="Border">
                    <Setter Property="Background" Value="Transparent"/>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="True">
                            <Setter Property="Background" Value="{Binding ElementName=stateUC,Path=StatusActiveBackGround,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                            <Setter Property="BorderBrush" Value="{Binding ElementName=stateUC,Path=StatusActiveBorderBrush,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                            <Setter Property="BorderThickness" Value="{Binding ElementName=stateUC,Path=StatusActiveBorderThickness,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="False">
                            <Setter Property="Background" Value="{Binding ElementName=stateUC,Path=StatusInActiveBackGround,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                            <Setter Property="BorderBrush" Value="{Binding ElementName=stateUC,Path=StatusInActiveBorderBrush,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                            <Setter Property="BorderThickness" Value="{Binding ElementName=stateUC,Path=StatusInActiveBorderThickness,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Border.Style>
            <TextBlock
                x:Name="stateUCText"
                Text="{Binding ElementName=stateUC, Path=StatusTitle,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontSize="{Binding ElementName=stateUC,Path=StatusFontSize,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="True">
                                <Setter Property="Foreground" Value="White"/>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="False">
                                <Setter Property="Foreground" Value="Black"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </Border>
        <Viewbox Width="10" Height="30" Margin="0 5 0 0">
            <Canvas
                Width="10"
                Height="30">
                <Path Visibility="{Binding ElementName=stateUC,Path=StatusArrowVisibility, UpdateSourceTrigger=PropertyChanged}">
                    <Path.Style>
                        <Style TargetType="Path">
                            <Setter Property="Fill" Value="Transparent"/>
                            <Setter Property="Stroke" Value="Transparent"/>
                            <Setter Property="StrokeThickness" Value="2"/>
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="True">
                                    <Setter Property="Visibility" Value="Visible"/>
                                    <Setter Property="Fill" Value="{Binding ElementName=stateUC,Path=StatusArrowActiveFill,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                                    <Setter Property="Stroke" Value="{Binding ElementName=stateUC,Path=StatusArrowActiveStroke,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding ElementName=stateUC, Path=StatusState, UpdateSourceTrigger=PropertyChanged}" Value="False">
                                    <Setter Property="Visibility" Value="Visible"/>
                                    <Setter Property="Fill" Value="{Binding ElementName=stateUC,Path=StatusArrowInActiveFill,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                                    <Setter Property="Stroke" Value="{Binding ElementName=stateUC,Path=StatusArrowInActiveStroke,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Path.Style>
                    <Path.Data>
                        <PathGeometry>
                            <PathGeometry.Figures>
                                <PathFigure StartPoint="60,10">
                                    <PathFigure.Segments>
                                        <LineSegment Point="30,0"/>
                                        <LineSegment Point="30,5"/>
                                        <LineSegment Point="0,5"/>
                                        <LineSegment Point="0,15"/>
                                        <LineSegment Point="30,15"/>
                                        <LineSegment Point="30,20"/>
                                        <LineSegment Point="60,10"/>
                                    </PathFigure.Segments>
                                </PathFigure>
                            </PathGeometry.Figures>
                        </PathGeometry>
                    </Path.Data>
                </Path>
            </Canvas>
        </Viewbox>
    </StackPanel>
</UserControl>

Status User Control XAML.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace User_Controls.UserControls
{
    public partial class StateUC : UserControl
    {
        public StateUC()
        {
            InitializeComponent();
        }

        public CornerRadius StatusCornerRadius
        {
            get { return (CornerRadius)GetValue(StatusCornerRadiusProperty); }
            set { SetValue(StatusCornerRadiusProperty, value); }
        }

        public static readonly DependencyProperty StatusCornerRadiusProperty =
            DependencyProperty.Register("StatusCornerRadius", 
                typeof(CornerRadius), 
                typeof(StateUC), 
                new PropertyMetadata(new CornerRadius(5)));

        public bool StatusState
        {
            get { return (bool)GetValue(StatusStateProperty); }
            set { SetValue(StatusStateProperty, value); }
        }

        public static readonly DependencyProperty StatusStateProperty =
            DependencyProperty.Register("StatusState", 
                typeof(bool), 
                typeof(StateUC), 
                new PropertyMetadata(false));

        public string StatusTitle
        {
            get { return (string)GetValue(StatusTitleProperty); }
            set { SetValue(StatusTitleProperty, value); }
        }

        public static readonly DependencyProperty StatusTitleProperty =
            DependencyProperty.Register("StatusTitle", 
                typeof(string), 
                typeof(StateUC), 
                new PropertyMetadata("Title"));

        public int StatusHeight
        {
            get { return (int)GetValue(StatusHeightProperty); }
            set { SetValue(StatusHeightProperty, value); }
        }

        public static readonly DependencyProperty StatusHeightProperty =
            DependencyProperty.Register("StatusHeight", 
                typeof(int), 
                typeof(StateUC), 
                new PropertyMetadata(100));

        public int StatusWidth
        {
            get { return (int)GetValue(StatusWidthProperty); }
            set { SetValue(StatusWidthProperty, value); }
        }

        public static readonly DependencyProperty StatusWidthProperty =
            DependencyProperty.Register("StatusWidth", 
                typeof(int), 
                typeof(StateUC), 
                new PropertyMetadata(300));

        public Visibility StatusArrowVisibility
        {
            get { return (Visibility)GetValue(StatusArrowVisibilityProperty); }
            set { SetValue(StatusArrowVisibilityProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowVisibilityProperty =
            DependencyProperty.Register("StatusArrowVisibility", 
                typeof(Visibility), 
                typeof(StateUC), 
                new PropertyMetadata(null));

        public Brush StatusActiveBackGround
        {
            get { return (Brush)GetValue(StatusActiveBackGroundProperty); }
            set { SetValue(StatusActiveBackGroundProperty, value); }
        }

        public static readonly DependencyProperty StatusActiveBackGroundProperty =
            DependencyProperty.Register("StatusActiveBackGround", 
                typeof(Brush), 
                typeof(StateUC), 
                new PropertyMetadata(Brushes.Aquamarine));

        public Brush StatusActiveBorderBrush
        {
            get { return (Brush)GetValue(StatusActiveBorderBrushProperty); }
            set { SetValue(StatusActiveBorderBrushProperty, value); }
        }

        public static readonly DependencyProperty StatusActiveBorderBrushProperty =
            DependencyProperty.Register("StatusActiveBorderBrush", 
                typeof(Brush), 
                typeof(StateUC), 
                new PropertyMetadata(Brushes.Blue));

        public Thickness StatusActiveBorderThickness
        {
            get { return (Thickness)GetValue(StatusActiveBorderThicknessProperty); }
            set { SetValue(StatusActiveBorderThicknessProperty, value); }
        }

        public static readonly DependencyProperty StatusActiveBorderThicknessProperty =
            DependencyProperty.Register("StatusActiveBorderThickness", 
                typeof(Thickness), 
                typeof(StateUC), 
                new PropertyMetadata(new Thickness(2)));

        public Brush StatusInActiveBackGround
        {
            get { return (Brush)GetValue(StatusInActiveBackGroundProperty); }
            set { SetValue(StatusInActiveBackGroundProperty, value); }
        }

        public static readonly DependencyProperty StatusInActiveBackGroundProperty =
            DependencyProperty.Register("StatusInActiveBackGround", 
                typeof(Brush), 
                typeof(StateUC), 
                new PropertyMetadata(Brushes.LightGray));

        public Brush StatusInActiveBorderBrush
        {
            get { return (Brush)GetValue(StatusInActiveBorderBrushProperty); }
            set { SetValue(StatusInActiveBorderBrushProperty, value); }
        }

        public static readonly DependencyProperty StatusInActiveBorderBrushProperty =
            DependencyProperty.Register("StatusInActiveBorderBrush", 
                typeof(Brush), 
                typeof(StateUC), 
                new PropertyMetadata(Brushes.Gray));

        public Thickness StatusInActiveBorderThickness
        {
            get { return (Thickness)GetValue(StatusInActiveBorderThicknessProperty); }
            set { SetValue(StatusInActiveBorderThicknessProperty, value); }
        }

        public static readonly DependencyProperty StatusInActiveBorderThicknessProperty =
            DependencyProperty.Register("StatusInActiveBorderThickness", 
                typeof(Thickness), 
                typeof(StateUC), 
                new PropertyMetadata(new Thickness(2)));

        public int StatusFontSize
        {
            get { return (int)GetValue(StatusFontSizeProperty); }
            set { SetValue(StatusFontSizeProperty, value); }
        }

        public static readonly DependencyProperty StatusFontSizeProperty =
            DependencyProperty.Register("StatusFontSize", 
                typeof(int), 
                typeof(StateUC), 
                new PropertyMetadata(20));

        public Brush StatusArrowActiveFill
        {
            get { return (Brush)GetValue(StatusArrowActiveFillProperty); }
            set { SetValue(StatusArrowActiveFillProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowActiveFillProperty =
            DependencyProperty.Register("StatusArrowActiveFill", 
                typeof(Brush), 
                typeof(StateUC), 
                new PropertyMetadata(Brushes.Blue));

        public Brush StatusArrowActiveStroke
        {
            get { return (Brush)GetValue(StatusArrowActiveStrokeProperty); }
            set { SetValue(StatusArrowActiveStrokeProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowActiveStrokeProperty =
            DependencyProperty.Register("StatusArrowActiveStroke", 
                typeof(Brush), 
                typeof(StateUC), 
                new PropertyMetadata(Brushes.White));

        public Brush StatusArrowInActiveFill
        {
            get { return (Brush)GetValue(StatusArrowInActiveFillProperty); }
            set { SetValue(StatusArrowInActiveFillProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowInActiveFillProperty =
            DependencyProperty.Register("StatusArrowInActiveFill", 
                typeof(Brush), 
                typeof(StateUC), 
                new PropertyMetadata(Brushes.LightGray));      

        public Brush StatusArrowInActiveStroke
        {
            get { return (Brush)GetValue(StatusArrowInActiveStrokeProperty); }
            set { SetValue(StatusArrowInActiveStrokeProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowInActiveStrokeProperty =
            DependencyProperty.Register("StatusArrowInActiveStroke", 
                typeof(Brush), 
                typeof(StateUC), 
                new PropertyMetadata(Brushes.Gray));
    }
}

Usage

In the usage I’m just defining my property values for the most part i could rebind them again, but normally you only need to bind to properties that are dynamic.

<ItemsControl ItemsSource="{Binding StatusModels}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="20 0 0 20"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <uc:StateUC 
                StatusTitle="{Binding StatusTitle}"
                StatusState="{Binding StatusState}"
                StatusArrowVisibility="{Binding StatusArrowVisibility}"
                StatusActiveBackGround="Red"
                StatusActiveBorderBrush="Green"
                StatusActiveBorderThickness="5"
                StatusInActiveBackGround="LawnGreen"
                StatusInActiveBorderBrush="Beige"
                StatusInActiveBorderThickness="3"
                StatusArrowActiveFill="Chocolate"
                StatusArrowActiveStroke="DarkCyan"
                StatusArrowInActiveFill="Coral"
                StatusArrowInActiveStroke="Azure"
                StatusCornerRadius="10"
                StatusFontSize="12"
                StatusHeight="50"
                StatusWidth="100"
                Margin="0 0 50 0"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Going One Step Further

Now that we know our User Control works in the view and from the view model, we can encapsulate it within its own user control, this makes the final usage much simpler. Unlike before i didn’t bind everything but you can if you want to.

Lifecycle User Control XAML

<UserControl x:Class="User_Controls.UserControls.LifeCycleUC"
             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:User_Controls.UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="450" 
             d:DesignWidth="800"
             x:Name="lifeCycleUC">
    <Grid>
        <ItemsControl ItemsSource="{Binding ElementName=lifeCycleUC,Path=StatusModels,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="20 0 0 20"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <uc:StateUC 
                        StatusTitle="{Binding StatusTitle,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusState="{Binding StatusState,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusArrowVisibility="{Binding StatusArrowVisibility,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusActiveBackGround="{Binding ElementName=lifeCycleUC,Path=StatusActiveBackGround,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusActiveBorderBrush="{Binding ElementName=lifeCycleUC,Path=StatusActiveBorderBrush,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusActiveBorderThickness="{Binding ElementName=lifeCycleUC,Path=StatusActiveBorderThickness,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusInActiveBackGround="{Binding ElementName=lifeCycleUC,Path=StatusInActiveBackGround,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusInActiveBorderBrush="{Binding ElementName=lifeCycleUC,Path=StatusInActiveBorderBrush,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusInActiveBorderThickness="{Binding ElementName=lifeCycleUC,Path=StatusInActiveBorderThickness,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusArrowActiveFill="{Binding ElementName=lifeCycleUC,Path=StatusArrowActiveFill,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusArrowActiveStroke="{Binding ElementName=lifeCycleUC,Path=StatusArrowActiveStroke,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusArrowInActiveFill="{Binding ElementName=lifeCycleUC,Path=StatusArrowInActiveFill,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusArrowInActiveStroke="{Binding ElementName=lifeCycleUC,Path=StatusArrowInActiveStroke,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusCornerRadius="{Binding ElementName=lifeCycleUC,Path=StatusCornerRadius,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusFontSize="{Binding ElementName=lifeCycleUC,Path=StatusFontSize,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusHeight="{Binding ElementName=lifeCycleUC,Path=StatusHeight,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        StatusWidth="{Binding ElementName=lifeCycleUC,Path=StatusWidth,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
                        Margin="0 0 50 0"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</UserControl>

Lifecycle User Control XAML.cs

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

namespace User_Controls.UserControls
{
    public partial class LifeCycleUC : UserControl
    {
        public LifeCycleUC()
        {
            InitializeComponent();
        }

        public IEnumerable StatusModels
        {
            get { return (IEnumerable)GetValue(StatusModelsProperty); }
            set { SetValue(StatusModelsProperty, value); }
        }

        public static readonly DependencyProperty StatusModelsProperty =
            DependencyProperty.Register("StatusModels", 
                typeof(IEnumerable), 
                typeof(LifeCycleUC), 
                new PropertyMetadata(null));

        public CornerRadius StatusCornerRadius
        {
            get { return (CornerRadius)GetValue(StatusCornerRadiusProperty); }
            set { SetValue(StatusCornerRadiusProperty, value); }
        }

        public static readonly DependencyProperty StatusCornerRadiusProperty =
            DependencyProperty.Register("StatusCornerRadius",
                typeof(CornerRadius),
                typeof(LifeCycleUC),
                new PropertyMetadata(new CornerRadius(5)));

        public int StatusHeight
        {
            get { return (int)GetValue(StatusHeightProperty); }
            set { SetValue(StatusHeightProperty, value); }
        }

        public static readonly DependencyProperty StatusHeightProperty =
            DependencyProperty.Register("StatusHeight",
                typeof(int),
                typeof(LifeCycleUC),
                new PropertyMetadata(100));

        public int StatusWidth
        {
            get { return (int)GetValue(StatusWidthProperty); }
            set { SetValue(StatusWidthProperty, value); }
        }

        public static readonly DependencyProperty StatusWidthProperty =
            DependencyProperty.Register("StatusWidth",
                typeof(int),
                typeof(LifeCycleUC),
                new PropertyMetadata(300));

        public Brush StatusActiveBackGround
        {
            get { return (Brush)GetValue(StatusActiveBackGroundProperty); }
            set { SetValue(StatusActiveBackGroundProperty, value); }
        }

        public static readonly DependencyProperty StatusActiveBackGroundProperty =
            DependencyProperty.Register("StatusActiveBackGround",
                typeof(Brush),
                typeof(LifeCycleUC),
                new PropertyMetadata(Brushes.Aquamarine));

        public Brush StatusActiveBorderBrush
        {
            get { return (Brush)GetValue(StatusActiveBorderBrushProperty); }
            set { SetValue(StatusActiveBorderBrushProperty, value); }
        }

        public static readonly DependencyProperty StatusActiveBorderBrushProperty =
            DependencyProperty.Register("StatusActiveBorderBrush",
                typeof(Brush),
                typeof(LifeCycleUC),
                new PropertyMetadata(Brushes.Blue));

        public Thickness StatusActiveBorderThickness
        {
            get { return (Thickness)GetValue(StatusActiveBorderThicknessProperty); }
            set { SetValue(StatusActiveBorderThicknessProperty, value); }
        }

        public static readonly DependencyProperty StatusActiveBorderThicknessProperty =
            DependencyProperty.Register("StatusActiveBorderThickness",
                typeof(Thickness),
                typeof(LifeCycleUC),
                new PropertyMetadata(new Thickness(2)));

        public Brush StatusInActiveBackGround
        {
            get { return (Brush)GetValue(StatusInActiveBackGroundProperty); }
            set { SetValue(StatusInActiveBackGroundProperty, value); }
        }

        public static readonly DependencyProperty StatusInActiveBackGroundProperty =
            DependencyProperty.Register("StatusInActiveBackGround",
                typeof(Brush),
                typeof(LifeCycleUC),
                new PropertyMetadata(Brushes.LightGray));

        public Brush StatusInActiveBorderBrush
        {
            get { return (Brush)GetValue(StatusInActiveBorderBrushProperty); }
            set { SetValue(StatusInActiveBorderBrushProperty, value); }
        }

        public static readonly DependencyProperty StatusInActiveBorderBrushProperty =
            DependencyProperty.Register("StatusInActiveBorderBrush",
                typeof(Brush),
                typeof(LifeCycleUC),
                new PropertyMetadata(Brushes.Gray));

        public Thickness StatusInActiveBorderThickness
        {
            get { return (Thickness)GetValue(StatusInActiveBorderThicknessProperty); }
            set { SetValue(StatusInActiveBorderThicknessProperty, value); }
        }

        public static readonly DependencyProperty StatusInActiveBorderThicknessProperty =
            DependencyProperty.Register("StatusInActiveBorderThickness",
                typeof(Thickness),
                typeof(LifeCycleUC),
                new PropertyMetadata(new Thickness(2)));

        public int StatusFontSize
        {
            get { return (int)GetValue(StatusFontSizeProperty); }
            set { SetValue(StatusFontSizeProperty, value); }
        }

        public static readonly DependencyProperty StatusFontSizeProperty =
            DependencyProperty.Register("StatusFontSize",
                typeof(int),
                typeof(LifeCycleUC),
                new PropertyMetadata(20));

        public Brush StatusArrowActiveFill
        {
            get { return (Brush)GetValue(StatusArrowActiveFillProperty); }
            set { SetValue(StatusArrowActiveFillProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowActiveFillProperty =
            DependencyProperty.Register("StatusArrowActiveFill",
                typeof(Brush),
                typeof(LifeCycleUC),
                new PropertyMetadata(Brushes.Blue));

        public Brush StatusArrowActiveStroke
        {
            get { return (Brush)GetValue(StatusArrowActiveStrokeProperty); }
            set { SetValue(StatusArrowActiveStrokeProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowActiveStrokeProperty =
            DependencyProperty.Register("StatusArrowActiveStroke",
                typeof(Brush),
                typeof(LifeCycleUC),
                new PropertyMetadata(Brushes.White));

        public Brush StatusArrowInActiveFill
        {
            get { return (Brush)GetValue(StatusArrowInActiveFillProperty); }
            set { SetValue(StatusArrowInActiveFillProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowInActiveFillProperty =
            DependencyProperty.Register("StatusArrowInActiveFill",
                typeof(Brush),
                typeof(LifeCycleUC),
                new PropertyMetadata(Brushes.LightGray));

        public Brush StatusArrowInActiveStroke
        {
            get { return (Brush)GetValue(StatusArrowInActiveStrokeProperty); }
            set { SetValue(StatusArrowInActiveStrokeProperty, value); }
        }

        public static readonly DependencyProperty StatusArrowInActiveStrokeProperty =
            DependencyProperty.Register("StatusArrowInActiveStroke",
                typeof(Brush),
                typeof(LifeCycleUC),
                new PropertyMetadata(Brushes.Gray));
    }
}

Lifecycle User Control Usage

Now we can see that the final usage is much simpler and directly binds to the Observable collection.

        <uc:LifeCycleUC
    		StatusModels="{Binding StatusModels , UpdateSourceTrigger = PropertyChanged}"
            StatusActiveBackGround="DarkBlue"
            StatusActiveBorderBrush="Red"
            StatusActiveBorderThickness="2"
            StatusArrowActiveFill="Cyan"
            StatusArrowActiveStroke="Green"
            StatusArrowInActiveFill="Blue"
            StatusArrowInActiveStroke="DarkCyan"
            StatusCornerRadius="10"
            StatusFontSize="18"
            StatusHeight="30"
            StatusInActiveBackGround="DarkOliveGreen"
            StatusInActiveBorderBrush="Purple"
            StatusInActiveBorderThickness="3"
            StatusWidth="100"/>

Final Control

Lifecycle User Control