一个项目涉及XML文档中节点的自动计算,就是XML文档的每个节点都参与运算,要求:
⑴如果节点有计算公式则按照计算公式进行;
⑵如果节点没有计算公式则该节点的值就是所有子节点的值之和;
⑶节点有4种类型,计算节点、输入框、单选节点、多选节点;
计算节点:汇总;
输入框:点击该节点弹出输入框用于输入数据;
单选节点:众多选项中只能选择一个,根据选择项确定该节点的具体值;
多选节点:众多选项中可以选择多个,该节点的值是所有选择项的和;
类似下图(实际选项近100个):

问题是点击任何图标节点后都要完成的自动计算。
开始的时候,我将所有XML信息加载到Treeview中,包括属性,在Treeview中进行计算,完成后同步到XML文档,这样完成后效果不好,选项多了速度慢,如果计算机配置一般的话有略微的卡顿。
今天下午,我修改了方法,直接在XML文档中进行操作,使用递归完成节点的自动计算,这样速度很快,并且不需要同步到Treeview中(因为Treeview只是用于显示)。
1、点击节点
在Treeview中确定节点,根据节点类型完成图标变化,在XML中找到对应的节点。
⑴完成状态标识,如果是Radio则标识单选;如果是Checkbox标识多选;
⑵提取Value值,如果是Textbox则是输入值,如果是Radio则是父项value值是点击节点的Value值;如果是Checkbox则父项是所有选择项的value值之和。
⑶调用自动计算,如果是Radio或者Checkbox则是从父项的父项开始,如果是Textbox则是从父项开始。
- private void treeView1_MouseDown(object sender, MouseEventArgs e)
- {
- //获取鼠标点击的位置
- TreeNode FocusNode = treeView1.GetNodeAt(e.Location);
- string StrCurrentFullPath = FocusNode.FullPath;
- string StrNodeType = "";
- if (FocusNode != null)
- {
- //获取鼠标点击的位置是否在节点的图标上
- //在Treeview中针对Radio、Checkbox、TextBook分别进行设置
- TreeViewHitTestInfo hitTestInfo = treeView1.HitTest(e.Location);
- if (hitTestInfo.Location == TreeViewHitTestLocations.Image)
- {
- StrNodeType = FocusNode.Tag.ToString();
- //鼠标点击了节点的图标
- switch (StrNodeType)
- {
- case "Radio":
- // 取消同级节点的选中状态
- foreach (TreeNode node1 in FocusNode.Parent.Nodes)
- {
- if (node1 != FocusNode)
- {
- node1.ImageKey = "Radio";
- node1.SelectedImageKey = "Radio";
- }
- }
- // 设置当前节点为选中状态
- FocusNode.ImageKey = "RadioChecked";
- FocusNode.SelectedImageKey = "RadioChecked";
- //在XML文档中处理
- HandleNodeInfoAtXmlContent(StrCurrentFullPath, StrNodeType,"");//在文档中找到该节点并处理
-
- //
- break;
- case "Checkbox":
- if (FocusNode.ImageKey == "Checkbox")
- {
- FocusNode.ImageKey = "CheckboxChecked";
- FocusNode.SelectedImageKey = "CheckboxChecked";
- }
- else
- {
- FocusNode.ImageKey = "Checkbox";
- FocusNode.SelectedImageKey = "Checkbox";
- }
- //在XML文档中处理
- HandleNodeInfoAtXmlContent(StrCurrentFullPath, StrNodeType,"");//在文档中找到该节点并处理
-
- break;
- case "Textbox":
- string StrMin = "";
- string StrMax = "";
- string StrMemo = "";
- float fTemp;
- ToTextboxInputWinPara.fMax = 0;
- ToTextboxInputWinPara.fMin = 0;
- ToTextboxInputWinPara.StrMemo = "";
- FrmTextBoxInput FTI= new FrmTextBoxInput();
- DialogResult result= FTI.ShowDialog();
- if(result == DialogResult.OK)
- {
- StrCurrentTextboxValue = FTI.StrReturn;
- }
- //在XML文档中处理
- HandleNodeInfoAtXmlContent(StrCurrentFullPath, StrNodeType, StrCurrentTextboxValue);//在文档中找到该节点并处理
- break;
- }
- treeView1.Invalidate();
- }
-
- if (hitTestInfo.Location == TreeViewHitTestLocations.Label)
- {
- //点击标签
- if (FocusNode.Tag != null)
- {
- switch (FocusNode.Tag.ToString())
- {
- case "Radio":
- if (FocusNode.ImageKey == "RadioChecked")
- {
- FocusNode.SelectedImageKey = "RadioChecked";
- }
- if (FocusNode.ImageKey == "Radio")
- {
- FocusNode.SelectedImageKey = "Radio";
- }
- break;
- case "Checkbox":
- if (FocusNode.ImageKey == "Checkbox")
- {
- FocusNode.SelectedImageKey = "Checkbox";
- }
- if (FocusNode.ImageKey == "CheckboxChecked")
- {
- FocusNode.SelectedImageKey = "CheckboxChecked";
- }
- break;
- default: break;
- }
- treeView1.Invalidate();
- }
- }
- }
- }
对应在XML文档中的处理函数:
- private void HandleNodeInfoAtXmlContent(string StrCurrentFullPath,string StrNodeType,string StrInputTextValue)
- {
- //在XML文档内容中处理节点信息,传入参数:StrCurrentFullPath是当前点击选择的节点全路径名称
- int FirstIndex = StrCurrentFullPath.IndexOf("\\");
- int LastIndex = StrCurrentFullPath.LastIndexOf("\\");
- string StrCurrentNodeName = StrCurrentFullPath.Substring(LastIndex + 1);
- //提取父节点的名称
- string[] SubStr= StrCurrentFullPath.Split("\\");
- string ParentStr = SubStr[SubStr.Length - 2];
- // 使用XPath表达式定位到具体的节点,点击的节点名称是caption值
- string XpathExpression="";
- XmlNode CalculateNode=null;//计算节点
- switch (StrNodeType)
- {
- case "Radio":
- XpathExpression = "//" + ParentStr + "/option[@caption='" + StrCurrentNodeName + "']";
- break;
- case "Checkbox":
- XpathExpression = "//" + ParentStr + "/input[@caption='" + StrCurrentNodeName + "']";
- break;
- case "Textbox":
- XpathExpression = "//" + ParentStr + "/"+ StrCurrentNodeName;
- break;
- }
- XmlNode BeSelectNode = XmlDoc.SelectSingleNode(XpathExpression);
- //得到父节点的全路径名
- string SParentPath = StrCurrentFullPath.Substring(0, LastIndex);
- //得到父节点
- XmlNode ParentNode = FindNodeAtXmlContentByFullPath(SParentPath);
- XmlNode TempNode = null;
- if (BeSelectNode != null && ParentNode!=null)
- {
- //根据节点类型处理本节点
- switch (StrNodeType)
- {
- case "Radio":
- string StrValue = "";
- //找到该节点标识选中状态
- foreach (XmlNode RadioChildNode in ParentNode.ChildNodes)
- {
- //单选,先将父节点下的子节点的select属性全部删除
- if (RadioChildNode.Attributes["select"] != null)
- {
- RadioChildNode.Attributes.Remove(RadioChildNode.Attributes["select"]);
- }
- //找到子节点
- if (RadioChildNode.Attributes["caption"].Value == StrCurrentNodeName)
- {
- TempNode = RadioChildNode;
- StrValue = TempNode.Attributes["value"].Value;
- }
- }
- //添加select属性
- if (TempNode!=null)
- {
- if (HasAttribute(TempNode, "select"))
- {
- TempNode.Attributes["select"].Value = "true";
- }
- else
- {
- XmlAttribute RadioNodeAttr = XmlDoc.CreateAttribute("select");
- RadioNodeAttr.Value = "true";
- TempNode.Attributes.Append(RadioNodeAttr);
- }
- }
- //为父节点的value属性赋值
- ParentNode.Attributes["value"].Value = StrValue;
- //寻找父节点的父节点
- CalculateNode = ParentNode.ParentNode;
- //计算
- Autocalculate(CalculateNode);
- break;
- case "Checkbox":
- Single TempSum = 0.0f;
- //找到该节点标识状态,如果是选择则去掉,没有选择则加上,同时计算和
- foreach (XmlNode CheckChildNode in ParentNode.ChildNodes)
- {
- if (CheckChildNode.Attributes["caption"].Value == StrCurrentNodeName)
- {
- TempNode = CheckChildNode;
- }
- }
- //添加select属性
- if (HasAttribute(TempNode, "select"))
- {
- if (TempNode.Attributes["select"].Value == "true")
- {
- //如果已经选择了,需要去掉选择
- TempNode.Attributes.Remove(TempNode.Attributes["select"]);
- }
- else
- {
- TempNode.Attributes["select"].Value = "true";
- }
- }
- else
- {
- XmlAttribute CheckSelectedAttr = XmlDoc.CreateAttribute("select");
- CheckSelectedAttr.Value = "true";
- TempNode.Attributes.Append(CheckSelectedAttr);
- }
-
- foreach (XmlNode CheckChildNode in ParentNode.ChildNodes)
- {
- if (HasAttribute(CheckChildNode, "select"))
- {
- TempSum += Convert.ToSingle(CheckChildNode.Attributes["value"].Value);
- }
- }
- //为父节点的value属性赋值
- ParentNode.Attributes["value"].Value = TempSum.ToString();
- //寻找父节点的父节点
- CalculateNode = ParentNode.ParentNode;
- //计算
- Autocalculate(CalculateNode);
- break;
- case "Textbox":
- //找到该节点修改Value值
- BeSelectNode.Attributes["value"].Value = StrInputTextValue;
- //寻找本节点的父节点
- CalculateNode = BeSelectNode.ParentNode;
- //计算
- Autocalculate(CalculateNode);
- break;
- }
- }
- else
- {
- textBox1.Text += "提取属性值发生错误,没有找到对应节点或者属性值错误!" + Environment.NewLine;
- }
-
- }
-
2、递归计算
- private void Autocalculate(XmlNode CalculateNode)
- {
- //在XML文档中,节点自动计算结果
- //CalculateResult MyCalcuteResult= new CalculateResult();
- float fSum = 0f;
- string StrID = "";
- string StrValue = "";
- string StrFormula = "";
- Boolean Continue = true;
- string StrFalse = "";
- //判断是否有子节点
- if (CalculateNode.HasChildNodes)
- {
- //有子节点需要看是否有计算公式,根据指定的节点进行自动计算
- if (HasAttribute(CalculateNode, "formula"))
- {
- //如果节点有formula属性,则提取出计算公式。
- StrFormula = GetAttrValue(CalculateNode, "formula");
- //将所有子节点的值进行替换完成后再进行计算。
- foreach (XmlNode MyNode in CalculateNode.ChildNodes)
- {
- if (HasAttribute(MyNode,"id"))
- {
- StrID = MyNode.Attributes["id"].Value;
- StrValue = MyNode.Attributes["value"].Value;
- if (StrValue.IsNullOrEmpty())
- {
- Continue = false;
- StrFalse = $"{StrID}为空";
- break;
- }
- else
- {
- //替换公式中的字符串,ID和值
- StrFormula = StrFormula.Replace(StrID, StrValue);
- }
- }
- else
- {
- Continue = false;
- }
- }
- if (Continue)
- {
- //进行计算获得结果
- fSum = GetFormulaResult(StrFormula);
- }
- }
- else
- {
- //没有formula属性,计算结果等于所有子节点的和。
- foreach (XmlNode MyNode in CalculateNode.ChildNodes)
- {
- StrValue = MyNode.Attributes["value"].Value;
- if (StrValue.IsNullOrEmpty())
- {
- Continue = false;
- StrFalse = MyNode.Name +"的值为空";
- break;
- }
- else
- {
- fSum += Convert.ToSingle(StrValue);
- }
- }
- }
- if (Continue)
- {
- //修改本节点的Value属性
- CalculateNode.Attributes["value"].Value = fSum.ToString();
- }
- CalculateNode = CalculateNode.ParentNode;
- //if (CalculateNode.NodeType == XmlNodeType.Document)
- if(CalculateNode==null)
- {
- StrFalse = "没有了父节点";
- Continue = false;
- }
- //是否继续计算
- if (Continue)
- {
- Autocalculate(CalculateNode);
- }
- else
- {
- textBox1.Text += StrFalse+Environment.NewLine;
- }
- }
- }
这个问题看似简单,实际上也的确不难,就是有一点麻烦,需要耐心去解决细节问题。