컨트롤 제작 개요

WPF(Windows Presentation Foundation) 컨트롤 모델의 확장성 덕분에 새 컨트롤을 만들 필요성이 상당히 줄어들었습니다. 그러나 어떤 경우에는 여전히 사용자 지정 컨트롤을 만들어야 할 수 있습니다. 이 항목에서는 WPF(Windows Presentation Foundation)에서 사용자 지정 컨트롤과 다양한 컨트롤 제작 모델을 만들 필요성을 최소화시키는 기능에 대해 설명합니다. 또한 새 컨트롤을 만드는 방법을 설명합니다.

새 컨트롤 작성에 대한 대안

지금까지 기존 컨트롤에서 사용자 지정 환경을 구현하려고 하면 배경색, 테두리 너비 및 글꼴 크기와 같은 컨트롤의 표준 속성을 변경하는 것으로 제한되어 있었습니다. 미리 정의된 이러한 매개 변수 이상으로 컨트롤의 모양이나 동작을 확장하려면 일반적으로 기존 컨트롤에서 상속받게 하고 컨트롤 그리기를 담당하는 메서드를 재정의하여 새 컨트롤을 만들어야 했습니다. 여전히 옵션이기는 하지만 WPF를 사용하면 풍부한 콘텐츠 모델, 스타일, 템플릿 및 트리거를 사용하여 기존 컨트롤을 사용자 지정할 수 있습니다. 다음 목록에는 새 컨트롤을 만들지 않고 이러한 기능을 사용하여 사용자 지정 및 일관된 환경을 만드는 예제가 나와 있습니다.

  • 풍부한 콘텐츠. 많은 표준 WPF 컨트롤이 풍부한 콘텐츠를 지원합니다. 예를 들어 의 콘텐츠 Button 속성은 Object 형식이므로 이론적으로 모든 내용을 에 표시할 수 Button 있습니다. 이미지와 텍스트를 표시 하는 단추를 이미지와 추가할 수 TextBlock 있습니다는 StackPanel 에 할당 하 StackPanel 고는 Content 속성입니다. 이러한 컨트롤은 WPF 시각적 요소와 임의의 데이터를 표시할 수 있기 때문에 복잡한 시각화를 지원하기 위해 새 컨트롤을 만들거나 기존 컨트롤을 수정할 필요성이 적습니다. 의 콘텐츠 모델 및 기타 콘텐츠 모델에 대한 자세한 내용은 Button WPF WPF 콘텐츠 모델 을 참조하세요.

  • 스타일. Style는 컨트롤의 속성을 나타내는 값의 컬렉션입니다. 스타일을 사용하면 새 컨트롤을 작성하지 않고도 원하는 컨트롤 모양과 동작을 재사용 가능한 표현으로 만들 수 있습니다. 예를 들어 모든 TextBlock 컨트롤의 글꼴 크기가 14인 빨간색 Arial 글꼴을 사용한다고 가정합니다. 스타일을 리소스로 만들고 이에 따라 적절한 속성을 설정할 수 있습니다. 그러면 TextBlock 애플리케이션에 추가할 때마다 모양이 동일합니다.

  • 데이터 템플릿. DataTemplate을 사용하면 컨트롤에 데이터가 표시되는 방식을 사용자 지정할 수 있습니다. 예를 들어 를 DataTemplate 사용하여 에 데이터가 표시되는 방식을 지정할 수 ListBox 있습니다. 이에 대한 예제는 데이터 템플릿 개요를 참조하세요. 데이터의 모양을 사용자 지정하는 것 외에도 DataTemplate 에는 사용자 지정 UI에 많은 유연성을 제공하는 UI 요소가 포함될 수 있습니다. 예를 들어 를 사용하여 DataTemplate ComboBox 각 항목에 확인란이 포함된 을 만들 수 있습니다.

  • 컨트롤 템플릿. 사용 하는 많은 WPF 컨트롤을 ControlTemplate 컨트롤의 구조와 모양을 정의 하는 컨트롤의 모양에서 컨트롤의 기능을 구분 합니다. 를 다시 정의하여 컨트롤의 모양을 크게 변경할 수 ControlTemplate 있습니다. 예를 들어 신호등 모양의 컨트롤이 필요하다고 가정해 보겠습니다. 이 컨트롤에는 간단한 사용자 인터페이스 및 기능이 있습니다. 컨트롤은 세 개의 원으로, 한 번에 하나씩만 불을 켤 수 있습니다. 일부 리플렉션 후에는 가 RadioButton 한 번에 하나의 기능만 선택하지만 의 기본 모양은 정지등의 조명과 전혀 비슷하지 않다는 것을 알 수 RadioButton 있습니다. 때문에 RadioButton 는 컨트롤 템플릿을 사용하여 모양을 정의하기 때문에 컨트롤의 요구 사항에 맞게 를 다시 ControlTemplate 정의하고 라디오 단추를 사용하여 정지를 만드는 것이 쉽습니다.

    참고

    에서는 를 사용할 수 있지만 이 예제에서는 가 RadioButton DataTemplate 충분하지 DataTemplate 않습니다. 는 DataTemplate 컨트롤 내용의 모양을 정의합니다. 의 경우 RadioButton 콘텐츠는 가 선택되었는지 여부를 나타내는 원 오른쪽에 표시되는 RadioButton 내용입니다. 신호등의 예제에서 라디오 버튼은 "불을 켤 수 있는" 원이어야 합니다. 중지등의 모양 요구 사항이 의 기본 모양과 다르므로 를 RadioButton 다시 정의해야 ControlTemplate 합니다. 일반적으로 DataTemplate 는 컨트롤의 콘텐츠(또는 데이터)를 정의하는 ControlTemplate 데 사용되며, 은 컨트롤이 구조화되는 방식을 정의하는 데 사용됩니다.

  • 트리거.Trigger 사용하면 새 컨트롤을 만들지 않고도 컨트롤의 모양과 동작을 동적으로 변경할 수 있습니다. 예를 들어 ListBox 애플리케이션에 컨트롤이 여러 개 있고 각 항목이 ListBox 선택될 때 굵게 표시되고 빨간색으로 표시된다고 가정합니다. 첫 번째 방법은 에서 상속되는 클래스를 만들고 ListBox OnSelectionChanged 메서드를 재정의하여 선택한 항목의 모양을 변경하는 것이지만, 더 나은 방법은 선택한 항목의 모양을 변경하는 의 스타일에 트리거를 추가하는 ListBoxItem 것입니다. 트리거를 사용하면 속성 값을 변경하거나 속성 값을 기반으로 작업을 수행할 수 있습니다. 를 사용하면 이벤트가 발생할 때 작업을 수행할 수 EventTrigger 있습니다.

스타일, 템플릿 및 트리거에 대한 자세한 내용은 스타일 지정 및 템플릿을 참조하세요.

일반적으로 컨트롤이 기존 컨트롤의 기능을 반영하지만 컨트롤이 다르게 보이게 하려면 이 섹션에서 설명하는 메서드 중 하나를 사용하여 기존 컨트롤의 모양을 변경할 수 있는지 여부를 먼저 고려해야 합니다.

컨트롤 제작 모델

풍부한 콘텐츠 모델, 스타일, 템플릿 및 트리거를 사용하면 새 컨트롤을 만들어야 하는 필요성이 최소화됩니다. 그러나 새 컨트롤을 만들어야 한다면 WPF의 다양한 컨트롤 제작 모델을 이해하는 것이 중요합니다. WPF는 컨트롤을 만들기 위해 세 가지 일반적인 모델을 제공하며 각 모델은 서로 다른 일련의 기능과 유연성 수준을 제공합니다. 세 가지 모델의 기본 클래스는 UserControl , 및 입니다. Control FrameworkElement

UserControl에서 파생

에서 컨트롤을 만드는 가장 간단한 방법은 WPF 에서 파생하는 UserControl 것입니다. 에서 상속되는 컨트롤을 빌드할 때 UserControl 기존 구성 요소를 에 UserControl 추가하고, 구성 요소의 이름을 지정하고, 에서 이벤트 처리기를 참조합니다. XAML(Extensible Application Markup Language) 그런 다음 코드에서 명명된 요소를 참조하고 이벤트 처리기를 정의할 수 있습니다. 이 개발 모델은 WPF의 애플리케이션 개발에 사용된 모델과 매우 유사합니다.

올바르게 빌드된 경우 는 UserControl 풍부한 콘텐츠, 스타일 및 트리거의 이점을 활용할 수 있습니다. 그러나 컨트롤이 에서 상속되는 경우 UserControl 컨트롤을 사용하는 사람은 또는 을 사용하여 모양을 사용자 지정할 수 DataTemplate ControlTemplate 없습니다. 템플릿을 Control 지원하는 사용자 지정 컨트롤을 만들려면 클래스 또는 파생 클래스 중 하나(이외)에서 UserControl 파생해야 합니다.

UserControl에서 파생하는 이점

다음이 모두 적용되는 경우 에서 파생하는 것이 좋습니다. UserControl

  • 애플리케이션을 빌드하는 방법과 유사하게 컨트롤을 빌드하려고 합니다.

  • 컨트롤이 기존 구성 요소로만 구성됩니다.

  • 복잡한 사용자 지정을 지원하지 않아도 됩니다.

Control에서 파생

파생 된 Control 클래스는 대부분의 기존 컨트롤에서 사용 되는 WPF 모델입니다. 클래스에서 상속되는 컨트롤을 만들 때 Control 템플릿을 사용하여 모양을 정의합니다. 그렇게 함으로써 작동 논리를 시각적 표현과 분리합니다. 이벤트 대신 명령 및 바인딩을 사용하고 가능하면 에서 요소를 참조하지 않도록 하여 UI 및 논리를 분리할 수도 ControlTemplate 있습니다. 컨트롤의 UI와 논리가 제대로 분리되면 컨트롤의 사용자가 컨트롤의 를 다시 정의하여 모양을 사용자 지정할 수 ControlTemplate 있습니다. 사용자 지정 Control 빌드는 를 빌드하는 것만큼 간단하지는 않지만 UserControl 사용자 지정 Control 은 가장 유연한 기능을 제공합니다.

Control에서 파생하는 이점

Control다음 중에서 적용되는 경우 클래스를 사용하는 대신 에서 파생하는 것이 좋습니다. UserControl

  • 를 통해 컨트롤의 모양을 사용자 지정할 수 있게 하려고 ControlTemplate 합니다.

  • 컨트롤이 다른 테마를 지원하게 하려고 합니다.

FrameworkElement에서 파생

UserControl에서 파생되거나 기존 Control 요소를 구성하는 데 의존하는 컨트롤입니다. 에서 상속되는 모든 개체가 에 있을 수 있기 때문에 많은 시나리오에서 이 솔루션은 허용 FrameworkElement 가능한 ControlTemplate 솔루션입니다. 그러나 컨트롤의 모양이 단순한 요소 컴퍼지션 이상의 기능을 필요로 하는 경우가 있습니다. 이러한 시나리오의 경우 구성 요소를 기반으로 하는 FrameworkElement 것이 올바른 선택입니다.

기반 구성 요소를 빌드하는 두 가지 표준 메서드인 FrameworkElement 직접 렌더링 및 사용자 지정 요소 컴퍼지션이 있습니다. 직접 렌더링에는 OnRender 의 메서드를 재정의하고 구성 요소 시각적 개체를 명시적으로 정의하는 작업을 제공하는 작업이 FrameworkElement DrawingContext 포함됩니다. 이는 및 에서 사용하는 Image Border 메서드입니다. 사용자 지정 요소 컴퍼지션에는 형식의 개체를 사용하여 Visual 구성 요소의 모양을 구성하는 작업이 포함됩니다. 예제는 DrawingVisual 개체 사용을 참조하세요. Track 는 WPF 사용자 지정 요소 컴퍼지션을 사용하는 의 컨트롤 예제입니다. 직접 렌더링과 사용자 지정 요소 컴퍼지션을 같은 컨트롤에서 혼합하여 사용할 수도 있습니다.

FrameworkElement에서 파생하는 이점

다음 중에서 적용되는 경우 에서 FrameworkElement 파생하는 것이 좋습니다.

  • 단순한 요소 컴퍼지션에서 제공하는 기능 이상으로 컨트롤의 모양을 정확하게 제어하려고 합니다.

  • 자체 렌더링 논리를 정의하여 컨트롤의 모양을 정의하려고 합니다.

  • 및 를 사용하여 가능한 것 이상의 새로운 방식으로 기존 요소를 작성하려고 UserControl Control 합니다.

컨트롤 제작 기본 사항

앞에서 설명한 것처럼 WPF의 가장 강력한 기능 중 하나는 컨트롤의 기본 속성 설정 이상으로 모양 및 동작을 변경하면서 사용자 지정 컨트롤을 만들지 않아도 되는 것입니다. 스타일 지정, 데이터 바인딩 및 트리거 기능은 WPF 속성 시스템 및 WPF 이벤트 시스템에 의해 가능합니다. 다음 섹션에서는 사용자 지정 컨트롤을 만드는 데 사용하는 모델에 관계없이 따라야 하는 몇 가지 방법을 설명합니다. 이에 따라 사용자 지정 컨트롤의 사용자는 WPF에 포함된 컨트롤의 경우처럼 이러한 기능을 사용할 수 있습니다.

종속성 속성 사용

속성이 종속성 속성인 경우 다음을 수행할 수 있습니다.

  • 스타일에서 속성을 설정합니다.

  • 속성을 데이터 소스에 바인딩합니다.

  • 속성의 값으로 동적 리소스를 사용합니다.

  • 속성에 애니메이션 효과를 줍니다.

컨트롤의 속성이 이 기능을 지원하도록 하려면 종속성 속성으로 구현해야 합니다. 다음 예제에서는 다음을 수행하여 Value라는 종속성 속성을 정의합니다.

  • 라는 DependencyProperty 식별자를 ValueProperty public static readonly 필드로 정의합니다.

  • 를 호출하여 속성 시스템에 속성 이름을 DependencyProperty.Register 등록하여 다음을 지정합니다.

    • 속성의 이름입니다.

    • 속성의 형식입니다.

    • 속성을 소유하는 형식입니다.

    • 속성의 메타데이터입니다. 메타 데이터에는 속성의 기본값인 CoerceValueCallback 및 가 PropertyChangedCallback 포함됩니다.

  • 속성의 Value 및 접근자를 구현하여 종속성 속성을 등록하는 데 사용되는 것과 동일한 이름인 라는 CLR 래퍼 속성을 get set 정의합니다. getset 접근자만 각각 및 를 GetValue SetValue 호출합니다. 종속성 속성의 접근자 클라이언트 및 접근자를 바이패스 하 고 호출 하 고 직접 수 있으므로 추가 논리를 포함 하지 않는 것이 WPF GetValue SetValue 좋습니다. 예를 들어 속성이 데이터 소스에 바인딩되면 해당 속성의 set 접근자가 호출되지 않습니다. get 및 set 접근자 에 논리를 추가하는 대신 , 및 대리자를 사용하여 ValidateValueCallback CoerceValueCallback PropertyChangedCallback 값이 변경되면 응답하거나 확인합니다. 이 콜백에 대한 자세한 내용은 종속성 속성 콜백 및 유효성 검사를 참조하세요.

  • 명명된 에 대한 메서드를 CoerceValueCallback CoerceValue 정의합니다. CoerceValueValueMinValue보다 크거나 같고 MaxValue보다 작거나 같도록 합니다.

  • 라는 에 대한 메서드를 PropertyChangedCallback OnValueChanged 정의합니다. OnValueChangedRoutedPropertyChangedEventArgs<T> 개체를 만들고 라우드된 이벤트를 발생시키기 위해 ValueChanged 준비합니다. 라우트된 이벤트는 다음 섹션에서 설명합니다.

/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value", typeof(decimal), typeof(NumericUpDown),
        new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
                                      new CoerceValueCallback(CoerceValue)));

/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{
    get { return (decimal)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

private static object CoerceValue(DependencyObject element, object value)
{
    decimal newValue = (decimal)value;
    NumericUpDown control = (NumericUpDown)element;

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));

    return newValue;
}

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    NumericUpDown control = (NumericUpDown)obj;			

    RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
        (decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
    control.OnValueChanged(e);
}
''' <summary>
''' Identifies the Value dependency property.
''' </summary>
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))

''' <summary>
''' Gets or sets the value assigned to the control.
''' </summary>
Public Property Value() As Decimal
    Get
        Return CDec(GetValue(ValueProperty))
    End Get
    Set(ByVal value As Decimal)
        SetValue(ValueProperty, value)
    End Set
End Property

Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
    Dim newValue As Decimal = CDec(value)
    Dim control As NumericUpDown = CType(element, NumericUpDown)

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))

    Return newValue
End Function

Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
    Dim control As NumericUpDown = CType(obj, NumericUpDown)

    Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
    control.OnValueChanged(e)
End Sub

자세한 내용은 사용자 지정 종속성 속성을 참조하세요.

라우트된 이벤트 사용

종속성 속성이 추가 기능을 사용하여 CLR 속성의 개념을 확장하는 것처럼 라우드된 이벤트는 표준 CLR 이벤트의 개념을 확장합니다. 라우트된 이벤트는 다음 동작을 지원하기 때문에 새로운 WPF 컨트롤을 만들 때 이벤트를 라우트된 이벤트로 구현하는 것도 좋습니다.

  • 이벤트는 여러 컨트롤의 부모에서 처리될 수 ​​있습니다. 이벤트가 버블링 이벤트인 경우 요소 트리의 단일 부모가 이벤트를 구독할 수 있습니다. 그런 다음 애플리케이션 작성자는 하나의 처리기를 사용하여 여러 컨트롤의 이벤트에 응답할 수 있습니다. 예를 들어 컨트롤이 의 각 항목에 포함된 ListBox 경우(에 DataTemplate 포함되므로) 애플리케이션 개발자는 에서 컨트롤의 이벤트에 대한 이벤트 처리기를 정의할 수 ListBox 있습니다. 이벤트가 컨트롤 중 하나에서 발생할 때마다 이벤트 처리기가 호출됩니다.

  • 라우팅된 이벤트는 애플리케이션 개발자가 스타일 내에서 이벤트의 처리기를 지정할 수 있도록 하는 에서 사용할 수 EventSetter 있습니다.

  • 라우팅된 이벤트는 에서 사용할 수 있습니다. 이 이벤트는 를 EventTrigger 사용하여 속성에 애니메이션을 제공하는 데 XAML 유용합니다. 자세한 내용은 애니메이션 개요를 참조하세요.

다음 예제는 다음을 수행하여 라우트된 이벤트를 정의합니다.

  • 라는 RoutedEvent 식별자를 ValueChangedEvent public static readonly 필드로 정의합니다.

  • 메서드를 호출하여 라우드된 이벤트를 EventManager.RegisterRoutedEvent 등록합니다. 이 예제에서는 를 호출할 때 다음 정보를 지정합니다. RegisterRoutedEvent

    • 이벤트의 이름은 ValueChanged입니다.

    • 라우팅 전략은 Bubble 입니다. 즉, 소스의 이벤트 처리기(이벤트를 발생시키는 개체)가 먼저 호출된 다음, 가장 가까운 부모 요소의 이벤트 처리기부터 시작하여 소스의 부모 요소에 대한 이벤트 처리기가 연속적으로 호출됩니다.

    • 이벤트 처리기의 형식은 RoutedPropertyChangedEventHandler<T> 형식으로 구성된 Decimal 입니다.

    • 이벤트의 소유 형식은 NumericUpDown입니다.

  • ValueChanged라는 공용 이벤트를 선언하고 이벤트 접근자 선언을 포함합니다. 이 예제에서는 AddHandler add 접근자 선언 및 RemoveHandler remove 접근자 선언에서 를 호출하여 WPF 이벤트 서비스를 사용합니다.

  • ValueChanged 이벤트를 발생시키는 OnValueChanged라는 보호된 가상 메서드를 만듭니다.

/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
    "ValueChanged", RoutingStrategy.Bubble,
    typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

/// <summary>
/// Occurs when the Value property changes.
/// </summary>
public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
{
    add { AddHandler(ValueChangedEvent, value); }
    remove { RemoveHandler(ValueChangedEvent, value); }
}

/// <summary>
/// Raises the ValueChanged event.
/// </summary>
/// <param name="args">Arguments associated with the ValueChanged event.</param>
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
{
    RaiseEvent(args);
}
''' <summary>
''' Identifies the ValueChanged routed event.
''' </summary>
Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))

''' <summary>
''' Occurs when the Value property changes.
''' </summary>
Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
    AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.AddHandler(ValueChangedEvent, value)
    End AddHandler
    RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.RemoveHandler(ValueChangedEvent, value)
    End RemoveHandler
    RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
    End RaiseEvent
End Event

''' <summary>
''' Raises the ValueChanged event.
''' </summary>
''' <param name="args">Arguments associated with the ValueChanged event.</param>
Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
    MyBase.RaiseEvent(args)
End Sub

자세한 내용은 라우트된 이벤트 개요사용자 지정 라우트된 이벤트 만들기를 참조하세요.

바인딩 사용

해당 논리에서 컨트롤의 UI를 분리하려면 데이터 바인딩 사용을 고려합니다. 이는 를 사용하여 컨트롤의 모양을 정의하는 경우에 특히 ControlTemplate 중요합니다. 데이터 바인딩을 사용하면 코드에서 UI의 특정 부분을 참조하지 않아도 될 수 있습니다. 코드가 에 있는 요소를 참조하고 이 ControlTemplate 변경되면 참조된 요소를 새 에 ControlTemplate 포함해야 하므로 에 있는 요소를 참조하지 않는 ControlTemplate 것이 ControlTemplate 좋습니다.

다음 예제에서는 업데이트 합니다 TextBlockNumericUpDown 컨트롤에 이름을 할당 하 고 코드에서 이름으로 텍스트 상자를 참조 합니다.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
  <TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
private void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}
Private Sub UpdateTextBlock()
    valueText.Text = Value.ToString()
End Sub

다음 예제에서는 바인딩을 사용하여 동일한 작업을 수행합니다.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">

    <!--Bind the TextBlock to the Value property-->
    <TextBlock 
        Width="60" TextAlignment="Right" Padding="5"
        Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                       AncestorType={x:Type local:NumericUpDown}}, 
                       Path=Value}"/>

</Border>

데이터 바인딩에 대한 자세한 내용은 데이터 바인딩 개요를 참조하세요.

디자이너를 위한 디자인

Visual Studio 위해 WPF 디자이너에서 사용자 지정 WPF 컨트롤에 대한 지원을 받으려면(예: 속성 창 사용하여 속성 편집) 다음 지침을 따르세요. WPF 디자이너용 개발에 대한 자세한 내용은 Visual Studio XAML 디자인을참조하세요.

종속성 속성

get set "종속성 속성 사용"에서 앞에서 설명한 대로 CLR 및 접근자를 구현해야 합니다. 디자이너는 래퍼를 사용하여 종속성 속성의 존재를 감지할 수 있지만 WPF 및 컨트롤의 클라이언트와 마찬가지로 속성을 가져오거나 설정할 때 접근자를 호출할 필요가 없습니다.

연결된 속성

다음 지침을 사용하여 사용자 지정 컨트롤에서 연결된 속성을 구현해야 합니다.

  • public static readonly DependencyProperty 메서드를 사용하여 만든 PropertyName Property 형식의 를 갖습니다. RegisterAttached 에 전달되는 속성 이름은 RegisterAttached PropertyName과 일치해야 합니다.

  • SetPropertyNameGetPropertyName 이라는 public static CLR 메서드 쌍을 구현합니다. 두 메서드 모두 에서 파생된 클래스를 첫 번째 인수로 허용해야 DependencyProperty 합니다. SetPropertyName 메서드는 그 형식이 속성의 등록된 데이터 형식과 일치하는 인수도 수락합니다. GetPropertyName 메서드는 동일한 형식의 값을 반환해야 합니다. SetPropertyName 메서드가 누락된 경우 속성이 읽기 전용으로 표시됩니다.

  • SetPropertyNameGet PropertyName은 GetValue 각각 대상 종속성 개체의 및 메서드로 직접 라우팅해야 SetValue 합니다. 디자이너는 메서드 래퍼를 통해 호출하거나 대상 종속성 개체를 직접 호출하여 연결된 속성에 액세스할 수 있습니다.

연결된 속성에 대한 자세한 내용은 연결된 속성 개요를 참조하세요.

공유 리소스 정의 및 사용

애플리케이션과 동일한 어셈블리에 컨트롤을 포함하거나 여러 애플리케이션에서 사용할 수 있는 별도의 어셈블리에 컨트롤을 패키지화할 수 있습니다. 대부분, 이 항목에서 설명하는 정보는 사용하는 메서드에 관계없이 적용됩니다. 그러나 주목할 만한 차이점이 하나 있습니다. 애플리케이션과 동일한 어셈블리에 컨트롤을 배치하면 App.xaml 파일에 전역 리소스를 자유롭게 추가할 수 있습니다. 그러나 컨트롤만 포함된 어셈블리에는 Application 연결된 개체가 없으므로 App.xaml 파일을 사용할 수 없습니다.

애플리케이션이 리소스를 찾을 때 다음 순서로 세 가지 수준을 조사합니다.

  1. 요소 수준

    시스템이 리소스를 참조하는 요소로 시작한 다음 루트 요소에 도달할 때까지 논리 부모 등의 리소스를 검색합니다.

  2. 애플리케이션 수준

    개체에 의해 정의된 Application 리소스입니다.

  3. 테마 수준

    테마 수준 사전은 Themes라는 하위 폴더에 저장됩니다. Themes 폴더의 파일은 테마에 해당합니다. 예를 들어 Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml 등이 있을 수 있습니다. generic.xaml이라는 파일이 있을 수도 있습니다. 시스템이 테마 수준에서 리소스를 찾으면 먼저 테마별 파일에서 찾은 다음 generic.xaml에서 찾습니다.

컨트롤이 애플리케이션과 별도의 어셈블리에 있을 때는 전역 리소스를 요소 수준이나 테마 수준에 배치해야 합니다. 두 가지 방법 모두 장점이 있습니다.

요소 수준에서 리소스 정의

사용자 지정 리소스 사전을 만들고 컨트롤의 리소스 사전과 병합하여 요소 수준에서 공유 리소스를 정의할 수 있습니다. 이 메서드를 사용하면 리소스 파일의 이름을 원하는 대로 지정할 수 있으며 컨트롤과 동일한 폴더에 배치할 수 있습니다. 요소 수준의 리소스는 간단한 문자열을 키로 사용할 수도 있습니다. 다음 예제에서는 LinearGradientBrush Dictionary1 라는 리소스 파일을 만듭니다.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <LinearGradientBrush 
    x:Key="myBrush"  
    StartPoint="0,0" EndPoint="1,1">
    <GradientStop Color="Red" Offset="0.25" />
    <GradientStop Color="Blue" Offset="0.75" />
  </LinearGradientBrush>
  
</ResourceDictionary>

사전을 정의한 후에는 컨트롤의 리소스 사전과 병합해야 합니다. XAML 또는 코드를 사용하여 이 작업을 수행할 수 있습니다.

다음 예제는 XAML을 사용하여 리소스 사전을 병합합니다.

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

이 방법의 단점은 ResourceDictionary 참조할 때마다 개체가 만들어지기 때문입니다. 예를 들어 라이브러리에 10 개의 사용자 지정 컨트롤이 있고 XAML을 사용 하 여 각 컨트롤에 대 한 공유 리소스 사전을 병합 하는 경우 10 개의 동일한 개체를 만듭니다 ResourceDictionary . 코드의 리소스를 병합 하 고 결과를 반환 하는 정적 클래스를 만들어이를 방지할 수 있습니다 ResourceDictionary .

다음 예제에서는 공유를 반환 하는 클래스를 만듭니다 ResourceDictionary .

internal static class SharedDictionaryManager
{
    internal static ResourceDictionary SharedDictionary
    {
        get
        {
            if (_sharedDictionary == null)
            {
                System.Uri resourceLocater =
                    new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml",
                                    System.UriKind.Relative);

                _sharedDictionary =
                    (ResourceDictionary)Application.LoadComponent(resourceLocater);
            }

            return _sharedDictionary;
        }
    }

    private static ResourceDictionary _sharedDictionary;
}

다음 예제에서는 공유 리소스를 InitializeComponent를 호출하기 전에 컨트롤의 생성자에 있는 사용자 지정 컨트롤의 리소스와 병합합니다. 는 SharedDictionaryManager.SharedDictionary 정적 속성 이므로은 ResourceDictionary 한 번만 만들어집니다. InitializeComponent가 호출되기 전에 리소스 사전이 병합되었기 때문에 XAML 파일의 컨트롤에서 리소스를 사용할 수 있습니다.

public NumericUpDown()
{
    this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
    InitializeComponent();
}

테마 수준에서 리소스 정의

WPF를 사용하면 다양한 Windows 테마를 위한 리소스를 만들 수 있습니다. 컨트롤 작성자는 특정 테마의 리소스를 정의하여 사용 중인 테마에 따라 컨트롤의 모양을 변경할 수 있습니다. 예를 들어 Button Windows 클래식 테마에서의 모양 (2000 Windows의 기본 테마)은 Button Button ControlTemplate 각 테마에 대해 다른를 사용 하기 때문에 Windows Luna 테마 (Windows XP의 기본 테마)의와 다릅니다.

테마와 관련된 리소스는 특정 파일 이름의 리소스 사전에 보관됩니다. 이러한 파일은 컨트롤이 포함된 폴더의 하위 폴더인 Themes라는 폴더에 있어야 합니다. 다음 표에는 각 파일과 관련된 리소스 사전 파일과 테마가 나와 있습니다.

리소스 사전 파일 이름 Windows 테마
Classic.xaml Windows XP의 고전 Windows 9x/2000 모양
Luna.NormalColor.xaml Windows XP의 기본 파란색 테마
Luna.Homestead.xaml Windows XP의 올리브색 테마
Luna.Metallic.xaml Windows XP의 은색 테마
Royale.NormalColor.xaml Windows XP Media Center Edition의 기본 테마
Aero.NormalColor.xaml Windows Vista의 기본 테마

모든 테마에 대해 리소스를 정의할 필요는 없습니다. 특정 테마에 대해 리소스가 정의되지 않은 경우 컨트롤이 리소스에 대해 Classic.xaml을 확인합니다. 현재 테마에 해당하는 파일 또는 Classic.xaml에 리소스가 정의되지 않은 경우 컨트롤이 generic.xaml이라는 리소스 사전 파일에 있는 제네릭 리소스를 사용합니다. generic.xaml 파일은 테마별 리소스 사전 파일과 같은 폴더에 있습니다. generic.xaml은 특정 Windows 테마에 해당하지 않지만 여전히 테마 수준의 사전입니다.

테마 및 UI 자동화 지원 샘플을 포함 하는 c # 또는 Visual Basic NumericUpDown 사용자 지정 컨트롤에는 컨트롤에 대 한 두 개의 리소스 사전이 포함 되어 있습니다. NumericUpDown 하나는 Luna입니다.

ControlTemplate 테마별 리소스 사전 파일에 배치 하는 경우 컨트롤에 대 한 정적 생성자를 만들고 OverrideMetadata(Type, PropertyMetadata) DefaultStyleKey 다음 예제와 같이에서 메서드를 호출 해야 합니다.

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
    DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
테마 리소스에 대한 키 정의 및 참조

요소 레벨에서 리소스를 정의할 때 문자열을 키로 지정하고 문자열을 통해 리소스에 액세스할 수 있습니다. 테마 수준에서 리소스를 정의 하는 경우를 키로 사용 해야 합니다 ComponentResourceKey . 다음 예제에서는 generic.xaml에서 리소스를 정의합니다.

<LinearGradientBrush 
     x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter}, 
                                  ResourceId=MyEllipseBrush}"  
                                  StartPoint="0,0" EndPoint="1,0">
    <GradientStop Color="Blue" Offset="0" />
    <GradientStop Color="Red" Offset="0.5" />
    <GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>

다음 예제에서는를 키로 지정 하 여 리소스를 참조 합니다 ComponentResourceKey .

<RepeatButton 
    Grid.Column="1" Grid.Row="0"
    Background="{StaticResource {ComponentResourceKey 
                        TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                        ResourceId=ButtonBrush}}">
    Up
</RepeatButton>
<RepeatButton 
    Grid.Column="1" Grid.Row="1"
    Background="{StaticResource {ComponentResourceKey 
                    TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                    ResourceId=ButtonBrush}}">
    Down
 </RepeatButton>
테마 리소스의 위치 지정

컨트롤에 대한 리소스를 찾으려면 호스팅 애플리케이션이 어셈블리에 컨트롤 관련 리소스가 있는지 알아야 합니다. ThemeInfoAttribute컨트롤을 포함 하는 어셈블리에를 추가 하 여이를 수행할 수 있습니다. 에는 ThemeInfoAttribute GenericDictionaryLocation 제네릭 리소스의 위치를 지정 하는 속성과 ThemeDictionaryLocation 테마별 리소스의 위치를 지정 하는 속성이 있습니다.

다음 예제에서는 GenericDictionaryLocation 및 속성을 ThemeDictionaryLocation 로 설정 SourceAssembly 하 여 제네릭 및 테마별 리소스가 컨트롤과 동일한 어셈블리에 있도록 지정 합니다.

[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
           ResourceDictionaryLocation.SourceAssembly)]
<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>

추가 정보