MainWindow에는 메뉴가 있고, 메뉴를 선택하면 컨텐츠 영역에 메뉴에 해당하는 페이지가 표시된다. 현재 프로젝트는 3개의 페이지를 샘플로 제공하고 있다.
INavigationWindow
를 구현하고, Page는 INavigableView
를 구현한다. 이 차이로 HostService가 MainWindow와 Page를 구분할 수 있는 것으로 보인다.이 페이지는 ViewModel의 값을 View에 어떻게 표시하는지, View의 버튼을 눌렀을 때 ViewModel에서 버튼 클릭 이벤트를 어떻게 처리하는지 예시를 보여준다.
MainWindow의 Title 영역과 NavigationView 영역은 앞서 MainWindow에서 살펴보았다.
DashboardPage는 하나의 버튼과 TextBlock을 가진다.
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:Button
Grid.Column="0"
Command="{Binding ViewModel.CounterIncrementCommand, Mode=OneWay}"
Content="Click me!"/>
<TextBlock
Grid.Column="1"
Text="{Binding ViewModel.Counter, Mode=OneWay}" />
</Grid>
Command
속성에 ViewModel.CounterIncrementCommand
를 바인딩한다.몇가지 궁금한 점이 생긴다.
View에서 참조하는 ViewModel은 어디서 왔는가?
public partial class DashboardPage : INavigableView<DashboardViewModel>
{
public DashboardViewModel ViewModel { get; }
public DashboardPage(DashboardViewModel viewModel)
{
ViewModel = viewModel;
...
}
}
class DashboardPage : INavigableView<DashboardViewModel>
이 코드를 보면 클래스를 정의할 때 제너릭 인터페이스인 INavigableView<T>
를 사용하고, 이 때 ViewModel을 Type으로 사용한다. 이 부분은 나중에 더 살펴봐야할 것 같다.DashboardViewModel
을 살펴보자.
public partial class DashboardViewModel : ObservableObject
{
[ObservableProperty]
private int _counter = 0;
[RelayCommand]
private void OnCounterIncrement()
{
Counter++;
}
}
ObservableObject
의 동작을 알아야 한다. 자세한 내용은 나중에 알아보기로 ObservableObject는 값 변경을 Notify 할 수 있다는 정도만 기억하고 넘어가자. [xxx]
형태의 Annotation 같은 것이 보인다. _counter
앞에 [ObservableProperty]
는 Counter라는 Property를 만들고, 이 Property가 변경되면 ObservableObject의 SetProperty()를 자동으로 호출한다는 의미이다.OnCounterIncrement()
앞에 [RelayCommand]
는 RelayCommand 객체가 생성되고 이 함수가 RelayCommand의 핸들러로 사용된다는 것을 의미한다.다시 View로 돌아가 보자.
WPF-UI
내부에서 이루어지는 것은 짐작해 볼 수 있다.CounterIncrementCommand
는 [RelayCommand]
Annotation에 의해 생성되는 RelayCommand 객체이다.OneWay
로 바인딩 된다.이 페이지는 ViewModel의 컬렉션(배열, 리스트) 데이터를 View에 어떻게 표시하는지 예시를 보여준다.
<Grid>
<ui:VirtualizingItemsControl
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
ItemsSource="{Binding ViewModel.Colors, Mode=OneWay}"
VirtualizingPanel.CacheLengthUnit="Item">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type models:DataColor}">
<ui:Button
...
Background="{Binding Color, Mode=OneWay}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ui:VirtualizingItemsControl>
</Grid>
Colors
컬렉션을 바인딩한다.DataViewModel(ViewModel)은 DataPage(View)에 제공할 데이터(컬렉션)을 가진다.
public partial class DataViewModel : ObservableObject, INavigationAware
{
...
[ObservableProperty]
private IEnumerable<DataColor> _colors;
...
}
Colors
라는 속성이름으로 접근 가능하다.[ObservableProperty]
에 의해 ViewModel에 Colors
속성이 자동으로 정의된다.DataColor
(Model)이고 다음과 같이 정의된다.public struct DataColor
{
public Brush Color { get; set; }
}
ItemsControl에 ItemTemplate이 정의되고 각 항목에 Color(Model) 값을 지정하기 위해서 DataTemplate을 정의한다.
xmlns:models="clr-namespace:UiDesktopApp1.Models"
...
<DataTemplate DataType="{x:Type models:DataColor}">
<ui:Button
...
Background="{Binding Color, Mode=OneWay}"
/>
</DataTemplate>
models:DataColor
ViewModel에서 넘겨 받은 데이터의 Type이 Models.DataColor 클래스임을 알려준다.Button
을 사용한다. (여러개의 컨트롤을 매핑해도 된다)BackgroundColor
를 데이터(Type=Model.DataColor)의 Color 속성에 바인딩한다.이 페이지는 컨트롤의 속성 Type과 ViewModel에서 제공하는 데이터의 Type이 불일치하는 경우 Converter를 통해 변환해서 Binding하는 모습을 보여주고, Command를 ViewModel에 보낼 때 Parameter를 지정하는 예시도 확인할 수 있다.
화면에는 테마(Light/Dark)를 선택할 수 있는 Radio 버튼이 있다. 이 버튼을 선택하면 App.xaml에서 DynamicResouce로 참조하고 있는 WPF-UI
Theme를 즉시 변경한다.
<StackPanel>
...
<RadioButton
Command="{Binding ViewModel.ChangeThemeCommand, Mode=OneWay}"
CommandParameter="theme_light"
Content="Light"
GroupName="themeSelect"
IsChecked="{Binding ViewModel.CurrentTheme, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Light, Mode=OneWay}" />
<RadioButton
Command="{Binding ViewModel.ChangeThemeCommand, Mode=OneWay}"
CommandParameter="theme_dark"
.../>
...
</StackPanel>
ViewModel.ChangeThemeCommand
에, IsChecked는 ViewModel.CurrentTheme
에 바인딩된다.[ObservableProperty]
로 AppVersion, CurrentTheme 속성을 제공한다.
[RelayCommand]
로 ChangeThemeCommand 커맨드를 제공한다.
이상으로 WPF-UI
의 Application Wizard로 생성한 MVVM 예제소스를 살펴 보았다. 앞에서 자세히 설명하지 못한 몇가지 개념과 관련 클래스를 설명하는 것으로 튜토리얼을 마무리하겠다.