Пошаговое руководство. Реализация редактирования по месту

В данном примере показано, как реализовать редактирование на месте для пользовательского элемента управления Windows Presentation Foundation (WPF). Эту функцию времени разработки можно использовать в Конструктор WPF для Visual Studio для присвоения значения свойству Content для пользовательского элемента управления "кнопка". В данном примере элемент управления является простой кнопкой, а графический элемент — текстовым полем, позволяющим изменять содержимое кнопки.

В данном пошаговом руководстве выполняются следующие задачи.

  • Создание проекта библиотеки пользовательских элементов управления WPF.

  • Создание отдельной сборки для метаданных времени разработки.

  • Реализация поставщика графических элементов для редактирования на месте.

  • Тестирование элемента управления во время разработки.

В результате будет полностью описан процесс создания поставщика графических элементов для пользовательского элемента управления.

Примечание

Отображаемые диалоговые окна и команды меню могут отличаться от описанных в справке в зависимости от текущих настроек или выпуска.Чтобы изменить параметры, выберите в меню Сервис пункт Импорт и экспорт параметров.Дополнительные сведения см. в разделе Работа с параметрами.

Обязательные компоненты

Ниже приведены компоненты, необходимые для выполнения данного пошагового руководства.

  • Visual Studio 2010.

Создание пользовательского элемента управления

Первым этапом является создание проекта для пользовательского элемента управления. Этот элемент управления представляет собой простую кнопку, созданную с помощью небольшого количества кода, в котором для реализации поведения во время разработки используется метод GetIsInDesignMode.

Создание пользовательского элемента управления

  1. Создайте новый проект библиотеки настраиваемых элементов управления WPF на языке Visual C# с именем CustomControlLibrary.

    В редакторе кода откроется код для элемента управления CustomControl1.

  2. В обозревателе решений измените имя файла с кодом на DemoControl.cs. Если появляется окно сообщения, запрашивающее подтверждение переименования всех ссылок в этом проекте, нажмите кнопку Да.

  3. Откройте файл DemoControl.cs в редакторе кода.

  4. Замените автоматически создаваемый код на следующий код. Пользовательский элемент управления DemoControl является производным от класса Button

    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace CustomControlLibrary
    {
        public class DemoControl : Button
        {   
        }
    }
    
  5. Задайте выходной путь проекта как "bin\".

  6. Выполните построение решения.

Создание сборки метаданных времени разработки

Код времени разработки развертывается в особых сборках метаданных. В данном пошаговом руководстве пользовательский графический элемент поддерживается только средой Visual Studio и развертывается в сборке с именем CustomControlLibrary.VisualStudio.Design. Дополнительные сведения см. в разделе Предоставление метаданных времени разработки.

Создание сборки метаданных времени разработки

  1. Добавьте к решению новый проект библиотеки классов на языке Visual C# с именем CustomControlLibrary.VisualStudio.Design.

  2. Задайте выходной путь проекта как ".. \CustomControlLibrary\bin\". В этом случае сборка элемента управления и сборка метаданных будут находиться в одной папке, что обеспечит доступ к метаданным для конструкторов.

  3. Добавьте ссылки на следующие сборки WPF:

    • PresentationCore

    • PresentationFramework

    • System.Xaml

    • WindowsBase

  4. Добавьте ссылки на следующие сборки сред. Конструктор WPF.

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  5. Добавьте ссылку на проект CustomControlLibrary.

  6. В обозревателе решений измените имя файла с кодом "Class1" на Metadata.cs.

  7. Замените автоматически создаваемый код на следующий код. Этот код создает таблицу AttributeTable, которая присоединяет пользовательскую реализацию времени разработки к классу 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. Сохраните решение.

Реализация поставщика графических элементов

Поставщик графических элементов реализован в типе с именем InplaceButtonAdorners. Этот поставщик графических элементов позволяет пользователю устанавливать свойство элемента управления Content во время разработки.

Реализация поставщика графических элементов

  1. Добавьте новый класс с именем InplaceButtonAdorners к проекту CustomControlLibrary.VisualStudio.Design.

  2. В редакторе кода для класса InplaceButtonAdorners замените автоматически создаваемый код на следующий код. Этот код реализует поставщик PrimarySelectionAdornerProvider, который предоставляет графический элемент на основе элемента управления 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. Выполните построение решения.

Тестирование реализации времени разработки

Класс DemoControl можно использовать как любой другой элемент управления WPF. сред. Конструктор WPF управляет созданием всех объектов времени разработки.

Тестирование реализации времени разработки

  1. Добавьте к решению новый проект приложения WPF на языке Visual C# с именем DemoApplication.

    Файл MainWindow.xaml будет открыт в сред. Конструктор WPF.

  2. Добавьте ссылку на проект CustomControlLibrary.

  3. В представлении XAML замените автоматически созданный код XAML на следующий код XAML. Этот код XAML добавляет ссылку на пространство имен CustomControlLibrary и пользовательский элемент управления DemoControl. Если элемент управления не отображается, можно щелкнуть панели информации в верхней части конструктора для перезагрузки представления.

    <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. Вновь Выполните сборку решения.

  5. В представлении конструктора выберите элемент управления DemoControl.

    Небольшой глиф Rectangle появится в верхнем левом углу элемента управления DemoControl.

  6. Щелкните глиф Rectangle для активации режима редактирования на месте.

    Появится текстовое поле, в котором отображается свойство Content элемента управления DemoControl. Поскольку содержимое в настоящий момент пусто, будет виден только курсор в центре кнопки.

  7. Введите новое значение для текстового содержимого, затем нажмите клавишу ВВОД.

    В представлении XAML свойству Content присваивается текстовое значение, введенное в представлении конструктора.

  8. Сделайте проект DemoApplication автоматически загружаемым проектом и запустите решение.

    Во время выполнения проекта кнопка имеет текстовое значение, заданное графическим элементом.

Следующие действия

Можно добавить дополнительные пользовательские функции времени разработки для пользовательских элементов управления.

См. также

Другие ресурсы

Создание пользовательских редакторов

Расширяемость среды конструктора WPF

Предоставление метаданных времени разработки