WPF MVVM을 이용해서 View가 전환(Switching, Navigation)되는 방법 문의

안녕하세요.
각각의 버튼을 클릭했을 때, 뷰가 전환되는 간단한 예제 프로그램을 알고싶습니다.
우선은 제 생각대로 공부한대로 작성해봤는데 잘 되지 않아 문의드립니다.

Page1Model

namespace Step3_1.Models.Page1
{
    class Page1Model
    {
        public string name { get; set; }
        public int age { get; set; }
    }
}

Page2Model

namespace Step3_1.Models.Page2
{
    class Page2Model
    {
        public string message { get; set; }
    }
}

Page1VM

namespace Step3_1.ViewModels.Page1
{
    class Page1VM : ViewModelBase
    {
        private Models.Page1.Page1Model _page1Model;

        public string Name
        {
            get { return _page1Model.name; }
            set { _page1Model.name = value; OnPropertyChanged(); }
        }

        public int age
        {
            get { return _page1Model.age; }
            set { _page1Model.age = value; OnPropertyChanged(); }
        }

        public Page1VM()
        {
            InitializeVM();
        }

        private void InitializeVM()
        {
            _page1Model = new Models.Page1.Page1Model();
        }

    }
}

Page2VM

namespace Step3_1.ViewModels.Page2
{
    class Page2VM :ViewModelBase
    {
        private Models.Page2.Page2Model _page2Model;

        public string Message
        {
            get { return _page2Model.message; }
            set { _page2Model.message = value; OnPropertyChanged(); }
        }

        public Page2VM()
        {
            InitializeVM();
        }

        private void InitializeVM()
        {
            _page2Model = new Models.Page2.Page2Model();
        }



    }
}

Page1View

<UserControl x:Class="Step3_1.VIews.Page1.Page1View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Step3_1.VIews.Page1"
             xmlns:vm="clr-namespace:Step3_1.ViewModels.Page1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.DataContext>
        <vm:Page1VM/>
    </UserControl.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="20"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" Background="AntiqueWhite" Orientation="Horizontal">
            <TextBlock Text="Name : "/>
            <TextBox Text="{Binding Name}" Width="100"/>
        </StackPanel>
        <StackPanel Grid.Row="1" Background="Gainsboro"  Orientation="Horizontal">
            <TextBlock Text="Age : "/>
            <TextBox Text="{Binding age}" Width="100"/>
        </StackPanel>
    </Grid>
</UserControl>

Page2View

<UserControl x:Class="Step3_1.VIews.Page2.Page2View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Step3_1.VIews.Page2"
             xmlns:vm="clr-namespace:Step3_1.ViewModels.Page2"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.DataContext>
        <vm:Page2VM/>
    </UserControl.DataContext>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0" Background="Aquamarine"  Orientation="Horizontal">
            <TextBlock Text="Message : "/>
            <TextBox Text="{Binding Message}" Width="100"/>
        </StackPanel>
    </Grid>
</UserControl>

MainWindowVM

namespace Step3_1.ViewModels
{
    public class MainWindowVM : ViewModelBase
    {
        private object _currentVM;
        public object CurrentVM
        {
            get { return _currentVM; }
            set { _currentVM = value; OnPropertyChanged(); }
        }

        private Page1.Page1VM _page1VM;
        private Page2.Page2VM _page2VM;

        public ICommand Page1Command { get; set; }
        private void Page1(object obj)
        {
            CurrentVM = _page1VM;
        }

        public ICommand Page2Command { get; set; }
        private void Page2(object obj)
        {
            CurrentVM = _page2VM;
        }

        public MainWindowVM()
        {
            InitializeVM();
            SwitchToFirstView();
        }

        private void InitializeVM()
        {
            _page1VM = new Page1.Page1VM();
            _page2VM = new Page2.Page2VM();

            Page1Command = new RelayCommand<object>(Page1);
            Page2Command = new RelayCommand<object>(Page2);
        }

        private void SwitchToFirstView()
        {
            CurrentVM = _page1VM;
        }



    }
}

MainWindow

<Window x:Class="Step3_1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Step3_1"
        xmlns:vm="clr-namespace:Step3_1.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:MainWindowVM/>
    </Window.DataContext>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0" Background="Beige" Orientation="Horizontal">
            <Button Content="Page1" Width="100" Command="{Binding Page1Command}"/>
            <Button Content="Page2" Width="100" Command="{Binding Page2Command}"/>
        </StackPanel>

        <DockPanel Grid.Row="1">
            <ContentControl Content="{Binding CurrentVM}"/>
        </DockPanel>
        
    </Grid>
</Window>
  1. View를 볼수가 없는데 방법이 있을까요?
  2. View를 Switching(Navigation)하는 가장 범용적이고 좋은 코드(방법)이 있을까요?

감사합니다.

4 Likes

ContentControl의 Content에 CurrentVM 프로퍼티를 바인딩하는 것만으로는 그 Content를 어떻게 그려야 할 지 알 수 없습니다.

리소스에 ViewModel 타입에 대한 DataTemplate을 정의해서, View에서 ViewModel을 Content로 받을 때 어떻게 그릴 것인지를 미리 알려줘야 합니다.

<DataTemplate DataType={x:Type vm:Page1VM}>
    <v:Page1/> <!-- namespace 적절히 변경 -->
</DataType>
4 Likes

우선 답변주셔서 감사합니다!
그러면 저 DataTemplate를 MainWindow나 따로 Resource에 추가해서 전환이 되는것은 확인했습니다.
그런데 Page1에 있는 string이나 int 데이터가 페이지 전환하면 값이 사라지고 마치 인스턴스가 새로 생성되는 것처럼 보이는데
처음 한번만 생성하고 더 생성안되게 하려면 어떻게 해야하는지 알 수 있을까요?

2 Likes

아뇨 MainWindow이나 App.xaml, 혹은 별도의 리소스 사전 등 애플리케이션에서 참조되는 곳에 넣으셔야 합니다.

현 상황에서 계층 구조를 머릿속으로 그려보신다면, Page1이나 Page2는 어느 곳에서도 참조가 되지 않는다는 것을 아실 수 있을 겁니다.

Page1VM이 들어왔을 때는 Page1을 그려라, Page2VM이 들어왔을 때는 Page2를 그려라 하는 것도 결국 어딘가에서 참조가 되어야 렌더러가 알 수 있을 텐데, 어느 곳에서도 참조되지 않는 Page1이나 Page2에 템플릿을 넣어봤자 렌더러가 알 수 없겠죠?

따라서 DataTemplate은 애플리케이션에서 참조되는 곳에 넣으셔야 하고, 일반적으로는 별도의 리소스 사전에 정의하는 편입니다.

제가 글재주가 없어서 좀 장황하게 늘어지는 것 같네요 ㅎㅎ… 아래 다른 분께서 작성하신 아티클을 추천드립니다.

https://blog.naver.com/vactorman/222613914741

4 Likes

네 링크 가르쳐주셔서 감사합니다!
자꾸 꼬리를 무는것 같아 시간을 빼앗는것같아 죄송하지만ㅠ
한가지만 더 여쭤봐도 될까요?
말씀하신대로 DataTemplate를 MainWindow에 추가해서 전환이 되는것은 확인했습니다.
그런데 Page1에 name Textbox에 입력하고 Page2로 전환 후 다시 Page1로 전환하면 값이 사라지고 마치 Page1VM의 생성자가 다시 호출되는 것처럼 보이는데
처음 한번만 생성하고 더 생성안되게 하려면 어떻게 해야하는지 알 수 있을까요?

3 Likes

캐시처리를 구현하시면 됩니다.

해당 내용도 위 블로그에 같이 있습니다.

5 Likes

DataTemplate으로 View를 그릴 때는 매번 View를 새로 생성합니다.

그런데 각 View의 xaml 코드에서 DataContext를 할당해주고 있기 때문에, ViewModel 역시 View의 생성자에서 매번 새로 생성되는 로직으로 진행됩니다. View에서 DataContext로 할당되는 ViewModel은 MainWindowVM에서 들고 있는 ViewModel과는 다른 객체입니다.

Page1, Page2의 xaml 코드에서 DataContext를 지우시면 됩니다. DataTemplate에 의해 View를 그릴 때는 DataType으로 넣어준 ViewModel이 자동으로 DataContext로 할당됩니다.

View가 매번 새로 생성되는 문제는 @aroooong 님이 말씀하신 대로 해당 블로그에 아티클이 있습니다.

5 Likes

정말 감사합니다!
캐시처리에 대해 위 블로그 보고 캐시처리가 무엇인지부터 공부해야 될 것 같습니다.
다시 한번 가르쳐주셔서 감사합니다!

4 Likes

Page1과 Page2의 DataContext와
DataTemplate에 의해 View를 그릴때 자동으로 DataContext로 할당된다는 사실을 처음 알았습니다.
말씀해주신대로 작성해서 테스트해보니 원하는 대로 작동되었습니다!
또 공유해주신 링크 블로그 확인해서 '캐시처리’에 대해서도 같이 공부해서 좀 더 개선 해야 될 것 같습니다!

좋은 가르침 주셔서 감사합니다!
2주간을 고민하고 진도가 나가지 않았는데 이렇게 해결되니 신기하고 감사합니다!

3 Likes