现在界面设计越来越注重与代码的分离,把表现界面的元素写在XML文件中,程序加载时再通过反射等机制加载到程序里。以前我写的小程序,也有些设置功能,往往把界面直接在代码里写死。如果选项不多还好,如果选项一多,就使界面混乱不堪了。所以我也采用了XML配置文件的方式来编写设置功能。
但是既然是小程序,就要保持原来短小精悍的风格,速度也不能太慢,最重要的是代码编写得方便,所以不能太那些框架那样搞。我先分析,设置界面大概需要这些东西:1,数值选择框、2,复选框、3,单选框,4,其它(可以有TextBox之类的)。我只需要前三项,且第3项单选框我用“下拉选择框”来代替,可以节省空间(其实主要是方便编码啦)。
先来规定下XML的格式,下面是一个示例。
- <?xml version="1.0" encoding="utf-8"?>
- <preference>
- <page name="标签1">
- <item type="int" name="set1">
- <text>设置1</text>
- <value max="100" min="0">5</value>
- </item>
- <item type="int" name="set6">
- <text>设置1</text>
- <value max="100" min="0">100</value>
- </item>
- <item type="checkbox" name="set2">
- <text>设置2</text>
- <value>True</value>
- </item>
- <item type="combo" name="set3">
- <combo>
- <text>item0</text>
- <value>0</value>
- </combo>
- <combo>
- <text>item1</text>
- <value>1</value>
- </combo>
- <text>选择XX选项:</text>
- <value>1</value>
- </item>
- </page>
- <page name="标签2">
- <item type="int" name="set1">
- <text>设置1</text>
- <value max="100" min="0">100</value>
- </item>
- <page name="标签2_1">
- </page>
- </page>
- </preference>
用“preference”做根节点,根节点下面是“page”,一个“page”代表一个选项卡,一个“page”里可以有子“page”,就像Eclipse的设置一样,也可以包含有“Item”,一个“Item”代表一个选项。“Item”可以有3种类型,代表“数值选择框”、“复选框”和“下拉框”。路径由"name"属性来唯一标识,如:“name1\name1_1”,同一个page下相同的"name"将会导致解析的错误(未定义行为)。上面的例子中有“标签1”,“标签2”两页,“标签2”有一个“标签2_1”的子页。
为了解析这个XML,我设计了三个类。
1,PreferenceTreeAdapter,读取XML的目录结构,显示到TreeView控件中。
-
-
-
- public class PreferenceTreeAdapter
- {
- protected string xmlDoc;
-
- public PreferenceTreeAdapter(String filePath)
- {
- xmlDoc = File.ReadAllText(filePath);
- }
-
- public void Fill(TreeView treeView)
- {
- XmlDocument doc = new XmlDocument();
- doc.LoadXml(xmlDoc);
-
- XmlNodeList nodeList = doc.SelectSingleNode("preference").SelectNodes("page");
- FillChildren(nodeList, treeView.Nodes);
- }
-
- protected void FillChildren(XmlNodeList nodeList, TreeNodeCollection treeNodeCollection)
- {
- if (nodeList.Count == 0)
- {
- return;
- }
-
- for (int i = 0; i < nodeList.Count; i++)
- {
- XmlNode node = nodeList[i];
- TreeNode curTreeNode = treeNodeCollection.Add(node.Attributes.GetNamedItem("name").Value);
- XmlNodeList newNodeList = node.SelectNodes("page");
- if (newNodeList != null)
- {
- FillChildren(newNodeList, curTreeNode.Nodes);
- }
- }
- }
- }
2,OptionsAdapter 负责一个"page"中的选项和控件的对应关系,是一个双向的适配器。
-
-
-
- public class OptionsAdapter
- {
- protected Dictionary<Control, XmlNode> map = new Dictionary<Control, XmlNode>();
- protected XmlDocument doc = new XmlDocument();
-
- public OptionsAdapter(string filePath)
- {
- doc.LoadXml(File.ReadAllText(filePath));
- }
-
-
-
-
-
-
- public void Fill(TableLayoutPanel layout, string fullPath)
- {
- layout.Controls.Clear();
-
- XmlNode node = XmlLocate(fullPath);
- XmlNodeList optionsList = node.SelectNodes("item");
- for (int i = 0; i < optionsList.Count; i++ )
- {
- Panel panel = new Panel();
- panel.AutoSize = true;
-
- Control control = FillPanel(optionsList[i], panel);
- if (control != null)
- {
- map.Add(control, optionsList[i]);
- layout.Controls.Add(panel, 0, i);
- }
- }
-
- }
-
- protected XmlNode XmlLocate(string fullPath)
- {
- string[] pathList = fullPath.Split('\\');
-
- XmlNode node = doc.SelectSingleNode("preference");
-
- int pathLevelCount = pathList.Count();
- for (int i = 0; i < pathLevelCount; i++)
- {
- bool flag = false;
- foreach (XmlNode curNode in node.SelectNodes("page"))
- {
- if (curNode.Attributes.GetNamedItem("name").Value == pathList[i])
- {
- node = curNode;
- flag = true;
- break;
- }
- }
- if (!flag)
- {
- throw new Exception("no such path:" + pathList[i]);
- }
- }
-
- return node;
- }
-
- protected Control FillPanel(XmlNode node, Panel panel)
- {
- string type = node.Attributes.GetNamedItem("type").Value;
-
- switch (type)
- {
- case "checkbox":
- {
- CheckBox checkBox = new CheckBox();
- checkBox.Text = node.SelectSingleNode("text").InnerText;
- checkBox.Checked = bool.Parse(
- node.SelectSingleNode("value").InnerText);
- panel.Controls.Add(checkBox);
- return checkBox;
- }
- case "combo":
- {
- Label label = new Label();
- label.Text = node.SelectSingleNode("text").InnerText;
- label.Location = new System.Drawing.Point(0, 0);
-
- XmlNodeList list = node.SelectNodes("combo");
- ComboBox comboBox = new ComboBox();
- comboBox.DisplayMember = "Key";
- comboBox.ValueMember = "Value";
- comboBox.Location = new System.Drawing.Point(
- 5, label.Height + label.Top + 2);
-
- string selectedValue = node.SelectSingleNode("value").InnerText;
- for (int i = 0; i < list.Count; i++)
- {
- XmlNode curNode = list[i];
- string key = curNode.SelectSingleNode("text").InnerText;
- string value = curNode.SelectSingleNode("value").InnerText;
- comboBox.Items.Add(new KeyValuePair<string, string>(key, value));
- if(selectedValue == value)
- {
- comboBox.SelectedIndex = i;
- }
- }
-
- panel.Controls.Add(label);
- panel.Controls.Add(comboBox);
- return comboBox;
- }
- case "int":
- {
- Label label = new Label();
- label.Location = new System.Drawing.Point(0, 0);
- label.Text = node.SelectSingleNode("text").InnerText;
-
- NumericUpDown numericUpDown = new NumericUpDown();
- XmlNode valueNode = node.SelectSingleNode("value");
- if (valueNode.Attributes.GetNamedItem("max") != null)
- {
- numericUpDown.Maximum = decimal.Parse(
- valueNode.Attributes.GetNamedItem("max").Value);
- }
- if (valueNode.Attributes.GetNamedItem("min") != null)
- {
- numericUpDown.Minimum = decimal.Parse(
- valueNode.Attributes.GetNamedItem("min").Value);
- }
- numericUpDown.Value = decimal.Parse(valueNode.InnerText);
- numericUpDown.Location = new System.Drawing.Point(
- 5, label.Height + label.Top + 2);
-
- panel.Controls.Add(label);
- panel.Controls.Add(numericUpDown);
-
- return numericUpDown;
- }
- }
-
- return null;
- }
-
-
-
-
-
- public void Update(TableLayoutPanel layout)
- {
- foreach(Control panel in layout.Controls)
- {
- if (panel is Panel)
- {
- foreach(Control control in panel.Controls)
- {
- if (map.ContainsKey(control))
- {
- map[control].SelectSingleNode("value").InnerText
- = getControlValue(control);
- }
- }
- }
- }
- doc.Save(Resource.ResourceManager.GetString("preference"));
- }
-
- protected string getControlValue(Control control)
- {
- switch (control.GetType().Name.ToString().ToLower())
- {
- case "checkbox":
- {
- return ((CheckBox)control).Checked.ToString();
- }
- case "combobox":
- {
- ComboBox comboBox = (ComboBox)control;
-
- return ((KeyValuePair<string, string>)comboBox.SelectedItem).Value;
- }
- case "numericupdown":
- {
- return ((NumericUpDown)control).Value.ToString();
- }
- }
- throw new Exception("未知的类型");
- }
- }
3、PreferenceReader 读取一个选项的值
-
-
-
- class PreferenceReader
- {
- protected XmlDocument doc = new XmlDocument();
-
- public PreferenceReader(string filePath)
- {
- doc.LoadXml(File.ReadAllText(filePath));
- }
-
- public bool ReadCheckbox(string fullPath)
- {
- XmlNode node = XmlLocate(fullPath);
- if(node.Attributes.GetNamedItem("type").Value != "checkbox")
- {
- throw new Exception("路径与类型不符");
- }
-
- return bool.Parse(node.SelectSingleNode("value").InnerText);
- }
-
- public string ReadCombo(string fullPath)
- {
- XmlNode node = XmlLocate(fullPath);
- if (node.Attributes.GetNamedItem("type").Value != "combo")
- {
- throw new Exception("路径与类型不符");
- }
-
- return node.SelectSingleNode("value").InnerText;
- }
-
- public int ReadInt(string fullPath)
- {
- XmlNode node = XmlLocate(fullPath);
- if (node.Attributes.GetNamedItem("type").Value != "int")
- {
- throw new Exception("路径与类型不符");
- }
-
- return int.Parse(node.SelectSingleNode("value").InnerText);
- }
-
- protected XmlNode XmlLocate(string fullPath)
- {
- string[] pathList = fullPath.Split('\\');
-
- XmlNode node = doc.SelectSingleNode("preference");
-
- int pathLevelCount = pathList.Count();
- for (int i = 0; i < pathLevelCount; i++)
- {
- bool flag = false;
- foreach (XmlNode curNode in node.ChildNodes)
- {
- if (curNode.Attributes.GetNamedItem("name") != null &&
- curNode.Attributes.GetNamedItem("name").Value == pathList[i])
- {
- node = curNode;
- flag = true;
- break;
- }
- }
- if (!flag)
- {
- throw new Exception("no such path:" + pathList[i]);
- }
- }
-
- return node;
- }
- }
Demo:
- public partial class FrmPreference : Form
- {
- OptionsAdapter optionsAdapter = new OptionsAdapter(
- Resource.ResourceManager.GetString("preference"));
-
- public FrmPreference()
- {
- InitializeComponent();
- }
-
- private void FrmPreference_Load(object sender, EventArgs e)
- {
- PreferenceTreeAdapter preferenceTreeAdapter = new PreferenceTreeAdapter("preference.xml");
- preferenceTreeAdapter.Fill(optionsView);
- if(optionsView.Nodes.Count > 0)
- {
- optionsView.SelectedNode = optionsView.Nodes[0];
- }
-
- tableLayoutPanel.AutoSize = true;
- }
-
- private void btnSave_Click(object sender, EventArgs e)
- {
- optionsAdapter.Update(tableLayoutPanel);
- }
-
- private void button1_Click(object sender, EventArgs e)
- {
- PreferenceReader preferenceReader = new PreferenceReader(
- Resource.ResourceManager.GetString("preference"));
-
- MessageBox.Show(preferenceReader.ReadInt("标签1\\set1").ToString());
- MessageBox.Show(preferenceReader.ReadCheckbox("标签1\\set2").ToString());
- MessageBox.Show(preferenceReader.ReadCombo("标签1\\set3").ToString());
- }
-
- private void optionsView_AfterSelect(object sender, TreeViewEventArgs e)
- {
- optionsAdapter.Fill(tableLayoutPanel, optionsView.SelectedNode.FullPath);
- }
Demo效果如下
最近一直在实习,晚上才有一点点时间自己写代码,断断续续写了N天,写的也有点乱了,初步就这么一个效果,有空再完善下界面。这种方式写的设置界面有个好处,只需要编码一次,以后再用就只要修改下XML文件就行了,界面的显示就交给软件自动去完成。而且代码还算精简,没有用到反射,但也有一定的扩展性了。
本文转自 dogegg250 51CTO博客,原文链接:http://blog.51cto.com/jianshusoft/637077,如需转载请自行联系原作者