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.