소규모 프로젝트에서는 SQLite와 같은 전용 데이터베이스를 구축하지 않고도 INI 설정 파일을 사용하여 기본적인 데이터베이스 기능을 구현할 수 있습니다. 이 문서에서는 Windows Forms를 이용해 INI 파일 기반의 최소한의 CRUD(생성, 조회, 수정, 삭제) 기능을 갖춘 간단한 GUI 데이터베이스 시스템을 설계합니다.
INI 파일 구조
INI 파일은 다음과 같은 형식으로 구성됩니다:
[섹션1] 키1=값1 키2=값2 [섹션2] 키3=값3 키4=값4 ...
GUI 구성 요소
데이터베이스를 시각화하기 위해 ComboBox 컨트롤을 사용합니다. 주요 인터페이스 요소는 다음과 같습니다:
- cmb_region: 섹션 목록 표시
- cmb_item: 선택된 섹션의 키 목록 표시
- tbx_data: 값 입력/수정 필드
핵심 기능 구현
1. 모든 섹션 목록 가져오기
public void ExtractAllSections(string fileName, out List<string> sectionCollection)
{
sectionCollection = new List<string>();
string content = "";
try
{
using (StreamReader reader = new StreamReader(fileName, Encoding.Default))
{
content = reader.ReadToEnd();
}
}
catch (Exception)
{
MessageBox.Show($"파일 {fileName}을(를) 읽을 수 없습니다.");
return;
}
try
{
string[] segments = content.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string segment in segments)
{
string[] parts = segment.Split(new char[] { ']' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 0 && !string.IsNullOrWhiteSpace(parts[0]))
{
sectionCollection.Add(parts[0].Trim());
}
}
}
catch (Exception)
{
// 예외 처리
}
}
2. 특정 섹션의 모든 키 추출
public void ExtractKeysInSection(string fileName, out List<string> keyCollection, int sectionIndex)
{
keyCollection = new List<string>();
try
{
string content;
using (StreamReader reader = new StreamReader(fileName, Encoding.Default))
{
content = reader.ReadToEnd();
}
string[] sections = content.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries);
if (sectionIndex + 1 >= sections.Length) return;
string[] sectionParts = sections[sectionIndex + 1].Split(new char[] { ']' }, StringSplitOptions.RemoveEmptyEntries);
if (sectionParts.Length < 2) return;
string keyValueContent = sectionParts[1];
keyValueContent = keyValueContent.Replace("\r\n", "|");
string[] entries = keyValueContent.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string entry in entries)
{
if (!string.IsNullOrWhiteSpace(entry))
{
string[] keyValue = entry.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
if (keyValue.Length > 0 && !string.IsNullOrWhiteSpace(keyValue[0]))
{
keyCollection.Add(keyValue[0].Trim());
}
}
}
}
catch (Exception)
{
MessageBox.Show($"파일 {fileName} 처리 중 오류가 발생했습니다.");
}
}
3. 리스트 항목 ComboBox에 추가
public void PopulateComboBox(List<string> dataSource, ComboBox targetCombo)
{
targetCombo.Items.Clear();
foreach (string item in dataSource)
{
targetCombo.Items.Add(item);
}
}
전체 애플리케이션 코드
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace SimpleIniDatabase
{
public partial class DatabaseForm : Form
{
private const string DATA_FILE = "database.ini";
public DatabaseForm()
{
InitializeComponent();
this.StartPosition = FormStartPosition.CenterScreen;
}
private void DatabaseForm_Load(object sender, EventArgs e)
{
RefreshSections();
}
private void RefreshSections()
{
List<string> sections = new List<string>();
List<string> firstSectionKeys = new List<string>();
ExtractAllSections(DATA_FILE, out sections);
PopulateComboBox(sections, cmb_region);
if (sections.Count > 0)
{
ExtractKeysInSection(DATA_FILE, out firstSectionKeys, 0);
PopulateComboBox(firstSectionKeys, cmb_item);
}
}
private void cmb_region_SelectedIndexChanged(object sender, EventArgs e)
{
int selectedIndex = cmb_region.SelectedIndex;
cmb_item.Items.Clear();
cmb_item.Text = "";
List<string> keys = new List<string>();
ExtractKeysInSection(DATA_FILE, out keys, selectedIndex);
PopulateComboBox(keys, cmb_item);
}
private void btn_delete_Click(object sender, EventArgs e)
{
try
{
string currentSection = cmb_region.Text;
string currentKey = cmb_item.Text;
string currentValue = ReadValue(DATA_FILE, currentSection, currentKey);
string entryToRemove = $"{currentKey}={currentValue}";
string fileContent;
using (StreamReader reader = new StreamReader(DATA_FILE, Encoding.Default))
{
fileContent = reader.ReadToEnd();
}
fileContent = fileContent.Replace(entryToRemove, "");
using (StreamWriter writer = new StreamWriter(DATA_FILE, false, Encoding.Default))
{
writer.Write(fileContent);
}
MessageBox.Show("항목이 성공적으로 삭제되었습니다.");
RefreshSections();
}
catch (Exception ex)
{
MessageBox.Show($"삭제 작업 중 오류 발생: {ex.Message}");
}
}
private void btn_update_Click(object sender, EventArgs e)
{
string section = cmb_region.Text.Trim();
string key = cmb_item.Text.Trim();
string value = tbx_data.Text.Trim();
WriteValue(DATA_FILE, section, key, value);
MessageBox.Show("데이터가 성공적으로 업데이트되었습니다.");
}
// 섹션 추출 메서드
public void ExtractAllSections(string fileName, out List<string> sectionCollection)
{
sectionCollection = new List<string>();
string content = "";
try
{
using (StreamReader reader = new StreamReader(fileName, Encoding.Default))
{
content = reader.ReadToEnd();
}
}
catch (Exception)
{
MessageBox.Show($"파일 {fileName}을(를) 읽을 수 없습니다.");
return;
}
try
{
string[] segments = content.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string segment in segments)
{
string[] parts = segment.Split(new char[] { ']' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 0 && !string.IsNullOrWhiteSpace(parts[0]))
{
sectionCollection.Add(parts[0].Trim());
}
}
}
catch (Exception)
{
// 예외 처리
}
}
// 키 추출 메서드
public void ExtractKeysInSection(string fileName, out List<string> keyCollection, int sectionIndex)
{
keyCollection = new List<string>();
try
{
string content;
using (StreamReader reader = new StreamReader(fileName, Encoding.Default))
{
content = reader.ReadToEnd();
}
string[] sections = content.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries);
if (sectionIndex + 1 >= sections.Length) return;
string[] sectionParts = sections[sectionIndex + 1].Split(new char[] { ']' }, StringSplitOptions.RemoveEmptyEntries);
if (sectionParts.Length < 2) return;
string keyValueContent = sectionParts[1];
keyValueContent = keyValueContent.Replace("\r\n", "|");
string[] entries = keyValueContent.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string entry in entries)
{
if (!string.IsNullOrWhiteSpace(entry))
{
string[] keyValue = entry.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
if (keyValue.Length > 0 && !string.IsNullOrWhiteSpace(keyValue[0]))
{
keyCollection.Add(keyValue[0].Trim());
}
}
}
}
catch (Exception)
{
MessageBox.Show($"파일 {fileName} 처리 중 오류가 발생했습니다.");
}
}
// ComboBox 채우기 메서드
public void PopulateComboBox(List<string> dataSource, ComboBox targetCombo)
{
targetCombo.Items.Clear();
foreach (string item in dataSource)
{
targetCombo.Items.Add(item);
}
}
#region INI 파일 읽기/쓰기 API
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section, string key, string defaultValue,
StringBuilder result, int size, string filePath);
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section, string key, string value, string filePath);
public string ReadValue(string fileName, string section, string key)
{
var buffer = new StringBuilder(255);
string fullPath = Path.Combine(Environment.CurrentDirectory, fileName);
GetPrivateProfileString(section, key, "", buffer, 255, fullPath);
return buffer.ToString();
}
public void WriteValue(string fileName, string section, string key, string value)
{
string fullPath = Path.Combine(Environment.CurrentDirectory, fileName);
WritePrivateProfileString(section, key, value, fullPath);
}
#endregion
}
}
참고: 위 코드는 학습 목적의 최소 구현이며, 실제 프로덕션 환경에서는 오류 처리 및 성능 최적화가 필요합니다.