65.9K
CodeProject 正在变化。 阅读更多。
Home

DesignSurface -WPF 设计器扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (4投票s)

2011 年 10 月 13 日

CPOL

7分钟阅读

viewsIcon

46228

downloadIcon

3052

帮助您扩展 WPF 设计器并在设计器上直接设计控件

引言

前几天,我在网上浏览 WPF 的第三方 UI。我看到一家供应商声称对他们的组件提供完整的运行时支持,这让我感到很有趣。作为一名 UI 开发者,我知道在 XAML 中导航以配置 UI 的痛苦。功能区控件和停靠控件非常复杂,足以让你头痛。这时,设计师扩展就出现了,以解决这个问题。

在一周的文档阅读、教程学习和一些简单的小事实践后,我发现把东西放到设计器里很简单。所以,我现在已经准备好为我们的控件库做事情了,库?哦!有很多控件,从最简单的需求到复杂的布局要求,我们要为每个控件编写设计师扩展吗?不,一定有更好的方法,而它恰好是 `DesignSurface`——这篇文章的主题。

那么,什么是 DesignSurface?

你知道,我是一名 Windows Forms 开发者,后来才转到 WPF。在我之前的文章中,我尝试将动画框架带回 Windows Forms。这篇文章将ControlDesignerDesignerVerbs的概念结合到 `DesignSurface` 中,让你的 WPF 设计器生动起来。

Preview.png

`DesignSurface` 是一个能够为你的设计器类中的设计师动词集合自动生成属性编辑器的装饰器。它让你创建控件设计器的工作量大大减少,无论控件类型是什么,它都能负责值的更改并为你更新设计器,你只需继承 `DesignSurface` 并添加感兴趣的设计师动词,然后将其构建到单独的程序集中,并与控件库一起部署。此时,很明显,这篇文章是为控件开发者/供应商准备的,而不是为控件的消费者准备的。如果你是消费者,别忘了向你的供应商索要设计器支持。

开始之前,要知道的事情

有关于构建和部署设计师扩展项目的指导。以下是这些规则的简要概览。

  • 在项目名称后添加 'Design.dll' 后缀,例如我的控件库名为 `Creatives.CustomControl`,那么设计师项目应命名为 `Creatives.CustomControl.Design`。
  • 在你的命名空间中添加 `assembly: ProvideMetaData` 属性,它向设计器表明你的程序集提供了控件设计器的元数据。
  • 将你的设计师 DLL 与控件库一起部署到 GAC 或控件库被设计器拾取的根文件夹中。
  • 设计师扩展部署在不同的 DLL 中,并且仅在 Visual Studio 设计器上下文中有效,它对控件的运行时行为没有影响。

所有这些信息对于设计器正确拾取 DLL 和控件设计器类型是必需的,除非设计器检测到这些信息和 DLL,否则你将在设计器中看不到任何变化,因此当你在设计器中一无所获时,请务必检查是否符合规则。

如何开始?

关于 `DesignSurface` 的介绍就到这里。让我们来看看如何开始使用 `DesignSurface`,我假设你已经有了自己的自定义控件,并且将使用 `DesignSurface` 来添加设计器支持。为了演示,我有一个 `DesignerExpander`,它是一个简单的派生自 `Expander` 的控件,字面上没有其他内容,请注意我将我的控件库命名为 `Creatives.CustomControl`。

namespace Creatives.CustomControl
{
	public class DeisgnerExpander : Expander
	{
		static DeisgnerExpander() { }
	}
}

现在我需要为我的客户提供良好的设计时体验,我所要做的就是定义设计师动词,我的 `DesignSurface` 将处理其余的工作。`DesignerExpander` 实际上是一个 `Expander` 控件,我需要将以下属性暴露在设计器中,其中两个定义了控件的外观,另外两个定义了控件的行为,并且我将为类别添加标题。

  • `DeisgnerExpander.HeaderProperty` – 控件上的标题文本
  • `DeisgnerExpander.ContentProperty` - 控件正文的文本
  • `DeisgnerExpander.IsExpandedProperty` – 获取或设置内容是否展开
  • `DeisgnerExpander.VisibilityProperty` – 获取或设置控件的可见性

首先是为设计器设置一个项目,我将我的设计师项目命名为 `Creatives.CustomControl.Desiger`,因此我满足了第一个规则,第二个是向命名空间添加属性,完成后的类将如下所示。

[assembly: ProvideMetadata(typeof(Creatives.CustomControl.Design.CustomControlDesigner))]
namespace Creatives.CustomControl.Design
{
	public class CustomControlDesigner : DesignSurface
	{
         }
}

其次是实现 `IProvideDesignerVerbs`,用于收集 `designerVerbs`,这里我将上述四个属性添加到设计师动词集合中,请记住,它们出现的顺序与添加到集合中的顺序一致。

public DesignerVerbCollection GetDesignerVerbs()
{
    DesignerVerbCollection verbs = new DesignerVerbCollection();

    verbs.AddHeader("CustomControl Editor");
 
    verbs.AddHeader("Appearance");
    verbs.AddProperty(DeisgnerExpander.HeaderProperty, "Header");
    verbs.AddProperty(DeisgnerExpander.ContentProperty, "Content");

    verbs.AddHeader("Behavior");
    verbs.AddProperty(DeisgnerExpander.IsExpandedProperty, "IsExpanded");
    verbs.AddProperty(DeisgnerExpander.VisibilityProperty, "Visibility");

    return verbs;
}

最后,你还有两件事需要完成,在构造函数中将设计器类型注册到 `DesignSurface`,并提供控件类型的信息。代码如下:

DesignSurface.RegisterDesignerVerbProvider(typeof(DeisgnerExpander), 
	typeof(CustomControlDesigner));

public override Type ControlType { get { return typeof(DeisgnerExpander); } }

就是这样!构建项目并与控件 DLL 一起部署,相信我,你现在已经为你的自定义控件添加了设计器支持。创建一个测试项目,拖放你的自定义控件,选中自定义控件将在设计器中直接显示编辑器。拥有这个是不是很棒!!!

所有这些是如何工作的?

是的,它正在工作,我发现它简单而优雅。但这一切是如何工作的呢?我知道你心里有这个疑问,我是故意的。 ;) 我从理解事物运作的那一天起就开始写文章,然而我的文章并没有引起任何关注,甚至我收到了关于我写作的评论 :P 已经一年多了,情况比以前好多了。这是一个倡议,要有所不同,并尝试了颠倒的做法,我相信这次对我有效。不要忘记留下你的评论和建议,无论好坏,它们都能让我进步,我相信在未来的文章中我会帮助你写更少的代码。

回到主题:它是如何工作的

在我们深入研究概念之前,先快速浏览一下项目中使用的助手和包装器。

  • `DesignerVerb` – 定义在设计时装饰器中显示的项,无论是 `Header` 还是 `PropertyEditor`。`Header` 包含要显示的文本,属性编辑器包含一个依赖属性及其显示名称。
  • `DesignerVerbCollection` – 定义一个 `IEnumerable`,允许你向集合添加属性、标题或 `DesignerVerb`。
  • 我有一个 `PropertyGrid` 和一个 `GridFactory` 来为给定的依赖属性类型生成相应的编辑器。`PropertyGrid` 执行一个简单的任务,它使用设计师动词,并在 `GridFactory` 的帮助下构建属性编辑器。`PropertyGrid` 中大部分工作是向面板添加 `FrameworkElements`,并为网格列和网格行设置适当的值。
  • `GridFactory` – 这个工厂实现决定了给定属性的编辑器类型并生成编辑器。(例如,`string` 类型的 `textbox`。请原谅我,我没有用于画笔和其他颜色类型的编辑器。)
public static PropertyEditor GetEditor(DependencyProperty property)
{
    if (property == null) return null;

    Type valueType = GetValueType(property);
    Type editorType = GetEditorType(valueType);

    FrameworkElement editor = Activator.CreateInstance(editorType) as FrameworkElement;

    if ( editor!=null && editorType == typeof(ComboBox))
        (editor as ComboBox).ItemsSource = Enum.GetValues(valueType);

    return new PropertyEditor(editor, property);
}

现在你已经了解了大部分工作原理,还有一件事需要揭开——WPF 定义的FeatureProvider,每个都贡献特定的设计时行为。`AdornerProviders`、`ContextMenu` Provider、`DefaultInitializer` 等等。本文使用了 `PrimarySelectionAdornerProvider` – 在设计器中选择元素时提供一个装饰器。当控件被激活时,它会调用 `AdornerProvider.Activate` 成员,我们将在设计器中进行所有需要的工作。

protected override void Activate(Microsoft.Windows.Design.Model.ModelItem item)
{
    ModelItem = item;

    InitSurfacePanel();

    SurfacePanel.PropertyGrid.UpdateLayout();

    base.Activate(item);
}

好的,这个新的 `ModelItem` 是什么?让我解释一下,`ModelItem` 是视图(在设计器中选中的元素)的抽象,通过这个引用,我们将把属性编辑器中的值更改同步到模型项。

ModelItem.Properties[property.Name].SetValue(newValue);

下一步是什么?

就是这样,我的工作完成了。希望你喜欢它。别忘了留下你的评论和建议。下次再见!

最后一句!这仅适用于 Visual Studio 2010,VS2008 的架构不同!

编码愉快!!!

© . All rights reserved.