Compartir a través de


Tutorial: Implementar la edición en contexto

En este tutorial se muestra cómo implementar la edición en contexto para un control personalizado de Windows Presentation Foundation (WPF). Puede utilizar esta característica en tiempo de diseño en WPF Designer for Visual Studio para establecer el valor de la propiedad Content en un control de botón personalizado. En este tutorial, el control es un botón simple y el adorno es un cuadro de texto que permite cambiar el contenido del botón.

En este tutorial realizará las siguientes tareas:

  • Crear un proyecto de biblioteca de controles personalizados de WPF.

  • Crear un ensamblado independiente para los metadatos de tiempo de diseño.

  • Implementar el proveedor de adornos para la edición en contexto.

  • Probar el control en tiempo de diseño.

Cuando haya finalizado, sabrá crear un proveedor de adornos para un control personalizado.

Nota

Los cuadros de diálogo y comandos de menú que se ven pueden diferir de los descritos en la Ayuda, en función de los valores de configuración o de edición activos. Para cambiar la configuración, elija la opción Importar y exportar configuraciones del menú Herramientas. Para obtener más información, vea Trabajar con valores de configuración.

Requisitos previos

Necesita los componentes siguientes para completar este tutorial:

  • Visual Studio 2010.

Crear el control personalizado

El primer paso consiste en crear el proyecto para el control personalizado. El control es un botón simple con poco código en tiempo de diseño, que utiliza el método GetIsInDesignMode para implementar un comportamiento en tiempo de diseño.

Para crear el control personalizado

  1. Cree un nuevo proyecto de biblioteca de controles personalizados de WPF en Visual C# denominado CustomControlLibrary.

    El código de CustomControl1 se abre en el editor de código.

  2. En el Explorador de soluciones, cambie el nombre del archivo de código a DemoControl.cs. Si se muestra un cuadro de mensaje que le pregunta si desea cambiar el nombre de todas las referencias de este proyecto, haga clic en .

  3. Abra DemoControl.cs en el editor de código.

  4. Reemplace el código generado automáticamente por el código siguiente. El control personalizado DemoControl hereda de Button

    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace CustomControlLibrary
    {
        public class DemoControl : Button
        {   
        }
    }
    
  5. Establezca la ruta de acceso de los resultados del proyecto en "bin\".

  6. Genere la solución.

Crear el ensamblado de metadatos en tiempo de diseño

El código en tiempo de diseño se implementa en ensamblados de metadatos especiales. En este tutorial, el adorno personalizado sólo lo admite Visual Studio y se implementa en un ensamblado denominado CustomControlLibrary.VisualStudio.Design. Para obtener más información, vea Proporcionar metadatos en tiempo de diseño.

Para crear el ensamblado de metadatos en tiempo de diseño

  1. Agregue a la solución un nuevo proyecto de biblioteca de clases en Visual C# denominado CustomControlLibrary.VisualStudio.Design.

  2. Establezca la ruta de acceso de salida del proyecto en ".. \CustomControlLibrary\bin\". De este modo, el ensamblado del control y el ensamblado de metadatos permanecen en la misma carpeta, lo que permite que los diseñadores detecten los metadatos.

  3. Agregue referencias a los siguientes ensamblados de WPF.

    • PresentationCore

    • PresentationFramework

    • System.Xaml

    • WindowsBase

  4. Agregue referencias a los siguientes ensamblados de WPF Designer.

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  5. Agregue una referencia al proyecto CustomControlLibrary.

  6. En el Explorador de soluciones, cambie el nombre del archivo de código Class1 a Metadata.cs.

  7. Reemplace el código generado automáticamente por el código siguiente. Este código crea un objeto AttributeTable que asocia la implementación en tiempo de diseño personalizada a la clase DemoControl.

    using System;
    using Microsoft.Windows.Design.Features;
    using Microsoft.Windows.Design.Metadata;
    
    // The ProvideMetadata assembly-level attribute indicates to designers
    // that this assembly contains a class that provides an attribute table. 
    [assembly: ProvideMetadata(typeof(CustomControlLibrary.VisualStudio.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 IProvideAttributeTable. If found, designers instantiate 
        // this class and access its AttributeTable property automatically.
        internal class Metadata : IProvideAttributeTable
        {
            // Accessed by the designer to register any design-time metadata.
            public AttributeTable AttributeTable
            {
                get 
                {
                    AttributeTableBuilder builder = new AttributeTableBuilder();
    
                    // Add the adorner provider to the design-time metadata.
                    builder.AddCustomAttributes(
                        typeof(DemoControl),
                        new FeatureAttribute(typeof(InplaceButtonAdorners)));
    
                    return builder.CreateTable();
                }
            }
        }
    }
    
  8. Guarde la solución.

Implementar el proveedor de adornos

El proveedor de adornos se implementa en un tipo denominado InplaceButtonAdorners. Este proveedor de adornos permite al usuario establecer la propiedad Content del control en tiempo de diseño.

Para implementar el proveedor de adornos

  1. Agregue una nueva clase denominada InplaceButtonAdorners al proyecto CustomControlLibrary.VisualStudio.Design.

  2. En el editor de código para InplaceButtonAdorners, reemplace el código generado automáticamente por el código siguiente. Este código implementa un PrimarySelectionAdornerProvider que proporciona un adorno basado en un control TextBox.

    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;
    //using SampleControls.Designer;
    using System.Windows.Media;
    using Microsoft.Windows.Design.Metadata;
    using System.Globalization;
    
    
    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;
            private ModelItem _editedItem;
    
            public InplaceButtonAdorners()
            {
                adornersPanel = new AdornerPanel();
                adornersPanel.IsContentFocusable = true;
                adornersPanel.Children.Add(ActivateGlyph);
    
                Adorners.Add(adornersPanel);
            }
    
            protected override void Activate(ModelItem item)
            {
                _editedItem = item;
                _editedItem.PropertyChanged += new PropertyChangedEventHandler(OnEditedItemPropertyChanged);
                base.Activate(item);
            }
    
    
            protected override void Deactivate()
            {
                if (_editedItem != null)
                {
                    _editedItem.PropertyChanged -= new PropertyChangedEventHandler(OnEditedItemPropertyChanged);
                    _editedItem = null;
                }
                base.Deactivate();
            }
    
            private ModelItem EditedItem
            {
                get
                {
                    return _editedItem;
                }
            }
            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 = 20;
                        glyph.Height = 15;
                        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.
                        AdornerPanel.SetAdornerHorizontalAlignment(glyph, AdornerHorizontalAlignment.Left);
                        AdornerPanel.SetAdornerVerticalAlignment(glyph, AdornerVerticalAlignment.Top);
                        AdornerPanel.SetAdornerMargin(glyph, new Thickness(5, 5, 0, 0));
    
                        // 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 && EditedItem != null)
                    {
                        TextBox glyph = new TextBox();
                        glyph.Padding = new Thickness(0);
                        glyph.BorderThickness = new Thickness(0);
    
                        UpdateTextBlockLocation(glyph);
    
                        // Make the background white to draw over the existing text.
                        glyph.Background = SystemColors.WindowBrush;
    
    
                        // Two-way data bind the text box's text property to content.
                        Binding binding = new Binding();
                        binding.Source = EditedItem;
                        binding.Path = new PropertyPath("Content");
                        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;
                }
            }
    
            private void UpdateTextBlockLocation(TextBox glyph)
            {
                Point textBlockLocation = FindTextBlock();
                AdornerPanel.SetAdornerMargin(glyph, new Thickness(textBlockLocation.X, textBlockLocation.Y, 0, 0));
            }
    
    
            /// <summary>
            /// iterate through the visual tree and look for TextBlocks to position the glyph
            /// </summary>
            /// <returns></returns>
            private Point FindTextBlock()
            {
                // use ModelFactory to figure out what the type of text block is - works for SL and WPF.
                Type textBlockType = ModelFactory.ResolveType(Context, new TypeIdentifier(typeof(TextBlock).FullName));
    
                ViewItem textBlock = FindTextBlock(textBlockType, EditedItem.View);
                if (textBlock != null)
                {
                    // transform the top left of the textblock to the view coordinate system.
                    return textBlock.TransformToView(EditedItem.View).Transform(new Point(0, 0));
                }
    
                // couldn't find a text block in the visual tree.  Return a default position.
                return new Point();
            }
            private ViewItem FindTextBlock(Type textBlockType, ViewItem view)
            {
                if (view == null)
                {
                    return null;
                }
    
                if (textBlockType.IsAssignableFrom(view.ItemType))
                {
                    return view;
                }
                else
                {
                    // walk through the child tree recursively looking for it.
                    foreach (ViewItem child in view.VisualChildren)
                    {
                        return FindTextBlock(textBlockType, child);
                    }
                }
                return null;
            }
            void OnEditedItemPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if (e.PropertyName == "Content")
                {
                    if (EditGlyph != null)
                    {
                        UpdateTextBlockLocation(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 ModelItem)
                    {
                        return ((ModelItem)value).GetCurrentValue();
                    }
                    else if (value != null)
                    {
                        return value.ToString();
                    }
    
                    return string.Empty;
                }
    
                public object ConvertBack(
                    object value,
                    Type targetType,
                    object parameter,
                    System.Globalization.CultureInfo culture)
                {
                    return value;
                }
            }
        }
    
    }
    
  3. Genere la solución.

Probar la implementación en tiempo de diseño

Puede utilizar la clase DemoControl como utilizaría cualquier otro control de WPF. WPF Designer administra la creación de todos los objetos en tiempo de diseño.

Para probar la implementación en tiempo de diseño

  1. Agregue a la solución un nuevo proyecto de aplicación de WPF en Visual C# denominado DemoApplication.

    MainWindow.xaml se abre en WPF Designer.

  2. Agregue una referencia al proyecto CustomControlLibrary.

  3. En la vista XAML, reemplace el XAML generado automáticamente por el XAML siguiente. Este XAML agrega una referencia al espacio de nombres CustomControlLibrary y agrega el control personalizado DemoControl. Si el control no aparece, es posible que tenga que hacer clic en la barra de información situada en la parte superior del diseñador para volver a cargar la vista.

    <Window x:Class="DemoApplication.MainWindow"
        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. Vuelva a generar la solución.

  5. En la Vista de diseño, haga clic en el control DemoControl para seleccionarlo.

    Aparece un pequeño glifo de Rectangle en la esquina superior izquierda del control DemoControl.

  6. Haga clic en el glifo de Rectangle para activar la edición en contexto.

    Aparecerá un cuadro de texto con la propiedad Content de DemoControl. Puesto que su contenido está vacío, únicamente se ve un cursor en el centro del botón.

  7. Escriba un valor nuevo para el contenido de texto y presione la tecla ENTRAR.

    En la vista XAML, la propiedad Content se establece en el valor de texto que ha escrito en la Vista de diseño.

  8. Establezca el proyecto DemoApplication como proyecto de inicio y ejecute la solución.

    En tiempo de ejecución, el botón tiene el valor de texto establecido con la etiqueta contextual.

Pasos siguientes

Puede agregar más características personalizadas en tiempo de diseño a los controles personalizados.

Vea también

Otros recursos

Crear editores personalizados

Extensibilidad de WPF Designer

Proporcionar metadatos en tiempo de diseño