Click here to Skip to main content
15,891,837 members
Articles / Desktop Programming / WPF
Tip/Trick

Custom Expander Control for WPF

Rate me:
Please Sign up or sign in to vote.
3.32/5 (4 votes)
7 Feb 2017CPOL1 min read 16.6K   388   2   1
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:

C#
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:

XAML
<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:

XML
<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionListVIew Pin
Fazil1326-Dec-20 3:44
Fazil1326-Dec-20 3:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.