チュートリアル : 埋め込み先編集の実装
このチュートリアルでは、Windows Presentation Foundation (WPF) カスタム コントロールに対して埋め込み先編集の機能を実装する方法について説明します。 WPF Designer for Visual Studio のこのデザイン時機能を使用すると、カスタム ボタン コントロールの 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 プロパティをデザイン時に設定できるようになります。
装飾プロバイダーを実装するには
InplaceButtonAdorners という名前の新しいクラスを CustomControlLibrary.VisualStudio.Design プロジェクトに追加します。
InplaceButtonAdorners のコード エディターで、自動的に生成されたコードを次のコードに置き換えます。 このコードにより、TextBox コントロールに基づく装飾を提供する PrimarySelectionAdornerProvider を実装します。
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 デザイナーは、すべてのデザイン時オブジェクトの作成を処理します。
デザイン時実装をテストするには
Visual C# の DemoApplication という名前の WPF アプリケーション プロジェクトをソリューションに追加します。
WPF デザイナーで MainWindow.xaml が開きます。
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 コントロールをクリックして選択します。
小さな Rectangle グリフが DemoControl コントロールの左上端に表示されます。
Rectangle グリフをクリックして、埋め込み先編集を有効にします。
DemoControl の Content を示すテキスト ボックスが表示されます。 今は、内容は空であるため、単にボタンの中央にカーソルが表示されるだけです。
テキスト コンテンツの新しい値を入力して、Enter キーを押します。
XAML ビュー内の Content プロパティは、デザイン ビューで入力したテキスト値に設定されます。
DemoApplication プロジェクトをスタートアップ プロジェクトとして設定し、ソリューションを実行します。
実行時には、装飾で設定したテキスト値がボタンに表示されます。
次の手順
カスタム コントロールに、さらにカスタム デザイン時機能を追加できます。
カスタム デザイン時に対して MenuAction を追加します。 詳細については、「チュートリアル: メニュー プロバイダーの作成」を参照してください。
[プロパティ] ウィンドウで使用できるカスタム カラー エディターを作成します。 詳細については、「チュートリアル : カラー エディターの実装」を参照してください。
コントロールの不透明度を設定するカスタム装飾を作成します。 詳細については、「チュートリアル : デザイン時装飾の作成」を参照してください。