介面程式碼分離的軟體設定介面設計
現在介面設計越來越注重與程式碼的分離,把表現介面的元素寫在XML檔案中,程式載入時再通過反射等機制載入到程式裡。以前我寫的小程式,也有些設定功能,往往把介面直接在程式碼裡寫死。如果選項不多還好,如果選項一多,就使介面混亂不堪了。所以我也採用了XML配置檔案的方式來編寫設定功能。
但是既然是小程式,就要保持原來短小精悍的風格,速度也不能太慢,最重要的是程式碼編寫得方便,所以不能太那些框架那樣搞。我先分析,設定介面大概需要這些東西:1,數值選擇框、2,複選框、3,單選框,4,其它(可以有TextBox之類的)。我只需要前三項,且第3項單選框我用“下拉選擇框”來代替,可以節省空間(其實主要是方便編碼啦)。
先來規定下XML的格式,下面是一個示例。
- <?xmlversion="1.0"encoding="utf-8"?>
- <preference>
- <pagename="標籤1">
- <itemtype="int"name="set1">
- <text>設定1</text>
- <valuemax="100"min="0">5</value>
- </item>
- <itemtype="int"
- <text>設定1</text>
- <valuemax="100"min="0">100</value>
- </item>
- <itemtype="checkbox"name="set2">
- <text>設定2</text>
- <value>True</value>
- </item>
- <itemtype="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>
- <pagename="標籤2">
- <itemtype="int"name="set1">
- <text>設定1</text>
- <valuemax="100"min="0">100</value>
- </item>
- <pagename="標籤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>
- publicclassPreferenceTreeAdapter
- {
- protectedstringxmlDoc;
- publicPreferenceTreeAdapter(StringfilePath)
- {
- xmlDoc=File.ReadAllText(filePath);
- }
- publicvoidFill(TreeViewtreeView)
- {
- XmlDocumentdoc=newXmlDocument();
- doc.LoadXml(xmlDoc);
- XmlNodeListnodeList=doc.SelectSingleNode("preference").SelectNodes("page");
- FillChildren(nodeList,treeView.Nodes);
- }
- protectedvoidFillChildren(XmlNodeListnodeList,TreeNodeCollectiontreeNodeCollection)
- {
- if(nodeList.Count==0)
- {
- return;
- }
- for(inti=0;i<nodeList.Count;i++)
- {
- XmlNodenode=nodeList[i];
- TreeNodecurTreeNode=treeNodeCollection.Add(node.Attributes.GetNamedItem("name").Value);
- XmlNodeListnewNodeList=node.SelectNodes("page");
- if(newNodeList!=null)
- {
- FillChildren(newNodeList,curTreeNode.Nodes);
- }
- }
- }
- }
2,OptionsAdapter 負責一個"page"中的選項和控制元件的對應關係,是一個雙向的介面卡。
- ///<summary>
- ///將XML中一個page的選項展示在窗體中
- ///</summary>
- publicclassOptionsAdapter
- {
- protectedDictionary<Control,XmlNode>map=newDictionary<Control,XmlNode>();
- protectedXmlDocumentdoc=newXmlDocument();
- publicOptionsAdapter(stringfilePath)
- {
- doc.LoadXml(File.ReadAllText(filePath));
- }
- ///<summary>
- ///當路徑對應的設定項填充到介面中
- ///</summary>
- ///<paramname="layout">要填充到的物件</param>
- ///<paramname="fullPath">路徑</param>
- publicvoidFill(TableLayoutPanellayout,stringfullPath)
- {
- layout.Controls.Clear();
- XmlNodenode=XmlLocate(fullPath);
- XmlNodeListoptionsList=node.SelectNodes("item");
- for(inti=0;i<optionsList.Count;i++)
- {
- Panelpanel=newPanel();
- panel.AutoSize=true;
- Controlcontrol=FillPanel(optionsList[i],panel);
- if(control!=null)
- {
- map.Add(control,optionsList[i]);//新增控制元件對節點的對映關係
- layout.Controls.Add(panel,0,i);
- }
- }
- }
- protectedXmlNodeXmlLocate(stringfullPath)
- {
- string[]pathList=fullPath.Split('\\');
- XmlNodenode=doc.SelectSingleNode("preference");
- intpathLevelCount=pathList.Count();
- for(inti=0;i<pathLevelCount;i++)
- {
- boolflag=false;
- foreach(XmlNodecurNodeinnode.SelectNodes("page"))
- {
- if(curNode.Attributes.GetNamedItem("name").Value==pathList[i])
- {
- node=curNode;
- flag=true;
- break;
- }
- }
- if(!flag)
- {
- thrownewException("nosuchpath:"+pathList[i]);
- }
- }
- returnnode;
- }
- protectedControlFillPanel(XmlNodenode,Panelpanel)
- {
- stringtype=node.Attributes.GetNamedItem("type").Value;
- switch(type)
- {
- case"checkbox":
- {
- CheckBoxcheckBox=newCheckBox();
- checkBox.Text=node.SelectSingleNode("text").InnerText;
- checkBox.Checked=bool.Parse(
- node.SelectSingleNode("value").InnerText);
- panel.Controls.Add(checkBox);
- returncheckBox;
- }
- case"combo":
- {
- Labellabel=newLabel();
- label.Text=node.SelectSingleNode("text").InnerText;
- label.Location=newSystem.Drawing.Point(0,0);
- XmlNodeListlist=node.SelectNodes("combo");
- ComboBoxcomboBox=newComboBox();
- comboBox.DisplayMember="Key";
- comboBox.ValueMember="Value";
- comboBox.Location=newSystem.Drawing.Point(
- 5,label.Height+label.Top+2);
- stringselectedValue=node.SelectSingleNode("value").InnerText;
- for(inti=0;i<list.Count;i++)
- {
- XmlNodecurNode=list[i];
- stringkey=curNode.SelectSingleNode("text").InnerText;
- stringvalue=curNode.SelectSingleNode("value").InnerText;
- comboBox.Items.Add(newKeyValuePair<string,string>(key,value));
- if(selectedValue==value)
- {
- comboBox.SelectedIndex=i;
- }
- }
- panel.Controls.Add(label);
- panel.Controls.Add(comboBox);
- returncomboBox;
- }
- case"int":
- {
- Labellabel=newLabel();
- label.Location=newSystem.Drawing.Point(0,0);
- label.Text=node.SelectSingleNode("text").InnerText;
- NumericUpDownnumericUpDown=newNumericUpDown();
- XmlNodevalueNode=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=newSystem.Drawing.Point(
- 5,label.Height+label.Top+2);
- panel.Controls.Add(label);
- panel.Controls.Add(numericUpDown);
- returnnumericUpDown;
- }
- }
- returnnull;
- }
- ///<summary>
- ///把窗體上的控制元件的值儲存到XML中
- ///</summary>
- ///<paramname="layout"></param>
- publicvoidUpdate(TableLayoutPanellayout)
- {
- foreach(Controlpanelinlayout.Controls)
- {
- if(panelisPanel)
- {
- foreach(Controlcontrolinpanel.Controls)
- {
- if(map.ContainsKey(control))
- {
- map[control].SelectSingleNode("value").InnerText
- =getControlValue(control);
- }
- }
- }
- }
- doc.Save(Resource.ResourceManager.GetString("preference"));
- }
- protectedstringgetControlValue(Controlcontrol)
- {
- switch(control.GetType().Name.ToString().ToLower())
- {
- case"checkbox":
- {
- return((CheckBox)control).Checked.ToString();
- }
- case"combobox":
- {
- ComboBoxcomboBox=(ComboBox)control;
- return((KeyValuePair<string,string>)comboBox.SelectedItem).Value;
- }
- case"numericupdown":
- {
- return((NumericUpDown)control).Value.ToString();
- }
- }
- thrownewException("未知的型別");
- }
- }
3、PreferenceReader 讀取一個選項的值
- ///<summary>
- ///讀取某個選項的值
- ///</summary>
- classPreferenceReader
- {
- protectedXmlDocumentdoc=newXmlDocument();
- publicPreferenceReader(stringfilePath)
- {
- doc.LoadXml(File.ReadAllText(filePath));
- }
- publicboolReadCheckbox(stringfullPath)
- {
- XmlNodenode=XmlLocate(fullPath);
- if(node.Attributes.GetNamedItem("type").Value!="checkbox")
- {
- thrownewException("路徑與型別不符");
- }
- returnbool.Parse(node.SelectSingleNode("value").InnerText);
- }
- publicstringReadCombo(stringfullPath)
- {
- XmlNodenode=XmlLocate(fullPath);
- if(node.Attributes.GetNamedItem("type").Value!="combo")
- {
- thrownewException("路徑與型別不符");
- }
- returnnode.SelectSingleNode("value").InnerText;
- }
- publicintReadInt(stringfullPath)
- {
- XmlNodenode=XmlLocate(fullPath);
- if(node.Attributes.GetNamedItem("type").Value!="int")
- {
- thrownewException("路徑與型別不符");
- }
- returnint.Parse(node.SelectSingleNode("value").InnerText);
- }
- protectedXmlNodeXmlLocate(stringfullPath)
- {
- string[]pathList=fullPath.Split('\\');
- XmlNodenode=doc.SelectSingleNode("preference");
- intpathLevelCount=pathList.Count();
- for(inti=0;i<pathLevelCount;i++)
- {
- boolflag=false;
- foreach(XmlNodecurNodeinnode.ChildNodes)
- {
- if(curNode.Attributes.GetNamedItem("name")!=null&&
- curNode.Attributes.GetNamedItem("name").Value==pathList[i])
- {
- node=curNode;
- flag=true;
- break;
- }
- }
- if(!flag)
- {
- thrownewException("nosuchpath:"+pathList[i]);
- }
- }
- returnnode;
- }
- }
Demo:
- publicpartialclassFrmPreference:Form
- {
- OptionsAdapteroptionsAdapter=newOptionsAdapter(
- Resource.ResourceManager.GetString("preference"));
- publicFrmPreference()
- {
- InitializeComponent();
- }
- privatevoidFrmPreference_Load(objectsender,EventArgse)
- {
- PreferenceTreeAdapterpreferenceTreeAdapter=newPreferenceTreeAdapter("preference.xml");
- preferenceTreeAdapter.Fill(optionsView);
- if(optionsView.Nodes.Count>0)
- {
- optionsView.SelectedNode=optionsView.Nodes[0];
- }
- tableLayoutPanel.AutoSize=true;
- }
- privatevoidbtnSave_Click(objectsender,EventArgse)
- {
- optionsAdapter.Update(tableLayoutPanel);
- }
- privatevoidbutton1_Click(objectsender,EventArgse)
- {
PreferenceReaderpreferenceReader=newPreferenceReader(- 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());
- }
- privatevoidoptionsView_AfterSelect(objectsender,TreeViewEventArgse)
- {
- optionsAdapter.Fill(tableLayoutPanel,optionsView.SelectedNode.FullPath);
- }
Demo效果如下
最近一直在實習,晚上才有一點點時間自己寫程式碼,斷斷續續寫了N天,寫的也有點亂了,初步就這麼一個效果,有空再完善下介面。這種方式寫的設定介面有個好處,只需要編碼一次,以後再用就只要修改下XML檔案就行了,介面的顯示就交給軟體自動去完成。而且程式碼還算精簡,沒有用到反射,但也有一定的擴充套件性了。
轉載於:https://blog.51cto.com/jianshusoft/637077