Exemplarische Vorgehensweise: Implementieren der direkten Bearbeitung

In dieser exemplarischen Vorgehensweise wird veranschaulicht, wie Sie die direkte Bearbeitung für ein benutzerdefiniertes WPF (Windows Presentation Foundation)-Steuerelement implementieren. Mit diesem Entwurfszeitfeature im WPF Designer für Visual Studio können Sie den Wert der Content-Eigenschaft für ein benutzerdefiniertes Schaltflächen-Steuerelement festlegen. Bei dem Steuerelement in dieser exemplarischen Vorgehensweise handelt es sich um eine einfache Schaltfläche, und bei dem Adorner um ein Textfeld, mit dem Sie den Inhalt der Schaltfläche ändern können.

Im Verlauf dieser exemplarischen Vorgehensweise führen Sie folgende Aufgaben aus:

  • Erstellen eines benutzerdefinierten WPF-Steuerelementbibliothek-Projekts

  • Erstellen einer separaten Assembly für Entwurfszeitmetadaten

  • Implementieren des Adorneranbieters für die direkte Bearbeitung

  • Testen des Steuerelements zur Entwurfszeit

Nach Abschluss dieser Aufgaben wissen Sie, wie Sie einen Adorneranbieter für ein benutzerdefiniertes Steuerelement erstellen.

Tipp

Je nach den aktiven Einstellungen oder der Version unterscheiden sich die Dialogfelder und Menübefehle auf Ihrem Bildschirm möglicherweise von den in der Hilfe beschriebenen. Klicken Sie im Menü Extras auf Einstellungen importieren und exportieren, um die Einstellungen zu ändern. Weitere Informationen finden Sie unter Arbeiten mit Einstellungen.

Vorbereitungsmaßnahmen

Zum Durchführen dieser exemplarischen Vorgehensweise benötigen Sie die folgenden Komponenten:

  • Visual Studio 2010.

Erstellen des benutzerdefinierten Steuerelements

Zuerst wird das Projekt für das benutzerdefinierte Steuerelement erstellt. Bei dem Steuerelement handelt es sich um eine einfache Schaltfläche mit wenig Entwurfszeitcode, für die eine GetIsInDesignMode-Methode zum Implementieren eines Entwurfszeitverhaltens verwendet wird.

So erstellen Sie das benutzerdefinierte Steuerelement

  1. Erstellen Sie ein neues benutzerdefiniertes WPF-Steuerelementbibliothek-Projekt in Visual C# mit dem Namen CustomControlLibrary.

    Der Code für CustomControl1 wird im Code-Editor geöffnet.

  2. Ändern Sie im Projektmappen-Explorer den Namen der Codedatei in DemoControl.cs. Wenn eine Meldung mit der Frage angezeigt wird, ob Sie eine Umbenennung für alle Verweise in diesem Projekt vornehmen möchten, klicken Sie auf Ja.

  3. Öffnen Sie DemoControl.cs im Code-Editor.

  4. Ersetzen Sie den automatisch generierten Code durch den folgenden Code. Das benutzerdefinierte DemoControl-Steuerelement erbt vom Button.

    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace CustomControlLibrary
    {
        public class DemoControl : Button
        {   
        }
    }
    
  5. Legen Sie den Ausgabepfad des Projekts auf "bin\" fest.

  6. Erstellen Sie die Projektmappe.

Erstellen der Entwurfszeit-Metadatenassembly

Entwurfszeitcode wird in speziellen Metadatenassemblys bereitgestellt. Bei dieser exemplarischen Vorgehensweise wird der benutzerdefinierte Adorner nur von Visual Studio unterstützt und in einer Assembly mit dem Namen CustomControlLibrary.VisualStudio.Design bereitgestellt. Weitere Informationen finden Sie unter Bereitstellen von Entwurfszeitmetadaten.

So erstellen Sie die Entwurfszeit-Metadatenassembly

  1. Fügen Sie der Projektmappe ein neues Klassenbibliothek-Projekt in Visual C# mit dem Namen CustomControlLibrary.VisualStudio.Design hinzu.

  2. Legen Sie den Ausgabepfad des Projekts auf folgenden Pfad fest: ".. \CustomControlLibrary\bin\". Dadurch wird die Assembly des Steuerelements im selben Ordner wie die Metadatenassembly gespeichert, wodurch Designern die Metadatensuche ermöglicht wird.

  3. Fügen Sie Verweise auf die folgenden WPF-Assemblys hinzu.

    • PresentationCore

    • PresentationFramework

    • System.Xaml

    • WindowsBase

  4. Fügen Sie Verweise auf die folgenden WPF-Designer-Assemblys hinzu.

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  5. Fügen Sie einen Verweis auf das CustomControlLibrary-Projekt hinzu.

  6. Ändern Sie im Projektmappen-Explorer den Namen der Class1-Codedatei in Metadata.cs.

  7. Ersetzen Sie den automatisch generierten Code durch den folgenden Code. Durch diesen Code wird eine AttributeTable erstellt, mit der die benutzerdefinierte Entwurfszeitimplementierung an die DemoControl-Klasse angefügt wird.

    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. Speichern Sie die Projektmappe.

Implementieren des Adorneranbieters

Der Adorneranbieter wird in einem Typ mit dem Namen InplaceButtonAdorners implementiert. Dieser Adorneranbieter ermöglicht es dem Benutzer, die Content-Eigenschaft des Steuerelements zur Entwurfszeit festzulegen.

So implementieren Sie den Adorneranbieter

  1. Fügen Sie dem CustomControlLibrary.VisualStudio.Design-Projekt eine neue Klasse mit dem Namen InplaceButtonAdorners hinzu.

  2. Ersetzen Sie im Code-Editor für InplaceButtonAdorners den automatisch generierten Code durch den folgenden Code. Mit diesem Code wird ein PrimarySelectionAdornerProvider implementiert, der einen auf einem TextBox-Steuerelement basierenden Adorner bereitstellt.

    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. Erstellen Sie die Projektmappe.

Testen der Entwurfszeitimplementierung

Sie können die DemoControl-Klasse auf dieselbe Art wie jedes andere WPF-Steuerelement verwenden. Der WPF-Designer behandelt die Erstellung aller Entwurfszeitobjekte.

So testen Sie die Entwurfszeitimplementierung

  1. Fügen Sie der Projektmappe ein neues WPF-Anwendungsprojekt in Visual C# mit dem Namen DemoApplication hinzu.

    Die Datei MainWindow.xaml wird im WPF-Designer geöffnet.

  2. Fügen Sie einen Verweis auf das CustomControlLibrary-Projekt hinzu.

  3. Ersetzen Sie in der XAML-Ansicht den automatisch generierten XAML-Code durch den folgenden XAML-Code. Mit diesem XAML-Code werden ein Verweis auf den CustomControlLibrary-Namespace sowie das benutzerdefinierte DemoControl-Steuerelement hinzugefügt. Wenn das Steuerelement nicht angezeigt wird, müssen Sie möglicherweise auf die Informationsleiste am oberen Rand des Designers klicken, um die Ansicht zu aktualisieren.

    <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. Generieren Sie die Projektmappe neu.

  5. Klicken Sie in der Entwurfsansicht auf das DemoControl-Steuerelement, um es auszuwählen.

    Ein kleines Rectangle-Symbol wird an der linken oberen Ecke des DemoControl-Steuerelements angezeigt.

  6. Klicken Sie auf das Rectangle-Symbol, um die direkte Bearbeitung zu aktivieren.

    In einem Textfeld wird der Content von DemoControl angezeigt. Da der Inhalt derzeit leer ist, wird in der Mitte der Schaltfläche nur ein Cursor angezeigt.

  7. Geben Sie einen neuen Wert für den Textinhalt ein, und drücken Sie dann die EINGABETASTE.

    In der XAML-Ansicht wird die Content-Eigenschaft auf den Textwert festgelegt, den Sie in der Entwurfsansicht eingegeben haben.

  8. Legen Sie das DemoApplication-Projekt als Startprojekt fest, und führen Sie die Projektmappe aus.

    Zur Laufzeit hat die Schaltfläche den Textwert, den Sie mit dem Adorner festlegen.

Nächste Schritte

Sie können benutzerdefinierten Steuerelementen weitere benutzerdefinierte Entwurfszeitfeatures hinzufügen.

Siehe auch

Weitere Ressourcen

Erstellen von benutzerdefinierten Editoren

WPF-Designer-Erweiterbarkeit

Bereitstellen von Entwurfszeitmetadaten