Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Custom Expander Control for WPF

0.00/5 (No votes)
7 Feb 2017 2  
A WPF custom control for grouping data similar to build-in expander

Introduction

This is a very simple custom expander control. The control is called "GroupExpander".

Background

I don't like the built-in expander control in WPF. Although I can change the control template to give a new look and feel, I thought it will be quicker to create a custom one.

Using the Code

I have a default WPF project "WpfApplication1". This will create a MainWindow.xaml and cs by default. Add a custom control "GroupExpander". This will give us a GroupExpander.cs and a Generic.xaml. Create a Header property in GroupExpander control and rest of the stuff is done in XAML in Generic.xaml file. I have used "WpfApplication1" as namespace all across. I have coded the GroupExpander control to match my existing UI. But the XAML for template is available. Feel free to change.

The code for GroupExpander control is as follows:

public class GroupExpander : ContentControl
{
  public string Header
  {
    get { return (string)GetValue(HeaderProperty); }
    set { SetValue(HeaderProperty, value); }
  }
  public static readonly DependencyProperty HeaderProperty = 
       DependencyProperty.Register("Header", typeof(string), typeof(GroupExpander), 
       new PropertyMetadata(string.Empty));
  static GroupExpander()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(GroupExpander), 
      new FrameworkPropertyMetadata(typeof(GroupExpander)));
  }
}

In the generic.xaml file, we are defining the control template for the GroupExpander control. We have a Toggle button to control the visibility of the content presenter. The template of the toggle button has been changed. This has a textblock to show the header, a polyline to draw angle indicator and a line separator. The XAML in Generic.xaml file to format the control:

<Style TargetType="{x:Type local:GroupExpander}">
  <Setter Property="SnapsToDevicePixels" Value="True" />
  <Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type local:GroupExpander}">
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ToggleButton Grid.Row="0" 
    Name="PART_ToggleButton" IsChecked="True" 
    OverridesDefaultStyle="True">
      <ToggleButton.Style>
        <Style TargetType="{x:Type ToggleButton}">
          <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToggleButton}">
              <Grid HorizontalAlignment="Stretch" 
              VerticalAlignment="Stretch" Margin="1,0,1,0">
              <Grid.Triggers>
                <EventTrigger RoutedEvent="UIElement.MouseEnter">
                  <BeginStoryboard>
                    <BeginStoryboard.Storyboard>
                      <Storyboard Storyboard.TargetName="PART_Rectangle" 
                                  Storyboard.TargetProperty="Opacity">
                        <DoubleAnimation To="1" Duration="0:0:0.01" />
                      </Storyboard>
                    </BeginStoryboard.Storyboard>
                  </BeginStoryboard>
                </EventTrigger>
                <EventTrigger RoutedEvent="UIElement.MouseLeave">
                  <BeginStoryboard>
                    <BeginStoryboard.Storyboard>
                      <Storyboard Storyboard.TargetName="PART_Rectangle" 
                                  Storyboard.TargetProperty="Opacity">
                        <DoubleAnimation To="0" Duration="0:0:0.1" />
                      </Storyboard>
                    </BeginStoryboard.Storyboard>
                  </BeginStoryboard>
                </EventTrigger>
              </Grid.Triggers>
              <Rectangle x:Name="PART_Rectangle" 
              Fill="#EEE" Opacity="0" />
              <DockPanel Background="Transparent" Margin="0,5,0,10">
                <TextBlock DockPanel.Dock="Left" 
                FontWeight="Bold" Margin="0,4,0,0" 
                           Text="{Binding Path=Header,
                           RelativeSource={RelativeSource AncestorType=
                           {x:Type local:GroupExpander}}}"/>
                <Grid DockPanel.Dock="Right" Margin="0,4,0,0" 
                VerticalAlignment="Center">
                  <Polyline Points="0,0 4,4 8,0" 
                  Stroke="Black" StrokeThickness="1">
                    <Polyline.Style>
                      <Style TargetType="{x:Type Polyline}">
                        <Setter Property="Visibility" Value="Visible"/>
                        <Style.Triggers>
                          <DataTrigger Binding="{Binding ElementName=PART_ToggleButton, 
                                       Path=IsChecked }" Value="True">
                            <Setter Property="Visibility" Value="Collapsed"/>
                          </DataTrigger>
                        </Style.Triggers>
                      </Style>
                    </Polyline.Style>
                  </Polyline>
                  <Polyline Points="0,4 4,0 8,4" 
                  Stroke="Black" StrokeThickness="1">
                    <Polyline.Style>
                      <Style TargetType="{x:Type Polyline}">
                        <Setter Property="Visibility" Value="Collapsed"/>
                        <Style.Triggers>
                          <DataTrigger Binding="{Binding ElementName=PART_ToggleButton, 
                                       Path=IsChecked }" Value="True">
                            <Setter Property="Grid.Visibility" Value="Visible"/>
                          </DataTrigger>
                        </Style.Triggers>
                      </Style>
                    </Polyline.Style>
                  </Polyline>
                </Grid>
                <Separator Margin="4,4,4,0" />
              </DockPanel>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </ToggleButton.Style>
    </ToggleButton>
    <Grid Grid.Row="1" x:Name="PART_Grid" Visibility="Collapsed" 
          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
          VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
          Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
        <ContentPresenter Content="{Binding RelativeSource=
                          {RelativeSource TemplatedParent},Path=Content}" />
    </Grid>
   </Grid>
   <ControlTemplate.Triggers>
     <DataTrigger Binding="{Binding ElementName=PART_ToggleButton,Path=IsChecked}" 
                  Value="True">
       <Setter TargetName="PART_Grid" 
       Property="Visibility" Value="Visible"/>
     </DataTrigger>
   </ControlTemplate.Triggers>
  </ControlTemplate>
  </Setter.Value>
 </Setter>
</Style>

Once you have compiled the project, you will be able to use the control in MainWindow. I have put some dummy control inside, just to give you an idea. Insert the following XAML inside the <window> tag. The XAML for MainWindow is as given below:

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <local:GroupExpander Header="Single Customer" Grid.Row="0">
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
      </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Text="ID"/>
        <TextBox Grid.Row="0" Grid.Column="1" Text="101"/>
        <TextBlock Grid.Row="1" Grid.Column="0" Text="Name"/>
        <TextBox Grid.Row="1" Grid.Column="1" Text="Bob Wang"/>
        <Button Grid.Row="2" Content="Save"/>
      </Grid>
  </local:GroupExpander>
  <local:GroupExpander Grid.Row="1" Header="All Customers">
    <ListBox>
      <ListBoxItem>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="101"/>
          <TextBlock Text="Bob Wang"/>
        </StackPanel>
      </ListBoxItem>
      <ListBoxItem>
        <StackPanel Orientation="Horizontal">
          <TextBlock Text="102"/>
          <TextBlock Text="Tim Bret"/>
        </StackPanel>
      </ListBoxItem>
    </ListBox>
  </local:GroupExpander>
</Grid>

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here