Click here to Skip to main content
13,768,886 members
Click here to Skip to main content
Add your own
alternative version

Stats

6.8K views
212 downloads
2 bookmarked
Posted 7 Feb 2017
Licenced CPOL

Custom Expander Control for WPF

, 7 Feb 2017
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

shahidul.haq
United Kingdom United Kingdom
No Biography provided

You may also be interested in...

Pro

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web05-2016 | 2.8.181117.1 | Last Updated 7 Feb 2017
Article Copyright 2017 by shahidul.haq
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid