Avalonia와 ReactiveUI 및 소스 생성기 활용한 데이터 수 자동 갱신 구현

1. 프로젝트 구성 파일

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <LangVersion>latest</LangVersion>
    <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
  </PropertyGroup>

  <ItemGroup>
    <AvaloniaResource Include="Assets\**" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Avalonia" Version="11.3.9" />
    <PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.9" />
    <PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.9" />
    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.9" />
    <PackageReference Include="ReactiveUI.Avalonia" Version="11.3.8" />
    <PackageReference Include="ReactiveUI.SourceGenerators" Version="2.4.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>

2. 모델 정의

using ReactiveUI;
using ReactiveUI.SourceGenerators;

namespace AvaRea.Models
{
    public partial class UserProfile : ReactiveObject
    {
        [Reactive] private string _username;
        [Reactive] private int _userAge;
        [Reactive] private string _userEmail;
    }
}

3. 뷰모델 (ReactiveUI 소스 생성기 활용)

using AvaRea.Models;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Linq;

namespace AvaRea.ViewModels
{
    public partial class DashboardViewModel : ReactiveObject
    {
        private int newItemIndex = 0;
        public ObservableCollection<UserProfile> Users { get; set; } = new();
        
        [ObservableAsProperty]
        private int _totalUsers;
        
        [Reactive]
        private UserProfile _selectedUser;
        
        [ReactiveCommand]
        private void CreateUser()
        {
            Users.Add(new UserProfile 
            { 
                Username = $"사용자{newItemIndex++}", 
                UserAge = 25 + newItemIndex, 
                UserEmail = $"user{newItemIndex}@example.com"
            });
        }
        
        public DashboardViewModel()
        {
            var initialData = new[]
            {
                new UserProfile { Username = "김철수", UserAge = 28, UserEmail = "kim@example.com" },
                new UserProfile { Username = "박지영", UserAge = 32, UserEmail = "park@example.com" }
            };
            
            Users.AddRange(initialData);
            
            var countObserver = Users
                .ToObservableChangeSet()
                .Select(_ => Users.Count)
                .ToProperty(this, x => x.TotalUsers);
        }
    }

    using ReactiveUI;

    namespace AvaRea.ViewModels
    {
        public class BaseViewModel : ReactiveObject
        {
        }
    }
}

4. 뷰 정의

<Window
    x:Class="AvaRea.Views.MainView"
    xmlns="https://github.com/avaloniaui"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:model="clr-namespace:AvaRea.Models"
    xmlns:vm="clr-namespace:AvaRea.ViewModels"
    d:DesignHeight="450"
    d:DesignWidth="800"
    x:DataType="vm:DashboardViewModel"
    mc:Ignorable="d">
    <Window.DataTemplates>
        <DataTemplate DataType="model:UserProfile">
            <StackPanel
                Margin="5"
                Orientation="Horizontal"
                Spacing="10">
                <Ellipse
                    Width="32"
                    Height="32"
                    Fill="LightBlue" />
                <StackPanel>
                    <TextBlock FontWeight="Bold" Text="{Binding Username}" />
                    <TextBlock
                        FontSize="12"
                        Foreground="Gray"
                        Text="{Binding UserEmail}" />
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </Window.DataTemplates>
    <Design.DataContext>
        <vm:DashboardViewModel />
    </Design.DataContext>
    <StackPanel>
        <TextBlock Text="{Binding TotalUsers}" />
        <ListBox
            ItemsSource="{Binding Users}"
            SelectedItem="{Binding SelectedUser}" />
        <Button Command="{Binding CreateUserCommand}" Content="등록" />
    </StackPanel>
</Window>

using Avalonia.Controls;

namespace AvaRea.Views
{
    public partial class MainView : Window
    {
        public MainView()
        {
            InitializeComponent();
        }
    }
}

5. 애플리케이션 설정 파일

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="AvaRea.App"
             RequestedThemeVariant="Default">
    <Application.Styles>
        <FluentTheme />
    </Application.Styles>
</Application>

6. 애플리케이션 코드

using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;

using AvaRea.ViewModels;
using AvaRea.Views;

namespace AvaRea
{
    public partial class App : Application
    {
        public override void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }

        public override void OnFrameworkInitializationCompleted()
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            {
                desktop.MainWindow = new MainView
                {
                    DataContext = new DashboardViewModel()
                };
            }
            else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
            {
                singleViewPlatform.MainView = new MainView
                {
                    DataContext = new DashboardViewModel()
                };
            }

            base.OnFrameworkInitializationCompleted();
        }
    }
}

7. 프로그램 진입점

using System;
using Avalonia;
using ReactiveUI.Avalonia;

namespace AvaRea.Desktop
{
    class Program
    {
        [STAThread]
        public static void Main(string[] args) => BuildAvaloniaApp()
            .StartWithClassicDesktopLifetime(args);

        public static AppBuilder BuildAvaloniaApp()
            => AppBuilder.Configure<App>()
                .UsePlatformDetect()
                .WithInterFont()
                .LogToTrace()
                .UseReactiveUI();
    }
}

태그: Avalonia ReactiveUI SourceGenerators

7월 17일 05:35에 게시됨