티스토리 뷰

반응형

2022.11.30 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part6 - TabControl Region Navigation

2022.11.25 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part5 - Region & ContentControl Region Navigation

2022.11.18 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part4 - Register Types

2022.11.15 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part3 - DelegateCommand

2022.10.28 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part2 - 프로젝트 구성 살펴 보기

2022.10.27 - [WPF .NET] - Prism Library를 사용하는 개발자를 위한 안내 Part1

Prism을 사용하는 프로젝트와 이전에 Mvvm Toolkit을 이용해서 생성했던 CustomControlSample 프로젝트와 비교를 하도록 하겠습니다.

1. App.Xaml

CustomControlSample - App.xaml

<Application
    x:Class="CustomControlSample.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CustomControlSample">
    <Application.Resources />
</Application>

PrismStep1 - App.xaml

PrismApplication은 Application을 상속받은 녀석으로 내부에서 여러가지 일들을 처리하고 있습니다.

<prism:PrismApplication x:Class="PrismStep1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:PrismStep1"
             xmlns:prism="http://prismlibrary.com/" >
    <Application.Resources>
         
    </Application.Resources>
</prism:PrismApplication>

2. App.xaml.cs

CustomControlSample  - App.xaml.cs

  • App 생성자에서 ConfigureServices를 호출해서, 앱에서 사용할 Type을 등록합니다.
  • OnStartup 메서드에서 MainWindow를 컨테이너에서 인스턴스해서 가져오고 화면에 출력합니다.

 

namespace CustomControlSample
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public App()
        {
            Services = ConfigureServices();
            //this.InitializeComponent();
        }

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            var mainWindow = Services.GetService<MainWindow>();
            if(mainWindow != null)
            {
                mainWindow.Show();
            }
            else
            {
                Shutdown();
            }
        }

        /// <summary>
        /// Gets the current <see cref="App"/> instance in use
        /// </summary>
        public new static App Current => (App)Application.Current;

        /// <summary>
        /// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
        /// </summary>
        public IServiceProvider Services { get; }

        /// <summary>
        /// Configures the services for the application.
        /// </summary>
        private static IServiceProvider ConfigureServices()
        {
            var services = new ServiceCollection();

            services.AddSingleton(typeof(MainWindow));
            services.AddSingleton(typeof(MainWindowViewModel));

            services.AddTransient(typeof(UserConsent));
            services.AddTransient(typeof(UserConsentViewModel));

            services.AddTransient(typeof(CustomUserConsent));

            return services.BuildServiceProvider();
        }
    }
}

PrismStep1 - App.xaml.cs

  • 여러개의 인스턴스를 생성하는 Type은 containerRegistry에 등록할 필요가 없습니다. 아래 코드에서 MainWindow를 등록하지 않은 이유는 호출 할 때마다 새로운 MainWindow가 생성되서 반환되는 기본 유형이기 때문입니다.
  • CreateShell() 메서드가 거의 OnStartup 메서드와 유사한 기능을 한다고 보시면 될 것 같습니다.
namespace PrismStep1
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {

        }
    }
}

위의 코드에 간단하게 몇가지 테스트 코드를 추가하겠습니다.

protected override Window CreateShell()
{
    var win1 = Container.Resolve<MainWindow>();
    var win2 = Container.Resolve<MainWindow>();
    if(win1.Equals(win2))
    {
        Debug.WriteLine("Equals");
    }
    else
    {
        Debug.WriteLine("Not Equals");
    }
    return Container.Resolve<MainWindow>();
}

win1과 win2는 서로 다른 객체이고 return Container.Resolve<MainWindow>()를 통해서 반환되는 MainWindow도 새로운 객체입니다.

그렇다면, 단 1개의 MainWindow만 생성하고 사용하려면 어떻게 해야할까요?

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterSingleton<MainWindow>();
}

containerRegistry를 이용해서 MainWindow는 Singleton 객체만을 사용한다고 선언해 주면 됩니다.

PrismStep1 프로젝트를 다시 실행하면, Equals라는 문자열을 출력합니다.

이 부분을 CustomControlSample의 ConfigureServices의 내용들과 비교를 하면 아래와 같습니다.

//싱글톤으로 사용하겠다고 등록
services.AddSingleton(typeof(MainWindow));
//여러개의 객체를 생성해서 반환하기 위해 등록
services.AddTransient(typeof(UserConsent));

3. MainWindow.xaml

CustomControlSample - MainWindow.xaml

<Window
    x:Class="CustomControlSample.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:local="clr-namespace:CustomControlSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Window.Resources />
    <Grid>
      //컨텐츠 영역
    </Grid>
</Window>

PrismStep1 - MainWindow.xaml

  • prism:ViewModelLocator.AutoWireViewModel="True" : 뷰모델 자동연결 기능을 사용한다는 것입니다.
  • prism:RegionManager.RegionName="ContentRegion" : 프리즘에는 Region이라는 기능이 있는데, ContentControl의 Content 영역을 RegionName으로 선언해 놓으면, 그 곳의 컨텐츠를 RegionNavigate를 이용해서 변경할 수 있습니다.
<Window x:Class="PrismStep1.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="350" Width="525" >
    <Grid>
        <ContentControl prism:RegionManager.RegionName="ContentRegion" />
    </Grid>
</Window>

4. MainWindowViewModel을 연결하는 방식의 차이점

CustomControlSample - MainWindow.xaml.cs

  • MainWindow를 Resolve할 때 MainWindow(MainWindowViewModel viewModel) 생성자를 통해서 주입(Injection)되고, 그 ViewModel을 DataContext에 넣어주는 구조입니다.
  • 각 화면마다 연결할 ViewModel을 수동으로 연결해야 합니다.
namespace CustomControlSample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public MainWindow(MainWindowViewModel viewModel) : this()
        {
            ViewModel = viewModel;
        }

        public MainWindowViewModel ViewModel
        {
            //프로젝트 속성이 null 허용이 아니라 이렇게 처리했습니다
            get => (MainWindowViewModel)DataContext;
            set => DataContext = value;
        }
    }
}

PrismStep1 - MainWindow.xaml.cs

코드 비하인드에는 코드가 없습니다.

namespace PrismStep1.Views
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}
  • 뷰와 뷰모델을 연결하는 방식은 클래스 이름을 통해서 강제로 연결해주는 방식입니다.
  • 기본적으로 Views 폴더에 들어가있는 뷰 이름뒤에 ViewModels 폴더에 뷰모델이 있고, 뷰이름 + ViewModel로 구성되는 것으로 설정되어 있습니다.
  • 그래서 Views/MainWindow.xaml 의 뷰모델은 ViewModels/MainWindowViewModel.cs를 사용한다고 정의 되어 있어서, 새로운 뷰를 만들고, 뷰모델을 만들 때 폴더 위치와 이름만 맞추면 자동으로 연결됩니다.

5. 프리즘에서 뷰와 뷰모델 이름 연결 방법을 변경하려면?

App.xaml.cs에 ConfigureViewModelLocator 메소드를 override해서 변경할 수 있습니다.

아래 예제는 기존 Views, ViewModels 폴더에 추가로 Controls와 ControlViewModel도 추가할려고 만들었습니다. 

이 코드는 소스에 포함하지 않습니다.

/// <summary>
/// 뷰모델 로케이터 이름 설정
/// </summary>
protected override void ConfigureViewModelLocator()
{
    base.ConfigureViewModelLocator();

    Prism.Mvvm.ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
    {
        string viewName = viewType.FullName;
        if (viewName == null)
        {
            return null;
        }

        if (viewName.EndsWith("View"))
        {
            viewName = viewName.Substring(0, viewName.Length - 4);
        }

        viewName = viewName.Replace(".Views.", ".ViewModels.");
        //viewName = viewName.Replace(".Controls.", ".ControlViewModels.");
        string viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
        string viewModelName = $"{viewName}ViewModel, {viewAssemblyName}";
        return Type.GetType(viewModelName);
    });
}

6. 소스

WpfTest/PrismStep1 at master · kaki104/WpfTest (github.com)

 

GitHub - kaki104/WpfTest

Contribute to kaki104/WpfTest development by creating an account on GitHub.

github.com

 

반응형
댓글