2Bbear's knowledge workshop

https://github.com/2Bbear/JavaProgramDevelop/tree/master/WebViewBrowserTester_72.0.3626.105

내가 만든 코드



https://developer.android.com/reference/android/webkit/WebView


기본적인 코드는 


WebView 

L WebChromeClient

L WebViewClient 


 이 세가지를 잘 생성하고 이후 Setting을 잘 해주면 끝이다.


WebView의 경우 . setWebChromeClient 로 WebVhreomClient를 생성해서 추가해주면 끝이고


이후 WebView의 세팅을 하기 위해서 .getSettings를 통해 다양한 부분을 세팅해주는 것으로 WebView를 세팅 해줄 수 있다.


1
2
3
4
5
6
7
wv.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
 
            }
        });
cs


WebView setting이 끝나면 WebViewClient를 넣어주면 된다.


WEbViewClient는 App에서 실행되는 WebView의 메세지를 처리하는 것을 담당한다.

즉 화면을 터치하거나, 끝나거나, 바뀌거나 시작되거나 할 경우 이 클래스로 메세지가 와서 메소드를 실행시키니, 나중에 API를 보면서 발동하는 각 메소드를 이용하여 App Client 부분을 처리하면 된다.



이 모든 것이 완료되면 WebView.loadUrl을 통해 페이지를 호출하면 끝난다.


보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

확인

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://illef.tistory.com/entry/WPF-Command-Part4-DelegateCommand

http://www.sysnet.pe.kr/2/0/10917

이곳에서 했습니다.


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
69
70
71
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
 
namespace HowToDelegateCommand
{
    class DelegateCommand<T> : ICommand
    {
        Action<T> executeTargets = delegate { };
        Func<bool> canExecuteTargets = delegate { return false; };
        bool m_Enabled = false;
 
        public bool CanExecute(object parameter)
        {
            Delegate[] targets = canExecuteTargets.GetInvocationList();
            foreach(Func<bool> target in targets)
            {
                m_Enabled = false;
                bool localenable = target.Invoke();
                if(localenable)
                {
                    m_Enabled = true;
                    break;
                }
            }
            return m_Enabled;
        }
        public void Execute(object parameter)
        {
            if (CanExecute(parameter))
            {
                executeTargets(parameter !=null?(T)parameter: default(T));
            }
        }
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
        public event Action<T> ExecuteTargets
        {
            add
            {
                executeTargets += value;
            }
            remove
            {
                executeTargets -= value;
            }
        }
        public event Func<bool> CanExecuteTargets
        {
            add
            {
                canExecuteTargets += value;
            }
            remove
            {
                canExecuteTargets -= value;
            }
        }
        
       
 
       
    }
}
 
cs



Generic을 이용한 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#region 어셈블리 System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.dll
#endregion
 
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Markup;
 
namespace System.Windows.Input
{
    //
    // 요약:
    //     명령을 정의합니다.
    [TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
    [TypeForwardedFrom("PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
    [ValueSerializer("System.Windows.Input.CommandValueSerializer, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
    public interface ICommand
    {
        //
        // 요약:
        //     명령을 실행해야 하는지 여부에 영향을 주는 변경이 발생할 때 발생합니다.
        event EventHandler CanExecuteChanged;
 
        //
        // 요약:
        //     명령을 현재 상태에서 실행할 수 있는지를 결정하는 메서드를 정의합니다.
        //
        // 매개 변수:
        //   parameter:
        //     명령에 사용된 데이터입니다. 명령에서 데이터를 전달할 필요가 없으면 이 개체를 null로 설정할 수 있습니다.
        //
        // 반환 값:
        //     이 명령을 실행할 수 있으면 true이고, 그러지 않으면 false입니다.
        bool CanExecute(object parameter);
        //
        // 요약:
        //     명령이 호출될 때 호출될 메서드를 정의합니다.
        //
        // 매개 변수:
        //   parameter:
        //     명령에 사용된 데이터입니다. 명령에서 데이터를 전달할 필요가 없으면 이 개체를 null로 설정할 수 있습니다.
        void Execute(object parameter);
    }
}
cs


위 코드에서 사용한 ICommand는 이렇게 구성이 되어 있습니다.


CanExecute의 경우 실행이 가능하다면 return을 하는 형식으로 구현 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RelayCommand : ICommand
{
    public event EventHandler CanExecuteChanged;
 
    public bool CanExecute(object parameter)
    {
        return DateTime.Now.Hour != 9;
    }
 
    public void Execute(object parameter)
    {
        Console.WriteLine(DateTime.Now + ": RelayCommand.Execute - " + parameter);
    }
}

cs


이렇게 말입니다.

위 코드는 현재 시간이 9시가 아닐 경우 return을 하는 형태입니다.

즉 9시가 아닌 경우에만 동작을 하도록 하고 싶을 경우 이렇게 작성하게 됩니다.


1
2
3
4
if (Command.CanExecute(null== true)
            {
                Command.Execute(txt);
            }
cs

그리고 위 커맨드를 다른 곳에서 호출 할때 이런 식으로 호출하게 됩니다.

즉 CanExecute가 반환된 값이 있을때 Command.Excute를 한다고 말이죠.


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

그러한 맥락으로 보았을 때 DelegateCommand는

어디선가 호출자가 Command를 호출할때, 즉 어떤 호출자가 CanCommand를 실행하였을 때,

해당 호출자가 CanCommand가 갖고 있는 Target들, 즉 호출을 할 수 있는 호출자 목록 안에 있을 때 해당 호출자에 맞추어 Execute를 실행하게 해주는 코드를 갖고있습니다.


따라서 CanCommand 에는 foreach 문으로 자신에게 할당 되어 있는 호출자들을 매번 검색해야합니다.


순서는 execute -> CanExecute의 순으로서. CanExecute에서 "이 호출자는 실행해도 되는 호출자야"라고 허락을 해야 execute가 실행하게 되는 구조입니다.



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/PrismLibrary/Prism-Samples-Wpf/tree/master/08-ViewModelLocator/ViewModelLocator

코드 참조


https://github.com/2Bbear/WindowsProgrmaDevelop/tree/master/WPF/UsingMvvmPrismLibrary/08-ViewModelLocator

내가 만든 코드참조


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using Prism.Ioc;
using Prism.Unity;
using System.Windows;
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)
        {
 
        }
    }
}
 
cs


시작 윈도우는 역시 Mainwindow


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using Prism.Mvvm;
 
namespace ViewModelLocator.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Unity Application";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
 
        public MainWindowViewModel()
        {
 
        }
    }
}
 
cs


ViewModelLocator라는 폴더 안에 MainWindowViewModel이라는 파일을 보니.

BindableBase라는 상속을 받은 형태의 클래스가 존재하고 있었다.


단순히 프로퍼티가 존재 할 뿐이고, 특이하게 set에 SetProperty라는 함수가 있다

SetProperty메소드는 bindalbleBase의 메소드로. ref 형태의 매개변수로 들어가는 값을 참조자를 넘겨주고 있다. 즉 실제값을 넘기고 있다는 말이다.


1
2
3
4
5
6
7
8
9
10
11
<Window x:Class="ViewModelLocator.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>
 
cs


MainWindow의 XAML은 title에 바인딩이 걸려잇는데. 

하단의 ContentControl은 딱히 불러오는 RegionManager에서 ContentRegion이라는 등록명으로 등록한 View가 없어 출력이 되지 않을 것이다.


실행을 시키면


이렇게 실행 제목창에 만든 MainwindowViewModel의 title변수에 있는 값이 나타나게 된다.


이렇게 바인딩이 가능하게 되는 코드는


XAML에 있는 prism:ViewModelLocator.AutoWireViewModel="True" 이 있기 때문이다.

이 말은 prism 라이브러리 안에 있는 ViewModellocator의 AutoWireViewModel의 값을 True로 변경하라는 의미이다.


그럼 어떻게 Prism은 View와 ViewModel을 연관지었을까?

예상은 아무래도 이름의 형식에 맞추어서 찾은 것으로 생각된다. 간단하게 새로운 윈도우를 만들어 시험해보자


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
using System.Windows;
 
 
namespace ViewModelLocator.Views
{
    /// <summary>
    /// MyWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class MyWindow : Window
    {
        public MyWindow()
        {
            InitializeComponent();
        }
    }
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Window x:Class="ViewModelLocator.Views.MyWindow"
        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:ViewModelLocator.Views"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
      
        Title="{Binding Title}" Height="350" Width="525">
    <Grid>
        
    </Grid>
</Window>
 
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using Prism.Ioc;
using Prism.Unity;
using System.Windows;
using ViewModelLocator.Views;
 
namespace ViewModelLocator
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MyWindow>();
        }
 
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
 
        }
    }
}
 
cs



새롭게 MyWindow라는 것을 추가하고 


MyWindowViewModel 이라는 것을 만들어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
using Prism.Mvvm;
namespace ViewModelLocator.ViewModels
{
    class MyWindowViewModel: BindableBase
    {
        private string _title = "Prism Unity Applicationddd";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
 
        public MyWindowViewModel()
        {
 
        }
    }
}
 
cs



다르게 출력할 수 있게 만들어놓았다.




실행 해보면



간단하게 값이 바뀐 것을 확인 할 수 있다.


이로서 ViewLocator를 이용하여 Prism은 이름만으로 뷰와 뷰 모델을 연관지을 수 있다는 것을 알았다.





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


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가 출현되는 방식이다.