티스토리 뷰

반응형

2022.09.06 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내v1.0 part9-2 StyleSelector

2022.08.31 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part9-1 DataTemplateSelector

2022.08.08 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part8-3 Template

2022.08.02 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part8-2 Template

2022.07.21 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part8-1 Template

2022.07.13 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part7 Behavior

2022.07.05 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part6 Command

2022.06.27 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part5 Converter

2022.06.15 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part4-2 Data Binding

2022.06.13 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part4-1 Data Binding

2022.06.07 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part3-3

2022.06.02 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part3-2

2022.05.30 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 V1.0 part3-1

2022.05.11 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part2

2022.05.09 - [WPF .NET] - MVVM Pattern을 사용하는 개발자를 위한 안내 v1.0 part1

 

이벤트 드리븐 방식과 MVVM을 사용하는 방식을 서로 비교해 보는 포스팅입니다. 

 

Event는 CrudSample.Event 프로젝트를 이야기 합니다.

Mvvm은 CrudSample.Mvvm 프로젝트를 이야기 합니다.

 

1. 준비

이벤트 드리븐을 이용한 개발에서는 준비할 내용은 없습니다.

MVVM Pattern을 이용하기위해서는 Nuget package를 설치하고, Dependency Injection을 위한 IoC Conatiner를 준비해야합니다.

준비하는 방법에 대한 자세한 사항은 여기를 참고하시기 바랍니다.

Mvvm -> App.xaml.cs

    public partial class App : Application
    {
        public App()
        {
            Services = ConfigureServices();
            this.InitializeComponent();
        }

        /// <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();

            //ViewModel 등록
            services.AddTransient(typeof(MainViewModel));

            return services.BuildServiceProvider();
        }
    }

Mvvm -> MainWindow.xaml.cs

코드 비하인드에 비지니스 로직에 관련된 코딩은 하지 않습니다. 다만, 여기서는 ViewModel을 View와 연결시키기 위해서 한줄 사용합니다. 

 

ViewModel을 View와 연결시키는 여러가지 방법이 있는데, Microsoft.Toolkit.Mvvm을 사용할 때는 이 방법을 사용하고 있습니다.

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = App.Current.Services.GetService(typeof(MainViewModel));
        }
    }

2. 기본 데이터 출력

레이아웃은 제외하고 DataGrid만 포커스 합니다.

Event - MainWindow.xaml

  • _member는 public property로 만들 이유가 없기 때문에 private 필드로 사용합니다.
  • 이벤트 방식에서는 _member 목록에 새로운 Member를 추가한 후,
  • MemberDataGrid.ItemsSource = _members;
  • 코드를 사용해서 직접 ItemsSource에 _members를 연결해서 처리합니다.
        <DataGrid
            x:Name="MemberDataGrid"
            Grid.Row="1"
            Margin="0,10,0,0"
            AutoGenerateColumns="False"
            IsReadOnly="True">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name, Mode=TwoWay}" Header="Name" />
                <DataGridTextColumn Binding="{Binding Phone, Mode=TwoWay}" Header="Phone" />
                <DataGridTextColumn Binding="{Binding RegDate, StringFormat={}{0:d}}" Header="Regested Date" />
                <DataGridCheckBoxColumn Binding="{Binding IsUse, Mode=TwoWay}" Header="Use" />
            </DataGrid.Columns>
        </DataGrid>

Event - MainWindow.xaml.cs

        private IList<Member> _members = new ObservableCollection<Member>();

        private void Init()
        {
            _members.Add(new Member 
            {
                Id = 1,
                Name = "kaki104",
                Phone = "010-1111-2222",
                RegDate = DateTime.Now,
                IsUse = true,
            });
            MemberDataGrid.ItemsSource = _members;
        }

Mvvm - MainWindow.xaml

MainViewModel.cs에 있는 Members property를 바인딩하도록 {Binding Members} 를 사용합니다.

        <DataGrid
            x:Name="MemberDataGrid"
            Grid.Row="1"
            Margin="0,10,0,0"
            AutoGenerateColumns="False"
            IsReadOnly="True"
            ItemsSource="{Binding Members}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Name, Mode=TwoWay}" Header="Name" />
                <DataGridTextColumn Binding="{Binding Phone, Mode=TwoWay}" Header="Phone" />
                <DataGridTextColumn Binding="{Binding RegDate, StringFormat={}{0:d}}" Header="Regested Date" />
                <DataGridCheckBoxColumn Binding="{Binding IsUse, Mode=TwoWay}" Header="Use" />
            </DataGrid.Columns>
        </DataGrid>

MVVM에서 핵심이 PropertyChanged 이벤트를 이용해야 하기 때문에 Members 프로퍼티가 계속 변경된다면 아래와 같은 형태를 사용하는 것이 좋습니다. 다만, 생성할 때 처음에만 변경되는 구조라면 단순한 모양을 사용해도 상관 없습니다.

        //public IList<Member> Members { get; set; }
        private IList<Member> _member;
        public IList<Member> Members
        {
            get { return _member; }
            set { SetProperty(ref _member, value); }
        }
        
        private void Init()
        {
            Members = new ObservableCollection<Member> 
            {
                new Member
                {
                    Id = 1,
                    Name = "kaki104",
                    Phone = "010-1111-2222",
                    RegDate = DateTime.Now,
                    IsUse = true,
                }
            };
        }

3. 실행

4. New 버튼 처리

Event - MainWindows.xaml

Click 이벤트 핸들러를 추가했습니다.

            <Button
                x:Name="NewButton"
                Width="80"
                Click="NewButton_Click"
                Content="New" />

Event - MainWindows.xaml.cs

NewButton_Click 이벤트 핸들러에는 _isEditing 필드를 true로 만들어서, 버튼들의 Visibility를 변경합니다.

즉, 논리적인 부분(수정 중인지 아닌지)과 물리적인 부분(컨트롤이 보이는지 않보이는지..)이 공존하고 있는 것이죠

        private void NewButton_Click(object sender, RoutedEventArgs e)
        {
            _isEditing = true;
            ClearDetail();
            ChangeButtonVisibility();
        }
        
        private void ChangeButtonVisibility()
        {
            if(_isEditing)
            {
                NewButton.Visibility = Visibility.Collapsed;
                EditButton.Visibility = Visibility.Collapsed;
                DeleteButton.Visibility = Visibility.Collapsed;
                SaveButton.Visibility = Visibility.Visible;
                CancelButton.Visibility = Visibility.Visible;
                DetailGrid.IsEnabled = true;
            }
            else
            {
                NewButton.Visibility = Visibility.Visible;
                EditButton.Visibility = Visibility.Visible;
                DeleteButton.Visibility = Visibility.Visible;
                SaveButton.Visibility = Visibility.Collapsed;
                CancelButton.Visibility = Visibility.Collapsed;
                DetailGrid.IsEnabled = false;
            }
        }

Mvvm - MainViewModel.cs

NewCommand를 추가합니다. NewButton과 연결해서 버튼 Click을 Command로 변경하는데 사용합니다.

NewCommand를 실행시켰을 때 해야할 일이 간단하면 아래와 같이 짧게 만들어서 사용할 수 있습니다.

IsEditing 프로퍼티를 추가합니다. 이 프로퍼티는 _isEditing 처럼 수정 상태인지 여부를 판단하기 위해 사용합니다.

        public IRelayCommand NewCommand { get; set; }

        private bool _isEditing;
        public bool IsEditing
        {
            get { return _isEditing; }
            set { SetProperty(ref _isEditing, value); }
        }

        private void Init()
        {
            Members = new ObservableCollection<Member> 
            {
              ...
            };

            NewCommand = new RelayCommand(() => IsEditing = true);
        }

Mvvm - MainWindow.xaml

MainViewModel.cs에 있는 NewCommand와 연결합니다. 

            <Button
                x:Name="NewButton"
                Width="80"
                Command="{Binding NewCommand}"
                Content="New" />

New 버튼 클릭

6. IValueConverter

New 버튼을 클릭해서 IsEditing의 값을 변경해서 논리적으로 수정 상태라는 것은 알 수 있습니다. 이제 이 상태 값을 이용해서 View의 상태를 변경하도록 하겠습니다.

Mvvm - BoolToVisibilityConverter.cs

  • IValueConverter를 인터페이스를 사용하는 컨버터를 하나 추가합니다. 이 컨버터는 True이면 TrueValue 값을 반환하고, False이면 FalseValue값을 반환합니다.
  • 입력된 값이 bool이 아니라면, 아무것도 하지 않습니다. Binding.DoNothing은 WPF에서만 제공됩니다. 다른곳에서는 사용하실 수 없습니다.
    /// <summary>
    /// BoolToVisibilityConverter
    /// </summary>
    public class BoolToVisibilityConverter : IValueConverter
    {
        public Visibility TrueValue { get; set; } = Visibility.Visible;

        public Visibility FalseValue { get; set; } = Visibility.Collapsed;

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool boolValue)
            {
                if (boolValue)
                {
                    return TrueValue;
                }
                else
                {
                    return FalseValue;
                }
            }
            return Binding.DoNothing;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

Mvvm - MainWindows.xaml

  • 새로만든 BoolToVisibilityConverter를 사용하기 위해서는 먼저 StaticResource로 등록을 해주어야 합니다. 여기서는 Window.Resource에 추가합니다.
  • Key가 BoolToVisibilityConverter는 true이면 보이고, false이면 숨깁니다. ReversBoolToVisibilityConverter는 true이면 숨기고, false이면 보여줍니다.
    xmlns:converters="clr-namespace:CrudSample.Mvvm.Converters"
    
    <Window.Resources>
        <converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
        <converters:BoolToVisibilityConverter
            x:Key="ReversBoolToVisibilityConverter"
            FalseValue="Visible"
            TrueValue="Collapsed" />
    </Window.Resources>

각 버튼들의 Visibility 프로퍼티를 바인딩해 줍니다. 

이렇게 하면 디자인 타임에서는 모든 버튼이 보이게됩니다. 하지만, 런타임에서는 이전과 동일한 동작을 합니다.

            <Button
                x:Name="NewButton"
                Width="80"
                Command="{Binding NewCommand}"
                Content="New"
                Visibility="{Binding IsEditing, Converter={StaticResource ReversBoolToVisibilityConverter}}" />
            <Button
                x:Name="EditButton"
                Width="80"
                Margin="5,0,0,0"
                Content="Edit"
                Visibility="{Binding IsEditing, Converter={StaticResource ReversBoolToVisibilityConverter}}" />
            <Button
                x:Name="DeleteButton"
                Width="80"
                Margin="5,0,0,0"
                Content="Delete"
                Visibility="{Binding IsEditing, Converter={StaticResource ReversBoolToVisibilityConverter}}" />
            <Button
                x:Name="SaveButton"
                Width="80"
                Content="Save"
                Visibility="{Binding IsEditing, Converter={StaticResource BoolToVisibilityConverter}}" />
            <Button
                x:Name="CancelButton"
                Width="80"
                Margin="5,0,0,0"
                Content="Cancel"
                Visibility="{Binding IsEditing, Converter={StaticResource BoolToVisibilityConverter}}" />

7. Cancel 버튼 클릭

Event - MainWindow.xaml

            <Button
                x:Name="CancelButton"
                Width="80"
                Margin="5,0,0,0"
                Click="CancelButton_Click"
                Content="Cancel"
                Visibility="Collapsed" />

Event - MainWindow.xaml.cs

취소 버튼을 클릭했을 때 _isEditing을 false로 변경하고 컨트롤들의 Visibility 속성을 변경합니다.

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            _isEditing = false;
            ChangeButtonVisibility();
        }

Mvvm - MainViewModel.cs

CancelCommand를 추가하고, IsEditing 속성을 false로 변경하는 코드만 추가합니다.

이미 각 버튼들에 IsEditing 속성이 바인딩되어 있기 때문에 false로 변경하면 다시 원래 상태로 변경됩니다.

        public IRelayCommand CancelCommand { get; set; }

        private void Init()
        {
            Members = new ObservableCollection<Member> 
            {
              ...
            };

            NewCommand = new RelayCommand(() => IsEditing = true);
            CancelCommand = new RelayCommand(() => IsEditing = false);
        }

Mvvm - MainWindow.xaml

Command에 CancelCommand를 추가합니다.

            <Button
                x:Name="CancelButton"
                Width="80"
                Margin="5,0,0,0"
                Command="{Binding CancelCommand}"
                Content="Cancel"
                Visibility="{Binding IsEditing, Converter={StaticResource BoolToVisibilityConverter}}" />

8. 소스

CrudSample.Core, CrudSample.Event, CrudSample.Mvvm

 

GitHub - kaki104/WpfTest

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

github.com

3개의 소스를 참고하시면 됩니다. 다음 포스팅에서 나머지 기능에 대해서도 설명하도록 하겠습니다.

 

이벤트 드리븐 방식으로 개발했는데 MVVM 패턴으로 어떻게 변경해야할지 모르는 분들께서는 간단한 셈플을 만들어서 이메일이나 Github에 올려주시면 제가 MVVM 패턴으로 변경하는 방법에 대해서 포스팅을 하도록 하겠습니다.

 

반응형
댓글