在应用程序开发中,需求不清晰从而导致需求变更是一个让所有人深恶痛绝的事情,用户界面也是其中很重要的部分。之所以用户的需求不清晰是因为在很多时候,用户的脑海中往往只会构思和想象用户界面的一部分,他只能告诉你他想要的软件大概是个什么样子,有哪些功能和操作;你们也许会在纸上或者通过绘图工具绘制一些用户界面,这也不够准确直观;而让用户自己使用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的源码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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的源码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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扩展、序列化、控件设计等相关技术感兴趣,欢迎订阅收藏本博客。