一个明确用户界面需求的想法

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

posted on 2010-09-21 10:56  cylj.xu  阅读(5549)  评论(17编辑  收藏  举报