예약 종류 관리 WPF 애플리케이션

  • 32 minutes to read

WPF를 사용하여 간단한 데이터베이스 프로그래밍을 연습해 보겠습니다.

이 강의에서 사용된 소스는 다음 경로에서 찾을 수 있습니다.

실행 모양은 다음 그림과 같습니다.

예약 종류 관리 WPF 애플리케이션

WPF 8.0 데이터베이스 프로그래밍 학습을 위한 프로젝트 생성

SQL Server 데이터베이스 프로젝트 생성

VisualAcademy.SqlServer 프로젝트 소개

다음 그림과 같은 형태로 프로젝트 구조를 만들 예정입니다.

isualAcademy.SqlServer 프로젝트 소개

VisualAcademy.SqlServer 프로젝트에는 dbo 폴더가 생성되어 있습니다. 이 폴더 내에는 Stored Procedures 폴더와 Tables 폴더가 존재합니다.

  • Tables 폴더 : 데이터베이스에서 사용되는 테이블 파일들이 위치하는 폴더입니다. AppointmentsTypes.sql 파일은 AppointmentsTypes 테이블을 생성합니다. 이 테이블은 예약 유형을 관리하기 위한 것으로, Id, AppointmentTypeName, IsActive, DateCreated 컬럼을 가지고 있습니다.
  • Stored Procedures 폴더 : 데이터베이스에서 사용되는 저장 프로시저 파일들이 위치하는 폴더입니다. AppointmentsTypes 폴더 내에는 AppointmentsTypes_Delete.sql, AppointmentsTypes_GetAll.sql, AppointmentsTypes_GetById.sql, AppointmentsTypes_Insert.sql, AppointmentsTypes_Update.sql 파일이 존재합니다. 이들 파일은 AppointmentsTypes 테이블에 대한 CRUD 작업을 수행하기 위한 저장 프로시저를 구현합니다.

AppointmentsTypes 테이블의 구조는 Id, AppointmentTypeName, IsActive, DateCreated 네 개의 컬럼으로 구성되어 있으며, AppointmentTypeName 컬럼은 NOT NULL 제약 조건을 갖습니다.

Stored Procedures 폴더의 AppointmentsTypes 폴더 내에는 AppointmentsTypes_Delete.sql, AppointmentsTypes_GetAll.sql, AppointmentsTypes_GetById.sql, AppointmentsTypes_Insert.sql, AppointmentsTypes_Update.sql 파일이 위치해 있습니다. 이들 파일은 AppointmentsTypes 테이블에 대한 CRUD 작업을 위한 저장 프로시저를 구현합니다.

이렇게 구성된 폴더와 파일 구조는 데이터베이스 관리의 효율성을 높이고, 유지보수 및 확장성을 고려하여 구성한 것입니다. 이를 통해, VisualAcademy.SqlServer 프로젝트에서 예약 유형 관련 기능을 개발할 때, 필요한 테이블과 저장 프로시저를 손쉽게 찾아서 사용할 수 있습니다.

AppointmentsTypes 테이블을 이용한 예약 종류 관리

이번 글에서는 AppointmentsTypes 테이블을 이용하여 예약 종류를 관리하는 방법에 대해 알아보겠습니다.

AppointmentsTypes 테이블은 예약 종류의 이름과 활성화 여부, 만들어진 날짜 및 시간 등을 저장하는 테이블입니다. 예약 종류는 "Scheduled", "Completed", "Incomplete", "Canceled" 등과 같이 여러 종류가 있을 수 있습니다. 이러한 예약 종류를 AppointmentsTypes 테이블에 저장하고 관리하면, 데이터 일관성을 유지할 수 있으며, 새로운 예약 종류를 추가하거나 수정할 때도 편리하게 처리할 수 있습니다.

AppointmentsTypes 테이블의 구조는 다음과 같습니다.

CREATE TABLE AppointmentsTypes (
  Id INT PRIMARY KEY IDENTITY(1,1),
  AppointmentTypeName NVARCHAR(50) NOT NULL,
  IsActive BIT NOT NULL DEFAULT 1,
  DateCreated DATETIME NOT NULL DEFAULT GETDATE()
)

보충 설명을 위한 주석문이 추가된 내용은 다음 코드를 참고하세요.

-- 테이블 생성: AppointmentTypes
-- 이 테이블은 예약 유형을 저장하며, 각 유형에 대한 정보를 포함합니다.
CREATE TABLE AppointmentsTypes (
  -- 기본 키: Id
  -- 자동 증가하는 정수 값 (1부터 시작)
  Id INT PRIMARY KEY IDENTITY(1,1),

  -- 예약 유형 이름: AppointmentTypeName
  -- 최대 50자의 NVARCHAR 형식
  AppointmentTypeName NVARCHAR(50) NOT NULL,

  -- 활성화 여부: IsActive
  -- 비트 타입 (0 또는 1, 1: 활성, 0: 비활성)
  -- 기본값: 1 (활성)
  IsActive BIT NOT NULL DEFAULT 1,

  -- 생성 날짜: DateCreated
  -- DATETIME 형식의 날짜와 시간
  -- 기본값: 현재 시스템 날짜와 시간 (GETDATE() 함수 사용)
  DateCreated DATETIME NOT NULL DEFAULT GETDATE()
)

위의 CREATE 문에서는 Id 컬럼이 기본 키(PK)이며, IDENTITY(1,1)을 사용하여 자동으로 값을 할당합니다. AppointmentTypeName 열은 NVARCHAR(50) 데이터 형식을 사용하여 예약 종류의 이름을 저장하며, NOT NULL 제약 조건을 가지고 있습니다. IsActive 열은 BIT 데이터 형식을 사용하여 예약 종류의 활성화 여부를 저장합니다. 기본값은 1로 설정되어 있으며, NOT NULL 제약 조건을 가지고 있습니다. DateCreated 열은 DATETIME 데이터 형식을 사용하여 예약 종류가 만들어진 날짜와 시간을 저장합니다. 기본값은 GETDATE() 함수를 사용하여 현재 시간으로 설정되어 있으며, NOT NULL 제약 조건을 가지고 있습니다.

예약 종류를 추가하려면, INSERT 문을 사용하여 AppointmentsTypes 테이블에 새로운 레코드를 추가하면 됩니다. 예를 들어, "Canceled"라는 예약 종류를 추가하려면 다음과 같이 쿼리를 작성할 수 있습니다.

INSERT INTO AppointmentsTypes (AppointmentTypeName)
VALUES ('Canceled');

예약 종류를 수정하려면, UPDATE 문을 사용하여 해당 레코드를 수정하면 됩니다. 예를 들어, "Canceled" 예약 종류를 "Cancelled"로 수정하려면 다음과 같이 쿼리를 작성할 수 있습니다.

UPDATE AppointmentsTypes
SET AppointmentTypeName = 'Cancelled'
WHERE AppointmentTypeName = 'Canceled';

참고: "Cancelled"와 "Canceled"

"Cancelled"와 "Canceled"는 동일한 의미를 가지고 있지만, 철자가 다릅니다. 이 두 단어는 무언가를 취소하거나 중지하는 것을 의미합니다. 주요 차이점은 미국 영어와 영국 영어 간의 철자 규칙에 있습니다.

Canceled: 미국 영어에서 사용되는 철자입니다. 미국에서는 "canceled"가 일반적으로 사용되며, 대부분의 미국 출판물에서 이 철자를 따릅니다.

Cancelled: 영국 영어, 캐나다 영어, 호주 영어 등에서 사용되는 철자입니다. 영국 및 다른 영어 사용 국가에서는 이 철자를 선호합니다.

요약하면, "canceled"와 "cancelled"는 동일한 의미를 가지고 있으며, 사용되는 지역에 따라 철자가 다를 뿐입니다. 미국 영어에서는 "canceled"를 사용하고, 영국 영어 및 다른 영역에서는 "cancelled"를 사용합니다.


예약 종류를 비활성화하려면, 해당 레코드의 IsActive 값을 0으로 설정하면 됩니다. 예를 들어, "Cancelled" 예약 종류를 비활성화하려면 다음과 같이 쿼리를 작성할 수 있습니다.

UPDATE AppointmentsTypes
SET IsActive = 0
WHERE AppointmentTypeName = 'Cancelled';

AppointmentsTypes 테이블을 사용하여 예약 종류를 관리하면, 데이터 일관성을 유지할 수 있으며, 새로운 예약 종류를 추가하거나 수정할 때도 편리하게 처리할 수 있습니다. 또한, 예약 종류의 활성화 여부를 쉽게 관리할 수 있어, 필요한 경우 예약 종류를 활성화하거나 비활성화할 수 있습니다. 이러한 기능을 활용하여 예약 종류를 관리하면, 애플리케이션의 성능과 유지보수성을 향상시킬 수 있습니다.

이상으로 AppointmentsTypes 테이블을 이용한 예약 종류 관리에 대해 알아보았습니다.

AppointmentsTypes 테이블에 대한 CRUD를 구현한 저장 프로시저

이번에는 AppointmentsTypes 테이블에 대한 CRUD(Create, Read, Update, Delete)를 구현한 저장 프로시저(Stored Procedure)들을 소개합니다.

AppointmentsTypes 테이블은 예약 종류의 이름과 활성화 여부, 만들어진 날짜 및 시간 등을 저장하는 테이블입니다. 이러한 예약 종류를 AppointmentsTypes 테이블에 저장하고 관리하면, 데이터 일관성을 유지할 수 있으며, 새로운 예약 종류를 추가하거나 수정할 때도 편리하게 처리할 수 있습니다.

이번 글에서는 AppointmentsTypes 테이블에 대한 CRUD(Create, Read, Update, Delete)를 구현한 Stored Procedure들을 소개합니다. 이 Stored Procedure들을 사용하면, 애플리케이션에서 AppointmentsTypes 테이블을 조작할 수 있으며, 데이터 일관성을 유지하면서 새로운 예약 종류를 추가하거나 수정, 삭제할 수 있습니다.

Create - AppointmentsTypes_Insert

새로운 예약 종류를 추가하는 Stored Procedure입니다. 이 Stored Procedure을 사용하면, AppointmentsTypes 테이블에 새로운 예약 종류를 추가할 수 있습니다.

CREATE PROCEDURE AppointmentsTypes_Insert
  @AppointmentTypeName NVARCHAR(50)
AS
BEGIN
  INSERT INTO AppointmentsTypes (AppointmentTypeName)
  VALUES (@AppointmentTypeName);
END

Read - AppointmentsTypes_GetAll

모든 예약 종류를 가져오는 Stored Procedure입니다. 이 Stored Procedure을 사용하면, AppointmentsTypes 테이블에서 모든 예약 종류를 가져올 수 있습니다.

CREATE PROCEDURE AppointmentsTypes_GetAll
AS
BEGIN
  SELECT [Id], [AppointmentTypeName], [IsActive], [DateCreated] 
  FROM AppointmentsTypes;
END

Read - AppointmentsTypes_GetById

특정 Id 값을 가진 예약 종류를 가져오는 Stored Procedure입니다. 이 Stored Procedure을 사용하면, AppointmentsTypes 테이블에서 특정 Id 값을 가진 예약 종류를 가져올 수 있습니다.

CREATE PROCEDURE AppointmentsTypes_GetById
  @Id INT
AS
BEGIN
  SELECT [Id], [AppointmentTypeName], [IsActive], [DateCreated] 
  FROM AppointmentsTypes 
  WHERE Id = @Id;
END

Update - AppointmentsTypes_Update

기존 예약 종류를 수정하는 Stored Procedure입니다. 이 Stored Procedure을 사용하면, AppointmentsTypes 테이블에서 기존 예약 종류의 이름을 수정할 수 있습니다.

CREATE PROCEDURE AppointmentsTypes_Update
  @Id INT,
  @AppointmentTypeName NVARCHAR(50)
AS
BEGIN
  UPDATE AppointmentsTypes
  SET 
    AppointmentTypeName = @AppointmentTypeName
  WHERE Id = @Id;
END

Delete - AppointmentsTypes_Delete

기존 예약 종류를 삭제하는 Stored Procedure입니다. 이 Stored Procedure을 사용하면, AppointmentsTypes 테이블에서 기존 예약 종류를 삭제할 수 있습니다.

CREATE PROCEDURE AppointmentsTypes_Delete
  @Id INT
AS
BEGIN
  DELETE FROM AppointmentsTypes
  WHERE Id = @Id;
END

위의 Stored Procedure들은 각각 AppointmentsTypes 테이블에 대한 Create, Read, Update, Delete 기능을 구현한 것입니다. 이러한 Stored Procedure들을 사용하면, 애플리케이션에서 AppointmentsTypes 테이블을 조작할 수 있으며, 데이터 일관성을 유지하면서 새로운 예약 종류를 추가하거나 수정, 삭제할 수 있습니다.

이상으로 AppointmentsTypes 테이블에 대한 CRUD를 구현한 Stored Procedure들에 대해 알아보았습니다. Stored Procedure들을 효율적으로 사용하여 AppointmentsTypes 테이블을 관리하면, 애플리케이션의 성능과 유지보수성을 향상시킬 수 있습니다.

WPF Window 만들기

AppointmentsTypes WPF 애플리케이션

이번에는 WPF 애플리케이션을 만들어서 데이터베이스와 상호 작용하는 방법에 대해 알아보겠습니다. 이번 예제에서는 AppointmentsTypes라는 예약 종류를 관리하는 애플리케이션을 만들어보겠습니다. 이 애플리케이션은 AppointmentsTypes 테이블에서 예약 종류를 조회, 추가, 수정, 삭제할 수 있도록 UI를 제공합니다.

VisualAcademy.Desktop WPF 프로젝트 구조

C:.
│  App.xaml
│  App.xaml.cs
│  AssemblyInfo.cs
│  MainWindow.xaml
│  MainWindow.xaml.cs
│  VisualAcademy.Desktop.csproj
│  VisualAcademy.Desktop.csproj.user
│
├─AppointmentsTypes
│      AddAppointmentTypeWindow.xaml
│      AddAppointmentTypeWindow.xaml.cs
│      AppointmentsTypesMainForm.xaml
│      AppointmentsTypesMainForm.xaml.cs
│      EditAppointmentTypeWindow.xaml
│      EditAppointmentTypeWindow.xaml.cs

VisualAcademy.Desktop 이름의 WPF 프로젝트는 위와 같은 구조로 구성되어 있습니다.

  • App.xaml, App.xaml.cs : WPF 애플리케이션의 시작점인 App 클래스와 관련된 파일입니다.
  • AssemblyInfo.cs : 어셈블리에 대한 정보를 담고 있는 파일입니다.
  • MainWindow.xaml, MainWindow.xaml.cs : 애플리케이션의 메인 윈도우와 관련된 파일입니다.
  • VisualAcademy.Desktop.csproj : 프로젝트 파일입니다.
  • VisualAcademy.Desktop.csproj.user : 개발자가 로컬 환경에서 프로젝트를 설정한 내용이 담긴 파일입니다.

AppointmentsTypes 폴더에는 예약 유형 관련 기능을 담당하는 코드 파일들이 위치해 있습니다. 각각 AddAppointmentTypeWindow.xaml, AddAppointmentTypeWindow.xaml.cs, AppointmentsTypesMainForm.xaml, AppointmentsTypesMainForm.xaml.cs, EditAppointmentTypeWindow.xaml, EditAppointmentTypeWindow.xaml.cs 파일이 있습니다. 이 파일들은 각각, 예약 유형을 추가하는 창, 예약 유형을 관리하는 메인 폼, 예약 유형을 수정하는 창과 관련된 코드를 구현합니다.

이러한 구조를 통해, 예약 유형 관련 기능을 담당하는 코드들이 AppointmentsTypes 폴더에 위치하여, 프로젝트의 유지보수성이 향상됩니다. 또한, App.xaml, App.xaml.cs, MainWindow.xaml, MainWindow.xaml.cs와 같은 프로젝트의 기본적인 파일들은 프로젝트 루트 폴더에 위치하여, 프로젝트의 전체적인 구조가 명확하게 드러납니다.

ShowDialog 메서드로 Window 열기

WPF 프로젝트 만들기

VisualAcademy 프로젝트에 AppointmentsTypes 폴더를 만들고 이곳에 AppointmentsTypesMainForm 이름으로 XAML 파일과 코드 비하인드 파일을 작성하겠습니다.

예약 종류를 입력하는 AddAppointmentTypeWindow 폼 만들기

AddAppointmentTypeWindow 창의 XAML과 코드 비하인드 코드

AddAppointmentTypeWindow에서는 사용자가 새로운 예약 종류의 이름을 입력할 수 있는 TextBox와 OK, Cancel 버튼을 포함합니다.

먼저, AddAppointmentTypeWindow의 XAML 코드를 작성합니다.

<Window x:Class="VisualAcademy.AppointmentsTypes.AddAppointmentTypeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Add Appointment Type" Height="150" Width="300">
    <StackPanel Margin="5">
        <TextBlock Text="Appointment Type Name" Margin="0 5 0 5"/>
        <TextBox Name="AppointmentTypeNameTextBox" Margin="0 0 0 10"/>
        <StackPanel Orientation="Horizontal">
            <Button Content="OK" Width="100" Margin="5" Click="OkButton_Click"/>
            <Button Content="Cancel" Width="100" Margin="5" Click="CancelButton_Click"/>
        </StackPanel>
    </StackPanel>
</Window>

위의 코드에서는 Window와 StackPanel을 사용하여 애플리케이션의 레이아웃을 구성합니다. TextBlock과 TextBox를 사용하여 사용자가 예약 종류의 이름을 입력할 수 있도록 합니다. 또한 OK, Cancel 버튼을 배치합니다.

이제 AddAppointmentTypeWindow의 코드 비하인드 파일을 작성합니다.

using System.Windows;

namespace VisualAcademy.AppointmentsTypes
{
    public partial class AddAppointmentTypeWindow : Window
    {
        public string AppointmentTypeName { get; private set; }

        public AddAppointmentTypeWindow()
        {
            InitializeComponent();
        }

        private void OkButton_Click(object sender, RoutedEventArgs e)
        {
            AppointmentTypeName = AppointmentTypeNameTextBox.Text;
            DialogResult = true;
        }

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

위의 코드에서는 AddAppointmentTypeWindow 클래스에서 OK, Cancel 버튼의 클릭 이벤트를 처리하는 함수를 구현합니다.

OK 버튼을 클릭하면 AppointmentTypeName 변수에 TextBox의 값을 저장하고, DialogResult를 true로 설정합니다. DialogResult를 true로 설정하면, 부모 창에서 ShowDialog() 함수를 호출한 곳에서 해당 창이 닫혔을 때 DialogResult를 확인하여 OK, Cancel 버튼을 구분할 수 있습니다.

Cancel 버튼을 클릭하면 DialogResult를 false로 설정합니다. 이로 인해 부모 창에서 ShowDialog() 함수를 호출한 곳에서 해당 창이 닫혔을 때 DialogResult가 false로 설정되어, 새로운 예약 종류를 추가하지 않게 됩니다.

이러한 방식으로 AddAppointmentTypeWindow에서 OK 버튼을 클릭했을 때 새로운 예약 종류를 추가하는 기능을 구현할 수 있습니다.

예약 종류 관리 앱의 수정 폼(Edit Form) UI 만들기

EditAppointmentTypeWindow 창의 XAML과 코드 비하인드 코드

EditAppointmentTypeWindow에서는 사용자가 선택한 예약 종류의 이름과 활성화 여부를 수정할 수 있는 TextBox와 CheckBox 그리고 OK, Cancel 버튼을 포함합니다.

실행 모양은 다음 그림과 같습니다.

EditAppointmentTypeWindow

먼저, EditAppointmentTypeWindow의 XAML 코드를 작성합니다.

<Window x:Class="VisualAcademy.Desktop.AppointmentsTypes.EditAppointmentTypeWindow"
        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:VisualAcademy.Desktop.AppointmentsTypes"
        mc:Ignorable="d"
        Title="Edit Appointment Type" Height="200" Width="300">
    <StackPanel Margin="5">
        <TextBlock Text="Appointment Type Name" Margin="0 5 0 5" />
        <TextBox Name="AppointmentTypeNameTextBox" Margin="0 0 0 10" />
        <StackPanel Orientation="Horizontal">
            <CheckBox Name="IsActiveCheckBox" Margin="0 0 10 0" Content="Active"></CheckBox>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <Button Content="OK" Width="100" Margin="5" Click="OkButton_Click" />
            <Button Content="Cancel" Width="100" Margin="5" Click="CancelButton_Click" />
        </StackPanel>
    </StackPanel>
</Window>

위의 코드에서는 Window와 StackPanel을 사용하여 애플리케이션의 레이아웃을 구성합니다. TextBlock과 TextBox를 사용하여 사용자가 예약 종류의 이름을 입력할 수 있도록 합니다. 또한 CheckBox를 사용하여 사용자가 예약 종류의 활성화 여부를 선택할 수 있도록 합니다. OK, Cancel 버튼을 배치합니다.

이제 EditAppointmentTypeWindow의 코드 비하인드 파일을 작성합니다.

using System.Windows;

namespace VisualAcademy.Desktop.AppointmentsTypes;

/// <summary>
/// EditAppointmentTypeWindow.xaml에 대한 상호 작용 논리
/// </summary>
public partial class EditAppointmentTypeWindow : Window
{
    // 약속 유형의 이름을 저장하는 속성
    public string AppointmentTypeName { get; private set; } = null!;
    // 약속 유형의 활성화 상태를 저장하는 속성
    public new bool IsActive { get; private set; }

    // 생성자를 사용하여 약속 유형 이름 및 활성화 상태를 초기화
    public EditAppointmentTypeWindow(string appointmentTypeName, bool isActive)
    {
        InitializeComponent();
        AppointmentTypeNameTextBox.Text = appointmentTypeName;
        IsActiveCheckBox.IsChecked = isActive;
    }

    // 확인 버튼 클릭 시 처리하는 이벤트 핸들러
    private void OkButton_Click(object sender, RoutedEventArgs e)
    {
        // 약속 유형 이름 및 활성화 상태를 저장
        AppointmentTypeName = AppointmentTypeNameTextBox.Text;
        IsActive = (IsActiveCheckBox.IsChecked == true);
        // 대화 상자 결과를 true로 설정하여 사용자가 확인 버튼을 클릭했음을 나타냄
        DialogResult = true;
    }

    // 취소 버튼 클릭 시 처리하는 이벤트 핸들러
    private void CancelButton_Click(object sender, RoutedEventArgs e)
    {
        // 대화 상자 결과를 false로 설정하여 사용자가 취소 버튼을 클릭했음을 나타냄
        DialogResult = false;
    }
}

위의 코드에서는 EditAppointmentTypeWindow 클래스에서 OK, Cancel 버튼의 클릭 이벤트를 처리하는 함수를 구현합니다.

EditAppointmentTypeWindow 생성자에서는 선택한 예약 종류의 이름과 활성화 여부를 TextBox와 CheckBox에 표시합니다.

OK 버튼을 클릭하면 AppointmentTypeName 변수에 TextBox의 값을, IsActive 변수에 CheckBox의 값을 저장하고, DialogResult를 true로 설정합니다. DialogResult를 true로 설정하면, 부모 창에서 ShowDialog() 함수를 호출한 곳에서 해당 창이 닫혔을 때 DialogResult를 확인하여 OK, Cancel 버튼을 구분할 수 있습니다.

Cancel 버튼을 클릭하면 DialogResult를 false로 설정합니다. 이로 인해 부모 창에서 ShowDialog() 함수를 호출한 곳에서 해당 창이 닫혔을 때 DialogResult가 false로 설정되어, 선택한 예약 종류의 이름과 활성화 여부를 수정하지 않게 됩니다.

이러한 방식으로 EditAppointmentTypeWindow에서 OK 버튼을 클릭했을 때 예약 종류의 이름과 활성화 여부를 수정하는 기능을 구현할 수 있습니다.

예약 종류의 모든 데이터를 읽어 출력하는 LoadData 메서드 구현하기

AppointmentsTypesMainForm 창의 XAML과 코드 비하인드 코드

프로젝트에 Models 폴더를 만들고 AppointmentType.cs 이름의 모델 클래스를 다음과 같이 생성합니다.

#nullable disable

namespace VisualAcademy.Desktop.Models 
{
    public class AppointmentType 
    {
        public int Id { get; set; }
        public string AppointmentTypeName { get; set; }
        public bool IsActive { get; set; } = true;
        public DateTime DateCreated { get; set; } = DateTime.Now;
    }
}

AppointmentsTypesMainForm에서는 AppointmentsTypes 테이블에서 예약 종류를 조회, 추가, 수정, 삭제할 수 있도록 UI를 제공합니다.

먼저, AppointmentsTypesMainForm의 XAML 코드를 작성합니다.

<Window x:Class="VisualAcademy.AppointmentsTypes.AppointmentsTypesMainForm"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Appointment Types" Height="400" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Margin="5">
            <Button Name="AddButton" Content="Add" Width="100" Margin="5" Click="AddButton_Click"/>
            <Button Name="EditButton" Content="Edit" Width="100" Margin="5" Click="EditButton_Click" IsEnabled="False"/>
            <Button Name="DeleteButton" Content="Delete" Width="100" Margin="5" Click="DeleteButton_Click" IsEnabled="False"/>
        </StackPanel>

        <ListView Grid.Row="1" Name="AppointmentTypesListView" Margin="5"
                  SelectionChanged="AppointmentTypesListView_SelectionChanged">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Id" DisplayMemberBinding="{Binding Id}"/>
                    <GridViewColumn Header="AppointmentTypeName" DisplayMemberBinding="{Binding AppointmentTypeName}"/>
                    <GridViewColumn Header="IsActive" DisplayMemberBinding="{Binding IsActive}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

위의 코드에서는 Window, Grid, StackPanel, Button, ListView를 사용하여 애플리케이션의 레이아웃을 구성합니다. StackPanel에서는 Add, Edit, Delete 버튼을 배치합니다. ListView에서는 Id, Name, IsActive 컬럼을 포함한 예약 종류 목록을 출력합니다.

AddButton에 대한 클릭 이벤트에 저장 로직 추가

이제 AppointmentsTypesMainForm의 코드 비하인드 파일을 작성합니다.

여기서는 ADO.NET을 사용하여 데이터베이스와 상호 작용합니다.

using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Windows;

namespace VisualAcademy.AppointmentsTypes
{
    public partial class AppointmentsTypesMainForm : Window
    {
        private readonly List<AppointmentType> _appointmentTypes;

        private readonly string _connectionString = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=AppointmentDatabase;Integrated Security=True";

        public AppointmentsTypesMainForm()
        {
            InitializeComponent();

            _appointmentTypes = new List<AppointmentType>();
            AppointmentTypesListView.ItemsSource = _appointmentTypes;
            LoadData();
        }

        private void LoadData()
        {
            _appointmentTypes.Clear();
            using (var connection = new SqlConnection(_connectionString))
            {
                connection.Open();

                var command = new SqlCommand("SELECT Id, AppointmentTypeName, IsActive FROM AppointmentsTypes", connection);

                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        var id = (int)reader["Id"];
                        var name = (string)reader["AppointmentTypeName"];
                        var isActive = (bool)reader["IsActive"];

                        var appointmentType = new AppointmentType
                        {
                            Id = id,
                            Name = name,
                            IsActive = isActive
                        };
                        _appointmentTypes.Add(appointmentType);
                    }
                }
            }
            AppointmentTypesListView.Items.Refresh();
        }

        private void AddButton_Click(object sender, RoutedEventArgs e)
        {
            var addWindow = new AddAppointmentTypeWindow();
            if (addWindow.ShowDialog() == true)
            {
                using (var connection = new SqlConnection(_connectionString))
                {
                    connection.Open();

                    var command = new SqlCommand("INSERT INTO AppointmentsTypes (AppointmentTypeName, IsActive, DateCreated) VALUES (@AppointmentTypeName, @IsActive, @DateCreated)", connection);

                    command.Parameters.AddWithValue("@AppointmentTypeName", addWindow.AppointmentTypeName);
                    command.Parameters.AddWithValue("@IsActive", true);
                    command.Parameters.AddWithValue("@DateCreated", DateTime.Now);

                    command.ExecuteNonQuery();
                }
                LoadData();
            }
        }

        private void EditButton_Click(object sender, RoutedEventArgs e)
        {
            var appointmentType = (AppointmentType)AppointmentTypesListView.SelectedItem;
            if (appointmentType == null)
            {
                return;
            }

            var editWindow = new EditAppointmentTypeWindow(appointmentType.Id, appointmentType.Name, appointmentType.IsActive);
            if (editWindow.ShowDialog() == true)
            {
                using (var connection = new SqlConnection(_connectionString))
                {
                    connection.Open();

                    var command = new SqlCommand("UPDATE AppointmentsTypes SET AppointmentTypeName=@AppointmentTypeName, IsActive=@IsActive WHERE Id=@Id", connection);

                    command.Parameters.AddWithValue("@Id", appointmentType.Id);
                    command.Parameters.AddWithValue("@AppointmentTypeName", editWindow.AppointmentTypeName);
                    command.Parameters.AddWithValue("@IsActive", editWindow.IsActive);

                    command.ExecuteNonQuery();
                }
                LoadData();
            }
        }

        private void DeleteButton_Click(object sender, RoutedEventArgs e)
        {
            var appointmentType = (AppointmentType)AppointmentTypesListView.SelectedItem;
            if (appointmentType == null)
            {
                return;
            }

            if (MessageBox.Show("Are you sure you want to delete this appointment type?", "Delete", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
            {
                using (var connection = new SqlConnection(_connectionString))
                {
                    connection.Open();

                    var command = new SqlCommand("DELETE FROM AppointmentsTypes WHERE Id=@Id", connection);

                    command.Parameters.AddWithValue("@Id", appointmentType.Id);

                    command.ExecuteNonQuery();
                }
                LoadData();
            }
        }

        private void AppointmentTypesListView_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            var appointmentType = (AppointmentType)AppointmentTypesListView.SelectedItem;
            if (appointmentType == null)
            {
                EditButton.IsEnabled = false;
                DeleteButton.IsEnabled = false;
            }
            else
            {
                EditButton.IsEnabled = true;
                DeleteButton.IsEnabled = true;
            }
        }
    }
}

위 코드에서는 LoadData() 메서드를 사용하여 AppointmentsTypes 테이블에서 데이터를 조회합니다. AddButton_Click() 메서드를 사용하여 새로운 예약 종류를 추가하고, EditButton_Click() 메서드와 DeleteButton_Click() 메서드를 사용하여 예약 종류를 수정하고 삭제합니다.

이제 AppointmentsTypesMainForm을 실행하여 예약 종류를 조회, 추가, 수정, 삭제할 수 있습니다.

저장 프로시저를 사용하여 CRUD 구현

저장 프로시저를 사용하는 ADO.NET 코드로 AppointmentsTypesMainForm의 코드 비하인드를 변경하는 방법을 설명하겠습니다.

먼저, App.config 파일에 데이터베이스 연결 문자열을 추가합니다. 이때 연결 문자열을 <connectionStrings> 태그 안에 작성해야 합니다.

<connectionStrings>
    <add name="AppointmentTypesDb" connectionString="Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=AppointmentTypes;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False" providerName="System.Data.SqlClient" />
</connectionStrings>

위 연결 문자열은 로컬 데이터베이스 (localdb)\MSSQLLocalDB에 AppointmentTypes라는 데이터베이스를 사용하고, Windows 인증을 사용하여 연결하는 것입니다.

이제 AppointmentsTypesMainForm의 코드 비하인드를 수정합니다. 이 예제에서는 다음과 같은 저장 프로시저를 사용합니다.

  • AppointmentsTypes_GetAll: AppointmentsTypes 테이블에서 모든 예약 종류 데이터를 가져옵니다.
  • AppointmentsTypes_Insert: AppointmentsTypes 테이블에 새로운 예약 종류 데이터를 추가합니다.
  • AppointmentsTypes_Update: AppointmentsTypes 테이블의 예약 종류 데이터를 수정합니다.
  • AppointmentsTypes_Delete: AppointmentsTypes 테이블에서 예약 종류 데이터를 삭제합니다.

저장 프로시저를 호출하는 코드는 다음과 같습니다.

private void LoadData()
{
    try
    {
        using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["AppointmentTypesDb"].ConnectionString))
        using (var cmd = new SqlCommand("AppointmentsTypes_GetAll", conn))
        using (var da = new SqlDataAdapter(cmd))
        {
            var dt = new DataTable();
            da.Fill(dt);
            appointmentsTypesDataGrid.ItemsSource = dt.DefaultView;
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("An error occurred while loading the data: " + ex.Message);
    }
}

private void AddButton_Click(object sender, RoutedEventArgs e)
{
    var addWindow = new AddAppointmentTypeWindow();
    if (addWindow.ShowDialog() == true)
    {
        var name = addWindow.NameTextBox.Text;
        var isActive = addWindow.IsActiveCheckBox.IsChecked ?? false;
        var dateCreated = DateTime.Now;

        try
        {
            using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["AppointmentTypesDb"].ConnectionString))
            using (var cmd = new SqlCommand("AppointmentsTypes_Insert", conn))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@AppointmentTypeName", name);
                cmd.Parameters.AddWithValue("@IsActive", isActive);
                cmd.Parameters.AddWithValue("@DateCreated", dateCreated);
                conn.Open();
                cmd.ExecuteNonQuery();
            }

            LoadData();
        }
        catch (Exception ex)
        {
            MessageBox.Show("An error occurred while adding the data: " + ex.Message);
        }
    }
}

위 코드에서 EditButton_Click 메서드도 저장 프로시저를 사용하도록 수정합니다.

private void EditButton_Click(object sender, RoutedEventArgs e)
{
    var selectedRow = appointmentsTypesDataGrid.SelectedItem as DataRowView;
    if (selectedRow == null)
    {
        MessageBox.Show("Please select a row to edit.");
        return;
    }

    var id = (int)selectedRow["Id"];
    var name = (string)selectedRow["AppointmentTypeName"];
    var isActive = (bool)selectedRow["IsActive"];

    var editWindow = new EditAppointmentTypeWindow();
    editWindow.NameTextBox.Text = name;
    editWindow.IsActiveCheckBox.IsChecked = isActive;
    if (editWindow.ShowDialog() == true)
    {
        name = editWindow.NameTextBox.Text;
        isActive = editWindow.IsActiveCheckBox.IsChecked ?? false;

        try
        {
            using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["AppointmentTypesDb"].ConnectionString))
            using (var cmd = new SqlCommand("AppointmentsTypes_Update", conn))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@Id", id);
                cmd.Parameters.AddWithValue("@AppointmentTypeName", name);
                cmd.Parameters.AddWithValue("@IsActive", isActive);
                conn.Open();
                cmd.ExecuteNonQuery();
            }

            LoadData();
        }
        catch (Exception ex)
        {
            MessageBox.Show("An error occurred while editing the data: " + ex.Message);
        }
    }
}

DeleteButton_Click 메서드도 저장 프로시저를 사용하도록 수정합니다.

private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
    var selectedRow = appointmentsTypesDataGrid.SelectedItem as DataRowView;
    if (selectedRow == null)
    {
        MessageBox.Show("Please select a row to delete.");
        return;
    }

    var id = (int)selectedRow["Id"];

    if (MessageBox.Show("Are you sure you want to delete this item?", "Confirm", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
    {
        try
        {
            using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["AppointmentTypesDb"].ConnectionString))
            using (var cmd = new SqlCommand("AppointmentsTypes_Delete", conn))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@Id", id);
                conn.Open();
                cmd.ExecuteNonQuery();
            }

            LoadData();
        }
        catch (Exception ex)
        {
            MessageBox.Show("An error occurred while deleting the data: " + ex.Message);
        }
    }
}

이제 AppointmentsTypesMainForm은 저장 프로시저를 사용하여 데이터베이스에 접근하도록 변경되었습니다.

appsettings.json 파일 사용

WPF 6.0에서는 .NET Core의 기본적인 웹 호스팅 모델을 사용하기 때문에 Program.cs 파일 대신에 App.xaml.cs 파일에서 호스팅 코드를 작성해야 합니다. 이를 위해서는 다음과 같은 단계를 따릅니다.

  1. appsettings.json 파일 생성
  • 프로젝트의 루트 디렉토리에 appsettings.json 파일을 생성합니다.
  • 파일 안에는 다음과 같이 ConnectionStrings 객체를 추가합니다.
{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=your_server;Initial Catalog=your_database;Integrated Security=True;"
  }
}
  1. 프로젝트 설정
  • 프로젝트 파일 (.csproj)을 열고 Microsoft.Extensions.Configuration.Json NuGet 패키지가 설치되어 있는지 확인합니다.
  • 없다면 패키지 매니저 콘솔에서 다음 명령어를 실행하여 설치합니다.
dotnet add package Microsoft.Extensions.Configuration.Json
  1. App.xaml.cs 파일 수정
  • App.xaml.cs 파일에 다음 코드를 추가합니다.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

public partial class App : Application
{
    public App()
    {
        Host.CreateDefaultBuilder()
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddDbContext<YourDbContext>(options =>
                    options.UseSqlServer(hostContext.Configuration.GetConnectionString("DefaultConnection")));
            })
            .Build()
            .Run();
    }
}

심플한 버전은 다음 코드 형태를 사용하면 됩니다.

using System.Windows;
using Microsoft.Extensions.Configuration;

namespace WebSiteRatings
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public static IConfiguration Config { get; private set; }

        public App()
        {
            Config = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();
        }
    }
}

위 코드로 설정된 상태라면, 다음과 같이 App.Config.GetConnectionString()으로 접근할 수 있습니다.

using Microsoft.Data.Sqlite;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;

namespace WebSiteRatings.Models
{
    internal class Database
    {
        public static SqliteConnection OpenConnection() =>
            new SqliteConnection(App.Config.GetConnectionString("database"));

        // More code below...
  1. DbContext 파일 수정
  • DbContext 파일에는 다음 코드를 추가합니다.
public class YourDbContext : DbContext
{
    public YourDbContext(DbContextOptions<YourDbContext> options) : base(options)
    {
    }

    // DbSet 및 모델 구성 등 추가
}

이제 Connection String은 appsettings.json 파일에 저장되며, App.xaml.cs에서 Configuration 객체를 사용하여 읽을 수 있습니다. DbContext 생성자에서 Configuration 객체를 사용하여 Connection String을 DbContextOptions 객체로 전달합니다. 이렇게 함으로써 Connection String이 프로젝트 내의 여러 위치에 중복되지 않고 일관적으로 사용됩니다.

동영상 강의

https://youtu.be/yDYx6sfhAIQ

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com