티스토리 뷰

728x90

■ 목차

1. CommandSample 들어가기

2. 프로그램 동작 및 구조

3. 서브 뷰 내포시키기

4. 뷰와 아발로니아 요소들

5. ReactiveUI와 CommunityToolkit.Mvvm

 

1. CommandSample 들어가기

크로스플랫폼 닷넷 UI 프레임워크인 아바로니아 맛보기를 넘어서서 본격적인 활용에 들어가기 위한 학습 방법으로 필자는 예제 프로그램 리뷰를 하나씩 수행하기로 했다. 기술 자료가 넉넉하고, 비주얼스튜디오의 WPF 디자이너와 같은 도구가 있다면 넘어갈 수도 있는 문제겠지만 지금까지 접하지 않았던 새로운 도구와 친숙해지는 방법은 역시 예제를 통하는 것이 아닌가 싶다. 공식 예제 코드는 깃허브 https://github.com/AvaloniaUI/Avalonia.Samples 에서 받을 수 있다. 커뮤니티에서 발굴해 놓은 예제와 참조 프로젝트도 있는데 깃허브 https://github.com/AvaloniaCommunity/awesome-avalonia 에서 확인할 수 있다.

 

 

CommandSample은 MVVM(Models, Views, View Models) 패턴으로 개발하는 앱에서 버튼 클릭을 통한 명령 수행이 필요한 경우 참조할만한 예제이다. MVVM 패턴을 적용하기 위하여 Reactive UI 프레임워크와 함께 마이크로소프트의 CommunityToolkit.Mvvm을 사용하는 뷰모델을 제시하고 있는 것도 주목할만하다.

 

2. 프로그램 동작 및 구조

 

프로그램의 동작은 단순하다. 상단의 탭으로 서로 다른 뷰 모델의 동작을 선택한다. 탭 제목처럼 Reactive UI와 CommunityToolkit.Mvvm 기반의 뷰모델을 선택할 수 있지만 각각의 뷰모델과 연동하는 뷰의 형태는 동일하다. 버튼으로 인공지능 컴퓨터 HAL 9000에 명령을 내리면 해당 명령에 응답하는 모양새로 동작한다. 내부적으로는 버튼 단위의 명령이 내려지면 명령에 해당하는 메시지를 하단에 출력하는 방식이다.

 

프로젝트는 MVVM 패턴에서 별도의 모델 없이 뷰모델과 뷰로만 구성된 단순한 형태이다. MainWindow 뷰에서 다른 뷰인 ReactiveUiCommandsSampleView, CommunityToolkitCommandsSampleView를 내포시키는 방법을 적용했다.

 

3. 서브 뷰 내포시키기

<TabControl>
  <TabItem Header="ReactiveUI">
    <views:ReactiveUiCommandsSampleView DataContext="{Binding ReactiveUiCommandsViewModel}" />
  </TabItem>
  
  <TabItem Header="CommunityToolkit-MVVM">
    <views:CommunityToolkitCommandsSampleView DataContext="{Binding CommunityToolkitCommandsViewModel}" />
  </TabItem>
</TabControl>

MainWindow 뷰를 살펴보면 TabItem 컨트롤 내부에 "views" 태그를 통해서 서브 뷰를 내포하고 있음을 확인할 수 있다. 뷰 이름을 지정하면서 DataContext에 뷰모델을 지정했다. 메인뷰는 "Window" 컨트롤이지만 서브 뷰는 "UserControl"로 정의했다. 관련 컨트롤의 세부 내용은 아래와 같다.

 

4. 뷰와 아발로니아 요소들

<Grid RowDefinitions="Auto, Auto, *" ColumnDefinitions="Auto, *">
<TextBlock Grid.Column="0" Grid.Row="0" Text="Command:" 
	FontWeight="Bold" VerticalAlignment="Center" Margin="5" />
<StackPanel Grid.Column="1" Grid.Row="0" Spacing="5" Margin="5" >
  <Button Command="{Binding OpenThePodBayDoorsDirectCommand}" 
	  Content="Open the pod bay doors, HAL." />
  <StackPanel Orientation="Horizontal" Spacing="5">  
	<TextBox Text="{Binding RobotName}" Watermark="Robot Name" />
	<Button Command="{Binding OpenThePodBayDoorsFellowRobotCommand}"
		Content="{Binding RobotName, StringFormat='Open the Pod Bay for {0}'}"
		CommandParameter="{Binding RobotName}" />
  </StackPanel>
  <Button Command="{Binding OpenThePodBayDoorsAsyncCommand}"
	  Content="Start Pod Bay Opening Sequence" />
</StackPanel>

<Separator Grid.Row="1" Grid.ColumnSpan="2" 
	   HorizontalAlignment="Stretch" Margin="5" Height="2"
	   Background="LightGray"/>

<TextBlock Grid.Column="0" Grid.Row="2"
	   Text="HAL 9000:" FontWeight="Bold" Margin="5"
	   VerticalAlignment="Center"/>
	   
<Border Grid.Column="1" Grid.Row="2"
	CornerRadius="10" Margin="5"
	BorderThickness="1" BorderBrush="{DynamicResource SystemAccentColor}">
  <Grid ColumnDefinitions="*,Auto">
	<ItemsControl Margin="5" ItemsSource="{Binding ConversationLog}" />
	<Button Grid.Column="1"
		VerticalAlignment="Stretch" VerticalContentAlignment="Center"
		Background="Transparent"
		Command="{Binding ConversationLog.Clear}" Content="❌"/>
  </Grid>
</Border>
</Grid>

 

5. ReactiveUI와 CommunityToolkit.Mvvm

using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace CommandSample.ViewModels;
public partial class CommunityToolkitCommandsViewModel : ObservableObject
{
  public CommunityToolkitCommandsViewModel()
  {
    OpenThePodBayDoorsDirectCommand = new RelayCommand(OpenThePodBayDoors);
  }

  public ICommand OpenThePodBayDoorsDirectCommand { get; }

  private void OpenThePodBayDoors()
  {
    ConversationLog.Clear();
    AddToConvo("I'm sorry, Dave, I'm afraid I can't do that.");
  }

  [RelayCommand(CanExecute = nameof(CanRobotOpenTheDoor))]
  private void OpenThePodBayDoorsFellowRobot(string? robotName)
  {
    ConversationLog.Clear();
    AddToConvo($"Hello {robotName}, the Pod Bay is open :-)");
  }

  private bool CanRobotOpenTheDoor() => !string.IsNullOrWhiteSpace(RobotName);

  [ObservableProperty] 
  [NotifyCanExecuteChangedFor(nameof(OpenThePodBayDoorsFellowRobotCommand))]
  private string? _robotName;

  [RelayCommand]
  private async Task OpenThePodBayDoorsAsync()
  {
    ConversationLog.Clear();
    AddToConvo("Preparing to open the Pod Bay...");
    await Task.Delay(1000);

    AddToConvo("Depressurizing Airlock...");
    await Task.Delay(2000);

    AddToConvo("Retracting blast doors...");
    await Task.Delay(2000);

    AddToConvo("Pod Bay is open to space!");
  }

  public ObservableCollection<string> ConversationLog { get; } = new ObservableCollection<string>();
  private void AddToConvo(string content)
  {
    ConversationLog.Add(content);
  }
}

 

예제에서는 MVVM 패턴을 사용하기 위해서 ReactiveUI와 함께 CommunityToolkit.Mvvm 패키지를 사용하는 코드를 보여준다. 두 방식 모두 ICommand 인터페이스를 구현하는데 ReactiveUI를 사용하는 뷰모델은 ReactiveObject를 상속받아 ReactiveCommand로 명령을 생성하지만 CommunityToolkit.Mvvm를 사용하는 뷰모델은 ObservableObject를 상속받아 RelayCommand로 명령을 생성한다. 뷰에서 해당 명령의 수행 가능 여부를 나타내는 CanExecute 설정 방식도 약간의 차이가 있다.

 

728x90
댓글
최근에 올라온 글
최근에 달린 댓글
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함