2007年6月20日星期三

回车变成Tab的解决方案

IE会将页面上的第一个按钮自动设为默认按钮。当用户在页面上按回车键时,就会触发这个默认按钮。事实上,有很多用户有在TextBox中输入数据之后按回车的习惯(似乎是长期使用Excel养成的习惯)。想像一下,当用户依次在20个TextBox中输入了数据之后,一不小心习惯性地按了一下回车,而该页面上的默认按钮是“检索”按钮!@##¥@...刚刚的工作全部丢失了。

有了VS2005,这个问题似乎一下子变得容易解决了,因为在VS2005的页面中,每个Panel都可以指定一个DefaultButton。我们可以把这个DefaultButton设置成“保存”按钮,这样当用户按下回车时会自动触发“保存”按钮。

不过有时我们希望当用户在页面上按回车键时就是什么也不做,或者,希望用户在按了回车后焦点会转移到下一个TextBox上。

我参考了VS2005的 WebForm_FireDefaultButton() 函数和网上的一些代码,弄了一个可以满足上述需求的解决方案,它的特性包括:

  • 全部JavaScript代码都封装在了一个用户控件"EnterAsTab_SubPage.ascx"中,哪个页面想要回车变Tab或者屏蔽掉回车键只要把这个控件拖到那个页面上就可以了。
  • 可以在运行时通过修改用户控件"EnterAsTab_SubPage.ascx"的属性"DiscardIEDefaultButton"和"EnterAsTab"的值来动态决定是否启用回车键变成Tab键的功能。
  • 在启用回车键变成Tab键的功能的状态下,不会影响到Panel的默认按钮的触发。换句话说,如果在Panel中设置了默认按钮,那么在Panel内的文本框中按回车仍然会触发默认按钮,而不会变成按Tab键的效果。

下面是示例程序的截图:







实现原理

原理很简单,就是截获onkeypress事件,然后调用下一个控件的focus()方法。一共才30几行JavaScript代码,相信你一看就能懂,我就不多说了。

下载全部源代码
http://download.csdn.net/source/197432

2007年6月17日星期日

终于用上了Mac,高兴ing...

早就听说Mac推出了for x86的Mac Os,但是听说安装起来挺麻烦的,而且弄不好会造成硬盘数据丢失,所以一直没敢尝试。前几天在网上无意间发现原来有好心人将tiger-x86操作系统作成了VMware的虚拟镜像文件。只要下载一个叫tiger-x86.tar.bz2的文件,再安装VMware 5.0,就可以在Windows下虚拟Mac Os了。
虽然速度有些慢,显卡和网卡驱动都上不去,不过能看到Mac的界面还是很高兴。帖几张图留作纪念:)
图中正在运行的程序是Mac Os自带的工具软件 Grapher。功能是根据函数表达式作图,很强的。正巧我想重新学习一下高数,找到宝贝了:)




























2007年6月12日星期二

使用JavaScript实现Web程序中多个文本框的批量选择、批量修改

我最近几年一直从事B/S信息系统的开发和实施工作,期间总会听到用户抱怨说上了信息系统还不如以前用Excel的时候方便。的确,无论是登录信息还是查找信息,Web程序都要比Excel麻烦很多。其中,不能方便地批量更新一直是用户抱怨较多的地方。最近,我使用纯Javascript实现了一个批量修改文本框中的数据的功能,请看看效果先。
这是初始页面:

现在,让我们选择第2、3、4、6、7个文本框:

输入新值:

结果:

实现方法概述

首先,将页面上所有的TextBox控件保存在数组text_boxes中;将每个TextBox控件的left、right、top、bottom信息保存到client_rects数组中。
然后,接管document.onmousemove、document.onkeydown、document.onkeyup事件,当Ctrl和Alt键同时按下时,在onmousemove事件中判断鼠标是否经过了某个TextBox控件(我们已经把每个TextBox控件的left、right、top、bottom信息保存到client_rects数组中^_^),将鼠标经过的TextBox控件保存到 _control_selected数组中。
最后,弹出对话框,要求用户给出新的值,然后修改 _control_selected 数组中的所有TextBox的值。

实际使用时...

实际使用时,我将实现这个功能的Javascript代码放到了一个用户控件中。以后,只要哪个页面需要这个批量修改的功能,直接将用户控件拖放到那个页面中就行了,不用多加一行代码。

下载源代码

您可以到这个地址http://download.csdn.net/source/193470下载全部源代码。需要安装VS2005才能打开工程并运行。如果您没有安装VS2005,可以使用记事本打开Default.aspx查看源代码。

2007年6月5日星期二

要是函数能指定使用范围就好了

当我们重构的时候,经常会从一个函数中抽取出十个八个只有几行代码的“小”函数。有些小函数在逻辑上比较独立,可以预期它们会被其它的函数调用;而有的函数被抽取出来仅仅是为了提高代码的可读性,我们并不希望它们被其它的函数调用,或者我们至少想知道它到底会给哪几个函数提供服务。具体点说,我希望能够这样编程:(C#伪代码)
public bool isAdult()
{
return age(birthday) >= 18;
}

private int age(DateTime birthday) used by isAdult()
{
return DateTime.Now.Year - birthday.Year;
}

2007年6月4日星期一

终于找到RDLC报表中使用嵌入图像后导出PDF出现“绘图错误”的原因了

我在VS2005的RDLC报表中使用了几张嵌入图片,结果总时不时的在打开导出的PDF文件时出现“绘图错误”。经过一番排查,发现原来是嵌入的PNG格式的图像的色深必须要大于24bit。我喜欢使用SnagIt7的虚拟打印机生成Png图片,而SnagIt7默认的Png图片的色深是1bit,所以总会出现“绘图错误”。

2007年5月31日星期四

制作一个自动登录Blogger的快捷方式

每次登录Blogger都要输入长长的用户名和密码(我数了一下,一共要输入28个字母),实在是很麻烦。于是我作了一个放在桌面上的网页文件,双击它即可自动登录到我的Blogger上。




它的制作方法是这样的,
第一步:在桌面上新建一个文本文档,然后把扩展名由.txt改为.htm,我建的是MyBlog.htm。
第二步:用记事本打开MyBlog.htm,在里面输入如下内容

<form action="https://www.google.com/accounts/ServiceLoginBoxAuth" id="start-login" name="login" method="post">
<input type="hidden" name="continue" value="http://www.blogger.com/loginz?d=%2Fstart">
<input type="hidden" name="service" value="blogger">
<input type="hidden" name="nui" value="8">
<input type="hidden" name="naui" value="8">
<input type="hidden" name="fpui" value="2">
<input type="hidden" name="uilel" value="3">
<input type="hidden" name="skipvpage" value="true">
<input type="hidden" name="rm" value="false">
<input type="hidden" name="hl" value="zh_CN">
<input type="hidden" name="alwf" value="true">
<input type="hidden" name="roeu" value="https://www.blogger.com/login.g"> <input type="hidden" name="GA3T" value="DJY310qrAc8">
<input type="hidden" name="Email" value="你的mailto:zahuifan@163.com" tabindex="1" id="Email" size="10">
<input type="hidden" name="Passwd" value="你的密码" autocomplete="off" tabindex="2" id="Passwd" size="10">
</form>
<script type='text/javascript'>
document.forms[
0].submit();
</script>

第三步:保存。
然后当我想登录Blogger的时候,只要双击桌面上的MyBlog.htm就可以了。

2007年5月26日星期六

在进入和离开网页时应用CSS滤镜

只要把下面这两行放到网页的HEAD区中,就可以在进入和离开网页时应用相应的CSS的滤镜了。本例中应用的是动态的马赛克效果。

<meta content="progid:DXImageTransform.Microsoft.Pixelate(Duration=3)" http-equiv="Page-Enter">
<meta content="progid:DXImageTransform.Microsoft.Pixelate(Duration=3)" http-equiv="Page-Exit">

2007年5月19日星期六

我的 Formatter

你如何将 DateTime 对象转换成 String?这是我的答案:

// 那个"?"不是乱码呦!这个相当

// 于写Nullbale<DateTime> birthday...

DateTime? birthday = new DateTime(2007, 1, 1);

DateTime? birthday2 = null;

string s = Formatter.New("d") ¦ birthday; // s = "2007-1-1"

string s2 = Formatter.New("d") ¦ birthday2; // s2 = ""

string s3 = Formatter.New("d", "不详") ¦ birthday2; // s3 = "不详"


怎么样,是不是很酷? 其实 Formatter 的实现相当的简单,下面是全部源代码

public class Formatter

{

  private string _fmt = "";

  private string _nullString = "";

  public Formatter(string fmt, string nullString)

  {

    _fmt = fmt;

    _nullString = nullString;

  }

  public static Formatter New(string fmt)

  {

    return new Formatter(fmt, "");

  }

  public static Formatter New(string fmt, string nullString)

  {

    return new Formatter(fmt, nullString);

  }

  public static string operator ¦(Formatter lhs, Nullable<DateTime> rhs)

  {

    if (rhs.HasValue)

    {

      return rhs.Value.ToString(lhs._fmt);

    }

    else

    {

      return lhs._nullString;

    }

  }

} // class Formatter


其实就是重载了竖线操作符而已。其实我想重载“<<”来着,可惜 .Net 规定“<<”操作符的右操作数必须是 int,真TNND气死我了,杀死比尔。

2007年3月28日星期三

CopySourceAsHtml 插件中文出现乱码的问题

上一篇文章中,我修正了CopySourceAsHtml 插件无法用于中文的Bug。 在VS2005的项目中CopySourceAsHtml 插件工作的很好。可是在VS2005的类库中执行“Copy As Html...”命令却会出现乱码。例如,复制一个类库中的一行C#代码:

47 int i = 0; // 啊


会变成:

47 int i = 0; // 啊'b0?


在“啊”的后面对了“'b0?”这几个字母。 这是为什么呢?

查找问题原因

在执行了“Copy As Html...” 命令后,打开记事本,进行“粘贴”操作。可以看到生成的HTML代码为:


&#21834 是“啊”的Unicode码,'b0?是什么东东呢?看来还得看源代码。在CopySourceAsHtml的源代码的Connnect.cs 的Copy(bool copyNow)函数中,有这样一段代码:

373 try

374 {

375 rtf = Clipboard.GetRtf();

376 if (rtf == null)

377 {

378 MessageBox.Show(Resources.NoRtfDataOnClipboardMessage, Resources.NoRtfDataOnClipboardCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);

379 return;

380 }

381 }

382 catch (ExternalException)

383 {

384 MessageBox.Show(Resources.UnableToAccessClipboardMessage, Resources.UnableToAccessClipboardCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);

385 return;

386 }

387

388 html = copier.CopyAsHtml(rtf);

389

390 Clipboard.SetHtml(html);


很明显,CopySourceAsHtml的工作原理是先取得VS2005存放在剪贴板中的RTF字符串(第375行),然后将RTF字符串解析、替换成HTML标记的代码(第388行)。
那么,造成乱码的原因就可能有两个:(1) VS2005存放在剪贴板中的RTF文本有问题(毕竟只有在类库中使用CopySourceAsHtml插件会出现乱码。(2) CopySourceAsHtml插件解析RTF的代码有问题。

VS2005存放在剪贴板中的RTF文本

在VS2005中选中一个汉字,例如:"啊",按Ctrl+C进行复制,然后使用通过一段小程序即可取得VS2005 放到剪贴板中的RFT文本:

130 private void button1_Click(object sender, EventArgs e)

131 {

132 textBox1.Text = System.Windows.Forms.Clipboard.GetText(TextDataFormat.Rtf);

133 }


发现RTF文本中啊被表示成了 “... \fs18 \cf11 \uc2\u21834 \'b0\'a1}”。其中21834是“啊”的Unicode编码,b0a1是“啊”的GB2312编码。不知道为什么VS2005会同时将这两种编码全都放到RTF文本中。不过如果把这段RTF文本放到一个.RTF 文件中,会发现写字板会将\u21834 过滤掉,只留下\'b0\'a1。而CopySourceAsHtml并未进行这些过滤,结果出现了“啊'b0?”这样的结果。

修正

俺对RTF格式一点都不了解,下载了一个RTF Spec也看得头大。幸好有前辈高人已经将这个问题解决了,请见Huan-Lin's Blog的文章http://huanlin.dyndns.org/cs/blogs/huan-lins_blog/archive/2006/10/20/CopySourceAsHtml-Fix.aspx。Hua-Lin修正了多处Unicode方面的Bug,真的很佩服他。

2007年3月24日星期六

修正 CopySourceAsHTML 插件无法用于中文 VS2005 的Bug

为了写Blog,我下载了一个叫 CopySourceAsHTML 的 VS2005 插件,通过它为VS2005增加的“Copy As HTML...”命令复制出来的代码会被HTML标记包裹,可以直接粘贴到Blog的HTML标记中(本篇文章中的代码就是用它作成的^_^)。

令人遗憾的是,当我兴冲冲地下载了CopySourceAsHTML 的安装文件 “CopySourceAsHtml.msi” 并且完成安装后,启动 VS2005,看到了这样一个 Exception:




进入VS2005后,发现菜单上也没有“Copy As Html...”选项。好在CopySourceAsHtml是一个开源软件,通过分析其源代码,笔者找到了修正此Bug的方法。

分析源代码

可以看到 Exception 的信息是“值不在预期范围内”,位置是 Connect.AddControls() 中对 CommandBars.CommandBarControls.get_Item() 的调用。安装了CopySourceAsHtml软件后,在“C:\Program Files\J.T. Leigh & Associates Inc\CopySourceAsHtml\” 可以找到一个叫 “Source.zip” 的压缩包,即是源代码了。将其解压缩,双击“Source\CopySourceAsHtml.sln” 打开工程,再打开 “Connect.cs”,来到第195行,可以看到如下代码:

187 private void AddControls()

188 {

189

190 CommandBars commandBars;

191 CommandBarPopup editPopup;

192 int copyIndex;

193

194 commandBars = (CommandBars)this.application.CommandBars;

195 editPopup = (CommandBarPopup)commandBars["MenuBar"].Controls["Edit"];

196

197 copyIndex = FindCopyOnCommandBar("Edit") + 1;




猜测不是在commandBars中找不到"MenuBar"就是在commandBars["MenuBar"].Controls中找不到"Edit"。

在第194行后面插入如下代码:

194 commandBars = (CommandBars)this.application.CommandBars;

195 // test

196 System.IO.StringWriter msg = new StringWriter();

197 foreach (CommandBar eachCommandBar in commandBars)

198 {

199 msg.Write(eachCommandBar.Name + " ");

200 }

201 throw new Exception(msg.ToString());

202 editPopup = (CommandBarPopup)commandBars["MenuBar"].Controls["Edit"];




重新编译工程,然后将新生成的CopySourceAsHtml.dll(在C:\Program Files\J.T. Leigh & Associates Inc\CopySourceAsHtml\Source\Source\Bin\) 复制粘贴到“My Documents\Visual Studio 2005\Addins\”覆盖同名文件(如果提示文件正在使用中,则需要关闭VS2005。也可以将原来的CopySourceAsHtml.dll重命名为CopySourceAsHtml.dll.old再粘贴新的CopySourceAsHtml.dll),然后打开一个新的VS2005,就可以看到commandBars中的所有内容了,发现有“MenuBar”。将测试代码变为:

194 commandBars = (CommandBars)this.application.CommandBars;

195 // test

196 System.IO.StringWriter msg = new StringWriter();

197 foreach (CommandBarControl eachCommandBarControl in commandBars["MenuBar"].Controls)

198 {

199 msg.Write(eachCommandBarControl.Caption + " ");

200 }

201 throw new Exception(msg.ToString());

202 editPopup = (CommandBarPopup)commandBars["MenuBar"].Controls["Edit"];



再次替换“My Documents\Visual Studio 2005\Addins\CopySourceAsHtml.dll”,重启VS2005,可以看到commandBars["MenuBar"].Controls中每个CommandBarControl的Caption都是中文的,其中与Edit相对应的应该是“编辑”,将editPopup = (CommandBarPopup)commandBars["MenuBar"].Controls["Edit"]; 替换为editPopup = (CommandBarPopup)commandBars["MenuBar"].Controls["编辑"]; 再次替换CopySourceAsHtml.dll并重启VS2005,Exception不再显现了。 现在源代码变为:

187 private void AddControls()

188 {

189

190 CommandBars commandBars;

191 CommandBarPopup editPopup;

192 int copyIndex;

193

194 commandBars = (CommandBars)this.application.CommandBars;

195 //editPopup = (CommandBarPopup)commandBars["MenuBar"].Controls["Edit"];

196 editPopup = (CommandBarPopup)commandBars["MenuBar"].Controls["编辑"];

197

198 copyIndex = FindCopyOnCommandBar("Edit") + 1;



不过“Copy As HTML...”命令出现的位置似乎不对,而且我们记得在第一次看到的Exception中提示在Connect.cs的第63行也有错误。通过与上面相似的方法,可以发现菜单“编辑|复制”不是英文的“Copy”而是“复制(C)”。修正的方法是在VS2005中的解决方案资源管理器中右击CopySourceAsHTML项目,在上下文菜单中点击“属性”,点击“资源”选项卡,将名称为“CopyMenuItemText”的值由原来的“Copy”更改为“复制(C)”。再次替换“My Documents\Visual Studio 2005\Addins\CopySourceAsHtml.dll”,重启VS2005,CopySourceAsHTML已经可以正常工作了。

下载修正后的CopySourceAsHtml.dll

如果你觉得自己改代码太麻烦,可以直接下载我的修正过的CopySourceAsHtml.dll文件。请按如下步骤操作:
  1. 下载CopySourceAsHtml的安装文件CopySourceAsHtml.msi,并安装。
  2. 下载我修正过的CopySourceAsHtml.dll文件,地址为:http://download.csdn.net/source/162560
  3. 将第二步下载的CopySourceAsHtml.dll文件复制到“My Documents\Visual Studio 2005\Addins\”覆盖同名文件。
  4. 重新启动VS2005。

一些疑问

CopySourceAsHtml这个插件之所以会抛出异常,是因为application.CommandBars.Controls的ObjectIndex由英文版VS2005的“Edit”变成了中文版VS2005的“编辑”。我对插件编程一窍不通,不知到为什么会发生这种事情,但想来不外乎三种情况:
  • CopySourceAsHtml这个插件使用了错误的访问application.CommandBars.Controls的方法。例如,也许存在一种英文版VS2005和中文版VS2005通用的访问application.CommandBars.Controls的方法,使得不论是哪个语言版本的VS2005都能正常使用这个插件。或者:
  • 中文版VS2005使用了错误的汉化方法。例如不应该直接将“Edit”改为“编辑”,而应该在资源文件中进行更改。或者:
  • VS2005的application.CommandBars.Controls设计有缺陷,没有考虑国际化方面的问题。造成无法编写出适用于所有语言版本的VS2005的插件。

请网友高人不吝赐教。

2007年3月23日星期五

应用泛型和匿名函数打造 DictionaryBuilder

编写数据库相关的程序,Dictinary是一个比较常用的工具。例如,在你的系统数据库中有一个“部门”表:

部门ID部门名称部门人数
1信息系统部17
2总务部23
3销售部55
4制造部880
5技术部250

与之对应的 Domain Object 为 Department:

public class Department

{

  private long _id;

  public long id // 部门ID

  {

    get { return _id; }

    set { _id = value; }

  }

  private string _name;

  public string name // 部门名称

  {

    get { return _name; }

    set { _name = value; }

  }

  private int _deployeeCount;

  public int deployeeCount // 部门人数

  {

    get { return _deployeeCount; }

    set { _deployeeCount = value; }

  }

}


假设我们已经从数据库中取得了部门表中的所有数据,并由这些数据创建了5个Department的实体(每条数据一个Department的实体),这些数据存放在一个List对象中,伪代码为:
List<department> departments = 从数据库中取得部门表中的所有数据并创建一个List;
创建一个以部门ID为Key的Dictonary的代码为:

Dictionary<long, Department> id_Department_Dic = new Dictionary<long, Department>();

foreach (Department eachDepartment in departments)

{

  if (!id_Department_Dic.ContainsKey(eachDepartment.id))

  {

    id_Department_Dic.Add(eachDepartment.id, eachDepartment);

  }

}


创建一个以部门名称为Key的Dictionary的代码为:

Dictionary<long, Department> name_Department_Dic = new Dictionary<long, Department>();

foreach (Department eachDepartment in departments)

{

  if (!name_Department_Dic.ContainsKey(eachDepartment.name))

  {

    name_Department_Dic.Add(eachDepartment.name, eachDepartment);

  }

}


看出这两段代码的不同之处了么?没错,仅仅是将两处“id”换成了“name”而已。你是不是已经嗅到了腐朽的气味了呢?让我们来把重复的代码提炼出来吧。

重构

我们希望把创建Dictionary的代码提炼到一个通用的函数中,把Key和Value作为参数传递到这个函数中。第一种方法是使用反射机制,代码类似于这样:

public Dictionary<long, Department> buildDictionary(string keyName, List<Department> src)

{

  foreach (Department eachDepartment in src)

  {

    long id = 通过反射机制从eachDepartment 中取得名为keyName的属性的值;

    if (!id_Department_Dic.ContainsKey(id))

    {

      id_Department_Dic.Add(id, eachDepartment);

    }

  }

}


使用反射的一大缺点是速度很慢,慢到了令人无法忍受的地步。还有一个缺点是作为Key的属性的名称是通过一个string类型的参数传递到函数中的,如果属性名拼写错了,只有在运行时才能发现。所以我们放弃使用这一方法。

第二种方法是使用委托机制。什么是委托呢?如果你熟悉C++,可以将委托当作函数指针来使用。或者,你可以把委托理解为一个类(Class),只不过这个类中定义的不是普通的属性和方法,而是定义了函数的参数个数、参数类型和返回值的类型。

我们可以在类库中新建一个叫DictionaryBuilder的类,它接收从Client传来的List,并委托Client告诉它每个Department的Key是什么。DictionaryBuilder类和Client代码见下图:


使用匿名函数

其实我们不必非得在Client代码中写一个getKey()函数,因为.Net 2.0提供了一个叫“匿名函数”的功能。我们在Client代码中可以直接这样写:

class Program

{

  static void Main(string[] args)

  {

    IList<Department> departments = new List<Department>();

    //Dictionary<long, Department> dic

    //  = new DictionaryBuilder(departments).toDictionary(getKey);

    Dictionary<long, Department> dic

      = new DictionaryBuilder(departments).toDictionary(

      delegate(Department arg)

      {

        return arg.id;

      });

  }

  //private static long getKey(Department arg)

  //{

  //  return arg.id;

  //}

}


匿名函数的原理和妙处留给大家慢慢体会,我就不多说了(其实是我也不知道)。

加上泛型

当然,我们费了半天的劲写的DictionaryBuilder如果只能接收List<Department>就太不划算了,我们的DictionaryBuilder要能够接受任何类型!这就要使用 .Net 2.0 新增的泛型功能。让我们来先看看代码吧:

public class DictionaryBuilder<TKey, TValue>

{

  private ICollection<TValue> _values;

  public delegate TKey GetKey(TValue obj);

  public DictionaryBuilder(ICollection<TValue> values)

  {

    _values = values;

  }

  public Dictionary<TKey, TValue> toDictionary(GetKey getKey)

  {

    Dictionary<TKey, TValue> result =

      new Dictionary<TKey, TValue>();

    foreach (TValue eachValue in _values)

    {

      TKey key = getKey(eachValue);

      if (!result.ContainsKey(key))

      {

        result.Add(key, eachValue);

      }

    }

    return result;

  }

}


觉得有些难懂么?如果你把这段代码与上图中的代码仔细对比一下,就会发现,只不过是把所有的 long 换成了TKey,把所有的Department换成了TValue而已!呵呵,简单吧?单从使用泛型的角度讲,只要把泛型理解成“类型的占位符”就行了。我们的Client代码也需要做一点小小的改变(涂了阴影的部分):

class Program

{

  static void Main(string[] args)

  {

    IList<Department> departments = new List<Department>();

    //Dictionary<long, Department> dic

    //  = new DictionaryBuilder(departments).toDictionary(getKey);

    Dictionary<long, Department> dic

      = new DictionaryBuilder<long, Department>(departments).toDictionary(

      delegate(Department arg)

      {

        return arg.id;

      });

  }

  //private static long getKey(Department arg)

  //{

  //  return arg.id;

  //}

}



好了,到目前为止主要工作均已完成,我的DictionaryBuilder要比上文提到的复杂一点点,可以在这里下载: http://docs.google.com/Doc?id=dgwxq3xg_2fmtd98
(完)