목차

리소스

리소스 : 이미지,폰트,오디오,비디오,문자열테이블

이런 리소스를 패키지화(?)해서 쓸 수 있도록 지원해준다.

리소스는 바이너리, 로지컬 두 방식이 있다.

바이너리 리소스

비트맵 같은 일반적인 것부터 컴파일된 xaml도 리소스로 저장 가능.

추가 : 프로젝트에 파일을 추가하고 '속성'에서 적합한 빌드작업(빌드액션)을 선택하면 된다.

바이너리 리소스에 접근

다른 어셈블리에 있는 리소스 접근

어셈블리참조;컴포넌트/리소스명(AssemblyReference;Component/ResourceName)

라는 규칙이 있다.

사이트 원점에서 리소스 접근하기

표시방법

pack://siteOfOrigin:,,,/

코드에서 리소스 접근

Image img = new Image();
img.Source = new BitmapImage(new Uri("pack://application:,,,/logo.jpg"))

지역화

지원 언어(컬쳐)만큼 위성 어셈블리로 분리.

LocBaml 을 이용하면 지역화할 문자열 작업을 간편하게 가능.

로지컬 리소스

프로그래밍 코드에서 만들고 사용할 수 있는 리소스. 동적으로 데이터가 추가/삭제되는 데이터 타입의 리소스를 가리키는 듯.

하드코딩하는 예제

<!-- 귀찮아서 아직 안적음 -->

Window.Resouce 에 브러쉬 리소스를 세팅해 놓고 사용하는 방식

<!-- 귀찮아서 아직 안적음 -->

리소스로 정의한 것

잠시, 리소스로 사용할 수 있는 것들의 부모 클래스

리소스 룩업(찾기)

StaticResource 마크업 확장식은 리소스 딕셔너리 (위에서는 Window.Resource 부분)에서 아이템을 가리키는 키(x:Key 로 설정한)를 파라미터로 받는다.

어느 리소스 딕셔너리에든 있기만 하다면 x:Key로 접근 가능하다. 리소스 딕셔너리느는 상위 부모 엘리먼트 또는 애플리케이션 수준의 것도 있을 수 있다.

검색 단계

  1. 현재의 엘리먼트 리소스 딕셔너리 Resources 컬렉션. (없으면 다음 단계)
  2. 부모의 것에서 찾기, 반복, 루트 엘리먼트까지 검색 (없으면 다음 단계)
  3. Application 의 Resources 에서 검색 (없으면 다음 단계)
  4. 시스템에서 검색 (없으면 다음 단계)
  5. 이래도 없으면 InvalidOperationException 발생

스태틱vs다이나믹 리소스

키워드

스태틱 리소스는 선언된 후에 사용할 수 있다. 다이나믹은 그런 제한은 없다.

<Window 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="SimpleWindow" Background='{DynamicResource backgroundBrush}'>
<!--
  DynamicResource 가 위에서 사용 되어도 에러는 없다.
  아래 리소스 정의 하는 부분에 backgroundBrush 를 추가해서 사용하게끔 한다.
-->
  <Window.Resources>
    <SolidColorBrush x:Key='backgroundBrush'>Yellow</SolidColorBrush>
    <SolidColorBrush x:Key='borderBrush'>Red</SolidColorBrush>
  </Window.Resources>
  ...
</Window>

리소스 분리해서 별개 파일로 나누기

ResourceDictionary 클래스의 MergedDictionaries 프로퍼티를 써서 다른 파일의 리소스를 자신의 컬렉션으로 (리소스로) 합칠 수 있다. 코드보면 이해하기 쉬움.

<Window.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source='file1.xaml'/>
      <ResourceDictionary Source='file2.xaml'/>
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Window.Resources>

개별 파일은 ResourceDictionary를 루트 엘리먼트로 써야 한다. 예, file1.xaml

<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Image x:Key="logo" Source="logo.jpg"/>
</ResourceDictionary>

공유 없는 리소스

'False' 설정이면 StaticResource 로 한 섹션에 여러번 적용해도 다른 개체로 인식되어 에러가 나지 않는다.

<Window.Resources>
  <Image x:Key="logo" x:Shared='False' Source="logo.jpg"/>
</ResourceDictionary>
...
<StaticResource ResourceKey='logo'/>
<StaticResource ResourceKey='logo'/>
<StaticResource ResourceKey='logo'/>
<StaticResource ResourceKey='logo'/>
...

프로그래밍 코드에서 리소스 정의하고 적용하기

정의는 간단

window.Resources.Add("backgroundBrush", new SolidColorBrush("Yellow"));

적용(사용)

Case Static

<Button
    Background='{StaticResource backgroundBrush}'
    BroderBrush='{StaticResource borderBrush}'/>

이걸 코드로 쓰려면,

Button _btn = new Button();
_btn.Background = (Brush)_btn.FindResource("backgroundBrush");
_btn.BorderBrush = (Brush)_btn.FindReosurce("borderBrush");

Case Dynamic

<Button
    Background='{DynamicResource backgroundBrush}'
    BroderBrush='{DynamicResource borderBrush}'/>
Button _btn = new Button();
_btn.SetResourceReference(Button.BackgroundProperty, "backgroundBrush");
_btn.SetResourceReference(Button.BorderBrushProperty, "borderBrush");

데이터 바인딩

Data binding, Template, trigger

System.Windows.Data.Binding 클래스가 핵심. 두개의 프로퍼티를 이용해서 둘 사이에 연결된 채널을 유지한다.

코드로 바인딩 사용

한개의 텍스트 블록과 트리뷰가 있는 화면에서,

<TextBlock x:Name='currentFolder' Text='TextBlock'/>

트리뷰 아이템을 선택할때마다, 텍스트블록에 메시지를 갱신한다면.

private void DummyTreeView_SelectedItemChanged(
    object sender,
    RoutedPropertyChangedEventArgs<object> e)
{
    currentFolder.Text = DummyTreeView.SelectedItem.ToString();
}

바인딩 하기

SetBinding()
BindingOperations.SetBinding( _TARGET_, _TARGET_PROP_, binding );

바인딩 제거

BindingOperations.ClearBinding( _TARGET_, _TARGET_PROP_ );
// 한개 이상
BindingOperations.ClearAllBindings(..)

xaml 바인딩

Binding 선언으로 사용할 수 있는 마크업 확장식이 있다.
타겟 프로퍼티에 바인딩 인스턴스를 추가하고 프로퍼티처러 사용할 수 있다.

<TextBlock x:Name='currentFolder' DockPanel.Dock='Top' 
           Text='{Binding ElementName=treeView, Path=SelectedItem.Header}'
           Background='AliceBlue' FontSize='16'/>
<!-- 
SelectedItem.Header 일지 SelectedItem 일지는 트리뷰 설정 상태에 따라
-->

Path 프로퍼티는 생략 될 수 있다.

<TextBlock x:Name='currentFolder' DockPanel.Dock='Top' 
           Text='{Binding SelectedItem ElementName=treeView}'
           Background='AliceBlue' FontSize='16'/>

데이터 바인딩은 수동으로 하니씩 처리하는 것보다, 소스와 타겟 프로퍼티의 연결을 프로그래밍 코드를 통해 쉽게(?) 표현할 수 있다.

Source 프로퍼티 대신 ElementName 프로퍼티를 사용하는데, ElementName이 더 단순한다.
Source를 쓰려면 대상 객체가 ResourceDictionary 에 리소스로 정의 되어 있어야 한다. (리소스StaticResource 참조)

<TextBlock x:Name='currentFolder' DockPanel.Dock='Top' 
           Text='{Binding Source={StaticResource treeView} Path=SelectedItem}'
           Background='AliceBlue' FontSize='16'/>

RelativeSource 바인딩

Path 프로퍼티 이외에, 자신과 타켓 엘리먼트의 관계를 통해서 바인딩하는 방법이 있다. 이때 쓰는 것이 RelativeSource

일단 넘어가고 다시 정리하자.

단순 프로퍼티와 바인딩

데이터가 표시되는 타겟과 소스 프로퍼티를 동기적으로 유지하려면,

첫번째 방법이 권장되는 방법.

특정 항목(예를 들면 사진을(photo)가리는 photos)의 컬렉션을 가진 컨트롤이 INotifyPropertyChanged 인터페이스를 구현하도록 수정하면 PropertyChanged 이벤트 발생시

같은 동작에 대응해서 처리할 수 있다.

ObservableCollection 클래스

이 코드를 작성할때 필요한 콜렉션 클래스.

// Photo 라는 속성의 아이템 컬렉션의 일반적인 선언
public class Photos : Collection<Photo> { /* ... */ }
 
// INotifyPropertyChagned 와 연동 되는 컬렉션 
public class Photos : ObservableCollection<Photo> { /* ... */ }

주의

객체 전체와 바인딩

객체 전체와 바인딩 하는 것은.. Path를 사용하지 않고 객체를 소스로 사용한다.

<Label x:Name='numItemsLabel' Content='{Binding Source={StaticResource photos}}'/>

주의 : UIElement 전체와 바인딩 할때는

컬렉션에 바인딩

초기바인딩

ListBox 사용하는 경우, ListBox.Items 는 의존 프로퍼티가 아니어서 쓸 수 없다. 대신, ItemsSource 를 사용.
이 프로퍼티는 IEnumerable 타입이라 컬렉션 객체를 소스로 사용할 수 있다.

<ListBox x:Name='pictureBox' 
         ItemsSource='{Binding Source={StaticResource photos}}'/>

소스 컬렉션의 엘리먼트 변화(추가,제거) 때문에 타겟 프로퍼티 (뷰 반영)가 갱신되어야 한다면

표시 방법의 개선

데이터 바인딩 : 재정리

글만 읽고서는 알기 어렵다. 여러 케이스를 테스트 해보면서 코드 작성 방법을 익혀야 할듯.

다른 엘리먼트의 텍스트(프로퍼티)를 읽어 오기

<TextBox x:Name="TargetFolderPath" VerticalContentAlignment="Center" Text="Enter ..."/>
 
<!-- 엘리먼트 TargetFolderPath 의 Text 프로퍼티 값을 그대로 반영한다 -->
<TextBox x:Name="CopyTargetFolderPath" VerticalContentAlignment="Center" 
         Text="{Binding ElementName=TargetFolderPath, Path=Text}"/>

Source를 사용해서 소스와 대상의 바인딩

<TextBlock x:Name='currentFolder' DockPanel.Dock='Top' 
           Text='{Binding Source={StaticResource treeView} Path=SelectedItem}'
           Background='AliceBlue' FontSize='16'/>

DataContext : 데이터를 전달 하는 방법

DataContext

모든 WPF 컨트롤은 FrameWorkElement 로부터 상속된다. 이 클래스에는 DataContext 가 포함되어 있다. DataContext 에 클래스 형식의 데이터를 바인딩 소스로 설정할 수 있다.

간단한 예로,

극단적으로 MainWindow 를 통째로 바인딩 소스로 넘겼다. 윈도우에 대한 프로퍼티를 사용할 수 있게 된다.

public partial class MainWindow : Window
{
    // 극단적으로 MainWindow 를 통째로 바인딩 소스로 넘겼다. 윈도우에 대한 프로퍼티를 사용할 수 있게 된다.
    public MainWindow() {
        InitializeComponent();
        this.DataContext = this;
    }
    // ...
}

ValueConverts : 데이터를 전달 하는 방법

타입이 다른 두개의 프로퍼티를 바인딩 하는 경우, 값의 컨버팅 할 필요가 있다. ( 에러가 나거나 원하는 값이 안나오거나 )

이럴 때 ValueConverter 사용. xaml 키워드 'Converter'

코드로 작성 해야 하며, IValueConverter 인터페이스를 상속 받는 클래스를 만들어야 한다.

예제로, 불 값을 Visibility 프로퍼티로 컨버팅 하는 경우를 본다. bool 값을 Visible , Collapsed , Hidden 으로 변경한다.

<StackPanel>
  <StackPanel.Resources>
    <!-- 클래스 이름이 xaml 키로 사용 되었다. -->
    <!-- x:Name이 아닌 것에 유의(확인 필요!) -->
    <BooleanToVisibilityConverter x:Key="boolToVis" />
  </StackPanel.Resources>
 
  <CheckBox x:Name="chkShowDetails" Content="Show Details" />
  <StackPanel x:Name="detailsPanel" 
              Visibility="{Binding IsChecked, ElementName=chkShowDetails, 
                          Converter={StaticResource boolToVis}}">
  </StackPanel>
</StackPanel>
public class BooleanToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        if (value is Boolean) {
            return ((bool)value) ? Visibility.Visible : Visibility.Collapsed;
        } 
        return value;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        throw new NotImplementedException();
    }
} 

Binding 다음에 나올 수 있는 키워드

{Binding

컨트롤을 리소스로 정의한 다음 사용하는 예제

<Window.Resources>
   <ControlTemplate x:Key="Ampel" TargetType="ContentControl">
      <Canvas>
         <Rectangle Fill="Black" HorizontalAlignment="Left" Height="52" Stroke="Black" VerticalAlignment="Top" Width="50"/>
         <Ellipse x:Name="RedGreen" 
                  Fill="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}" 
                  HorizontalAlignment="Left" Height="27" Margin="11,12,0,0" Stroke="Black" 
                  VerticalAlignment="Top" Width="28" RenderTransformOrigin="0.214,0.256"/>
      </Canvas>
   </ControlTemplate>
</Window.Resources >
<ContentControl Template="{StaticResource Ampel}" Tag="Red"></ContentControl>
<ContentControl Template="{StaticResource Ampel}" Tag="Green"></ContentControl>
<ContentControl Template="{StaticResource Ampel}" Tag="Blue"></ContentControl>

DataBinding 디버깅

문법 검사로 발견하지 못한 에러의 경우

TRACE 메시지 출력

<Window x:Class="DebugDataBinding.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <StackPanel x:Name="stack">
    <TextBlock Text="{Binding ElementName=stack, Path=InvalidPath}" />
  </StackPanel>
</Window>

출력창에 잘못된 프로퍼티라는 에러 메시지가 뜬다. (뜨나? 다른 예제로 해보니 뜬다.)

System.Windows.Data Error: 39 : BindingExpression path error: 'InvalidPath' property not found on 'object' ''StackPanel' (Name='stack')'. BindingExpression:Path=InvalidPath; DataItem='StackPanel' (Name='stack'); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

trace level 조정

xmal에서 하는 것은 잘 안됐고. 코드로 조정하는게 확실하게 되었다.
<Window x:Class="DebugDataBinding.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase">
 
  <StackPanel x:Name="stack">
    <TextBlock Text="{Binding ElementName=stack, Path=InvalidPath, 
                     diag:PresentationTraceSources.TraceLevel=High}" />
  </StackPanel>
</Window>

코드로 조정하는 경우

PresentationTraceSources.DataBindingSource.Listeners.Add(new ConsoleTraceListener()); 
PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.All;

ValueConverter 를 사용한 브레이크 포인트 설정

그닥 쓸모가..

IValueConverter 에서 상속되는 디버그용 클래스를 작성 아래 예시처럼

/// <summary>
/// This converter does nothing except breaking the
/// debugger into the convert method
/// </summary>
public class DatabindingDebugConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        Debugger.Break();
        return value;
    }
 
    public object ConvertBack(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        Debugger.Break();
        return value;
    }
} 

xaml 코드에서 브레이크 포인트를 설정할 곳에 추가한다.

DatabindingDebugConverter 클래스가 TestAPP 라는 네임스페이스에 포함되어 있는 것으로 가정.

<Window x:Class="TestAPP.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestAPP"
        Title="Window1" Height="300" Width="300">
 
    <Window.Resources>
        <local:DatabindingDebugConverter x:Key="debugConverter" />
    </Window.Resources>
 
    <StackPanel x:Name="stack">
        <TextBlock Text="{Binding ElementName=stack, Path=ActualWidth, 
                          Converter={StaticResource debugConverter}}" />
    </StackPanel>
</Window>