演练:实现就地编辑
本演练演示如何为 Windows Presentation Foundation (WPF) 自定义控件实现就地编辑。 可以在 适用于 Visual Studio 的 WPF 设计器中使用此设计时功能为自定义按钮控件设置 Content 属性值。 在本演练中,控件是一个简单的按钮,装饰器是一个用来更改该按钮的内容的文本框。
在本演练中,您将执行下列任务:
创建一个 WPF 自定义控件库项目。
为设计时元数据创建一个单独的程序集。
为就地编辑实现装饰提供程序。
在设计时测试控件。
完成本演练后,您将知道如何为自定义控件创建装饰器提供程序。
提示
显示的对话框和菜单命令可能会与“帮助”中的描述不同,具体取决于您现用的设置或版本。 若要更改设置,请在“工具”菜单上选择“导入和导出设置”。 有关更多信息,请参见 使用设置。
系统必备
您需要以下组件来完成本演练:
- Visual Studio 2010.
创建自定义控件
第一步是为自定义控件创建项目。 该控件是一个带有少量设计时代码的简单按钮,该按钮使用 GetIsInDesignMode 方法来实现设计时行为。
创建自定义控件
使用 Visual C# 创建一个名为 CustomControlLibrary 的新 WPF 自定义控件库项目。
CustomControl1 的代码在“代码编辑器”中打开。
在**“解决方案资源管理器”中,将代码文件的名称改为 DemoControl.cs。 如果出现消息框,询问是否对此项目中的所有引用执行重命名操作,请单击“是”**。
在代码编辑器中打开 DemoControl.cs。
用下面的代码替换自动生成的代码。 DemoControl 自定义控件继承自 Button
using System; using System.Windows; using System.Windows.Controls; namespace CustomControlLibrary { public class DemoControl : Button { } }
将项目的输出路径设置为“bin\”。
生成解决方案。
创建设计时元数据程序集
设计时代码在特定元数据程序集中部署。 本演练中的自定义装饰器仅受 Visual Studio 支持,而且部署到名为 CustomControlLibrary.VisualStudio.Design 的程序集内。 有关更多信息,请参见提供设计时元数据。
创建设计时元数据程序集
使用 Visual C# 为解决方案添加一个名为 CustomControlLibrary.VisualStudio.Design 的新类库项目。
将项目的输出路径设置为“.. \CustomControlLibrary\bin\”。 这样可使控件的程序集与元数据程序集位于同一文件夹中,从而可为设计器启用元数据发现。
添加对下列 WPF 程序集的引用。
PresentationCore
PresentationFramework
System.Xaml
WindowsBase
添加对下列 WPF 设计器程序集的引用。
Microsoft.Windows.Design.Extensibility
Microsoft.Windows.Design.Interaction
添加对 CustomControlLibrary 项目的引用。
在**“解决方案资源管理器”**中,将 Class1 代码文件的名称改为 Metadata.cs。
用下面的代码替换自动生成的代码。 此代码将创建一个将自定义设计时实现附加到 DemoControl 类的 AttributeTable。
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(); } } } }
保存解决方案。
实现装饰器提供程序
装饰器提供程序是在名为 InplaceButtonAdorners 的类型中实现的。 此装饰器提供程序使用户可以在设计时设置控件的 Content 属性。
实现装饰器提供程序
向 CustomControlLibrary.VisualStudio.Design 项目中添加一个名为 InplaceButtonAdorners 的新类。
在 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; } } } }
生成解决方案。
测试设计时实现
可以像使用任何其他 WPF 控件一样使用 DemoControl 类。 WPF 设计器处理所有设计时对象的创建。
测试设计时实现
使用 Visual C# 向解决方案中添加一个名为 DemoApplication 的新 WPF 应用程序项目。
MainWindow.xaml 将在 WPF 设计器中打开。
添加对 CustomControlLibrary 项目的引用。
在 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>
重新生成解决方案。
在“设计”视图中,单击 DemoControl 控件将其选中。
在 DemoControl 控件的左上角出现一个小的 Rectangle 标志符号。
单击 Rectangle 标志符号激活就地编辑。
随即出现一个文本框,其中显示了 DemoControl 的 Content。 由于内容当前为空,因此只能看到按钮中心的光标。
键入文本内容的新值,然后按 Enter 键。
在 XAML 视图中,Content 属性将设置为在“设计”视图中键入的文本值。
将 DemoApplication 项目设置为启动项目,并运行解决方案。
在运行时,该按钮具有您用装饰器设置的文本值。
后续步骤
您可以向自定义控件中添加更多设计时功能。
向自定义设计时中添加一个 MenuAction。 有关更多信息,请参见演练:创建菜单提供程序。
创建一个可在“属性”窗口中使用的自定义颜色编辑器。 有关更多信息,请参见演练:实现颜色编辑器。
创建一个用于设置控件的不透明度的自定义装饰器。 有关更多信息,请参见演练:创建设计时装饰器。