sourcenouveau
sourcenouveau

Reputation: 30524

Preventing a WPF Expander from expanding when header is clicked

How can I prevent a WPF Expander from expanding when its header is clicked? I would like my Expander to expand or collapse only when the expand button itself is clicked.

I imagine the answer has something to do with canceling a bubbled event. If possible I would like to implement the solution in XAML while avoiding retemplating the entire Expander.

Upvotes: 14

Views: 22074

Answers (7)

Pawcio
Pawcio

Reputation: 490

As I've just checked, the header is named "HeaderSite". You can use method like the one described in this post in order to find child by name. When you have the child you can just change visibility of it. Also make sure the expander is loaded, otherwise you wont see the child.

expander.Loaded += (s, e) => {
    var hv = expander.findChildByName("HeaderSite");
    if (hv == null) return;
    hv.Visibility = Visibility.Collapsed;
};

I guess it's not guaranteed the name won't change and for me it's just cosmetic problem.

Upvotes: 0

Wouter
Wouter

Reputation: 2958

You could just set IsEnabled to false but in case you need to determine if you can expand or collapse when the header is clicked:

public class MyExpander : Expander
{
    private bool CanExpand() => true;

    private bool CanCollapse() => true;

    private bool rollback;

    protected override void OnExpanded()
    {
        if (rollback) return;
        if (CanExpand())
        {
            base.OnExpanded();
        }
        else
        {
            rollback = true;
            SetCurrentValue(IsExpandedProperty, false);
            rollback = false;
        }
    }

    protected override void OnCollapsed()
    {
        if (rollback) return;
        if (CanCollapse())
        {
            base.OnCollapsed();
        }
        else
        {
            rollback = true;
            SetCurrentValue(IsExpandedProperty, true);
            rollback = false;
        }
    }
}

Upvotes: 0

Michael Johnston
Michael Johnston

Reputation: 11

A rather simple solution is to modify the toggle button templates that the Expander control tempate uses in the following way.

  • Set the background of the grid and border to {x:Null}
  • Set the content presenter IsHitTestVisible to False

            <ControlTemplate TargetType="{x:Type ToggleButton}">
            <Border Padding="{TemplateBinding Padding}" Background="{x:Null}">
                <Grid Background="{x:Null}" SnapsToDevicePixels="False">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="19"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid>
                        <Grid.LayoutTransform>
                            <TransformGroup>
                                <TransformGroup.Children>
                                    <TransformCollection>
                                        <RotateTransform Angle="-90"/>
                                    </TransformCollection>
                                </TransformGroup.Children>
                            </TransformGroup>
                        </Grid.LayoutTransform>
                        <Ellipse x:Name="circle" HorizontalAlignment="Center" Height="19" Stroke="DarkGray" VerticalAlignment="Center" Width="19"/>
                        <Path x:Name="arrow" Data="M 1,1.5 L 4.5,5 L 8,1.5" HorizontalAlignment="Center" SnapsToDevicePixels="false" Stroke="#666" StrokeThickness="2" VerticalAlignment="Center"/>
                    </Grid>
                    <ContentPresenter HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"IsHitTestVisible="False"/>
                </Grid>
            </Border>
        </ControlTemplate>
    

Upvotes: 1

bugged87
bugged87

Reputation: 3142

There is actually a much simpler XAML solution than modifying templates. Simply DON'T use the Expander's header property in this case. Instead, cover the expander with your own styled TextBlock.

<Application.Resources>
    <Style x:Key="ExpanderHeader" TargetType="{x:Type TextBlock}">
        <Setter Property="Height" Value="22" />
        <Setter Property="Margin" Value="21,0,0,0" />
        <Setter Property="Padding" Value="9,3,0,0" />
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="VerticalAlignment" Value="Top" />
    </Style>
</Application.Resources>

<Grid>
    <Expander>
        <TextBlock Text="I am some content. I have disowned my default header." Margin="10,5" />
    </Expander>
    <TextBlock Text="I'm filling in for the default header. You'll like me better anyway."
               Style="{StaticResource ResourceKey=ExpanderHeader}"/>
</Grid>

Upvotes: 18

RobJohnson
RobJohnson

Reputation: 885

My solution was to leave the header blank and just tag on a control that acts as the header (using a canvas for absolute positioning), that way only the icon expands the control:

<Grid Height="{Binding ElementName=exp, Path=ActualHeight}">
    <Canvas>
        <Expander Name="exp">
        ...
        </Expander>
        <Control Style="{StaticResource ExpanderHeaderStyle}" Margin="20,0,0,0" />
     </Canvas>
</Grid>

Upvotes: 1

Julian Lettner
Julian Lettner

Reputation: 3669

I modified the default template for the Expander control as suggested by Simeon.

The following ControlTemplate defines an expander, which only reacts (expands/collapses) when the user clicks on the header icon.

This is quick and dirty so expect it to break.

<ControlTemplate x:Key="LazyExpanderTemplate" TargetType="Expander">
        <Border BorderThickness="{TemplateBinding Border.BorderThickness}" CornerRadius="3,3,3,3" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" SnapsToDevicePixels="True">
            <DockPanel>
                <DockPanel DockPanel.Dock="Top" Name="HeaderSite">
                    <ToggleButton 
                        DockPanel.Dock="Left"
                        IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded}"
                        Foreground="{TemplateBinding TextElement.Foreground}" 
                        FontFamily="{TemplateBinding TextElement.FontFamily}" 
                        FontSize="{TemplateBinding TextElement.FontSize}" 
                        FontStretch="{TemplateBinding TextElement.FontStretch}" 
                        FontStyle="{TemplateBinding TextElement.FontStyle}" 
                        FontWeight="{TemplateBinding TextElement.FontWeight}" 
                        HorizontalContentAlignment="{TemplateBinding Control.HorizontalContentAlignment}" 
                        VerticalContentAlignment="{TemplateBinding Control.VerticalContentAlignment}" 
                        Padding="{TemplateBinding Control.Padding}" 
                        MinWidth="0" 
                        MinHeight="0" 
                        Margin="1,1,1,1" 
                        >
                        <ToggleButton.Style>
                            <Style TargetType="ToggleButton">
                                <Setter Property="Control.Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="ToggleButton">
                                            <Border Padding="{TemplateBinding Control.Padding}">
                                                <Grid Background="#00FFFFFF" SnapsToDevicePixels="False">
                                                    <Grid.ColumnDefinitions>
                                                        <ColumnDefinition Width="19" />
                                                        <ColumnDefinition Width="*" />
                                                    </Grid.ColumnDefinitions>
                                                    <Ellipse Stroke="#FFA9A9A9" Name="circle" Width="19" Height="19" HorizontalAlignment="Center" VerticalAlignment="Center" />
                                                    <Ellipse Name="shadow" Width="17" Height="17" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden" />
                                                    <Path Data="M1,1.5L4.5,5 8,1.5" Stroke="#FF666666" StrokeThickness="2" Name="arrow" HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="False" />
                                                </Grid>
                                            </Border>
                                            <ControlTemplate.Triggers>
                                                <Trigger Property="ToggleButton.IsChecked" Value="True" >
                                                    <Setter Property="Path.Data" TargetName="arrow">
                                                        <Setter.Value>
                                                            <StreamGeometry>M1,4.5L4.5,1 8,4.5</StreamGeometry>
                                                        </Setter.Value>
                                                    </Setter>
                                                </Trigger>
                                                <Trigger Property="UIElement.IsMouseOver" Value="True">
                                                    <Setter Property="Shape.Stroke" TargetName="circle">
                                                        <Setter.Value>
                                                            <SolidColorBrush>#FF666666</SolidColorBrush>
                                                        </Setter.Value>
                                                    </Setter>
                                                    <Setter Property="Shape.Stroke" TargetName="arrow">
                                                        <Setter.Value>
                                                            <SolidColorBrush>#FF222222</SolidColorBrush>
                                                        </Setter.Value>
                                                    </Setter>
                                                    <Setter Property="UIElement.Visibility" TargetName="shadow">
                                                        <Setter.Value>
                                                            <x:Static Member="Visibility.Visible" />
                                                        </Setter.Value>
                                                    </Setter>
                                                </Trigger>
                                            </ControlTemplate.Triggers>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </ToggleButton.Style>
                        <ToggleButton.FocusVisualStyle>
                            <Style TargetType="IFrameworkInputElement">
                                <Setter Property="Control.Template">
                                    <Setter.Value>
                                        <ControlTemplate>
                                            <Border>
                                                <Rectangle Stroke="#FF000000" StrokeThickness="1" StrokeDashArray="1 2" Margin="0,0,0,0" SnapsToDevicePixels="True" />
                                            </Border>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </ToggleButton.FocusVisualStyle>
                    </ToggleButton>
                    <ContentPresenter
                        RecognizesAccessKey="True"
                        Content="{TemplateBinding HeaderedContentControl.Header}" 
                        ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}"
                        ContentStringFormat="{TemplateBinding HeaderedContentControl.HeaderStringFormat}"
                        Margin="4,0,0,0"
                        HorizontalAlignment="Left"
                        VerticalAlignment="Center"
                        SnapsToDevicePixels="True"
                        />
                </DockPanel>
                <ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" Name="ExpandSite" Margin="{TemplateBinding Control.Padding}" HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" Visibility="Collapsed" Focusable="False" DockPanel.Dock="Bottom" />
            </DockPanel>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="Expander.IsExpanded">
                <Setter Property="UIElement.Visibility" TargetName="ExpandSite">
                    <Setter.Value>
                        <x:Static Member="Visibility.Visible" />
                    </Setter.Value>
                </Setter>
                <Trigger.Value>
                    <s:Boolean>True</s:Boolean>
                </Trigger.Value>
            </Trigger>
            <Trigger Property="Expander.ExpandDirection" Value="Right">
                <Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Right"/>
                <Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Left"/>
                <Setter Property="FrameworkElement.Style" TargetName="HeaderSite">
                    <Setter.Value>
                        <Style TargetType="ToggleButton">
                            <Setter Property="Control.Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="ToggleButton">
                                        <Border Padding="{TemplateBinding Control.Padding}">
                                            <Grid Background="#00FFFFFF" SnapsToDevicePixels="False">
                                                <Grid.RowDefinitions>
                                                    <RowDefinition Height="19" />
                                                    <RowDefinition Height="*" />
                                                </Grid.RowDefinitions>
                                                <Grid>
                                                    <Grid.LayoutTransform>
                                                        <TransformGroup>
                                                            <TransformGroup.Children>
                                                                <RotateTransform Angle="-90" />
                                                            </TransformGroup.Children>
                                                        </TransformGroup>
                                                    </Grid.LayoutTransform>
                                                    <Ellipse Stroke="#FFA9A9A9" Name="circle" Width="19" Height="19" HorizontalAlignment="Center" VerticalAlignment="Center" />
                                                    <Ellipse Name="shadow" Width="17" Height="17" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden" />
                                                    <Path Data="M1,1.5L4.5,5 8,1.5" Stroke="#FF666666" StrokeThickness="2" Name="arrow" HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="False" />
                                                </Grid>
                                                <ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding ContentControl.Content}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" Margin="0,4,0,0" HorizontalAlignment="Center" VerticalAlignment="Top" SnapsToDevicePixels="True" Grid.Row="1" />
                                            </Grid>
                                        </Border>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="ToggleButton.IsChecked">
                                                <Setter Property="Path.Data" TargetName="arrow">
                                                    <Setter.Value>
                                                        <StreamGeometry>M1,4.5L4.5,1 8,4.5</StreamGeometry>
                                                    </Setter.Value>
                                                </Setter>
                                                <Trigger.Value>
                                                    <s:Boolean>True</s:Boolean>
                                                </Trigger.Value>
                                            </Trigger>
                                            <Trigger Property="UIElement.IsMouseOver">
                                                <Setter Property="Shape.Stroke" TargetName="circle">
                                                    <Setter.Value>
                                                        <SolidColorBrush>#FF666666</SolidColorBrush>
                                                    </Setter.Value>
                                                </Setter>
                                                <Setter Property="Shape.Stroke" TargetName="arrow">
                                                    <Setter.Value>
                                                        <SolidColorBrush>#FF222222</SolidColorBrush>
                                                    </Setter.Value>
                                                </Setter>
                                                <Setter Property="UIElement.Visibility" TargetName="shadow">
                                                    <Setter.Value>
                                                        <x:Static Member="Visibility.Visible" />
                                                    </Setter.Value>
                                                </Setter>
                                                <Trigger.Value>
                                                    <s:Boolean>True</s:Boolean>
                                                </Trigger.Value>
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </Setter.Value>
                </Setter>
            </Trigger>
            <Trigger Property="Expander.ExpandDirection">
                <Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Top"/>
                <Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Bottom"/>
                <Setter Property="FrameworkElement.Style" TargetName="HeaderSite">
                    <Setter.Value>
                        <Style TargetType="ToggleButton">
                            <Setter Property="Control.Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="ToggleButton">
                                        <Border Padding="{TemplateBinding Control.Padding}">
                                            <Grid Background="#00FFFFFF" SnapsToDevicePixels="False">
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="19" />
                                                    <ColumnDefinition Width="*" />
                                                </Grid.ColumnDefinitions>
                                                <Grid>
                                                    <Grid.LayoutTransform>
                                                        <TransformGroup>
                                                            <TransformGroup.Children>
                                                                <RotateTransform Angle="180" />
                                                            </TransformGroup.Children>
                                                        </TransformGroup>
                                                    </Grid.LayoutTransform>
                                                    <Ellipse Stroke="#FFA9A9A9" Name="circle" Width="19" Height="19" HorizontalAlignment="Center" VerticalAlignment="Center" />
                                                    <Ellipse Name="shadow" Width="17" Height="17" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden" />
                                                    <Path Data="M1,1.5L4.5,5 8,1.5" Stroke="#FF666666" StrokeThickness="2" Name="arrow" HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="False" />
                                                </Grid>
                                                <ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding ContentControl.Content}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" Margin="4,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" SnapsToDevicePixels="True" Grid.Column="1" />
                                            </Grid>
                                        </Border>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="ToggleButton.IsChecked">
                                                <Setter Property="Path.Data" TargetName="arrow">
                                                    <Setter.Value>
                                                        <StreamGeometry>M1,4.5L4.5,1 8,4.5</StreamGeometry>
                                                    </Setter.Value>
                                                </Setter>
                                                <Trigger.Value>
                                                    <s:Boolean>True</s:Boolean>
                                                </Trigger.Value>
                                            </Trigger>
                                            <Trigger Property="UIElement.IsMouseOver">
                                                <Setter Property="Shape.Stroke" TargetName="circle">
                                                    <Setter.Value>
                                                        <SolidColorBrush>#FF666666</SolidColorBrush>
                                                    </Setter.Value>
                                                </Setter>
                                                <Setter Property="Shape.Stroke" TargetName="arrow">
                                                    <Setter.Value>
                                                        <SolidColorBrush>#FF222222</SolidColorBrush>
                                                    </Setter.Value>
                                                </Setter>
                                                <Setter Property="UIElement.Visibility" TargetName="shadow">
                                                    <Setter.Value>
                                                        <x:Static Member="Visibility.Visible" />
                                                    </Setter.Value>
                                                </Setter>
                                                <Trigger.Value>
                                                    <s:Boolean>True</s:Boolean>
                                                </Trigger.Value>
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </Setter.Value>
                </Setter>
                <Trigger.Value>
                    <x:Static Member="ExpandDirection.Up" />
                </Trigger.Value>
            </Trigger>
            <Trigger Property="Expander.ExpandDirection">
                <Setter Property="DockPanel.Dock" TargetName="ExpandSite">
                    <Setter.Value>
                        <x:Static Member="Dock.Left" />
                    </Setter.Value>
                </Setter>
                <Setter Property="DockPanel.Dock" TargetName="HeaderSite">
                    <Setter.Value>
                        <x:Static Member="Dock.Right" />
                    </Setter.Value>
                </Setter>
                <Setter Property="FrameworkElement.Style" TargetName="HeaderSite">
                    <Setter.Value>
                        <Style TargetType="ToggleButton">
                            <Setter Property="Control.Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="ToggleButton">
                                        <Border Padding="{TemplateBinding Control.Padding}">
                                            <Grid Background="#00FFFFFF" SnapsToDevicePixels="False">
                                                <Grid.RowDefinitions>
                                                    <RowDefinition Height="19" />
                                                    <RowDefinition Height="*" />
                                                </Grid.RowDefinitions>
                                                <Grid>
                                                    <Grid.LayoutTransform>
                                                        <TransformGroup>
                                                            <TransformGroup.Children>
                                                                <RotateTransform Angle="90" />
                                                            </TransformGroup.Children>
                                                        </TransformGroup>
                                                    </Grid.LayoutTransform>
                                                    <Ellipse Stroke="#FFA9A9A9" Name="circle" Width="19" Height="19" HorizontalAlignment="Center" VerticalAlignment="Center" />
                                                    <Ellipse Name="shadow" Width="17" Height="17" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden" />
                                                    <Path Data="M1,1.5L4.5,5 8,1.5" Stroke="#FF666666" StrokeThickness="2" Name="arrow" HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="False" />
                                                </Grid>
                                                <ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding ContentControl.Content}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" Margin="0,4,0,0" HorizontalAlignment="Center" VerticalAlignment="Top" SnapsToDevicePixels="True" Grid.Row="1" />
                                            </Grid>
                                        </Border>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="ToggleButton.IsChecked">
                                                <Setter Property="Path.Data" TargetName="arrow">
                                                    <Setter.Value>
                                                        <StreamGeometry>M1,4.5L4.5,1 8,4.5</StreamGeometry>
                                                    </Setter.Value>
                                                </Setter>
                                                <Trigger.Value>
                                                    <s:Boolean>True</s:Boolean>
                                                </Trigger.Value>
                                            </Trigger>
                                            <Trigger Property="UIElement.IsMouseOver">
                                                <Setter Property="Shape.Stroke" TargetName="circle">
                                                    <Setter.Value>
                                                        <SolidColorBrush>#FF666666</SolidColorBrush>
                                                    </Setter.Value>
                                                </Setter>
                                                <Setter Property="Shape.Stroke" TargetName="arrow">
                                                    <Setter.Value>
                                                        <SolidColorBrush>#FF222222</SolidColorBrush>
                                                    </Setter.Value>
                                                </Setter>
                                                <Setter Property="UIElement.Visibility" TargetName="shadow">
                                                    <Setter.Value>
                                                        <x:Static Member="Visibility.Visible" />
                                                    </Setter.Value>
                                                </Setter>
                                                <Trigger.Value>
                                                    <s:Boolean>True</s:Boolean>
                                                </Trigger.Value>
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </Setter.Value>
                </Setter>
                <Trigger.Value>
                    <x:Static Member="ExpandDirection.Left" />
                </Trigger.Value>
            </Trigger>
            <Trigger Property="UIElement.IsEnabled">
                <Setter Property="TextElement.Foreground">
                    <Setter.Value>
                        <DynamicResource ResourceKey="{x:Static SystemColors.GrayTextBrushKey}" />
                    </Setter.Value>
                </Setter>
                <Trigger.Value>
                    <s:Boolean>False</s:Boolean>
                </Trigger.Value>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

Upvotes: 1

Simeon Pilgrim
Simeon Pilgrim

Reputation: 25968

The header is the button that creates the event, so you need to change the template for your Expander to just have the expander icon as the button.

Here is a post of how to change the expander.

Upvotes: 3

Related Questions