다른 윈도우에서 같은 데이터를 참조하기

안녕하세요, 이제 막 WPF와 MVVM을 배우기 시작한 학생입니다.

MVVM의 패턴에 따라 윈도우A에서 새 윈도우B를 열기 위해서는
윈도우A의 VM에서 직접 윈도우B의 View를 참조하면 안되기 때문에 윈도우A의 VM에서 윈도우B의 VM을 생성하고, 이를 서비스를 통해 넘겨주는 방식으로 해야한다고 이해했습니다.

이런 상황에서 윈도우B에서 윈도우A의 데이터를 어떻게 참조/바인딩 하는 것이 올바른 방법인가요?

두개의 창을 띄운 후, 다른 창에서 다른 하나창의 데이터를 건드리고 싶습니다. - al6uiz 님의 게시물 #4

위 링크의 질문 게시글을 읽어보았는데,

var viewModel = new ViewModel();

View1.DataContext = viewModel;
View2.DataContext = viewModel;

이렇게 같은 VM을 공유하게 하라 하셨습니다. 근데 이걸 실제 MVVM패턴상 어느위치에 어떻게 적용해야할 지 잘 모르겠습니다. 윈도우A의 VM에서 서비스로 윈도우B를 여는 작업을 할때 서비스에 윈도우A의 VM을 같이 넘겨주고 이를 윈도우B의 데이터 컨텍스트로 설정해주면 되는건가요?

서로 다른 View가 하나의 VM을 공유해도 괜찮은 것인가요?

기초적인 이해가 아직 부족하여 설명을 잘 못하겠습니다. 죄송합니다.

6개의 좋아요
2개의 좋아요

참조해주신 답변 링크의 경우 두 개의 뷰가 하나의 쌍으로 동작하는 특수한 상황에 대한 답변으로 보통은 View와 ViewModel은 1:1로 매핑됩니다.

일반적인 MVVM에서 새 창을 띄우는 케이스가 아니라면 View와 ViewModel은 DataTemplate을 통해 연결할 수 있기 때문에 ViewModel이 View를 직접 참조하지 않아도 MVVM을 위반하지 않고 View를 표시 할 수 있습니다.

<DataTemplate DataType="{x:Type local:SomeViewModel}"> 
    <local:SomeView/>
</DataTemplate>

하지만 새로운 창을 띄워야 하는 상황이라면, 부모 ViewModel에서 아래와 같은 서비스를 가지고 있다 사용하는 방법을 고려해 볼 수 있습니다.

public class WindowResolveService
{
    private readonly Dictionary<Type, Type> _map = new Dictionary<Type, Type>();

    /// <summary>
    /// ViewModel, View(Window) 타입 등록
    /// </summary>
    public void Register<TViewModel, TView>()
        where TViewModel : ViewModelBase
        where TView : Window
    {
        _map[typeof(TViewModel)] = typeof(TView);
    }

    public void Show(ViewModelBase viewModel)
    {
        var window = CreateWindow(viewModel);
        window.Show();
    }

    public bool? ShowDialog(ViewModelBase viewModel)
    {
        var window = CreateWindow(viewModel);
        return window.ShowDialog();
    }

    private Window CreateWindow(ViewModelBase viewModel)
    {
        var vmType = viewModel.GetType();
        if (!_map.TryGetValue(vmType, out var viewType))
            throw new InvalidOperationException($"등록되지 않은 ViewModel 타입입니다: {vmType.Name}");

        var window = (Window)Activator.CreateInstance(viewType)!;
        window.DataContext = viewModel;
        return window;
    }
}

의존성 주입을 사용하고 있지 않다면 ViewModel 형식에 따른 View 형식의 등록은 App 클래스에서 전역적으로 설정하시거나, View 계열 클래스에서 필요한 것만 등록해서 사용하시면 됩니다.

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        var windowService = new WindowResolveService();

        // ViewModel ↔ Window 매핑 등록
        windowService.Register<DetailViewModel, DetailWindow>();
        windowService.Register<SettingsViewModel, SettingsWindow>();

        var mainViewModel = new MainViewModel { WindowService = windowService };
        var mainWindow = new MainWindow { DataContext = mainViewModel };
        mainWindow.Show();
    }
}

호출은 아래와 같이

public class MainViewModel
{
    public WindowResolveService WindowService { get; set; }
    // public required WindowResolveService WindowService { get; init; }
    ...

    private void OnExecuteShowSettingDialogCommand(object arg)
    {
        var settingsVM = new SettingsViewModel
        {
            ...
        };
        var result = WindowService.ShowDialog(settingsVM);
        if (result == true)
        {
            ...
        }
    });
}

또한, 위와 같은 상황이 필요한 경우에 대해서 답변 드리자면,
엔터프라이즈 어플리케이션 급으로 깊이 들어가면 구조가 조금은 달라질 수 있지만 View(Window)를 생성 및 표시하는 책임을 갖는 별도의 서비스를 통하는 기본적인 접근 방법은 같습니다.

public class ABWindowService //: IABWindowService
{
    public void Show(SharedViewModel viewModel)
    {
        var winA = new AWindow { DataContext = viewModel };
        var winB = new BWindow { DataContext = viewModel };
        winA.Show();
        winB.Show();
    }
}

최근 커뮤니티에 부쩍 MVVM에 대한 논의가 올라오고 있는데요,

복잡도가 크지않은 간단한 프로젝트라면 원칙적으로 접근하지 않고 오직 View 생성할 때만 ViewModel이 View의 형식 알고 있는것을 허용한다 라는 예외 규칙을 두고 WPF와 MVVM이 제공하는 편의성을 적절히 활용하는 절충안도 검토해 볼 만할 것 같습니다.
상황에 따라 유연한 접근을 취하는 것도 빠른 배움에 도움이 되지 않을까 합니다.

이상입니다.

8개의 좋아요

하나의 VM 클래스를 여러 뷰에 사용하는 것
하나의 VM 클래스의 인스턴스를 공유하는 것

목적에 따라 괜찮습니다.

4개의 좋아요

@al6uiz @favdra @leechw9 친절한 답변 모두 감사드립니다. 가르쳐주신 내용을 토대로 적용해 보겠습니다. 감사합니다 <(_ _)>

2개의 좋아요

좀 오래된 내용이긴한데

Windows 를 핸들링하는 기초적인 개념을 포함하고 있긴해요.
https://blog.naver.com/vactorman/222062958706

그리고 지금 질문하신 상황이라면 VM 을 개별적으로 생성하는 것보다는
VM 생성이나 호출을 별도의 서비스나 인스턴스에 위임하고
Message 를 publish 하는 게 더 나은 선택같아 보여요.

2개의 좋아요

저도 쫌 오래된 내용인데,

tyeom/MVVM_PopupSample: WPF MVVM Popup

DialogService 를 사용해

MVVM에 맞게 팝업 윈도우 Open / Close 샘플 예제 코드를 공유해 드립니다.


또한 다른 뷰모델간 데이터 통신 방법은 저도 @Greg.Lee 님 의견 처럼 메신저 등 (이벤트어그리게이트 방식)을 이용한 방식을 추천 드립니다.

2개의 좋아요