// 이 클래스는 학생(Student) 객체와 대상 화면(TargetView)에 대한 정보를 함께 담아서
// ViewModel 간에 메시지로 전달하기 위한 용도로 사용됩니다.
// Messenger를 통해 데이터를 전달할 때, 수신자가 자신에게 온 메시지인지 식별할 수 있도록 돕습니다.
public class StudentSelectedMessage
{
// 전달하고자 하는 학생 정보입니다.
// 외부에서는 읽을 수만 있고, 변경은 할 수 없습니다.
public Student SelectedStudent { get; }
// 이 메시지를 수신해야 할 대상 화면의 이름입니다.
// 예: "DetailView", "EditView" 등. 수신자가 메시지 필터링을 할 수 있게 도와줍니다.
public string TargetView { get; }
// 생성자: 메시지를 생성할 때 학생 정보와 대상 화면 이름을 함께 전달받습니다.
// 이 생성자를 통해 속성들이 초기화됩니다.
public StudentSelectedMessage(Student student, string targetView)
{
SelectedStudent = student; // 전달할 학생 객체 저장
TargetView = targetView; // 수신자 식별용 문자열 저장
}
}
// 이 클래스는 한 명의 학생(Student)에 대한 정보를 표현하는 데이터 모델입니다.
// MVVM 구조에서 ViewModel과 View가 다룰 데이터의 기본 단위로 사용됩니다.
public class Student
{
// 학생의 이름을 저장하는 속성입니다.
// string.Empty로 초기화하여 기본값을 빈 문자열로 설정합니다.
// 예: "홍길동"
public string Name { get; set; } = string.Empty;
// 학생의 나이를 저장하는 속성입니다.
// 예: 18
public int Age { get; set; }
// 객체를 문자열로 표현할 때 사용되는 메서드입니다.
// 예: student.ToString()을 호출하면 "홍길동 (18세)"처럼 출력됩니다.
public override string ToString() => $"{Name} ({Age}세)";
}
using CommunityToolkit.Mvvm.ComponentModel; // ObservableObject, ObservableProperty 사용을 위한 네임스페이스
using CommunityToolkit.Mvvm.Input; // RelayCommand 사용을 위한 네임스페이스
using System.Collections.ObjectModel; // ObservableCollection 사용
using System; // Action 델리게이트 사용
using WpfMvvmToolkit07_ObservableRecipient02.Models; // Student 모델 참조
public partial class MainViewModel : ObservableObject
{
// 학생 목록을 담는 컬렉션입니다.
// ObservableCollection은 UI와 바인딩되어 있을 경우 자동으로 변경 사항이 반영됩니다.
public ObservableCollection<Student> Students { get; } = new()
{
new Student { Name = "홍길동", Age = 20 },
new Student { Name = "김영희", Age = 22 },
new Student { Name = "이철수", Age = 21 },
};
// 사용자가 선택한 학생 정보를 저장하는 속성입니다.
// [ObservableProperty]를 붙이면 SelectedStudent 프로퍼티와 PropertyChanged 이벤트가 자동 생성됩니다.
[ObservableProperty]
private Student? selectedStudent;
// 특정 화면(View)의 이름을 인자로 받아서, 해당 화면을 열어달라는 요청을 보낼 이벤트입니다.
// View에서는 이 이벤트를 구독하고, 전달받은 문자열을 기준으로 어떤 창을 열지 결정합니다.
public event Action<string>? RequestShowDetail;
// DetailView 화면을 열어달라고 요청하는 명령입니다.
// [RelayCommand]를 붙이면 ShowInDetailViewCommand라는 ICommand가 자동으로 생성됩니다.
[RelayCommand]
private void ShowInDetailView()
{
// 학생이 선택된 경우에만 이벤트를 발생시킵니다.
if (SelectedStudent is not null)
RequestShowDetail?.Invoke("DetailView"); // "DetailView" 라는 문자열로 수신자 구분
}
// LogView 화면을 열어달라고 요청하는 명령입니다.
// 이 명령은 선택된 학생 정보를 로그 화면에 표시하고자 할 때 사용됩니다.
[RelayCommand]
private void ShowInLogView()
{
if (SelectedStudent is not null)
RequestShowDetail?.Invoke("LogView"); // "LogView" 라는 문자열로 수신자 구분
}
}
📁 DetailViewModel
using CommunityToolkit.Mvvm.ComponentModel; // ObservableRecipient, ObservableProperty 기능 사용
using CommunityToolkit.Mvvm.Messaging; // 메시지 수신을 위한 인터페이스 사용
using WpfMvvmToolkit07_ObservableRecipient02.Messages; // 메시지 클래스(StudentSelectedMessage) 참조
using WpfMvvmToolkit07_ObservableRecipient02.Models; // Student 모델 참조
namespace WpfMvvmToolkit07_ObservableRecipient02.ViewModels
{
// DetailViewModel은 학생 정보 상세 화면에 해당하는 ViewModel입니다.
// 메시지를 통해 선택된 학생 정보를 받아서 화면에 표시합니다.
public partial class DetailViewModel : ObservableRecipient, IRecipient<StudentSelectedMessage>
{
// 생성자: ViewModel이 메시지를 수신할 수 있도록 IsActive를 true로 설정합니다.
// ObservableRecipient는 IsActive가 true일 때만 메시지를 받을 수 있습니다.
public DetailViewModel()
{
IsActive = true; // 메시지 수신 시작
}
// 메시지 수신 메서드: StudentSelectedMessage를 받으면 자동으로 실행됩니다.
// 메시지 안의 TargetView가 "DetailView"일 때만 해당 데이터를 수신하도록 필터링합니다.
public void Receive(StudentSelectedMessage message)
{
if (message.TargetView == "DetailView")
{
// 메시지로 전달받은 학생 정보를 StudentInfo에 저장
// StudentInfo 속성은 View에 바인딩되어 있으므로 자동으로 화면에 반영됩니다.
StudentInfo = message.SelectedStudent;
}
}
// 수신한 학생 정보를 저장하는 프로퍼티입니다.
// ObservableProperty 특성을 사용하면, 자동으로 StudentInfo 속성과 변경 알림이 생성됩니다.
// View에서는 이 속성을 바인딩하여 학생 정보를 표시할 수 있습니다.
[ObservableProperty]
private Student? studentInfo;
}
}
📁 LogViewModel
using CommunityToolkit.Mvvm.ComponentModel; // ObservableRecipient, ObservableProperty 사용
using CommunityToolkit.Mvvm.Messaging; // 메시지 수신 인터페이스 사용
using WpfMvvmToolkit07_ObservableRecipient02.Messages; // StudentSelectedMessage 메시지 참조
namespace WpfMvvmToolkit07_ObservableRecipient02.ViewModels
{
// LogViewModel은 선택된 학생 정보를 받아 로그 형태의 문자열로 저장하는 역할을 합니다.
// 이 ViewModel은 메시지를 수신할 수 있도록 ObservableRecipient를 상속받고,
// 특정 메시지를 받기 위해 IRecipient<StudentSelectedMessage> 인터페이스를 구현합니다.
public partial class LogViewModel : ObservableRecipient, IRecipient<StudentSelectedMessage>
{
// 생성자: 메시지 수신을 활성화합니다.
// ObservableRecipient는 IsActive가 true일 때만 메시지를 수신할 수 있습니다.
public LogViewModel()
{
IsActive = true; // 메시지 수신 시작
}
// 메시지를 수신했을 때 자동으로 호출되는 메서드입니다.
// 메시지의 TargetView가 "LogView"인 경우에만 메시지를 처리합니다.
public void Receive(StudentSelectedMessage message)
{
if (message.TargetView == "LogView")
{
// 전달받은 학생 정보를 로그 문자열로 가공하여 Log 속성에 저장합니다.
// 이 속성은 View와 바인딩되어 있으므로 자동으로 UI에 표시됩니다.
Log = $"[{message.SelectedStudent.Name}] 선택됨 - 나이 {message.SelectedStudent.Age}";
}
}
// 로그 메시지를 저장하는 속성입니다.
// [ObservableProperty]를 사용하면 Log 속성과 변경 알림 기능이 자동으로 생성됩니다.
// View에서는 이 속성을 바인딩하여 로그 메시지를 표시할 수 있습니다.
[ObservableProperty]
private string? log;
}
}
// 이 메서드는 전달받은 문자열 target에 따라 다른 창(윈도우)을 띄우고,
// 선택된 학생(Student) 정보를 메시지로 함께 전달하는 역할을 합니다.
private void ShowTargetWindow(string target)
{
// 1. 현재 View의 DataContext가 MainViewModel인지 확인합니다.
// 2. ViewModel에 SelectedStudent(선택된 학생)가 설정되어 있는지 확인합니다.
if (DataContext is MainViewModel vm && vm.SelectedStudent != null)
{
// target 문자열 값에 따라 새로 열 창을 결정합니다.
// switch 표현식을 사용하여 target이 "DetailView"면 DetailWindow를,
// "LogView"면 LogWindow를 생성합니다.
// 그 외의 값은 null을 반환합니다.
Window? dialog = target switch
{
"DetailView" => new DetailWindow(), // 상세 보기 창
"LogView" => new LogWindow(), // 로그 보기 창
_ => null // 그 외의 경우는 창 없음
};
// 창이 유효하게 생성되었는지 확인한 후 실행합니다.
if (dialog != null)
{
// 창이 화면에 완전히 표시되었을 때 실행할 이벤트를 등록합니다.
// Loaded 이벤트는 창이 모두 그려졌을 때 발생합니다.
dialog.Loaded += (sender, args) =>
{
// 선택된 학생 정보와 창의 대상 이름을 담은 메시지를 생성합니다.
var message = new StudentSelectedMessage(vm.SelectedStudent, target);
// 메시지를 전송하여 해당 ViewModel이 이 메시지를 수신할 수 있도록 합니다.
WeakReferenceMessenger.Default.Send(message);
};
// 새로 열 창의 부모 창(owner)을 현재 창(this)으로 설정합니다.
// 이렇게 하면 자식 창이 부모 창 위에 항상 떠 있게 됩니다.
dialog.Owner = this;
// 창을 모달 모드로 띄웁니다.
// 사용자가 이 창을 닫기 전까지는 다른 창과 상호작용할 수 없습니다.
dialog.ShowDialog();
}
}
}