2Bbear's knowledge workshop

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

확인

Mvvm이란 무엇인가

Code/C#2019. 2. 28. 10:12


(출처 : https://happybono.wordpress.com/2017/10/10/mvvm-i-mvvm-%ED%8C%A8%ED%84%B4%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90/)



- 설명 -

Mvvm이란 Model, View, ViewModel을 줄인 약자로써 MVC 패턴에서 단점으로 여겨지는 View와 Model의 의존성을 최소화 하기 위해 사용되어진다. 

간단히 말하자면 화면에 보이는 View 와 프로그래밍적 로직을 담고 있는 Model을 서로 분리해서, Model을 기준으로 다른 View로 표현하게 할 수 있고, View를 기준으로 다른 데이터를 표현하기 위해 Model을 바꾸는 것이 쉬운 구조로 만드는 것이 주 목적이다.


- 주요 기능-

이러한 Mvvm을 구현하기 위해서 만들어진것이. Command 기능과 데이터 바인딩이라는 기법이다.


Command는 View에서 UI 이벤트를 처리하기 위해서 만들어졌다.

Command를 이용하여 View에서는 Model이 변경될 때 UI에 붙은 이벤트 이름을 변경할 필요가 없어졌다.


데이터 바인딩은 Model에서 만들어진 Data를 View에 표현하기 위해서 만들어졌다.

데이터 바인딩을 이용하여 Model에서 다른 View로 데이터를 표현할때 View의 UI를 호출하지 않고 데이터를 출력 할 수 있게 되었다.



- 구성 -



기본적으로 UI가 나오는 프로그램의 기본 요소는

자료를 저장할 데이터 ,

데이터를 처리하는 로직 ,

데이터를 표현하는 UI 

이렇게 3가지 정도로 나눌 수 있다.


여기에 Mvvm을 하기 위해 Property - 데이터 바인딩용 과 Delegate-Command용이 추가된다.


이러한 요소들을 Model, ViewModel, View로 나누어 넣으면


Model

- 데이터를 처리하는 메소드


ViewModel

- 데이터를 저장하고 있는 프로퍼티

- 데이터를 저장하기 위한 공간

- 이벤트 처리를 위한 Delegate


View

- Command가 붙은 UI


이렇게 구성이 된다.



- 도움이 되는 프레임워크 또는 라이브러리 -

WPF 

 - DevExpress

 - Prism


- 도움이 되는 예제 -

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

Comment +0

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

확인

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

확인

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을 통해 페이지를 호출하면 끝난다.


Comment +0

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

확인

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를 실행시키는 것입니다.



























Comment +0

정보 참고는 

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가 실행하게 되는 구조입니다.



Comment +0