https://www.youtube.com/watch?v=KWZ3bxBe1mQ&list=PLlrfTSXS0LLKHfOfwM31jJw3SHuDnkF49&index=12
📌 초기 세팅
- WpfDINavigation 프로젝트 생성 후 Nuget패키지 추가 및 MainWindow는 삭제 후 Models, ViewModels, Views 폴더 생성
📌 Views, ViewModels에 파일 추가
- App.xaml에서 StartupUri 삭제
<Application x:Class="WpfDINavigation.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
-App.xaml.cs
namespace WpfDINavigation
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
//자기 자신을 호출 Current은 타입이 Application이기 때문에 App 타입으로 으로 변환
public new static App Current => (App)Application.Current;
//의존성 주입을 위한 생성자 규칙을 서비스에 등록하고 그 서비스를 반환해서 바깥에서 그 서비스를 실행해서
//의존성 주입 가능하도록 하는 IServiceProvider 인터페이스 생성
private IServiceProvider? ConfigureServices()
{
var service = new ServiceCollection();
// ViewModels
service.AddSingleton<MainViewModel>();
// VIews
service.AddSingleton(s => new MainView()
{
//MainViewModel생성자 주입이 DataContext로 된다.
DataContext = s.GetRequiredService<MainViewModel>()
});
//BuildServiceProvider를 반환
return service.BuildServiceProvider();
}
public App()
{
//생성자 (BuildServiceProvider) 로 생성자 주입
Service = ConfigureServices();
var mainView = Service.GetRequiredService<MainView>();
mainView.Show();
}
public IServiceProvider Service { get; }
}
}
App()의 Service.GetRequiredService<MainView>(); 가 실행되면 private IServiceProvider? ConfigureServices()의 s => new MainView() 에서 MainView 생성자가 생성되고, DataContext = s.GetRequiredService<MainViewModel>() 에서 MainViewModel이 DataContext 로 들어간다.
📌 ViewModelBase 파일 추가
- ViewModels 폴더에 ViewModelBase 파일 추가
-ViewModelBase.cs
namespace WpfDINavigation.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
//속성값이 변화가 있을때 이벤트에 알려주기위해 함수 생성
//CallerMemberName 어트리뷰트를 사용하는 경우 OnPropertyChanged 를 호출했을 때 매개변수를 입력하지 않으면
//그 속성의 이름이 인자값으로 넘어오게 된다.
protected void OnPropertyChanged([CallerMemberName]string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
INotifyPropertyChanged 인터페이스는 MVVM 패턴의 필수 인터페이스
📌 RelayCommand 추가
- Command Binding을 위해 추가
- RelayCommand.cs
namespace WpfDINavigation.Commands
{
// RelayCommand는 Command를 바인딩하기 위해 ICommand 사용
//ICommand 에는 canExecute 와 execute 가 존재
// canExecute : Command를 실행할지에 대한 제한 조건을 거는 것
// execute : :Command를 실행
public class RelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Predicate<T>? _canExecute;
public RelayCommand(Action<T>? execute, Predicate<T>? canExecute = null)
{
this._execute = execute;
this._canExecute = canExecute;
}
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object? parameter)
{
// 해당 콜백 함수가 false 를 반환하면 Command는 실행 불가
return _canExecute?.Invoke((T)parameter!) ?? true;
}
public void Execute(object? parameter)
{
//parameter를 매개변수로 넘겨주고 콜백함수 실행
_execute?.Invoke((T)parameter!);
}
}
}
📌 View, ViewModel 생성
- 사용자 정의 컨트롤 LoginView, SingupView, TestView 추가
- 클래스 LoginViewModel , SingupViewModel , TestViewModel 추가
📌 MainView 디자인
- MainView.xaml
<Window x:Class="WpfDINavigation.Views.MainView"
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:WpfDINavigation.Views"
xmlns:viewmodels="clr-namespace:WpfDINavigation.ViewModels"
mc:Ignorable="d"
Title="MainView" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type viewmodels:LoginViewModel}">
<local:LoginView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:SingupViewModel}">
<local:SingupView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:TestViewModel}">
<local:TestView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</Window>
-MainViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfDINavigation.ViewModels
{
public class MainViewModel : ViewModelBase
{
private INotifyPropertyChanged? _currentViewModel;
public MainViewModel()
{
CurrentViewModel = new LoginViewModel();
}
public INotifyPropertyChanged? CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
//_currentViewModel 추가
_currentViewModel = value;
//OnPropertyChanged 호출
OnPropertyChanged();
}
}
}
}
}
◾ Binding 잘 작동하는지 확인 하기
-LoginViewModel.cs
public class LoginViewModel : ViewModelBase
{
private INotifyPropertyChanged? _currentViewModel;
public MainViewModel()
{
CurrentViewModel = new LoginViewModel();
}
- LoginViewModel.cs
namespace WpfDINavigation.ViewModels
{
public class LoginViewModel : ViewModelBase
{
}
}
- LoginView.xaml
<UserControl x:Class="WpfDINavigation.Views.LoginView"
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:WpfDINavigation.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="Red">
</Grid>
</UserControl>
📌 사용자 정의 컨트롤 디자인
◾ LoginView.xaml
<UserControl x:Class="WpfDINavigation.Views.LoginView"
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:WpfDINavigation.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="Yellow">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="LoginView"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="35"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Command="{Binding ToSignupCommand}"
Content="ToSignup"
Margin="5"/>
<Button Command="{Binding ToTestCommand}"
Grid.Column="1"
Content="ToTest"
Margin="5"/>
</Grid>
</Grid>
</UserControl>
◾ SignupView.xaml
<UserControl x:Class="WpfDINavigation.Views.SignupView"
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:WpfDINavigation.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="pink">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="SignupView"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="35"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Command="{Binding ToLoginCommand}"
Content="ToLogin"
Margin="5"/>
<Button Command="{Binding ToTestCommand}"
Grid.Column="1"
Content="ToTest"
Margin="5"/>
</Grid>
</Grid>
</UserControl>
◾ TestView.xaml
<UserControl x:Class="WpfDINavigation.Views.TestView"
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:WpfDINavigation.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightSkyBlue">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="TestView"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="35"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Command="{Binding ToSignupCommand}"
Content="ToSignup"
Margin="5"/>
<Button Command="{Binding ToLoginCommand}"
Grid.Column="1"
Content="ToLogin"
Margin="5"/>
</Grid>
</Grid>
</UserControl>
📌 Navigationg
◾ MainNavigationStore 생성
- 네비게이팅을 위해서는 CurrentViewModel을 변경해야한다.
이를 위해 Store 폴더를 추가하여 MainNavigationStore 클래스파일 생성
- MainNavigationStore.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfDINavigation.ViewModels;
namespace WpfDINavigation.Stores
{
public class MainNavigationStore : ViewModelBase
{
//mainview에 속성 추가
private INotifyPropertyChanged? _currentViewModel;
public INotifyPropertyChanged? CurrentViewModel
{
get { return _currentViewModel; }
set {
_currentViewModel = value;
//메인뷰에서 실행시키면 CurrentViewModel를
//NameView에 있는 CurrentViewModel에 할당
CurrentViewModelChanged ?.Invoke();
_currentViewModel = null;
}
}
//대리자 추가
public Action CurrentViewModelChanged { get; set; }
}
}
◾ MainNavigationService 생성
- Services 폴더 생성 후 클래스 NavigationService 및 인터페이스 INavigationService , 클래스 NaviType 파일 생성
- NaviType.cs
namespace WpfDINavigation.Services
{
public enum NaviType
{
LoginView, SignupView, TestView
}
}
-INavigationService.cs
namespace WpfDINavigation.Services
{
public interface INavigationService
{
void Navigate(NaviType naviType)
{
}
}
}
-NavigationService.cs
namespace WpfDINavigation.Services
{
public class NavigationService
{
}
}
이 까지 하면 타입까지 생성 완료한 것임
-NavigationService.cs
namespace WpfDINavigation.Services
{
public class NavigationService : INavigationService
{
//의존성 주입 사용
private readonly MainNavigationStore _mainNavigationStore;
//_mainNavigationStore currentVireModel을 할당하기 위한 속성 추가
private INotifyPropertyChanged CurrentViewModel
{
set => _mainNavigationStore.CurrentViewModel = value;
}
public NavigationService(MainNavigationStore mainNavigationStore)
{
//mainNavigationStore가 생성자의 매개변수로 들어오게 되면
//mainNavigationStore는 객체가 생성되어 넘어오게된다.
//필드로 할당
this._mainNavigationStore = mainNavigationStore;
}
//인터페이스 구현
public void Navigate(NaviType naviType)
{
switch (naviType)
{
case NaviType.LoginView:
//생성자주입
CurrentViewModel = (ViewModelBase)App.Current.Services.GetService(typeof(LoginViewModel))!;
break;
case NaviType.SignupView:
CurrentViewModel = (ViewModelBase)App.Current.Services.GetService(typeof(SignupViewModel))!;
break;
case NaviType.TestView:
CurrentViewModel = (ViewModelBase)App.Current.Services.GetService(typeof(TestViewModel))!;
break;
default: return;
}
}
}
}
-MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private INotifyPropertyChanged? _currentViewModel;
private readonly MainNavigationStore _mainNavigationStore;
private void CurrentViewModelChanged()
{
CurrentViewModel = _mainNavigationStore.CurrentViewModel;
}
public MainViewModel(MainNavigationStore mainNavigationStore)
{
_mainNavigationStore = mainNavigationStore;
_mainNavigationStore.CurrentViewModelChanged += CurrentViewModelChanged;
}
◾ ViewModel에 command 기능 추가
-LoginViewModel.cs
namespace WpfDINavigation.ViewModels
{
public class LoginViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
private void ToSignup(object _)
{
_navigationService.Navigate(NaviType.SignupView);
}
private void ToTest(object _)
{
_navigationService.Navigate(NaviType.TestView);
}
public LoginViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
ToSignupCommand = new RelayCommand<object>(ToSignup);
ToTestCommand = new RelayCommand<object>(ToTest);
}
public ICommand ToSignupCommand { get; set; }
public ICommand ToTestCommand { get; set; }
}
}
-SignupViewModel.cs
namespace WpfDINavigation.ViewModels
{
public class SignupViewModel : ViewModelBase
{
private readonly INavigationService _navigationService;
private void ToLogin(object _)
{
_navigationService.Navigate(NaviType.LoginView);
}
private void ToTest(object _)
{
_navigationService.Navigate(NaviType.TestView);
}
public SignupViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
ToLoginCommand = new RelayCommand<object>(ToLogin);
ToTestCommand = new RelayCommand<object>(ToTest);
}
public ICommand ToLoginCommand { get; set; }
public ICommand ToTestCommand { get; set; }
}
}
◾ Dependency Injection Service 추가
-MainViewModel.cs
namespace WpfDINavigation.ViewModels
{
public class MainViewModel : ViewModelBase
{
private INotifyPropertyChanged? _currentViewModel;
private readonly MainNavigationStore _mainNavigationStore;
private void CurrentViewModelChanged()
{
CurrentViewModel = _mainNavigationStore.CurrentViewModel;
}
public MainViewModel(MainNavigationStore mainNavigationStore)
{
_mainNavigationStore = mainNavigationStore;
_mainNavigationStore.CurrentViewModelChanged += CurrentViewModelChanged;
}
public INotifyPropertyChanged? CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
//_currentViewModel 추가
_currentViewModel = value;
//OnPropertyChanged 호출
OnPropertyChanged();
}
}
}
}
}
-App.xaml.cs
namespace WpfDINavigation
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
//자기 자신을 호출 Current은 타입이 Application이기 때문에 App 타입으로 으로 변환
public new static App Current => (App)Application.Current;
private IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
// Stores
services.AddSingleton<MainNavigationStore>();
// Services
services.AddSingleton<INavigationService, NavigationService>();
/// ViewModels
services.AddSingleton<MainViewModel>();
services.AddSingleton<LoginViewModel>();
services.AddSingleton<SignupViewModel>();
services.AddSingleton<TestViewModel>();
// Views
services.AddSingleton(s => new MainView()
{
DataContext = s.GetRequiredService<MainViewModel>()
});
return services.BuildServiceProvider();
}
public App()
{
Services = ConfigureServices();
var mainView = Services.GetRequiredService<MainView>();
mainView.Show();
}
public IServiceProvider Services { get; }
}
}
-MainViewModel.cs
public MainViewModel(MainNavigationStore mainNavigationStore, INavigationService navigationService)
{
_mainNavigationStore = mainNavigationStore;
_mainNavigationStore.CurrentViewModelChanged += CurrentViewModelChanged;
navigationService.Navigate(NaviType.LoginView);
}
여기까지 한 후 실행 해 보면 LoginView 가 화면에 정상적으로 보인다. ToSignup 을 클릭하면 ToSignup으로 이동 한다 ( ViewModelBase 상속처리 해줘야함)