您的位置:首页 > 编程语言

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

2011-08-11 22:53 609 查看
现在界面设计越来越注重与代码的分离,把表现界面的元素写在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控件中。

/// <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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐