乐者为王

Do one thing, and do it well.

Visual Studio 2005下wxWidgets-2.8.6开发环境的搭建

wxWidgets是一个开源的跨平台C++类库,它包含一个可以支持现今几乎所有操作系统的图形用户界面(GUI)库和其它一些很有用的工具,提供了类似MFC的功能。wxWidgets的主体是由C++构建的,但你并不是必需通过C++才能使用wxWidgets。wxWidgets拥有许多其它语言的绑定,使你在用其它语言编写程序的时候也可以使用wxWidgets。

wxWidgets的下载、安装和编译

  1. http://www.wxwidgets.org/downloads/ 下载一个wxMSW版本的wxWidgets;
  2. 运行安装文件。在安装好后设置环境变量WXWIN,指向wxWidgets的安装目录。因为安装过程仅仅是把文件拷贝到指定的目录,所以还需要对wxWidgets进行编译;
  3. 进入$(WXWIN)/build/msw目录,用VS2005打开wx.dsw,提示是否进行项目转换,点确定。如果想要链接静态运行库,在编译前应该对每个项目进行设置,如下图:

编译好后的wxWidgets会在$(WXWIN)/lib/vc_lib目录下生成一系列的lib文件,这些lib文件的名字遵循下面的命名规则:不依赖于GUI组件的库会以wxbase开头,紧跟着的是版本号,然后的字母表明这个库是否是编译为Unicode('u')或是否是编译为Debug('d'),名字中的最后部分是wxWidgets组件的名字。

注意:wxWidgets编译完后在$(WXWIN)/build/msw目录下会生成许多预编译头文件,占了很大的空间,如果确定以后不再编译wxWidgets库的话,可以考虑删掉。

设置开发环境

打开Tools -> Options -> Projects and Solutions -> VC++ Directories

  1. 在Include files中加入$(WXWIN)/include和$(WXWIN)/include/msvc
  2. 在Library files中加入$(WXWIN)/lib/vc_lib

创建wxWidgets项目

新建一个General -> Empty Project或Win32 -> Win32 Project项目,然后设置项目的一些属性,可以选择下面的其中一个来设置。譬如你如果打算使用Unicode,那么选择Unicode Debug或Unicode Release,如果你仅仅想调试程序而非发布,则只需要选择Debug设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Debug:
Project Properties -> General -> Character Set: No Set
Project Properties -> C/C++ -> Code Generation -> Runtime Library: Multi-threaded Debug (/MTd)
Project Properties -> Linker -> Input -> Additional Dependencies: wxbase28d.lib wxmsw28d_core.lib winmm.lib comctl32.lib rpcrt4.lib wsock32.lib oleacc.lib

Release:
Project Properties -> General -> Character Set: No Set
Project Properties -> C/C++ -> Code Generation -> Runtime Library: Multi-threaded (/MT)
Project Properties -> Linker -> Input -> Additional Dependencies: wxbase28.lib wxmsw28_core.lib winmm.lib comctl32.lib rpcrt4.lib wsock32.lib oleacc.lib

Unicode Debug:
Project Properties -> General -> Character Set: Use Unicode Character Set
Project Properties -> C/C++ -> Code Generation -> Runtime Library: Multi-threaded Debug (/MTd)
Project Properties -> Linker -> Input -> Additional Dependencies: wxbase28ud.lib wxmsw28ud_core.lib winmm.lib comctl32.lib rpcrt4.lib wsock32.lib oleacc.lib

Unicode Release:
Project Properties -> General -> Character Set: Use Unicode Character Set
Project Properties -> C/C++ -> Code Generation -> Runtime Library: Multi-threaded (/MT)
Project Properties -> Linker -> Input -> Additional Dependencies: wxbase28u.lib wxmsw28u_core.lib winmm.lib comctl32.lib rpcrt4.lib wsock32.lib oleacc.lib

问题

1、编译时出现

1
Cannot open include file: '../mswu/wx/setup.h': No such file or directory

是因为项目属性的Character Set设置不正确,必须与使用的wxWidgets库的Character Set一致。

2、编译时如果出现

1
MSVCRT.lib(MSVCRT.dll) : error LNK2005: _free already defined in LIBC.lib(free.obj)

这是因为没有连接正确的wxWidgets库,譬如Unicode Debug版本的项目就需要连接Unicode+Debug版本的wxWidgets库(库名后缀为'ud'的lib文件),或者是wxWidgets库和程序使用的Runtime Library不同。

3、运行时出现No Debugging Information对话框。

将Project Properties -> Linker -> Debugging -> Generate Debug Info的值改成Yes (/DEBUG)。

数据窗口中Column控件的EditMask.UseFormat属性的作用

当数据窗口中的EditMask控件失去焦点时,如果EditMask.UseFormat为yes,则使用Format属性值格式化EditMask的内容,否则,使用EditMask.Mask属性值格式化控件的内容。当EditMask得到焦点时,使用EditMask.Mask格式化内容。

根据此规则写的伪代码如下:

1
2
3
4
5
6
7
8
9
if (isFocused) {
    EditMask.Mask
} else if (loseFocus) {
    if (EditMask.UseFormat == "yes" && Format != "") {
        Format
    } else {
        EditMask.Mask
    }
}

Painter: Use Format option

使用ANTLR构建PowerScript语法分析器(3)

转义字符(Special ASCII Characters)

PowerScript中转义字符是以波浪号(~)开头。下图是PowerScript支持的完整的转义字符,#字符表示数字。Decimal由三个十进制数组成,范围是000-255;Hexadecimal由两个十六进制数组成,范围是00-FF;Octal由三个八进制数组成,范围是000-377。

注意:#表示数字是必须的。譬如,必须用~007这种样式来表示转义字符,而不能是~7这种。

这里是转义字符的词法规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fragment
Escape
    : '~' ('n'|'t'|'v'|'r'|'f'|'b'|'\"'|'\''|'~')
    | DecimalEscape
    | HexEscape
    | OctalEscape
    ;

fragment
DecimalEscape
    : '~' ('0'..'1') ('0'..'9') ('0'..'9')    // 000 - 199
    | '~' '2' ('0'..'5') ('0'..'5')           // 200 - 255
    ;

fragment
HexEscape
    : '~h' ('0'..'9'|'a'..'f'|'A'..'F') ('0'..'9'|'a'..'f'|'A'..'F')    // 00 - FF
    ;

fragment
OctalEscape
    : '~o' ('0'..'3') ('0'..'7') ('0'..'7')   // 000 - 377
    ;

字符和字符串字面量(Character and String Literals)

PowerScript程序中字符和字符串字面量没有明显的区别。字符是指由单引号(')或双引号(")括起来的一个ASCII字符,例如:

1
2
3
char c
c = 'T'
c = "T"

字符串字面量则是指由单引号或双引号括起来的不多于1024个的ASCII字符串,例如:

1
2
3
string s
s = 'This is a string'
s = "This is a string"

由于两者没有明显区别,因此在词法分析时只能将两者都作为字符串字面量来处理。至于到底是字符还是字符串,只有到了语意分析阶段才能作出判断。

1
2
3
4
5
6
7
8
STRING_LITERAL
    : ( '\'' (Escape|~('~'|'\r'|'\n'|'\''))* '\''
      | '\"' (Escape|~('~'|'\r'|'\n'|'\"'))* '\"'
      )
      {
          System.out.println("string>" + getText());
      }
    ;

数字和布尔字面量(Numeric and Boolean Literals)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
INTEGER_LITERAL
    : ('0'|'1'..'9' ('0'..'9')*)
      {
          System.out.println("integer>" + getText());
      }
    ;

FLOAT_LITERAL
    : ( ('0'..'9')+ '.' ('0'..'9')* Exponent?
      | '.' ('0'..'9')+ Exponent?
      | ('0'..'9')+ Exponent?
      )
      {
          System.out.println("float>" + getText());
      }
    ;

BOOLEAN_LITERAL
    : ('true'|'false')
      {
          System.out.println("boolean>" + getText());
      }

    ;

fragment
Exponent
    : ('e'|'E') ('+'|'-')? ('0'..'9')+
    ;

标识符(Identifier)

标识符规则可以参看PowerScript标识符一文,以下是标识符的词法规则:

1
2
3
4
5
6
7
8
9
10
11
IDENTIFIER
    : Letter (Letter|'0'..'9'|'$'|'#'|'%')*    // 暂时不支持短横线(-)
      {
          System.out.println("identifier>" + getText());
      }
    ;

fragment
Letter
    : ('A'..'Z'|'a'..'z'|'_')
    ;

Ruby DBI的安装和使用

Ruby DBI是一个为Ruby程序访问数据库提供的与数据库无关的统一数据库编程接口。结构层次上可以分为两层:

  1. Database Interface 数据库接口层,与数据库无关,提供与数据库无关的标准接口
  2. Database Driver 数据库驱动,与数据库相关

Ruby DBI模块可以从 http://ruby-dbi.sourceforge.net/ 取得,下载后解压缩,配置,然后安装:

1
2
3
ruby setup.rb config --with=dbi,dbd_mysql
ruby setup.rb setup
ruby setup.rb install

注意:--with参数必须根据你所安装的数据库类型来选择。可以配置多个,但不能选择没有安装在你机器上的数据库类型。记得刚开始的时候我使用ruby setup.rb config来配置(我机器上只安装了MySQL和SQL Server),结果在ruby setup.rb setup时一直出现像下面这样的错误信息:

下面是一个完整的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# dbitest.rb
require 'dbi'

# Connect to a database
dbh = DBI.connect('dbi:Mysql:rubydbi:localhost', 'root', '123456')

dbh.do('DROP TABLE IF EXISTS people')
dbh.do('CREATE TABLE people (id int, name varchar(30))')

# Insert some rows, use placeholders
sql = 'INSERT INTO people VALUES (?, ?)'
dbh.prepare(sql) do |sth|
    sth.execute('100', 'Michael')
    sth.execute('200', 'John')
end

# Select all rows
sth = dbh.prepare('SELECT * FROM people')
sth.execute

# Print out each row
while row = sth.fetch do
    p row
end

# Close the statement handle when done
sth.finish

dbh.do('DROP TABLE people')

# Finally, disconnect
dbh.disconnect

使用ANTLR构建PowerScript语法分析器(2)

使用ANTLR构建PowerScript语法分析器(1)中写的词法规则存在几个小问题:

  1. 换行符和回车符同时存在于WS和EndOfLine两个词法规则中,属于重复定义;
  2. 输出单行注释时紧随其后会额外多输出一个空行;
  3. 单行注释是文件的最后一行时不能被词法分析器识别;
  4. /* comments /* nested comments */ */这种嵌套注释会被输出成:
1
2
bc>/* comments /* nested comments */
bc>/* comments /* nested comments */ */

下面我们将逐个修复它们。

解决第1个问题前先要问问自己,单行注释的词法规则中真的需要包含EndOfLine这个词法规则吗?真的需要吗?如果没有的话,当词法分析器识别到换行符或回车符时,就会去匹配词法规则WS。这样看来,EndOfLine这个词法规则在这里其实是不必要的,完全可以删除掉。

第2个问题因为词法规则EndOfLine已经被删除,所以也就不存在了。这儿仅仅分析下问题的原因。词法分析器分析单行注释时,EndOfLine会被当作单行注释的一部分予以识别。翻看生成的词法识别器代码,可以看到打印代码块出现在词法规则EndOfLine调用的后面。打印代码块中的getText()方法是从CharStream中获取文本的,如果在词法规则EndOfLine被调用后才执行,因为词法规则EndOfLine被调用时会将CharStream的当前索引向尾部移动,这时获得的文本就会包含EndOfLine,导致打印时额外输出一个空行。

第3个问题同样也由于第1个问题的修复而不存在了。它出现的原因是因为文件最后一行的结尾字符是EOF,无法匹配词法规则EndOfLine。

第4个问题则可以通过计数器来处理。先初始化一个计数器变量depthOfComments来标记块注释的深度,当遇到“/*”时就加1,遇到“*/”时则减1。输出时只要判断depthOfComments是否等于0就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@lexer::members {
    int depthOfComments = 0;
}

BLOCK_COMMENT
    : '/*' {depthOfComments++;}
      ( options {greedy=false;}
      : ('/' '*')=> BLOCK_COMMENT
      | '/' ~('*')
      | ~('/')
      )*
      '*/' {depthOfComments--;}
      {
          if (depthOfComments == 0) {
              System.out.println("bc>" + getText());
          }
      }
    ;

使用ANTLR构建PowerScript语法分析器(1)

首先,让我们从最简单的做起,先实现一套能正确分析PowerScript注释的词法规则。PowerScript支持两种注释形式:单行注释(//)和多行注释(/* ... */)。

单行注释以双斜杠(//)起头,直到遇到行结束符为止,所以在这里要先明确一下行结束符用什么表示。在DOS系统中行结束符用“\r\n”表示,在Unix系统中行结束符用“\n”表示,现在的Mac系统也是以“\n”表示行结束符,但早期的Mac系统中行结束符则是以“\r”表示。所以兼容这几种操作系统的行结束符词法规则为:

1
2
3
4
5
6
fragment
EndOfLine
    : '\r' '\n'    // DOS
    | '\r'         // Mac
    | '\n'         // Unix
    ;

现在,有了行结束符词法规则,就可以定义单行注释的词法规则了:

1
2
3
LINE_COMMENT
    : '//' ~('\n'|'\r')* EndOfLine
    ;

通常的多行注释词法规则是这样的:

1
2
3
BLOCK_COMMENT
    : '/*' ( options {greedy=false;} : . )* '*/'
    ;

但由于PowerScript支持嵌套注释,所以上面的规则无效,必须重新定义。当词法分析器遇到“/*”后就进入了多行注释。这时假如遇到了“/”符号,词法分析器必须预取一个字符,看这个字符是不是“*”。如果是的话,就进入了嵌套的多行注释,否则,则还是处在多行注释中。

1
2
3
4
5
6
7
8
9
BLOCK_COMMENT
    : '/*'
      ( options {greedy=false;}
      : ('/' '*')=> BLOCK_COMMENT    // 在多行注释中遇到“/*”,用语法断言确定是嵌套多行注释
      | '/' ~('*')                   // 在多行注释中只遇到“/”,而没有紧跟着的“*”
      | ~('/')                       // 在多行注释中没有遇到“/”
      )*
      '*/'
    ;

当然,我们还希望忽略空格、制表符、回车符、换行符等无意义字符。

1
2
3
WS
    : (' '|'\t'|'\r'|'\n') {$channel=HIDDEN;}
    ;

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* This is a comment.   */
/* /This is a comment. */
/* *This is a comment. */
/* This is a comment. /* */*/
/* This is a comment. /* This is a nested comment. */ */
/*  The comment starts here.
 /*  The nested comment starts here.
 The nested comment ends here.  */
 The comment ends here.  */
/* The comment starts here,
 continues to this line,&
 and finally ends here. */
// This entire line is a comment.
// This entire line is a comment. //
// /This entire line is a comment.
// This entire line is a comment. */
// This entire line is a comment. /*

完整的PowerScript.g的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
grammar PowerScript;

program
    : LINE_COMMENT+
    | BLOCK_COMMENT+
    ;

LINE_COMMENT
    : '//' ~('\n'|'\r')* EndOfLine
      {
          System.out.println("lc>" + getText());
      }
    ;

BLOCK_COMMENT
    : '/*'
      ( options {greedy=false;}
      : ('/' '*')=> BLOCK_COMMENT
      | '/' ~('*')
      | ~('/')
      )*
      '*/'
      {
          System.out.println("bc>" + getText());
      }
    ;

WS
    : (' '|'\t'|'\r'|'\n') {$channel=HIDDEN;}
    ;

fragment
EndOfLine
    : '\r' '\n'    // DOS
    | '\r'         // Mac
    | '\n'         // Unix
    ;

JPA + Spring 2入门

JPA(Java Persistence API)是EJB 3.0 新引入的一个把Java数据对象映射成关系数据库对象的数据持久化编程模型,它弥补了JDBC、ORM、EJB 2等在Java对象持久化方面的不足之处,并且非常易于使用。JPA可以被当作一个单独的 POJO(Plain Old Java Object)持续化使用,或者被集成到任意Java EE兼容的容器或其它的轻量级框架(例如Spring)等一起使用。

配置JPA

Spring提供了两种方法创建JPA的EntityManagerFactory对象。

方法1:

1
2
3
4
5
<beans>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="persistenceUnit" />
    </bean>
</beans>

FactoryBean创建的EntityManagerFactory适用于仅通过JPA进行数据访问的环境。由于使用了PersistenceProvider自动侦测机制,所以只能从默认路径classpath:META-INF/persistence.xml中读取配置信息。

方法2:

1
2
3
4
5
6
7
8
<beans>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="someDataSource" />
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver" />
        </property>
    </bean>
</beans>

FactoryBean提供了对JPA EntityManagerFactory的完整控制,非常适合那种有简单定制需要的环境。你可以处理多个persistence.xml配置文件;覆盖persistence.xml文件的默认路径;可以传递Spring托管的JDBC DataSource给JPA PersistenceProvider,用来替代persistence.xml中的JDBC配置(这个Spring托管的DataSource通常被作为nonJtaDataSource传送给PersistenceProvider,并且覆盖persistence.xml中相同的nonJtaDataSource)。

数据访问

基于JPA的DAO可以通过三种方式进行数据访问JpaDaoSupport,JpaTemplate和plain JPA。其中JpaTemplate是plain JPA的封装,而JpaDaoSupport又是JpaTemplate的封装。无疑,使用不对Spring产生任何依赖的Plain JPA的API进行编程是最好选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProductDaoImpl implements ProductDao {
    private EntityManager entityManager = null;

    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public Collection loadProductsByCategory(String category) {
         Query query = em.createQuery("from Product as p where p.category = :category");
         query.setParameter("category", category);
         return query.getResultList();
    }
}

需要注意的是,必须激活PersistenceAnnotationBeanPostProcessor功能才能让Spring识别@PersistenceContext注解。

1
2
3
4
5
6
<beans>
    <!-- JPA annotations bean post processor -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

    <bean id="productDao" class="com.codemany.netlink.dao.impl.ProductDaoImpl" />
</beans>

异常转化

Spring提供了一个允许通过使用@Repository注解进行透明的异常转化的解决方案。

1
2
@Repository
public class ProductDaoImpl implements ProductDao {
1
2
3
<beans>
    <!-- Exception translation bean post processor -->
    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

后置处理器将自动的寻找所有的异常转化器(PersistenceExceptionTranslator这个接口的实现类)并通知所有打上@Repository注解的bean,从而能够使得被找到的异常转化器能够在抛出异常时做相应的异常转化工作。

总的来说,DAO能够基于普通的Java持久层API和注解来实现,但同样也能享受到由Spring管理事务、IoC和透明的异常转化(转化成为Spring的异常体系)等好处。

重新认识button元素

英文原文:http://www.particletree.com/features/rediscovering-the-button-element/

简介

对每个应用程序设计者来说,为你的用户创建一套一致的界面是个不懈的斗争。在Web上构建一致性尤为艰难,因为跨越浏览器和操作系统的视觉渲染差异是完全不同的,并且在什么能做和不能做上几乎是随心所欲的。在你处理 表单元素时这个问题变得更为明显。在标准外观的战争里,它们中最大的输家是臭名昭著的提交按钮。

现状是,带有type="submit"的输入要么是太丑陋(Firefox),有点缺陷(Internet Explorer),要么完全缺乏弹性(Safari)。大多数的解决方案是使用图片输入和自己创建这个该死的东西。这是不幸的,因为那样做以后每当我们急需一个新按钮时工作就被归结为启用Photoshop的繁琐任务。我们需要的是更好的东西——一些对于设计者来说更加灵活和有意义的东西。我们很幸运,因为解决方案已经存在,它所需要的是一点点爱。我的朋友们,让我向你介绍我的小朋友:<button>元素。

输入 vs 按钮

这里是标准的提交按钮标记:

1
<input type="submit" value="Submit" />

它在浏览器三兄弟里看起来像这样:

嗯。这里是创建一个用于提交的按钮元素时使用的标记:

1
<button type="submit">Submit</button>

并且它看起来像这样:

这些按钮与上面对等物的工作和行为方式完全相同。除提交表单外,你可以让它们禁用,添加一个accesskey甚至指定一个tabindex。除了Safari里的按钮看起来有视觉差异(它没有把不自然的aqua界面放到按钮上,这对我们是有利的),关于<button>标签最酷的事情是你可以在它们里面放置有用的HTML元素,像图片:

1
<button type="submit"><img src="" alt="" /> Submit</button>

它们看起来像这样:

非常好。(好吧,它们有点丑,但是我说过它们需要一点点爱。)实际上,根据W3C所说,这些特殊的视觉差异正是<button>元素被创建的原因。

Buttons created with the BUTTON element function just like buttons created with the INPUT element, but they offer richer rendering possibilities: the BUTTON element may have content. For example, a BUTTON element that contains an image functions like and may resemble an INPUT element whose type is set to “image”, but the BUTTON element type allows content.

The Button Element - W3C

所以这里我们正在寻找一种设计解决方案,好在拥有海量资料的互联网上有一段标记可以帮助我们解决这个问题。这很方便,然而不幸的是大多数设计者和开发者甚至不知道该元素存在。现在,在我直接用按钮元素替换所有在Wufoo中的图片输入之前,我决定标记和CSS必须满足几个需求:

需求

  1. 它们必须看起来像按钮。
  2. 它们必须在浏览器里看起来相同。
  3. 我用于按钮的样式需要同样能被使用在链接上(因为在Wufoo中的交互总是由一个表单提交或者从链接来的一个Ajax调用引发,这些东西很可能并排排列,我需要它们具有相同的视觉重量感)。
  4. 在许多不同情况下使用时标记需要保持弹性和易于修改。
  5. 我应该能够有效地使用图标和颜色来传递关于这种交互将要发生的信息。

带着这些适当的挑战,我潜进CSS,在解决一些跨浏览器的挑战之后,得出了以下内容(你也可以在Wufoo上看到所有的这些内容):

结果

没有什么疯狂的。简单,但是有效。现在,我喜欢这种处理按钮的方式是我可以使用FAMFAMFAM的1000个图标库去说明大量可笑的想法和动作而不必每一次我需要一些新东西时都要从Photoshop生成。如果我们快速查看下标记,你会注意到在那里的最后两个按钮实际上是链接:

1
2
3
4
5
6
7
8
9
10
11
<div class="buttons">
    <button type="submit" class="positive">
        <img src="/images/icons/tick.png" alt=""/> Save
    </button>
    <a href="/password/reset/">
        <img src="/images/icons/key.png" alt=""/> Change Password
    </a>
    <a href="#" class="negative">
        <img src="/images/icons/cross.png" alt=""/> Cancel
    </a>
</div>

这很有用的原因是因为在Web应用中许多动作是由REST驱动的,所以只要通过链接把用户请求发送到某个特定的URL就会发起它们需要做的事情。使用可以在这两种类型的元素(链接和按钮)上工作的样式,为我们提供了灵活性,以保持我们的交互方式看起来是一致的和适当的,不管它是通过Ajax完成还是一个标准的提交。

仅仅是一些简短的题外话。你可能想知道我为什么把那些图标图片中的alt属性留成空白。它可能会令一些人感到惊讶,虽然每个图片的alt属性是必需的,实际上描述它们不是必需的。空的alt属性完全有效,并且帮助屏幕阅读器知道哪些信息事实上要被忽略,在你试图寻找下一个适当的可操作条目时节省你用户宝贵的时间。因为图标实际上是多余的,我宁愿不要浪费用户的时间听到我用于可视化的图片发生了什么事。它们只会听到“Submit”而不是“Checkmark Submit”,这实际上会让事情有点困惑。

CSS

在大多数情况下,样式化这些按钮的CSS是相当直白的。让人抓破脑袋的跨浏览器的不一致性导致下列代码中填充的数量不一致,但没什么不可能,你们很幸运,这个问题已经解决了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* BUTTONS */
.buttons a, .buttons button {
    display: block;
    float: left;
    margin: 0 7px 0 0;
    background-color: #f5f5f5;
    border: 1px solid #dedede;
    border-top: 1px solid #eee;
    border-left: 1px solid #eee;
    font-family: "Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
    font-size: 100%;
    line-height: 130%;
    text-decoration: none;
    font-weight: bold;
    color: #565656;
    cursor: pointer;
    padding: 5px 10px 6px 7px;    /* Links */
}
.buttons button {
    width: auto;
    overflow: visible;
    padding: 4px 10px 3px 7px;    /* IE6 */
}
.buttons button[type] {
    padding: 5px 10px 5px 7px;    /* Firefox */
    line-height: 17px;    /* Safari */
}
*:first-child+html button[type] {
    padding: 4px 10px 3px 7px;    /* IE7 */
}
.buttons button img, .buttons a img {
    margin: 0 3px -3px 0 !important;
    padding: 0;
    border: none;
    width: 16px;
    height: 16px;
}

当致力于不一致性的时候有件事出现了,就是Internet Explorer中关于显示长按钮有个渲染缺陷的事实。你可以在Jehiah.cz阅读相关内容,但它是什么导致了以上代码中一些宽度和溢出的声明。

添加一些颜色

在Wufoo中,我们使悬停颜色蓝色成为中性动作,对于正面和负面内涵适当地使用绿色和红色。以下的代码是我们为处理按钮而创建的样式,这些按钮意味着显示像添加和保存这样的正面交互以及像取消和删除这样的负面交互。对我们来说这是一次不错的尝试,显然你可以根据自己的喜好选择颜色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* STANDARD */
button:hover, .buttons a:hover {
    background-color: #dff4ff;
    border: 1px solid #c2e1ef;
    color: #336699;
}
.buttons a:active {
    background-color: #6299c5;
    border: 1px solid #6299c5;
    color: #fff;
}
/* POSITIVE */
button.positive, .buttons a.positive {
    color: #529214;
}
.buttons a.positive:hover, button.positive:hover {
    background-color: #e6efc2;
    border: 1px solid #c6d880;
    color: #529214;
}
.buttons a.positive:active {
    background-color: #529214;
    border: 1px solid #529214;
    color: #fff;
}
/* NEGATIVE */
.buttons a.negative, button.negative {
    color: #d12f19;
}
.buttons a.negative:hover, button.negative:hover {
    background: #fbe3e4;
    border: 1px solid #fbc2c4;
    color: #d12f19;
}
.buttons a.negative:active {
    background-color: #d12f19;
    border: 1px solid #d12f19;
    color: #fff;
}

总结

最后,这只是我们决定如何用Wufoo处理事情,它是为我们制定的开发流程。当然,这不是玩这个游戏的唯一方法。有许多你可以给它添加趣味的方法(使用渐变!)和改变周围的事物(在标记中的图片上使用图片替换)。因为<button>标签几乎可以处理在它里面的任何标记,你可以添加一些<span>标签,然后使用Alex Griffioen最近写的方法来创建真正漂亮的圆角渐变作品。老实说,我希望对于许多与他们应用中可复用表单界面斗争的设计者来说这只是一个很好的起点。或许甚至,我希望你再次看看这个经常被浪费的表单元素,在不经思考就为提交采用输入和PSD之前要二思。

在Visual C#中调试C++的DLL

在Visual C#中调试C++的DLL很简单,只要在C#项目属性的Debug一项中钩上“Enable unmanaged code debugging”,然后你就可以像调试一般程序那样调试DLL了。

VC++动态链接库(DLL)编程入门

DLL是一个包含可由多个程序同时使用的代码和数据的集合。例如,在Windows操作系统中,Comdlg32动态链接库 执行与对话框有关的常见函数。因此,每个程序都可以使用该DLL中包含的功能来实现“打开”对话框。这有助于促进代码复用和内存的有效使用。通过使用DLL,程序可以实现模块化,由相对独立的组件组成。例如,一个计帐程序可以按模块来销售。可以在运行时将各个模块加载到主程序中(如果安装了相应模块)。因为模块是彼此独立的,所以程序的加载速度更快,而且模块只在相应的功能被请求时才加载。

DLL的优点

  1. 使用较少的资源。当多个程序使用同一个函数库时,DLL可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以大大影响其它在Windows操作系统上运行的程序;
  2. 简化部署和安装。当DLL中的函数需要更新或修复时,只要函数的参数和返回值没有更改,就不需重新编译或重新建立程序与该DLL的链接。此外,如果多个程序使用同一个DLL,那么多个程序都将从该更新或修复中获益;
  3. 支持多语言程序。只要程序遵循函数的调用约定,用不同编程语言编写的程序就可以调用相同的DLL函数。程序与DLL函数在下列方面必须是兼容的:函数期望其参数被推送到堆栈上的顺序,是函数还是应用程序负责清理堆栈,以及寄存器中是否传递了任何参数;
  4. 使国际版本的创建轻松完成。通过将资源放到DLL中,创建应用程序的国际版本变得容易得多。可将用于应用程序的每个语言版本的字符串放到单独的DLL资源文件中,并使不同的语言版本加载合适的资源。

DLL的类型(Kinds of DLLs)

Visual C++支持三种类型的DLL,它们分别是Non-MFC DLL、MFC Regular DLL和MFC Extension DLL:

  1. Non-MFC DLL指的是不用MFC的类库结构,直接用C语言写的DLL,其导出的函数是标准的C接口,能被MFC或非MFC编写的客户程序调用;
  2. Extension DLL支持C++接口,也就是说它导出C++函数或者整个类给客户程序。导出函数可以使用C++或MFC的数据形式作为参数或返回值,当导出整个类时,客户程序可以创建此类的对象或者从这些类进行派生。使用Extension DLL的一个问题就是该DLL仅能和MFC客户程序一起工作;
  3. Regular DLL和上述的Extension Dll一样,也是用MFC类库编写的,它的一个明显的特点是在源文件里有一个继承CWinApp的类(注意:此类DLL虽然从CWinApp派生,但没有消息循环)。Regular DLL有一个很大的限制就是,它只可以导出C风格的函数,但不能导出C++类、成员函数或重载函数。调用Regular DLL的客户程序不必是MFC应用程序,也可以是在Visual C++、Dephi、Visual Basic等环境下开发的客户程序。

DLL的加载

客户程序使用DLL的方式有两种:隐式链接(静态加载或加载时动态链接)和显式链接(动态加载或运行时动态链接)。

为了隐式链接到DLL,客户程序必须从DLL的提供程序获取下列项:

  1. 包含导出函数或C++类声明的头文件(.h文件);
  2. 要链接的导入库(.lib文件);
  3. 实际的DLL(.dll文件)。

使用DLL的客户程序必须include头文件(此头文件包含每个DLL中的导出函数或C++类),并且链接到此DLL的创建者所提供的导入库。

1
2
3
4
5
// Cacl.cpp
DLLAPI int Sum(int a, int b)
{
    return a + b;
}
1
2
3
4
5
6
7
8
// Cacl.h
#ifdef CACL_EXPORTS
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif

DLLAPI int Sum(int a, int b);
1
2
// Client.cpp
DLLAPI int Sum(int a, int b);

为了显式链接到DLL,客户程序必须在运行时通过函数调显式加载DLL:

  1. 调用LoadLibrary加载DLL和获取模块句柄;
  2. 调用GetProcAddress获取指向客户程序要调用的每个导出函数的函数指针(由于客户程序是通过指针调用DLL的函数,编译器不生成外部引用,故无需与导入库链接);
  3. 使用完DLL后调用FreeLibrary释放资源。
1
2
3
4
5
6
7
8
9
10
11
12
// Client.cpp
HINSTANCE hDLL = LoadLibrary("demo");
if (hDLL != NULL)
{
    LPFNDLLFUNC1 lpfnDllFunc1 = GetProcAddress(hDLL, "Sum");
    if (lpfnDllFunc1 == NULL)
    {
        FreeLibrary(hDLL);
        return SOME_ERROR_CODE;
    }
    return lpfnDllFunc1(dwParam, uParam);
}

客户程序如何找到DLL

如果用LoadLibrary显式链接DLL的话,可以指定DLL的全路径名。如果没有指定路径名,或者使用了隐式链接,则系统将使用下面的搜索序列来定位DLL:

  1. 包含客户可执行文件所在的目录;
  2. 当前目录;
  3. Windows系统目录(可用GetSystemDirectory函数获取);
  4. Windows目录(可用GetWindowsDirectory函数获取);
  5. 在Path环境变量里列出的目录(注意:不使用LIBPATH环境变量)。

导出DLL函数

DLL文件的布局与EXE文件非常相似,但有一个重要差异:DLL文件包含导出表。导出表包含DLL导出到客户程序的每个函数的名称。只有导出表中的函数可由客户程序访问。DLL中的任何其它函数都是DLL私有的。通过使用带/EXPORTS选项的dumpbin工具,可以查看DLL的导出表。有两种方法可以从DLL导出函数:

方法1:在定义中使用__declspec(dllexport)关键词。

1
2
3
4
5
6
7
// 导出函数
__declspec(dllexport) void __cdecl Function();

// 导出类中的所有公共数据成员和成员函数
class __declspec(dllexport) CExampleExport : public CObject
{
}

方法2:在生成DLL时,创建一个后缀名为def的模块定义文件(如果希望按序号而不是按名称从DLL导出函数,则使用此方法)。

实例DLL和客户程序代码:

1
2
3
4
5
6
7
8
// Cacl.h
#ifdef CACL_EXPORTS
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif

DLLAPI int Sum(int a, int b);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Cacl.cpp
#include <windows.h>
#include "Cacl.h"

BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved)
{
    switch (reason)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}

DLLAPI int Sum(int a, int b)
{
    return a + b;
}
1
2
3
4
5
6
7
8
9
// Client.cpp
#include <stdio.h>
#include "Cacl.h"

int main(int argc, char* argv[])
{
    printf("Sum = %d\n", Sum(5, 3));
    return 0;
}

如何调试DLL

调试DLL很容易,只要从DLL工程启动调试器即可。第一次这样做的时候,调试器会请求给出客户EXE程序的路径。之后,每次从调试器运行DLL时,调试器会自动装入客户EXE程序,而EXE用搜索序列找到DLL。