Friday, July 26, 2013

Editable Button in WPF

Today I am going to share a code snippet to create an Editable Button in WPF, where we can update the text of a Button at runtime. In this control the user would right-click a button and then click 'Edit' to make to control Editable, and later right-click it to save/cancel the change.

First step to make the Button editable is to change the Style of Button and add a TextBox inside it, which we will make visible when the user clicks on 'Edit'.

01.<Style TargetType="my:EditableButton">
02.    <Setter Property="FocusVisualStyle">
03.        <Setter.Value>
04.            <Style>
05.                <Setter Property="Control.Template">
06.                    <Setter.Value>
07.                        <ControlTemplate>
08.                            <Rectangle Margin="2" SnapsToDevicePixels="True" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
09.                        </ControlTemplate>
10.                    </Setter.Value>
11.                </Setter>
12.            </Style>
13.        </Setter.Value>
14.    </Setter>
15.    <Setter Property="Background" Value="#FFDDDDDD"/>
16.    <Setter Property="BorderBrush" Value="#FF707070"/>
17.    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
18.    <Setter Property="BorderThickness" Value="1"/>
19.    <Setter Property="HorizontalContentAlignment" Value="Center"/>
20.    <Setter Property="VerticalContentAlignment" Value="Center"/>
21.    <Setter Property="Padding" Value="1"/>
22.    <Setter Property="Template">
23.        <Setter.Value>
24.            <ControlTemplate TargetType="{x:Type Button}">
25.                <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
26.                    <Grid>
27.                        <ContentPresenter x:Name="tbkContent" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
28.                        <TextBox   Name="txtContent" Height="{TemplateBinding Height}" TextAlignment="Center" Visibility="Collapsed" Text="{TemplateBinding Content}" Padding="0" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"  VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}"/>
29.                    </Grid>
30.                </Border>
31.                <ControlTemplate.Triggers>
32.                    <Trigger Property="IsDefaulted" Value="True">
33.                        <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
34.                    </Trigger>
35.                    <Trigger Property="IsMouseOver" Value="True">
36.                        <Setter Property="Background" TargetName="border" Value="#FFBEE6FD"/>
37.                        <Setter Property="BorderBrush" TargetName="border" Value="#FF3C7FB1"/>
38.                    </Trigger>
39.                    <Trigger Property="IsPressed" Value="True">
40.                        <Setter Property="Background" TargetName="border" Value="#FFC4E5F6"/>
41.                        <Setter Property="BorderBrush" TargetName="border" Value="#FF2C628B"/>
42.                    </Trigger>
43.                    <Trigger Property="ToggleButton.IsChecked" Value="True">
44.                        <Setter Property="Background" TargetName="border" Value="#FFBCDDEE"/>
45.                        <Setter Property="BorderBrush" TargetName="border" Value="#FF245A83"/>
46.                    </Trigger>
47.                    <Trigger Property="IsEnabled" Value="False">
48.                        <Setter Property="Background" TargetName="border" Value="#FFF4F4F4"/>
49.                        <Setter Property="BorderBrush" TargetName="border" Value="#FFADB2B5"/>
50.                        <Setter Property="TextElement.Foreground" TargetName="tbkContent" Value="#FF838383"/>
51.                    </Trigger>
52.                </ControlTemplate.Triggers>
53.            </ControlTemplate>
54.        </Setter.Value>
55.    </Setter>
56.</Style>

As you can see in the above code, I have added a "txtContent" textbox which is hidden. Also to make editing look like a part of the Button, the Background and BorderBrush properties of textbox are transparent

01.Public Class EditableButton
02.    Inherits Button
03.    Private mButtonContentPresenter As ContentPresenter
04.    Private mButtonTextBox As TextBox
05. 
06.    Public Sub New()
07.        Me.DefaultStyleKey = GetType(EditableButton)
08.        AddHandler Me.Loaded, New RoutedEventHandler(AddressOf EditableButton_Loaded)
09.    End Sub
10. 
11.    Private Sub EditableButton_Loaded(sender As Object, e As RoutedEventArgs)
12. 
13.        Me.OnApplyTemplate()
14.    End Sub
15. 
16.    Public Overrides Sub OnApplyTemplate()
17.        MyBase.OnApplyTemplate()
18.        mButtonContentPresenter = DirectCast(Me.GetTemplateChild("tbkContent"), ContentPresenter)
19.        mButtonTextBox = DirectCast(Me.GetTemplateChild("txtContent"), TextBox)
20.        CreateSaveContextMenu()
21.        CreateEditContextMenu()
22.    End Sub
23. 
24.    Public Property IsEditMode() As Boolean
25.        Get
26.            Return CBool(GetValue(IsEditModeProperty))
27.        End Get
28.        Private Set(value As Boolean)
29.            SetValue(IsEditModeProperty, value)
30.        End Set
31.    End Property
32. 
33.    ' Using a DependencyProperty as the backing store for IsEditMode.  This enables animation, styling, binding, etc...
34.    Public Shared ReadOnly IsEditModeProperty As DependencyProperty = DependencyProperty.Register("IsEditMode", GetType(Boolean), GetType(EditableButton), New PropertyMetadata(False))
35. 
36.    Private Sub CreateSaveContextMenu()
37.        Dim menu As New ContextMenu
38.        Dim itm As New MenuItem()
39.        itm.Header = "Save"
40.        AddHandler itm.Click, New RoutedEventHandler(AddressOf itmSave_Click)
41.        menu.Items.Add(itm)
42. 
43.        itm = New MenuItem()
44.        itm.Header = "Cancel"
45.        AddHandler itm.Click, New RoutedEventHandler(AddressOf itmCancel_Click)
46.        menu.Items.Add(itm)
47. 
48.        ContextMenuService.SetContextMenu(mButtonTextBox, menu)
49.    End Sub
50. 
51. 
52. 
53.    Private Sub CreateEditContextMenu()
54.        Dim menu As New ContextMenu
55.        Dim itm As New MenuItem
56.        itm.Header = "Edit"
57.        AddHandler itm.Click, New RoutedEventHandler(AddressOf itmEdit_Click)
58.        menu.Items.Add(itm)
59. 
60.        ContextMenuService.SetContextMenu(mButtonContentPresenter, menu)
61.    End Sub
62. 
63.    Private Sub itmEdit_Click(sender As Object, e As RoutedEventArgs)
64.        mButtonContentPresenter.Visibility = Visibility.Collapsed
65.        mButtonTextBox.Visibility = Visibility.Visible
66.        IsEditMode = True
67.    End Sub
68. 
69.    Private Sub itmSave_Click(sender As Object, e As RoutedEventArgs)
70.        mButtonContentPresenter.Visibility = Visibility.Visible
71.        mButtonTextBox.Visibility = Visibility.Collapsed
72.        Me.Content = mButtonTextBox.Text
73.        IsEditMode = False
74.    End Sub
75. 
76.    Private Sub itmCancel_Click(sender As Object, e As RoutedEventArgs)
77.        mButtonContentPresenter.Visibility = Visibility.Visible
78.        mButtonTextBox.Visibility = Visibility.Collapsed
79.        IsEditMode = False
80.    End Sub
81.End Class

In the above code we have an "IsEditMode" property which the user can use to know whether the control is in Edit Mode or not. Also, we have two different Context Menus, one to Edit the Control, and the another to Save/Cancel the changes. Based on the menuitems that are clicked, we are going to set the visibility of TextBox and ContentPresenter, defined in XAML.

Now we can use the control in our application as below


01.<Window x:Class="MainWindow"
04.    xmlns:my="clr-namespace:WpfApplication1"
05.    Title="MainWindow" Height="350" Width="525">
06.   
07.    <Grid>
08.        <my:EditableButton x:Name="MyButton" Width="100" Height="25" Content="Edit Me"/>
09.    </Grid>
10.</Window>

Hope the above control helps you in your development.