在应用程序开发中,需求不清晰从而导致需求变更是一个让所有人深恶痛绝的事情,用户界面也是其中很重要的部分。之所以用户的需求不清晰是因为在很多时候,用户的脑海中往往只会构思和想象用户界面的一部分,他只能告诉你他想要的软件大概是个什么样子,有哪些功能和操作;你们也许会在纸上或者通过绘图工具绘制一些用户界面,这也不够准确直观;而让用户自己使用VisualStudio设计他想要的界面更是不现实的。所以,我们是不是可以提供一个简单的界面设计器,让用户自己设计他的一部分界面,从而使得他的需求更明确些呢?
Microsoft Expression Blend就是这样的一个软件,但是Blend还是太专业复杂了些,用户肯定不愿意去学习使用这样的软件;而我们自己做一个界面设计器也代价太大,使用第三方开发的软件也需要购买成本和学习成本。所以,我想我们是不是可以先做好一个大概的界面原型,然后让用户自己设置更改这个界面原型?
我设想的流程是这样的:
1. 开发人员和客户先讨论界面大概做成什么样子。
2. 开发人员做出一个界面的原型,就是一个From。
3. 客户将Form跑起来,并能够自定义该Form,能够给Form及Form上的元素加注释。
4. 客户将自定义的Form保存,开发人员拿到结果后再做一个原型…
关于界面原型的一些初步构想:
1、 只做一个纯粹的界面,界面上不需要加事件处理函数等,除非会弹出另外一个需要和客户确认的Dialog/Form。
2、 在希望用户自定义的地方(一般而言是Form或者某个控件)添加ContextMenu或者其它可以定制控件的能力,但是添加的设计功能不应该影响用户对于界面的直观感受,所以直接暴露在用户眼中的部分越少越好
3、 应当给用户设置每一个控件注释的能力,注释中包含开发人员的解释以及客户的解释,比如说这个按钮点击以后会发生什么事,而客户对于无法设置的地方也可以加上他自己的想法。
关于如何保存用户对界面的修改:我们可以把Form对象序列化成源代码,就像在VisualStudio的设计器中设计Form那样。至于如何将一个Form序列化成源代码,请参照从 Component对象到CodeDom——舞动你的Code系列(1)
开发人员拿到源代码后,根据客户的改动以及注释再做一个更接近用户想法的原型,可以再给客户自己修改直到满意为止。
以下是本人使用一个ToolStrip实现的一个简单的Demo,用户可以使用ToolStrip定制Form上任意Control的注释、字体、位置、尺寸以及前景色背景色。当用户没有点击任何Control的时候:
用户点击了‘客户名’这个Label后:
设置字体为‘黑体’,字号为‘10’,背景色,前景色:
这个ToolStrip已经被包装成了一个Control,只需要把它拖到任意Form上,并在Form上接收所有Control的MouseClick事件,并把点击的Control设置给该ToolStrip的SelectedControl属性即可。
ToolStrip的源码:
代码
using
System;
using
System.Collections.Generic;
using
System.Text;
using
System.Windows.Forms;
using
System.Drawing;
namespace
GrapeCity.Cylj.ControlSetToolBar
{
public
class
ControlSetToolBar : ToolStrip
{
public
ControlSetToolBar()
{
this
.InitializeComponent();
System.Drawing.Text.InstalledFontCollection fonts
=
new
System.Drawing.Text.InstalledFontCollection();
foreach
(System.Drawing.FontFamily font
in
fonts.Families)
{
this
.font.Items.Add(font.Name);
}
}
private
Control selectedControl;
public
Control SelectedControl
{
get
{
return
selectedControl;
}
set
{
if
(value
==
null
)
{
this
.Enabled
=
false
;
this
.comments.Text
=
""
;
this
.font.Text
=
""
;
this
.fontSize.Text
=
""
;
this
.xLocation.Text
=
""
;
this
.yLocation.Text
=
""
;
this
.width.Text
=
""
;
this
.height.Text
=
""
;
}
if
(
this
.selectedControl
!=
value)
{
this
.Enabled
=
true
;
selectedControl
=
value;
string
comments
=
selectedControl.Tag
==
null
?
""
: selectedControl.Tag.ToString();
if
(
string
.IsNullOrEmpty(comments))
{
this
.comments.TextChanged
-=
new
EventHandler(comments_TextChanged);
this
.comments.Text
=
"
请在此输入注释
"
;
this
.comments.ForeColor
=
Color.Gray;
this
.comments.TextChanged
+=
new
EventHandler(comments_TextChanged);
}
else
{
this
.comments.Text
=
comments;
this
.comments.ForeColor
=
SystemColors.WindowText;
}
this
.font.Text
=
selectedControl.Font.Name;
this
.fontSize.Text
=
(
int
)Math.Round(selectedControl.Font.Size)
+
""
;
this
.xLocation.Text
=
selectedControl.Location.X
+
""
;
this
.yLocation.Text
=
selectedControl.Location.Y
+
""
;
this
.width.Text
=
selectedControl.Width
+
""
;
this
.height.Text
=
selectedControl.Height
+
""
;
this
.backColor.BackColor
=
selectedControl.BackColor;
this
.backColor.ForeColor
=
selectedControl.ForeColor;
this
.foreColor.BackColor
=
selectedControl.BackColor;
this
.foreColor.ForeColor
=
selectedControl.ForeColor;
}
}
}
private
ToolStripTextBox comments;
private
ToolStripLabel toolStripLabel1;
private
ToolStripLabel toolStripLabel2;
private
ToolStripComboBox font;
private
ToolStripLabel toolStripLabel3;
private
ToolStripComboBox fontSize;
private
ToolStripLabel toolStripLabel4;
private
ToolStripTextBox xLocation;
private
ToolStripLabel toolStripLabel5;
private
ToolStripTextBox yLocation;
private
ToolStripLabel toolStripLabel6;
private
ToolStripTextBox width;
private
ToolStripLabel toolStripLabel7;
private
ToolStripTextBox height;
private
ToolStripButton backColor;
private
ToolStripButton foreColor;
private
ToolStripSeparator toolStripSeparator1;
private
ToolStripSeparator toolStripSeparator2;
private
ToolStripSeparator toolStripSeparator3;
private
void
InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources
=
new
System.ComponentModel.ComponentResourceManager(
typeof
(ControlSetToolBar));
this
.comments
=
new
System.Windows.Forms.ToolStripTextBox();
this
.toolStripLabel1
=
new
System.Windows.Forms.ToolStripLabel();
this
.toolStripLabel2
=
new
System.Windows.Forms.ToolStripLabel();
this
.font
=
new
System.Windows.Forms.ToolStripComboBox();
this
.toolStripLabel3
=
new
System.Windows.Forms.ToolStripLabel();
this
.fontSize
=
new
System.Windows.Forms.ToolStripComboBox();
this
.toolStripLabel4
=
new
System.Windows.Forms.ToolStripLabel();
this
.xLocation
=
new
System.Windows.Forms.ToolStripTextBox();
this
.toolStripLabel5
=
new
System.Windows.Forms.ToolStripLabel();
this
.yLocation
=
new
System.Windows.Forms.ToolStripTextBox();
this
.toolStripLabel6
=
new
System.Windows.Forms.ToolStripLabel();
this
.width
=
new
System.Windows.Forms.ToolStripTextBox();
this
.toolStripLabel7
=
new
System.Windows.Forms.ToolStripLabel();
this
.height
=
new
System.Windows.Forms.ToolStripTextBox();
this
.backColor
=
new
System.Windows.Forms.ToolStripButton();
this
.foreColor
=
new
System.Windows.Forms.ToolStripButton();
this
.toolStripSeparator1
=
new
ToolStripSeparator();
this
.toolStripSeparator2
=
new
ToolStripSeparator();
this
.toolStripSeparator3
=
new
ToolStripSeparator();
this
.SuspendLayout();
//
//
toolStripTextBox1
//
this
.comments.Name
=
"
comments
"
;
this
.comments.Size
=
new
System.Drawing.Size(
100
,
21
);
this
.comments.Text
=
"
请在此输入注释
"
;
this
.comments.TextChanged
+=
new
EventHandler(comments_TextChanged);
//
//
toolStripLabel1
//
this
.toolStripLabel1.Name
=
"
toolStripLabel1
"
;
this
.toolStripLabel1.Size
=
new
System.Drawing.Size(
0
,
22
);
//
//
toolStripLabel2
//
this
.toolStripLabel2.Name
=
"
toolStripLabel2
"
;
this
.toolStripLabel2.Size
=
new
System.Drawing.Size(
29
,
12
);
this
.toolStripLabel2.Text
=
"
字体
"
;
//
//
toolStripComboBox1
//
this
.font.FlatStyle
=
System.Windows.Forms.FlatStyle.System;
this
.font.Name
=
"
font
"
;
this
.font.Size
=
new
System.Drawing.Size(
75
,
20
);
this
.font.TextChanged
+=
new
EventHandler(font_TextChanged);
//
//
toolStripLabel3
//
this
.toolStripLabel3.Name
=
"
toolStripLabel3
"
;
this
.toolStripLabel3.Size
=
new
System.Drawing.Size(
29
,
12
);
this
.toolStripLabel3.Text
=
"
字号
"
;
this
.fontSize.TextChanged
+=
new
EventHandler(fontSize_TextChanged);
//
//
toolStripComboBox2
//
this
.fontSize.Items.AddRange(
new
object
[] {
"
8
"
,
"
9
"
,
"
10
"
,
"
12
"
,
"
14
"
,
"
16
"
});
this
.fontSize.Name
=
"
toolStripComboBox2
"
;
this
.fontSize.Size
=
new
System.Drawing.Size(
75
,
20
);
//
//
toolStripLabel4
//
this
.toolStripLabel4.Name
=
"
toolStripLabel4
"
;
this
.toolStripLabel4.Size
=
new
System.Drawing.Size(
17
,
12
);
this
.toolStripLabel4.Text
=
"
X:
"
;
//
//
toolStripTextBox2
//
this
.xLocation.Name
=
"
toolStripTextBox2
"
;
this
.xLocation.Size
=
new
System.Drawing.Size(
30
,
21
);
this
.xLocation.TextChanged
+=
new
EventHandler(xLocation_TextChanged);
//
//
toolStripLabel5
//
this
.toolStripLabel5.Name
=
"
toolStripLabel5
"
;
this
.toolStripLabel5.Size
=
new
System.Drawing.Size(
17
,
12
);
this
.toolStripLabel5.Text
=
"
Y:
"
;
//
//
toolStripTextBox3
//
this
.yLocation.Name
=
"
toolStripTextBox3
"
;
this
.yLocation.Size
=
new
System.Drawing.Size(
30
,
21
);
this
.yLocation.TextChanged
+=
new
EventHandler(yLocation_TextChanged);
//
//
toolStripLabel6
//
this
.toolStripLabel6.Name
=
"
toolStripLabel6
"
;
this
.toolStripLabel6.Size
=
new
System.Drawing.Size(
23
,
12
);
this
.toolStripLabel6.Text
=
"
宽:
"
;
//
//
toolStripTextBox4
//
this
.width.Name
=
"
toolStripTextBox4
"
;
this
.width.Size
=
new
System.Drawing.Size(
30
,
21
);
this
.width.TextChanged
+=
new
EventHandler(width_TextChanged);
//
//
toolStripLabel7
//
this
.toolStripLabel7.Name
=
"
toolStripLabel7
"
;
this
.toolStripLabel7.Size
=
new
System.Drawing.Size(
23
,
12
);
this
.toolStripLabel7.Text
=
"
高:
"
;
//
//
toolStripTextBox5
//
this
.height.Name
=
"
toolStripTextBox5
"
;
this
.height.Size
=
new
System.Drawing.Size(
30
,
21
);
this
.height.TextChanged
+=
new
EventHandler(height_TextChanged);
//
//
toolStripButton1
//
this
.backColor.DisplayStyle
=
System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this
.backColor.Image
=
((System.Drawing.Image)(resources.GetObject(
"
toolStripButton1.Image
"
)));
this
.backColor.ImageTransparentColor
=
System.Drawing.Color.Magenta;
this
.backColor.Name
=
"
toolStripButton1
"
;
this
.backColor.Size
=
new
System.Drawing.Size(
26
,
22
);
this
.backColor.Text
=
"
背景色
"
;
this
.backColor.Click
+=
new
EventHandler(backColor_Click);
this
.backColor.Margin
=
new
Padding(
0
,
0
,
3
,
0
);
//
//
toolStripButton2
//
this
.foreColor.DisplayStyle
=
System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this
.foreColor.Image
=
((System.Drawing.Image)(resources.GetObject(
"
toolStripButton2.Image
"
)));
this
.foreColor.ImageTransparentColor
=
System.Drawing.Color.Magenta;
this
.foreColor.Name
=
"
toolStripButton2
"
;
this
.foreColor.Size
=
new
System.Drawing.Size(
26
,
22
);
this
.foreColor.Text
=
"
前景色
"
;
this
.foreColor.Click
+=
new
EventHandler(foreColor_Click);
//
//
ControlSetToolBar
//
this
.Items.AddRange(
new
System.Windows.Forms.ToolStripItem[] {
this
.toolStripLabel1,
this
.comments,
this
.toolStripSeparator1,
this
.toolStripLabel2,
this
.font,
this
.toolStripLabel3,
this
.fontSize,
this
.toolStripLabel4,
this
.toolStripSeparator3,
this
.xLocation,
this
.toolStripLabel5,
this
.yLocation,
this
.toolStripLabel6,
this
.width,
this
.toolStripLabel7,
this
.height,
this
.toolStripSeparator3,
this
.backColor,
this
.foreColor});
this
.ResumeLayout(
false
);
}
void
foreColor_Click(
object
sender, EventArgs e)
{
Color newColor;
if
(SetColor(
this
.selectedControl.ForeColor,
out
newColor))
{
this
.selectedControl.ForeColor
=
newColor;
this
.foreColor.ForeColor
=
newColor;
this
.backColor.ForeColor
=
newColor;
}
}
void
backColor_Click(
object
sender, EventArgs e)
{
Color newColor;
if
(SetColor(
this
.selectedControl.BackColor,
out
newColor))
{
this
.selectedControl.BackColor
=
newColor;
this
.foreColor.BackColor
=
newColor;
this
.backColor.BackColor
=
newColor;
}
}
private
bool
SetColor(Color oldColor,
out
Color newColor)
{
ColorDialog dialog
=
new
ColorDialog();
dialog.Color
=
oldColor;
DialogResult result
=
dialog.ShowDialog(
this
.selectedControl.FindForm());
if
(result
==
DialogResult.OK
||
result
==
DialogResult.Yes)
{
newColor
=
dialog.Color;
return
true
;
}
newColor
=
oldColor;
return
false
;
}
void
height_TextChanged(
object
sender, EventArgs e)
{
if
(
this
.selectedControl
==
null
)
{
return
;
}
int
intValue;
bool
success
=
Int32.TryParse(
this
.height.Text,
out
intValue);
if
(
!
success)
{
this
.height.Text
=
this
.selectedControl.Height
+
""
;
return
;
}
this
.selectedControl.Height
=
intValue;
}
void
width_TextChanged(
object
sender, EventArgs e)
{
if
(
this
.selectedControl
==
null
)
{
return
;
}
int
intValue;
bool
success
=
Int32.TryParse(
this
.width.Text,
out
intValue);
if
(
!
success)
{
this
.width.Text
=
this
.selectedControl.Width
+
""
;
return
;
}
this
.selectedControl.Width
=
intValue;
}
void
yLocation_TextChanged(
object
sender, EventArgs e)
{
if
(
this
.selectedControl
==
null
)
{
return
;
}
int
intValue;
bool
success
=
Int32.TryParse(
this
.yLocation.Text,
out
intValue);
if
(
!
success)
{
this
.yLocation.Text
=
this
.selectedControl.Location.Y
+
""
;
return
;
}
this
.selectedControl.Location
=
new
System.Drawing.Point(
this
.selectedControl.Location.X, intValue);
}
void
xLocation_TextChanged(
object
sender, EventArgs e)
{
if
(
this
.selectedControl
==
null
)
{
return
;
}
int
intValue;
bool
success
=
Int32.TryParse(
this
.xLocation.Text,
out
intValue);
if
(
!
success)
{
this
.xLocation.Text
=
this
.selectedControl.Location.X
+
""
;
return
;
}
this
.selectedControl.Location
=
new
System.Drawing.Point(intValue,
this
.selectedControl.Location.Y);
}
void
fontSize_TextChanged(
object
sender, EventArgs e)
{
if
(
this
.selectedControl
==
null
)
{
return
;
}
int
intValue;
bool
success
=
Int32.TryParse(
this
.fontSize.Text,
out
intValue);
if
(
!
success)
{
this
.fontSize.Text
=
Math.Round(
this
.selectedControl.Font.Size)
+
""
;
return
;
}
this
.selectedControl.Font
=
new
System.Drawing.Font(
this
.font.Text, intValue);
}
void
font_TextChanged(
object
sender, EventArgs e)
{
if
(
this
.selectedControl
==
null
)
{
return
;
}
if
(
this
.font.SelectedItem
!=
null
)
{
this
.selectedControl.Font
=
new
System.Drawing.Font(
this
.font.SelectedItem.ToString(),
this
.selectedControl.Font.Size);
}
}
void
comments_TextChanged(
object
sender, EventArgs e)
{
if
(
this
.selectedControl
==
null
)
{
return
;
}
this
.selectedControl.Tag
=
this
.comments.Text;
this
.comments.ToolTipText
=
this
.comments.Text;
}
}
}
Form的源码:
代码
public
Form1()
{
InitializeComponent();
AddEvents(
this
);
}
private
void
AddEvents(Control parentControl)
{
foreach
(Control control
in
parentControl.Controls)
{
if
(control
==
this
.controlSetToolBar1)
{
continue
;
}
control.MouseClick
+=
new
MouseEventHandler(control_MouseClick);
AddEvents(control);
}
}
void
control_MouseClick(
object
sender, MouseEventArgs e)
{
Control control
=
sender
as
Control;
if
(control
!=
null
)
{
this
.controlSetToolBar1.SelectedControl
=
control;
}
}
当然,这只是本人的一个构想,尚没有成功的商业案例。如何您对这个想法感兴趣或者您还有些别的更好的做法,请回帖或者直接给我发信讨论。
本篇是 从Component对象到CodeDom——舞动你的Code系列(1) 的应用,可以算作舞动你的Code系列(1.1)。如果你对设计器、VisualStudio扩展、序列化、控件设计等相关技术感兴趣,欢迎订阅收藏本博客。

