Showing posts with label Silverlight. Show all posts
Showing posts with label Silverlight. Show all posts

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.

Sunday, October 7, 2012

MultiPanel Control in WPF/Silverlight

Many time during designing we require different pages similar to a TabControl but without displaying Tabs. There are few article to create similar control for Windows Form and ASP.NET but not much for WPF or Silverlight. So I decide the create my own Custom MultiPanel.
MultiPanel is a simple control which inherits an ItemsControl and has SelectedIndex and SelectedPanel property to change the selected panel.
Public Class MultiPanel
    Inherits ItemsControl

    Public Sub New()
        MyBase.New()
        Me.DefaultStyleKey = GetType(MultiPanel)
    End Sub

    Public Property SelectedIndex() As Integer
        Get
            Return CInt(GetValue(SelectedIndexProperty))
        End Get

        Set(value As Integer)
            SetValue(SelectedIndexProperty, value)
        End Set
    End Property

    Public Event SelectionChanged As EventHandler(Of EventArgs)

    Public Shared ReadOnly SelectedIndexProperty As DependencyProperty = DependencyProperty.Register("SelectedIndex", GetType(Integer), GetType(MultiPanel), New PropertyMetadata(-1, New PropertyChangedCallback(AddressOf SelectedIndexChanged)))

    Public Property SelectedPanel() As Object
        Get
            Return GetValue(SelectedPanelProperty)
        End Get

        Set(value As Object)
            SetValue(SelectedPanelProperty, value)
        End Set
    End Property


    Public Shared ReadOnly SelectedPanelProperty As DependencyProperty = DependencyProperty.Register("SelectedPanel", GetType(Object), GetType(MultiPanel), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf SelectedPanelChanged)))

    Private Shared Sub SelectedIndexChanged(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim mp As MultiPanel = DirectCast(sender, MultiPanel)
        Dim index As Integer = Integer.Parse(e.NewValue.ToString())
        Dim oldIndex As Integer = Integer.Parse(e.OldValue.ToString())

        If index <> -1 AndAlso index <> oldIndex Then
            mp.SelectedPanel = mp.Items(index)
        ElseIf index = -1 Then
            mp.SelectedPanel = Nothing
        End If

        mp.RaiseSelectionChanged()
    End Sub

    Private Sub RaiseSelectionChanged()
        RaiseEvent SelectionChanged(Me, New EventArgs())
    End Sub

    Private Shared Sub SelectedPanelChanged(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim mp As MultiPanel = DirectCast(sender, MultiPanel)
        Dim pnl As Panel = DirectCast(e.NewValue, Panel)

        If pnl Is Nothing OrElse mp.Items.IndexOf(pnl) = -1 Then
            mp.SelectedPanel = Nothing
            mp.SelectedIndex = -1
        Else
            mp.SelectedPanel = pnl
            mp.SelectedIndex = mp.Items.IndexOf(pnl)
        End If
    End Sub

   
End Class



And below is the MultiPanel Style

    <Style TargetType="local:MultiPanel">
        <Setter Property="HorizontalAlignment" Value="Stretch"/>
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        <Setter Property="VerticalAlignment" Value="Stretch"/>
        <Setter Property="VerticalContentAlignment" Value="Stretch"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:MultiPanel">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" >

                        <ContentPresenter Content="{TemplateBinding SelectedPanel}"
                                                Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>



Below is a simple XAML Code example of using this control

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:WpfApplication2"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <my:MultiPanel x:Name="MainMultiPanel">
            <my:MultiPanel.Items>
                <Grid Name="grdCustomer">
                    <my:CustomerDetailControl />
                </Grid>
                <Grid Name="grdEmployee">
                    <my:EmployeeDetailControl />
                </Grid>
                <Grid Name="grdOrder">
                    <my:OrderDetailControl />
                </Grid>
            </my:MultiPanel.Items>
        </my:MultiPanel>
    </Grid>
</Window>



Now you can use SelectedIndex or SelectedPanel property of MultiPanel to display the required grid

MainMultiPanel.SelectedIndex = 2


Hope this control help you in your designing task. Look forward to your feedbacks.