共用方式為


逐步解說:實作就地編輯

更新:2007 年 11 月

本逐步解說將示範如何為 Windows Presentation Foundation (WPF) 自訂控制項實作就地編輯 (In-Place Editing)。您可以在 Windows Presentation Foundation (WPF) Designer for Visual Studio 中使用這項設計階段功能,設定自訂按鈕控制項上的 Content 屬性。在這個逐步解說中,控制項是簡單的按鈕,而裝飾項是可讓您變更按鈕內容的文字方塊。 

在這個逐步解說中,您會執行下列工作:

  • 建立 WPF 自訂控制項程式庫專案。

  • 為設計階段中繼資料建立個別組件。

  • 實作裝飾項提供者以進行就地編輯。

  • 在設計階段測試控制項。

完成時,您就知道要如何建立自訂控制項的裝飾項提供者。

注意事項:

根據您目前使用的設定或版本,您所看到的對話方塊與功能表命令可能會與 [說明] 中描述的不同。若要變更設定,請從 [工具] 功能表中選擇 [匯入和匯出設定]。如需詳細資訊,請參閱 Visual Studio 設定

必要條件

您需要下列元件才能完成此逐步解說:

  • Visual Studio 2008.

建立自訂控制項

第一個步驟是為自訂控制項建立專案。這個控制項是一個具有少量設計階段程式碼的簡易按鈕,這個程式碼使用 GetIsInDesignMode 方法來實作設計階段行為。

建立自訂控制項

  1. 在 Visual C# 中建立名為 CustomControlLibrary 的新 WPF 自訂控制項程式庫。

    CustomControl1 的程式碼隨即在 [程式碼編輯器] 中開啟。

  2. 在 [方案總管] 中,將程式碼檔案的名稱變更為 DemoControl.cs。如果顯示訊息方塊詢問您是否要重新命名專案中的所有參考,請按一下 [是]。

  3. 展開 [方案總管] 中的 [Themes] 資料夾。

  4. 按兩下 [Generic.xaml]。

    Generic.xaml 會在 WPF 設計工具中開啟。

  5. 在 [XAML] 檢視中,以 "DemoControl" 取代 "CustomControl1" 的所有項目。

  6. 在 [程式碼編輯器] 中開啟 DemoControl.cs。

  7. 以下列程式碼取代自動產生的程式碼。DemoControl 自訂控制項會繼承自 Button

    using System;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace CustomControlLibrary
    {
        public class DemoControl : Button
        {   
        }
    }
    
  8. 將專案的輸出路徑設定為 "bin\"。

  9. 建置方案。

建立設計階段中繼資料組件

設計階段程式碼部署在特殊中繼資料組件中。如需詳細資訊,請參閱 HOW TO:使用中繼資料存放區。在這個逐步解說中,只有 Visual Studio 才支援自訂裝飾項,此自訂裝飾項部署在名為 CustomControlLibrary.VisualStudio.Design 的組件中。

若要建立設計階段中繼資料組件

  1. 在 Visual C# 中,將名為 CustomControlLibrary.VisualStudio.Design 的新類別庫 (Class Library) 專案加入至方案。

  2. 將專案的輸出路徑設定為 "..\CustomControlLibrary\bin\"。這麼做會將控制項的組件和中繼資料組件保留在同一個資料夾中,讓設計工具可進行中繼資料探索。

  3. 加入下列 WPF 組件的參考。

    • PresentationCore

    • PresentationFramework

    • WindowsBase

  4. 加入下列 WPF 設計工具組件的參考。

    • Microsoft.Windows.Design

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  5. 加入 CustomControlLibrary 專案的參考。

  6. 在 [方案總管] 中,將 Class1 程式碼檔案的名稱變更為 Metadata.cs。

  7. 以下列程式碼取代自動產生的程式碼。這個程式碼會建立 AttributeTable,會將自訂設計階段實作附加至 DemoControl 類別。

    using System;
    using Microsoft.Windows.Design.Features;
    using Microsoft.Windows.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 IRegisterMetadata. If found, designers instantiate 
        // this class and call its Register() method automatically.
        internal class Metadata : IRegisterMetadata
        {
            // Called by the designer to register any design-time metadata.
            public void Register()
            {
                AttributeTableBuilder builder = new AttributeTableBuilder();
    
                // Add the adorner provider to the design-time metadata.
                builder.AddCustomAttributes(
                    typeof(DemoControl),
                    new FeatureAttribute(typeof(InplaceButtonAdorners)));
    
                MetadataStore.AddAttributeTable(builder.CreateTable());
    
            }
        }
    }
    
  8. 儲存方案。

實作裝飾項提供者。

裝飾項提供者實作在名為 InplaceButtonAdorners 的型別中。此裝飾項提供者可讓使用者在設計階段設定控制項的 Content 屬性。

若要實作裝飾項提供者

  1. 將名為 InplaceButtonAdorners 的新類別,加入至 CustomControlLibrary.VisualStudio.Design 專案。

  2. 在 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;
    
    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;
    
            public InplaceButtonAdorners()
            {
                adornersPanel = new AdornerPanel();
                adornersPanel.IsContentFocusable = true;
                adornersPanel.Children.Add(ActivateGlyph);
    
                Adorners.Add(adornersPanel);
            }
    
            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 = 10;
                        glyph.Height = 5;
                        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.
                        AdornerPlacementCollection placement = new AdornerPlacementCollection();
                        placement.PositionRelativeToContentHeight(0, 10);
                        placement.PositionRelativeToContentWidth(0, 5);
                        placement.SizeRelativeToAdornerDesiredHeight(1, 0);
                        placement.SizeRelativeToAdornerDesiredWidth(1, 0);
    
                        AdornerPanel.SetPlacements(glyph, placement);
    
                        // 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)
                    {
                        TextBox glyph = new TextBox();
                        glyph.BorderThickness = new Thickness(0);
                        glyph.Margin = new Thickness(4);
    
                        AdornerPlacementCollection placement = new AdornerPlacementCollection();
                        placement.PositionRelativeToContentWidth(0, 0);
                        placement.PositionRelativeToContentHeight(0, 0);
                        placement.SizeRelativeToContentHeight(1, 0);
                        placement.SizeRelativeToContentWidth(1, 0);
    
                        AdornerPanel.SetPlacements(glyph, placement);
    
                        // Data bind the glyph's vertical and horizontal alignment
                        // to the target control's alignment properties.
                        Binding binding = new Binding();
                        binding.Source = glyph;
                        binding.Path = new PropertyPath(
                            "(0).(1)", 
                            AdornerProperties.ActualViewProperty, 
                            Button.HorizontalContentAlignmentProperty);
                        glyph.SetBinding(TextBox.HorizontalContentAlignmentProperty, binding);
    
                        binding = new Binding();
                        binding.Source = glyph;
                        binding.Path = new PropertyPath(
                            "(0).(1)", 
                            AdornerProperties.ActualViewProperty, 
                            Button.VerticalContentAlignmentProperty);
                        glyph.SetBinding(TextBox.VerticalContentAlignmentProperty, binding);
    
                        // Make the glyph's background match the control's background. 
                        binding = new Binding();
                        binding.Source = glyph;
                        binding.Path = new PropertyPath(
                            "(0).(1)", 
                            AdornerProperties.ActualViewProperty, 
                            Button.BackgroundProperty);
                        glyph.SetBinding(TextBox.BackgroundProperty, binding);
    
                        // Two-way data bind the text box's text property to content.
                        binding = new Binding();
                        binding.Source = glyph;
                        binding.Path = new PropertyPath("(0).(1)[Content].(2)",
                          AdornerProperties.ActualModelProperty,
                          TypeDescriptor.GetProperties(
                              typeof(ModelItem))["Properties"],
                              TypeDescriptor.GetProperties(
                                  typeof(ModelProperty))["ComputedValue"]);
                        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;
                }
            }
    
            // 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 string)
                    {
                        return value;
                    }
    
                    return string.Empty;
                }
    
                public object ConvertBack(
                    object value, 
                    Type targetType, 
                    object parameter, 
                    System.Globalization.CultureInfo culture)
                {
                    return value;
                }
            }
        }
    }
    
  3. 建置方案。

測試設計階段實作

您可以使用 DemoControl 類別,就像使用任何其他 WPF 控制項一樣。WPF 設計工具會負責建立所有的設計階段物件。

若要測試設計階段實作

  1. 將 Visual C# 中名為 DemoApplication 的新 WPF 應用程式專案加入至方案。

    Window1.xaml 隨即在 WPF 設計工具中開啟。

  2. 加入 CustomControlLibrary 專案的參考。

  3. 在 [XAML] 檢視中,以下列 XAML 取代自動產生的 XAML。這個 XAML 會加入 CustomControlLibrary 命名空間的參考,並加入 DemoControl 自訂控制項。如果控制項未顯示,您可能需要按一下設計工具頂端的資訊列以重新載入檢視。

    <Window x:Class="DemoApplication.Window1"
        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. 重建方案。

  5. 在 [設計] 檢視中,按一下 DemoControl 控制項以選取該項目。

    DemoControl 控制項的左上角會出現一個小 Rectangle 圖像 (Glyph)。

  6. 按一下 Rectangle 圖像,以啟動就地編輯。

    文字方塊隨即出現,其中顯示 DemoControl 的 Content。由於內容目前是空的,您只會在按鈕的中央看到游標。

  7. 輸入文字內容的新值,然後按 ENTER 鍵。

    在 [XAML] 檢視中,Content 屬性會設定為您在 [設計] 檢視中輸入的文字值。

  8. 將 DemoApplication 專案設定為啟始專案,然後執行方案。

    在執行階段,按鈕就具有您使用裝飾項設定的文字值。

後續步驟

您可以將多個自訂設計階段功能,加入至自訂控制項。

請參閱

其他資源

建立自訂編輯器

WPF 設計工具擴充性