2Bbear's knowledge workshop

https://github.com/PrismLibrary/Prism-Samples-Wpf/tree/master/12-UsingCompositeCommands

코드는 이쪽에서 참조

https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/12-UsingCompositeCommands

내가 만든 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }
 
        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
            moduleCatalog.AddModule<ModuleA.ModuleAModule>();
        }
 
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>();
        }
    }
cs


기본 시작 윈도우를 MainWindow로 설정했고.


모듈을 사용하니 ConfigureModuleCatalog를 이용하여 모듈 카타로그에 ModuleA의 ModuleAModule을 추가했네요


RegisterTypes으로 컨테이너를 등록하였는데


containerRegistry에 ApplicationCommands를 등록했습니다. 이 부분은 잘 모르겠으니 나중에 자세히 알아보겠습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<Window x:Class="UsingCompositeCommands.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">
 
    <Window.Resources>
        <Style TargetType="TabItem">
            <Setter Property="Header" Value="{Binding DataContext.Title}" />
        </Style>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
 
        <Button Content="Save" Margin="10" Command="{Binding ApplicationCommands.SaveCommand}"/>
 
        <TabControl Grid.Row="1" Margin="10" prism:RegionManager.RegionName="ContentRegion" />
    </Grid>
</Window>
 
cs


일단 MainWindow의 XAML을 확인해보니 ViewModelLocator를 이용하여 ViewModel을 이용 하였음을 알 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Unity Application";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
 
        private IApplicationCommands _applicationCommands;
        public IApplicationCommands ApplicationCommands
        {
            get { return _applicationCommands; }
            set { SetProperty(ref _applicationCommands, value); }
        }
 
        public MainWindowViewModel(IApplicationCommands applicationCommands)
        {
            ApplicationCommands = applicationCommands;
        }
    }
}
cs


MainWindow의 ViewModel을 확인하니 title 프로퍼티가 있고, 


IApplicationCommands 라는 프로퍼티가 생겨있습니다. 


MainWindowViewModel이 생성될때 applicationCommands를 받아와서 저장 하는 용도로 사용하려는 프로퍼티로 보입니다.


====================

다시 Mainwindow의 XMAL로 돌아가서


<TabControl Grid.Row="1" Margin="10" prism:RegionManager.RegionName="ContentRegion" />

탭 컨트롤에 리전이 걸려있는데.

ContentRegion은 MainWindow관련 코드에서는 찾아보질 못했습니다. 그럼 어디에 있는지 찾아보면 App에서 붙인 모듈 ModuleAModule에 정의되어 있을 겁니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 public class ModuleAModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            IRegion region = regionManager.Regions["ContentRegion"];
 
            var tabA = containerProvider.Resolve<TabView>();
            SetTitle(tabA, "Tab A");
            region.Add(tabA);
 
            var tabB = containerProvider.Resolve<TabView>();
            SetTitle(tabB, "Tab B");
            region.Add(tabB);
 
            var tabC = containerProvider.Resolve<TabView>();
            SetTitle(tabC, "Tab C");
            region.Add(tabC);
        }
 
        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            
        }
 
        void SetTitle(TabView tab, string title)
        {
            (tab.DataContext as TabViewModel).Title = title;
        }
    }
cs



OnInitialized , 즉 모듈이 생성될 당시에 컨테이너 제공자로부터 regionManager를 받아옵니다.

해당 region에 ContentRegion이라는 등록명을 넣고

해당 View로 tabA, tabB, tabC 를 넣어놓습니다.


이 tab view들은 하나의 클래스 인데 TabView라는 유저컨트롤로 만들어져있습니다.


SetTitle 이라는 메소드는 Tabview형식을 받아 title을 변경하는 메소드입니다.

내용을 보면 

TabView.DataContext는 TabViewModel 형식 일텐데. 즉 ViewLocator로 연결되어 있는 ViewModel이라는 것입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class TabViewModel : BindableBase
    {
        IApplicationCommands _applicationCommands;
 
        private string _title;
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
 
        private bool _canUpdate = true;
        public bool CanUpdate
        {
            get { return _canUpdate; }
            set { SetProperty(ref _canUpdate, value); }
        }
 
        private string _updatedText;
        public string UpdateText
        {
            get { return _updatedText; }
            set { SetProperty(ref _updatedText, value); }
        }
 
        public DelegateCommand UpdateCommand { get; private set; }
 
        public TabViewModel(IApplicationCommands applicationCommands)
        {
            _applicationCommands = applicationCommands;
 
            UpdateCommand = new DelegateCommand(Update).ObservesCanExecute(() => CanUpdate);
 
            _applicationCommands.SaveCommand.RegisterCommand(UpdateCommand);
        }
 
        private void Update()
        {
            UpdateText = $"Updated: {DateTime.Now}";
        }       
    }
cs



TabViewModel 을 열어보면 내부에 Title이라는 프로퍼티가 만들어져있는 것을 알 수 있습니다.


===========================


그런데, 이 내부 안에 DelegateCommand가 있는게 보입니다.


  public DelegateCommand UpdateCommand { get; private set; }


UpdateCommand 가 어디에 연결되어 있는지 확인하니 Save Button에 연결되어 있음을 확인할 수 있습니다.

이제 TabViewModel로 만든 모든 tab은 버튼 클릭시 이 updataCommand가 실행되게 됩니다.


생성자를 다시 보면 applicationCommadns에 SaveCommand.Registercommand를 통해서 런타임으로 만들어진 DelegateCommand를 등록 할 수 있습니다.

이렇게 등록하게 될 경우 어떤 일이 일어나는 걸까요.

바로 전역으로 ApplicationCommand를 불러서 모든 버튼에 Command를 발생시킬 수 있게 됩니다.

그 코드는 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<Window x:Class="UsingCompositeCommands.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">
 
    <Window.Resources>
        <Style TargetType="TabItem">
            <Setter Property="Header" Value="{Binding DataContext.Title}" />
        </Style>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
 
        <Button Content="Save" Margin="10" Command="{Binding ApplicationCommands.SaveCommand}"/>
 
        <TabControl Grid.Row="1" Margin="10" prism:RegionManager.RegionName="ContentRegion" />
    </Grid>
</Window>
 
cs


MainWindow XAML에서 확인 할 수 있습니다.


최상단에 button이 있는데 Command로 ApplicationComands.SaveCommand를 호출하는 것을 볼 수 있습니다.

즉 응용프로그램 생성 당시 만들어지는 applicationCommand를 MainWindow에 저장하여 해당 윈도우에서 전역 applicationCommand에 접근하여 저장된 모든 Command를 실행시키는 것입니다.



























https://github.com/PrismLibrary/Prism-Samples-Wpf/tree/master/11-UsingDelegateCommands

참고 코드

https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/11-UsingDelegateCommands

내가 만든 코드

기본적인 코드는 비슷하니 패스


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using Prism.Commands;
using Prism.Mvvm;
 
namespace UsingDelegateCommands.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private bool _isEnabled;
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                SetProperty(ref _isEnabled, value);
                ExecuteDelegateCommand.RaiseCanExecuteChanged();
            }
        }
 
        private string _updateText;
        public string UpdateText
        {
            get { return _updateText; }
            set { SetProperty(ref _updateText, value); }
        }
 
 
        public DelegateCommand ExecuteDelegateCommand { get; private set; }
 
        public DelegateCommand<string> ExecuteGenericDelegateCommand { get; private set; }        
 
        public DelegateCommand DelegateCommandObservesProperty { get; private set; }
 
        public DelegateCommand DelegateCommandObservesCanExecute { get; private set; }
 
 
        public MainWindowViewModel()
        {
            Console.WriteLine("JJH: MainWindowViewModel MainWindowViewModel");
            ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
 
            DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);
 
            DelegateCommandObservesCanExecute = new DelegateCommand(Execute).ObservesCanExecute(() => IsEnabled);
 
            ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);
        }
 
        private void Execute()
        {
            Console.WriteLine("JJH: MainWindowViewModel Execute");
            UpdateText = $"Updated: {DateTime.Now}";
        }
 
        private void ExecuteGeneric(string parameter)
        {
            Console.WriteLine("JJH: MainWindowViewModel ExecuteGeneric");
            UpdateText = parameter;
        }
 
        private bool CanExecute()
        {
            Console.WriteLine("JJH: MainWindowViewModel CanExecute");
            return IsEnabled;
        }
    }
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Window x:Class="UsingDelegateCommands.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="Using DelegateCommand" Width="350" Height="275">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <CheckBox IsChecked="{Binding IsEnabled}" Content="Can Execute Command" Margin="10"/>
        <Button Command="{Binding ExecuteDelegateCommand}" Content="DelegateCommand" Margin="10"/>
        <Button Command="{Binding DelegateCommandObservesProperty}" Content="DelegateCommand ObservesProperty" Margin="10"/>
        <Button Command="{Binding DelegateCommandObservesCanExecute}" Content="DelegateCommand ObservesCanExecute" Margin="10"/>
        <Button Command="{Binding ExecuteGenericDelegateCommand}" CommandParameter="Passed Parameter" Content="DelegateCommand Generic" Margin="10"/>
        <TextBlock Text="{Binding UpdateText}" Margin="10" FontSize="22"/>
    </StackPanel>
</Window>
 
cs




MainWindowViewModel 과 MainWindow가 XAML에서 ViewModelLocator로 연결되어 있는 상태이다.


메인윈도우 뷰모델을 확인하면.



IsChecked 컨트롤은 IsEnabled 프로퍼티와 연결되어 있다. 이게 two way인가보다 프로퍼티의 상태를 보면

get과 set이 있는데, get의 경우 단순히 _isEnabled를 반환하고

set의 경우 _isEnabled를 value값으로 초기화하고, ExecuteDelegateCommand.RaiseCanExecuteChanged();를 실행시킨다.



프로그램의 실행 순서로 보았을 때, 생성자가 제일먼저 실행되고 그 뒤에 CanExecute가 실행되는 것을 확인 할 수 있다.



생성자를 살펴보면

1
2
3
4
5
6
7
8
9
10
11
12
public MainWindowViewModel()
        {
            Console.WriteLine("JJH: MainWindowViewModel MainWindowViewModel");
            ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
 
            DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);
 
            DelegateCommandObservesCanExecute = new DelegateCommand(Execute).ObservesCanExecute(() => IsEnabled);
 
            ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);
            Console.WriteLine("JJH: MainWindowViewModel MainWindowViewModel end");
        }
cs


이렇게 구성되어 있는데, 


ExecuteDelegateCommand는 버튼의 DelegateCommand에 붙어있다.


ExecuteDelegateCommand는 이렇게 public DelegateCommand ExecuteDelegateCommand { get; private set; } 프로퍼티로 선언되어 있다.


이 ExecuteDelegateCommand에 들어간 new DelegateCommand(Execute, CanExecute);를 살펴보면


DelegateCommand를 생성하여 담았는데, 첫번째 매개변수에 실행할 함수, 두번째 매개변수에 CanExecute 즉 실행 여부 함수를 담아야 한다.


따라서 DelegateCommand버튼을 누를 경우 실행 순서가 CanExecute ->Execute  순으로 일어나게 될 것이다.




CanExecute가 먼저 실행되고 그 뒤에 Execute가 실행되는 모습을 볼 수 있다.


따라서 DelegateCommand(실행할 메소드, 실행 여부 판단 메소드)로 구성되는 것을 알 수 있었다.


==================================================================


이제 DelegateCommand ObservesProperty에 대해 확인해보면

해당 버튼은 DelegateCommandObservesProperty라는 프로퍼티와 연결되어 있다.

생성자에서  DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled); 이렇게 DelegateCommand가 붙어있는데 기본적인 딜리게이트 커맨드의 생성자는 이해했지만 ObservesProperty가 뭔지 모르겠다.


선언문을 확인해보면

//

        // 요약:

        //     Observes a property that implements INotifyPropertyChanged, and automatically

        //     calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.

        //

        // 매개 변수:

        //   propertyExpression:

        //     The property expression. Example: ObservesProperty(() => PropertyName).

        //

        // 형식 매개 변수:

        //   T:

        //     The object type containing the property specified in the expression.

        //

        // 반환 값:

        //     The current instance of DelegateCommand

        public DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression);


이렇게 되어 있는데


 INotifyPropertyChanged 인터페이스를 구현한 property를 관찰합니다. 그리고 속성 변경 알림에서 자동적으로 DelegateCommandBase.RaiseCanExecutechanged를 호출합니다.  


저 RaiseCanExecuteChanged는 뭘까요?


namespace Prism.Commands

{

    //

    // 요약:

    //     An System.Windows.Input.ICommand whose delegates can be attached for Prism.Commands.DelegateCommandBase.Execute(System.Object)

    //     and Prism.Commands.DelegateCommandBase.CanExecute(System.Object).

    public abstract class DelegateCommandBase : ICommand, IActiveAware

    {

        //

        // 요약:

        //     Creates a new instance of a Prism.Commands.DelegateCommandBase, specifying both

        //     the execute action and the can execute function.

        protected DelegateCommandBase();


        //

        // 요약:

        //     Gets or sets a value indicating whether the object is active.

        public bool IsActive { get; set; }


        //

        // 요약:

        //     Occurs when changes occur that affect whether or not the command should execute.

        public event EventHandler CanExecuteChanged;

        //

        // 요약:

        //     Fired if the Prism.Commands.DelegateCommandBase.IsActive property changes.

        public event EventHandler IsActiveChanged;


        //

        // 요약:

        //     Raises Prism.Commands.DelegateCommandBase.CanExecuteChanged so every command

        //     invoker can requery to check if the command can execute. Note that this will

        //     trigger the execution of Prism.Commands.DelegateCommandBase.CanExecuteChanged

        //     once for each invoker.

        public void RaiseCanExecuteChanged();





DelegateCommand 가 상속한 DelegateCommandBase 안에 있는 RaiseCanExceuteChanged입니다.

요약을 읽어보면

command가 execute 할 수 있을 때, Prism.Commands.DelegateCommandBase.CanExcecutechanged를 올립니다 모든 command 호출자가 확인할 수 있도록 말입니다. 각 호출자에 대해서 한번씩 실행하게 됩니다.


어...음...번역이 매끄럽지 못한데. 이해를 해보자면

ObservesProperty(람다식)을 실행하게 되면 DelegateCommand를 반환합니다. 다만 이때 반환되는 DelegateCommand는 Property가 변경될때 어떠한 메소드가 trigger로 실행되게 됩니다. 그것이 람다식으로 넘긴 함수 ( )=>IsEnabled가 실행되게 됩니다.

즉 여기서의 IsEnabled는 get{return _isEnabled;}가 실행되게 되는 것입니다. 


정확히 이해하자면.

ObservesProperty는 프로퍼티가 변경되어있는지 확인하는 메소드이며 변경되었다면 해당 Command를 실행 할 수 있게합니다.

매개변수로 넘어가는 람다 메소드 즉 프로퍼티 식은, 관찰할 프로퍼티이며 해당 프로퍼티가 바뀔 경우 반응하게 됩니다.

ObservesProperty(변경을 관찰할 프로퍼티)



new DelegateCommand(Execute).ObservesCanExecute(() => IsEnabled); 의 경우

CanExecute가 붙지 않았으므로 프로퍼티가 변경됨에 상관없이 항상 실행이 가능한 상태인데.

여기에 CanExecute를 관찰하는 ObservesCanExecute를 실행함으로써 다른 CanExecute를 이용 할 수 있게 만든 코드입니다.

즉 하나의 DelegateCommand가 굳이 하나의 CanExecute만을 사용하는 것이 아닌 런타임시에 변경 되게 할 수있는 코드입니다.

매개변수로 프로퍼티의 상태를 나타내는 get{}을 넘겼으므로 프로퍼티 상태에 따라서 해당 Command를 사용 할 수 있게 됩니다.



 ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);

의 경우 제네릭을 사용하여 만들어진 Command인데.

이건 진짜 모르겠네.


대충 이해하자면 ExecuteGeneric를 실행시키는데 이 실행여부 함수로 ObservesCanExecute를 이용하여 IsEnabled가 실행 가능하게 true 일경우 이 command를 실행 가능하게 하는 것이고.



private void ExecuteGeneric(string parameter)

        {

            parameter = "what?";

            Console.WriteLine("JJH: MainWindowViewModel ExecuteGeneric");

            UpdateText = parameter;

        }


이렇게 구현되어 있는데. 

이 Execute가 실행되면 화면 하단의 시간이 출력되던 부분에서 waht? 이 출력되게 된다.





9번 안되서

10번으로 넘어감

https://github.com/PrismLibrary/Prism-Samples-Wpf/tree/master/10-CustomRegistrations

코드 참조


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using Prism.Ioc;
using Prism.Mvvm;
using Prism.Unity;
using System.Windows;
using ViewModelLocator.ViewModels;
using ViewModelLocator.Views;
 
namespace ViewModelLocator
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }
 
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            
        }
 
        protected override void ConfigureViewModelLocator()
        {
            base.ConfigureViewModelLocator();
 
            // type / type
            //ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(CustomViewModel));
 
            // type / factory
            //ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), () => Container.Resolve<CustomViewModel>());
 
            // generic factory
            //ViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<CustomViewModel>());
 
            // generic type
            ViewModelLocationProvider.Register<MainWindow, CustomViewModel>();
        }
    }
}
 
cs


기본적인 루틴은 같았고.

configureViewModelLocator만 달랐는데.


확인해보면. 

스태틱하게 싱글톤으로 살아 있는 ViewModellocationProvider의 Register메소드를 이용하여 <View , ViewModel>() 방식으로 붙여놓았다.


잘된다..

지난번 모듈 예제를 그대로 설명하려한다.


https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/HowToPrismModules


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Prism.Ioc;
using Prism.Unity;
using HowToPrismModules.Views;
using Prism.Modularity;
using JJHModule;
namespace HowToPrismModules
{
    /// <summary>
    /// App.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            Console.WriteLine("JJH: App CreateShell");
            return Container.Resolve<MainWindow>();
        }
 
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
           //
           //
        }
 
        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
            var moduleAType = typeof(Class1);
            moduleCatalog.AddModule(new ModuleInfo()
            {
                ModuleName = moduleAType.Name,
                ModuleType = moduleAType.AssemblyQualifiedName,
                InitializationMode = InitializationMode.OnDemand
            });
        }
    }
}
 
cs


시작 윈도우로 Mainwindow를 설정하고 

Bootstrapper에서 초기화를 시도하는 configureModuleCatalog메소드를 오버라이딩 한다.



1
2
3
4
5
6
7
8
9
10
11
<prism:PrismApplication x:Class="HowToPrismModules.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:HowToPrismModules"
                        xmlns:prism="http://prismlibrary.com/"
            >
    <Application.Resources>
         
    </Application.Resources>
</prism:PrismApplication>
 
cs


혹여나 실행이 안되면 xml부분을 잘 확인하자

<prism:PrismApplication 이라는 점과 xmlns:prism이라는 네임스페이스를 받아오는 것에 유의하면 된다.


configureModuleCatalog 메소드에는 다른 모듈의 class1을 참조하기 위하여

이렇게 참조 추가를 해주어야 한다.


특히 만들어지는 JJHModule, 즉 다른 모듈의 경우 클래스라이브러리 형태의 프로젝트로 만들어야지 참조하기 편하다. 원래 클래스 라이브러리가 맞기도 하고...


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Prism.Modularity;
namespace HowToPrismModules.Views
{
    /// <summary>
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MainWindow : Window
    {
        IModuleManager _moduleManager;
        public MainWindow(IModuleManager moduleManager)
        {
            InitializeComponent();
            _moduleManager = moduleManager;
        }
 
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            _moduleManager.LoadModule("Class1");
        }
    }
}
 
cs





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Window x:Class="HowToPrismModules.Views.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:HowToPrismModules.Views"
        xmlns:prism="http://prismlibrary.com/"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Click="Button_Click" Content="Button" HorizontalAlignment="Left" Height="96" Margin="248,109,0,0" VerticalAlignment="Top" Width="157"/>
        <ContentControl prism:RegionManager.RegionName="ContentRegion"/>
    </Grid>
</Window>
 
cs

이제 메인윈도우에서 해당 View를 가져와야하니 클래스를 이렇게 선언해주고



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
namespace JJHModule
{
    public class Class1 : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<RegionManager>();
            regionManager.RegisterViewWithRegion("ContentRegion",typeof(Views.ViewA));
 
        }
 
        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            
        }
    }
}
 
cs

모듈 쪽의 클래스는 이렇게 만들어 놓아야 한다.

간단하게 OnInitialized라는 메소드가 하는 일은 단순히 RegionManager에 자신의 View를 추가하는 일밖에 하지 않는다.


이후 MainWindow에서는 이 RegionManger를 호출하여 등록명을 불르면 해당 View가 출현되는 방식이다.

코드는 여기를 참조

https://github.com/PrismLibrary/Prism-Samples-Wpf


여기서 07- Modules-LoadManual


나는 이 코드를 실행 하고 싶어도 실행이 안된다. 이유가 뭔지는 모름 ㅠㅠ


https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/HowToPrismModules

일단 성공한 프로젝트

아무튼 분석을 하자면.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }
 
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            
        }
 
        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
            var moduleAType = typeof(ModuleAModule);
            moduleCatalog.AddModule(new ModuleInfo()
            {
                ModuleName = moduleAType.Name,
                ModuleType = moduleAType.AssemblyQualifiedName,
                InitializationMode = InitializationMode.OnDemand
            });
        }
    }
cs


app에서 Mainwindow를 시작 윈도우로 설정하였다.

추가적으로 CinfigureModuleCatalog메소드가 보이는데 이 메소드는 응용프로그램 시작 후 Booststrapper에서 실행하게 되는 메소드이다.

내용을 보자면 자동적으로 만들어진 moduleCatalog를 매개변수로 받아

ModuleModule이라는 괴상한 이름의 클래스 타입을 모듈로 추가하게된다.


이때 추가하는 형태가 ModuleInfo라는 형태인데 ModuleInfo 안에는 ModuleName, Moduletype, InitializationMode 등을 설정할 수 있게 되어 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public partial class MainWindow : Window
    {
        IModuleManager _moduleManager;
 
        public MainWindow(IModuleManager moduleManager)
        {
            InitializeComponent();
            _moduleManager = moduleManager;
        }
 
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            _moduleManager.LoadModule("ModuleAModule");
        }
    }
cs



Mainwindow으로 가게되면 moduleManager를 받아오고

클릭할 경우 모듈 메니저에서 LoadModule이라는 메소드를 실행시킨다. 이때 넣는 매개변수가 모듈 이름이 된다 


그럼 도대체 이 모듈모듈ModuleModule은 어디서 가저오는 것일까?


ModulA라는 네임스페이스 안에 ModuleModue이 존재하고있다.


이런 변태 같은.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using ModuleA.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
 
namespace ModuleA
{
    public class ModuleAModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            regionManager.RegisterViewWithRegion("ContentRegion"typeof(ViewA));
        }
 
        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            
        }
    }
}
cs


IModule을 상속받아 구현된 OnInitialized는 매개변수로 컨테이너 제공자라는 매개변수를 받아오는데, 이 컨테이너제공자는 해당 모듈을 실행시킬 때 모듈을 실행 시키는 주체 모듈로부터 받아와진다.


따라서 받은 컨테이너 제공자에서 IRegionManager를 호출하면 해당 RegionManager안에서 ContentRegion이라는 등록명에 ModuleModule의 ViewA를 등록하면 

연결이 완료된다.

전 포스팅에서 다뤘듯이 RegionManager를 통해 등록명으로 접근 view를 출력 시킬 수 있다는 것을 알 수 있었다.

그러면 응용으로 런타임시에 view를 바꿀 수 있지 않을까?


https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/06-ViewActivationDeactivation

코드 참조는 이곳에서 하면 된다


1
2
3
4
5
6
7
8
9
10
11
12
public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }
 
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
 
        }
    }
cs


app에서 Mainwindow를 실행시키게 해놓았다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public partial class MainWindow : Window
    {
        IContainerExtension _container;
        IRegionManager _regionManager;
        IRegion _region;
 
        ViewA _viewA;
        ViewB _viewB;
 
        public MainWindow(IContainerExtension container, IRegionManager regionManager)
        {
            InitializeComponent();
            _container = container;
            _regionManager = regionManager;
 
            this.Loaded += MainWindow_Loaded;
        }
 
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            _viewA = _container.Resolve<ViewA>();
            _viewB = _container.Resolve<ViewB>();
 
            _region = _regionManager.Regions["ContentRegion"];
 
            _region.Add(_viewA);
            _region.Add(_viewB);
        }
 
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //activate view a
            _region.Activate(_viewA);
        }
 
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            //deactivate view a
            _region.Deactivate(_viewA);
        }
 
        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            //activate view b
            _region.Activate(_viewB);
        }
 
        private void Button_Click_3(object sender, RoutedEventArgs e)
        {
            //deactivate view b
            _region.Deactivate(_viewB);
        }
    }
cs


Mainwindow를 확인하면


생성자로 받을 컨테이너와 리전매니저를 저장할 멤버변수가 보이고

Loaded 이벤트에 Mainwindow_Loaded 메소드가 붙어있는 것을 확인 할 수 있다.


Loaded 이벤트는 윈도우가 로드 될때 실행되는 이벤트로 그러한 의미에서 

MainWindow_Loaded는 윈도우가 실행될때 실행되게 되는 메소드다. 왜 굳이 loaded에 놓았을까?...


Loaded 메소드는 생성자의 InitializeComponent에서 Loaded를 실행시킨다.. 아니면 누가 알주세요.

따라서 InitialzeComponent가 된 후에 Loaded를 한다는 것은....xaml이 실행되기 전에 load를 연결시키겠다는 의미로 보인다.


이후 Mainwindow_Loaded 메소드를 확인하면 배주에 regionManager에서 region을 호출하여 _region에 담는다.

그렇기에 구조가

_regionManager

 -> region["ContentRegion"] = _region 

    -> _viewA

    -> _viewB


이런 식으로 구조가 만들어지게 된다.


_region을 포인터용으로 사용하여 만들어진 region["ContentRegion"] 에 접근하여 view를 추가하는 행위이다.


두개 이상의 view가 하나의 region에 연결되어 있는 경우 기본적으로 제일먼저 붙은 view가 실행되겠지만 

region.Activate로 출현할 view를 선택할 수 있다.


아무래도 구조가

_regionManger

  ->region["ContentRegion"] = _region

    ->_viewA , true

    ->_viewB , false


이런 형태로 되어 있는 것 같고, 

_region.Activate(view)를 이용하여 해당 region["ContentRegion"] 에서 value값으로 view와 같은 녀석을 검색하고 

찾았다면 해당 view의 bool 값을 true로 바꾸고 나머지를 false로 바꾸어 출력 가능 대상을 바꾸는 것 같다.










기본적인 WPF의 경우


window에서 다른 usercontrol을 출현시키기 위해서는 해당 usercontrol을 알고 있어야 했다.

그것을 prism에서 어떻게 분리하였는지 확인해본다.


https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/05-ViewInjection

참고는 이곳에서 하면 된다



1
2
3
4
5
6
7
8
9
10
11
12
 public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }
 
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
 
        }
    }
cs


app에서 시작 윈도우로 MainWindow를 지정해 놓았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 public partial class MainWindow : Window
    {
        IContainerExtension _container;
        IRegionManager _regionManager;
 
        public MainWindow(IContainerExtension container, IRegionManager regionManager)
        {
            InitializeComponent();
            _container = container;
            _regionManager = regionManager;
        }
 
        /*
         어차피 regionmanager에 등록하는 과정이다. 바로 registerviewwithregion으로 하나
         분리하여 등록 명 따로 붙일 view따로 하나 하는 행동은 같은 것이다.
             */
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var view = _container.Resolve<ViewA>();
            _regionManager.RegisterViewWithRegion("ContentRegion"typeof(ViewA));
            //IRegion region = _regionManager.Regions["ContentRegion"];
            //region.Add(view);
        }
    }
cs

Main윈도우를 확인하면 IContainerExtension 과 IregionManager가 멤버 변수로 되어 있는데 이는 생성자에서 받을 수 있는 현재 retion manager와 container를 저장하고 있기 위함이다.


이후 Button_Click메소드가 


1
2
3
4
5
6
7
8
9
10
11
<Window x:Class="ViewInjection.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/"
        Title="Shell" Height="350" Width="525">
    <DockPanel LastChildFill="True">
        <Button DockPanel.Dock="Top" Click="Button_Click">Add View</Button>
        <ContentControl prism:RegionManager.RegionName="ContentRegion" />
    </DockPanel>
</Window>
 
cs



mainwindow xaml에서 이렇게 붙어있게된다.


이후 컴파일시에는 ContentRegion이라는 것이 무엇인지 몰라 출력하지 않고 있다가

런타임에서 버튼 클릭시 해당 view를 regionmanager에 등록시키는 과정을 진행하여 런타임으로 붙을 수 있게 한다.






















이 내용은 ViewDiscovery에 대한 내용이다.

https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/04-ViewDiscovery

참조는 04번 항목을 참조하면 된다.



1
2
3
4
5
6
7
8
9
10
11
12
   public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }
 
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
 
        }
    }
cs


app 부분을 보면 시작 화면으로 MainWindow로 해놓았다.


1
2
3
4
5
6
7
8
9
  public partial class MainWindow : Window
    {
        public MainWindow(IRegionManager regionManager)
        {
            InitializeComponent();
            //view discovery
            regionManager.RegisterViewWithRegion("ContentRegion"typeof(ViewA));
regionManager.RegisterViewWithRegion("ContentRegion_Main",typeof(MyView));
        }
    }
cs


MainWindow 생성자를 확인하면 매개변수로 IRegionManager를 받고있다.

이 RegionManager로 모든 View를 관리 할 수 있게 된다.


여기서 regionManger는 응용프로그램 생성 초기 단계에서 만들어진 객체이다.


이제 이 regionManger에 View를 등록해야한다. (등록 이름, 등록할 타입view)

이후


xaml에서는 이 등록 이름으로 각 view를 호출 할 수 있게 된다.

1
2
3
4
5
6
7
8
9
10
11
<Window x:Class="ViewDiscovery.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/"
        Title="Shell" Height="350" Width="525">
    <Grid>
        <ContentControl prism:RegionManager.RegionName="ContentRegion" />
        <ContentControl prism:RegionManager.RegionName="ContentRegion_Main" />
    </Grid>
</Window>
 
cs




이런 식으로 만들어 View가 직접적으로 View를 참조하지 못하게만들어 느슨하게 개발 할 수 있게되는 것이다... 호오.