UWP(Universal Windows Platform) 소개 — 빠른 시작

1. Xaml

1.1. XAML 소개

  • XAML 개요
  • 주로 시각적 UI 요소 생성에 사용됨.
  • Xaml 기본 문법은 XML 기반.
  • 네임스페이스 별칭 선언:
xmlns:controls="using:Common.Controls"
  • 네임스페이스에 선언된 클래스 사용:
<controls:MyControl />
  • 재사용 가능한 리소스(Resource),

    x:Key

<Style x:Key="TextBlock_Style" />
  • 컨트롤 요소 이름,

x:Name

Xaml:

<MyControl x:Name="myControl" />

C#:

private MyControl myControl;
  • 지역화

    x:Uid

<TextBlock x:Uid="sampleText" />

1.2. 가장 기본적인 컨트롤(Control) – TextBlock, Button

<Page x:Class="MyPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <TextBlock Text="UWP 소개" />
        <Button Content="UWP 소개" Click="Button_Click" />
    </StackPanel>
</Page>

2. MVVM

MVVM 상세 설명

View <=> ViewModel <=> Model

  • View는 가능한 UI 표시 내용만 포함, View는 대부분 Xaml 언어로 작성;
  • ViewModel은 비즈니스 처리 로직을 포함하지 않고 Model의 함수를 호출하여 작업 수행;
  • Model은 가능한 모든 비즈니스 데이터와 로직을 포함하며 View 및 ViewModel에 의존하지 않도록 함;

3. ViewModel과 Model의 상호작용

3.1. ViewModel이 Model 데이터 조작

ViewModel:

public class ViewModel
{
    private Model model;
    private ChangeA()
    {
        this.model.A = "A";
    }
}

Model:

public class Model
{
    public string A { get; set; }
    public string B { get; set; }
}

3.2. Model이 ViewModel에 알림 – event

ViewModel:

public class ViewModel
{
private Model model;
    private ChangeA()
    {
        r.BEventArgs += this.Handler;
    }
    private void Handler(object sender, EventArgs e)
    {
        AnyActions();
    }
}

Model:

public delegate void BChangedHandler(object sender, EventArgs e);

public class Model
{
    public string A { get; set; }
    private string _B;
    public string B
    {
        get { return this._B; }
        set
        {
            this._B = value;
            if (BEventArgs != null)
            {
                BEventArgs(this, new EventArgs());
            }
        }
    }
    public event BChangedHandler BEventArgs;
}

4. View와 ViewModel의 상호작용

4.1. 데이터 바인딩

4.1.1. ViewModel에 바인딩

View:

<TextBlock Text={Binding SampleText} />

ViewModel:

public string SampleText { get; set; }

4.1.2. 다른 Control에 바인딩

View:

<TextBlock x:Name="TextBlock1" Text="SampleText" />
<Button Content="{Binding ElementName=TextBlock1, Path= Text}" />

4.1.3. DataContext 지정

public ViewModelClass ViewModel { get; set; }

...
SpecifiedControl.DataContext = ViewModel;
...

5. (View와 ViewModel) 알림 구현 – INotifyPropertyChanged

SampleText가 변경될 때, 이 property에 바인딩된 DependencyProperty에 알림

public class MyControlViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void Notify(string propName)
        {
            if (this.PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }

        private string _sampleText;
        public string SampleText
        {
            get
            {
                return _sampleText;
            }
            set
            {
                _sampleText = value;
                Notify(nameof(SampleText));
            }
        }
    }

또는 ViewModelBase 상속

public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void Notify(string propName)
        {
            if (this.PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
            }
        }
    }

6. (View와 ViewModel) ListView를 알림 가능한 소스에 바인딩 – ObservableCollection

ListView

ListView에서 알림 가능한 ItemSource에 바인딩 구현

View:

<ListView ItemsSource="{Binding Items}">

ViewModel:

public ObservableCollection<Recording> Items { get; set; }
  • ObservableCollection은 Item 추가/제거, 전체 목록 새로고침 시에만 알림 발생;
  • item Recording의 내용 변경 시 UI에 알리려면 Recording이 INotifyPropertyChanged를 구현해야 함.

7. (View) ListView의 Item 템플릿(DataTemplate | UserControl)

7.1. DataTemplate

View:

<ListView ItemsSource="{Binding Items}">
    <ListView.ItemTemplate>
        <DataTemplate DataType="local:Recording">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding A}" />
                <TextBlock Text="{Binding B}" />
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

ViewModel:

public ObservableCollection<RecordingViewModel> Items { get; set; }

public class RecordingViewModel : INotifyPropertyChanged
{
    ...
    INotifyPropertyChanged 구현
    ...

    private Recording _recording;

    public string A
    {
        get
        {
            return this._recording.A;
        }
        set
        {
            this._recording.A = value;
            Notify(nameof(A));
        }
    }

    public string B { get; set; } = this._recording.B;

    public RecordingViewModel (Recording recording)
    {
        this._recording = recording;
    }
}

Model:

public class Recording
{
    public string A { get; set; }
    public string B { get; set; }
    public string C { get; set; }
    ... ...
}

비교:

ViewModel/Model 분리

7.2. UserControl

7.2.1. DependencyProperty

  • Dependency Properties 개요
  • DependencyProperty만 다른 property에 바인딩 가능, 바인딩만 View에 알림 구현 가능
  • 우선순위 가짐

View:

<control:MyControl Text="앱이 검색 중입니다" IsSearching="{Binding ViewModel.IsSearching}" />

ViewModel:

public class MyControl
{
...
public static readonly DependencyProperty IsSearchingProperty =
    DependencyProperty.Register
    (
        "IsSearching", typeof(Boolean),
        typeof(MyControl), null
    );

public bool IsSearching
{
    get { return (bool)GetValue(IsSearchingProperty); }
    set { SetValue(IsSearchingProperty, value); }
}
...
}

8. (ViewModel과 Model) 데이터 변환 – IValueConverter

IValueConverter를 사용하여 ViewModel과 Model 간 데이터 변환 가능.

public class ShowAllButtonVisibilityConverter:IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is IList)
            {
                int count = (value as IList).Count;
                if (count > 3)
                {
                    return Windows.UI.Xaml.Visibility.Visible;
                }
            }
            return Windows.UI.Xaml.Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            if (value is IList)
            {
                int count = (value as IList).Count;
                if (count > 3)
                {
                    return Windows.UI.Xaml.Visibility.Visible;
                }
            }
            return Windows.UI.Xaml.Visibility.Collapsed;
        }

9. (View) Control 스타일 수정

9.1. Control 내에서 스타일 커스터마이징

View:

<TextBlock Foreground="Red" Text="샘플 텍스트" />

9.2. 통합 스타일 사용 – ResourceDictionary

View:

<Window.Resources>
    <ResourceDictionary>
        <Style TargetType="TextBlock" x:Key="ImportantText">
            <Setter Property="Foreground" Value="Red" />
        </Style>
    </ResourceDictionary>
</Window.Resources>
...
<TextBlock Text="샘플 텍스트" Style={StaticResource ImportantText} />

9.3. ThemeResource 사용

View:

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.ThemeDictionaries>
            <ResourceDictionary x:Key="Light">
                <Style TargetType="TextBlock" x:Key="ImportantText">
                    <Setter Property="Foreground" Value="Red" />
                </Style>
            </ResourceDictionary>

            <ResourceDictionary x:Key="Dark">
                <Style TargetType="TextBlock" x:Key="ImportantText">
                    <Setter Property="Foreground" Value="Yellow" />
                </Style>
            </ResourceDictionary>

            <ResourceDictionary x:Key="HighContrast">
                <Style TargetType="TextBlock" x:Key="ImportantText">
                    <Setter Property="Foreground" Value="Black" />
                </Style>
            </ResourceDictionary>
        </ResourceDictionary.ThemeDictionaries>
    </ResourceDictionary>
</Window.Resources>
...
<TextBlock Text="샘플 텍스트" Style={ThemeResource ImportantText} />

10. Panel 사용

10.1. Grid

특징:

  • 기본 Height/Width는 부모 요소와 동일;

권장:

  • Grid 내에서 자식 요소의 레이아웃 크기 정의;
  • 비율로 표시;

10.2. StackPanel

특징:

  • 부모 요소 경계를 초과할 수 있음;
  • Height 또는 Width가 Panel 내 요소에 따라 변경;

권장:

  • Padding과 비율 유연하게 사용;
  • 부모 요소에서 StackPanel의 Width 또는 Height에 값을 지정하여 Control이 경계를 벗어나지 않도록 방지;

11. (View) 적응형 UI (Adaptive UI)

  • VisualStateManager.VisualStateGroup 사용
<VisualStateGroup>
    <VisualState x:Name="WideLayout">
        <VisualState.StateTriggers>
            <AdaptiveTrigger x:Name="WideLayoutTrigger" MinWindowWidth="1280" />
        </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="SystemUpdateSideGrid.Width" Value="800" />
            <Setter Target="SystemUpdateSideGrid.Grid.Row" Value="0" />
        </VisualState.Setters>
    </VisualState>

    <VisualState x:Name="MidLayout">
        <VisualState.StateTriggers>
            <AdaptiveTrigger x:Name="MidLayoutTrigger" MinWindowWidth="700" />
        </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="SystemUpdateSideGrid.Width" Value="400" />
            <Setter Target="SystemUpdateSideGrid.Grid.Row" Value="1" />
        </VisualState.Setters>
    </VisualState>
    ...
</VisualStateGroup>

12. 레이아웃 원칙

  • 요소 크기를 명시적으로 설정하지 않음;
  • 화면 좌표로 요소 위치 지정하지 않음;
  • 컨테이너 내 자식 요소는 사용 가능한 공간 공유;
  • 중첩 가능한 레이아웃 컨테이너;

13. 지역화(Localization)

  • x:Uid 사용, 요소의 고유 식별자
    <TextBlock x:Uid="S_TextBlock1" />
  • 리소스 파일 사용
    <data name="S_TextBlock1.Text" xml:space="preserve">
        <value>샘플 텍스트</value>
    </data>

14. 명명 규칙

대문자 카멜 케이스 big camel-case: firstName

소문자 카멜 케이스 little camel-case: FirstName

  • Class: 대문자 카멜 케이스
  • Property: 대문자 카멜 케이스
  • Field: 소문자 카멜 케이스 + “_” 접두사
  • Xaml의 Control: 소문자 카멜 케이스

15. 주의사항

  • ViewModel의 Property 이름 변경 시 주의, Xaml의 Binding 이름은 리팩토링을 따라가지 않음;