逐步解說:實作就地編輯
本逐步解說將示範如何為 Windows Presentation Foundation (WPF) 自訂控制項實作就地編輯 (In-Place Editing)。 您可以在 WPF Designer for Visual Studio 中使用這項設計階段功能,設定自訂按鈕控制項上的 Content 屬性。 在這個逐步解說中,控制項是簡單的按鈕,而裝飾項是可讓您變更按鈕內容的文字方塊。
在這個逐步解說中,您會執行下列工作:
建立 WPF 自訂控制項程式庫專案。
為設計階段中繼資料建立個別組件。
實作裝飾項提供者以進行就地編輯。
在設計階段測試控制項。
完成時,您就知道要如何建立自訂控制項的裝飾項提供者。
注意
根據您目前使用的設定或版本,您所看到的對話方塊與功能表指令可能會與 [說明] 中描述的不同。若要變更設定,請從 [工具] 功能表中選擇 [匯入和匯出設定]。如需詳細資訊,請參閱 Visual Studio 設定。
必要條件
您需要下列元件才能完成此逐步解說:
- Visual Studio 2012 RC.
建立自訂控制項
第一個步驟是為自訂控制項建立專案。 這個控制項是一個具有少量設計階段程式碼的簡易按鈕,這個程式碼使用 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 的新類別庫 (Class Library) 專案加入至方案。
將專案的輸出路徑設定為 ".. \CustomControlLibrary\bin\"。 這麼做會將控制項的組件和中繼資料組件保留在同一個資料夾中,讓設計工具可進行中繼資料探索。
加入下列 WPF 組件的參考。
PresentationCore
PresentationFramework
System.Xaml
WindowsBase
加入下列 WPF Designer組件的參考。
Microsoft.Windows.Design.Extensibility
Microsoft.Windows.Design.Interaction
加入 CustomControlLibrary 專案的參考。
在 [方案總管] 中,將 Class1 程式碼檔案的名稱變更為 Metadata.cs。
以下列程式碼取代自動產生的程式碼。 這個程式碼會建立 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(); } } } }
儲存方案。
實作裝飾項提供者。
裝飾項提供者實作在名為 InplaceButtonAdorners 的型別中。 此裝飾項提供者可讓使用者在設計階段設定控制項的 Content 屬性。
若要實作裝飾項提供者
將名為 InplaceButtonAdorners 的新類別,加入至 CustomControlLibrary.VisualStudio.Design 專案。
在 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; } } } }
建置方案。
測試設計階段實作
您可以使用 DemoControl 類別,就像使用任何其他 WPF 控制項一樣。 WPF Designer會負責建立所有的設計階段物件。
若要測試設計階段實作
將 Visual C# 中名為 DemoApplication 的新 WPF 應用程式專案加入至方案。
MainWindow.xaml 隨即在 WPF Designer中開啟。
加入 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 圖像 (Glyph)。
按一下 Rectangle 圖像,以啟動就地編輯。
文字方塊隨即出現,其中顯示 DemoControl 的 Content。 由於內容目前是空的,您只會在按鈕的中央看到游標。
輸入文字內容的新值,然後按 ENTER 鍵。
在 [XAML] 檢視中,Content 屬性會設定為您在 [設計] 檢視中輸入的文字值。
將 DemoApplication 專案設定為啟始專案,然後執行方案。
在執行階段,按鈕就具有您使用裝飾項設定的文字值。
後續步驟
您可以將多個自訂設計階段功能,加入至自訂控制項。
將 MenuAction 加入到自訂設計階段。 如需詳細資訊,請參閱逐步解說:建立功能表提供者。
建立您可以在 [屬性] 視窗中使用的自訂色彩編輯器。 如需詳細資訊,請參閱逐步解說:實作色彩編輯器。
建立自訂裝飾項來設定控制項的不透明度。 如需詳細資訊,請參閱逐步解說:建立設計階段裝飾項。