首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 讀寫 INI 檔案完整指南 (二):關鍵步驟、按鈕事件實作與錯誤處理

     編輯:關於C語言

承接上篇核心 API,本文聚焦WinForms 實戰場景:介面佈局、按鈕事件處理、路徑選擇、讀寫流程與異常處理。提供可直接編譯執行的完整表單代碼,展示「選擇檔案 → 輸入區段/鍵/值 → 寫入/讀取 → 回饋結果」的標準交互閉環。

WinForms 介面設計

控制項NameText/屬性用途
TextBoxtxtFilePathReadOnly=true顯示 INI 完整路徑
ButtonbtnBrowse「選擇 INI 檔案」開啟 OpenFileDialog
TextBoxtxtSection輸入區段名稱
TextBoxtxtKey輸入鍵名稱
TextBoxtxtValue輸入/顯示值
ButtonbtnWrite「寫入 INI」觸發寫入
ButtonbtnRead「讀取 INI」觸發讀取
LabellblStatusAutoSize=true狀態訊息

OpenFileDialog 設定


private readonly OpenFileDialog _openDlg = new OpenFileDialog
{
    Filter = "INI 檔案 (*.ini)|*.ini|所有檔案 (*.*)|*.*",
    Title = "選擇或建立 INI 設定檔",
    CheckFileExists = false, // 允許輸入新檔名
    AddExtension = true,
    DefaultExt = "ini"
};

完整表單代碼 (Form1.cs)


using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

public partial class IniForm : Form
{
    // P/Invoke 宣告 (同上篇)
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool WritePrivateProfileString(string section, string key, string value, string filePath);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern uint GetPrivateProfileString(string section, string key, string defaultValue, StringBuilder retVal, uint size, string filePath);

    private readonly OpenFileDialog _openDlg = new OpenFileDialog
    {
        Filter = "INI Files (*.ini)|*.ini|All Files (*.*)|*.*",
        CheckFileExists = false,
        AddExtension = true,
        DefaultExt = "ini"
    };

    public IniForm()
    {
        InitializeComponent(); // 設計器生成的控制項初始化
    }

    // 選擇檔案
    private void btnBrowse_Click(object sender, EventArgs e)
    {
        if (_openDlg.ShowDialog() == DialogResult.OK)
            txtFilePath.Text = _openDlg.FileName;
    }

    // 寫入 INI
    private void btnWrite_Click(object sender, EventArgs e)
    {
        try
        {
            string path = txtFilePath.Text.Trim();
            if (string.IsNullOrEmpty(path))
            { ShowError("請先選擇 INI 檔案路徑"); return; }

            string section = txtSection.Text.Trim();
            string key = txtKey.Text.Trim();
            string value = txtValue.Text; // 值可為空字串

            if (string.IsNullOrEmpty(section) || string.IsNullOrEmpty(key))
            { ShowError("區段與鍵名稱不可為空"); return; }

            if (!WritePrivateProfileString(section, key, value, path))
                ThrowWin32Error("WritePrivateProfileString");

            ShowSuccess($"成功寫入:[{section}] {key} = {value}");
        }
        catch (Exception ex)
        {
            ShowError($"寫入失敗:{ex.Message}");
        }
    }

    // 讀取 INI
    private void btnRead_Click(object sender, EventArgs e)
    {
        try
        {
            string path = txtFilePath.Text.Trim();
            if (string.IsNullOrEmpty(path))
            { ShowError("請先選擇 INI 檔案路徑"); return; }

            string section = txtSection.Text.Trim();
            string key = txtKey.Text.Trim();

            if (string.IsNullOrEmpty(section) || string.IsNullOrEmpty(key))
            { ShowError("區段與鍵名稱不可為空"); return; }

            var sb = new StringBuilder(256);
            uint len = GetPrivateProfileString(section, key, "【讀取失敗:找不到鍵】", sb, (uint)sb.Capacity, path);

            // 緩衝區不足自動擴容
            if (len == sb.Capacity - 1)
            {
                sb.Capacity = (int)(len * 2);
                GetPrivateProfileString(section, key, "【讀取失敗:找不到鍵】", sb, (uint)sb.Capacity, path);
            }

            string value = sb.ToString();
            txtValue.Text = value;
            ShowSuccess(value == "【讀取失敗:找不到鍵】" ? "鍵不存在,已顯示預設值" : $"讀取成功:{value}");
        }
        catch (Exception ex)
        {
            ShowError($"讀取失敗:{ex.Message}");
        }
    }

    private void ShowSuccess(string msg)
    {
        lblStatus.Text = msg;
        lblStatus.ForeColor = Color.Green;
    }

    private void ShowError(string msg)
    {
        lblStatus.Text = msg;
        lblStatus.ForeColor = Color.Red;
        MessageBox.Show(msg, "錯誤", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    private static void ThrowWin32Error(string api)
    {
        int err = Marshal.GetLastWin32Error();
        throw new System.ComponentModel.Win32Exception(err, $"{api} failed (0x{err:X8})");
    }
}

關鍵邏輯細節解析

1. 寫入前驗證層層把關

  • 路徑非空 → OpenFileDialog 保證合法路徑格式
  • 區段/鍵非空 → API 允許空值但無意義
  • 值允許空字串 → 對應刪除鍵行為 (若傳 null 會刪除鍵,空字串保留鍵=空)

2. 讀取緩衝區動態擴容


// 首次 256 字元
uint len = GetPrivateProfileString(..., sb, 256, ...);
// 若 len == 255 → 緩衝區滿 (保留 1 字元給 \0)
if (len == sb.Capacity - 1)
{
    sb.Capacity = (int)(len * 2); // 雙倍擴容
    GetPrivateProfileString(..., sb, (uint)sb.Capacity, ...);
}

避免長值被截斷,單次 API 呼叫即可完成 (極少需再次擴容)。

3. 預設值語義區分

場景lpDefault 傳入行為
鍵不存在"預設值"回傳 "預設值"
鍵存在但值為空"預設值"回傳空字串 (非預設值)
區段不存在"預設值"回傳 "預設值"

本範例傳入明確標記字串 「【讀取失敗:找不到鍵】」,方便判斷是否命中預設值。

4. Win32 錯誤碼轉換為 .NET 例外


if (!WritePrivateProfileString(...))
    ThrowWin32Error("WritePrivateProfileString"); // 拋 Win32Exception 含錯誤碼

常見錯誤碼:

  • 0x00000002 (ERROR_FILE_NOT_FOUND):目錄不存在
  • 0x00000005 (ERROR_ACCESS_DENIED):唯讀/權限不足
  • 0x00000020 (ERROR_SHARING_VIOLATION):檔案被其他進程獨佔

測試驗證步驟

  1. 執行程式 → 點「選擇 INI 檔案」 → 輸入 C:\Temp\test.ini (目錄需存在)
  2. 區段:Database,鍵:ConnectionString,值:Server=.;Database=Test;Uid=sa;Pwd=123 → 點「寫入 INI」
  3. 清空值欄位 → 點「讀取 INI」 → 值欄位顯示連線字串,狀態綠字「讀取成功」
  4. 修改值 → 再次寫入 → 讀取驗證更新
  5. 刪除鍵測試:值欄位留空 → 寫入 → 讀取 → 顯示空字串 (鍵存在、值為空)
  6. 刪除區段測試:區段輸入 Database,鍵留空 → 呼叫 WritePrivateProfileString("Database", null, null, path) → 整區段消失

生產環境強化清單

  • ☐ 路徑輸入改為 FolderBrowserDialog + 檔名輸入框,避免手動輸入錯誤
  • ☐ 新增「另存新檔」功能:複製現有 INI 到新路徑
  • ☐ 區段/鍵下拉選單:讀取現有 INI 結構 (GetPrivateProfileString(null, null, ...)) 供選擇
  • ☐ 多執行緒鎖:lock (_sync) 包裹讀寫,防止並發損壞
  • ☐ 備份機制:寫入前自動 File.Copy(path, path + ".bak", true)
  • ☐ 編碼偵測:讀取前檢測 BOM,自動切換 CharSet.Ansi/Unicode

結語:WinForms 操作 INI 的核心流程已完整呈現。關鍵在於輸入驗證、緩衝區擴容、預設值語義區分、Win32 錯誤碼轉換。下一篇將封裝為強型別設定類別,實現 IniConfig.Bind<T>() 體驗,並整合進 ASP.NET Core IConfiguration 體系。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved