컨트롤 제작 개요Control Authoring Overview

WPF(Windows Presentation Foundation)Windows Presentation Foundation (WPF) 컨트롤 모델의 확장성 덕분에 새 컨트롤을 만들 필요성이 상당히 줄어들었습니다.The extensibility of the WPF(Windows Presentation Foundation)Windows Presentation Foundation (WPF) control model greatly reduces the need to create a new control. 그러나 어떤 경우에는 여전히 사용자 지정 컨트롤을 만들어야 할 수 있습니다.However, in certain cases you may still need to create a custom control. 이 항목에서는 WPF(Windows Presentation Foundation)Windows Presentation Foundation (WPF)에서 사용자 지정 컨트롤과 다양한 컨트롤 제작 모델을 만들 필요성을 최소화시키는 기능에 대해 설명합니다.This topic discusses the features that minimize your need to create a custom control and the different control authoring models in WPF(Windows Presentation Foundation)Windows Presentation Foundation (WPF). 또한 새 컨트롤을 만드는 방법을 설명합니다.This topic also demonstrates how to create a new control.

새 컨트롤 작성에 대한 대안Alternatives to Writing a New Control

지금까지 기존 컨트롤에서 사용자 지정 환경을 구현하려고 하면 배경색, 테두리 너비 및 글꼴 크기와 같은 컨트롤의 표준 속성을 변경하는 것으로 제한되어 있었습니다.Historically, if you wanted to get a customized experience from an existing control, you were limited to changing the standard properties of the control, such as background color, border width, and font size. 미리 정의된 이러한 매개 변수 이상으로 컨트롤의 모양이나 동작을 확장하려면 일반적으로 기존 컨트롤에서 상속받게 하고 컨트롤 그리기를 담당하는 메서드를 재정의하여 새 컨트롤을 만들어야 했습니다.If you wished to extend the appearance or behavior of a control beyond these predefined parameters, you would need to create a new control, usually by inheriting from an existing control and overriding the method responsible for drawing the control. 여전히 옵션이기는 하지만 WPFWPF를 사용하면 풍부한 콘텐츠 모델, 스타일, 템플릿 및 트리거를 사용하여 기존 컨트롤을 사용자 지정할 수 있습니다.Although that is still an option, WPFWPF enables to you customize existing controls by using its rich content model, styles, templates, and triggers. 다음 목록에는 새 컨트롤을 만들지 않고 이러한 기능을 사용하여 사용자 지정 및 일관된 환경을 만드는 예제가 나와 있습니다.The following list gives examples of how these features can be used to create custom and consistent experiences without having to create a new control.

  • 풍부한 콘텐츠.Rich Content. 많은 표준 WPFWPF 컨트롤이 풍부한 콘텐츠를 지원합니다.Many of the standard WPFWPF controls support rich content. 예를 들어 Button의 content 속성은 Object형식 이므로 이론적으로는 Button에 표시 될 수 있습니다.For example, the content property of a Button is of type Object, so theoretically anything can be displayed on a Button. 단추에 이미지와 텍스트를 표시 하려면 이미지와 TextBlockStackPanel에 추가 하 고 StackPanelContent 속성에 할당할 수 있습니다.To have a button display an image and text, you can add an image and a TextBlock to a StackPanel and assign the StackPanel to the Content property. 이러한 컨트롤은 WPFWPF 시각적 요소와 임의의 데이터를 표시할 수 있기 때문에 복잡한 시각화를 지원하기 위해 새 컨트롤을 만들거나 기존 컨트롤을 수정할 필요성이 적습니다.Because the controls can display WPFWPF visual elements and arbitrary data, there is less need to create a new control or to modify an existing control to support a complex visualization. WPFWPFButton 및 기타 콘텐츠 모델에 대 한 콘텐츠 모델에 대 한 자세한 내용은 WPF 콘텐츠 모델을 참조 하세요.For more information about the content model for Button and other content models in WPFWPF, see WPF Content Model.

  • 스타일.Styles. Style는 컨트롤의 속성을 나타내는 값의 컬렉션입니다.A Style is a collection of values that represent properties for a control. 스타일을 사용하면 새 컨트롤을 작성하지 않고도 원하는 컨트롤 모양과 동작을 재사용 가능한 표현으로 만들 수 있습니다.By using styles, you can create a reusable representation of a desired control appearance and behavior without writing a new control. 예를 들어 모든 TextBlock 컨트롤의 글꼴 크기가 14 인 빨강 굴림 글꼴을 사용 하려고 한다고 가정 합니다.For example, assume that you want all of your TextBlock controls to have red, Arial font with a font size of 14. 스타일을 리소스로 만들고 이에 따라 적절한 속성을 설정할 수 있습니다.You can create a style as a resource and set the appropriate properties accordingly. 그러면 응용 프로그램에 추가 하는 모든 TextBlock의 모양이 동일 하 게 됩니다.Then every TextBlock that you add to your application will have the same appearance.

  • 데이터 템플릿.Data Templates. DataTemplate를 사용 하 여 컨트롤에 데이터를 표시 하는 방법을 사용자 지정할 수 있습니다.A DataTemplate enables you to customize how data is displayed on a control. 예를 들어 DataTemplate를 사용 하 여 ListBox에 데이터를 표시 하는 방법을 지정할 수 있습니다.For example, a DataTemplate can be used to specify how data is displayed in a ListBox. 이에 대한 예제는 데이터 템플릿 개요를 참조하세요.For an example of this, see Data Templating Overview. 데이터의 모양을 사용자 지정 하는 것 외에도 DataTemplate는 UI 요소를 포함 하 여 사용자 지정 ui에서 많은 유연성을 제공 합니다.In addition to customizing the appearance of data, a DataTemplate can include UI elements, which gives you a lot of flexibility in custom UIs. 예를 들어 DataTemplate를 사용 하 여 각 항목에 확인란을 포함 하는 ComboBox을 만들 수 있습니다.For example, by using a DataTemplate, you can create a ComboBox in which each item contains a check box.

  • 컨트롤 템플릿.Control Templates. WPFWPF의 많은 컨트롤은 ControlTemplate를 사용 하 여 컨트롤의 모양과 모양을 정의 합니다 .이 컨트롤은 컨트롤의 기능과 컨트롤의 모양을 구분 합니다.Many controls in WPFWPF use a ControlTemplate to define the control's structure and appearance, which separates the appearance of a control from the functionality of the control. ControlTemplate를 재정의 하 여 컨트롤의 모양을 크게 변경할 수 있습니다.You can drastically change the appearance of a control by redefining its ControlTemplate. 예를 들어 신호등 모양의 컨트롤이 필요하다고 가정해 보겠습니다.For example, suppose you want a control that looks like a stoplight. 이 컨트롤에는 간단한 사용자 인터페이스 및 기능이 있습니다.This control has a simple user interface and functionality. 컨트롤은 세 개의 원으로, 한 번에 하나씩만 불을 켤 수 있습니다.The control is three circles, only one of which can be lit up at a time. 일부 리플렉션 후 RadioButton은 한 번에 하나만 선택 하는 기능을 제공 하지만 RadioButton의 기본 모양은 신호등의 조명 처럼 보입니다.After some reflection, you might realize that a RadioButton offers the functionality of only one being selected at a time, but the default appearance of the RadioButton looks nothing like the lights on a stoplight. RadioButton 컨트롤 템플릿을 사용 하 여 모양을 정의 하기 때문에 컨트롤의 요구 사항에 맞게 ControlTemplate를 다시 정의 하 고 라디오 단추를 사용 하 여 신호등을 만들 수 있습니다.Because the RadioButton uses a control template to define its appearance, it is easy to redefine the ControlTemplate to fit the requirements of the control, and use radio buttons to make your stoplight.

    참고

    RadioButton에서 DataTemplate를 사용할 수 있지만이 예제에서는 DataTemplate 충분 하지 않습니다.Although a RadioButton can use a DataTemplate, a DataTemplate is not sufficient in this example. DataTemplate는 컨트롤 내용의 모양을 정의 합니다.The DataTemplate defines the appearance of the content of a control. RadioButton의 경우 RadioButton 선택 되었는지 여부를 나타내는 원의 오른쪽에 내용이 표시 됩니다.In the case of a RadioButton, the content is whatever appears to the right of the circle that indicates whether the RadioButton is selected. 신호등의 예제에서 라디오 버튼은 "불을 켤 수 있는" 원이어야 합니다.In the example of the stoplight, the radio button needs just be a circle that can "light up." 신호등의 모양 요구 사항은 RadioButton의 기본 모양과 다르기 때문에 ControlTemplate를 다시 정의 해야 합니다.Because the appearance requirement for the stoplight is so different than the default appearance of the RadioButton, it is necessary to redefine the ControlTemplate. 일반적으로 DataTemplate는 컨트롤의 콘텐츠 (또는 데이터)를 정의 하는 데 사용 되 고 ControlTemplate는 컨트롤의 구성 방법을 정의 하는 데 사용 됩니다.In general a DataTemplate is used for defining the content (or data) of a control, and a ControlTemplate is used for defining how a control is structured.

  • 트리거.Triggers. Trigger를 사용 하면 새 컨트롤을 만들지 않고도 컨트롤의 모양과 동작을 동적으로 변경할 수 있습니다.A Trigger allows you to dynamically change the appearance and behavior of a control without creating a new control. 예를 들어 응용 프로그램에 여러 개의 ListBox 컨트롤이 있고 각 ListBox의 항목이 선택 될 때 굵게 표시 되 고 빨간색으로 표시 되는 경우가 있습니다.For example, suppose you have multiple ListBox controls in your application and want the items in each ListBox to be bold and red when they are selected. 첫 번째 이러한는 ListBox에서 상속 하는 클래스를 만들고 OnSelectionChanged 메서드를 재정의 하 여 선택한 항목의 모양을 변경 하는 것이 고, 선택 된 항목의 모양을 변경 하는 ListBoxItem 스타일에 트리거를 추가 하는 것이 더 나은 방법입니다.Your first instinct might be to create a class that inherits from ListBox and override the OnSelectionChanged method to change the appearance of the selected item, but a better approach is to add a trigger to a style of a ListBoxItem that changes the appearance of the selected item. 트리거를 사용하면 속성 값을 변경하거나 속성 값을 기반으로 작업을 수행할 수 있습니다.A trigger enables you to change property values or take actions based on the value of a property. EventTrigger를 사용 하면 이벤트가 발생할 때 작업을 수행할 수 있습니다.An EventTrigger enables you to take actions when an event occurs.

스타일, 템플릿 및 트리거에 대한 자세한 내용은 스타일 지정 및 템플릿을 참조하세요.For more information about styles, templates, and triggers, see Styling and Templating.

일반적으로 컨트롤이 기존 컨트롤의 기능을 반영하지만 컨트롤이 다르게 보이게 하려면 이 섹션에서 설명하는 메서드 중 하나를 사용하여 기존 컨트롤의 모양을 변경할 수 있는지 여부를 먼저 고려해야 합니다.In general, if your control mirrors the functionality of an existing control, but you want the control to look different, you should first consider whether you can use any of the methods discussed in this section to change the existing control's appearance.

컨트롤 제작 모델Models for Control Authoring

풍부한 콘텐츠 모델, 스타일, 템플릿 및 트리거를 사용하면 새 컨트롤을 만들어야 하는 필요성이 최소화됩니다.The rich content model, styles, templates, and triggers minimize the need for you to create a new control. 그러나 새 컨트롤을 만들어야 한다면 WPFWPF의 다양한 컨트롤 제작 모델을 이해하는 것이 중요합니다.However, if you do need to create a new control, it is important to understand the different control authoring models in WPFWPF. WPFWPF는 컨트롤을 만들기 위해 세 가지 일반적인 모델을 제공하며 각 모델은 서로 다른 일련의 기능과 유연성 수준을 제공합니다.provides three general models for creating a control, each of which provides a different set of features and level of flexibility. 세 모델의 기본 클래스는 UserControl, ControlFrameworkElement입니다.The base classes for the three models are UserControl, Control, and FrameworkElement.

UserControl에서 파생Deriving from UserControl

WPFWPF에서 컨트롤을 만드는 가장 간단한 방법은 UserControl에서 파생 하는 것입니다.The simplest way to create a control in WPFWPF is to derive from UserControl. UserControl에서 상속 되는 컨트롤을 빌드할 때 UserControl에 기존 구성 요소를 추가 하 고, 구성 요소 이름을, XAML(Extensible Application Markup Language)Extensible Application Markup Language (XAML)에서 이벤트 처리기를 참조 합니다.When you build a control that inherits from UserControl, you add existing components to the UserControl, name the components, and reference event handlers in XAML(Extensible Application Markup Language)Extensible Application Markup Language (XAML). 그런 다음 코드에서 명명된 요소를 참조하고 이벤트 처리기를 정의할 수 있습니다.You can then reference the named elements and define the event handlers in code. 이 개발 모델은 WPFWPF의 애플리케이션 개발에 사용된 모델과 매우 유사합니다.This development model is very similar to the model used for application development in WPFWPF.

올바르게 빌드된 경우 UserControl는 풍부한 콘텐츠, 스타일 및 트리거의 이점을 활용할 수 있습니다.If built correctly, a UserControl can take advantage of the benefits of rich content, styles, and triggers. 그러나 컨트롤이 UserControl에서 상속 되는 경우 컨트롤을 사용 하는 사용자는 DataTemplate 또는 ControlTemplate를 사용 하 여 모양을 사용자 지정할 수 없습니다.However, if your control inherits from UserControl, people who use your control will not be able to use a DataTemplate or ControlTemplate to customize its appearance. 템플릿을 지 원하는 사용자 지정 컨트롤을 만들려면 Control 클래스 또는 파생 클래스 중 하나 (UserControl제외)에서 파생 해야 합니다.It is necessary to derive from the Control class or one of its derived classes (other than UserControl) to create a custom control that supports templates.

UserControl에서 파생하는 이점Benefits of Deriving from UserControl

다음이 모두 적용 되는 경우 UserControl에서 파생 하는 것이 좋습니다.Consider deriving from UserControl if all of the following apply:

  • 애플리케이션을 빌드하는 방법과 유사하게 컨트롤을 빌드하려고 합니다.You want to build your control similarly to how you build an application.

  • 컨트롤이 기존 구성 요소로만 구성됩니다.Your control consists only of existing components.

  • 복잡한 사용자 지정을 지원하지 않아도 됩니다.You don't need to support complex customization.

Control에서 파생Deriving from Control

Control 클래스에서 파생 하는 것은 대부분의 기존 WPFWPF 컨트롤에서 사용 하는 모델입니다.Deriving from the Control class is the model used by most of the existing WPFWPF controls. Control 클래스에서 상속 되는 컨트롤을 만들 때 템플릿을 사용 하 여 해당 컨트롤의 모양을 정의 합니다.When you create a control that inherits from the Control class, you define its appearance by using templates. 그렇게 함으로써 작동 논리를 시각적 표현과 분리합니다.By doing so, you separate the operational logic from the visual representation. 이벤트 대신 명령 및 바인딩을 사용 하 고 가능 하면 ControlTemplate에서 요소를 참조 하지 않도록 하 여 UI와 논리의 분리를 보장할 수도 있습니다.You can also ensure the decoupling of the UI and logic by using commands and bindings instead of events and avoiding referencing elements in the ControlTemplate whenever possible. 컨트롤의 UI와 논리가 적절히 분리 된 경우 컨트롤의 사용자는 컨트롤의 ControlTemplate를 다시 정의 하 여 모양을 사용자 지정할 수 있습니다.If the UI and logic of your control are properly decoupled, a user of your control can redefine the control's ControlTemplate to customize its appearance. 사용자 지정 Control을 빌드하는 것은 UserControl를 구축 하는 것 만큼 간단 하지 않지만 사용자 지정 Control는 유연성이 가장 높습니다.Although building a custom Control is not as simple as building a UserControl, a custom Control provides the most flexibility.

Control에서 파생하는 이점Benefits of Deriving from Control

다음 중 하나가 적용 되는 경우 UserControl 클래스를 사용 하는 대신 Control에서 파생 하는 것이 좋습니다.Consider deriving from Control instead of using the UserControl class if any of the following apply:

  • ControlTemplate를 통해 컨트롤의 모양을 사용자 지정할 수 있습니다.You want the appearance of your control to be customizable via the ControlTemplate.

  • 컨트롤이 다른 테마를 지원하게 하려고 합니다.You want your control to support different themes.

FrameworkElement에서 파생Deriving from FrameworkElement

UserControl 또는 Control에서 파생 되는 컨트롤은 기존 요소 작성에 의존 합니다.Controls that derive from UserControl or Control rely upon composing existing elements. 대부분의 시나리오에서는 FrameworkElement에서 상속 되는 모든 개체가 ControlTemplate에 있을 수 있기 때문에이 방법이 적합 합니다.For many scenarios, this is an acceptable solution, because any object that inherits from FrameworkElement can be in a ControlTemplate. 그러나 컨트롤의 모양이 단순한 요소 컴퍼지션 이상의 기능을 필요로 하는 경우가 있습니다.However, there are times when a control's appearance requires more than the functionality of simple element composition. 이러한 시나리오의 경우 구성 요소를 FrameworkElement 기반으로 하는 것이 적합 합니다.For these scenarios, basing a component on FrameworkElement is the right choice.

FrameworkElement기반 구성 요소를 빌드하는 데는 직접 렌더링과 사용자 지정 요소 컴퍼지션 이라는 두 가지 표준 방법이 있습니다.There are two standard methods for building FrameworkElement-based components: direct rendering and custom element composition. 직접 렌더링을 수행 하려면 FrameworkElementOnRender 메서드를 재정의 하 고 구성 요소 시각적 개체를 명시적으로 정의 하는 DrawingContext 작업을 제공 해야 합니다.Direct rendering involves overriding the OnRender method of FrameworkElement and providing DrawingContext operations that explicitly define the component visuals. ImageBorder에서 사용 하는 방법입니다.This is the method used by Image and Border. 사용자 지정 요소 컴퍼지션에는 Visual 형식의 개체를 사용 하 여 구성 요소의 모양을 구성 하는 작업이 포함 됩니다.Custom element composition involves using objects of type Visual to compose the appearance of your component. 예제는 DrawingVisual 개체 사용을 참조하세요.For an example, see Using DrawingVisual Objects. Track은 사용자 지정 요소 컴퍼지션을 사용 하는 WPFWPF 컨트롤의 예입니다.Track is an example of a control in WPFWPF that uses custom element composition. 직접 렌더링과 사용자 지정 요소 컴퍼지션을 같은 컨트롤에서 혼합하여 사용할 수도 있습니다.It is also possible to mix direct rendering and custom element composition in the same control.

FrameworkElement에서 파생하는 이점Benefits of Deriving from FrameworkElement

다음 중 하나가 적용 되는 경우 FrameworkElement에서 파생 하는 것이 좋습니다.Consider deriving from FrameworkElement if any of the following apply:

  • 단순한 요소 컴퍼지션에서 제공하는 기능 이상으로 컨트롤의 모양을 정확하게 제어하려고 합니다.You want to have precise control over the appearance of your control beyond what is provided by simple element composition.

  • 자체 렌더링 논리를 정의하여 컨트롤의 모양을 정의하려고 합니다.You want to define the appearance of your control by defining your own render logic.

  • UserControlControl를 사용 하 여 가능한 한 novel 방식으로 기존 요소를 구성 하려고 합니다.You want to compose existing elements in novel ways that go beyond what is possible with UserControl and Control.

컨트롤 제작 기본 사항Control Authoring Basics

앞에서 설명한 것처럼 WPFWPF의 가장 강력한 기능 중 하나는 컨트롤의 기본 속성 설정 이상으로 모양 및 동작을 변경하면서 사용자 지정 컨트롤을 만들지 않아도 되는 것입니다.As discussed earlier, one of the most powerful features of WPFWPF is the ability to go beyond setting basic properties of a control to change its appearance and behavior, yet still not needing to create a custom control. 스타일 지정, 데이터 바인딩 및 트리거 기능은 WPFWPF 속성 시스템 및 WPFWPF 이벤트 시스템에 의해 가능합니다.The styling, data binding, and trigger features are made possible by the WPFWPF property system and the WPFWPF event system. 다음 섹션에서는 사용자 지정 컨트롤을 만드는 데 사용하는 모델에 관계없이 따라야 하는 몇 가지 방법을 설명합니다. 이에 따라 사용자 지정 컨트롤의 사용자는 WPFWPF에 포함된 컨트롤의 경우처럼 이러한 기능을 사용할 수 있습니다.The following sections describe some practices that you should follow, regardless of the model you use to create the custom control, so that users of your custom control can use these features just as they would for a control that is included with WPFWPF.

종속성 속성 사용Use Dependency Properties

속성이 종속성 속성인 경우 다음을 수행할 수 있습니다.When a property is a dependency property, it is possible to do the following:

  • 스타일에서 속성을 설정합니다.Set the property in a style.

  • 속성을 데이터 소스에 바인딩합니다.Bind the property to a data source.

  • 속성의 값으로 동적 리소스를 사용합니다.Use a dynamic resource as the property's value.

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

컨트롤의 속성이 이 기능을 지원하도록 하려면 종속성 속성으로 구현해야 합니다.If you want a property of your control to support any of this functionality, you should implement it as a dependency property. 다음 예제에서는 다음을 수행하여 Value라는 종속성 속성을 정의합니다.The following example defines a dependency property named Value by doing the following:

  • ValueProperty 이라는 DependencyProperty 식별자를 public static readonly 필드로 정의 합니다.Define a DependencyProperty identifier named ValueProperty as a public static readonly field.

  • DependencyProperty.Register를 호출 하 여 속성 시스템에 속성 이름을 등록 하 고 다음을 지정 합니다.Register the property name with the property system, by calling DependencyProperty.Register, to specify the following:

    • 속성의 이름입니다.The name of the property.

    • 속성의 형식입니다.The type of the property.

    • 속성을 소유하는 형식입니다.The type that owns the property.

    • 속성의 메타데이터입니다.The metadata for the property. 메타 데이터에는 속성의 기본값, CoerceValueCallbackPropertyChangedCallback포함 되어 있습니다.The metadata contains the property's default value, a CoerceValueCallback and a PropertyChangedCallback.

  • 속성의 getset 접근자를 구현 하 여 종속성 속성을 등록 하는 데 사용 되는 것과 동일한 이름인 Value라는 CLR 래퍼 속성을 정의 합니다.Define a CLR wrapper property named Value, which is the same name that is used to register the dependency property, by implementing the property's get and set accessors. getset 접근자는 각각 GetValueSetValue를 호출 합니다.Note that the get and set accessors only call GetValue and SetValue respectively. 클라이언트와 WPFWPF에서 접근자를 사용 하지 않고 GetValue를 호출 하 고 직접 SetValue 수 있으므로 종속성 속성의 접근자에 추가 논리를 포함 하지 않는 것이 좋습니다.It is recommended that the accessors of dependency properties not contain additional logic because clients and WPFWPF can bypass the accessors and call GetValue and SetValue directly. 예를 들어 속성이 데이터 소스에 바인딩되면 해당 속성의 set 접근자가 호출되지 않습니다.For example, when a property is bound to a data source, the property's set accessor is not called. Get 및 set 접근자에 추가 논리를 추가 하는 대신 ValidateValueCallback, CoerceValueCallbackPropertyChangedCallback 대리자를 사용 하 여 응답 하거나 값이 변경 될 때 값을 확인 합니다.Instead of adding additional logic to the get and set accessors, use the ValidateValueCallback, CoerceValueCallback, and PropertyChangedCallback delegates to respond to or check the value when it changes. 이 콜백에 대한 자세한 내용은 종속성 속성 콜백 및 유효성 검사를 참조하세요.For more information on these callbacks, see Dependency Property Callbacks and Validation.

  • CoerceValue라는 CoerceValueCallback에 대 한 메서드를 정의 합니다.Define a method for the CoerceValueCallback named CoerceValue. CoerceValueValueMinValue보다 크거나 같고 MaxValue보다 작거나 같도록 합니다.CoerceValue ensures that Value is greater or equal to MinValue and less than or equal to MaxValue.

  • OnValueChanged이라는 PropertyChangedCallback에 대 한 메서드를 정의 합니다.Define a method for the PropertyChangedCallback, named OnValueChanged. OnValueChanged RoutedPropertyChangedEventArgs<T> 개체를 만들고 ValueChanged 라우트된 이벤트를 발생 시 키도 록 준비 합니다.OnValueChanged creates a RoutedPropertyChangedEventArgs<T> object and prepares to raise the ValueChanged routed event. 라우트된 이벤트는 다음 섹션에서 설명합니다.Routed events are discussed in the next section.

       /// <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

자세한 내용은 사용자 지정 종속성 속성을 참조하세요.For more information, see Custom Dependency Properties.

라우트된 이벤트 사용Use Routed Events

종속성 속성이 추가 기능을 사용 하 여 CLR 속성의 개념을 확장 하는 것 처럼 라우트된 이벤트는 표준 CLR 이벤트의 개념을 확장 합니다.Just as dependency properties extend the notion of CLR properties with additional functionality, routed events extend the notion of standard CLR events. 라우트된 이벤트는 다음 동작을 지원하기 때문에 새로운 WPFWPF 컨트롤을 만들 때 이벤트를 라우트된 이벤트로 구현하는 것도 좋습니다.When you create a new WPFWPF control, it is also good practice to implement your event as a routed event because a routed event supports the following behavior:

  • 이벤트는 여러 컨트롤의 부모에서 처리될 수 ​​있습니다.Events can be handled on a parent of multiple controls. 이벤트가 버블링 이벤트인 경우 요소 트리의 단일 부모가 이벤트를 구독할 수 있습니다.If an event is a bubbling event, a single parent in the element tree can subscribe to the event. 그런 다음 애플리케이션 작성자는 하나의 처리기를 사용하여 여러 컨트롤의 이벤트에 응답할 수 있습니다.Then application authors can use one handler to respond to the event of multiple controls. 예를 들어 컨트롤이 DataTemplate에 포함 되어 ListBox에 있는 각 항목의 일부인 경우 응용 프로그램 개발자는 ListBox에서 컨트롤의 이벤트에 대 한 이벤트 처리기를 정의할 수 있습니다.For example, if your control is a part of each item in a ListBox (because it is included in a DataTemplate), the application developer can define the event handler for your control's event on the ListBox. 이벤트가 컨트롤 중 하나에서 발생할 때마다 이벤트 처리기가 호출됩니다.Whenever the event occurs on any of the controls, the event handler is called.

  • 라우트된 이벤트는 응용 프로그램 개발자가 스타일 내에서 이벤트 처리기를 지정할 수 있도록 하는 EventSetter에서 사용할 수 있습니다.Routed events can be used in an EventSetter, which enables application developers to specify the handler of an event within a style.

  • 라우트된 이벤트는 XAMLXAML를 사용 하 여 속성에 애니메이션을 적용 하는 데 유용한 EventTrigger에서 사용할 수 있습니다.Routed events can be used in an EventTrigger, which is useful for animating properties by using XAMLXAML. 자세한 내용은 애니메이션 개요를 참조하세요.For more information, see Animation Overview.

다음 예제는 다음을 수행하여 라우트된 이벤트를 정의합니다.The following example defines a routed event by doing the following:

  • ValueChangedEvent 이라는 RoutedEvent 식별자를 public static readonly 필드로 정의 합니다.Define a RoutedEvent identifier named ValueChangedEvent as a public static readonly field.

  • EventManager.RegisterRoutedEvent 메서드를 호출 하 여 라우트된 이벤트를 등록 합니다.Register the routed event by calling the EventManager.RegisterRoutedEvent method. 이 예에서는 RegisterRoutedEvent를 호출할 때 다음 정보를 지정 합니다.The example specifies the following information when it calls RegisterRoutedEvent:

    • 이벤트의 이름은 ValueChanged입니다.The name of the event is ValueChanged.

    • 라우팅 전략은 Bubble입니다. 즉, 소스 (이벤트를 발생 시키는 개체)에 대 한 이벤트 처리기가 먼저 호출 된 다음 소스의 부모 요소에 대 한 이벤트 처리기가 가장 가까운에서 이벤트 처리기부터 시작 하 여 연속적으로 호출 됩니다. 부모 요소입니다.The routing strategy is Bubble, which means that an event handler on the source (the object that raises the event) is called first, and then event handlers on the source's parent elements are called in succession, starting with the event handler on the closest parent element.

    • 이벤트 처리기의 형식은 Decimal 형식으로 생성 된 RoutedPropertyChangedEventHandler<T>됩니다.The type of the event handler is RoutedPropertyChangedEventHandler<T>, constructed with a Decimal type.

    • 이벤트의 소유 형식은 NumericUpDown입니다.The owning type of the event is NumericUpDown.

  • ValueChanged라는 공용 이벤트를 선언하고 이벤트 접근자 선언을 포함합니다.Declare a public event named ValueChanged and includes event-accessor declarations. 이 예제에서는 add 접근자 선언에서 AddHandler를 호출 하 고 remove 접근자 선언에 RemoveHandler 하 여 WPFWPF 이벤트 서비스를 사용 합니다.The example calls AddHandler in the add accessor declaration and RemoveHandler in the remove accessor declaration to use the WPFWPF event services.

  • ValueChanged 이벤트를 발생시키는 OnValueChanged라는 보호된 가상 메서드를 만듭니다.Create a protected, virtual method named OnValueChanged that raises the ValueChanged event.

/// <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

자세한 내용은 라우트된 이벤트 개요사용자 지정 라우트된 이벤트 만들기를 참조하세요.For more information, see Routed Events Overview and Create a Custom Routed Event.

바인딩 사용Use Binding

해당 논리에서 컨트롤의 UI를 분리하려면 데이터 바인딩 사용을 고려합니다.To decouple the UI of your control from its logic, consider using data binding. ControlTemplate를 사용 하 여 컨트롤의 모양을 정의 하는 경우에 특히 중요 합니다.This is particularly important if you define the appearance of your control by using a ControlTemplate. 데이터 바인딩을 사용하면 코드에서 UI의 특정 부분을 참조하지 않아도 될 수 있습니다.When you use data binding, you might be able to eliminate the need to reference specific parts of the UI from the code. ControlTemplate에 있는 요소를 참조 하지 않는 것이 좋습니다. ControlTemplate에 있는 요소를 참조 하 고 ControlTemplate 변경 되는 경우 참조 된 요소가 새 ControlTemplate포함 되어야 하기 때문입니다.It's a good idea to avoid referencing elements that are in the ControlTemplate because when the code references elements that are in the ControlTemplate and the ControlTemplate is changed, the referenced element needs to be included in the new ControlTemplate.

다음 예제에서는 NumericUpDown 컨트롤의 TextBlock을 업데이트 하 고 이름을 할당 하 고 코드에서 이름을 기준으로 텍스트 상자를 참조 합니다.The following example updates the TextBlock of the NumericUpDown control, assigning a name to it and referencing the textbox by name in code.

<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

다음 예제에서는 바인딩을 사용하여 동일한 작업을 수행합니다.The following example uses binding to accomplish the same thing.

<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>

데이터 바인딩에 대한 자세한 내용은 데이터 바인딩 개요를 참조하세요.For more information about data binding, see Data Binding Overview.

디자이너를 위한 디자인Design for Designers

Visual Studio 용 WPF 디자이너에서 사용자 지정 WPF 컨트롤에 대 한 지원을 받으려면 (예: 속성 창을 사용한 속성 편집) 다음 지침을 따르세요.To receive support for custom WPF controls in the WPF Designer for Visual Studio (for example, property editing with the Properties window), follow these guidelines. WPF 디자이너를 위한 개발에 대 한 자세한 내용은 Visual Studio에서 XAML 디자인을 참조 하세요.For more information on developing for the WPF Designer, see Design XAML in Visual Studio.

종속성 속성Dependency Properties

앞의 "종속성 속성 사용"에서 설명한 대로 CLR getset 접근자를 구현 해야 합니다.Be sure to implement CLR get and set accessors as described earlier, in "Use Dependency Properties." 디자이너는 래퍼를 사용하여 종속성 속성의 존재를 감지할 수 있지만 WPFWPF 및 컨트롤의 클라이언트와 마찬가지로 속성을 가져오거나 설정할 때 접근자를 호출할 필요가 없습니다.Designers may use the wrapper to detect the presence of a dependency property, but they, like WPFWPF and clients of the control, are not required to call the accessors when getting or setting the property.

연결된 속성Attached Properties

다음 지침을 사용하여 사용자 지정 컨트롤에서 연결된 속성을 구현해야 합니다.You should implement attached properties on custom controls using the following guidelines:

  • Property 메서드를 사용 하 여 만든 PropertyName RegisterAttached 폼의 public static readonly DependencyProperty 합니다.Have a public static readonly DependencyProperty of the form PropertyNameProperty that was creating using the RegisterAttached method. RegisterAttached 전달 되는 속성 이름은 PropertyName과 일치 해야 합니다.The property name that is passed to RegisterAttached must match PropertyName.

  • SetPropertyNameGetPropertyName이라는 public static CLR 메서드 쌍을 구현합니다.Implement a pair of public static CLR methods named SetPropertyName and GetPropertyName. 두 메서드는 DependencyProperty에서 파생 된 클래스를 첫 번째 인수로 수락 해야 합니다.Both methods should accept a class derived from DependencyProperty as their first argument. SetPropertyName 메서드는 그 형식이 속성의 등록된 데이터 형식과 일치하는 인수도 수락합니다.The SetPropertyName method also accepts an argument whose type matches the registered data type for the property. GetPropertyName 메서드는 동일한 형식의 값을 반환해야 합니다.The GetPropertyName method should return a value of the same type. SetPropertyName 메서드가 누락된 경우 속성이 읽기 전용으로 표시됩니다.If the SetPropertyName method is missing, the property is marked read-only.

  • Set propertynameGetpropertyname 은 각각 대상 종속성 개체의 GetValueSetValue 메서드로 직접 라우팅합니다.Set PropertyName and GetPropertyName must route directly to the GetValue and SetValue methods on the target dependency object, respectively. 디자이너는 메서드 래퍼를 통해 호출하거나 대상 종속성 개체를 직접 호출하여 연결된 속성에 액세스할 수 있습니다.Designers may access the attached property by calling through the method wrapper or making a direct call to the target dependency object.

연결된 속성에 대한 자세한 내용은 연결된 속성 개요를 참조하세요.For more information on attached properties, see Attached Properties Overview.

공유 리소스 정의 및 사용Define and Use Shared Resources

애플리케이션과 동일한 어셈블리에 컨트롤을 포함하거나 여러 애플리케이션에서 사용할 수 있는 별도의 어셈블리에 컨트롤을 패키지화할 수 있습니다.You can include your control in the same assembly as your application, or you can package your control in a separate assembly that can be used in multiple applications. 대부분, 이 항목에서 설명하는 정보는 사용하는 메서드에 관계없이 적용됩니다.For the most part, the information discussed in this topic applies regardless of the method you use. 그러나 주목할 만한 차이점이 하나 있습니다.There is one difference worth noting, however. 애플리케이션과 동일한 어셈블리에 컨트롤을 배치하면 App.xaml 파일에 전역 리소스를 자유롭게 추가할 수 있습니다.When you put a control in the same assembly as an application, you are free to add global resources to the App.xaml file. 그러나 컨트롤만 포함 하는 어셈블리에는 연결 된 Application 개체가 없으므로 app.xaml 파일을 사용할 수 없습니다.But an assembly that contains only controls does not have an Application object associated with it, so an App.xaml file is not available.

애플리케이션이 리소스를 찾을 때 다음 순서로 세 가지 수준을 조사합니다.When an application looks for a resource, it looks at three levels in the following order:

  1. 요소 수준The element level.

    시스템이 리소스를 참조하는 요소로 시작한 다음 루트 요소에 도달할 때까지 논리 부모 등의 리소스를 검색합니다.The system starts with the element that references the resource and then searches resources of the logical parent and so forth until the root element is reached.

  2. 애플리케이션 수준The application level.

    Application 개체가 정의 하는 리소스입니다.Resources defined by the Application object.

  3. 테마 수준The theme level.

    테마 수준 사전은 Themes라는 하위 폴더에 저장됩니다.Theme-level dictionaries are stored in a subfolder named Themes. Themes 폴더의 파일은 테마에 해당합니다.The files in the Themes folder correspond to themes. 예를 들어 Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml 등이 있을 수 있습니다.For example, you might have Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml, and so on. generic.xaml이라는 파일이 있을 수도 있습니다.You can also have a file named generic.xaml. 시스템이 테마 수준에서 리소스를 찾으면 먼저 테마별 파일에서 찾은 다음 generic.xaml에서 찾습니다.When the system looks for a resource at the themes level, it first looks for it in the theme-specific file and then looks for it in generic.xaml.

컨트롤이 애플리케이션과 별도의 어셈블리에 있을 때는 전역 리소스를 요소 수준이나 테마 수준에 배치해야 합니다.When your control is in an assembly that is separate from the application, you must put your global resources at the element level or at the theme level. 두 가지 방법 모두 장점이 있습니다.Both methods have their advantages.

요소 수준에서 리소스 정의Defining Resources at the Element Level

사용자 지정 리소스 사전을 만들어 컨트롤 리소스 사전과 병합하면 요소 수준에서 공유 리소스를 정의할 수 있습니다.You can define shared resources at the element level by creating a custom resource dictionary and merging it with your control’s resource dictionary. 이 메서드를 사용하면 리소스 파일의 이름을 원하는 대로 지정할 수 있으며 컨트롤과 동일한 폴더에 배치할 수 있습니다.When you use this method, you can name your resource file anything you want, and it can be in the same folder as your controls. 요소 수준의 리소스는 간단한 문자열을 키로 사용할 수도 있습니다.Resources at the element level can also use simple strings as keys. 다음 예제에서는 Dictionary1 라는 LinearGradientBrush 리소스 파일을 만듭니다.The following example creates a LinearGradientBrush resource file named Dictionary1.xaml.

<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>

사전을 정의한 후에는 컨트롤의 리소스 사전과 병합해야 합니다.Once you have defined your dictionary, you need to merge it with your control's resource dictionary. XAMLXAML 또는 코드를 사용하여 이 작업을 수행할 수 있습니다.You can do this by using XAMLXAML or code.

다음 예제는 XAMLXAML을 사용하여 리소스 사전을 병합합니다.The following example merges a resource dictionary by using XAMLXAML.

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

이 방법의 단점은 참조할 때마다 ResourceDictionary 개체가 만들어지기 때문입니다.The disadvantage to this approach is that a ResourceDictionary object is created each time you reference it. 예를 들어 라이브러리에 10 개의 사용자 지정 컨트롤이 있고 XAML을 사용 하 여 각 컨트롤에 대 한 공유 리소스 사전을 병합 하는 경우 동일한 ResourceDictionary 개체 10 개를 만듭니다.For example, if you have 10 custom controls in your library and merge the shared resource dictionaries for each control by using XAML, you create 10 identical ResourceDictionary objects. 코드의 리소스를 병합 하 고 결과 ResourceDictionary을 반환 하는 정적 클래스를 만들어이를 방지할 수 있습니다.You can avoid this by creating a static class that merges the resources in code and returns the resulting ResourceDictionary.

다음 예제에서는 공유 ResourceDictionary를 반환 하는 클래스를 만듭니다.The following example creates a class that returns a shared 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를 호출하기 전에 컨트롤의 생성자에 있는 사용자 지정 컨트롤의 리소스와 병합합니다.The following example merges the shared resource with the resources of a custom control in the control's constructor before it calls InitializeComponent. SharedDictionaryManager.SharedDictionary는 정적 속성 이므로 ResourceDictionary 한 번만 만들어집니다.Because the SharedDictionaryManager.SharedDictionary is a static property, the ResourceDictionary is created only once. InitializeComponent가 호출되기 전에 리소스 사전이 병합되었기 때문에 XAMLXAML 파일의 컨트롤에서 리소스를 사용할 수 있습니다.Because the resource dictionary was merged before InitializeComponent was called, the resources are available to the control in its XAMLXAML file.

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

테마 수준에서 리소스 정의Defining Resources at the Theme Level

WPFWPF를 사용하면 다양한 Windows 테마를 위한 리소스를 만들 수 있습니다.enables you to create resources for different Windows themes. 컨트롤 작성자는 특정 테마의 리소스를 정의하여 사용 중인 테마에 따라 컨트롤의 모양을 변경할 수 있습니다.As a control author, you can define a resource for a specific theme to change your control's appearance depending on what theme is in use. 예를 들어 windows 클래식 테마의 Button (Windows 2000에 대 한 기본 테마)의 모양은 각 테마에 대해 다른 ControlTemplate를 사용 Button 하므로 windows Luna 테마 (windows XP의 기본 테마)의 Button와 다릅니다.For example, the appearance of a Button in the Windows Classic theme (the default theme for Windows 2000) differs from a Button in the Windows Luna theme (the default theme for Windows XP) because the Button uses a different ControlTemplate for each theme.

테마와 관련된 리소스는 특정 파일 이름의 리소스 사전에 보관됩니다.Resources that are specific to a theme are kept in a resource dictionary with a specific file name. 이러한 파일은 컨트롤이 포함된 폴더의 하위 폴더인 Themes라는 폴더에 있어야 합니다.These files must be in a folder named Themes that is a subfolder of the folder that contains the control. 다음 표에는 각 파일과 관련된 리소스 사전 파일과 테마가 나와 있습니다.The following table lists the resource dictionary files and the theme that is associated with each file:

리소스 사전 파일 이름Resource dictionary file name Windows 테마Windows theme
Classic.xaml Windows XP의 고전 Windows 9x/2000 모양Classic Windows 9x/2000 look on Windows XP
Luna.NormalColor.xaml Windows XP의 기본 파란색 테마Default blue theme on Windows XP
Luna.Homestead.xaml Windows XP의 올리브색 테마Olive theme on Windows XP
Luna.Metallic.xaml Windows XP의 은색 테마Silver theme on Windows XP
Royale.NormalColor.xaml Windows XP Media Center Edition의 기본 테마Default theme on Windows XP Media Center Edition
Aero.NormalColor.xaml Windows Vista의 기본 테마Default theme on Windows Vista

모든 테마에 대해 리소스를 정의할 필요는 없습니다.You do not need to define a resource for every theme. 특정 테마에 대해 리소스가 정의되지 않은 경우 컨트롤이 리소스에 대해 Classic.xaml을 확인합니다.If a resource is not defined for a specific theme, then the control checks Classic.xaml for the resource. 현재 테마에 해당하는 파일 또는 Classic.xaml에 리소스가 정의되지 않은 경우 컨트롤이 generic.xaml이라는 리소스 사전 파일에 있는 제네릭 리소스를 사용합니다.If the resource is not defined in the file that corresponds to the current theme or in Classic.xaml, the control uses the generic resource, which is in a resource dictionary file named generic.xaml. generic.xaml 파일은 테마별 리소스 사전 파일과 같은 폴더에 있습니다.The generic.xaml file is located in the same folder as the theme-specific resource dictionary files. generic.xaml은 특정 Windows 테마에 해당하지 않지만 여전히 테마 수준의 사전입니다.Although generic.xaml does not correspond to a specific Windows theme, it is still a theme-level dictionary.

테마 C# 및 UI 자동화 지원 샘플을 포함 하는 또는 Visual Basic NumericUpDown 사용자 지정 컨트롤에는 NumericUpDown 컨트롤에 대 한 두 개의 리소스 사전이 포함 되어 있습니다. 하나는 Luna입니다.The C# or Visual Basic NumericUpDown custom control with theme and UI automation support sample contains two resource dictionaries for the NumericUpDown control: one is in generic.xaml, and the other is in Luna.NormalColor.xaml.

테마별 리소스 사전 파일에 ControlTemplate를 배치 하는 경우 컨트롤에 대 한 정적 생성자를 만들고 다음 예제와 같이 DefaultStyleKey에 대 한 OverrideMetadata(Type, PropertyMetadata) 메서드를 호출 해야 합니다.When you put a ControlTemplate in any of the theme-specific resource dictionary files, you must create a static constructor for your control and call the OverrideMetadata(Type, PropertyMetadata) method on the DefaultStyleKey, as shown in the following example.

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

요소 레벨에서 리소스를 정의할 때 문자열을 키로 지정하고 문자열을 통해 리소스에 액세스할 수 있습니다.When you define a resource at the element level, you can assign a string as its key and access the resource via the string. 테마 수준에서 리소스를 정의 하는 경우 ComponentResourceKey를 키로 사용 해야 합니다.When you define a resource at the theme level, you must use a ComponentResourceKey as the key. 다음 예제에서는 generic.xaml에서 리소스를 정의합니다.The following example defines a resource in 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을 키로 지정 하 여 리소스를 참조 합니다.The following example references the resource by specifying the ComponentResourceKey as the key.

<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>
테마 리소스의 위치 지정Specifying the Location of Theme Resources

컨트롤에 대한 리소스를 찾으려면 호스팅 애플리케이션이 어셈블리에 컨트롤 관련 리소스가 있는지 알아야 합니다.To find the resources for a control, the hosting application needs to know that the assembly contains control-specific resources. 컨트롤을 포함 하는 어셈블리에 ThemeInfoAttribute을 추가 하 여이를 수행할 수 있습니다.You can accomplish that by adding the ThemeInfoAttribute to the assembly that contains the control. ThemeInfoAttribute에는 일반 리소스의 위치를 지정 하는 GenericDictionaryLocation 속성과 테마별 리소스의 위치를 지정 하는 ThemeDictionaryLocation 속성이 있습니다.The ThemeInfoAttribute has a GenericDictionaryLocation property that specifies the location of generic resources, and a ThemeDictionaryLocation property that specifies the location of the theme-specific resources.

다음 예제에서는 GenericDictionaryLocationThemeDictionaryLocation 속성을 SourceAssembly로 설정 하 여 제네릭 및 테마별 리소스가 컨트롤과 동일한 어셈블리에 있도록 지정 합니다.The following example sets the GenericDictionaryLocation and ThemeDictionaryLocation properties to SourceAssembly, to specify that the generic and theme-specific resources are in the same assembly as the control.

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

참조See also