Editable Label in WPF

While still in the dark woods of learning WPF, I was completely lost trying to fulfill a requirement to make an editable label that showed a formatted value when not selected. In my case, the formatted value would round a decimal value to a few digits to make long number more readable.

I asked over on StackOverflow, but didn’t get any responses and ended up posting a link to a real half-baked solution. That solution disallowed me from restyling an individual instance, and forced me to use OnPropertyChanged when binding to it. Yikes!

Now that I’ve fought through a bit more, I have a much better solution and in pure XAML!

<ControlTemplate x:Key="EditableDecimalTemplate" TargetType="{x:Type ContentControl}">
    <ContentPresenter Name="contentHolder" 
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            RecognizesAccessKey="True" 
                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
        <ContentPresenter.Content>
            <Grid Margin="0">
                <Border Name="nonFocusedBorder"
                        Grid.ZIndex="3" IsHitTestVisible="False"
                        BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" 
                        Background="{TemplateBinding Background}" 
                        />
                <TextBox Name="editTextBox"
                            Grid.ZIndex="1" Opacity="0"
                            Margin="0" Padding="{TemplateBinding Padding}"
                            HorizontalAlignment="Stretch" VerticalAlignment="Center"
                            TextAlignment="Right"
                            Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay}"
                            BorderThickness="{TemplateBinding BorderThickness}" 
                            />
                <Border BorderBrush="{x:Null}" Height="{Binding ElementName=editTextBox, Path=ActualHeight}" Margin="0,0,3,0"
                        Padding="{TemplateBinding BorderThickness}">
                    <TextBlock Name="displayTextBlock" 
                                Grid.ZIndex="2" IsHitTestVisible="False"
                                VerticalAlignment="Center" HorizontalAlignment="Stretch" 
                                Margin="{TemplateBinding Padding}"
                                TextAlignment="Right"
                                Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, StringFormat={}{0:#.###;-#.###;0}, Mode=OneWay}"
                                />
                </Border>
                <Border/>
            </Grid>
        </ContentPresenter.Content>
    </ContentPresenter>
    <ControlTemplate.Triggers>
        <Trigger SourceName="editTextBox" Property="IsKeyboardFocused" Value="True">
            <Setter TargetName="displayTextBlock" Property="Opacity" Value="0" />
            <Setter TargetName="editTextBox" Property="Opacity" Value="1" />
            <Setter TargetName="nonFocusedBorder" Property="Visibility" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="BorderThickness" Value="0">
            <Setter TargetName="editTextBox" Property="BorderThickness" Value="1" />
            <Setter TargetName="nonFocusedBorder" Property="BorderThickness" Value="1" />
            <Setter TargetName="nonFocusedBorder" Property="BorderBrush" Value="Transparent" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="EditableDecimalLabel" TargetType="{x:Type Label}">
    <Setter Property="Template" Value="{StaticResource EditableDecimalTemplate}" />
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
    <Setter Property="Padding" Value="4" />
    <Setter Property="FontFamily" Value="Consolas, Lucida Console, Courier New"/>
    <Setter Property="TextElement.FontSize" Value="13" />
    <Setter Property="SnapsToDevicePixels" Value="True"/>
    <Setter Property="BorderThickness" Value="1" />
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="BorderBrush" Value="#B5CFFF"/>
        </Trigger>
    </Style.Triggers>
</Style>

Sample usage:

<Label Name="TestControl"
       Width="120"
       Content="{Binding Path=MyNumber, Mode=TwoWay}"
       Style="{StaticResource EditableDecimalLabel}"
       />

So what we have here is a ControlTemplate combined with a Style. The ControlTemplate overrides the usual content of the label and we have a style to hook up the template, stretch our content template to fit the size of the label, and make things look generally pretty.

You’ll notice when we’re binding to the content of the Label, we have to use a RelativeSource TemplatedParent binding so we can specify that we’re TwoWay binding on the TextBox and to give the string format on the TextBlock.

All of this is far easier than the alternatives I’ve seen, but it required some deft taming of the WPF.

This entry was posted in Coding and tagged .

3 thoughts on “Editable Label in WPF

  1. Very useful – I’m still new to WPF though, and I@m wondering how I would modify this to force the Enter or Return keys to lose focus. Currently, you either have to hit Tab or click outside the control

    Thanks

  2. @Rob That’s will definitely require an event handler in code-behind. That said, you can always create a global event handler somewhere and bind using “{x:Static …}”.

Leave a Reply

Your email address will not be published. Required fields are marked *