Search code examples
wpfxamldata-binding

Data binding in custom WPF controls


I'm completely stuck trying to bind an image to my custom WPF Expander.

I found an examle for creating expander template here: https://www.codeproject.com/Articles/248112/Templating-WPF-Expander-Control and tried to edit this to use an image instead of expander icon.

Here is my custom template for expander button (I added an image source here, so it works properly with straight resource path, not binding):

<ControlTemplate x:Key="SimpleExpanderButtonTemp" 
                TargetType="{x:Type ToggleButton}">
            <Border x:Name="ExpanderButtonBorder"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                Padding="{TemplateBinding Padding}"
                >
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Image 
                        Height="35"
                        Width="35"
                        Source="{Binding Path = ImageSource, 
                        RelativeSource={RelativeSource TemplatedParent}}">
                    </Image>
                    
                    <ContentPresenter x:Name="HeaderContent"
                          Grid.Column="1"
                          Margin="4,0,0,0"
                          ContentSource="Content"/>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <!-- MouseOver, Pressed behaviours-->
                <Trigger Property="IsMouseOver"
                         Value="true">
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

Afterwards, I add template to expander itself:

<ControlTemplate x:Key="SidePanelExpander" TargetType="Expander">
            <DockPanel>
                <ToggleButton x:Name="ExpanderButton"
                    DockPanel.Dock="Top"
                    ImageSource="{Binding Path = ImageSource,
                    RelativeSource={RelativeSource TemplatedParent}}"
                    Template="{StaticResource SimpleExpanderButtonTemp}"
                    Content="{TemplateBinding Header}"
                    IsChecked="{Binding Path=IsExpanded, 
                    RelativeSource={RelativeSource TemplatedParent}}"
                    OverridesDefaultStyle="True"
                    Padding="1.5,0">
                </ToggleButton>
                <ContentPresenter x:Name="ExpanderContent"
                          Visibility="Collapsed"
                          DockPanel.Dock="Bottom"/>
            </DockPanel>
            <ControlTemplate.Triggers>
                <Trigger Property="IsExpanded" Value="True">
                    <Setter TargetName="ExpanderContent" 
              Property="Visibility" Value="Visible"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

And then I'm trying to use it this way:

        <Expander ExpandDirection="Right"
                  Template="{StaticResource SidePanelExpander}"
                  ImageSource="../Res/Images/engine.png"
                  >

I guess there are some difficulties in data binding forwarding through templates, but have no idea on how to solve this.


Solution

  • As Clemens said in the comments, ToggleButton and Expander don't have a property called ImageSource, so this code was never going to work. The "correct" way to do this would be to create a custom control, but a quick fix (hack) would be to specify the image path using the Tag property:

    In the "SimpleExpanderButtonTemp" template, change the Image element Source as follows:

    <Image Height="35"
           Width="35"
           Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}" />
    

    Next, in the "SidePanelExpander" template ToggleButton, get rid of the "ImageSource=..." line and replace it with this:

    Tag="{TemplateBinding Tag}"
    

    Finally, in the control itself, specify your image:

    <Expander Tag="../Res/Images/engine.png"
              ...
    

    Also, in the first template, I've noticed you use TemplateBindings on certain properties of the Border control (Background, BorderBrush, BorderThickness). These won't work either. To fix this, copy those same lines to the ToggleButton control of the second template, i.e.

    <ToggleButton x:Name="ExpanderButton"
                  Background="{TemplateBinding Background}"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  BorderThickness="{TemplateBinding BorderThickness}"
                  ...
    

    You can now specify (say) a background colour in the control itself:

    <Expander Background="Blue"
              ...
    

    The TemplateBindings on the ToggleButton act like stepping stones, allowing the property value to pass from the control itself to the ToggleButton, then on to the Border.