[WPF] WPF DevExpress 실습

목적

  • WPF DevExpress를 학습하고 실습합니다.

DevExpress BindableBase

  • INotifyPropertyChanged interface 및 GetProperty 및 SetProperty Method로 Binding 가능한 Property를 쉽게 구현할 수 있는 기능을 지원하는 추상클래스이다.
  • GetProperty Method : Property 이름을 기반으로 값을 가져온다.
  • SetProperty Method : Property 이름을 키로 사용하여 UI에 값을 알려준다.

DevExpress ViewModelBase

  • 런타임 및 디자인 타임 모드에 대해 개별적으로 속성 초기화
  • View 내에 등록된 액세스 서비스
  • View-Model 간에 데이터 전달
  • Command Property 없이 Command 가능

요구사항

  • 3장 프로젝트와 동일한 요구사항입니다.

실습

Converter

  • MouseEventConverter.cs
using DevExpress.Mvvm.UI;
using DevExpress.Xpf.Grid;
using System.Windows.Input;

namespace MVVM.Converter
{
    public class MouseEventConverter : EventArgsConverterBase<MouseEventArgs>
    {
        //MouseEvent를 받을 Convert 를 구현한다.
        protected override object Convert(object sender, MouseEventArgs args)
        {
            var grid = sender as GridControl;
            return args.GetPosition(grid);
        }
    }
}

Interface

  • IDialogView.cs
namespace MVVM
{
    public interface IDialogView
    {
        bool? ShowDialog();
        bool? DialogResult { get; set; }
        void Show();
        void Close();
    }
}
  • IWindowView.cs
using System.Windows;

namespace MVVM
{
    public interface IWindowView
    {
        void Show();
        void Close();
        Visibility Visibility { get; set; }
    }
}

Model

  • Person.cs
using DevExpress.Mvvm;

namespace MVVM
{
    //BindableBase 클래스는
    //Binding 가능한 Property 를 쉽게 구현할 수 있는 기능을 지원하는 추상 클래스이다.
    public class Person : BindableBase
    {
        public string Name
        {
            get { return GetProperty(() => Name); }
            set { SetProperty(() => Name, value); }
        }

        public bool Gender
        {
            get { return GetProperty(() => Gender); }
            set { SetProperty(() => Gender, value); }
        }

        public string PhoneNumber
        {
            get { return GetProperty(() => PhoneNumber); }
            set { SetProperty(() => PhoneNumber, value); }
        }

        public string Address
        {
            get { return GetProperty(() => Address); }
            set { SetProperty(() => Address, value); }

        }

        public Person()
        {

        }

        public Person(Person input)
        {
            Address = input.Address;
            Name = input.Name;
            PhoneNumber = input.PhoneNumber;
            Gender = input.Gender;
        }
    }
}

View

  • AddView.xaml
<dx:DXWindow x:Class="MVVM.AddView"
        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:MVVM"
        xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
        xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
        mc:Ignorable="d" Name="AddViewParam"
        d:DataContext="{d:DesignInstance local:AddView}"
        Title="{Binding Path=Caption,
                        UpdateSourceTrigger=PropertyChanged, 
                        Mode=TwoWay}" 
        Height="300" Width="300"
        WindowStartupLocation="CenterScreen"
        WindowStyle="ToolWindow">
    <dxlc:LayoutControl Orientation="Vertical" Padding="4">
        <dxlc:LayoutGroup Orientation="Vertical">
            <dxlc:LayoutItem Label="이름" VerticalAlignment="Top">
                <TextBox Text="{Binding Path=PersonData.Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
            </dxlc:LayoutItem>
            <dxlc:LayoutItem Label="성별" VerticalAlignment="Top">
                <TextBox Text="{Binding Path=PersonData.Gender, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
            </dxlc:LayoutItem>
            <dxlc:LayoutItem Label="전화번호" VerticalAlignment="Top">
                <TextBox Text="{Binding Path=PersonData.PhoneNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
            </dxlc:LayoutItem>
            <dxlc:LayoutItem Label="주소" VerticalAlignment="Stretch">
                <TextBox Text="{Binding Path=PersonData.Address, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
            </dxlc:LayoutItem>
        </dxlc:LayoutGroup>

        <dxlc:LayoutGroup VerticalAlignment="Bottom" HorizontalAlignment="Right"
                          Header="20">
            <Button Content="확인"
                    Command="{Binding Path=OkCommand}"
                    CommandParameter="{Binding ElementName=AddViewParam}"
                    Width="75"/>
            <Button Content="취소"
                    Command="{Binding Path=CancelCommand}"
                    CommandParameter="{Binding ElementName=AddViewParam}"
                    Width="75"/>
        </dxlc:LayoutGroup>
    </dxlc:LayoutControl>
</dx:DXWindow>
  • AddView.xaml.cs
using DevExpress.Xpf.Core;

namespace MVVM
{
    /// <summary>
    /// AddView.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class AddView : DXWindow, IDialogView
    {
        public AddView(AddViewModel viewModel)
        {
            InitializeComponent();
            this.DataContext = viewModel;
        }
    }
}
  • MainView.xaml
<dx:DXWindow x:Class="MVVM.MainView"
        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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
        xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
        xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
        xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
        xmlns:local="clr-namespace:MVVM"
        xmlns:cv="clr-namespace:MVVM.Converter"
        mc:Ignorable="d"  
        d:DataContext="{d:DesignInstance local:MainViewModel}"
        Title="주소록 관리 V0.1" Height="328" Width="570" 
        WindowStartupLocation="CenterScreen" Name="MainViewParam">

    <dxlc:LayoutControl Orientation="Vertical" Padding="4">
        <dxg:GridControl EnableSmartColumnsGeneration="True"
                         Name="PersonParam"
                         ItemsSource="{Binding Path=Persons,
                                               UpdateSourceTrigger=PropertyChanged,
                                               Mode=OneWay}">
            <dxmvvm:Interaction.Behaviors>
                <dxmvvm:EventToCommand EventName="MouseDoubleClick"
                                       Command="{Binding Path=ModifyCommand}"
                                       CommandParameter="{Binding ElementName=PersonParam,
                                                                  Path=SelectedItem}"/>
                <dxmvvm:EventToCommand EventName="MouseMove"
                                       Command="{Binding Path=MouseMoveCommand}"
                                       PassEventArgsToCommand="True">
                    <dxmvvm:EventToCommand.EventArgsConverter>
                        <cv:MouseEventConverter/>
                    </dxmvvm:EventToCommand.EventArgsConverter>
                </dxmvvm:EventToCommand>
            </dxmvvm:Interaction.Behaviors>

            <dxg:GridControl.Columns>
                <dxg:GridColumn Header="이름" FieldName="Name" ReadOnly="True"
                                HorizontalHeaderContentAlignment="Center"/>
                <dxg:GridColumn Header="성별" FieldName="Gender" ReadOnly="True"
                                HorizontalHeaderContentAlignment="Center"/>
                <dxg:GridColumn Header="전화번호" FieldName="PhoneNumber" ReadOnly="True"
                                HorizontalHeaderContentAlignment="Center"/>
                <dxg:GridColumn Header="주소" FieldName="Address" ReadOnly="True"
                                HorizontalHeaderContentAlignment="Center"/>
            </dxg:GridControl.Columns>

            <dxg:GridControl.View>
                <dxg:TableView AllowPerPixelScrolling="False"
                               AllowHorizontalScrollingVirtualization="False"
                               AllowCascadeUpdate="False"
                               AllowEditing="False"
                               ShowAutoFilterRow="False"
                               ShowTotalSummary="False"
                               ShowGroupPanel="False"
                               UseAnimationWhenExpanding="False"
                               NewItemRowPosition="None"
                               ScrollingMode="Smart"
                               AutoWidth="True"
                               NavigationStyle="Row"
                               FadeSelectionOnLostFocus="False"/>
            </dxg:GridControl.View>

        </dxg:GridControl>

        <dxlc:LayoutGroup VerticalAlignment="Bottom" Header="24">
            <dxlc:LayoutGroup HorizontalAlignment="Left" VerticalAlignment="Center">
                <dxlc:LayoutItem Label="Mouse X : ">
                    <TextBox Text="{Binding Path=MouseX, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                             Width="50"/>
                </dxlc:LayoutItem>
                <dxlc:LayoutItem Label="Mouse Y : ">
                    <TextBox Text="{Binding Path=MouseY, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                             Width="50"/>
                </dxlc:LayoutItem>
            </dxlc:LayoutGroup>

            <dxlc:LayoutGroup HorizontalAlignment="Right">
                <Button Command="{Binding Path=AddCommand}"
                        Content="추가" Width="75"/>
                <Button Command="{Binding Path=ModifyCommand}"
                        CommandParameter="{Binding ElementName=PersonParam, Path=SelectedItem}"
                        Content="변경" Width="75"/>
                <Button Command="{Binding Path=DeleteCommand}"
                        CommandParameter="{Binding ElementName=PersonParam, Path=SelectedItem}"
                        Content="삭제" Width="75"/>
                <Button Command="{Binding Path=ExitCommand}"
                        CommandParameter="{Binding ElementName=MainViewParam}"
                        Content="종료" Width="75"/>
            </dxlc:LayoutGroup>
        </dxlc:LayoutGroup>

    </dxlc:LayoutControl>
</dx:DXWindow>
  • MainView.xaml.cs
    using DevExpress.Xpf.Core;
    

namespace MVVM
{
///


/// MainView.xaml에 대한 상호 작용 논리
///

public partial class MainView : DXWindow, IWindowView
{
public MainView(MainViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
}
}


## **ViewModel**
* **AddView.cs**

```c#
using DevExpress.Mvvm;
using System.Windows.Input;

namespace MVVM
{
    // ViewModelBase를 상속 받는다.
    // 데이터를 ViewModel에 전달하는데 사용하는 interface.
    public class AddViewModel : ViewModelBase
    {
        public enum ViewType
        {
            Add,
            Modify
        }

        public string Caption
        {
            get { return GetProperty(() => Caption); }
            set { SetProperty(() => Caption, value); }
        }

        public Person PersonData { get; set; }
        public ICommand OkCommand { get; private set; }
        public ICommand CancelCommand { get; private set; }

        public AddViewModel(Person Data = null, ViewType type = ViewType.Add)
        {
            if (type == ViewType.Add)
            {
                Caption = "추가";
            }
            else if (type == ViewType.Modify)
            {
                Caption = "변경";
            }
            else
            {
                Caption = "추가";
            }

            PersonData = Data;
            OkCommand = new DelegateCommand<IDialogView>(view => _okCommandAction(view));
            CancelCommand = new DelegateCommand<IDialogView>(view => _cancelCommandAction(view));

        }

        private void _okCommandAction(IDialogView view)
        {
            view.DialogResult = true;
            view.Close();
        }

        private void _cancelCommandAction(IDialogView view)
        {
            view.DialogResult = false;
            view.Close();
        }
    }
}
  • MainView.cs
using DevExpress.Mvvm;
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;

namespace MVVM
{
    public class MainViewModel : ViewModelBase
    {
        public ObservableCollection<Person> Persons { get; set; }

        public ICommand AddCommand { get; set; }
        public ICommand DeleteCommand { get; set; }
        public ICommand ModifyCommand { get; set; }
        public ICommand ExitCommand { get; set; }
        public ICommand MouseMoveCommand { get; set; }

        private readonly IMessageBoxService _messageBoxService;
        private readonly Func<Person, AddViewModel.ViewType, IDialogView> _createAddView;

        public string MouseX
        {
            get { return GetProperty(() => MouseX); }
            set { SetProperty(() => MouseX, value); }
        }

        public string MouseY
        {
            get { return GetProperty(() => MouseY); }
            set { SetProperty(() => MouseY, value); }
        }

        public MainViewModel(IMessageBoxService messageBoxService
            , Func<Person, AddViewModel.ViewType, IDialogView> createAddView)
        {
            _messageBoxService = messageBoxService;
            _createAddView = createAddView;
            Persons = new ObservableCollection<Person>();

            _initCommand(); ;
            _initTestData();
        }

        private void _initTestData()
        {
            Persons.Add(new Person() { Address = "test1", Name = "홍길동1", Gender = true, PhoneNumber = "11111" });
            Persons.Add(new Person() { Address = "test2", Name = "홍길동2", Gender = true, PhoneNumber = "22222" });
            Persons.Add(new Person() { Address = "test3", Name = "홍길동3", Gender = true, PhoneNumber = "33333" });
            Persons.Add(new Person() { Address = "test4", Name = "홍길동4", Gender = true, PhoneNumber = "44444" });
        }

        private void _initCommand()
        {
            AddCommand = new DelegateCommand(_addCommandAction);
            DeleteCommand = new DelegateCommand<Person>(data => _deleteCommandAction(data));
            ModifyCommand = new DelegateCommand<Person>(data => _modifyCommandAction(data));
            ExitCommand = new DelegateCommand<IWindowView>(view => _exitCommandAction(view));
            MouseMoveCommand = new DelegateCommand<Point>(args => _mouseMoveCommand(args));
        }

        private void _mouseMoveCommand(Point currentPoint)
        {
            MouseX = $"{currentPoint.X}";
            MouseY = $"{currentPoint.Y}";
        }

        private void _exitCommandAction(IWindowView view)
        {
            view.Close();
        }

        private void _modifyCommandAction(Person selectedItem)
        {
            if (selectedItem == null)
            {
                _messageBoxService.Show("선택된 데이터가 없습니다.", "주소록 v0.1", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            var modifyData = new Person(selectedItem);

            IDialogView view = _createAddView(modifyData, AddViewModel.ViewType.Modify);
            if (true == view.ShowDialog())
            {
                var selectedIndex = Persons.IndexOf(selectedItem);
                Persons[selectedIndex] = modifyData;
            }
        }

        private void _deleteCommandAction(Person selectedItem)
        {
            if (selectedItem == null)
            {
                _messageBoxService.Show("선택된 데이터가 없습니다.", "주소록 v0.1", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            var result = _messageBoxService.Show("선택된 데이터가 삭제 하시겠습니까?", "주소록 v0.1", MessageBoxButton.OKCancel, MessageBoxImage.Question);
            if (result == MessageBoxResult.Cancel)
                return;

            Persons.Remove(selectedItem);
        }

        private void _addCommandAction()
        {
            var addData = new Person();

            IDialogView view = _createAddView(addData, AddViewModel.ViewType.Add);
            if (true == view.ShowDialog())
            {
                Persons.Add(addData);
            }
        }
    }
}

Etc

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

    </Application.Resources>
</Application>
  • App.xaml.cs
using DevExpress.Mvvm;
using DevExpress.Xpf.Core;
using System.Windows;

namespace MVVM
{
    /// <summary>
    /// App.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class App : Application
    {

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            ApplicationThemeHelper.ApplicationThemeName = Theme.VS2019DarkName;

            IMessageBoxService messageBoxService = new DXMessageBoxService();
            MainViewModel mainViewModel = new MainViewModel(messageBoxService, _createAddView);
            MainView mainView = new MainView(mainViewModel);
            mainView.Show();
        }

        protected override void OnExit(ExitEventArgs e)
        {
            base.OnExit(e);
        }

        private IDialogView _createAddView(Person modifyData, AddViewModel.ViewType type = AddViewModel.ViewType.Add)
        {
            IMessageBoxService messageBoxService = new DXMessageBoxService();
            var viewModel = new AddViewModel(modifyData, type);
            return new AddView(viewModel);
        }
    }
}
  • 실행결과

728x90

이 글을 공유하기

댓글(2)

  • 익명
    2022.06.24 11:05

    비밀댓글입니다

    • 2022.06.24 11:07 신고

      사실 wpf 책이 국내에 거의 없습니다ㅠㅠ 그나마 추천드리는거는 찰스 페졸드의 WPF와 MVVM 패턴 기초를 보고 싶으시면 wpf mvvm 일주일 만에 배우기 책 추천 드립니다~~!!

Designed by JB FACTORY