演练:创建菜单提供程序

本演练演示如何为 Windows Presentation Foundation (WPF) 自定义控件创建设计时菜单提供程序。 可以使用此快捷菜单项在自定义按钮控件上设置 Background 属性的值。 有关完整的代码清单,请参见 WPF Designer Extensibility Samples(WPF 设计器扩展性示例)站点上的“上下文菜单提供程序”示例。

在本演练中,您将执行下列任务:

  • 创建一个 WPF 自定义控件库项目。

  • 为设计时元数据创建一个单独的程序集。

  • 实现菜单提供程序。

  • 在设计时使用控件。

完成本演练后,您将知道如何为自定义控件创建菜单提供程序。

提示

显示的对话框和菜单命令可能会与“帮助”中的描述不同,具体取决于您现用的设置或版本。 若要更改设置,请在“工具”菜单上选择“导入和导出设置”。 有关更多信息,请参见 使用设置

系统必备

您需要以下组件来完成本演练:

  • Visual Studio 2010.

创建自定义控件

第一步是为自定义控件创建项目。 该控件是一个带有少量设计时代码的简单按钮,该按钮使用 GetIsInDesignMode 方法来实现设计时行为。

创建自定义控件

  1. 使用 Visual Basic 或 Visual C# 创建一个名为 CustomControlLibrary 的新 WPF 自定义控件库项目。

    CustomControl1 的代码在“代码编辑器”中打开。

  2. 在**“解决方案资源管理器”中,将代码文件的名称更改为 ButtonWithDesignTime.cs 或 ButtonWithDesignTime.vb。 如果出现消息框,询问是否对此项目中的所有引用执行重命名操作,请单击“是”**。

  3. 在“代码编辑器”中打开 ButtonWithDesignTime.cs 或 ButtonWithDesignTime.vb。

  4. 用下面的代码替换自动生成的代码。 此代码从 Button 继承而来,当按钮出现在设计器中时,将显示文本“已激活设计模式”。

    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.Windows.Controls
    Imports System.Windows.Media
    Imports System.ComponentModel
    
    Public Class ButtonWithDesignTime
        Inherits Button
    
        Public Sub New()
            ' The GetIsInDesignMode check and the following design-time 
            ' code are optional and shown only for demonstration.
            If DesignerProperties.GetIsInDesignMode(Me) Then
                Content = "Design mode active"
            End If
    
        End Sub
    End Class
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.ComponentModel;
    
    namespace CustomControlLibrary
    {
        public class ButtonWithDesignTime : Button
        {
            public ButtonWithDesignTime()
            {
                // The GetIsInDesignMode check and the following design-time 
                // code are optional and shown only for demonstration.
                if (DesignerProperties.GetIsInDesignMode(this))
                {
                    Content = "Design mode active";
                }
            }
        }
    }
    
  5. 将项目的输出路径设置为“bin\”。

  6. 生成解决方案。

创建设计时元数据程序集

设计时代码在特定元数据程序集中部署。 对于本演练,上下文菜单实现在一个名为 CustomControlLibrary.VisualStudio.Design 的程序集中部署。 有关更多信息,请参见提供设计时元数据

创建设计时元数据程序集

  1. 使用 Visual Basic 或 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 或 Metadata.vb。 如果出现消息框,询问是否对此项目中的所有引用执行重命名操作,请单击“是”**。

  7. 用下面的代码替换自动生成的代码。 此代码将创建一个将自定义设计时实现附加到 ButtonWithDesignTime 类的 AttributeTable

    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.ComponentModel
    Imports System.Windows.Media
    Imports System.Windows.Controls
    Imports System.Windows
    Imports CustomControlLibrary
    Imports Microsoft.Windows.Design.Features
    Imports Microsoft.Windows.Design.Metadata
    'Imports CustomControlLibrary.VisualStudio.Design.Slid
    
    ' The ProvideMetadata assembly-level attribute indicates to designers
    ' that this assembly contains a class that provides an attribute table. 
    <Assembly: ProvideMetadata(GetType(CustomControlLibrary.VisualStudio.Design.Metadata))> 
    
    ' 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.
    Friend Class Metadata
        Implements IProvideAttributeTable
    
        ' Accessed by the designer to register any design-time metadata.
        Public ReadOnly Property AttributeTable() As AttributeTable _
            Implements IProvideAttributeTable.AttributeTable
            Get
                Dim builder As New AttributeTableBuilder()
    
                ' Add the menu provider to the design-time metadata.
                builder.AddCustomAttributes(GetType(ButtonWithDesignTime), _
                                            New FeatureAttribute(GetType(CustomContextMenuProvider)))
    
                Return builder.CreateTable()
            End Get
        End Property
    End Class
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ComponentModel;
    using System.Windows.Media;
    using System.Windows.Controls;
    using System.Windows;
    
    using CustomControlLibrary;
    using Microsoft.Windows.Design.Features;
    using Microsoft.Windows.Design.Metadata;
    using CustomControlLibrary.VisualStudio.Design;
    
    // 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 menu provider to the design-time metadata.
                    builder.AddCustomAttributes(
                        typeof(ButtonWithDesignTime),
                        new FeatureAttribute(typeof(CustomContextMenuProvider)));
    
                    return builder.CreateTable(); 
                }
            }
        }
    }
    
  8. 保存解决方案。

实现菜单提供程序

菜单提供程序在名为 CustomContextMenuProvider 的类型中的实现。 通过提供的 MenuAction,可以在设计时设置控件的 Background 属性。

实现菜单提供程序

  1. 向 CustomControlLibrary.VisualStudio.Design 项目中添加一个名为 CustomContextMenuProvider 的新类。

  2. 在 CustomContextMenuProvider 的代码编辑器中,用下面的代码替换自动生成的代码。 此代码实现提供自定义 MenuActionPrimarySelectionContextMenuProvider

    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports Microsoft.Windows.Design.Interaction
    Imports System.Windows
    Imports Microsoft.Windows.Design.Model
    Imports System.Windows.Controls
    Imports System.Windows.Media
    
    ' The CustomContextMenuProvider class provides two context menu items
    ' at design time. These are implemented with the MenuAction class.
    Class CustomContextMenuProvider
        Inherits PrimarySelectionContextMenuProvider
    
        Private setBackgroundToBlueMenuAction As MenuAction
        Private clearBackgroundMenuAction As MenuAction
    
        ' The provider's constructor sets up the MenuAction objects 
        ' and the the MenuGroup which holds them.
        Public Sub New()
    
            ' Set up the MenuAction which sets the control's 
            ' background to Blue.
            setBackgroundToBlueMenuAction = New MenuAction("Blue")
            setBackgroundToBlueMenuAction.Checkable = True
            AddHandler setBackgroundToBlueMenuAction.Execute, AddressOf SetBackgroundToBlue_Execute
    
            ' Set up the MenuAction which sets the control's 
            ' background to its default value.
            clearBackgroundMenuAction = New MenuAction("Cleared")
            clearBackgroundMenuAction.Checkable = True
            AddHandler clearBackgroundMenuAction.Execute, AddressOf ClearBackground_Execute
    
            ' Set up the MenuGroup which holds the MenuAction items.
            Dim backgroundFlyoutGroup As New MenuGroup("SetBackgroundsGroup", "Set Background")
    
            ' If HasDropDown is false, the group appears inline, 
            ' instead of as a flyout. Set to true.
            backgroundFlyoutGroup.HasDropDown = True
            backgroundFlyoutGroup.Items.Add(setBackgroundToBlueMenuAction)
            backgroundFlyoutGroup.Items.Add(clearBackgroundMenuAction)
            Me.Items.Add(backgroundFlyoutGroup)
    
            ' The UpdateItemStatus event is raised immediately before 
            ' this provider shows its tabs, which provides the opportunity 
            ' to set states.
            AddHandler UpdateItemStatus, AddressOf CustomContextMenuProvider_UpdateItemStatus
    
        End Sub
    
        ' The following method handles the UpdateItemStatus event.
        ' It sets the MenuAction states according to the state
        ' of the control's Background property. This method is
        ' called before the context menu is shown.
        Sub CustomContextMenuProvider_UpdateItemStatus( _
            ByVal sender As Object, _
            ByVal e As MenuActionEventArgs)
    
            ' Turn everything on, and then based on the value 
            ' of the BackgroundProperty, selectively turn some off.
            clearBackgroundMenuAction.Checked = False
            clearBackgroundMenuAction.Enabled = True
            setBackgroundToBlueMenuAction.Checked = False
            setBackgroundToBlueMenuAction.Enabled = True
    
            ' Get a ModelItem which represents the selected control. 
            Dim selectedControl As ModelItem = _
                e.Selection.PrimarySelection
    
            ' Get the value of the Background property from the ModelItem.
            Dim backgroundProperty As ModelProperty = _
                selectedControl.Properties("Background")
    
            ' Set the MenuAction items appropriately.
            If Not backgroundProperty.IsSet Then
                clearBackgroundMenuAction.Checked = True
                clearBackgroundMenuAction.Enabled = False
            ElseIf backgroundProperty.ComputedValue.Equals(Brushes.Blue) Then
                setBackgroundToBlueMenuAction.Checked = True
                setBackgroundToBlueMenuAction.Enabled = False
            End If
    
        End Sub
    
        ' The following method handles the Execute event. 
        ' It sets the Background property to its default value.
        Sub ClearBackground_Execute( _
            ByVal sender As Object, _
            ByVal e As MenuActionEventArgs)
    
            Dim selectedControl As ModelItem = e.Selection.PrimarySelection
            selectedControl.Properties("Background").ClearValue()
    
        End Sub
    
        ' The following method handles the Execute event. 
        ' It sets the Background property to Brushes.Blue.
        Sub SetBackgroundToBlue_Execute( _
            ByVal sender As Object, _
            ByVal e As MenuActionEventArgs)
    
            Dim selectedControl As ModelItem = e.Selection.PrimarySelection
            selectedControl.Properties("Background").SetValue(Brushes.Blue)
    
        End Sub
    
    End Class
    
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Windows.Design.Interaction;
    using System.Windows;
    using Microsoft.Windows.Design.Model;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace CustomControlLibrary.VisualStudio.Design
    {
        // The CustomContextMenuProvider class provides two context menu items
        // at design time. These are implemented with the MenuAction class.
        class CustomContextMenuProvider : PrimarySelectionContextMenuProvider
        {
            private MenuAction setBackgroundToBlueMenuAction;
            private MenuAction clearBackgroundMenuAction;
    
            // The provider's constructor sets up the MenuAction objects 
            // and the the MenuGroup which holds them.
            public CustomContextMenuProvider()
            {   
                // Set up the MenuAction which sets the control's 
                // background to Blue.
                setBackgroundToBlueMenuAction = new MenuAction("Blue");
                setBackgroundToBlueMenuAction.Checkable = true;
                setBackgroundToBlueMenuAction.Execute += 
                    new EventHandler<MenuActionEventArgs>(SetBackgroundToBlue_Execute);
    
                // Set up the MenuAction which sets the control's 
                // background to its default value.
                clearBackgroundMenuAction = new MenuAction("Cleared");
                clearBackgroundMenuAction.Checkable = true;
                clearBackgroundMenuAction.Execute += 
                    new EventHandler<MenuActionEventArgs>(ClearBackground_Execute);
    
                // Set up the MenuGroup which holds the MenuAction items.
                MenuGroup backgroundFlyoutGroup = 
                    new MenuGroup("SetBackgroundsGroup", "Set Background");
    
                // If HasDropDown is false, the group appears inline, 
                // instead of as a flyout. Set to true.
                backgroundFlyoutGroup.HasDropDown = true;
                backgroundFlyoutGroup.Items.Add(setBackgroundToBlueMenuAction);
                backgroundFlyoutGroup.Items.Add(clearBackgroundMenuAction);
                this.Items.Add(backgroundFlyoutGroup);
    
                // The UpdateItemStatus event is raised immediately before 
                // this provider shows its tabs, which provides the opportunity 
                // to set states.
                UpdateItemStatus += 
                    new EventHandler<MenuActionEventArgs>(
                        CustomContextMenuProvider_UpdateItemStatus);
            }
    
            // The following method handles the UpdateItemStatus event.
            // It sets the MenuAction states according to the state
            // of the control's Background property. This method is
            // called before the context menu is shown.
            void CustomContextMenuProvider_UpdateItemStatus(
                object sender, 
                MenuActionEventArgs e)
            {
                // Turn everything on, and then based on the value 
                // of the BackgroundProperty, selectively turn some off.
                clearBackgroundMenuAction.Checked = false;
                clearBackgroundMenuAction.Enabled = true;
                setBackgroundToBlueMenuAction.Checked = false;
                setBackgroundToBlueMenuAction.Enabled = true;
    
                // Get a ModelItem which represents the selected control. 
                ModelItem selectedControl = e.Selection.PrimarySelection;
    
                // Get the value of the Background property from the ModelItem.
                ModelProperty backgroundProperty = 
                    selectedControl.Properties["Background"];
    
                // Set the MenuAction items appropriately.
                if (!backgroundProperty.IsSet)
                {
                    clearBackgroundMenuAction.Checked = true;
                    clearBackgroundMenuAction.Enabled = false;
                }
                else if (backgroundProperty.ComputedValue == Brushes.Blue)
                {
                    setBackgroundToBlueMenuAction.Checked = true;
                    setBackgroundToBlueMenuAction.Enabled = false;
                }
            }
    
            // The following method handles the Execute event. 
            // It sets the Background property to its default value.
            void ClearBackground_Execute(
                object sender, 
                MenuActionEventArgs e)
            {
                ModelItem selectedControl = e.Selection.PrimarySelection;
                selectedControl.Properties["Background"].ClearValue();
            }
    
            // The following method handles the Execute event. 
            // It sets the Background property to Brushes.Blue.
            void SetBackgroundToBlue_Execute(
                object sender, 
                MenuActionEventArgs e)
            {
                ModelItem selectedControl = e.Selection.PrimarySelection;
                selectedControl.Properties["Background"].SetValue(Brushes.Blue);
            }
        }
    }
    
  3. 生成解决方案。

测试设计时实现

可以像使用任何其他 WPF 控件一样使用 ButtonWithDesignTime 控件。 WPF 设计器处理所有设计时对象的创建。

测试设计时实现

  1. 使用 Visual Basic 或 Visual C# 为解决方案添加一个名为 DemoApplication 的新 WPF 应用程序项目。

    MainWindow.xaml 将在 WPF 设计器中打开。

  2. 添加对 CustomControlLibrary 项目的引用。

  3. 在 XAML 视图中,用以下 XAML 替换自动生成的 XAML。 此 XAML 将添加对 CustomControlLibrary 命名空间的引用并添加 ButtonWithDesignTime 自定义控件。 该按钮出现在“设计”视图中,并显示文本“已激活设计模式”,表明该按钮处于设计模式下。 如果该按钮未出现,可能需要单击设计器顶部的信息栏,以重新加载视图。

    <Window x:Class="DemoApplication.MainWindow"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cc="clr-namespace:CustomControlLibrary;assembly=CustomControlLibrary"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <cc:ButtonWithDesignTime Margin="30,30,30,30" Background="#FFD4D0C8"></cc:ButtonWithDesignTime>
        </Grid>
    </Window>
    
  4. 在“设计”视图中,单击 ButtonWithDesignTime 控件将其选中。

  5. 右击 ButtonWithDesignTime 控件,指向**“设置背景”,然后选择“蓝色”**。

    该控件的背景即设置为蓝色。 在 XAML 视图中,将 Background 属性设置为菜单操作指定的值。

  6. 运行 DemoApplication 项目。

    运行时,该按钮将具有您用快捷菜单设置的背景。

后续步骤

您可以向自定义控件中添加更多设计时功能。

请参见

参考

PrimarySelectionContextMenuProvider

其他资源

高级扩展性概念

WPF 设计器扩展性

WPF Designer Extensibility Samples