본문 바로가기

[C# WPF] MVVM 간단하게 시작하기 - 4 (DB연동, DataGrid 활용)

재과장 2023. 1. 28.
반응형

 

2023.01.26 - [프로그래밍/C#] - [C# WPF] MVVM 간단하게 시작하기 - 3 (DB연동, DataGrid 활용)

 

[C# WPF] MVVM 간단하게 시작하기 - 3 (DB연동, DataGrid 활용)

정말 오랜만에 글을 쓰는 것 같습니다. 현생에 집중한다는 핑계로 멀리했었네요.. 이제부터라도 꾸준히 제가 가지고 있는 C#에 대한 정보들을 써보려고 합니다. 한글로 되어 있는 WPF 관련 정보들

esound.tistory.com

 

이전 글에 이어서 Model, ViewModel을 만들어 프로그램을 완료해보겠습니다.

 

먼저 이번에 새로 생길 Class와 함께 전체적인 구성도를 같이 보는게 좋을 것 같습니다.

 

구성도

MVVM 패턴이기에 사용하는 ViewModel과 Model을 제외하고 Repository라는 새로운 클래스를 사용하게 되었습니다.

 

그 이유로는 ViewModel에서는 오직 비즈니스 로직만을 다루기 위함입니다.

데이터를 정의하거나, DB에서 가져오거나 하는 등의 기타 행동들은 전부 다른 클래스로 분리하여 관리하게 되는 것입니다.

그렇게 되면 ViewModel에서는 데이터를 가공하거나 로직에 맞게 진행하는 과정만을 포함하게 될 것입니다.

 

사실 이렇게 되면 엄청 귀찮긴한데... 만들어두면 프로그램을 개선하거나 할 때 굉장히 편리하겠죠?

 

그럼, 이제 클래스 파일을 하나씩 보며 확인해보겠습니다.

 


1. Model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Data;

namespace MyMVVM.Model
{
    class Model
    {
        public DataView dv_1
        {
            get;
            set;
        }

    }
}

 

앞에서 설명드린 글과 Model의 구성이 달라서 당황스러우시리라 생각합니다.

 

글을 작성하기 위해서 더 공부를 하다보니 Model에 불필요한 Notify는 없는 게 맞다는 생각이 들더라구요.

그래서 Model에는 위처럼 ViewModel에서 사용할 변수만 딱 지정해주었습니다.

Notify에 대한 지정은 ViewModel에서 진행하려고 합니다.

(앞의 글은 수정보완하려고 합니다.)

 


2. Repository

이 클래스는 앞에서 만들었던 Core라는 폴더 안에 포함시켰습니다.

 

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Windows;
namespace MyMVVM.Core
{

    class Repo
    {
        private readonly string _connString;

        public Repo(string connString)
        {
            _connString = connString;
        }


        public DataTable GetData()
        {
            using (SqlConnection connection = new SqlConnection(_connString))
            {
                try
                {
                    connection.Open();
                    string sql = "SELECT * FROM myTable";
                    SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);
                    DataTable table = new DataTable();
                    adapter.Fill(table);

                    return table;
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: " + ex.Message);
                    return null;
                }
            }
        }

    }
}

앞의 구성도에서 말씀드린 것처럼, 이 Repo 클래스에는 DB에 접근해서 데이터를 가져오는 등의 데이터 제어 메소드를 모아두었습니다.

 

그 이유도 동일하게 ViewModel에서는 로직만을 다루기 위함입니다.

 

추후에 데이터 관련해서 문제가 발생하거나, 유지보수를 진행해야한다면 이 클래스만 확인할 수도 있을 것입니다.

 

지금은 Select 외의 기능은 필요하지 않아 따로 구현하지는 않았습니다.

 

GetData 메소드의 내용은 ms-sql에 접근하는 기본적인 방법으로 인수로 쿼리문을 줘서 활용할 수도 있겠습니다.

 

* db에 접근하는 등의 코드는 이 글에서는 다루지 않을 예정입니다.

  여기서 다 다루다보면 주제에 맞지도 않고, 글이 너무 길어질 것 같아서 이러한 내용들은 다른글에서 같이 공부해보려고합니다!

 


3. MainViewModel

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

using System.Windows;
namespace MyMVVM.ViewModel
{
    class MainViewModel : INotifyPropertyChanged
    {
        // Model 객체 선언
        private Model.Model _model;

        // DB 접근 객체 선언
        private Core.Repo _repo;

        // Timer 선언
        private Timer _timer;

        private string _connstring = @"Data Source=db서버 ip;Initial Catalog=db명;User ID=db 접속 id;Password=db 접속 pw";


        public MainViewModel()
        {
            _model = new Model.Model();
            _repo = new Core.Repo(_connstring);

            // 타이머 설정
            _timer = new Timer(1000);
            _timer.Elapsed += TmrMonitoring;
            _timer.Start();

        }

        public DataView DV_1
        {
            get { return _model.dv_1; }
            set { _model.dv_1 = value; OnPropertyChanged("DV_1"); }
        }

        private void TmrMonitoring(object sender, ElapsedEventArgs e)
        {

            DV_1 = _repo.GetData().Copy().DefaultView;

            
        }


        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            { handler(this, new PropertyChangedEventArgs(name)); }
        }
    }
}

비즈니스 로직의 메인, ViewModel의 구성입니다.

 

        // Model 객체 선언
        private Model.Model _model;

        // DB 접근 객체 선언
        private Core.Repo _repo;

        // Timer 선언
        private Timer _timer;

        private string _connstring = @"Data Source=db서버 ip;Initial Catalog=db명;User ID=db 접속 id;Password=db 접속 pw";

        public DataView DV_1
        {
            get { return _model.dv_1; }
            set { _model.dv_1 = value; OnPropertyChanged("DV_1"); }
        }

이 부분에서 보시면 구성도에서 설명드린 것과 같이 각각의 객체를 선언하였습니다.

 

[정의된 Data를 사용하기위한 Model 객체]

[DB에 접근하여 Data를 다루기위한 Repo 객체]

 

두 객체를 선언하여 ViewModel에서는 오직 비즈니스로직만을 다루는 것이 가능해졌습니다.

 

그 아래로는 DB에서 주기적으로 Data를 가져오기위해 Timer를 선언하고, db에 접속하는 Connectionstring도 정의해주었습니다.

이 부분은 자신의 db 서버에 맞춰서 설정하시면 되겠습니다.

 

마지막으로 View와 Binding 될 DataView의 설정입니다.

아래에서 설명하겠지만 View에서 사용한 DataGrid는 DataView를 사용할 수 있기 때문에 Model에서 선언해주었고, 설정한 것을 객체로부터 불러와 정의하였습니다. 

(추후에 프로젝트가 커지면서 변수를 따로 관리해야한다면 관리 방법을 발전시키는 것도 생각해봐야할 것 같습니다.)

 

        public MainViewModel()
        {
            _model = new Model.Model();
            _repo = new Core.Repo(_connstring);

            // 타이머 설정
            _timer = new Timer(1000);
            _timer.Elapsed += TmrMonitoring;
            _timer.Start();

        }

생성자에 선언한 객체를 준비하며 Timer도 1초 간격으로 실행될 수 있도록 하였습니다.

 

        private void TmrMonitoring(object sender, ElapsedEventArgs e)
        {

            DV_1 = _repo.GetData().Copy().DefaultView;

            
        }


        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            { handler(this, new PropertyChangedEventArgs(name)); }
        }

Timer에 포함된 메소드입니다.

 

위에서 정의한 DataView에 repo 클래스로 부터 가져온 메소드의 결과를 변환시켜서 입력해준 것입니다.

 

_repo.GetData()의 return 값은 DataTable의 값이기 때문에, .Copy().DefaultView를 추가하여 DataView 형태로 변환해 입력해준 것입니다.

 

이렇게보니, 가독성이 안좋네요...

 

그 아래로는 Notify에 관한 설정인데, 관련 내용은 아래 글에서 확인부탁드립니다.

 

2021.07.19 - [프로그래밍/C#] - [C# WPF] MVVM 간단하게 시작하기 - 1 (데이터바인딩, 연동)

 

[C# WPF] MVVM 간단하게 시작하기 - 1 (데이터바인딩, 연동)

MVVM, Model - View - ViewModel WPF에서 사용할 수 있는 디자인패턴입니다. 이번 글과 앞으로 이어지는 글에서 MVVM을 쉽고 간단하게 사용할 수 있도록 공부하며 배운 내용을 정리하겠습니다. View : 사용자

esound.tistory.com

 

 


4. DB & View

 

DB에서 Data를 가져와서 가공없이 그대로 Datagrid에 입력할 것이기 때문에 View에도 컬럼에 이름이 그대로 들어가게됩니다. 

그래서 DB의 컬럼이름을 알고 있어야합니다.

db 설정

저는 3개의 컬럼을 설정했습니다.

이름은 원하시는대로 설정하시고 그에 맞게 View에 적용해주면 됩니다.

 

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ViewModel="clr-namespace:MyMVVM.ViewModel" x:Class="MyMVVM.MainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <ViewModel:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="4*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="0.5*"/>
            <RowDefinition Height="2*"/>
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" Grid.Column="0">
            <Rectangle Width="420" Height="70" Fill="#FFFFEB3B"
				RadiusX="2" RadiusY="2" VerticalAlignment="Center"/>
            <TextBlock Text="종합병원 대기시스템"
				HorizontalAlignment="Center" VerticalAlignment="Center"
				FontSize="32" Margin="0 00 0 15"/>
        </Grid>

        <!--Grid 2행 1열 설정-->
        <Grid Grid.Row="1" Grid.Column="0">
            <Border BorderBrush="LightGray" BorderThickness="2" Width="420">
                <StackPanel>
                    <DataGrid x:Name="DG1" Width="420" Height="255" ItemsSource="{Binding DV_1}"
						AutoGenerateColumns="False" CanUserAddRows="False"
						HorizontalAlignment="Center"
						FontSize="20"
						Margin="-2,0">
                        <DataGrid.RowStyle>
                            <Style TargetType="{x:Type DataGridRow}">
                                <Setter Property="Height" Value="60"/>
                            </Style>
                        </DataGrid.RowStyle>


                        <DataGrid.Columns>

                            <DataGridTextColumn Header="호출번호" Width="100"  FontSize="15" Binding="{Binding Path=No}" IsReadOnly="True">
                                <DataGridTextColumn.ElementStyle>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <Setter Property="HorizontalAlignment" Value="Center" />
                                        <Setter Property="VerticalAlignment" Value="Center"/>
                                    </Style>
                                </DataGridTextColumn.ElementStyle>
                            </DataGridTextColumn>

                            <DataGridTextColumn Header="진료실" Width="130"  FontSize="15" Binding="{Binding Path=Room}" IsReadOnly="True" >
                                <DataGridTextColumn.ElementStyle>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <Setter Property="HorizontalAlignment" Value="Center" />
                                        <Setter Property="VerticalAlignment" Value="Center"/>
                                    </Style>
                                </DataGridTextColumn.ElementStyle>
                            </DataGridTextColumn>

                            <DataGridTextColumn Header="성함" Width="190"  FontSize="15" Binding="{Binding Path=Name}" IsReadOnly="True" >
                                <DataGridTextColumn.ElementStyle>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <Setter Property="HorizontalAlignment" Value="Center" />
                                        <Setter Property="VerticalAlignment" Value="Center"/>
                                    </Style>
                                </DataGridTextColumn.ElementStyle>
                            </DataGridTextColumn>

                        </DataGrid.Columns>
                    </DataGrid>
                </StackPanel>
            </Border>
        </Grid>

        <!-- 열 설정-->
        <Grid Grid.RowSpan="1" Grid.Column="1">
            <Rectangle Height="323" Fill="Black"
				RadiusX="2" RadiusY="2" VerticalAlignment="Bottom" Margin="0,0,0,-259"/>
        </Grid>
    </Grid>
</Window>

완성된 View 입니다. 

 

이전 글에서는 없었던 DataContext도 MainViewModel로 추가가 되면서 View와 ViewModel이 간단하게 연결되었습니다.

(이 부분이 확실하지 않으시다면 이전 글을 확인해주시면 좋을 것 같습니다!)

 

이제 View와 ViewModel이 연결되었기 때문에 ViewModel에 있는 변수를 입력해줄 수 있습니다!

 

<DataGrid x:Name="DG1" Width="420" Height="255" ItemsSource="{Binding DV_1}"
AutoGenerateColumns="False" CanUserAddRows="False"
HorizontalAlignment="Center"
FontSize="20"
Margin="-2,0">

이전 글에서 입력하지 않았던 ItemSource에도 저희가 ViewModel에서 만들었던 DataView 타입의 DV_1을 연결해주었습니다.

 

                            <DataGridTextColumn Header="호출번호" Width="100"  FontSize="15" Binding="{Binding Path=No}" IsReadOnly="True">
                                <DataGridTextColumn.ElementStyle>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <Setter Property="HorizontalAlignment" Value="Center" />
                                        <Setter Property="VerticalAlignment" Value="Center"/>
                                    </Style>
                                </DataGridTextColumn.ElementStyle>
                            </DataGridTextColumn>

DataGrid의 열을 설정해줄 대에도 위에서 말씀드린 Table의 열이름을 직접 입력하여 바로 표시하도록 하였습니다.

 

Binding="{Binding Path=No}"

 

위 부분에서 컬럼 이름을 입력해주시면 됩니다.

 


자, 이렇게 해서 MVVM패턴으로 간단한 프로그램을 작성해보았습니다.

 

디자인은 멋이 없지만, 나름 DB 접속도 해보고 Timer도 사용하며 재밌게 진행했던 것 같습니다.

 

실제로 사용하기에 문제도 있을 것이고, 많은 부분에서 부족한 글이지만 따라와주신 모든 분들께 정말 감사드립니다!

 

 

동영상을 보면 DB에 Data를 입력하면 그 값이 실시간으로 반영이 되는 것을 확인할 수 있습니다.

 

지금은 직접 DB에 입력하지만 이 또한 프로그램으로 제작한다면 어느정도 그럴싸한 프로그램이 되지 않을까요?

 

저는 어설프게 디자인을 구현했지만 여러분들은 멋있게 구현해서 꼭 좋은 프로그램 만들어보셨으면 좋겠습니다!

MyMVVM.zip
0.13MB

 

프로젝트 전체파일을 첨부하였으니 같이 확인해보시면 좋을 것 같습니다.

 

감사합니다~!

반응형

댓글