天天看点

界面代码分离的软件设置界面设计

   现在界面设计越来越注重与代码的分离,把表现界面的元素写在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"> 

      <value max="100" min="0">100</value> 

    <item type="checkbox" name="set2"> 

      <text>设置2</text> 

      <value>True</value> 

    <item type="combo" name="set3"> 

      <combo> 

        <text>item0</text> 

        <value>0</value> 

      </combo> 

        <text>item1</text> 

        <value>1</value> 

      <text>选择XX选项:</text> 

      <value>1</value> 

  </page> 

  <page name="标签2"> 

    <page name="标签2_1"> 

    </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"中的选项和控件的对应关系,是一个双向的适配器。

    /// 将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": 

                        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( 

                        panel.Controls.Add(numericUpDown); 

                        return numericUpDown; 

            return null; 

        /// 把窗体上的控件的值保存到XML中 

        /// <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()) 

                        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> 

   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) 

           if (node.Attributes.GetNamedItem("type").Value != "combo") 

           return node.SelectSingleNode("value").InnerText; 

       public int ReadInt(string fullPath) 

           if (node.Attributes.GetNamedItem("type").Value != "int") 

           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( 

            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效果如下

<a target="_blank" href="http://blog.51cto.com/attachment/201108/225634117.png"></a>

最近一直在实习,晚上才有一点点时间自己写代码,断断续续写了N天,写的也有点乱了,初步就这么一个效果,有空再完善下界面。这种方式写的设置界面有个好处,只需要编码一次,以后再用就只要修改下XML文件就行了,界面的显示就交给软件自动去完成。而且代码还算精简,没有用到反射,但也有一定的扩展性了。

本文转自 dogegg250 51CTO博客,原文链接:http://blog.51cto.com/jianshusoft/637077,如需转载请自行联系原作者

继续阅读