Tag Archives: wpf

Mutually Exclusive WPF Checkboxes

using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;

public class UncheckOnSignalBehavior : Behavior<ToggleButton>
{
    public static readonly DependencyProperty SignalProperty = DependencyProperty.Register(
        "Signal",
        typeof(bool),
        typeof(UncheckOnSignalBehavior),
        new PropertyMetadata(SignalProperty_PropertyChanged));

    private static void SignalProperty_PropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        ((UncheckOnSignalBehavior)sender).Update();
    }

    public bool Signal
    {
        get { return (bool)GetValue(SignalProperty); }
        set { SetValue(SignalProperty, value); }
    }

    protected override void OnAttached()
    {
        Update();
    }

    private void Update()
    {
        if (AssociatedObject != null && Signal)
        {
            AssociatedObject.IsChecked = false;
        }
    }
}

Usage:

<CheckBox IsChecked="{Binding A}">
    <i:Interaction.Behaviors>
        <local:UncheckOnSignalBehavior Signal="{Binding None}" />
    </i:Interaction.Behaviors>
    Option A
</CheckBox>
<CheckBox IsChecked="{Binding B}">
    <i:Interaction.Behaviors>
        <local:UncheckOnSignalBehavior Signal="{Binding None}" />
    </i:Interaction.Behaviors>
    Option B
</CheckBox>
<CheckBox IsChecked="{Binding C}">
    <i:Interaction.Behaviors>
        <local:UncheckOnSignalBehavior Signal="{Binding None}" />
    </i:Interaction.Behaviors>
    Option C
</CheckBox>
<CheckBox IsChecked="{Binding None}">
    <i:Interaction.Behaviors>
        <local:UncheckOnSignalBehavior Signal="{Binding A}" />
        <local:UncheckOnSignalBehavior Signal="{Binding B}" />
        <local:UncheckOnSignalBehavior Signal="{Binding C}" />
    </i:Interaction.Behaviors>
    None of the above
</CheckBox>

Automatic WPF Grids

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

public class AutoGrid : Grid
{
    protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
    {
        base.OnVisualChildrenChanged(visualAdded, visualRemoved);
        SetCells();
    }

    private void SetCells()
    {
        int columnCount = ColumnDefinitions.Count;
        for (int index = 0; index < Children.Count; index++)
        {
            UIElement child = Children[index];
            SetRow(child, index / columnCount);
            SetColumn(child, index % columnCount);
        }
    }
}

Pixel-Perfect WPF Forms

App.xaml:

<Application x:Class="Test.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <Style TargetType="CheckBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type CheckBox}">
                        <Border Padding="0,6,0,5">
                            <Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <Border x:Name="checkBoxBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                                    <Grid x:Name="markGrid">
                                        <Path x:Name="optionMark" Data="F1M9.97498,1.22334L4.6983,9.09834 4.52164,9.09834 0,5.19331 1.27664,3.52165 4.255,6.08833 8.33331,1.52588E-05 9.97498,1.22334z" Fill="#FF212121" Margin="1" Opacity="0" Stretch="None"/>
                                        <Rectangle x:Name="indeterminateMark" Fill="#FF212121" Margin="2" Opacity="0"/>
                                    </Grid>
                                </Border>
                                <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="1" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasContent" Value="True">
                                <Setter Property="FocusVisualStyle">
                                    <Setter.Value>
                                        <Style>
                                            <Setter Property="Control.Template">
                                                <Setter.Value>
                                                    <ControlTemplate>
                                                        <Rectangle Margin="14,0,0,0" SnapsToDevicePixels="True" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
                                                    </ControlTemplate>
                                                </Setter.Value>
                                            </Setter>
                                        </Style>
                                    </Setter.Value>
                                </Setter>
                                <Setter Property="Padding" Value="4,-1,0,0"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" TargetName="checkBoxBorder" Value="#FFF3F9FF"/>
                                <Setter Property="BorderBrush" TargetName="checkBoxBorder" Value="#FF5593FF"/>
                                <Setter Property="Fill" TargetName="optionMark" Value="#FF212121"/>
                                <Setter Property="Fill" TargetName="indeterminateMark" Value="#FF212121"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Background" TargetName="checkBoxBorder" Value="#FFE6E6E6"/>
                                <Setter Property="BorderBrush" TargetName="checkBoxBorder" Value="#FFBCBCBC"/>
                                <Setter Property="Fill" TargetName="optionMark" Value="#FF707070"/>
                                <Setter Property="Fill" TargetName="indeterminateMark" Value="#FF707070"/>
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter Property="Background" TargetName="checkBoxBorder" Value="#FFD9ECFF"/>
                                <Setter Property="BorderBrush" TargetName="checkBoxBorder" Value="#FF3C77DD"/>
                                <Setter Property="Fill" TargetName="optionMark" Value="#FF212121"/>
                                <Setter Property="Fill" TargetName="indeterminateMark" Value="#FF212121"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter Property="Opacity" TargetName="optionMark" Value="1"/>
                                <Setter Property="Opacity" TargetName="indeterminateMark" Value="0"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="{x:Null}">
                                <Setter Property="Opacity" TargetName="optionMark" Value="0"/>
                                <Setter Property="Opacity" TargetName="indeterminateMark" Value="1"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style TargetType="RadioButton">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type RadioButton}">
                        <Border Padding="0,6,0,5">
                            <Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <Border x:Name="radioButtonBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="100" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="2,1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                                    <Grid x:Name="markGrid" Margin="2">
                                        <Ellipse x:Name="optionMark" Fill="#FF212121" MinWidth="6" MinHeight="6" Opacity="0"/>
                                    </Grid>
                                </Border>
                                <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="1" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasContent" Value="True">
                                <Setter Property="FocusVisualStyle">
                                    <Setter.Value>
                                        <Style>
                                            <Setter Property="Control.Template">
                                                <Setter.Value>
                                                    <ControlTemplate>
                                                        <Rectangle Margin="14,0,0,0" SnapsToDevicePixels="True" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
                                                    </ControlTemplate>
                                                </Setter.Value>
                                            </Setter>
                                        </Style>
                                    </Setter.Value>
                                </Setter>
                                <Setter Property="Padding" Value="4,-1,0,0"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" TargetName="radioButtonBorder" Value="#FFF3F9FF"/>
                                <Setter Property="BorderBrush" TargetName="radioButtonBorder" Value="#FF5593FF"/>
                                <Setter Property="Fill" TargetName="optionMark" Value="#FF212121"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Background" TargetName="radioButtonBorder" Value="#FFE6E6E6"/>
                                <Setter Property="BorderBrush" TargetName="radioButtonBorder" Value="#FFBCBCBC"/>
                                <Setter Property="Fill" TargetName="optionMark" Value="#FF707070"/>
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter Property="Background" TargetName="radioButtonBorder" Value="#FFD9ECFF"/>
                                <Setter Property="BorderBrush" TargetName="radioButtonBorder" Value="#FF3C77DD"/>
                                <Setter Property="Fill" TargetName="optionMark" Value="#FF212121"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter Property="Opacity" TargetName="optionMark" Value="1"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="{x:Null}">
                                <Setter Property="Opacity" TargetName="optionMark" Value="0.56"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style TargetType="TextBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBoxBase}">
                        <Border Padding="0,2">
                            <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="2" SnapsToDevicePixels="True">
                                <ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                            </Border>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Opacity" TargetName="border" Value="0.56"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="BorderBrush" TargetName="border" Value="#FF7EB4EA"/>
                            </Trigger>
                            <Trigger Property="IsKeyboardFocused" Value="True">
                                <Setter Property="BorderBrush" TargetName="border" Value="#FF569DE5"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Application.Resources>
</Application>

MainWindow.xaml:

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Height="300"
        Title="Test"
        Width="400">
    <StackPanel Margin="10">
        <Label>Test</Label>
        <TextBox>Test</TextBox>
        <CheckBox>Test</CheckBox>
        <RadioButton>Test</RadioButton>
        <StackPanel Orientation="Horizontal">
            <Label>Test</Label>
            <TextBox>Test</TextBox>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <CheckBox Margin="0,0,5,0">Test</CheckBox>
            <TextBox>Test</TextBox>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <CheckBox Margin="0,0,5,0">Test</CheckBox>
            <TextBox>Test</TextBox>
            <Label>Test</Label>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <RadioButton Margin="0,0,5,0">Test</RadioButton>
            <TextBox>Test</TextBox>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <RadioButton Margin="0,0,5,0">Test</RadioButton>
            <TextBox>Test</TextBox>
            <Label>Test</Label>
        </StackPanel>
    </StackPanel>
</Window>

Before:

After:

WPF Application Boilerplate

using System;
using System.Text;
using System.Windows;

public partial class App : Application
{
    [STAThread]
    private static void Main(string[] args)
    {
        try
        {
            App app = new App();
            app.Run();
        }
        catch (Exception ex)
        {
            HandleError(ex);
        }
    }

    private static void HandleError(Exception ex)
    {
        Clipboard.SetText(ex.ToString());
        StringBuilder message = new StringBuilder();
        message.AppendLine("The application has encountered an error and must shut down.");
        message.AppendLine();
        message.AppendLine(string.Format("{0}: {1}", ex.GetType(), ex.Message));
        message.AppendLine();
        message.Append("Full details have been copied to the clipboard.");
        MessageBox.Show(message.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }

    public App()
    {
        DispatcherUnhandledException += (sender, e) =>
        {
            HandleError(e.Exception);
            e.Handled = true;
            Shutdown(1);
        };
        InitializeComponent();
    }

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        Window window/* = ... */;
        window.Show();
    }
}

Update: April 5, 2018

For something a little less user-hostile, save the stack trace to a temporary file instead of the clipboard:

private static void HandleError(Exception ex)
{
    string path = Path.GetTempFileName();
    File.WriteAllText(path, ex.ToString());
    StringBuilder message = new StringBuilder();
    message.AppendLine("The application has encountered an error and must shut down.");
    message.AppendLine();
    message.AppendLine(string.Format("{0}: {1}", ex.GetType(), ex.Message));
    message.AppendLine();
    message.AppendLine("Full details have been saved to the following file:");
    message.AppendLine();
    message.Append(path);
    MessageBox.Show(message.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}