一个项目涉及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;
}
}
}
这个问题看似简单,实际上也的确不难,就是有一点麻烦,需要耐心去解决细节问题。