07.4 ObservableRecipient - 예제4
1️⃣ ObservableRecipient 예제4. 시나리오
더보기

✔️ 1. 동작 시나리오

- MainWindow 리스트에서 항목을 선택하고
- 상세 보기 버튼을 누르면 DetailWindow 에서 선택한 학생의 상세정보와 함께 실행됩니다.
- 이름과 나이를 수정하고
- 정보 반환 버튼을 누르면
- 수정된 내용이 MainWindow 리스트에 반영됩니다.
2️⃣ 프로젝트 구조
더보기
📁 프로젝트 구조
ProjectName/
├── Messages/
│ └── StudentSelectedMessage.cs
├── Models/
│ └── Student.cs
├── ViewModels/
│ ├── MainViewModel.cs
│ └── DetailViewModel.cs
│ └── LogViewModel.cs
├── Views/
│ ├── MainView.xaml
│ └── DetailView.xaml
│ └── LogView.xaml
├── App.xaml
└── App.xaml.cs
3️⃣ Message 구현
더보기

📁 Message

using CommunityToolkit.Mvvm.Messaging.Messages; // Toolkit에서 제공하는 메시지 기반 클래스 사용
using WpfMvvmToolkit07_ObservableRecipient04.Models; // Student 모델 참조
namespace WpfMvvmToolkit07_ObservableRecipient04.Messages
{
// 이 클래스는 선택된 학생 정보를 다른 ViewModel이나 화면에 전달할 때 사용하는 메시지입니다.
// 메시지 안에는 Student 객체와 대상 View 이름(TargetView)이 함께 들어 있습니다.
public class StudentSelectedMessage
{
// 선택된 학생 정보를 담는 속성입니다. 읽기 전용입니다.
public Student SelectedStudent { get; }
// 메시지를 수신해야 할 View 이름입니다.
// 예: "DetailView", "LogView" 등의 식별자로 사용됩니다.
public string TargetView { get; }
// 생성자: 메시지를 생성할 때 학생 정보와 대상 View 이름을 설정합니다.
public StudentSelectedMessage(Student student, string targetView)
{
SelectedStudent = student;
TargetView = targetView;
}
}
// 이 메시지는 학생(Student) 객체를 요청(Request)할 때 사용됩니다.
// 수신자는 이 메시지를 받고, 적절한 Student 객체를 응답으로 반환합니다.
public class StudentRequestMessage : RequestMessage<Student> { }
// 이 메시지는 학생(Student) 객체를 반환(Return)할 때 사용됩니다.
// 일반적으로 RequestMessage 응답용으로 활용되며, 비동기 응답 처리에도 사용됩니다.
public class StudentReturnMessage : RequestMessage<Student> { }
}
4️⃣ Model 구현
더보기

📁 Model

using CommunityToolkit.Mvvm.ComponentModel; // ObservableObject와 ObservableProperty 사용을 위한 네임스페이스
// Student 클래스는 학생 한 명의 이름과 나이를 저장하는 데이터 모델입니다.
// MVVM 구조에서 ViewModel과 View가 다룰 수 있는 바인딩 가능한 데이터 구조로 사용됩니다.
// ObservableObject를 상속하면, 속성 값이 바뀔 때 자동으로 View에 알림을 보낼 수 있습니다.
public partial class Student : ObservableObject
{
// 학생의 이름을 저장하는 속성입니다.
// [ObservableProperty]를 사용하면 자동으로 Name 프로퍼티와 PropertyChanged 알림이 생성됩니다.
// 이 속성이 바뀌면 UI에서도 자동으로 값이 업데이트됩니다.
[ObservableProperty]
private string name = string.Empty; // 기본값: 빈 문자열
// 학생의 나이를 저장하는 속성입니다.
// 역시 [ObservableProperty]를 통해 자동으로 Age 프로퍼티가 생성되고, 변경 시 알림이 발생합니다.
[ObservableProperty]
private int age;
// 이 메서드는 객체를 문자열로 표현할 때 사용됩니다.
// 예: ToString()을 호출하면 "홍길동 (20세)" 와 같은 형식으로 출력됩니다.
public override string ToString() => $"{Name} ({Age}세)";
}
5️⃣ ViewModel 구현
더보기


📁 MainViewModel

using CommunityToolkit.Mvvm.ComponentModel; // ObservableObject와 ObservableProperty 사용
using CommunityToolkit.Mvvm.Input; // RelayCommand 사용
using CommunityToolkit.Mvvm.Messaging; // Messenger 메시지 기능 사용
using System; // Action 대리자 사용
using System.Collections.ObjectModel; // ObservableCollection 사용
using System.Linq; // FirstOrDefault 등 LINQ 사용
using WpfMvvmToolkit07_ObservableRecipient02.Messages; // 메시지 클래스들 참조
public partial class MainViewModel : ObservableObject, IRecipient<Student>
{
// 학생 리스트를 저장하는 ObservableCollection입니다.
// UI와 바인딩되어 있어 항목이 추가되거나 변경되면 자동으로 반영됩니다.
public ObservableCollection<Student> Students { get; } = new()
{
new Student { Name = "홍길동", Age = 20 },
new Student { Name = "김영희", Age = 22 },
new Student { Name = "이철수", Age = 21 },
};
// 사용자가 선택한 학생을 저장하는 프로퍼티입니다.
// ObservableProperty 어트리뷰트로 자동으로 프로퍼티와 변경 알림이 생성됩니다.
[ObservableProperty]
private Student? selectedStudent;
// View에게 특정 화면을 열어달라고 요청하는 이벤트입니다.
// 문자열 인자로 어떤 화면("DetailView" 등)을 열지 구분합니다.
public event Action<string>? RequestShowDetail;
// 생성자: 이 ViewModel이 Student 타입 메시지를 받을 수 있도록 등록합니다.
public MainViewModel()
{
WeakReferenceMessenger.Default.Register<Student>(this);
}
// 메시지를 수신하면 실행되는 메서드입니다.
// Student 객체를 메시지로 받으면 SelectedStudent에 저장합니다.
public void Receive(Student message)
{
SelectedStudent = message;
}
// 상세 보기 화면을 요청하는 커맨드입니다.
// 선택된 학생이 있을 때만 RequestShowDetail 이벤트를 발생시킵니다.
[RelayCommand]
private void ShowInDetailView()
{
if (SelectedStudent is not null)
RequestShowDetail?.Invoke("DetailView");
}
// 상세 보기 화면에서 수정된 학생 정보를 요청하여 가져오는 커맨드입니다.
// StudentReturnMessage 메시지를 보내고, 응답이 있다면 해당 학생 정보를 업데이트합니다.
[RelayCommand]
private void ReturnStudentFromDetail()
{
// 메시지를 전송하고 응답을 받습니다.
var response = WeakReferenceMessenger.Default.Send<StudentReturnMessage>();
// 응답이 있다면
if (response.HasReceivedResponse)
{
var updated = response.Response; // 반환된 학생 정보
// 기존 학생 목록에서 동일한 이름의 학생을 찾습니다.
var existing = Students.FirstOrDefault(s => s.Name == updated.Name);
if (existing != null)
{
// 기존 학생 정보 업데이트
existing.Name = updated.Name;
existing.Age = updated.Age;
// 선택된 학생 변경 알림을 유도하여 UI 갱신
SelectedStudent = null;
SelectedStudent = existing;
}
else
{
// 목록에 없으면 새로 추가합니다.
Students.Add(updated);
SelectedStudent = updated;
}
}
}
}
📁 DetailViewModel

6️⃣ View 구현
더보기



📁 MainWindow

xmlns:vm="clr-namespace:WpfMvvmToolkit07_ObservableRecipient04.ViewModels"
Title="학생 목록" Height="300" Width="400">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<StackPanel Margin="10">
<ListBox ItemsSource="{Binding Students}"
SelectedItem="{Binding SelectedStudent, Mode=TwoWay}"
Height="150">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="(" />
<TextBlock Text="{Binding Age}" />
<TextBlock Text="세)" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="상세 보기" Command="{Binding ShowInDetailViewCommand}" Margin="5" Width="100"/>
<Button Content="로그 보기" Command="{Binding ShowInLogViewCommand}" Margin="5" Width="100"/>
</StackPanel>
</StackPanel>
📁 MainWindow - Code Behind

public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
if (DataContext is MainViewModel vm)
{
vm.RequestShowDetail += ShowTargetWindow;
}
}
private void ShowTargetWindow(string target)
{
if (DataContext is MainViewModel vm && vm.SelectedStudent != null)
{
Window? dialog = target switch
{
"DetailView" => new DetailView(),
"LogView" => new LogView(),
_ => null
};
if (dialog != null)
{
dialog.Loaded += (_, _) =>
{
WeakReferenceMessenger.Default.Send(new StudentSelectedMessage(vm.SelectedStudent, target));
};
dialog.Owner = this;
dialog.ShowDialog();
}
}
}
}
📁 DetailWindow

<Window x:Class="WpfMvvmToolkit07_ObservableRecipient04.Views.DetailView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfMvvmToolkit07_ObservableRecipient04.ViewModels"
Title="상세 정보" Height="250" Width="300" WindowStartupLocation="CenterOwner">
<Window.DataContext>
<vm:DetailViewModel />
</Window.DataContext>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="학생 정보" FontSize="16" FontWeight="Bold" Grid.Row="0"/>
<StackPanel Grid.Row="1" Margin="0,10,0,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="이름: " Width="50"/>
<TextBox Text="{Binding StudentInfo.Name, UpdateSourceTrigger=PropertyChanged}" Width="200"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<TextBlock Text="나이: " Width="50"/>
<TextBox Text="{Binding StudentInfo.Age, UpdateSourceTrigger=PropertyChanged}" Width="200"/>
</StackPanel>
</StackPanel>
<Button Content="정보 반환" Grid.Row="2" Height="30" Margin="0,20,0,0"
HorizontalAlignment="Center" Width="100"
Click="OnReturnButtonClick"/>
</Grid>
</Window>