65.9K
CodeProject 正在变化。 阅读更多。
Home

带 XML 文件的 C# 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.55/5 (16投票s)

2010年3月11日

CPOL

6分钟阅读

viewsIcon

117019

downloadIcon

9801

将 XML 文件用作数据库

更新 (2010/5/10)

C# 2010 Express 新代码可用 (也提供清理过的版本)。如果 2008 代码在 2010 中遇到问题,请下载 2010 版本。

引言

这是我关于我第一个 C# 学习应用程序的第一个文章。在阅读书籍和观看视频时,我想创建一个我自己也会使用的东西,不花哨,易于使用。这个应用程序也可以用于做笔记,不仅仅是代码。代码本身,而不是应用程序及其功能,是最重要的部分,也将是我后续文章的主要内容 (如果我能得到你们…读者…的认可)。

背景

我开始编写这个代码时使用的是 Access 数据库。然后我买了一台预装 64 位 Win7 操作系统的笔记本电脑。这是我的第一台 64 位电脑,我不知道我的代码会如何反应。显然,Microsoft Jet 引擎与 64 位系统存在一些问题。尽管我找到了一些修复和变通的方法,但我决定继续前进。我尝试了 SQL Express…非常强大的数据库,我喜欢 SQL。我之前 (非 C#) 的应用程序都使用了 SQL 服务器,我非常喜欢它。但是,我希望这个程序非常轻便,非常“便携+易于共享”,毫不费力 (或少费力)。所以,我决定不使用“自制”的文本数据库,而是使用 XML。这样,如果有人想将 XML 传输到另一个数据库,他们就可以轻松做到。这是我第一次处理 XML,并且我非常享受。

Using the Code

这个程序有两个窗体和两个自定义类

  • Form1 (主窗体)
  • 窗体设置
  • IO 类
  • XML 类

该应用程序允许您创建一个新的 XML 文件,其中包含三个字段 (ID、代码主题和主代码) 作为其数据库,或者定位现有文件。因此,如果多个用户使用不同的数据库,他们可以共享他们的 XML 文件,也可以在家中或办公室的应用程序之间共享。我没有设置任何限制,例如登录或用户级别来更改。也许你们中的一些人想添加一个选项,让用户可以灵活地为自己的目的创建任意多的字段…所有这些都可以添加到这个代码中。潜力无限。:)

由于整个代码都可供下载,我想在这里展示一些我喜欢的、因为它们的工作方式而让我欣赏的方法/代码。

工具提示设置:我在几乎所有的“窗体加载”方法中使用 “_toolTip” 类来向用户显示控件提示。很简单,但既然这是初学者文章,我想也许你们中的一些人可能错过了。这里还有 “#region” “#endregion” 的用法。这是一个非常好的工具,可以把一些代码隐藏起来。

#region ToolTips
	 // Set some tooltips here
	_toolTip.SetToolTip(btnCopy, "Copy to ClipBoard");
	_toolTip.SetToolTip(btnNew, "Add new code with this subject");
	_toolTip.SetToolTip(btnExit, "Exit program");
	_toolTip.SetToolTip(lstSubjects, "Click to see the code");
	_toolTip.SetToolTip(chkOnTop, "Check to keep this window top of all windows");
	_toolTip.SetToolTip(btnSearch, "Search DB");
	_toolTip.SetToolTip(txtCodeMain, "Double Click to copy code into clipboard");
#endregion

调整窗体大小和位置:即使 Windows 窗体有 “StartPosition” 属性,我仍然使用以下代码来根据当前屏幕边界调整窗体大小。

// Resize the form using current screen size
this.Width = Screen.PrimaryScreen.Bounds.Width / 2;
this.Height = Screen.PrimaryScreen.Bounds.Height - 300;

我在代码中经常使用 “Split”。有时在类和方法之间传递信息非常容易。我认为 Split 也是一种“廉价”的传递字段信息的方式,它占用的内存比数组少。找到合适的字符是关键,在这里我使用了“退格”字符,因为它无法通过文本框传递。

XMLDatabase clsXML = new XMLDatabase(); // Create instance of XMLDatabase class
ArrayList CurrentSubjectList = new ArrayList(); // Create instance of ArrayList
                                                // This will loaded with the ArrayList
                                                // sent by clsXML object
string[] SplitText = new string[2]; 	// Each item in the array will contain a string
                                    	// that will be split by 'Backspace' character

CurrentSubjectList = clsXML.GetSubjectList
	(clsIO.XMLPath, SearchWord); // Get the ArrayList from clsXML
char Splitchr = (char)8; // 'char 8' is Backspace character.

foreach (string FullString in CurrentSubjectList) // Start looping the ArrayList
{
    SplitText = FullString.Split(Splitchr); 	// Split Item
    ListViewItem s_Items; 		// Set ListView item to add new information
    s_Items = lstSubjects.Items.Add
	(SplitText[0]); 	// Add Subject ID to the first (not visible) column
    s_Items.SubItems.Add(SplitText[1]); // Add Subjectname to second (visible) column
}

ListView 控件是我最喜欢的控件之一 (见上图)。当在“Details”模式下使用时,它可以成为显示我的数据库字段项的非常有用的工具。我不喜欢数据绑定,所以一切都是动态编码来填充 ListView 控件…即使我使用 SQL 或 Access 数据库。没有绑定…我讨厌将东西绑定到我的应用程序。应用程序必须完全不依赖于任何数据源,并且连接应该在程序运行后,在需要时进行…(纯属个人观点)。

我可能不会在我的下一篇文章中重复所有这些“简单”的信息,但它们是非常有用的东西,你们中的一些人可能错过了。

这里有另一个根据条件更改某些控件属性的想法。这是一个类,可以在运行时启用/禁用控件。

private void EnableDisableButtons(bool EDButtons) 	// Change some of the 
						// control properties here
{
    // Menu items
    TSAddNew.Enabled = EDButtons;
    TSEdit.Enabled = EDButtons;
    TSDelete.Enabled = EDButtons;
    TSRefresh.Enabled = EDButtons;
    
    // Buttons
    btnSearch.Enabled = EDButtons;
    btnNew.Enabled = EDButtons;
    btnEdit.Enabled = EDButtons;
    btnDelete.Enabled = EDButtons;

    // Main codeview
    txtCodeMain.Enabled = EDButtons;
    txtSubject.Enabled = EDButtons;
}

cd02.jpg

以下代码显示了“重载”类。我不得不保留原始构造函数并添加另一个,因为我需要在实例化时设置一些控件值。

public frmSettings()
{
    InitializeComponent();
}

重载

// Settings form constructor overload
public frmSettings(string OnTopStr, string WordWrapStr, string AutoSaveStr)
{
    InitializeComponent();

    // Set some control values
    if (OnTopStr == "1")
    { chkOnTop.Checked = true; }

    if (WordWrapStr == "1")
    { chkWordWrap.Checked = true; }

    if (AutoSaveStr == "1")
    { chkAutoSave.Checked = true; }

    IOFile clsIO = new IOFile(); // Create instance of IOFile class

    if (clsIO.ErrHap == null)
    {
        Scripting.FileSystemObject FsO = 
	new Scripting.FileSystemObject(); // Create instance of FileSystemObject class
        lblXML.Text = "XML File Name : " + 
	FsO.GetFileName(clsIO.XMLPath); // XML file name into label box
        XMLDatabaseLocation = clsIO.XMLPath; // Set XML file path
    }
}

文章的其余部分将展示 XML 文件操作。XML 部分可能是我代码变得复杂(! ) 的唯一地方。希望您觉得有用。

创建一个具有 3 个子节点的 XML 文件。所以,有一个主标签、一个父标签和三个子标签。

XmlTextWriter XMLWrite = new XmlTextWriter(XMLPath, System.Text.Encoding.UTF8);

XMLWrite.WriteStartDocument();
XMLWrite.WriteStartElement("Codes"); // Main tag
XMLWrite.WriteStartElement("NewCode"); // Parent tag
XMLWrite.WriteElementString("ID", "1"); // Child 1
XMLWrite.WriteElementString("DBSubject", "Welcome"); // Child II
XMLWrite.WriteElementString("DBCode", 
	"Thank you for using CodeDatabase by Leo Koach (2010)"); // Child III
XMLWrite.WriteEndElement();
XMLWrite.WriteEndElement();
XMLWrite.WriteEndDocument();

XMLWrite.Flush();
XMLWrite.Close();

在 XML 文件末尾追加记录 (节点)

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(XMLPath);
XmlElement subRoot = xmlDoc.CreateElement("NewCode");

//ID
XmlElement appendedElementID = xmlDoc.CreateElement("ID");
XmlText xmlTextID = xmlDoc.CreateTextNode(GetLatestID(XMLPath));
appendedElementID.AppendChild(xmlTextID);
subRoot.AppendChild(appendedElementID);
xmlDoc.DocumentElement.AppendChild(subRoot);

//Subject
XmlElement appendedElementSubject = xmlDoc.CreateElement("DBSubject");
XmlText xmlTextSubject = xmlDoc.CreateTextNode(CodeSubject);
appendedElementSubject.AppendChild(xmlTextSubject);
subRoot.AppendChild(appendedElementSubject);
xmlDoc.DocumentElement.AppendChild(subRoot);

//Code
XmlElement appendedElementCode = xmlDoc.CreateElement("DBCode");
XmlText xmlTextCode = xmlDoc.CreateTextNode(CodeMain);
appendedElementCode.AppendChild(xmlTextCode);
subRoot.AppendChild(appendedElementCode);
xmlDoc.DocumentElement.AppendChild(subRoot);

xmlDoc.Save(XMLPath);

也许还有另一种编辑节点信息的方法,虽然这个看起来不错并且对我来说效果很好。我所做的就是循环遍历 XML 节点,搜索 ID 字段以匹配我传入的 ID。如果找到匹配项,则用新信息更改选定的字段信息。

XmlDocument XMLEdit = new XmlDocument();
XMLEdit.Load(XMLPath);

XmlElement XMLEditNode = XMLEdit.DocumentElement;

foreach (XmlNode node in XMLEditNode) // Loop through XML file
{
    if (node["ID"].InnerText == SubjectID) // Check for the ID field information
    {
        node["DBCode"].InnerText = CodeMain;
        break;
    }
}
XMLEdit.Save(XMLPath);

删除 XML 文件的一部分如下所示。与上面的代码一样,我正在查找 ID 字段。如果找到,我只删除包含 ID 字段和其他子字段的父节点。我可能可以将这两个循环合并成一个,但为了学习目的,这样也可以。你随时可以自己进行合并。这样可以节省你写额外的代码行。

XmlDocument XMLDelete = new XmlDocument();
XMLDelete.Load(XMLPath);

XmlElement XMLDeleteNode = XMLDelete.DocumentElement;

foreach (XmlNode node in XMLDeleteNode)
{
    if (node["ID"].InnerText == SubjectID) // Find the match for the ID field
    {
        node.ParentNode.RemoveChild(node); // Delete the parent for the children (fields)
        break;
    }
}
XMLDelete.Save(XMLPath);

从 XML 文件中获取一个字段信息 (节点),并抛出一个异常。我在这里包含了更多的代码来展示我是如何使用 Try/Catch 和异常抛出器的。当像这样抛出一个异常时,它会返回到“发送者”(我喜欢这样称呼它,它实际上是将异常发回给初始化该类的类的方法)。与另外三个代码一样,我循环遍历 XML 直到找到我的 ID。

XmlDocument XMLRead = new XmlDocument(); // Create instance of XmlDocument class

try
{
    XMLRead.Load(XMLPath); // Set XMLRead object's path value

    XmlNodeList XMLItems = XMLRead.SelectNodes
	("Codes/NewCode"); // Create instance of XmlNodeList class
    // and set its node value

    foreach (XmlNode node in XMLItems) // Loop Node for the child node items
    {
        if (node["ID"].InnerText == SubjectID) // Find the ID number
        {
            StrInf = node["DBCode"].InnerText; // Set return value to 
					// Main Code value from the XML file
            break; // Exit loop
        }
    }
    return StrInf;
}
catch (Exception exc)
{
    throw exc;
}
}

顺便说一句,即使从 XML 文件中删除了任何内容,ID 字段也总是会增长到更高的数字。我可以(而且很容易做到)找到缺失的数字并将其分配给下一个 ID (例如,我有从 ID 号 1 到 40 的数据…我删除了 23 号。所以现在有一个差距。下次输入新数据时,我就可以找到 23 号并将其分配给我的 ID)。但我喜欢它现在的样子。为了获取最新数字,我还有另一个循环,它只读取 ID 字段并添加“1”。

public string GetLatestID(string xmlFilePath)
{
    int LastIDEntry = 0;
    
    XmlDocument XMLGetLastID = new XmlDocument();
    XMLGetLastID.Load(xmlFilePath);

    XmlNodeList XMLItems = XMLGetLastID.SelectNodes("Codes/NewCode");

    foreach (XmlNode node in XMLItems)
    {
        LastIDEntry = Convert.ToInt32(node["ID"].InnerText) + 1;
    }
    return Convert.ToString(LastIDEntry);
}

我知道可能有比循环遍历 XML 文件更好的方法,当文件变大时,这可能会减慢速度,我需要你们的帮助来找出它们 (如果有的话)。

关注点

这段代码是为 C# 初学者 (就像我一样) 准备的。我热爱编程,并享受随之而来的任何挑战。我为此做了很多研究 (即使在这个年纪) 来学习。这些天,互联网是我研究 C# 的唯一来源 (99.90% 的时间)。希望你们中的一些人能够复制粘贴并使用我在这里的一些代码。我喜欢修改我找到的代码以适应我的代码,这可能是最好的学习方式 (仅次于观看 C# 视频)。希望在我的下一篇文章“学习 II”中见到你!:)

© . All rights reserved.