界面代码分离的软件设置界面设计
2011-08-11 22:53
609 查看
现在界面设计越来越注重与代码的分离,把表现界面的元素写在XML文件中,程序加载时再通过反射等机制加载到程序里。以前我写的小程序,也有些设置功能,往往把界面直接在代码里写死。如果选项不多还好,如果选项一多,就使界面混乱不堪了。所以我也采用了XML配置文件的方式来编写设置功能。
但是既然是小程序,就要保持原来短小精悍的风格,速度也不能太慢,最重要的是代码编写得方便,所以不能太那些框架那样搞。我先分析,设置界面大概需要这些东西:1,数值选择框、2,复选框、3,单选框,4,其它(可以有TextBox之类的)。我只需要前三项,且第3项单选框我用“下拉选择框”来代替,可以节省空间(其实主要是方便编码啦)。
先来规定下XML的格式,下面是一个示例。
用“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控件中。
2,OptionsAdapter 负责一个"page"中的选项和控件的对应关系,是一个双向的适配器。
3、PreferenceReader 读取一个选项的值
Demo:
Demo效果如下
最近一直在实习,晚上才有一点点时间自己写代码,断断续续写了N天,写的也有点乱了,初步就这么一个效果,有空再完善下界面。这种方式写的设置界面有个好处,只需要编码一次,以后再用就只要修改下XML文件就行了,界面的显示就交给软件自动去完成。而且代码还算精简,没有用到反射,但也有一定的扩展性了。
本文出自 “木又寸的技术博客” 博客,请务必保留此出处http://jianshusoft.blog.51cto.com/2380869/637077
但是既然是小程序,就要保持原来短小精悍的风格,速度也不能太慢,最重要的是代码编写得方便,所以不能太那些框架那样搞。我先分析,设置界面大概需要这些东西: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控件中。
/// <summary> /// 从XML文件中读取首选项的目录 /// </summary> 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"中的选项和控件的对应关系,是一个双向的适配器。
/// <summary> /// 将XML中一个page的选项展示在窗体中 /// </summary> 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)); } /// <summary> /// 当路径对应的设置项填充到界面中 /// </summary> /// <param name="layout">要填充到的对象</param> /// <param name="fullPath">路径</param> 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; } /// <summary> /// 把窗体上的控件的值保存到XML中 /// </summary> /// <param name="layout"></param> 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 读取一个选项的值
/// <summary> /// 读取某个选项的值 /// </summary> 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文件就行了,界面的显示就交给软件自动去完成。而且代码还算精简,没有用到反射,但也有一定的扩展性了。
本文出自 “木又寸的技术博客” 博客,请务必保留此出处http://jianshusoft.blog.51cto.com/2380869/637077
相关文章推荐
- WPF换肤之四:界面设计和代码设计分离
- EasyJWeb作为一个快速Java Web MVC框架,其设计目标不尽是要简化软件开发人员的代码书写工作,更是要能方便界面设计人员的工作。
- Android开发之使用Preferences设计软件设置界面(源代码分享)
- cocos studio设计界面,在代码中寻找按钮,设置监听函数等
- WPF换肤之四:界面设计和代码设计分离
- 一种全新的软件界面设计方法
- ios开发怎样才能做到代码和界面彻底分离,方便换肤?
- 自然系统是分层的,软件项目的设计需要减少层的相干性来推动工作的规划。微软的开发平台还是做得不完善,至少aspx界面需要浪费大量的沟通才能设计好。
- 小菜的系统框架界面设计-XiaoCai.WinformUI代码开源
- Android界面设计的4种方式之三——使用XML和JAVA代码混合控制UI界面
- Python学习 Python3.5+PyQt5环境--------02、代码与界面GUI分离
- 不懂语言代码,超级菜鸟的建站分享(二):界面设置
- 一种全新的软件界面设计方法(续)
- C#Windows窗体界面设计_攻击决策项目_02_设计窗体_03_设置窗体弹出退出顺序
- android代码中打开系统设置界面
- java代码块之简易qq登录界面及按钮颜色设置代码
- 需求分析师的核心能力--软件界面设计
- 由触摸屏所联想到的软件界面设计
- 代码大全2-软件构建中的设计
- 如何将界面代码和功能代码分离(基于Delphi/VCL)