Share via


Walkthrough: Implementing In-Place Editing

This walkthrough shows how to implement in-place editing for a Windows Presentation Foundation (WPF) custom control. You can use this design-time feature in the Windows Presentation Foundation (WPF) Designer for Visual Studio to set the value of the Content property on a custom button control. For this walkthrough, the control is a simple button and the adorner is a text box that allows you to change the content of the button. 

In this walkthrough, you perform the following tasks:

  • Create a WPF custom control library project.

  • Create a separate assembly for design-time metadata.

  • Implement the adorner provider for in-place editing.

  • Test the control at design time.

When you are finished, you will know how create an adorner provider for a custom control.

Note

The dialog boxes and menu commands you see might differ from those described in Help depending on your active settings or edition. To change your settings, choose Import and Export Settings on the Tools menu. For more information, see Visual Studio Settings.

Prerequisites

You need the following components to complete this walkthrough:

  • Visual Studio 2008.

Creating the Custom Control

The first step is to create the project for the custom control. The control is a simple button with small amount of design-time code, which uses the GetIsInDesignMode method to implement a design-time behavior.

To create the custom control

  1. Create a new WPF Custom Control Library project in Visual C# named CustomControlLibrary.

    The code for CustomControl1 opens in the Code Editor.

  2. In Solution Explorer, change the name of the code file to DemoControl.cs. If a message box appears that asks if you want to perform a rename for all references in this project, click Yes.

  3. In Solution Explorer, expand the Themes folder.

  4. Double-click Generic.xaml.

    Generic.xaml opens in the WPF Designer.

  5. In XAML view, replace all occurrences of "CustomControl1" with "DemoControl".

  6. Open DemoControl.cs in the Code Editor.

  7. Replace the automatically generated code with the following code. The DemoControl custom control inherits from Button

    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace CustomControlLibrary
    {
        public class DemoControl : Button
        {   
        }
    }
    
  8. Set the project's output path to "bin\".

  9. Build the solution.

Creating the Design-time Metadata Assembly

Design-time code is deployed in special metadata assemblies. For more information, see How to: Use the Metadata Store. For this walkthrough, the custom adorner is supported by Visual Studio only and is deployed in an assembly named CustomControlLibrary.VisualStudio.Design.

To create the design-time metadata assembly

  1. Add a new Class Library project in Visual C# named CustomControlLibrary.VisualStudio.Design to the solution.

  2. Set the project's output path to "..\CustomControlLibrary\bin\". This keeps the control's assembly and the metadata assembly in the same folder, which enables metadata discovery for designers.

  3. Add references to the following WPF assemblies.

    • PresentationCore

    • PresentationFramework

    • WindowsBase

  4. Add references to the following WPF Designer assemblies.

    • Microsoft.Windows.Design

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  5. Add a reference to the CustomControlLibrary project.

  6. In Solution Explorer, change the name of the Class1 code file to Metadata.cs.

  7. Replace the automatically generated code with the following code. This code creates an AttributeTable which attaches the custom design-time implementation to the DemoControl class.

    using System;
    using Microsoft.Windows.Design.Features;
    using Microsoft.Windows.Design.Metadata;
    
    namespace CustomControlLibrary.VisualStudio.Design
    {
        // Container for any general design-time metadata to initialize. 
        // Designers look for a type in the design-time assembly that  
        // implements IRegisterMetadata. If found, designers instantiate  
        // this class and call its Register() method automatically. 
        internal class Metadata : IRegisterMetadata
        {
            // Called by the designer to register any design-time metadata. 
            public void Register()
            {
                AttributeTableBuilder builder = new AttributeTableBuilder();
    
                // Add the adorner provider to the design-time metadata.
                builder.AddCustomAttributes(
                    typeof(DemoControl),
                    new FeatureAttribute(typeof(InplaceButtonAdorners)));
    
                MetadataStore.AddAttributeTable(builder.CreateTable());
    
            }
        }
    }
    
  8. Save the solution.

Implementing the Adorner Provider

The adorner provider is implemented in a type named InplaceButtonAdorners. This adorner provider enables the user to set the control's Content property at design time.

To implement the adorner provider

  1. Add a new class named InplaceButtonAdorners to the CustomControlLibrary.VisualStudio.Design project.

  2. In the Code Editor for InplaceButtonAdorners, replace the automatically generated code with the following code. This code implements a PrimarySelectionAdornerProvider which provides an adorner based on a TextBox control.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Shapes;
    using Microsoft.Windows.Design.Interaction;
    using System.Windows.Data;
    using System.Windows.Input;
    using System.ComponentModel;
    using Microsoft.Windows.Design.Model;
    
    namespace CustomControlLibrary.VisualStudio.Design
    {
        // The InplaceButtonAdorners class provides two adorners:   
        // an activate glyph that, when clicked, activates in-place  
        // editing, and an in-place edit control, which is a text box. 
        internal class InplaceButtonAdorners : PrimarySelectionAdornerProvider
        {
            private Rectangle activateGlyph;
            private TextBox editGlyph;
            private AdornerPanel adornersPanel;
    
            public InplaceButtonAdorners()
            {
                adornersPanel = new AdornerPanel();
                adornersPanel.IsContentFocusable = true;
                adornersPanel.Children.Add(ActivateGlyph);
    
                Adorners.Add(adornersPanel);
            }
    
            private UIElement ActivateGlyph
            {
                get
                {
                    if (activateGlyph == null)
                    {
                        // The following code specifies the shape of the activate  
                        // glyph. This can also be implemented by using a XAML template.
                        Rectangle glyph = new Rectangle();
                        glyph.Fill = AdornerColors.HandleFillBrush;
                        glyph.Stroke = AdornerColors.HandleBorderBrush;
                        glyph.RadiusX = glyph.RadiusY = 2;
                        glyph.Width = 10;
                        glyph.Height = 5;
                        glyph.Cursor = Cursors.Hand;
    
                        ToolTipService.SetToolTip(
                            glyph, 
                            "Click to edit the text of the button.  " + 
                            "Enter to commit, ESC to cancel.");
    
                        // Position the glyph to the upper left of the DemoControl,  
                        // and slightly inside.
                        AdornerPlacementCollection placement = new AdornerPlacementCollection();
                        placement.PositionRelativeToContentHeight(0, 10);
                        placement.PositionRelativeToContentWidth(0, 5);
                        placement.SizeRelativeToAdornerDesiredHeight(1, 0);
                        placement.SizeRelativeToAdornerDesiredWidth(1, 0);
    
                        AdornerPanel.SetPlacements(glyph, placement);
    
                        // Add interaction to the glyph.  A click starts in-place editing.
                        ToolCommand command = new ToolCommand("ActivateEdit");
                        Task task = new Task();
                        task.InputBindings.Add(new InputBinding(command, new ToolGesture(ToolAction.Click)));
                        task.ToolCommandBindings.Add(new ToolCommandBinding(command, OnActivateEdit));
                        AdornerProperties.SetTask(glyph, task);
                        activateGlyph = glyph;
                    }
    
                    return activateGlyph;
                }
            }
            // When in-place editing is activated, a text box is placed  
            // over the control and focus is set to its input task.  
            // Its task commits itself when the user presses enter or clicks  
            // outside the control. 
            private void OnActivateEdit(object sender, ExecutedToolEventArgs args)
            {
                adornersPanel.Children.Remove(ActivateGlyph);
                adornersPanel.Children.Add(EditGlyph);
    
                // Once added, the databindings activate.  
                // All the text can now be selected.
                EditGlyph.SelectAll();
                EditGlyph.Focus();
    
                GestureData data = GestureData.FromEventArgs(args);
                Task task = AdornerProperties.GetTask(EditGlyph);
                task.Description = "Edit text";
                task.BeginFocus(data);
            }
    
            // The EditGlyph utility property creates a TextBox to use as  
            // the in-place editing control. This property centers the TextBox 
            // inside the target control and sets up data bindings between  
            // the TextBox and the target control. 
            private TextBox EditGlyph
            {
                get
                {
                    if (editGlyph == null)
                    {
                        TextBox glyph = new TextBox();
                        glyph.BorderThickness = new Thickness(0);
                        glyph.Margin = new Thickness(4);
    
                        AdornerPlacementCollection placement = new AdornerPlacementCollection();
                        placement.PositionRelativeToContentWidth(0, 0);
                        placement.PositionRelativeToContentHeight(0, 0);
                        placement.SizeRelativeToContentHeight(1, 0);
                        placement.SizeRelativeToContentWidth(1, 0);
    
                        AdornerPanel.SetPlacements(glyph, placement);
    
                        // Data bind the glyph's vertical and horizontal alignment 
                        // to the target control's alignment properties.
                        Binding binding = new Binding();
                        binding.Source = glyph;
                        binding.Path = new PropertyPath(
                            "(0).(1)", 
                            AdornerProperties.ActualViewProperty, 
                            Button.HorizontalContentAlignmentProperty);
                        glyph.SetBinding(TextBox.HorizontalContentAlignmentProperty, binding);
    
                        binding = new Binding();
                        binding.Source = glyph;
                        binding.Path = new PropertyPath(
                            "(0).(1)", 
                            AdornerProperties.ActualViewProperty, 
                            Button.VerticalContentAlignmentProperty);
                        glyph.SetBinding(TextBox.VerticalContentAlignmentProperty, binding);
    
                        // Make the glyph's background match the control's background. 
                        binding = new Binding();
                        binding.Source = glyph;
                        binding.Path = new PropertyPath(
                            "(0).(1)", 
                            AdornerProperties.ActualViewProperty, 
                            Button.BackgroundProperty);
                        glyph.SetBinding(TextBox.BackgroundProperty, binding);
    
                        // Two-way data bind the text box's text property to content.
                        binding = new Binding();
                        binding.Source = glyph;
                        binding.Path = new PropertyPath("(0).(1)[Content].(2)",
                          AdornerProperties.ActualModelProperty,
                          TypeDescriptor.GetProperties(
                              typeof(ModelItem))["Properties"],
                              TypeDescriptor.GetProperties(
                                  typeof(ModelProperty))["ComputedValue"]);
                        binding.Mode = BindingMode.TwoWay;
                        binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
                        binding.Converter = new ContentConverter();
                        glyph.SetBinding(TextBox.TextProperty, binding);
    
                        // Create a task that describes the UI interaction.
                        ToolCommand commitCommand = new ToolCommand("Commit Edit");
                        Task task = new Task();
                        task.InputBindings.Add(
                            new InputBinding(
                                commitCommand, 
                                new KeyGesture(Key.Enter)));
    
                        task.ToolCommandBindings.Add(
                            new ToolCommandBinding(commitCommand, delegate
                        {
                            task.Complete();
                        }));
    
                        task.FocusDeactivated += delegate
                        {
                            adornersPanel.Children.Remove(EditGlyph);
                            adornersPanel.Children.Add(ActivateGlyph);
                        };
    
                        AdornerProperties.SetTask(glyph, task);
    
                        editGlyph = glyph;
                    }
    
                    return editGlyph;
                }
            }
    
            // The ContentConverter class ensures that only strings 
            // are assigned to the Text property of EditGlyph. 
            private class ContentConverter : IValueConverter
            {
                public object Convert(
                    object value, 
                    Type targetType, 
                    object parameter, 
                    System.Globalization.CultureInfo culture)
                {
                    if (value is string)
                    {
                        return value;
                    }
    
                    return string.Empty;
                }
    
                public object ConvertBack(
                    object value, 
                    Type targetType, 
                    object parameter, 
                    System.Globalization.CultureInfo culture)
                {
                    return value;
                }
            }
        }
    }
    
  3. Build the solution.

Testing the Design-time Implementation

You can use the DemoControl class as you would use any other WPF control. The WPF Designer handles the creation of all design-time objects.

To test the design-time implementation

  1. Add a new WPF Application project in Visual C# named DemoApplication to the solution.

    Window1.xaml opens in the WPF Designer.

  2. Add a reference to the CustomControlLibrary project.

  3. In XAML view, replace the automatically generated XAML with the following XAML. This XAML adds a reference to the CustomControlLibrary namespace and adds the DemoControl custom control. If the control does not appear, you might have to click the Information bar at the top of the designer to reload the view.

    <Window x:Class="DemoApplication.Window1"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:ccl="clr-namespace:CustomControlLibrary;assembly=CustomControlLibrary"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <ccl:DemoControl></ccl:DemoControl>
        </Grid>
    </Window>
    
  4. Rebuild the solution.

  5. In Design view, click the DemoControl control to select it.

    A small Rectangle glyph appears in the upper-left corner of the DemoControl control.

  6. Click the Rectangle glyph to activate in-place editing.

    A text box appears showing the Content of DemoControl. Since the content is currently empty, you just see a cursor in the center of the button.

  7. Type in a new value for the text content, and then press the ENTER key.

    In XAML view, the Content property is set to the text value that you typed in Design view.

  8. Set the DemoApplication project as the startup project and run the solution.

    At run time, the button has the text value that you set with the adorner.

Next Steps

You can add more custom design-time features to your custom controls.

See Also

Other Resources

Creating Custom Editors

WPF Designer Extensibility