
WPF中ToolBar,可以放置Button。但是,默认的Button上是没有图片的。
网上找了很久,有说明如何放置图片的,但是只有图片,没有文字。
现在发现,原来图片文字都显示如此简单,XAML代码参考如下。
<ToolBar Grid.Row="0" Name="tlbMain" >
<!--Content="保存"-->
<Button Name="btnSave" Command="my:SunLightGridCommands.Save">
<Grid>
<Grid.RowDefinitions>
<RowDefinition>
</RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Image Source="/SunLight.Client.Common;component/Images/install.ico"></Image>
<TextBlock Text="保存" Grid.Row="1"></TextBlock>
</Grid>
</Button>
实际上就是设置了一下,Button的Content。通过Grid,还可以设置在左右显示等等。
在此记录一下,感觉WPF很多概念真是颠覆性的。
先介绍一个例子,如下两个表格数据:
左边表格
| ItemCode |
属性值1 |
属性值2 |
属性值3 |
| A |
20 |
30 |
40 |
| B |
30 |
30 |
44 |
| C |
25 |
30 |
44 |
| D |
34 |
30 |
60 |
|
右边表格
| ItemCode |
分类 |
属性值1 |
属性值2 |
属性值3 |
| A |
0 |
90 |
90 |
90 |
| B |
1 |
90 |
90 |
90 |
| D |
2 |
90 |
90 |
90 |
| E |
2 |
90 |
90 |
90 |
|
需求说明如下:
- 【分类】 列的值 有三种类型 0、1、2
- ItemCode是唯一的,在表格中不会重复
- 将左边的数据,按照ItemCode,复制到右边表格
如果左右两边都有ItemCode,检查分类值,如果对应的分类值是0:则不复制。
如果分类值是1:则修改属性值1
如果分类值是2:则修改属性值 1- 3
如果仅在右边的表格没有的数据,则把分类值修改成0,属性值不修改
如果仅在左边的表格有的数据,则右边新增一条,分类值是2,属性值1-3复制过来
如上例子,复制后的结果如下:
| ItemCode |
分类 |
属性值1 |
属性值2 |
属性值3 |
| A(不变) |
0 |
90 |
90 |
90 |
| B(修改1) |
1 |
30 |
90 |
90 |
| D(修改1-3) |
2 |
34 |
30 |
60 |
| E(仅右边) |
0 |
90 |
90 |
90 |
| C(仅左边) |
2 |
25 |
30 |
44 |
针对上述要求,程序实现可能如下:
public void CopyData(DataTable leftTable, DataTable rightTable)
{
//找出在仅在右边存在或者都存在的数据
foreach (DataRow row in rightTable.Rows)
{
string itemCode = row["ItemCode"] as string;
DataRow[] rows = leftTable.Select("ItemCode = '" + itemCode + "'");
if (rows.Length == 0)
{
//仅修改分类值
row["分类"] = 0;
}
else
{
//左右都存在
int type = (int)row["分类"];
if (type > 0)
{
row["属性值1"] = row["属性值1"];
if (type == 2)
{
row["属性值2"] = row["属性值2"];
row["属性值3"] = row["属性值3"];
}
}
}
}
//找出仅在左边存在数据
foreach (DataRow row in leftTable.Rows)
{
string itemCode = row["ItemCode"] as string;
DataRow[] rows = rightTable.Select("ItemCode = '" + itemCode + "'");
if (rows.Length == 0)
{
//在右边新增一条
DataRow newRow = rightTable.NewRow();
newRow["分类"] = 2; //分类值是2
newRow["ItemCode"] = itemCode;
newRow["属性值1"] = row["属性值1"];
newRow["属性值2"] = row["属性值2"];
newRow["属性值3"] = row["属性值3"];
rightTable.Rows.Add(newRow);
}
else
{
//左右都存在
//什么也不做
}
}
}
上面的代码应该算是清晰的。而且有了足够的注释,不算很难读懂。
但是,这个代码和业务逻辑描述的是一致的吗?换句话说:如果没有需求文档,单单看上面代码,能得到上面的总结的需求吗?
我觉得要整理出上面需求比较难,理由如下。
- 从代码中看不出来分类值 是 仅有0、1、2
- 如果没有这些注释,也很难读出需求三种描述的逻辑(两者都有、仅左边有、仅右边有)。尤其两个循环存在,让代码逻辑更加不清晰。
针对第一个问题,似乎很好解决。可以定义一个枚举类型来描述分类值就可以了,如下:
public enum Enu分类
{
不改变 = 0,
仅改变1 = 1,
全部改变 = 2
}
相应的代码可以修改成这样
if (rows.Length == 0)
{
//仅修改分类值
row["分类"] =(int) Enu分类.不改变;
}
else
{
//左右都存在
Enu分类 type = (Enu分类)row["分类"];
switch(type)
{
case Enu分类.全部改变:
row["属性值1"] = row["属性值1"];
row["属性值2"] = row["属性值2"];
row["属性值3"] = row["属性值3"];
break;
case Enu分类.仅改变1:
row["属性值1"] = row["属性值1"];
break;
case Enu分类.不改变:
break;
}
}
但是,对于第2个问题,则比较复杂了。如果和能像需求说明那样简洁的实现呢?
仔细分析,实际上需求说明中表述两个两层意思:
1、对比左右两个表格,得到都存在的、仅左边、仅右边结果
2、三种数据处理方式如下:
1)、都存在的处理方式按照分类
2)、仅左边的,则在右边新增一行
3)、仅右边的,则修改分类为0
那么另外一种实现方式是
1、做一个DataTable对比类,得到三种类型数据
2、遍历这三种数据即可。
实现方式如下:
public void CopyData2(DataTable leftTable, DataTable rightTable)
{
DataTableComparer cmp = new DataTableComparer(leftTable, rightTable, "ItemCode");
foreach(CompareResult result in cmp.Rows)
{
switch (result.Type)
{
case ResultType.Both:
//左右都存在
Enu分类 type = (Enu分类)result.RightRow["分类"];
switch (type)
{
case Enu分类.全部改变:
result.RightRow["属性值1"] = result.LeftRow["属性值1"];
result.RightRow["属性值2"] = result.LeftRow["属性值2"];
result.RightRow["属性值3"] = result.LeftRow["属性值3"];
break;
case Enu分类.仅改变1:
result.RightRow["属性值1"] = result.LeftRow["属性值1"];
break;
case Enu分类.不改变:
break;
}
break;
case ResultType.LeftOnly:
DataRow newRow = rightTable.NewRow();
newRow["分类"] = Enu分类.全部改变;
newRow["ItemCode"] = result.LeftRow["ItemCode"];
newRow["属性值1"] = result.LeftRow["属性值1"];
newRow["属性值2"] = result.LeftRow["属性值2"];
newRow["属性值3"] = result.LeftRow["属性值3"];
rightTable.Rows.Add(newRow);
break;
case ResultType.RightOnly:
//仅修改分类值
result.RightRow["分类"] = (int)Enu分类.不改变;
break;
}
}
}
TableComparer类实现如下:
class DataTableComparer
{
public DataTableComparer(DataTable left, DataTable right, params string[] keys)
{
m_rows = CreateAllRows(left.Rows, right.Rows, keys);
}
private string GetKey(DataRow row, string[] keys)
{
StringBuilder sb = new StringBuilder();
foreach (string key in keys)
{
sb.Append(Convert.ToString(row[key]) + "|");
}
return sb.ToString();
}
private Dictionary<string, CompareResult> CreateAllRows(IEnumerable leftRows, IEnumerable rightRows, string[] keys)
{
Dictionary<string, CompareResult> rows = new Dictionary<string, CompareResult>();
foreach (DataRow row in leftRows)
{
string key = GetKey(row, keys);
CompareResult result = new CompareResult();
result.LeftRow = row;
result.Type = ResultType.LeftOnly;
rows.Add(key, result);
}
foreach (DataRow row in rightRows)
{
string key = GetKey(row, keys);
if (rows.ContainsKey(key))
{
CompareResult result = rows[key];
result.Type = ResultType.Both;
result.RightRow = row;
}
else
{
CompareResult result = new CompareResult();
result.RightRow = row;
result.Type = ResultType.RightOnly;
rows.Add(key, result);
}
}
return rows;
}
private Dictionary<string, CompareResult> m_rows;
public IEnumerable<CompareResult> Rows
{
get
{
foreach (KeyValuePair<string, CompareResult> keyValue in m_rows)
{
yield return keyValue.Value;
}
}
}
}
public class CompareResult
{
public ResultType Type;
public DataRow LeftRow;
public DataRow RightRow;
}
public enum ResultType
{
Both,
LeftOnly,
RightOnly,
}
结论:
- 软件代码中的注释对代码维护工作非常重要。但是,结构良好,更接近于自然语言描述的代码更重要。甚至,有时候可以替代注释的效果。
- 软件需求在转换为软件代码过程中,仍然有值得仔细推敲,整理的必要,这样才能保证在实现过程中,尽量与软件需求描述一致。
- 当然,上述代码中,第二种方案有时候也未必是最优的。如果考虑性能的话,大数据量的dataTable,可能第一种方案还是可取的。
- 以上没有使用Linq去实现,如果用到Linq的话,代码应该还可以简化。
刚刚下载了Live Writer,据说使用该动动写博客比较方便。试验一下看看。
看到很多人太需求变化,我也谈一下,凑个热闹。
举个例子:
比如有一个程序要用combox显示一个列表。列表内容是X1、X2、X3。程序中会根据X1,X2,X3判断做一些处理。
参考代码如下:

代码
int value = comboBox1.SelectedIndex;
switch (value)
{
case 0:
MessageBox.Show("x1");
break;
case 1:
MessageBox.Show("x2");
break;
case 2:
MessageBox.Show("x3");
break;
}
用户使用过一段时间后,需要增加列表内容:比如增加X4,X5。那么上述程序就要改成:

代码
int value = comboBox1.SelectedIndex;
switch (value)
{
case 0:
MessageBox.Show("x1");
break;
case 1:
MessageBox.Show("x2");
break;
case 2:
MessageBox.Show("x3");
break;
//v2.0 add 支持x4,x5
case 3:
MessageBox.Show("x4");
break;
case 4:
MessageBox.Show("x5");
break;
}
维护代码,风格好点的会写点注释。
这样的方式可以一直延续下去,用户不断增加需求,程序员不断修改代码。但是,有没有更好的方案呢?我相信博客园的高手众多,肯定又很多很好的方案。我想的方案是这样的:

代码
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
List<ObjectHolder> list = new List<ObjectHolder>();
list.Add(new ObjectHolder(0,"test1"));
list.Add(new ObjectHolder(0,"test1"));
list.Add(new ObjectHolder(0,"test1"));
comboBox1.DataSource = list;
}
private void button1_Click(object sender, EventArgs e)
{
//int value = comboBox1.SelectedIndex;
//switch (value)
//{
// case 0:
// MessageBox.Show("x1");
// break;
// case 1:
// MessageBox.Show("x2");
// break;
// case 2:
// MessageBox.Show("x3");
// break;
// //v2.0 add 支持x4,x5
// case 3:
// MessageBox.Show("x4");
// break;
// case 4:
// MessageBox.Show("x5");
// break;
//}
// V3.0 重构
ObjectHolder selectObject = comboBox1.SelectedItem as ObjectHolder;
selectObject.DoWork();
}
}
class ObjectHolder
{
public int value;
public string caption;
public ObjectHolder(int value, string caption)
{
this.value = value;
this.caption = caption;
}
public override string ToString()
{
return caption;
}
public void DoWork()
{
MessageBox.Show(this.caption);
}
}
引入对象的方法来解决。将来如果变化仍然频繁,可以考虑把combox列表配置成外部文件,如:XML。Table等等。
说了这么多,我觉得就是敏捷里面的一个核心思想,拥抱变化。通过重构来不断进化你的程序架构。这样,始终保持你的程序健壮性。又不至于过度设计。
当然,上面的例子可能有些过于简单,第一个版本就设计成对象的方式也未尝不可。只是,如果复杂的例子,能够经历过这样的演进过程会让程序变化更平稳。
看过一个博士生与民工的笑话:
联合利华引进了一条香皂包装生产线,结果发现这条生产线有个缺陷:常常会有盒子里
没装入香皂。总不能把空盒子卖给顾客啊,他们只得请了一个学自动化的博士后设计一
个方案来分拣空的香皂盒。博士后拉起了一个十几人的科研攻关小组,综合采用了机械
、微电子、自动化、X射线探测等技术,花了几十万,成功解决了问题。每当生产线上
有空香皂盒通过,两旁的探测器会检测到,并且驱动一只机械手把空皂盒推走。
中国南方有个乡镇企业也买了同样的生产线,老板发现这个问题后大为发火,找了个小
工来说:“你他妈给老子把这个搞定,不然你给老子爬走。”小工很快想出了办法:他
花了90块钱在生产线旁边放了一台大功率电风扇猛吹,于是空皂盒都被吹走了。
这个故事对于从软件开发角度理解:我觉得是简单就是美。我们往往容易被自己的知识束缚,
往往会把本来简单的事情想复杂化了。或者,有时候过于迷恋技术,而往往忘了一个本来要解决的问题。
所以,考虑问题的时候,有时候可以先问问自己,有没有简单的方法? 而不是先想到可以
用哪些很酷的技术。
摘要: 前几天做了关于COM+的性能测试,http://www.cnblogs.com/dcll/archive/2004/08/30/37530.aspx,结果因为表格太长,被dudu屏蔽了。今天偶然在CodeProject上看到一篇文章也提到了COM+的性能问题,http://www.codeproject.com/dotnet/complusnetpracticalapp01.asp其中涉及到性能数...
阅读全文
摘要: 我希望实现一个分布式系统,要求可以通过Internet/Intranet访问。但是,不希望用WEB实现,因为web页面的处理能力有限。.NET中该选什么技术?Remoting ? COM+?,请各位高手指点。或者谈谈各位在工作怎么做的?谢谢。
阅读全文
摘要: 这两天在研究.NET分布式应用方案,我对.NET不是很了解,只知道就是三种方式可以实现分布式应用:COM+,.NET Remoting,Web Service。感觉.NET Remoting提供很大灵活性,支持tcp,http协议。而且,基于性能上也考虑两种序列化方式binary和Soap方式。要比较那种方式好,要分几个方面,目前做了COM+,.NET Remoting调用性能对比。对比方法:分别...
阅读全文