WPF: Editable Behavior for Labels


** updated this post for Blend 3 RC release
I’ve worked on a couple of business applications at work and whether it’s Web or Desktop, you almost always require some sort of view page/screen to see the data you asked for, e.g. customer details, order details…etc. Not surprisingly, there’s usually a corresponding Edit page/screen to allow users to modify the same details. What this means is that you will be required to develop two sets of screens for the same data, one to view and one for users to edit. In some cases, the two screens can be the same in terms of layout, with the exception of using Labels for viewing and Textboxes, Dropdowns..etc for editing. Is there a way we can avoid having two screens?

As a user, I would definitely prefer to be able to perform the editing on the same page/screen I’m currently viewing the data on, and not need to navigate to some other place. Therefore I came up with an idea that utilizes WPF Behaviors. By “tagging” your Labels with my Editable Behaviors, you can transform read-only Labels to input controls. When you mouse hovers over the Label, an “Edit Button” fades in (somewhat like MS Word when you highlight a block of text), and clicking it will switch that Label into editing mode. I have created two kinds of behaviors, one that changes your Label into a TextBox and another to a ComboBox. By the way, all these is done via Adorners. If you are unfamilar with Adorners and Behaviors, have a look at my other post. Here are some screenshots.

EditableBehavior1EditableBehavior2

I’ve also created 3 different Commands that allow you to hook up to e.g. MenuItems to trigger all your Labels to go into Edit mode, or to Save all and Cancel all. The behaviors also expose a OnSaving event that allows you to put your custom validation logic, and allows you to set an error message and cancel the saving via the event args properties. These commands are made possible with the EditableBehaviorManager class I created that. The Behaviors will register with the Manager when they are attached (OnAttached method), and the Manager takes care of delegating the commands to all registered behaviors.

In order to promote re-usability, I have abstracted out the behavior and adorners into abstract base classes, and the Textbox Behavior and ComboBox Behavior and their associated adorners inherit from them. So if you want to create other editable behaviors e.g. Label to DatePicker, you can easily do so by subclassing a new behavior and adorner, and off you go. To keep my post short and hopefully more readable, I won’t be going to review every aspect of the code. Please download the sample and have a look. Instead, below is some Xaml that shows you how to hook up the behavior.

Hooking up Commands to MenuItems

<Menu DockPanel.Dock="Top">
    <MenuItem Header="Edit All" Command="{x:Static lib:EditableBehaviorManager.EditAll}" />
    <MenuItem Header="Save All" Command="{x:Static lib:EditableBehaviorManager.SaveAll}" />
    <MenuItem Header="Cancel All" Command="{x:Static lib:EditableBehaviorManager.CancelAll}" />
</Menu>

Hooking up Behaviors to Labels

<Label Content="{Binding FirstName, Mode=TwoWay}" Grid.Column="1" Grid.Row="0"
    Style="{StaticResource EditableLabel}">
    <i:Interaction.Behaviors>
        <lib:EditableTextBoxBehavior MinimumEditWidth="150" OnSaving="NameEditableBehavior_OnSaving" />
    </i:Interaction.Behaviors>
</Label>
<Label Content="{Binding LastName, Mode=TwoWay}" Grid.Column="1" Grid.Row="1"
    Style="{StaticResource EditableLabel}">
    <i:Interaction.Behaviors>
        <lib:EditableTextBoxBehavior MinimumEditWidth="150" OnSaving="NameEditableBehavior_OnSaving"/>
    </i:Interaction.Behaviors>
</Label>
<Label Content="{Binding Age, Mode=TwoWay}" Grid.Column="1" Grid.Row="2"
    Style="{StaticResource EditableLabel}">
    <i:Interaction.Behaviors>
        <lib:EditableTextBoxBehavior MinimumEditWidth="150" OnSaving="AgeEditableBehavior_OnSaving"/>
    </i:Interaction.Behaviors>
</Label>
<Label Content="{Binding Gender, Mode=TwoWay}" Grid.Column="1" Grid.Row="3"
    Style="{StaticResource EditableLabel}">
    <i:Interaction.Behaviors>
        <lib:EditableComboBoxBehavior x:Name="comboBoxBehavior"
            MinimumEditWidth="75" DisplayMemberPath="Description" SelectedValuePath="Identifier" />
   </i:Interaction.Behaviors>
</Label>

In the Xaml above, I binded my labels to a Person object, so when the editing completes, my Person object is also updated via the TwoWay binding mode. The last label is hooked up to a EditableComboBoxBehavior, where you will need to specify the DisplayMemberPath and SelectedValuePath. These properties map to the properties of the object you are binding to. In this example, I binded the ComboBox values to a Gender class with properties Description and Identifier. In case you’re wondering how I’m specifying the data source of the ComboBox in the behavior, I wired it up in the Code Behind using the behavior’s ItemsSource property. Below is a snippet of the Code Behind.

public partial class Window1 : Window
{
    private Regex regex = new Regex(@"^\d+$");

    public Window1()
    {
        InitializeComponent();
        this.Loaded += new RoutedEventHandler(Window1_Loaded);
    }

    void Window1_Loaded(object sender, RoutedEventArgs e)
    {
        grid.DataContext = new Person() { FirstName = "Ed", LastName = "Foh", Age = 25, Gender='M' };
        comboBoxBehavior.ItemsSource = ReferenceData.GetGenders();
    }

    private void AgeEditableBehavior_OnSaving(object arg1, EditableBehaviorLibrary.OnSaveEventArgs arg2)
    {
        if (string.IsNullOrEmpty(arg2.NewValue) || !regex.IsMatch(arg2.NewValue))
        {
            arg2.Cancel = true;
            arg2.ErrorMessage = "Please enter a valid age";
        }
    }

    private void NameEditableBehavior_OnSaving(object arg1, EditableBehaviorLibrary.OnSaveEventArgs arg2)
    {
        if (string.IsNullOrEmpty(arg2.NewValue))
        {
            arg2.Cancel = true;
            arg2.ErrorMessage = "Please enter a value";
        }
    }

}

I hope this has been helpful, and if you found some use for this or have feedback, let me know.

Download the updated sample code here. (updated this sample to work with Blend 3 RC release)

Share this post :
Advertisements
Posted in WPF. Tags: . 5 Comments »

5 Responses to “WPF: Editable Behavior for Labels”

  1. Brent Schooley Says:

    This behavior will not work with Blend 3 RC due to the change in the DLL that is used for the Interactivity library. Any chance of an update that uses the new DLL?

    • Ed Foh Says:

      Hi Brent

      thanks for bringing that to my attention. I am going to update the solution shortly, and I will put a link for you to download the updated sample.

      FYI, all the new built-in core behaviors you see in Blend 3 RC are found in the Microsoft.Expression.Interactions.dll and the library we use for creating new behaviors are in the System.Windows.Interactivity.dll, found in “C:\Program Files\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\WPF” folder if you have installed Blend 3 RC. But these assemblies should also be in your GAC as well. Hope that helps.

  2. Plaimislani Says:

    Stunning, I did not know about this topic up to now. Thanx.

  3. ido Says:

    Thanks. Found it very useful to make it like apple does in the icloud.
    Question: I want to catch event of selectionchanged in the EditableComboBoxBehavior. What should I add ?

    Thanks.

  4. id1010 Says:

    Hi,
    I want to create a textbox in xaml that has an errortemplate to show when the ValidationError method is called.
    I do not manage to do that.
    Any ideas ?
    Thanks


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: