C# 如何實現完整的INI文件讀寫類

来源:https://www.cnblogs.com/ps3online/archive/2020/02/28/INIFile.html
-Advertisement-
Play Games

作者: 魔法軟糖 日期: 2020-02-27 引言 ************************************* .ini 文件是Initialization File的縮寫,即配置文件 。是windows的系統配置文件所採用的存儲格式。 它具有方便易用的特點,和註冊表的鍵值有著類似 ...


 

作者: 魔法軟糖

日期: 2020-02-27

引言

*************************************

.ini 文件是Initialization File的縮寫,即配置文件 。windows的系統配置文件所採用的存儲格式。

它具有方便易用的特點,和註冊表的鍵值有著類似的功能,各類應用程式也經常使用INI保存各種配置和選項。

在簡單隻需要讀取的場合,調用WINDOWS API就行,但在複雜的需要可視化編輯時,就需要建立自己的處理類了。

 

 

 

如何實現自己的INI類

首先我們需要瞭解

◇  INI的格式

 

 

◇  典型INI文件

;項目註釋
[.ShellClassInfo]
InfoTip=有圖標的文件夾
;圖標資源
IconResource="C:\Windows\system32\SHELL32.dll",4
#文件夾視圖
[ViewState]
Mode=
Vid=
FolderType=General
#尾部註釋

 一個典型INI文件由節、註釋和節下麵的項組成,而項為鍵=值的形式。

INI文件的註釋符號有兩種,規範為;分號,實際有些地方用#井號。

◇  保留註釋

為了在修改INI文件時不丟失任何信息,所以需要保存INI文件中所有有效元素、包括註釋甚至是無效行。

為了實現這個目的,將所有註釋和無效行都歸屬於它之後的有效元素。

以上面的desktop.ini為例,

  • 第一行 ;項目註釋歸屬於[.ShellClassInfo]
  • 第四行;圖標資源歸屬於IconResource=
  • #文件夾視圖歸屬於[ViewState]
  • 最後的#尾部註釋歸屬於整個INI文檔的EndText

◇  INIItem類

表示INI文件中節點下麵的項,它擁有三個屬性:名稱Name、值Value和註釋Comment

 1     /// <summary>
 2     /// 擁有名稱、值和註釋
 3     /// </summary>
 4     public class INIItem {
 5         /// <summary>
 6         /// 實例化INIItem。指定名稱、值和註釋。
 7         /// </summary>
 8         /// <param name="vName"></param>
 9         /// <param name="vValue"></param>
10         /// <param name="vComment"></param>
11         public INIItem(string vName, string vValue, string vComment = "") {
12             Name = vName;
13             Value = vValue;
14             Comment = vComment;
15         }
16         /// <summary>
17         /// 項名稱。例如 Color = 202,104,0 中的 Color
18         /// </summary>
19         public string Name { get; set; }
20         /// <summary>
21         /// 值內容。例如 Color = 202,104,0 中的 202,104,0
22         /// </summary>
23         public string Value { get; set; }
24         /// <summary>
25         /// 位於前面的所有註釋行。一般以 ; 開頭
26         /// </summary>
27         public string Comment { get; set; }
28         /// <summary>
29         /// 返回 INIItem 的文本形式。〈<see cref="string"/>30         /// <para>Name=Value</para>
31         /// </summary>
32         /// <returns>〈string〉返回 INIItem 的文本形式。</returns>
33         public override string ToString() {
34             return Name + INI.U等號 + Value;
35         }        
36     }

◇  ININode類

表示INI文件中的一個節點,它擁有項列表List{Of INIItem}、名稱Name和註釋Comment。

 1     /// <summary>
 2     /// 表示INI文件的一個節點,它擁有一個項目列表,還擁有名稱和註釋
 3     /// <para></para>
 4     /// </summary>
 5     public class ININode {
 6         /// <summary>
 7         /// 實例化ININode。指定初始的名稱和註釋。
 8         /// </summary>
 9         /// <param name="vName"></param>
10         /// <param name="vComment"></param>
11         public ININode(string vName, string vComment) { Name = vName; Comment = vComment; Items = new List<INIItem>(); }
12         /// <summary>
13         /// 節點名稱。例如 [Config]
14         /// </summary>
15         public string Name { get; set; }
16         /// <summary>
17         /// 位於前面的所有註釋行。一般以 ; 開頭
18         /// </summary>
19         public string Comment { get; set; }
20         /// <summary>
21         /// 含有的項列表
22         /// </summary>
23         public List<INIItem> Items { get; set; }
24         /// <summary>
25         /// 向本節點添加新項。
26         /// </summary>
27         /// <param name="vName"></param>
28         /// <param name="vValue"></param>
29         /// <param name="vComment"></param>
30         /// <returns></returns>
31         public INIItem New(string vName, string vValue, string vComment = "") {
32             var k = new INIItem(vName, vValue, vComment);
33             Items.Add(k);
34             return k;
35         }
36         /// <summary>
37         /// 返回 ININode的文本形式。〈<see cref="string"/>38         /// <para>[Name]</para>
39         /// </summary>
40         /// <returns>〈string〉返回 ININode 的文本形式。</returns>
41         public override string ToString() {
42             return INI.U左括弧 + Name + INI.U右括弧;
43         }
44     }

◇  INI類

它表示整個INI文件的全部內,擁有List{Of ININode}、EndText、FileName、StartLine等屬性

 1     /// <summary>
 2     /// 表示INI文件。擁有讀取和寫入文件的方法。
 3     /// <para>儲存在 <see cref="List{ININode}"/>&lt;<see cref="ININode"/>&gt;</para>
 4     /// </summary>
 5     public class INI {
 6         /// <summary>
 7         /// 實例化INI文件。
 8         /// </summary>
 9         public INI() { }
10 
11         #region "↓全局常量"
12         /// <summary>註釋的標準符號</summary>
13         public static string U註釋 = ";";
14         /// <summary>註釋的標準符號2</summary>
15         public static string U註釋2 = "#";
16         /// <summary>節左括弧的標準符號</summary>
17         public static string U左括弧 = "[";
18         /// <summary>節右括弧的標準符號</summary>
19         public static string U右括弧 = "]";
20         /// <summary>連接項和值的標準符號</summary>
21         public static string U等號 = "=";
22         /// <summary>讀取或寫入時忽略無意義的備註行(不包括註釋)。</summary>
23         public static bool 忽略備註 = false;
24         /// <summary>讀取的上個文件的有效行數(不包括註釋)。</summary>
25         public static int 上次讀取的有效行數 = 0;
26         #endregion
27 
28         /// <summary>
29         /// 所有節點
30         /// <para>每個節點含有項、值和註釋,當項名稱為空字元串時,整條語句視為註釋</para>
31         /// </summary>
32         public List<ININode> Nodes { get; set; } = new List<ININode>();
33         /// <summary>
34         /// 附加在INI文件後無意義的文本
35         /// </summary>
36         public string EndText { get; set; } = "";
37         /// <summary>
38         /// 附加在INI文件第一行的作者信息等文本
39         /// <para>其中的換行符將被替換為兩個空格</para>
40         /// </summary>
41         public string StartLine { get; set; } = "";
42         /// <summary>
43         /// 讀取INI時獲得的FileName。
44         /// <para>寫入文檔時可以使用這個名字,也可以不使用這個名字。</para>
45         /// </summary>
46         public string FileName { get; set; } = "";
47         /// <summary>
48         /// 向本INI文件添加新節點。
49         /// </summary>
50         /// <param name="vName"></param>
51         /// <param name="vComment"></param>
52         /// <returns></returns>
53         public ININode New(string vName, string vComment = "") {
54             var k = new ININode(vName, vComment);
55             Nodes.Add(k);
56             return k;
57         }
58     }

如何寫入INI文件

  1. 首先遍歷每個節點,寫入節點的註釋節點名稱(套個括弧)
  2. 然後遍歷每個節點下麵的,寫入項的註釋項的名稱=值
  3. 寫入尾部註釋

以下是寫入代碼

 1         #region "寫入文件"
 2 
 3         /// <summary>將文檔寫入指定路徑
 4         /// </summary>
 5         /// <param name="path">指定路徑</param>
 6         public bool 寫入文檔(string path, Encoding encoding = null) {
 7             try {
 8                 if (encoding == null) { encoding = Encoding.Default; }
 9                 using (StreamWriter SW = new StreamWriter(path)) {
10                     SW.Write(ToString());
11                 }
12             } catch (Exception) {
13                 return false;
14             }       
15             return true;
16         }
17         /// <summary>
18         /// 將INI文檔轉化為文本格式,會生成整個文檔。
19         /// <para>註意:較大的文檔可能會耗費大量時間</para>
20         /// </summary>
21         /// <returns></returns>
22         public override string ToString() {
23             StringBuilder sb = new StringBuilder();
24             if (StartLine.Length > 0) { sb.AppendLine(StartLine.Replace("\r\n", "  ")); }
25             for (int i = 0; i < Nodes.Count; i++) {
26                 var node = Nodes[i];
27                 if (忽略備註 == false) { sb.Append(node.Comment); }
28                 sb.AppendLine(node.ToString());
29                 for (int j = 0; j < node.Items.Count; j++) {
30                     var item = node.Items[j];
31                     if (忽略備註 == false) { sb.Append(item.Comment); }
32                     sb.AppendLine(item.ToString());
33                 }
34             }
35             if (EndText.Length > 0) { sb.AppendLine(EndText); }         
36             return sb.ToString();
37         }
38 
39         #endregion

 

 

如何讀取INI文件

讀取通常比寫入複雜。軟糖的代碼也是逐行檢查,多次調試才完成。

流程如下:

  1. 首先定義一些局部變數來記錄當前分析的節、項、已經累積的備註、是否為有效行
  2. 逐行讀取,首先判斷是否開頭為;#,如果是,添加到備註,加回車符,設為有效行。
  3. 判斷開頭是否為[,如果是則作為節來讀取,進一步分析,如果[A]這種形式,設置當前節,設為有效行,如果[B缺少反括弧,進行下一步流程,尚無法判斷是[B=K這種項還是純粹無意義的無效行。
  4. 判斷是否含有=,如果是則作為項來讀取
  5. 如果未標記為有效行,通通加入備註
  6. 如果讀完全文,備註不為空,則加入到INI.EndText中作為結章節附註釋。

代碼

 #region "讀取文件"
        /// <summary>
        /// 從指定路徑和字元編碼的文件中讀取文檔內容,以此生成本文檔。
        /// </summary>
        /// <param name="路徑">完整的路徑字元串</param>
        /// <param name="encoding">編碼格式:預設自動識別。(對於無bom可能識別錯誤)</param>
        public bool 讀取文檔(string 路徑, Encoding encoding = null) {
            if (File.Exists(路徑) == false) { return false; }
            try {
                if (encoding == null) { encoding = TXT.GetFileEncodeType(路徑); }
                using (StreamReader SR = new StreamReader(路徑, encoding)) {
                    bool 返回結果 = 讀取文檔(new StringReader(SR.ReadToEnd()));
                    SR.Close();
                    return 返回結果;
                }
            } catch (Exception) {
                return false;
            }
        }

        /// <summary>
        ///<see cref="StringReader"/> 中讀取文檔內容,以此生成本文檔。
        /// </summary>  
        /// <param name="MyStringReader">StringReader,可以由string或StreamReader.ReadToEnd()來生成。</param>
        /// <returns>〈bool〉返回是否讀取成功。</returns>
        public bool 讀取文檔(StringReader MyStringReader) {
            /// <summary>正在分析的節</summary>
            ININode 當前節 = null;
            /// <summary>正在分析的項</summary>
            INIItem 當前項 = null;
            /// <summary>正在分析的節名</summary>
            string 當前節名 = null;
            /// <summary>正在分析的項名</summary>
            string 當前項名 = null;
            /// <summary>累計讀取的屬性行的計數</summary>
            int 計數 = 0;
            /// <summary>該行是合法有效的行,還是無法識別的行。(無法識別作為備註處理)</summary>
            bool 有效行 = false;
            /// <summary>該行去掉空格和Tab符的文本長度</summary>
            int 有效文本長度;
            /// <summary>每個實體前的註釋</summary>
            string 備註 = "";
            // * 迴圈讀取每行內容 *
            while (true) {
                string 行文本 = MyStringReader.ReadLine();
                if (行文本 == null) {  if (備註.Length > 0) { EndText = 備註; } 上次讀取的有效行數 = 計數; break; } else {
                    string 行;

                    有效行 = false;
                    // * 獲取 去掉空格和Tab符的文本 *
                    行 = 行文本.Trim(' ', '\t');
                    // * 獲取 去掉空格和Tab符的文本的長度 *
                    有效文本長度 = 行.Length;
                    // * 檢測註釋符 *
                    if (行文本.Contains(U註釋)) {
                        int 註釋位置 = 行文本.IndexOf(U註釋);
                        行 = 行文本.Substring(0, 註釋位置);
                        int 註釋開始位置 = 註釋位置 + U註釋.Length - 1;
                        int 註釋長度 = 行文本.Length - 註釋開始位置;
                        if (註釋長度 > 0) {
                            if (備註.Length > 0) { 備註 += "\r\n"; }
                            備註 += 行文本.Substring(註釋開始位置, 註釋長度);
                        }
                        有效行 = true;
                    }
                    if (行文本.Contains(U註釋2)) {
                        int 註釋位置 = 行文本.IndexOf(U註釋2);
                        行 = 行文本.Substring(0, 註釋位置);
                        int 註釋開始位置 = 註釋位置 + U註釋2.Length - 1;
                        int 註釋長度 = 行文本.Length - 註釋開始位置;
                        if (註釋長度 > 0) {
                            if (備註.Length > 0) { 備註 += "\r\n"; }
                            備註 += 行文本.Substring(註釋開始位置, 註釋長度);
                        }
                        有效行 = true;
                    }
                    // * 檢查開頭字元 *
                    if (行.Length >= 2) {
                        //[類型定義]====首字元:U節首[
                        if (行[0] == U左括弧[0]) {
                            int 右括弧位置 = 行.IndexOf(U右括弧[0], 2);
                            if (右括弧位置 > 1) {
                                當前節名 = 行.Substring(1, 右括弧位置 - 1);
                                當前節 = New(當前節名, 備註);
                                備註 = "";
                                計數 += 1;
                                有效行 = true;
                            }
                        }
                        //項定義====含有等號的行
                        // -> 獲取賦值符號位置
                        int 賦值符位置 = 行.IndexOf(U等號, 2);
                        if (賦值符位置 > 1) {
                            // -> 獲得名稱和值,並新建項
                            當前項名 = 行.Substring(0, 賦值符位置).Trim(' ', '\t');
                            string 值 = 行.Substring(賦值符位置 + 1, 行.Length - 賦值符位置 - 1).Trim(' ', '\t');
                            if (當前節 != null) {
                                當前項 = 當前節.New(當前項名, 值, 備註);
                                備註 = "";
                                計數 += 1;
                                有效行 = true;
                            }                                                      
                        }
                    }
                    // * 無效行作為備註處理 *
                    if (有效行 == false) {
                        if (忽略備註 == false) {
                            if (行文本.Length == 0) { 備註 += "\r\n"; } else { 備註 += 行文本 + "\r\n"; }
                        }
                    }
                }                             
            }
            return true;
        }

        #endregion

◇  編碼問題

 

 1 /// <summary>
 2         /// 通過文件的頭部開始的兩個位元組來區分一個文件屬於哪種編碼。
 3         /// 如果文件長度不足2位元組,則返回null
 4         /// 當FF FE時,是Unicode;
 5         /// 當FE FF時,是BigEndianUnicode;
 6         /// 當EF BB時,是UTF-8;
 7         /// 當它不為這些時,則是ANSI編碼。
 8         /// </summary>
 9         public static Encoding GetFileEncodeType(string filename) {
10             FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read);
11             BinaryReader br = new BinaryReader(fs);
12             Byte[] buffer = br.ReadBytes(2);
13             if (buffer.Length < 2) { return null; }
14             if (buffer[0] >= 0xEF) {
15                 if (buffer[0] == 0xEF && buffer[1] == 0xBB) {
16                     return Encoding.UTF8;
17                 } else if (buffer[0] == 0xFE && buffer[1] == 0xFF) {
18                     return Encoding.BigEndianUnicode;
19                 } else if (buffer[0] == 0xFF && buffer[1] == 0xFE) {
20                     return Encoding.Unicode;
21                 } else {
22                     return Encoding.Default;
23                 }
24             } else {
25                 return Encoding.Default;
26             }
27         }

窗體讀取INI演示

 ◇  演示效果

 

◇ INIListView類

用一個輔助類將INI文件內容顯示到ListView來展現效果。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、前言 公司內考慮到伺服器資源成本的問題,目前業務上還在進行服務的容器化改造和遷移,計劃將容器化後的服務,以及一些中間件(MQ、DB、ES、Redis等)儘量都遷移到其他機房。 那你們為什麼不用阿裡雲啊,騰訊雲啊,還用自己的機房? 的確是這樣,公司內部目前還是有專門的運維團隊。也是因為歷史原因,當 ...
  • 博客遷移 記錄《Effective C#》學習過程。 任務運行的幾種方法 //1.new方式實例化一個Task,需要通過Start方法啟動 Task task = new Task(() => { Console.WriteLine($"task1的線程ID為{Thread.CurrentThrea ...
  • Django介紹 Django是一個基於Python的高級Web開發框架 它能夠讓開發人員進行高效且快速的開發 高度集成,方便開發 正常上網流程 打開瀏覽器 向目標URL發送一個HTTP請求 伺服器把頁面響應給瀏覽器 瀏覽網頁的基本原理 本質是 網路通信 ,即通過網路進行數據傳遞 瀏覽器經過通信後獲 ...
  • static void CheckedUnCheckedDemo() { int i = int.MaxValue; try { //checked //{ // Console.WriteLine(i + 1); //} unchecked { Console.WriteLine(i + 1); ...
  • A finally block does not always xecute. The code in the try block could go into an infinite loop, the exception could rigger a “fail fast” (which take ...
  • 項目需要(或者前後端分離的需要),前端我使用了用戶控制項庫,由後端用代碼載入和控制。 然而用戶控制項庫沒法指定資源字典,於是在用戶控制項的xaml文件裡面手工添加了資源字典 設計階段方便了,生成dll,被主程式調用的時候,就報錯了,說沒有該資源文件(d1.xaml),研究Pack Url後明白,可以有兩種 ...
  • C#中實現文件拖放打開的方法 設置Form屬性 AllowDrop = True; 在Form事件中 private void Form1_DragDrop(object sender, DragEventArgs e) { string localFilePath = ((System.Array ...
  • 修改註冊表,雙擊文件直接打開 string strProject = "Exec"; string p_FileTypeName =".cdb";//文件尾碼 string fileName = System.Windows.Forms.Application.ExecutablePath;// 獲 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...