WPF 리소스 시스템 완벽 가이드

1. 리소스의 개념과 구조

WPF(Windows Presentation Foundation)에서 리소스는 재사용 가능한 객체를 저장하는 메커니즘입니다. 모든 프레임워크 기반 요소는 Resources 속성을 가지고 있으며, 이 속성은 ResourceDictionary 타입입니다. 리소스를 조회할 때는 현재 컨트롤에서 시작하여 부모 요소를 거치ながら Application 수준까지 순차적으로 검색됩니다.

1.1 윈도우와 컨트롤 리소스 정의

리소스는 люб élément에서 정의할 수 있지만, 通常은 루트 요소인 Window나 Page에서 정의합니다.

<Window.Resources>
    <SolidColorBrush x:Key="PrimaryBrush" Color="Navy"/>
</Window.Resources>
<DockPanel>
    <!-- 정적 리소스: 로드 시 한 번만 참조 -->
    <Ellipse Fill="{StaticResource PrimaryBrush}" Width="120" Height="120"/>
    <!-- 동적 리소스: 런타임에 다시 조회 -->
    <Ellipse Fill="{DynamicResource PrimaryBrush}" Width="120" Height="120"/>
    <!-- 로컬 리소스가 있는 컨트롤 -->
    <Ellipse x:Name="Shape1" Width="120" Height="120" Fill="{DynamicResource PrimaryBrush}">
        <Ellipse.Resources>
            <SolidColorBrush x:Key="PrimaryBrush" Color="Crimson"/>
        </Ellipse.Resources>
    </Ellipse>
</DockPanel>

1.2 애플리케이션 전역 리소스

특정 윈도우나 페이지에서 리소스를 찾지 못하면, WPF는 애플리케이션 수준에서 리소스를 검색합니다. Visual Studio에서는 App.xaml 파일의 Application.Resources 섹션에 정의합니다.

<Application x:Class="MyApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MyApp"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <SolidColorBrush x:Key="PrimaryBrush" Color="Navy"/>
    </Application.Resources>
</Application>

1.3 시스템 리소스 참조

애플리케이션 리소스에서도 원하는 리소스를 찾지 못하면, WPF는 시스템 리소스를 추가로 검색합니다. 이러한 시스템 리소스는 다음 네임스페이스에서 제공됩니다:

  • SystemColors: 운영체제의 색상 설정에 접근
  • SystemFonts: 시스템 폰트 구성 정보 제공
  • SystemParameters: 화면 해상도, 마우스/키보드 설정, 그래픽 효과 등 다양한 시스템 매개변수 포함

2. 정적 리소스와 동적 리소스의 차이점

정적 리소스(StaticResource)는 애플리케이션이 메모리에 로드될 때 한 번만 리소스를 확인하고 그 이후에는 다시 참조하지 않습니다. 반면에 동적 리소스(DynamicResource)는 애플리케이션 실행 중에도 리소스 조회르 계속 수행합니다.

런타임 중 리소스 변경이 필요한 경우를 살펴보겠습니다:

public MainWindow()
{
    InitializeComponent();
    this.Resources["PrimaryBrush"] = new SolidColorBrush(Colors.Teal);           
}

위의 코드를 실행하면 정적 리소스로 참조한 요소는 변경 사항이 반영되지 않고, 동적 리소스로 참조한 요소만 새로운 색상으로 업데이트됩니다.

리소스 조회 메서드:

FindResource() 메서드 대신 TryFindResource()를 사용하면 리소스를 찾지 못할 때 예외 대신 null을 반환하여 더 안전합니다.

var brush = (SolidColorBrush)Shape1.TryFindResource("PrimaryBrush");

3. 리소스 딕셔너리 활용

리소스 딕셔너리는 순수 XAML 문서로, 재사용 가능한 리소스를 저장하는 용도로만 사용됩니다. ResourceDictionary의 Source 속성에 파일 경로를 지정하여 외부 리소스 딕셔너리를 로드할 수 있습니다.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush x:Key="PrimaryBrush" Color="Navy"/>
</ResourceDictionary>

애플리케이션 전역에서 여러 리소스 딕셔너리를 공유하려면 App.xaml의 병합된 딕셔너리에 포함시킵니다:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="ThemeDictionary.xaml"/>
            <ResourceDictionary Source="ColorPalette.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

개별 윈도우에서만 사용하려면 Window.Resources에 직접 지정합니다:

<Window.Resources>
    <ResourceDictionary Source="LocalResources.xaml"/>
</Window.Resources>

4. 리소스 딕셔너리 병합과 컴포넌트 공유

4.1 리소스 딕셔너리 생성

여러 프로젝트에서 공통으로 사용할 리소스를 정의하려면 별도의 WPF 라이브러리 프로젝트를 생성합니다. 리소스 딕셔너리 파일(SharedResources.xaml)을 만들고 ComponentResourceKey를 사용하여 리소스를 정의합니다:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:CustomControls">
    <SolidColorBrush x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:BaseControl},ResourceId=ThemeBrush}"  
        Color="Navy"/>
</ResourceDictionary>

이미지나 바이너리 리소스를 포함할 때는 절대 Pack URI 경로를 사용하고, 빌드 작업을 "Resource"로 설정해야 합니다.

Pack URI 구문:

pack://application,,,[/어셈블리명;][버전정보;][폴더명/]파일명

Pack URI 사용 시 주의사항:

  • 경로는 우에서 좌로 해석되는 슬래시(/) 사용
  • 축약형은 상대 경로로, UriKind는 Relative
  • 전체 형식은 절대 경로로, UriKind는 Absolute
  • 상대 경로에서 ./는 현재 디렉토리, ../는 상위 디렉토리 의미

4.2 리소스 구성

여러 리소스 딕셔너리를 통합하려면 Generic.xaml 파일에 병합 구성을 추가합니다:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SharedLibrary">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="pack://application:,,,/SharedLibrary;component/SharedResources.xaml"/>
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

동일한 결과를 주는 C# 코드:

Uri relativeUri = new Uri(@"./SharedResources.xaml", UriKind.Relative);
Uri absoluteUri = new Uri(@"pack://application:,,,/SharedLibrary;component/SharedResources.xaml", UriKind.Absolute);

커스텀 컨트롤 클래스 정의:

public class BaseControl : Control
{
    public static ComponentResourceKey ThemeBrush
    {
        get
        {
            return new ComponentResourceKey(typeof(BaseControl), "ThemeBrush");
        }
    }
}

4.3 컴포넌트 빌드 및 사용

SharedLibrary.dll을 빌드한 후 새 프로젝트에서 참조합니다. XAML 네임스페이스를 선언하고 리소스를 사용합니다:

<Window x:Class="AppUI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AppUI"
        xmlns:controls="clr-namespace:SharedLibrary;assembly=SharedLibrary"
        mc:Ignorable="d"
        Title="MainWindow" Height="500" Width="900">
    <DockPanel>
        <!-- ComponentResourceKey는 반드시 DynamicResource로 참조 -->
        <Ellipse Fill="{DynamicResource {x:Static controls:BaseControl.ThemeBrush}}" Width="150" Height="150"/>
    </DockPanel>
</Window>

태그: WPF XAML resource StaticResource DynamicResource

6월 17일 00:24에 게시됨