一个项目的数据表暂时没有定下来,但是有了一些确定性:
1、比较复杂,可能变化;
2、大部分是选择项目,因为输入项目都差不多;
3、应用程序是C/S的窗体应用。
对于这样的用户需求,为了快速让用户看到功能效果,可以使用XML结合Treeview来实现。
最终的输入效果图:
对于上图的实现,需要完成的工作:
1、定义XML文件
<事故调查 value="" max="100">
<气候因素 Value="" Formula="Item*1.5">
<气温 type="Textbox" max="" min="" value="" />
<湿度 type="Textbox" max="" min="" value="" />
</气候因素>
<地质灾害 Value="" Formula="Item" type="Radio">
<option caption="泥石流" value="5" />
<option caption="水灾" value="10" />
<option caption="地震" value="20" />
</地质灾害>
<人为因素 Value="" Formula="Item" type="Checkbox">
<input caption="管理失职" value="20" />
<input caption="能力欠缺" value="20" />
<input caption="制度不健全" value="15" />
</人为因素>
</事故调查>
2、将XML文件存入数据采集模板并存入数据库,需要数据录入的时候读出来即可。
⑴新增模板
string InsertQuery = $"INSERT INTO InputThemeXml(F01,F02,F03) VALUES (@ThemeName,@XmlData,@ThemeMemo)";
SqlCommand MyCommand = new SqlCommand(InsertQuery, DBConn);
MyCommand.Parameters.AddWithValue("@ThemeName", ThemeName);
MyCommand.Parameters.AddWithValue("@XmlData", XmlData);
MyCommand.Parameters.AddWithValue("@ThemeMemo", ThemeMemo);
int RowAffected = MyCommand.ExecuteNonQuery();
if (RowAffected > 0) { MessageBox.Show("新增-保存成功"); }
⑵修改模板
string UpdateSql = $"UPDATE InputThemeXml SET F02=@XmlData,F03=@ThemeMemo WHERE F01=@ThemeName";
SqlCommand MyCommand1 = new SqlCommand(UpdateSql, DBConn);
MyCommand1.Parameters.AddWithValue("@ThemeName", ThemeName);
MyCommand1.Parameters.AddWithValue("@XmlData", XmlData);
MyCommand1.Parameters.AddWithValue("@ThemeMemo", ThemeMemo);
int RowAffected1 = MyCommand1.ExecuteNonQuery();
if (RowAffected1 > 0) { MessageBox.Show("修改-保存成功"); }
3、准备工作
⑴准备图片,为16x16的PNG文件,创建资源文件并放入Textbox、Radio、RadioChecked(选择时显示)、Checkbox、CheckboxChecked(选中时显示);
⑵在窗体初始化时定义资源
//定义资源
MyImageList.Images.Add("Default", YQCYPRAS.Resourse.Resource1.line as Image);
MyImageList.Images.Add("Radio",YQCYPRAS.Resourse.Resource1.radio as Image);
MyImageList.Images.Add("RadioChecked", YQCYPRAS.Resourse.Resource1.radioChecked as Image);
MyImageList.Images.Add("Checkbox", YQCYPRAS.Resourse.Resource1.checkbox as Image);
MyImageList.Images.Add("CheckboxChecked", YQCYPRAS.Resourse.Resource1.checkboxChecked as Image);
MyImageList.Images.Add("Textbox", YQCYPRAS.Resourse.Resource1.textbox as Image);
treeView1.ImageList = MyImageList;
⑶事件挂载
treeView1.MouseDown+= treeView1_MouseDown;
treeView1.KeyDown += treeView1_KeyDown;
treeView1.LostFocus += treeView1_LostFocus;
treeView1.NodeMouseDoubleClick+= treeView1_MouseDouleClick;
4、读取XML模板并操作Treeview的生成
⑴ 读取XML模板
//创建一个Xml文档对象
XmlDocument XmlDoc = new XmlDocument();
//加载XML数据
string SelectThemeName = comboBox2.SelectedItem.ToString();
DBConn.Open();
string SqlQuery = $"SELECT F02 FROM InputThemeXml WHERE F01='{SelectThemeName}'";
SqlCommand MyCommand = new SqlCommand(SqlQuery, DBConn);
SqlDataReader MyReader = MyCommand.ExecuteReader();
if (MyReader.Read()) { XmlDoc.LoadXml(MyReader["F02"].ToString()); }
MyReader.Close();
DBConn.Close();
//获取文档的根节点
XmlNode RootNode = XmlDoc.SelectSingleNode(SelectThemeName);
//将根节点添加到treeview1控件中
TreeNode RootTreeviewNode = new TreeNode(SelectThemeName);
treeView1.Nodes.Add(RootTreeviewNode);
//递归生成Treeview
AddNodesToTreeview(RootNode, RootTreeviewNode);
treeView1.ExpandAll();
MessageBox.Show("OK");
⑵关键在于递归生成Treeview,是AddNodesToTreeview(RootNode, RootTreeviewNode);
public void AddNodesToTreeview(XmlNode MyXmlNode,TreeNode MyTreeNode)
{
//遍历该节点下的所有的子节点
foreach(XmlNode MyNode in MyXmlNode.ChildNodes)
{
string StrXmlNodeType = "";
if (HasAttribute(MyNode, "type"))
{
StrXmlNodeType=GetAttrValue(MyNode, "type");
}
//textBox4.Text += StrXmlNodeType + Environment.NewLine;
//判断节点是否为Textbox(编辑框)、Radio(单选按钮)、Checkbox(复选框)
if (StrXmlNodeType == "")
{
//将子节点添加到treeview控件中
TreeNode ChildTreeNode = new TreeNode(MyNode.Name);
ChildTreeNode.ImageKey= "Default";
MyTreeNode.Nodes.Add(ChildTreeNode);
//如果该子节点下面还有子节点,就递归调用继续生成
if (MyNode.HasChildNodes)
{
AddNodesToTreeview(MyNode, ChildTreeNode);
}
}
else
{
switch(StrXmlNodeType) {
case "Textbox":
TreeNode TextboxNode = new TreeNode(MyNode.Name);
TextboxNode.Tag= "Textbox";
TextboxNode.ImageKey= "Textbox";
TextboxNode.SelectedImageKey= "Textbox";
MyTreeNode.Nodes.Add(TextboxNode);
break;
case "Radio":
//单选按钮,表明子节点全部是Radio按钮
TreeNode ParentRadioNode=new TreeNode(MyNode.Name);
MyTreeNode.Nodes.Add(ParentRadioNode);
//添加子节点
SetChildNodeRadio(MyNode, ParentRadioNode);
break;
case "Checkbox":
//复选框
TreeNode ParentCheckboxNode = new TreeNode(MyNode.Name);
MyTreeNode.Nodes.Add(ParentCheckboxNode);
SetChildNodeCheckbox(MyNode, ParentCheckboxNode);
break;
}
}
}
}
public void SetChildNodeRadio(XmlNode ParentXmlNode, TreeNode ParentNode)
{
//遍历所有的子节点进行设置
foreach(XmlNode ChildXmlNode in ParentXmlNode.ChildNodes)
{
string STemp = "";
STemp = GetAttrValue(ChildXmlNode,"caption");
TreeNode ChildRadioNode = new TreeNode(STemp);
ChildRadioNode.Tag= "Radio";
ChildRadioNode.ImageKey = "Radio";
ParentNode.Nodes.Add(ChildRadioNode);
}
}
5、编写事件处理
private void treeView1_MouseDown(object sender, MouseEventArgs e)
{
// 获取鼠标点击的位置
TreeNode FocusNode = treeView1.GetNodeAt(e.Location);
if (FocusNode != null)
{
// 获取鼠标点击的位置是否在节点的图标上
TreeViewHitTestInfo hitTestInfo = treeView1.HitTest(e.Location);
if (hitTestInfo.Location == TreeViewHitTestLocations.Image)
{
// 鼠标点击了节点的图标
switch (FocusNode.Tag.ToString())
{
case "Radio":
// 取消同级节点的选中状态
foreach (TreeNode node1 in FocusNode.Parent.Nodes)
{
if (node1 != FocusNode)
{
node1.ImageKey = "Radio";
node1.SelectedImageKey = "Radio";
}
}
// 设置当前节点为选中状态
FocusNode.ImageKey = "RadioChecked";
FocusNode.SelectedImageKey = "RadioChecked";
break;
case "Checkbox":
if (FocusNode.ImageKey == "Checkbox")
{
FocusNode.ImageKey = "CheckboxChecked";
FocusNode.SelectedImageKey = "CheckboxChecked";
}
else
{
FocusNode.ImageKey = "Checkbox";
FocusNode.SelectedImageKey = "Checkbox";
}
break;
case "Textbox":
//System.Windows.Forms.TextBox MyTextbox=new System.Windows.Forms.TextBox();
//MyTextbox.Text = "请输入值:";
//MyTextbox.Location= new System.Drawing.Point(FocusNode.Bounds.Left+120, FocusNode.Bounds.Top -1);
//MyTextbox.Width= 120;
//MyTextbox.BorderStyle = BorderStyle.FixedSingle;
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;
case "Textbox":
FocusNode.BeginEdit();
break;
default: break;
}
treeView1.Invalidate();
}
}
}
}
6、对XML的属性的判断和值获取
public Boolean HasAttribute(XmlNode MyXmlNode,string StrAttr)
{
//判断是否具有属性
Boolean ReturnBool=false;
if (MyXmlNode.Attributes != null && MyXmlNode.Attributes[StrAttr] != null)
{
ReturnBool=true;
}
return ReturnBool;
}
public string GetAttrValue(XmlNode MyXmlNode,string StrAttr)
{
//得到属性值
string ReturnStr = "";
if (MyXmlNode.Attributes != null && MyXmlNode.Attributes[StrAttr] != null)
{
ReturnStr= MyXmlNode.Attributes[StrAttr].Value;
}
return ReturnStr;
}
对于XML生成Treeview有几种方法,最麻烦的方法是继承Treeview后重写,这能实现想要的所有功能,稍微麻烦一些的是获取节点位置后根据用户的需求动态生成输入组件放置到相应的位置,我采用的是最简单的方法,更改节点的图片文件来满足用户的需求。
需要注意的问题:
⑴、节点的imageKey和SelectimageKey分别是节点正常显示的图片和节点被选中时的图片;
⑵、节点点击图片和点击文本标签要分别处理;
⑶、对于Radio的处理是同级只能显示一个被选中,对于Checkbox是可以多选的;
⑷、XML文档与Treeview的数据是同步的。