Skip to main content

Example 10: Advanced UI Patterns and Custom Controls

Overview

The dashboard implements several specialized UI patterns and custom controls that enhance user experience and provide efficient data interaction capabilities specific to the MRP order creation workflow.

Custom Data Grid with Inline Editing

Implementation

<DataGrid x:Name="OrderingGrid" 
ItemsSource="{Binding FilteredData}"
CanUserAddRows="False"
AutoGenerateColumns="False">
<DataGrid.Columns>
<!-- Selection Column -->
<DataGridTemplateColumn Width="40">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding DataContext.SelectAll,
RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

<!-- Editable Quantity Column -->
<DataGridTemplateColumn Header="Order Qty" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding OrderQty, StringFormat=N2}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<xctk:DecimalUpDown Value="{Binding OrderQty}"
Minimum="0"
Maximum="{Binding MaxOrderQty}"
Increment="{Binding ReOrderQty}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

<!-- Status Indicator Column -->
<DataGridTemplateColumn Header="Status" Width="80">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Ellipse Width="12" Height="12"
Fill="{Binding Status, Converter={StaticResource StatusToColorConverter}}"/>
<Ellipse.ToolTip>
<ToolTip Content="{Binding StatusDescription}"/>
</Ellipse.ToolTip>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>

<!-- Row Details Template -->
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border Background="LightGray" Padding="10">
<StackPanel>
<TextBlock Text="Allocations:" FontWeight="Bold"/>
<DataGrid ItemsSource="{Binding Allocations}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
<DataGridTextColumn Header="Reference" Binding="{Binding Reference}"/>
<DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>

Multi-Select ComboBox Control

Custom Control Implementation

public class MultiSelectComboBox : ComboBox
{
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register(
nameof(SelectedItems),
typeof(IList),
typeof(MultiSelectComboBox),
new PropertyMetadata(null));

public IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}

protected override void OnDropDownOpened(EventArgs e)
{
base.OnDropDownOpened(e);

var popup = Template.FindName("PART_Popup", this) as Popup;
if (popup?.Child is Border border)
{
var listBox = new ListBox
{
SelectionMode = SelectionMode.Multiple,
ItemsSource = ItemsSource,
DisplayMemberPath = DisplayMemberPath
};

// Sync selection
foreach (var item in SelectedItems ?? new List<object>())
{
listBox.SelectedItems.Add(item);
}

listBox.SelectionChanged += (s, args) =>
{
SelectedItems = listBox.SelectedItems;
UpdateDisplayText();
};

border.Child = listBox;
}
}

private void UpdateDisplayText()
{
if (SelectedItems == null || SelectedItems.Count == 0)
{
Text = "Select items...";
}
else if (SelectedItems.Count == 1)
{
Text = SelectedItems[0].ToString();
}
else
{
Text = $"{SelectedItems.Count} items selected";
}
}
}

Responsive Loading Overlay

Loading Control

<UserControl x:Class="LoadingOverlay">
<Grid>
<!-- Content -->
<ContentPresenter Content="{Binding Content}"/>

<!-- Overlay -->
<Border Background="#80000000"
Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<!-- Modern spinner -->
<mah:ProgressRing IsActive="True" Width="60" Height="60"/>

<!-- Progress info -->
<TextBlock Text="{Binding LoadingMessage}"
Foreground="White"
FontSize="14"
Margin="0,10,0,0"/>

<!-- Progress bar for determinate operations -->
<ProgressBar Value="{Binding ProgressValue}"
Maximum="{Binding ProgressMaximum}"
Visibility="{Binding ShowProgress, Converter={StaticResource BooleanToVisibilityConverter}}"
Width="200"
Height="4"
Margin="0,10,0,0"/>

<!-- Cancel button -->
<Button Content="Cancel"
Command="{Binding CancelCommand}"
Visibility="{Binding CanCancel, Converter={StaticResource BooleanToVisibilityConverter}}"
Margin="0,10,0,0"
Padding="20,5"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</UserControl>

Smart Search TextBox

Auto-Complete Search Implementation

public class SmartSearchTextBox : TextBox
{
private Popup _suggestionsPopup;
private ListBox _suggestionsListBox;
private Timer _searchTimer;

public static readonly DependencyProperty SearchCommandProperty =
DependencyProperty.Register(
nameof(SearchCommand),
typeof(ICommand),
typeof(SmartSearchTextBox));

public ICommand SearchCommand
{
get => (ICommand)GetValue(SearchCommandProperty);
set => SetValue(SearchCommandProperty, value);
}

protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);

// Debounce search
_searchTimer?.Stop();
_searchTimer = new Timer(300);
_searchTimer.Elapsed += async (s, args) =>
{
await Dispatcher.InvokeAsync(() => PerformSearch());
};
_searchTimer.Start();
}

private async void PerformSearch()
{
if (string.IsNullOrWhiteSpace(Text) || Text.Length < 2)
{
HideSuggestions();
return;
}

// Get suggestions
var suggestions = await GetSuggestionsAsync(Text);

if (suggestions.Any())
{
ShowSuggestions(suggestions);
}
else
{
HideSuggestions();
}
}

private void ShowSuggestions(IEnumerable<string> suggestions)
{
_suggestionsListBox.ItemsSource = suggestions;
_suggestionsPopup.IsOpen = true;
}
}

Validation Feedback Controls

Visual Validation Indicator

<Style x:Key="ValidatedTextBox" TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Grid>
<Border x:Name="Border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
Background="{TemplateBinding Background}">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>

<!-- Validation indicator -->
<Border x:Name="ValidationBorder"
BorderBrush="Red"
BorderThickness="2"
Visibility="Collapsed"/>

<!-- Error icon -->
<Image x:Name="ErrorIcon"
Source="/Resources/Images/error.png"
Width="16" Height="16"
HorizontalAlignment="Right"
Margin="0,0,5,0"
Visibility="Collapsed">
<Image.ToolTip>
<ToolTip Content="{Binding (Validation.Errors)[0].ErrorContent,
RelativeSource={RelativeSource AncestorType=TextBox}}"/>
</Image.ToolTip>
</Image>
</Grid>

<ControlTemplate.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter TargetName="ValidationBorder" Property="Visibility" Value="Visible"/>
<Setter TargetName="ErrorIcon" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Animated Transitions

Page Transition Animation

public class AnimatedContentControl : ContentControl
{
protected override void OnContentChanged(object oldContent, object newContent)
{
if (oldContent != null && newContent != null)
{
var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(200));
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200));

fadeOut.Completed += (s, e) =>
{
base.OnContentChanged(oldContent, newContent);
BeginAnimation(OpacityProperty, fadeIn);
};

BeginAnimation(OpacityProperty, fadeOut);
}
else
{
base.OnContentChanged(oldContent, newContent);
}
}
}

Accessibility Enhancements

Screen Reader Support

<Button x:Name="CreateOrdersButton"
Content="Create Orders"
AutomationProperties.Name="Create purchase orders for selected items"
AutomationProperties.HelpText="Creates purchase orders grouped by supplier and warehouse"
AutomationProperties.AcceleratorKey="Ctrl+O"
AutomationProperties.AutomationId="CreateOrdersButton"/>

High Contrast Theme

<ResourceDictionary x:Key="HighContrastTheme">
<SolidColorBrush x:Key="BackgroundBrush" Color="Black"/>
<SolidColorBrush x:Key="ForegroundBrush" Color="White"/>
<SolidColorBrush x:Key="BorderBrush" Color="Yellow"/>
<SolidColorBrush x:Key="SelectedBrush" Color="Cyan"/>

<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</ResourceDictionary>

Performance Optimizations

UI Virtualization

<DataGrid VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ScrollViewer.CanContentScroll="True"
EnableRowVirtualization="True"
EnableColumnVirtualization="True"/>

Deferred Scrolling

<DataGrid ScrollViewer.IsDeferredScrollingEnabled="True"/>

Benefits

  • Enhanced user productivity
  • Reduced data entry errors
  • Improved accessibility
  • Better performance with large datasets
  • Professional user experience
  • Consistent interaction patterns