연습: 디자인 타임 표시기 만들기
업데이트: 2007년 11월
이 연습에서는 WPF(Windows Presentation Foundation) 사용자 지정 컨트롤에 대한 디자인 타임 표시기(Adorner)를 만드는 방법을 보여 줍니다. Windows Presentation Foundation(WPF) Designer for Visual Studio에서 이 표시기를 사용하여 사용자 지정 단추 컨트롤의 Opacity 속성 값을 설정할 수 있습니다. 이 연습에서 컨트롤은 단순한 단추이고 표시기는 단추의 투명도를 변경하는 데 사용할 수 있는 슬라이더입니다. 전체 코드는 방법: 디자인 타임 표시기 만들기를 참조하십시오.
이 연습에서는 다음 작업을 수행합니다.
WPF 사용자 지정 컨트롤 라이브러리 프로젝트를 만듭니다.
디자인 타임 메타데이터를 위한 별도의 어셈블리를 만듭니다.
표시기 공급자를 구현합니다.
디자인 타임에 컨트롤을 테스트합니다.
이 연습을 마치면 사용자 지정 컨트롤에 대한 표시기 공급자를 만드는 방법을 이해하게 됩니다.
참고: |
---|
실제 설정이나 버전에 따라서 화면에 나타나는 대화 상자와 메뉴 명령이 도움말의 설명과 다를 수 있습니다. @fsFX@설정을 변경하려면 도구 메뉴에서 설정 가져오기 및 내보내기를 선택합니다. 자세한 내용은 Visual Studio 설정을 참조하십시오. |
사전 요구 사항
이 연습을 완료하려면 다음 구성 요소가 필요합니다.
- Visual Studio 2008
사용자 지정 컨트롤 만들기
첫 번째 단계로 사용자 지정 컨트롤에 대한 프로젝트를 만듭니다. 이 컨트롤은 디자인 타임 코드가 약간 사용되는 간단한 단추이며, 이 코드에서는 GetIsInDesignMode 메서드를 사용하여 디자인 타임 동작을 구현합니다.
사용자 지정 컨트롤을 만들려면
Visual Basic 또는 Visual C#에서 CustomControlLibrary라는 새 WPF 사용자 지정 컨트롤 라이브러리 프로젝트를 만듭니다.
코드 편집기에 CustomControl1의 코드가 열립니다.
솔루션 탐색기에서 코드 파일의 이름을 ButtonWithDesignTime.cs 또는 ButtonWithDesignTime.vb로 변경합니다. 이 프로젝트의 모든 참조에서 이름을 바꿀지 묻는 메시지 상자가 표시되면 예를 클릭합니다.
솔루션 탐색기에서 테마 폴더를 확장합니다.
Generic.xaml을 두 번 클릭합니다.
WPF Designer에서 Generic.xaml이 열립니다.
XAML 뷰에서 발견되는 모든 "CustomControl1"을 "ButtonWithDesignTime"으로 바꿉니다.
코드 편집기에서 ButtonWithDesignTime.cs 또는 ButtonWithDesignTime.vb를 엽니다.
자동으로 생성된 코드를 다음 코드로 바꿉니다. ButtonWithDesignTime 사용자 지정 컨트롤은 Button에서 상속되며 디자이너에서 단추가 나타날 때 "Design mode active"라는 텍스트를 표시합니다. GetIsInDesignMode 검사 및 다음 디자인 타임 코드는 선택적이며 데모 용도로만 표시됩니다.
Imports System Imports System.Collections.Generic Imports System.Text Imports System.Windows.Controls Imports System.Windows.Media Imports System.ComponentModel Public Class ButtonWithDesignTime Inherits Button Public Sub New() ' The GetIsInDesignMode check and the following design-time ' code are optional and shown only for demonstration. If DesignerProperties.GetIsInDesignMode(Me) Then Content = "Design mode active" End If End Sub End Class
using System; using System.Collections.Generic; using System.Text; using System.Windows.Controls; using System.Windows.Media; using System.ComponentModel; namespace CustomControlLibrary { public class ButtonWithDesignTime : Button { public ButtonWithDesignTime() { // The GetIsInDesignMode check and the following design-time // code are optional and shown only for demonstration. if (DesignerProperties.GetIsInDesignMode(this)) { Content = "Design mode active"; } } } }
프로젝트의 출력 경로를 "bin\"으로 설정합니다.
솔루션을 빌드합니다.
디자인 타임 메타데이터 어셈블리 만들기
디자인 타임 코드는 특수 메타데이터 어셈블리에 배포됩니다. 자세한 내용은 방법: 메타데이터 저장소 사용을 참조하십시오. 이 연습에서 사용자 지정 표시기는 Visual Studio에서만 지원되고 CustomControlLibrary.VisualStudio.Design이라는 어셈블리에 배포됩니다.
디자인 타임 메타데이터 어셈블리를 만들려면
Visual Basic 또는 Visual C#에서 CustomControlLibrary.VisualStudio.Design이라는 새 클래스 라이브러리 프로젝트를 솔루션에 추가합니다.
프로젝트의 출력 경로를 "..\CustomControlLibrary\bin\"으로 설정합니다. 이렇게 하면 컨트롤의 어셈블리와 메타데이터 어셈블리가 같은 폴더에 유지되므로 디자이너에서 메타데이터를 검색할 수 있습니다.
다음 WPF 어셈블리에 대한 참조를 추가합니다.
PresentationCore
PresentationFramework
WindowsBase
다음 WPF Designer 어셈블리에 대한 참조를 추가합니다.
Microsoft.Windows.Design
Microsoft.Windows.Design.Extensibility
Microsoft.Windows.Design.Interaction
CustomControlLibrary 프로젝트에 대한 참조를 추가합니다.
솔루션 탐색기에서 Class1 코드 파일의 이름을 Metadata.cs 또는 Metadata.vb로 변경합니다.
자동으로 생성된 코드를 다음 코드로 바꿉니다. 이 코드는 사용자 지정 디자인 타임 구현을 ButtonWithDesignTime 클래스에 연결하는 AttributeTable을 만듭니다.
Imports System Imports System.Collections.Generic Imports System.Text Imports System.ComponentModel Imports System.Windows.Media Imports System.Windows.Controls Imports System.Windows Imports CustomControlLibrary Imports Microsoft.Windows.Design.Features Imports 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. Friend Class Metadata Implements IRegisterMetadata ' Called by the designer to register any design-time metadata. Public Sub Register() Implements IRegisterMetadata.Register Dim builder As New AttributeTableBuilder() ' Add the adorner provider to the design-time metadata. builder.AddCustomAttributes(GetType(ButtonWithDesignTime), _ New FeatureAttribute(GetType(OpacitySliderAdornerProvider))) MetadataStore.AddAttributeTable(builder.CreateTable()) End Sub End Class End Namespace
using System; using System.Collections.Generic; using System.Text; using System.ComponentModel; using System.Windows.Media; using System.Windows.Controls; using System.Windows; using CustomControlLibrary; 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(ButtonWithDesignTime), new FeatureAttribute(typeof(OpacitySliderAdornerProvider))); MetadataStore.AddAttributeTable(builder.CreateTable()); } } }
솔루션을 저장합니다.
표시기 공급자 구현
표시기 공급자는 OpacitySliderAdornerProvider라는 형식에 구현됩니다. 사용자는 이 표시기를 통해 디자인 타임에 컨트롤의 Opacity 속성을 설정할 수 있습니다.
표시기 공급자를 구현하려면
CustomControlLibrary.VisualStudio.Design 프로젝트에 OpacitySliderAdornerProvider라는 새 클래스를 추가합니다.
OpacitySliderAdornerProvider에 대한 코드 편집기에서 자동으로 생성된 코드를 다음 코드로 바꿉니다. 이 코드는 Slider 컨트롤을 기반으로 표시기를 제공하는 PrimarySelectionAdornerProvider를 구현합니다.
Imports System Imports System.Collections.Generic Imports System.Text Imports System.Windows.Input Imports System.Windows Imports System.Windows.Automation Imports System.Windows.Controls Imports System.Windows.Media Imports System.Windows.Shapes Imports Microsoft.Windows.Design.Interaction Imports Microsoft.Windows.Design.Model Namespace CustomControlLibrary.VisualStudio.Design ' The following class implements an adorner provider for the ' adorned control. The adorner is a slider control, which ' changes the Background opacity of the adorned control. Class OpacitySliderAdornerProvider Inherits PrimarySelectionAdornerProvider Private adornedControlModel As ModelItem Private batchedChange As ModelEditingScope Private opacitySlider As Slider Private opacitySliderAdornerPanel As AdornerPanel Public Sub New() opacitySlider = New Slider() End Sub ' The following method is called when the adorner is activated. ' It creates the adorner control, sets up the adorner panel, ' and attaches a ModelItem to the adorned control. Protected Overrides Sub Activate(ByVal item As ModelItem, ByVal view As DependencyObject) ' Save the ModelItem and hook into when it changes. ' This enables updating the slider position when ' a new Background value is set. adornedControlModel = item AddHandler adornedControlModel.PropertyChanged, AddressOf AdornedControlModel_PropertyChanged ' Setup the slider's min and max values. opacitySlider.Minimum = 0 opacitySlider.Maximum = 1 ' Setup the adorner panel. ' All adorners are placed in an AdornerPanel ' for sizing and layout support. Dim myPanel = Me.Panel AdornerPanel.SetHorizontalStretch(opacitySlider, AdornerStretch.Stretch) AdornerPanel.SetVerticalStretch(opacitySlider, AdornerStretch.None) Dim placement As New AdornerPlacementCollection() ' The adorner's width is relative to the content. ' The slider extends the full width of the control it adorns. placement.SizeRelativeToContentWidth(1.0, 0) ' The adorner's height is the same as the slider's. placement.SizeRelativeToAdornerDesiredHeight(1.0, 0) ' Position the adorner above the control it adorns. placement.PositionRelativeToAdornerHeight(-1.0, 0) ' Position the adorner up 5 pixels. This demonstrates ' that these placement calls are additive. These two calls ' are equivalent to the following single call: ' PositionRelativeToAdornerHeight(-1.0, -5). placement.PositionRelativeToAdornerHeight(0, -5) AdornerPanel.SetPlacements(opacitySlider, placement) ' Initialize the slider when it is loaded. AddHandler opacitySlider.Loaded, AddressOf slider_Loaded ' Handle the value changes of the slider control. AddHandler opacitySlider.ValueChanged, AddressOf slider_ValueChanged AddHandler opacitySlider.PreviewMouseLeftButtonUp, _ AddressOf slider_MouseLeftButtonUp AddHandler opacitySlider.PreviewMouseLeftButtonDown, _ AddressOf slider_MouseLeftButtonDown MyBase.Activate(item, view) End Sub ' The Panel utility property demand-creates the ' adorner panel and adds it to the provider's ' Adorners collection. Public ReadOnly Property Panel() As AdornerPanel Get If Me.opacitySliderAdornerPanel Is Nothing Then Me.opacitySliderAdornerPanel = New AdornerPanel() ' Add the adorner to the adorner panel. Me.opacitySliderAdornerPanel.Children.Add(opacitySlider) ' Add the panel to the Adorners collection. Adorners.Add(opacitySliderAdornerPanel) End If Return Me.opacitySliderAdornerPanel End Get End Property ' The following method deactivates the adorner. Protected Overrides Sub Deactivate() RemoveHandler adornedControlModel.PropertyChanged, _ AddressOf AdornedControlModel_PropertyChanged MyBase.Deactivate() End Sub ' The following method handles the PropertyChanged event. ' It updates the slider control's value if the adorned control's ' Background property changed, Sub AdornedControlModel_PropertyChanged( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.PropertyChangedEventArgs) If e.PropertyName = "Background" Then opacitySlider.Value = GetCurrentOpacity() End If End Sub ' The following method handles the Loaded event. ' It assigns the slider control's initial value. Sub slider_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) opacitySlider.Value = GetCurrentOpacity() End Sub ' The following method handles the MouseLeftButtonDown event. ' It calls the BeginEdit method on the ModelItem which represents ' the adorned control. Sub slider_MouseLeftButtonDown( _ ByVal sender As Object, _ ByVal e As System.Windows.Input.MouseButtonEventArgs) batchedChange = adornedControlModel.BeginEdit() End Sub ' The following method handles the MouseLeftButtonUp event. ' It commits any changes made to the ModelItem which represents the ' the adorned control. Sub slider_MouseLeftButtonUp( _ ByVal sender As Object, _ ByVal e As System.Windows.Input.MouseButtonEventArgs) If Not (batchedChange Is Nothing) Then batchedChange.Complete() batchedChange.Dispose() batchedChange = Nothing End If End Sub ' The following method handles the slider control's ' ValueChanged event. It sets the value of the ' Background opacity by using the ModelProperty type. Sub slider_ValueChanged( _ ByVal sender As Object, _ ByVal e As RoutedPropertyChangedEventArgs(Of Double)) If (True) Then Dim newOpacityValue As Double = e.NewValue ' During setup, don't make a value local and set the opacity. If newOpacityValue = GetCurrentOpacity() Then Return End If ' Access the adorned control's Background property ' by using the ModelProperty type. Dim backgroundProperty As ModelProperty = _ adornedControlModel.Properties(Control.BackgroundProperty) If Not backgroundProperty.IsSet Then ' If the value isn't local, make it local ' before setting a sub-property value. backgroundProperty.SetValue(backgroundProperty.ComputedValue) End If ' Set the Opacity property on the Background Brush. backgroundProperty.Value.Properties(Brush.OpacityProperty).SetValue(newOpacityValue) End If End Sub ' This utility method gets the adorned control's ' Background brush by using the ModelItem. Function GetCurrentOpacity() As Double If (True) Then Dim backgroundBrushComputedValue As Brush = _ CType(adornedControlModel.Properties(Control.BackgroundProperty).ComputedValue, _ Brush) Return backgroundBrushComputedValue.Opacity End If End Function End Class End Namespace
using System; using System.Collections.Generic; using System.Text; using System.Windows.Input; using System.Windows; using System.Windows.Automation; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using Microsoft.Windows.Design.Interaction; using Microsoft.Windows.Design.Model; namespace CustomControlLibrary.VisualStudio.Design { // The following class implements an adorner provider for the // adorned control. The adorner is a slider control, which // changes the Background opacity of the adorned control. class OpacitySliderAdornerProvider : PrimarySelectionAdornerProvider { private ModelItem adornedControlModel; private ModelEditingScope batchedChange; private Slider opacitySlider; private AdornerPanel opacitySliderAdornerPanel; public OpacitySliderAdornerProvider() { opacitySlider = new Slider(); } // The following method is called when the adorner is activated. // It creates the adorner control, sets up the adorner panel, // and attaches a ModelItem to the adorned control. protected override void Activate(ModelItem item, DependencyObject view) { // Save the ModelItem and hook into when it changes. // This enables updating the slider position when // a new Background value is set. adornedControlModel = item; adornedControlModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler( AdornedControlModel_PropertyChanged); // Setup the slider's min and max values. opacitySlider.Minimum = 0; opacitySlider.Maximum = 1; // Setup the adorner panel. // All adorners are placed in an AdornerPanel // for sizing and layout support. AdornerPanel myPanel = this.Panel; AdornerPanel.SetHorizontalStretch(opacitySlider, AdornerStretch.Stretch); AdornerPanel.SetVerticalStretch(opacitySlider, AdornerStretch.None); AdornerPlacementCollection placement = new AdornerPlacementCollection(); // The adorner's width is relative to the content. // The slider extends the full width of the control it adorns. placement.SizeRelativeToContentWidth(1.0, 0); // The adorner's height is the same as the slider's. placement.SizeRelativeToAdornerDesiredHeight(1.0, 0); // Position the adorner above the control it adorns. placement.PositionRelativeToAdornerHeight(-1.0, 0); // Position the adorner up 5 pixels. This demonstrates // that these placement calls are additive. These two calls // are equivalent to the following single call: // PositionRelativeToAdornerHeight(-1.0, -5). placement.PositionRelativeToAdornerHeight(0, -5); AdornerPanel.SetPlacements(opacitySlider, placement); // Initialize the slider when it is loaded. opacitySlider.Loaded += new RoutedEventHandler(slider_Loaded); // Handle the value changes of the slider control. opacitySlider.ValueChanged += new RoutedPropertyChangedEventHandler<double>( slider_ValueChanged); opacitySlider.PreviewMouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler( slider_MouseLeftButtonUp); opacitySlider.PreviewMouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler( slider_MouseLeftButtonDown); base.Activate(item, view); } // The Panel utility property demand-creates the // adorner panel and adds it to the provider's // Adorners collection. public AdornerPanel Panel { get { if (this.opacitySliderAdornerPanel == null) { opacitySliderAdornerPanel = new AdornerPanel(); opacitySliderAdornerPanel.Children.Add(opacitySlider); // Add the panel to the Adorners collection. Adorners.Add(opacitySliderAdornerPanel); } return this.opacitySliderAdornerPanel; } } // The following method deactivates the adorner. protected override void Deactivate() { adornedControlModel.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler( AdornedControlModel_PropertyChanged); base.Deactivate(); } // The following method handles the PropertyChanged event. // It updates the slider control's value if the adorned control's // Background property changed, void AdornedControlModel_PropertyChanged( object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == "Background") { opacitySlider.Value = GetCurrentOpacity(); } } // The following method handles the Loaded event. // It assigns the slider control's initial value. void slider_Loaded(object sender, RoutedEventArgs e) { opacitySlider.Value = GetCurrentOpacity(); } // The following method handles the MouseLeftButtonDown event. // It calls the BeginEdit method on the ModelItem which represents // the adorned control. void slider_MouseLeftButtonDown( object sender, System.Windows.Input.MouseButtonEventArgs e) { batchedChange = adornedControlModel.BeginEdit(); } // The following method handles the MouseLeftButtonUp event. // It commits any changes made to the ModelItem which represents the // the adorned control. void slider_MouseLeftButtonUp( object sender, System.Windows.Input.MouseButtonEventArgs e) { if (batchedChange != null) { batchedChange.Complete(); batchedChange.Dispose(); batchedChange = null; } } // The following method handles the slider control's // ValueChanged event. It sets the value of the // Background opacity by using the ModelProperty type. void slider_ValueChanged( object sender, RoutedPropertyChangedEventArgs<double> e) { double newOpacityValue = e.NewValue; // During setup, don't make a value local and set the opacity. if (newOpacityValue == GetCurrentOpacity()) { return; } // Access the adorned control's Background property // by using the ModelProperty type. ModelProperty backgroundProperty = adornedControlModel.Properties[Control.BackgroundProperty]; if (!backgroundProperty.IsSet) { // If the value isn't local, make it local // before setting a sub-property value. backgroundProperty.SetValue(backgroundProperty.ComputedValue); } // Set the Opacity property on the Background Brush. backgroundProperty.Value.Properties[Brush.OpacityProperty].SetValue(newOpacityValue); } // This utility method gets the adorned control's // Background brush by using the ModelItem. private double GetCurrentOpacity() { Brush backgroundBrushComputedValue = (Brush)adornedControlModel.Properties[Control.BackgroundProperty].ComputedValue; return backgroundBrushComputedValue.Opacity; } } }
솔루션을 빌드합니다.
디자인 타임 구현 테스트
ButtonWithDesignTime 컨트롤을 다른 WPF 컨트롤과 같은 방식으로 사용할 수 있습니다. WPF Designer에서는 모든 디자인 타임 개체의 생성을 처리합니다.
디자인 타임 구현을 테스트하려면
Visual Basic 또는 Visual C#에서 DemoApplication이라는 WPF 응용 프로그램 프로젝트를 솔루션에 추가합니다.
WPF Designer에 Window1.xaml이 열립니다.
CustomControlLibrary 프로젝트에 대한 참조를 추가합니다.
XAML 뷰에서 자동으로 생성된 XAML을 다음 XAML로 바꿉니다. 이 XAML은 CustomControlLibrary 네임스페이스에 대한 참조를 추가하고 ButtonWithDesignTime 사용자 지정 컨트롤을 추가합니다. 디자인 뷰에서 단추가 "Design mode active"라는 텍스트와 함께 표시되어 디자인 모드임을 나타냅니다. @FSHO4@단추가 표시되지 않는 경우 디자이너 위쪽의 정보 표시줄을 클릭하여 뷰를 다시 로드해야 할 수 있습니다.
<Window x:Class="DemoApplication.Window1" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:cc="clr-namespace:CustomControlLibrary;assembly=CustomControlLibrary" Title="Window1" Height="300" Width="300"> <Grid> <cc:ButtonWithDesignTime Margin="30,30,30,30" Background="#FFD4D0C8"></cc:ButtonWithDesignTime> </Grid> </Window>
<Window x:Class="DemoApplication.Window1" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:cc="clr-namespace:CustomControlLibrary;assembly=CustomControlLibrary" Title="Window1" Height="300" Width="300"> <Grid> <cc:ButtonWithDesignTime Margin="30,30,30,30" Background="#FFD4D0C8"></cc:ButtonWithDesignTime> </Grid> </Window>
디자인 뷰에서 ButtonWithDesignTime 컨트롤을 클릭하여 선택합니다.
Slider 컨트롤이 ButtonWithDesignTime 컨트롤 위에 나타납니다.
슬라이더 컨트롤 표시기를 사용하여 단추의 투명도를 변경합니다.
DemoApplication 프로젝트를 실행합니다.
런타임에 단추의 투명도가 표시기를 사용하여 설정한 투명도로 바뀝니다.
다음 단계
사용자 지정 컨트롤에 사용자 지정 디자인 타임 기능을 더 추가할 수 있습니다.
사용자 지정 디자인 타임에 MenuAction을 추가합니다. 자세한 내용은 연습: MenuAction 만들기를 참조하십시오.
내부 편집을 위한 표시기 공급자를 만듭니다. 자세한 내용은 연습: 내부 편집 기능 구현을 참조하십시오.
속성 창에서 사용할 수 있는 사용자 지정 색 편집기를 만듭니다. 자세한 내용은 연습: 색 편집기 구현을 참조하십시오.
참고 항목
작업
참조
PrimarySelectionAdornerProvider