乐者为王

Do one thing, and do it well.

如何实现一个可拖动的无标题栏窗口

无标题栏窗口的实现很简单。先将窗口从wxMiniFrame继承,然后在窗口的构造函数中设置一下窗口的样式。

1
2
3
4
5
MainFrame::MainFrame(const wxPoint& pos, const wxSize& size)
    : wxMiniFrame(NULL, wxID_ANY, wxEmptyString, pos, size)
{
    SetWindowStyleFlag(wxFRAME_NO_TASKBAR | wxNO_BORDER);
}

但是,窗口的移动通常都是通过鼠标点住标题栏拖动窗口来实现的,那么现在没有了标题栏,该如何移动窗口呢?我们知道,当鼠标拖动窗口时,它在窗口中的位置是始终不变的。所以如果能够在鼠标移动过程中,通过改变窗口在桌面上的坐标,并且始终保持鼠标相对于窗口的坐标不变,即可实现鼠标的拖动效果。

在具体的设计中,先在鼠标的MouseDown事件中记录下鼠标相对于窗口的偏移,在鼠标的MouseMove事件中根据鼠标在桌面上的位置实时设置窗口的位置,即可达到鼠标拖动窗口的操作。利用此方法实现鼠标拖动,与常规的标题栏鼠标拖动在效果上有一点区别。通过标题栏拖动时,鼠标移动过程中不重画窗口,只有松开鼠标后才在固定位置重画窗口,因此其速度较快。而采用本方法的拖动过程中,每移动一步都需要重画窗口,因此对速度稍有影响,在慢一些的机器上可能会出现轻微的拖尾现象。

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
void MainFrame::OnMouseMove(wxMouseEvent& event)
{
    if (event.Dragging() && event.LeftIsDown())
    {
        wxPoint pt = ClientToScreen(event.GetPosition());
        int x = pt.x - m_delta.x;
        int y = pt.y - m_delta.y;
        Move(x, y);
    }
}

void MainFrame::OnMouseLeftDown(wxMouseEvent& event)
{
    CaptureMouse();
    wxPoint pt = ClientToScreen(event.GetPosition());
    wxPoint origin = GetPosition();
    int dx = pt.x - origin.x;
    int dy = pt.y - origin.y;
    m_delta = wxPoint(dx, dy);
}

void MainFrame::OnMouseLeftUp(wxMouseEvent& WXUNUSED(event))
{
    if (HasCapture())
    {
        ReleaseMouse();
    }
}

代码下载

程序只运行一个实例,并将前一个实例提到前台

wxWidgets提供了一个用来检测是否只有一个实例在运行的wxSingleInstanceChecker类。为了检测程序只运行一个实例,你可以在程序运行之初使用该类创建一个m_check对象,这个对象将存在于程序的整个生命周期。然后就可以在OnInit函数中调用其IsAnotherRunning函数检测是否已经有别的实例在运行。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool MainApp::OnInit()
{
    wxString name = wxString::Format(wxT("MainApp-%s"), wxGetUserId().GetData());
    m_checker = new wxSingleInstanceChecker(name);
    if (m_checker->IsAnotherRunning())
    {
        wxLogError(wxT("Another program instance is already running, aborting."));
        delete m_checker;
        return false;
    }
    // more initializations
    return true;
}

int MainApp::OnExit()
{
    delete m_checker;
    return 0;
}

注意:上面使用了wxGetUserId()来构建实例名,这表示允许不同的用户能同时运行程序的一个实例。如果不这样,那么程序就只能被一个用户运行一次。

但是,如果你想把旧的实例提到前台,或者想使旧的实例打开传递给新的实例的作为命令行参数的文件,该怎么办呢?一般来说,这需要在这两个实例间进行通讯。我们可以使用wxWidgets提供的进程间通讯类来实现。

在下面的实例中,我们将实现程序多个实例间的通讯,以便允许第二个实例请求第一个实例将自己带到前台以提醒用户它已经在运行。下面的代码实现了一个连接类,这个类将被两个实例使用。一个服务器类被旧的实例使用,以便监听新的实例的连接请求。一个客户端类被新的实例使用,以便和旧的实例进行通讯。

1
2
3
4
5
class AppServer : public wxServer
{
public:
    virtual wxConnectionBase* OnAcceptConnection(const wxString& topic);
};
1
2
3
4
5
class AppClient : public wxClient
{
public:
    virtual wxConnectionBase* OnMakeConnection();
};
1
2
3
4
5
6
class AppConnection : public wxConnection
{
public:
    virtual bool OnExecute(const wxString& WXUNUSED(topic), wxChar* WXUNUSED(data),
                          int WXUNUSED(size), wxIPCFormat WXUNUSED(format));
};

当有新的实例(作为Client)进行连接请求时,旧的实例(作为Server)中的OnAcceptConnection函数首先检查旧的实例中没有任何模式对话框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
wxConnectionBase* AppServer::OnAcceptConnection(const wxString& topic)
{
    if (topic.Lower() == wxT("only-one"))
    {
        wxWindowList::Node* node = wxTopLevelWindows.GetFirst();
        while (node)
        {
            wxDialog* dialog = wxDynamicCast(node->GetData(), wxDialog);
            if (dialog && dialog->IsModal())
            {
                return false;
            }
            node = node->GetNext();
        }
        return new AppConnection();
    }
    else
    {
        return NULL;
    }
}

OnExecute函数是一个回调函数,在新的实例对其连接对象(由AppConnection创建的对象)调用Execute函数时被调用。OnExecute函数可以有一个空的参数,这表示它只要将自己提到前台就可以了。

1
2
3
4
5
6
7
8
9
10
11
bool AppConnection::OnExecute(const wxString& WXUNUSED(topic), wxChar* WXUNUSED(data),
                              int WXUNUSED(size), wxIPCFormat WXUNUSED(format))
{
    wxFrame* frame = wxDynamicCast(wxGetApp().GetTopWindow(), wxFrame);
    if (frame)
    {
        frame->Restore();    // 必须要有这句,不然当主窗口最小化时,就不能被提到前台
        frame->Raise();
    }
    return true;
}

接下来我们还需要修改OnInit()函数。当没有别的实例在运行时,这个实例需要将自己设置为Server,等待别的实例的连接请求,如果已经有实例在运行,那么就创建一个和那个实例的连接,请求那个实例将程序的主窗口提到前台。下面的修改后的代码:

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
bool MainApp::OnInit()
{
    wxString name = wxString::Format(wxT("MainApp-%s"), wxGetUserId().GetData());
    m_checker = new wxSingleInstanceChecker(name);
    if (!m_checker->IsAnotherRunning())
    {
        m_server = new AppServer();
        if (!m_server->Create(wxT("wxMainApp")))
        {
            wxLogDebug(wxT("Failed to create an IPC service."));
            return false;
        }
    }
    else
    {
        AppClient* client = new AppClient();
        wxString hostName = wxT("localhost");
        wxConnectionBase* conn = client->MakeConnection(hostName, wxT("wxMainApp"), wxT("only-one"));
        if (conn)
        {
            conn->Execute(wxEmptyString);
            conn->Disconnect();
            delete conn;
        }
        else
        {
            wxString msg = wxT("Sorry, the existing instance may be too busy to respond.\nPlease close any open dialogs and retry.");
            wxMessageBox(msg, wxT("wxMainApp"), wxICON_INFORMATION | wxOK);
        }
        delete client;    // 如果没有这句,在运行Debug版本时就会显示如下图的警告
        return false;
    }
    // more initializations
    return true;
}

wxdde-alert

释放实例资源

1
2
3
4
5
6
int MainApp::OnExit()
{
    delete m_checker;
    delete m_server;
    return 0;
}

遇到的问题:

1、调用wxGetApp时编译错误

在调用wxGetApp()时可能会有编译错误,提示说“identifier not found”。这可以通过在App类后加上一行DECLARE_APP(XXXApp)来解决。

2、引入ipc.h和wx.h时的顺序问题

运行第二个实例的时候,发现它总是会挂起在MakeConnection处,查看进程可以看到有两个实例在运行。在网上找了n久,只在wxWidgets Forum上发现有提到这个问题(Windows service using wxWidgets ipc),可是也没有提到如何解决。只能靠自己啦,经过对程序的一步步排除,终于发现是因为引入头文件时将ipc.h放在wx.h之前的原因,掉换引入头文件的顺序后问题被解决。

代码下载

短信分拆算法

今天,突然心血来潮,写了一段分拆短信的代码。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SMSHelper {
    private static final int SMS_MAX_LENGTH = 70;

    public static List split(String msg) {
        if (msg.length() <= SMS_MAX_LENGTH) {
            throw new IllegalArgumentException("被分拆的短信长度必须大于70个字符");
        }

        List<String> list = new ArrayList<String>();
        int i = 0, start = 0;
        while (true) {
            i++;
            String prompt = "(第" + i + "条)";
            int remain = SMS_MAX_LENGTH - prompt.length();
            if (start + remain > msg.length()) {
                list.add(prompt + msg.substring(start));
                break;
            }
            list.add(prompt + msg.substring(start, start + remain));
            start += remain;
        }
        return list;
    }
}

进度条的再思考

英文原文:http://chrisharrison.net/projects/progressbars/ProgBarHarrison.pdf

摘要

进度条在现代用户界面中是普遍存在的。通常,线性函数的使用使得条的进度和已经完成多少工作成正比。然而,许多因素会导致进度条以非线性的速率前进。此外,人类以一种非线性的方式感知时间。这篇论文探讨各种进度条行为在用户感知过程持续时间上的影响。结果被用来提出一些设计考量——可以使进度条看起来更快,最终改善用户的计算体验。

简介

大多数软件包使用进度条显示一个持续过程的状态。用户依靠进度条来验证操作进展顺利,以及估算它的完成时间。通常,线性函数的应用使得进度条的前进和已经完成工作的数量成正比。然而,为复杂的或多阶段的过程估算进度可能是困难的。不同的磁盘,内存,处理器,带宽和其它因素使这个问题进一步复杂化。因而,进度条经常表现出非线性的行为,例如加速、减速和停顿。

再者,人类不以线性的方式感知时间的流逝。再加上进度条的不规则行为,导致人类感知过程持续时间的不同。了解哪些行为在感知上缩短或延长过程持续时间可以被用来改变进度条让它看起来更快,尽管实际的持续时间保持不变。这篇论文描述一个实验,它试图识别在用户感知进度条行为中的模式。然后结果被分析去划分在感知上加速或减速过程执行的行为。我们得出一些设计建议,它们可以被应用于使用进度条的应用,有助于一种总体上反应更灵敏、令人愉悦的和以人为中心的计算体验。

相关工作

Myers调查研究了图形用户界面中进度指示器对用户体验的影响。他得出用户在长期的任务过程中对进度指示器有着强烈的偏好,并且,总的来说,发现它们是有用的。Conn在《Time Affordances: The Time Factor in Diagnostic Usability Heuristics》一文中探讨了时间可见性的概念,文中还列举了一系列理想的进度条的属性。范例给用户提供了一个准确易懂的方法以帮助测量交互系统中的进度。Conn也定义了其它概念——时间容忍窗口,它是用户在判定任务没有取得足够进展前愿意等待的时间长度。Conn接着描述了可以被应用于设置用户期望进行更长等待的预测算法,本质上用一个非线性方法报告进度来增强用户体验。

Fredrickson等人提出持续时间对情感体验多么令人满意的评估影响不大(持续时间忽视)。相反,在体验期间和在体验结束时(峰端效应)感知受到显著特征(好的和坏的)的影响最严重。出现这种情况是因为人类不能以一种一致的和线性的方式记住体验,而是有选择地和带着各种偏见地回忆事件。持续时间忽视(duration neglect)和峰端效应(peak-and-end effects)可以在各种各样的领域中看到,包括医学、经济学、广告和人机交互。

实验

我们识别并开发了8个非线性函数,它们体现不同的进度行为。包含一个线性函数作为比较的基线。Table 1和Figure 1描述了每个进度函数的行为。为测试这些函数的人类感知,开发了一个实验应用,它同时给用户提供像Figure 2这样的两个进度条。这些进度条逐次运行:当第一个进度条完成时第二个进度条自动开始。每个进度条充当控件的持续时间保持在一个恒定的5.5秒。界面提供三个响应按钮,允许用户选择是否第一个或第二个进度条显示更快些或者它们在持续时间上是相等的。另一个按钮允许用户在继续随后的一对进度条之前重放每次测试。一旦提供了答案,下一次测试就会被启动。响应和重放按钮可以在任何时候被按下。

progress-bar-table-1

基于Java的应用运行在一台12”显示器1024x768分辨率的Apple笔记本上。进度条是使用Java Graphics2D原语定制的,600x50像素大小(大约1.2cm x 14.3cm)。一套着色和命名方案被应用于更好地可视化地通知用户:运行的进度条显示成蓝色并且题为“running”,而完成的进度条染成绿色并且题为“finished”。用户通过带有一个集成的、单独的鼠标按钮的触摸板和界面交互。

比较9个进度函数的所有不同顺序对需要81次测试。最初的试点测试显示,在大约50套进度条后用户发现任务是相当乏味的并开始失去兴趣。为了保持主题的关注和确保响应完整的高水平,我们决定给每个用户呈现9个进度函数的所有组合(36次测试)以及与自己配对的函数组合(9次测试)总计45次测试。这保持总的任务时间在15分钟以下。呈现的顺序在两个方面被抵消。首先,对每对用户来说45次测试的序列是随机选择的。其次,在每对用户中,每次测试的呈现顺序是相反的(也就是说,如果配对中第一个用户看见Linear/Power,第二个用户将看见Power/Linear)。

progress-bar-figure-1

我们从两个大的计算机研究实验室招募了平均年龄约为37岁的22名参与者(14名男性,8名女性)。实验在参与者的办公室里进行。简易比较界面的一个简短口头解释被给出。参与者被告知进度条可能以不同速率前进,他们应该选择他们认为更快的进度条或者如果速度相同就选择等于。

progress-bar-figure-2

分析和结果

参与者往往偏好(也就是说,认为更快)第一次看到的那个函数。在990对比较中,第一个函数被优先选择376次(38%),第二个262次(26%),没有任何偏好352次(36%)。这一发现被随后讨论的卡方检验结果所支持。

参与者在9个函数中有着强烈的偏好。对函数的任何配对比较,我们指派一个+1的偏好分数如果第一个函数被优先选择,如果第二个函数被优先选择则是-1,如果参与者没有偏好就是0。Table 2显示了36个函数对中每一个的平均偏好分数。例如,在22个Slow Wavy和Fast Wavy的比较中(每个出现11次),10名参与者优先选择Fast Wavy,5名优先选择Slow Wavy,7名认为函数是相等的。因此,平均偏好分数是(10 – 5) / 22 = 0.23。表的行和列根据增长的总体偏好排序。粗体表示统计显著性从0开始在0.05水平上使用每个函数是同等可能被优先选择的零假设的一个双边符号测试。

progress-bar-table-2

使用Table 2中的平均偏好分数,我们为9个进度函数生成一个粗略的偏好排序,如Figure 3所示。

progress-bar-figure-3

为有效地组合跨单元信息,在控制呈现顺序时,我们使一个logistic回归模型与偏好已给出的638个案例相拟合。偏好Function i胜于Function j假定函数Function i被第一次看到的概率被建模为

progress-bar-formula

Hosmer-Lemeshow卡方检验(8.87和自由度7)未能显示模型拟合的不足。参数α据估计是带有标准误差0.09的0.42,反映了参与者更偏好他们看见的第一个函数的倾向性。估计的α度量在函数中的相对偏好。因为概率仅仅依赖α间的差异,我们把为Linear估计的α固定在0(Figure 4)。α间差异的标准误差介于0.28和0.37。

progress-bar-figure-4

9个函数干净利落地聚成三组(Figure 4):3个被认为比Linear更慢,4个被认为接近Linear,2个被认为比Linear更快。所有三组间的不同是显著的但在组内不一定显著。每个函数的α和其它任何群集中的每个函数在0.05水平上差异显著。被认为比Linear更快的2个函数Power和Fast Power都是指数函数,在接近过程的结尾以更快的进度发生。Slow Wavy、Fast Wavy和Late Pause,仅有的在接近过程结束有停顿的函数,都被认为比Linear更慢。

三个常规发现解释了估算模式,它符合前面提到的峰端效应。首先,参与者认为带有停顿的进度条需要更长的时间去完成(峰值效应)。其次,加速的进度被强烈地青睐。当位于过程即将结束时两种效应的后者有一个夸张的感知影响(末端效应)。有趣的是,两个因素似乎结合在Early Pause情况下,使它基本上等同于偏好的Linear函数。

论述

尽管我们的结果可以被用来增强整个系统的进度条,但有许多情况修改进度行为似乎不合适。一般而言,带有已知静态完成条件和稳定进度的过程不是好的候选者——标准进度条能有效地、精确地可视化这些。另外,这些过程类型往往很少被停顿或其它负面进度行为影响(足以使它们经常伴随着精确的时间估算)。这种过程类型的例子包括拷贝一个文件到磁盘,扫描一张照片,或者播放一个音频文件。

然而,带有动态完成条件和粗略估计持续时间的进度条(比如,碎片整理硬盘驱动器)可以在两个重要方面被加强。首先,因为用户似乎对停顿特别是一个操作即将结束时有种强烈的厌恶,进度条可以被设计成用于弥补这种行为。一个智能的进度条可以缓存进度当操作首先开始缓解以后的负面进度行为(比如,停顿或者迟缓)时。其次,进度可以在开始时被低估和在即将结束时被加速,提供一个迅速结束的感觉在我们的实验中是被用户高度青睐的。

感知增强也可以被集成到多阶段过程的设计中,例如软件的安装。我们的结果表明用户最愿意在一个操作的开始容忍负面的进度行为(比如,停顿和不一致的进度)。因此,过程阶段可以安排那些更慢的或多变的操作最先完成。例如,如果安装程序的一部分需要从远程服务器获取更新,而网络连接可能是不稳定或不可靠的,最好在安装顺序的早期运行这个阶段。更新自己总是可以被稍后应用,因为它们运行在本地,有更多的可预测行为。

总结

不同的进度条行为在用户感知进度持续时间上似乎有显著影响。通过最小化负面行为和结合正面行为,可以有效地使进度条及其相关过程看起来更快。此外,如果一个多阶段操作的元素可以被重新排列,它可能会用更令人满意和似乎更快的序列重新排序阶段。

未来的工作

在此次实验中,所有进度条在5.5秒内完成。然而许多使用进度条的操作有相当长的运行时。如果我们的发现扩展到其它持续时间,调查研究应该会很有趣。此外,其它进度行为和行为组合的研究可能揭示新的感知影响。同时,一个自适应实验可以被进行,在此实验中,单独的进度条时间将被动态调整到所有函数在持续时间上被认为相等的一个状态。这将允许相关感知的变体被定量评估。

Hello wxWidgets!

MainApp.h代码:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef __MAINAPP_H__
#define __MAINAPP_H__

#include <wx/wx.h>

class MainApp : public wxApp
{
public:
    virtual bool OnInit();
};

#endif

MainApp.cpp代码:

1
2
3
4
5
6
7
8
9
10
11
12
#include "MainApp.h"
#include "MainFrame.h"

IMPLEMENT_APP(MainApp)

bool MainApp::OnInit()
{
    MainFrame* frame = new MainFrame("Hello wxWidgets!");
    SetTopWindow(frame);
    frame->Show();
    return true;
}

MainFrame.h代码:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef __MAINFRAME_H__
#define __MAINFRAME_H__

#include <wx/frame.h>

class MainFrame : public wxFrame
{
public:
    MainFrame(const wxString& title);
};

#endif

MainFrame.cpp代码:

1
2
3
4
5
6
#include "MainFrame.h"

MainFrame::MainFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title)
{
    // TODO: add member initialization code here
}

身份证工具IDCardTool

周末赶了两天,终于把这个工具完成了。界面几乎完全仿制了“身份证生成与查询BBSt(共享版)”。

ict-main

中国居民身份证的常识

我国现行使用公民身份证号码有两种尊循两个国家标准,〖GB 11643-1989〗和〖GB 11643-1999〗。

〖GB 11643-1989〗中规定的是15位身份证号码:排列顺序从左至右依次为:六位数字地址码,六位数字出生日期码,三位数字顺序码,其中出生日期码不包含世纪数。

ict-15

〖GB 11643-1999〗中规定的是18位身份证号码:排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码,一位数字校验码。

ict-18

  1. 地址码:表示编码对象常住户口所在县(市、旗、区)的行政区划代码
  2. 出生日期码:表示编码对象出生的年、月、日,年、月、日之间不用分隔符。
  3. 顺序码:表示同一地址码所标识的区域范围内,对同年、同月、同日出生的人员编定的顺序号。顺序码的奇数分给男性,偶数分给女性。
  4. 校验码:根据前面十七位数字码,按照国际标准ISO 7064:1983.MOD 11-2规定的校验算法计算出来的检验码。

关于身份证号码最后一位校验码的算法如下:

1
∑(a[i] * W[i]) mod 11 (i = 2, 3, ..., 18)
  • *:表示乘号;
  • i:表示身份证号码每一位字符的位置序号,从右至左,最左侧为18,最右侧为1;
  • a[i]:表示身份证号码第i位上的号码数字值;
  • W[i]:表示第i位上的加权因子,其数值依据公式W[i] = 2i-1 mod 11计算得出。

设:R = ∑(a[i] * W[i]) mod 11 (i = 2, 3, ..., 18),C = 身份证号码的校验码,则R和C之间的对应关系如下表:

ict-check

由此看出Ⅹ相当于10(罗马数字中的10就是用Ⅹ表示),所以在新标准的身份证号码中可能含有非数字的字母Ⅹ。

代码下载:https://github.com/dohkoos/IDCardTool

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-compile

编译好后的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)。

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时一直出现像下面这样的错误信息:

ruby-dbi

下面是一个完整的例子

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

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" />

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

input-submit

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

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

并且它看起来像这样:

button-tag

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

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

它们看起来像这样:

button-images

非常好。(好吧,它们有点丑,但是我说过它们需要一点点爱。)实际上,根据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上看到所有的这些内容):

结果

buttons

没有什么疯狂的。简单,但是有效。现在,我喜欢这种处理按钮的方式是我可以使用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之前要二思。