九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

Windows中多指针输入技术的实现与应用(9 我设想用来实现MFC多鼠标的透明窗口源代码。。。)

/ 一下基本就是实现了一个透明的窗口,以此窗口来表示鼠标指针,我用了CImage,你可以到网上查查它的用法。


//  作者   :
九天雁翎(JTianLing)

//  软件   :
透明窗口示例程序

//  版本   :
0.1

//  文件  
: MouseWnd.h

//  描述   :

//        

//  实现在Visual Studio .NET 2005 SP1下编译测试通过。

//  Webs   :
groups/google.com/group/jiutianfile

//  Blog   :
blog.csdn.net/vagrxie

//  E-mail :
JTianLing@GMail.com

// 

//  欢迎大家在上述网页发帖或来信探讨,或说明该软件的BUG和可以改进之处

//  创建时间:23:11:2008

//
#################################################################pragma once

 

 

//
CMouseWnd

 

class CMouseWnd : public CWnd

{

    DECLARE_DYNAMIC(CMouseWnd)

 

public:

    CMouseWnd();

    virtual ~CMouseWnd();

    CImage m_image;

protected:

 

//  CImage m_imBackGround;

    CRect m_rtImage;

    CDC* m_pImDC;

    DECLARE_MESSAGE_MAP()

public:

    afx_msg void OnPaint();

    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

    afx_msg void OnDestroy();

    afx_msg BOOL OnEraseBkgnd(CDC* pDC);

protected:

    virtual BOOL
PreCreateWindow(CREATESTRUCT&
cs);

};

 

//---------------------------------------------------------------------------------------------------


//  文件  
: MouseWnd.cpp

//  描述   :

//        

//  实现在Visual Studio .NET 2005 SP1下编译测试通过。

//  Webs   :
groups/google.com/group/jiutianfile

//  Blog   :
blog.csdn.net/vagrxie

//  E-mail :
JTianLing@GMail.com

// 

//  欢迎大家在上述网页发帖或来信探讨,或说明该软件的BUG和可以改进之处

//  创建时间:23:11:2008

//
#################################################################

#include "stdafx.h"

#include "TestWnd.h"

#include "MouseWnd.h"

 

 

//
CMouseWnd

 

IMPLEMENT_DYNAMIC(CMouseWnd,
CWnd)

 

CMouseWnd::CMouseWnd()

{

    m_image.Load(_T("res/Mouseh.png"));

}

 

CMouseWnd::~CMouseWnd()

{

}

 

 

BEGIN_MESSAGE_MAP(CMouseWnd,
CWnd)

    ON_WM_PAINT()

    ON_WM_CREATE()

    ON_WM_DESTROY()

    ON_WM_ERASEBKGND()

END_MESSAGE_MAP()

 

 

 

//
CMouseWnd
消息处理程序

 

 

 

void CMouseWnd::OnPaint()

{

    CPaintDC dc(this); // device context for painting

    // TODO: 在此处添加消息处理程序代码

    // 不为绘图消息调用CWnd::OnPaint()

    //dc.TransparentBlt(m_rtImage.left,
m_rtImage.top, m_rtImage.Width(), m_rtImage.Height(),

    //  m_pImDC,
0, 0, m_image.GetWidth(), m_image.GetHeight(), RGB(255,255,255));

 

 

    //BLENDFUNCTION blend;

    //blend.BlendOp = AC_SRC_OVER;

    //blend.BlendFlags = 0;

    //blend.SourceConstantAlpha = 0;

    //blend.AlphaFormat = AC_SRC_ALPHA;

    //UpdateLayeredWindow(CPaintDC::FromHandle(dc.GetSafeHdc()),
&CPoint(0,0), &CSize(m_image.GetWidth(), m_image.GetHeight()),

    //  m_pImDC,
&CPoint(0,0), RGB(255,255,255), &blend, ULW_ALPHA | ULW_COLORKEY);

 

    //m_image.TransparentBlt(dc.GetSafeHdc(),m_rtImage,RGB(255,255,255));

}

 

int CMouseWnd::OnCreate(LPCREATESTRUCT
lpCreateStruct)

{

    if (CWnd::OnCreate(lpCreateStruct)
== -1)

       return -1;

 

    // TODO:  在此添加您专用的创建代码

    GetClientRect(&m_rtImage);

    m_rtImage.right = m_image.GetWidth();

    m_rtImage.bottom = m_image.GetHeight();

    m_pImDC = CDC::FromHandle(m_image.GetDC());

 

    ::SetWindowLong(m_hWnd, GWL_EXSTYLE,

       ::GetWindowLong(m_hWnd, GWL_EXSTYLE)
| WS_EX_LAYERED);

    SetLayeredWindowAttributes(RGB(255,255,255), 0, LWA_COLORKEY);

    SetWindowPos(&wndTopMost,0,0,0,0,SWP_NOMOVE
| SWP_NOSIZE);

    return 0;

}

 

void CMouseWnd::OnDestroy()

{

    CWnd::OnDestroy();

 

    // TODO: 在此处添加消息处理程序代码

    m_image.ReleaseDC();

}

 

BOOL CMouseWnd::OnEraseBkgnd(CDC*
pDC)

{

    // TODO: 在此添加消息处理程序代码和/或调用默认值

//  CWnd::OnEraseBkgnd(pDC);

 

    //pDC->TransparentBlt(m_rtImage.left,
m_rtImage.top, m_rtImage.Width(), m_rtImage.Height(),

    //  m_pImDC,
0, 0, m_image.GetWidth(), m_image.GetHeight(), RGB(255,255,255));

//  m_image.TransparentBlt(pDC->GetSafeHdc(),m_rtImage,RGB(255,255,255));

    m_image.Draw(pDC->GetSafeHdc(),m_rtImage);

//  m_image.BitBlt(pDC->GetSafeHdc(),m_rtImage.left,m_rtImage.right,SRCCOPY);

    return true;

}

 

BOOL CMouseWnd::PreCreateWindow(CREATESTRUCT&
cs)

{

    // TODO: 在此添加专用代码和/或调用基类

    //cs.lpszClass =
AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,

    //  ::LoadCursor(NULL,
IDC_ARROW), reinterpret_cast<HBRUSH>(GetStockObject(NULL_BRUSH)), NULL);

    return CWnd::PreCreateWindow(cs);

}

 

//--------------------------------------------------------------------------------------------------------------------------


 

//
ChildView.h : CChildView
类的接口

//

 

 

#pragma once

#include "MouseWnd.h"

 

//
CChildView
窗口

 

class CChildView : public CWnd

{

// 构造

public:

    CChildView();

 

// 属性

public:

 

// 操作

public:

 

// 重写

    protected:

    virtual BOOL
PreCreateWindow(CREATESTRUCT&
cs);

 

// 实现

public:

    virtual ~CChildView();

protected:

    CImage m_image;

    CImage m_imBackGround;

    CRect m_rtImage;

    CRect m_rtMouseWnd;

    CDC* m_pImDC;

    CMouseWnd m_mouseWnd;

    // 生成的消息映射函数

    //void SetTransparent(HWND   hwnd,UINT  
alpha);

protected:

    afx_msg void OnPaint();

    DECLARE_MESSAGE_MAP()

public:

    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

    afx_msg void OnTimer(UINT_PTR nIDEvent);

    afx_msg BOOL OnEraseBkgnd(CDC* pDC);

    afx_msg void OnDestroy();

};

 

//------------------------------------------------------------------------------


//
ChildView.cpp : CChildView
类的实现

//

 

#include "stdafx.h"

#include "TestWnd.h"

#include "ChildView.h"

 

#ifdef _DEBUG

#define new DEBUG_NEW

#endif

 

 

//
CChildView

 

CChildView::CChildView()

{

    m_image.Load(_T("res/Mouseh.png"));

    m_imBackGround.Load(_T("res/BackGround001.jpg"));

}

 

CChildView::~CChildView()

{

}

 

 

BEGIN_MESSAGE_MAP(CChildView,
CWnd)

    ON_WM_PAINT()

    ON_WM_CREATE()

    ON_WM_TIMER()

    ON_WM_ERASEBKGND()

    ON_WM_DESTROY()

END_MESSAGE_MAP()

 

 

 

//
CChildView
消息处理程序

 

BOOL CChildView::PreCreateWindow(CREATESTRUCT&
cs)

{

    if (!CWnd::PreCreateWindow(cs))

       return FALSE;

 

    cs.dwExStyle
|= WS_EX_CLIENTEDGE;

    cs.style
&= ~WS_BORDER;

    cs.lpszClass
= AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,

       ::LoadCursor(NULL, IDC_ARROW),
reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), NULL);

 

    return TRUE;

}

 

void CChildView::OnPaint()

{

    CPaintDC dc(this); // 用于绘制的设备上下文

   

    // TODO: 在此处添加消息处理程序代码

   

    // 不要为绘制消息而调用CWnd::OnPaint()

 

 

// Use pDC
here

    //dc.TransparentBlt(m_rtImage.left,
m_rtImage.top, m_rtImage.Width(), m_rtImage.Height(),

       //m_pImDC, 0, 0, m_image.GetWidth(),
m_image.GetHeight(), RGB(255,255,255));

    //dc.BitBlt(m_rtImage.left,
m_rtImage.top, m_rtImage.Width(), m_rtImage.Height(),

    //  m_pImDC,
0, 0, SRCCOPY);

 

    m_image.TransparentBlt(dc.GetSafeHdc(),m_rtImage,RGB(255,255,255));

// m_image.TransparentBlt(dc.GetSafeHdc(),0,0,m_image.GetWidth(),m_image.GetHeight(),RGB(255,255,255));

    //m_image.Draw(dc.GetSafeHdc(),0,0);

}

 

 

int CChildView::OnCreate(LPCREATESTRUCT
lpCreateStruct)

{

    if (CWnd::OnCreate(lpCreateStruct)
== -1)

       return -1;

 

    // TODO:  在此添加您专用的创建代码

    GetClientRect(&m_rtImage);

    m_rtImage.right = m_image.GetWidth();

    m_rtImage.bottom = m_image.GetHeight();

    m_pImDC = CDC::FromHandle(m_image.GetDC());

    SetTimer(NULL,100,NULL);

 

    GetClientRect(&m_rtMouseWnd);

    m_rtMouseWnd.right = m_mouseWnd.m_image.GetWidth();

    m_rtMouseWnd.bottom = m_mouseWnd.m_image.GetHeight();

    m_rtMouseWnd.OffsetRect(0, 100);

    m_mouseWnd.CreateEx(NULL, AfxRegisterWndClass(0), _T(""), WS_POPUP
| WS_VISIBLE,

                     m_rtMouseWnd,
NULL,  NULL, NULL );

    //m_mouseWnd.Create(NULL, NULL,
WS_CHILD | WS_VISIBLE, m_rtMouseWnd, this,

    //  1001);

    /*SetTransparent(m_mouseWnd.m_hWnd,0);*/

    return 0;

}

 

void CChildView::OnTimer(UINT_PTR
nIDEvent)

{

    // TODO: 在此添加消息处理程序代码和/或调用默认值

    CClientDC dc(this);

 

    m_rtImage.OffsetRect(1,0);

    CRect rect(m_rtImage.left-1,m_rtImage.top,m_rtImage.right,m_rtImage.bottom);

    //InvalidateRect(rect);

//  Invalidate();

    m_rtMouseWnd.OffsetRect(1, 0);

    m_mouseWnd.MoveWindow(m_rtMouseWnd);

//  m_mouseWnd.Invalidate();

//  m_mouseWnd.AnimateWindow(200,AW_SLIDE |
AW_HOR_POSITIVE );

    CWnd::OnTimer(nIDEvent);

}

 

BOOL CChildView::OnEraseBkgnd(CDC*
pDC)

{

    // TODO: 在此添加消息处理程序代码和/或调用默认值

//  CWnd::OnEraseBkgnd(pDC);

    CRect rect;

    GetClientRect(&rect);

    m_imBackGround.StretchBlt(pDC->GetSafeHdc(),rect);

    return true;

}

 

void CChildView::OnDestroy()

{

    CWnd::OnDestroy();

 

    m_image.ReleaseDC();

    // TODO: 在此处添加消息处理程序代码

}

 

//void
CChildView::SetTransparent(HWND  
hwnd,UINT   alpha)  

//{

//  typedef BOOL (*LAYERFUNC)(HWND hwnd,COLORREF
crKey,BYTE bAlpha,DWORD dwFlags);

//  LAYERFUNC  
SetLayer;  

//  HMODULE  
hmod=LoadLibrary(_T("user32.dll"));  

//  SetWindowLong(hwnd,GWL_EXSTYLE,GetWindowLong(hwnd,GWL_EXSTYLE)|0x80000L);  

//  SetLayer=(LAYERFUNC)GetProcAddress(hmod,"SetLayeredWindowAttributes");  

//  SetLayer(hwnd,0,(255*alpha)/100,0x2);  

//

//  FreeLibrary(hmod);  

//}

 


看到那么多注释就知道这是个没有完成的 东西了,不过用这个透明的窗口加上我那些多鼠标的底层实现,用MFC来实现一个比较好的通用多鼠标系统应该是没有技术问题了,来实现一般的多鼠标MFC
序更是没有问题。这是我在参加工作前的最后一个作品。。。。呵呵,已经是7个月前的事情了,(现在是20081123日),我今天晚上看到以前的这个东西,想起来那时候刚参加工作,家里连个可用的VS都没有,所以代码乱的很,今天整理一下,重新发一下,另外源代码也打包发到我常用的地址中:

http://groups.google.com/group/jiutianfile

另外,将当时没有发布的双鼠标五子棋源代码一起发了吧,那时候带着笔记本,台式机上的源码都发不了,直到现在才想起来:)

很多朋友发邮件或者在博客中问过我为什么没有继续对多鼠标这个东西研究下去,呵呵,在此一并答复了,本人实在是工作忙啊。。。。。。。再次说明一下什么叫 忙,每天工作12个小时以上,每周工作6天,再加上你看看我最近学的东西,lua,python,bash....实在没有时间了,呵呵,既然我把什么都 公布了,大家有什么想法就都自己去实现吧。。。。有不懂的我能解答的我尽量回答。。。

阅读全文....

Windows中多指针输入技术的实现与应用(8 总结及继续MFC的讨论

Windows中多指针输入技术的实现与应用(8 总结及继续MFC的讨论)

湖南大学 谢祁衡

5 结论和目前各实现的不足

       本文通过几种目前国外常用的多鼠标控制输入实现方法的比较,分析了各种方法的优劣,并给出使用建议。最后简单介绍了本文得出的综合性能最佳的SDG Toolkit具体使用方法。本文希望通过对此技术两种实现方法原理的详细介绍,各种实现方法的简单介绍,和对SDG Toolkit具体使用方法的简单介绍,可以让人了解并可以在更多多指针输入技术有优势的相关的软件中使用此技术。

       但是即便是现在最佳的实现方案SDG Toolkit也仍然有很多不足之处,目前没有任何方案能够较完美的兼容以前的软件,使得任何开发人员要利用此技术都不得不通过对此技术的学习,并且必须要对以前的软件进行一定改变才能很好的利用此技术。另外,虽然多指针输入的软件开发方案已经比较多了,但仅仅是解决了多鼠标操作同一个软件的问题,离SDG目标真正的实现还有一定差距,比如目前还没有办法同时让各人使用自己的鼠标操作各自不同的软件,就像分屏技术那样与其他人互不影响。因为此种技术的实现必然牵涉到Windows底层的窗口焦点问题,难度比较大,所以笔者目前还没有看到任何真正的实现。笔者非常希望期待有一天真正通用的多指针输入设备软件出现,或者有一天微软能从操作系统层面完全的支持多指针设备的输入,让软件开发者不需要了解任何相关知识,就可以在需要的时候,利用这种在很多方面都有独特优点的技术。不用MFC的朋友们就可以不往下看了,以下专门写个和我一样在MFC中痛苦挣扎的兄弟们。

     另外,个人比较遗憾的是,我最常用的类库MFC并没有很好的多鼠标技术实现,于是只能自己从头编程,这点我很郁闷,只能自己摸索摸索了,原论文到这里就完了,关于MFC的部分是我后来加的,我今天就要去北京了,所以不知道什么时候再有时间来完成关于MFC的部分了,甚至连梳理一下思路的时间都没有,啊。。。我已经一晚上没有睡了,虽然看了前面一部分估计你们都可以自己在MFC下实现了,但是为了抛砖引玉,我贴出以前两人五子棋的源代码,供大家参考,这里我要说明一下的是,那时候的版本还不是太好,但是我也对第2章的内容进行了优化,那就是只更新鼠标移动的一定范围内的矩形,以此达到减少全屏刷新的问题,那个程序很久没有动它了,其实就算要更新矩形也应该计算一下移动,然后更新移动范围内的矩形较好,当时仅仅作为测试,就没有想太多,就是更新了一个我自己定义的范围而已。现在我新的摸索中的实现是利用SDG Toolkit中类似的思想,让透明的绘制有鼠标指针图标的窗口作为鼠标指针,这样刷新的就仅仅是上层窗口了,那样几乎是我能想到的最完美的鼠标指针绘制方式,假如我等下能找到以前写的一些代码的话,我也贴出来。

由于现在我在家里电脑,没有安装VS,无法测试现存代码的正确性,不知道在进行了相关更改尝试后程序还能不能正常编译运行,但是方法和思想都还在里面,主要的问题就是绘制顶层透明窗口的问题而已,希望你们自己能识别一下。谢谢了,假如以后有时间的话,我把关于MFC的部分再梳理一下,也把我的程序源代码解释一下^^

其中我还尝试了在MFC 中插入.Net控件的方式,希望能在MFC中利用SDG Toolkit,但是发现实际的效果并不好,因为消息映射起来会相当麻烦,都要依靠手工代码,而且效率明显不如纯MFC程序,这里我就没有讲这种方法了,还是那句话,以后有时间也讲讲,大家各取所需吧。

目前这个系列也就到这里了,呵呵,要都看完和理解估计也不是那么快的吧:)

下面就贴几个源代码吧

阅读全文....

Windows中多指针输入技术的实现与应用(7 分时多鼠标控制系统鼠标的问题)


Windows中多指针输入技术的实现与应用(7 分时多鼠标控制系统鼠标的问题)

湖南大学 谢祁衡

在此以CPNMOUSE为例,看截图就都知道了,简而言之就是当同时操作两个鼠标的时候会导致混乱,我提供的建议是,你可以考虑完全不管系统鼠标,完全自己控制每个鼠标的输入数据,或者,你可以像SDG Toolkit那样,将系统鼠标的控制权交在某一个鼠标手中,由系统控制,而其他鼠标的输入数据完全由自己控制

 

阅读全文....

Windows中多指针输入技术的实现与应用(6 Single Display Groupware Toolkit的应用)

Windows中多指针输入技术的实现与应用(6 Single Display Groupware Toolkit的应用)

湖南大学 谢祁衡

4.Single Display Groupware Toolkit的应用

       在新设计软件中要加入多指针设备的输入,通过分析对比,目前最成熟的方案是利用SDG Toolkit实现,SDG Toolkit包含了其他方案的很多优点,而且使用的方便程度及官方的文档支持,甚至强过微软目前的MultiPoint SDK,而且效率上实现的非常好,加入SDG Toolkit的事件后,甚至看不出与原有软件的效率区别。还有就是因为SDG Toolkit是开源软件,自己可以进一步改进效率,比如去除不需要的部分重新编译以得到更精简的代码。最大的缺点就是此项目从2004年开始已停止更新,很难加入对未来Windows版本的直接支持,并且因为技术原因不能运行在Windows 9X上,导致目前SDG Toolkit可以预知的使用环境仅仅是Windows XP/NT系统。考虑到微软将来在Windows Vista中的直接支持及MultiPoint SDK将来的技术升级,利用MultiPoint SDK实现是将来有前途的实现。而假如不愿意受制于微软的Visual Studio.Net编程环境,或者希望在Windows 9X中也能运行,可以接受的方案就是自己利用RawInput技术实现或则利用CPNmouse库。对于一般情况,SDG Toolkit将是目前最好的选择。以下简要讲解了SDG Toolkit的使用方法,仍旧主要通过鼠标指针的绘制及输入数据的识别两方面讲述,并以Visual Studio .NET 2005 下使用C#语言编程控制3个鼠标为例,其中一个为PS/2,两个为USB接口。

 

4.1 Single Display Groupware Toolkit的安装

       因为SDG Toolkit向微软的.NET技术兼容,要使用SDG Toolkit就要先安装VS(Visual Studio) .NET。这里我以目前使用的最多的VS .NET 2005为例介绍。

       SDG Toolkit因为没有自己的安装程序,所以需要使用者自己在VS .NET中设置,首先要把从官方网站上下来的文件解压到磁盘的某个地方,因为2.0.0.9还未加入相关的控件,目前最新且可用的版本为2.0.0.8。共两个文件,分别为Sdgt 2.0.0.8.dllSDGWidgets.dll,然后在VS 的工具箱中通过 选择项->.NET Framwork组件->浏览,找到这两个文件并添加进工具箱,SDG就算安装完了。效果如图4.1

4.1 SDG Toolkit提供给开发者的控件

 

4.2 Single Display Groupware Toolkit指针的绘制

       在多鼠标输入时最难以解决的就是两个问题,即不同鼠标输入数据的识别和鼠标指针的绘制。能识别不同鼠标数据的输入开发者才能让软件对不同的输入进行不同的响应,绘制独立的鼠标指针才能让软件的用户可以进行操作。下面首先介绍使用SDG Toolkit时各鼠标独立指针的绘制。

       要在程序中使用SDG Toolkit,首先需要把图4.1所示的SdgManager控件加入新建立的程序。SdgManagerSDG Toolkit的主要控件,管理着SDG Toolkit包括所有输入事件的响应,鼠标指针的绘制等主要功能。

       在程序中添加了SdgManager后,不改动任何数据,也即所有的参数都使用默认值,此时程序可以被正常的编译运行,且SDG Toolkit已完成了不同鼠标指针的绘制。但是此时程序无法响应任何鼠标输入的事件,甚至连系统原有的事件都无法响应。因为我们此时没有指明到底系统应该响应哪个鼠标的,首先应该将SdgManagerRelativeTo属性更改为Form1,表示这个SdgManager的鼠标响应与Form1相关,然后代码中InitializeComponet后添加进如下两条语句:

 

sdgManager1.EmulateSystemMouseMode =  Sdgt.EmulateSystemMouseModes.FollowMouse;

            sdgManager1.MouseToFollow = 0;

 

    第一个语句表示系统鼠标的模式为跟随某个鼠标,第二个语句表示系统鼠标跟随第1个鼠标。在这里0表示迭代的索引号,0表示第一个,1表示第二个,依此类推,如C++中数组的规则。

       添加这两条语句之后,此程序就可以响应第一个鼠标引发的系统事件,即把第一个鼠标当作系统鼠标来操作。这里要提醒的是,系统所辨识的第一个鼠标为WindowsGetRawInputDeviceList函数所标识的Device 0,并且依此类推,而不是指用户接入的第一个鼠标。这是因为上面所提及的,SDG Toolkit是利用带IDRawInput技术所实现。另外需要非常注意的是,假如需要让程序在只有一个鼠标的情况下能够运行,那么此值就必须设为0,即第一个鼠标,因为只有一个鼠标的时候设为0以外的数值属于数组越界的情况,会导致程序的崩溃。

       此时会发现3个鼠标的指针完全相同,这样会导致用户不知道自己控制的是哪一个。这也是多鼠标输入需要解决的问题之一,SDG Toolkit提供了很多种方法以解决此问题。在SDG Toolkit中,可以利用鼠标指针样式的不同,鼠标指针的透明度不同,鼠标指针的文字标识以使用户很容易的识别自己所操作的鼠标指针。已经说过,在SDG Toolkit中把每一个鼠标的指针属性,数据信息和相关操作都放在一个为名为Mouse的类里面,多加一个鼠标,就在sdgManager下的Mice动态数组中多添加一个mouse类成员。因此对以上几种方法的实现都是通过更改sdgManagerMice数组成员不同的属性来完成的。比如更改sdgManager.Mice[i].Cursor为改变鼠标指针的类型,只要符合Windows的标准,也可以是自定义指针;改变sdgManager.Mice[i].Opacity改变鼠标指针的透明度;sdgManager.Mice[i].Text为定义鼠标指针的文字标识,系统默认在鼠标指针的右下角显示。还可以通过改变sdgManager.Mice[i]TextBrush来改变文字标识的颜色,TextFont来改变文字标识的字体,TextLocation来改变文字标识的位置。另外,上面介绍过,SDG Toolkit为考虑在多人使用触摸屏时站在屏幕不同的角度,所以加入了指针的方向特性。只需要改变sdgManager.Mice[i]DegreeRotation属性就可以实现。改变鼠标指针的方向不仅仅是为了识别,此时鼠标的移动也是以所在屏幕的方向为基准,这点真的是很符合特定情况的需要。在上文中,[i]表示的是第i个成员,i为鼠标的ID号,其分配也是以GetRawInputDeviceList为准。详细示例代码如下所示。




SDG鼠标指针绘制及改变测试程序具体代码(以下代码VS .NET 2005 上编译通过)

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

namespace SDG_TEST

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

            //设置鼠标指针样式

            Cursor[] sdgCursors = { Cursors.IBeam, Cursors.Hand, Cursors.Default };

            //设置鼠标文字标识内容

            String[] sdgText = { "Mouse 1", "Mouse 2", "Mouse 3" };

            //设置鼠标文字标识颜色

            Color[] sdgColor = { Color.Blue, Color.Red, Color.Black };

            //设置鼠标指针透明度

            double[] sdgOpacity = { 0.4, 0.6, 1 };

            //设置鼠标指针方向

            int[] sdgDegreeRotations = {90, 45, 0};

            //迭代遍历所有鼠标,这里以3个鼠标为例

               for (int i=0; i < sdgManager1.Mice.Count && i < 3; ++i)

            {

                   sdgManager1.Mice[i].Cursor = sdgCursors[i];

                    sdgManager1.Mice[i].Text = sdgText[i];

                sdgManager1.Mice[i].Opacity = sdgOpacity[i];

                sdgManager1.Mice[i].TextBrush = new SolidBrush(sdgColor[i]);

                   sdgManager1.Mice[i].DegreeRotation = sdgDegreeRotations[i];

               }

            //改变系统鼠标模式为跟随模式

            sdgManager1.EmulateSystemMouseMode = Sdgt.EmulateSystemMouseModes.FollowMouse;

            //制定第1个鼠标为系统鼠标

            sdgManager1.MouseToFollow = 0;

        }

    }

}

 



程序运行效果如图

4.2 SDG Toolkit鼠标指针的绘制及识别

 

4.3 Single Display Groupware Toolkit不同鼠标输入数据的识别

       已经讲过,在SDG Toolkit中不同鼠标输入数据的识别是通过Windows的事件系统来完成的。SDG Toolkit一共能响应鼠标的5个事件,分别与对应的Windows标准事件类似,只是参数不同,多了ID属性用来识别不同的鼠标。事件的响应程序设置也与标准的Windows程序事件响应设置类似。要加以说明的是Button属性识别按键是通过与System.Windows.FormsMouseButtons下各鼠标按键值做比较,Delta识别鼠标中键滚轮时因为Windows设计的原因,数据的输入都是按120的倍数,其中向下滚动为负,向上滚动为正。详细示例代码如下所示:



SDG不同鼠标输入数据的识别及与Windows标准控件的交互(以下代码VS .NET 2005 上编译通过)

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

 

namespace SDG_TEST

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

            //设置鼠标文字标识内容, 此例仅以文字标识区别不同鼠标

            String[] sdgText = { "Mouse 1", "Mouse 2", "Mouse 3" };

            //迭代遍历所有鼠标,这里以3个鼠标为例

               for (int i=0; i < sdgManager1.Mice.Count && i < 3; ++i)

            {

                    sdgManager1.Mice[i].Text = sdgText[i];

               }

            //改变系统鼠标模式为跟随模式

            sdgManager1.EmulateSystemMouseMode = Sdgt.EmulateSystemMouseModes.FollowMouse;

            //制定第1个鼠标为系统鼠标

            sdgManager1.MouseToFollow = 1;

        }

        //鼠标移动的事件响应

        private void sdgManager1_MouseMove(object sender, Sdgt.SdgMouseEventArgs e)

        {

            //通过识别不同的鼠标ID来改变不同的textBox属性

            switch (e.ID)

            {

                case 0:

                    this.tB_mouse1.Text = "X " + e.X + " Y " + e.Y;

                    break;

                case 1:

                    this.tB_mouse2.Text = "X " + e.X + " Y " + e.Y;

                    break;

                case 2:

                    this.tB_mouse3.Text = "X " + e.X + " Y " + e.Y;

                    break;

                default:

                    break;

            }

        }

 

        //有鼠标键按下时的事件响应

        private void sdgManager1_MouseDown(object sender, Sdgt.SdgMouseEventArgs e)

        {

            //首先识别鼠标ID

            switch (e.ID)

            {

                case 0:

                    //识别不同按键来改变不同的checkBox值为选中

                    if (e.Button == MouseButtons.Left)

                        this.cb_mouse1L.Checked = true;

                    if (e.Button == MouseButtons.Middle)

                        this.cb_mouse1M.Checked = true;

                    if (e.Button == MouseButtons.Right)

                        this.cb_mouse1R.Checked = true;

                    break;

                case 1:

                    //识别不同按键来改变不同的checkBox值为选中

                    if (e.Button == MouseButtons.Left)

                        this.cb_mouse2L.Checked = true;

                    if (e.Button == MouseButtons.Middle)

                        this.cb_mouse2M.Checked = true;

                    if (e.Button == MouseButtons.Right)

                        this.cb_mouse2R.Checked = true;

                    break;

                case 2:

                    //识别不同按键来改变不同的checkBox值为选中

                    if (e.Button == MouseButtons.Left)

                        this.cb_mouse3L.Checked = true;

                    if (e.Button == MouseButtons.Middle)

                        this.cb_mouse3M.Checked = true;

                    if (e.Button == MouseButtons.Right)

                        this.cb_mouse3R.Checked = true;

                    break;

                default:

                    break;

            }

        }

       

        //有鼠标键松开时的事件响应

        private void sdgManager1_MouseUp(object sender, Sdgt.SdgMouseEventArgs e)

        {

            //首先识别鼠标ID

            switch (e.ID)

            {

                case 0:

                    //识别不同按键来改变不同的checkBox值为取消选中

                    if (e.Button == MouseButtons.Left)

                        this.cb_mouse1L.Checked = false;

                    if (e.Button == MouseButtons.Middle)

                        this.cb_mouse1M.Checked = false;

                    if (e.Button == MouseButtons.Right)

                        this.cb_mouse1R.Checked = false;

                    break;

                case 1:

                    //识别不同按键来改变不同的checkBox值为取消选中

                    if (e.Button == MouseButtons.Left)

                        this.cb_mouse2L.Checked = false;

                    if (e.Button == MouseButtons.Middle)

                        this.cb_mouse2M.Checked = false;

                    if (e.Button == MouseButtons.Right)

                        this.cb_mouse2R.Checked = false;

                    break;

                case 2:

                    //识别不同按键来改变不同的checkBox值为取消选中

                    if (e.Button == MouseButtons.Left)

                        this.cb_mouse3L.Checked = false;

                    if (e.Button == MouseButtons.Middle)

                        this.cb_mouse3M.Checked = false;

                    if (e.Button == MouseButtons.Right)

                        this.cb_mouse3R.Checked = false;

                    break;

                default:

                    break;

            }

        }

 

        private void sdgManager1_MouseWheel(object sender, Sdgt.SdgMouseEventArgs e)

        {

            //首先识别鼠标ID

            switch (e.ID)

            {

                case 0:

                    //progressBar控件没有超出范围时,进行操作

                    if (this.pb_mouse1.Value == this.pb_mouse1.Minimum

                        && e.Delta < 0) break;

                    if (this.pb_mouse1.Value == this.pb_mouse1.Maximum

                        && e.Delta > 0) break;

                    this.pb_mouse1.Value += e.Delta / 120;

                    break;

                case 1:

                    //progressBar控件没有超出范围时,进行操作

                    if (this.pb_mouse2.Value == this.pb_mouse2.Minimum

                        && e.Delta < 0) break;

                    if (this.pb_mouse2.Value == this.pb_mouse2.Maximum

                        && e.Delta > 0) break;

                    this.pb_mouse2.Value += e.Delta / 120;

                    break;

                case 2:

                    //progressBar控件没有超出范围时,进行操作

                    if (this.pb_mouse3.Value == this.pb_mouse3.Minimum

                        && e.Delta < 0) break;

                    if (this.pb_mouse3.Value == this.pb_mouse3.Maximum

                        && e.Delta > 0) break;

                    this.pb_mouse3.Value += e.Delta / 120;

                    break;

                default:

                    break;

            }

        }

    }

}

 

以下为各控件在Form1.Designer.cs中的声明:

        private Sdgt.SdgManager sdgManager1;

        private System.Windows.Forms.Label label2;

        private System.Windows.Forms.Label label1;

        private System.Windows.Forms.TextBox tB_mouse3;

        private System.Windows.Forms.TextBox tB_mouse2;

        private System.Windows.Forms.TextBox tB_mouse1;

        private System.Windows.Forms.Label label3;

        private System.Windows.Forms.CheckBox cb_mouse1R;

        private System.Windows.Forms.CheckBox cb_mouse1M;

        private System.Windows.Forms.CheckBox cb_mouse1L;

        private System.Windows.Forms.CheckBox cb_mouse3R;

        private System.Windows.Forms.CheckBox cb_mouse2R;

        private System.Windows.Forms.CheckBox cb_mouse3M;

        private System.Windows.Forms.CheckBox cb_mouse2M;

        private System.Windows.Forms.CheckBox cb_mouse3L;

        private System.Windows.Forms.CheckBox cb_mouse2L;

        private System.Windows.Forms.ProgressBar pb_mouse1;

        private System.Windows.Forms.ProgressBar pb_mouse3;

        private System.Windows.Forms.ProgressBar pb_mouse2;

 


 

4.3 SDG Toolkit中数据的识别及与Windows标准控件的交互

 

4.4 Single Display Groupware Toolkit控件的使用

       为了不受制于Windows标准控件的局限性,SDG Toolkit为了多鼠标的使用特意开发了一组相关控件,并在示例文件Simple Widget Application中详细演示了相关控件的使用,这里仅以此例来说明具体使用方法。

       SDG Toolkit控件一共包含sdgRadioButton, sdgCheckBox, sdgTrackBar 3个,与名字相近的Windows标准控件用途一致,实现多鼠标控制的方式是为每个控件建立一个动态数组,以鼠标ID为索引,并且在显示上采用了同一控件分区域识别,使用户能知道自己相关控件的状态设置,在设计器层面使用方法与标准Windows控件完全相同。sdgRadioButton, sdgCheckBox控件数据主要利用CheckChanged事件来传递,事件的第二参数即为鼠标ID标识。sdgTrackBar主要利用Scroll事件来传递,鼠标的第二参数为sdgManager事件Sdgt.SdgMouseEventArgs形式,参数含义与4.3节介绍的一致。程序具体代码可在SDG Toolkit官方网站的example中找到,在此不详细列出,运行效果如图4.4所示。有一点要说明的是,到本文结稿之日为止,SDG Toolkit程序最终版本已经出到2.0.1.0,而控件程序的版本仍然只有2.0.0.8,所以,假如要使用其控件,就不得不使用2.0.0.8版的SDG Toolkit程序。

4.4 SDG Toolkit控件使用演示

最后说明,由于本人对C#理解不深,可能写出来的程序有些问题,请指正。

此程序也演示了SDG Toolkit是怎么样通过输入的数据与Windows标准控件进行交互的。程序运行效果,如图4.34.2

阅读全文....

Windows中多指针输入技术的实现与应用(5 利用多鼠标输入框架软件实现)

3 利用多鼠标输入框架软件实现

3.1利用CPNmouse库的实现

       CPNmousesourceforge上的一个开源项目,最先由过滤式鼠标驱动实现的作者 M.Westergaard发起,主要原理就是利用了文献[9]提到方法,并效率较高的实现一个高层的框架级程序,并提供一组API供开发者使用。

       利用CPNmouse开发的优点很明显,因为CPNmouse提供了驱动及高层的API给开发者使用,开发人员可以不考虑很多底层复杂的事情,专注于软件更高层的实现,对于新开发软件想支持多鼠标输入技术的实现比较便利。目前CPNmouse已经支持在     Windows 9xWindows XP下运行,并且利用了CPNmouse的开发人员可以在不改变或很少改变原有软件的情况下,获得将来CPNmouse改进升级后的好处。不得不提的就是CPNmouse作为开源软件,使得软件开发人员在需要的情况下可以对CPNmouse进行优化改良,自主掌握能力较为充分。缺点也是很致命的,因为CPNmouse仅仅提供了一组API,原有的软件要利用就得进行比较大的改变,而且此API的使用并不是太方便,往往可能需要对原有软件的结构进行彻底改变。而且CPNmouse利用的是时分系统来控制鼠标和绘制鼠标指针,这也会带来以前描述过的副作用。比如CPNmouse作者提供了一个示例程序。安装完CPNmouse提供的驱动,运行此程序后,当仅仅接入2个鼠标时,可以在屏幕上为新加入的鼠标绘制出独立的指针,但是当系统鼠标移动,新加入的鼠标将完全由第一次接入的鼠标控制。如附录CC1C2所示。接入第3个鼠标后,系统绘制出新加入的两个独立指针,并且用了左右镜像处理以示区别,但是,很显然的是利用了时分系统,导致当新加入的鼠标同时操作时,会导致混乱,而且,新加入的鼠标都会完全被第一次接入的鼠标所控制,以至于三个鼠标根本没有办法同时控制。如附录CC3C4C5所示。通过以上演示,可以发现CPNmouse不仅是利用了时分系统来控制鼠标和鼠标指针,甚至连鼠标是否是系统鼠标都没有分辨,所以导致新接入的鼠标会被原有的第一个鼠标完全控制。另外,CPNmouse虽然作为开源软件,但是使用的人不是太多,以至于文档资料极少,能够利用的官方文档也是由软件自动生成,实用性一般。我们几乎仅能通过文献[9]来理解此其的原理及使用,这也会成为软件开发的极大阻碍。实际上只有少数软件是利用此技术完成,比如CPNmouse作者自己开发的CPN tools和少部分符合 Mouse-Party的游戏。

 

3.2利用MultiPoint SDK的实现

       微软的MultiPoint SDK来源于其印度实验室技术人员的一次经历,[8]见识到多鼠标输入在教育行业的巨大潜力以后,进行了文献[5]里描述的具体对比实验与研究,并最终决定研发。2007625开始提供1.0版程序下载。按微软官方描述,此软件开发包是帮助软件开发人员开发在一台电脑上用多个鼠标的程序。为的是让开发出来的程序可以让多个学生在一台电脑上进行共享性或竞争性的学习,工作或娱乐。帮助填补印度或其他发展中国家电脑短缺的现象。

       作为微软的SDK开发包,MultiPoint有着较为优秀的运行性能,软件结构设计非常合理,并且继承着微软的传统,那就是官方的文档非常丰富,足以应付一般开发的需求。另外,就微软描述,MultiPoint之所以不叫MultiMouse,是因为将来还会加入其他输入设备的支持,更为难得的是,作为微软的一款产品,微软为了更快的推广它,目前仍然免费提供给软件开发人员使用。但是,MultiPoint SDK毕竟是1.0版本,技术上还有很多不成熟的地方,比如只支持USB指针输入设备,意味之大部分人使用的PS/2接口鼠标不能使用,其次是使用MultiPoint SDK设计出来的软件,比如最大化,最小化,关闭,卷动条等系统功能都不能通过指针设备来成功控制,另外当多个使用了MultiPoint的软件同时运行,它们分享同一个事件管理,意味着同时运行两个使用MultiPoint的软件会极度的混乱。还有因为MultiPoint技术是出自微软之手,这自然是个商业软件,所以软件开发人员难以知道此软件开发包的具体实现原理,也不可能对该软件开发包本身进行进一步的优化,只能是微软提供什么就用什么。而且软件开发人员的开发工具也不得不被绑在微软的专署工具Visual Studio .Net系列上面,最严重的是,甚至开发人员只能使用微软设计的C#语言进行开发工作。另外,虽然MultiPoint免费,但是却还是需要购买微软的产品,况且还不能知道微软什么停止MultiPoint的免费,开发人员在MultiPoint开发的产品自己也不能拥有全部的权利。最后,因为MultiPoint SDK是个非常新的SDK,所以除了官方的文档以外,还没有太多额外可参考的文档资料,成熟的利用此技术开发的产品也还没有。这些都是MultiPoint作为微软产品的普及障碍,也会成为其使用的难以避免的烦恼之处。

 

3.3利用Single Display Groupware Toolkit的实现

    Single Display Groupware Toolkit 加拿大calgary大学GroupLabE.Tse, S.Greenberg 开发,[10][11][12]是在前面介绍过的RawInput技术上建立的一个设计良好的框架程序,专门为开发SDG程序而设计。

       SDG Toolkit特别设计了一个.Net兼容的事件管理器,使得在Visual Studio .Net下设计使用非常简便,在设计普通程序的时候,就可以利用带有鼠标ID标志的事件,很简便的融入以前设计或新设计的程序当中。如图3.1所示。

3.1 SDG Toolkit VS .Net 2005 中增加的事件

 

    此工具因为和.Net技术兼容,可以利用的开发语言也比一般的软件开发库(包)要更为丰富,Visual Basic, Visual C++, Visual C#,都可利用此技术。而且SDG Toolkit不仅支持串口,PS/2USB各种接口的鼠标,目前也已经加入了对于多个键盘的支持,因为SDG Toolkit作者考虑到在多人使用触摸屏时使用者往往站在不同方向,甚至加入了指针的方向特性。[11]也可以通过颜色,或者指针右下的文字在屏幕上来标识和识别不同的指针。如下图3.2所示。

3.2 SDG 绘画板示意图[11]

 

SDG Toolkit还特意为多指针操作的需要,独立制作了一组交互控件,供开发者使用,还提供了一组自定义控件的模板以方便用户自定义控件的开发。另外,很重要的一点是SDG Toolkit还是开源软件,意味着开发人员在需要的情况下可以对SDG Toolkit进行特别的优化,也可以通过源代码了解到SDG Toolkit的工作原理,以便更好的优化自己设计的软件,并且SDG Toolkit相对于CPNmouse来说官方的文档支持非常丰富,包括了大量的资料及实例代码,足够日常的程序设计需要。但是此工具不是没有缺点,因为建立在带IDRawInput系统下,所以有着RawInput固有的缺点:只能在Windows XP/NT中运用。而且因为软件为了和.Net兼容,导致了此软件虽然开源,却有着和微软固有软件的一些缺点,比如要利用此技术,只能在Visual Studio .Net环境下进行开发,导致开

发人员要利用此技术就必须得购买Visual Studio .Net。另外,因为程序设计上的缺陷,在运行利用SDG Toolkit设计的软件时,在拔下新加入的鼠标时,鼠标指针并不会消失。

 

3.4 Single Display Groupware Toolkit的实现原理

         在此节,详细介绍了SDG Toolkit的实现原理。我还是主要从不同鼠标输入数据的识别和各鼠标独立指针的绘制两方面来分析SDG Toolkit的实现。首先,SDG Toolkit的主要框架结构如下页图3.3所示:

                                                        3.3 SDG Toolkit主要框架结构图[12]

 

       要分析SDG Toolkit的原理不能不先解释它封装数据的几个主要类。因为我这里主要是讲多指针输入的问题,所以没有提到SDG Toolkit的键盘输入部分。

       从图3.3中可以看到,SDG Toolkit最主要的部分就是SDG Manager类,在这个类中管理着各个事件的触发以及鼠标指针的绘制。这个类中包含一个类型为MouseCollectionMice成员变量,储存着现在系统中所有鼠标的信息,MouseCollection实际上是一个Mouse类的集合,而Mouse类中主要封装各鼠标的输入信息并实际完成鼠标的绘制工作,对于鼠标输入来讲,这是非常重要的一个类,它的成员变量,Xabs,Yabs就是各鼠标所在位置的X,Y坐标,X,Y表示的是由RelativeTo指定的目前各鼠标坐标是相对于窗口的坐标,Button指示了哪个键被按下或松开。SDG Toolkit.Net兼容且是利用事件触发的传统Windows方式来识别输入的,事件的信息主要被封装在SdgMouseEventArgs类中。每个事件触发时,事件处理函数的参数都是一个SdgMouseEventArgs类变量。其中SdgMouseAttached事件在有新鼠标接入时触发,SdgMouseMove事件在有鼠标移动时触发,SdgMouseDown事件在有鼠标按键按下时触发,SdgMouseUp事件在有鼠标键松开时触发,SdgMouseWheel事件在鼠标中间滚轮移动时触发。SdgMouseEventArgs类中的ID属性用来识别不同的鼠标,Button属性用来识别鼠标按下的是哪个键,Delta属性用来识别鼠标中间滚轮移动的距离,X,Y属性表示的是事件触发时鼠标所在的坐标。

 

3.4.1 Single Display Groupware Toolkit各鼠标输入数据的识别原理

    SDG Toolkit中具体的各鼠标输入识别是通过在程序回调函数中响应WM_INPUT消息,并利用GetRawInputData函数实现。在回调函数中,首先,SDG Toolkit响应了WM_CREATE消息,初始化了两个RAWINPUTDEVICE结构,并且调用了RegisterRawInputDevices函数对RawInput设备进行注册,为后来调用GetRawInputData函数做准备。在最新版本2.0.1.0中,源代码如下:

 

switch((Win32.WindowMessage) m.Msg)

{

       case Win32.WindowMessage.WM_CREATE:

       {

              Win32.RAWINPUTDEVICE[] rid = new Win32.RAWINPUTDEVICE[2];

              rid[0].usUsagePage = 1;

              rid[0].usUsage = 2;

              rid[0].dwFlags = 0;

              rid[0].hwndTarget = m.HWnd;

              rid[1].usUsagePage = 1;

              rid[1].usUsage = 6;

              rid[1].dwFlags = 0;

              rid[1].hwndTarget = m.HWnd;

              bool fResult = Win32.RegisterRawInputDevices(rid,2,Marshal.SizeOf(rid[0]));

              Debug.Assert(fResult == true, "Failed to Register Raw Input Devices");

       }    

       break;

}

 

       WM_CREATE的响应程序中,为GetRawInputData调用所做的准备工作与在介绍利用RawInput实现多鼠标输入时所讲的一样,即先注册RawInput设备。只不过SDG Toolkit将初始化放在了响应WM_CREATE时进行,另外,SDG Toolkit在文件Win32.cs中对各Windows API函数进行了包装,将其都放在了Win32名字空间下,意义和流程与原来的完全一样。

       注册RawInput设备后,再响应WM_INPUT消息,调用GetRawInputData函数,实现不同鼠标输入数据的识别。首先看各鼠标坐标的识别,源代码如下:

 

switch((Win32.WindowMessage) m.Msg)

{

       case Win32.WindowMessage.WM_INPUT:

       {

       Win32.RAWINPUT ri = new Win32.RAWINPUT();

       int cbri = Marshal.SizeOf(ri);

       Win32.GetRawInputData((Win32.RAWINPUT*) ((void*) m.LParam), (int) Win32.GetRawInputDataCommand.RID_INPUT, ref ri, ref cbri, Marshal.SizeOf(ri.header));

       if (ri.header.dwType == (int) Win32.RawInputDeviceType.RIM_TYPEMOUSE)

              {

                     bool foundMouse = false;

                     for (int i = 0; i < mice.Count; ++i)

                     {

                     if (new IntPtr(ri.header.hDevice) == mice[i].impl.m_hDevice)

                     {

                     ArrayList a = (ArrayList) m_RawInputList[i];

                     if (a.Count < m_RawInputListSize && 0 == ri.mouse.mouseUnion.usButtonData && 0 == ri.mouse.mouseUnion.usButtonFlags)

                     {

                     a.Add(ri);

                     }

                     else

                     {

                     m_MoveEventCount++;

                     for (int j=0; j< a.Count; j++)

                     {

                            Win32.RAWINPUT ri2 = (Win32.RAWINPUT) a[j];

                            ri.mouse.lLastX += ri2.mouse.lLastX;

                            ri.mouse.lLastY += ri2.mouse.lLastY;

                     }

              }

       }

}

 

       同样的,这里除了SDK ToolkitWindows API进行了一些包装外,与在介绍利用RawInput实现多鼠标输入时所讲的调用方法也基本一样,也是首先调用GetRawInputData获得输入数据,并通过dwType == RIM_TYPEMOUSE的比较识别是否为鼠标输入。最大的不同就是因为SDK Toolkit是利用事件触发来让用户使用数据,所以每次各鼠标坐标改变以后,SDK Toolkit都触发了一次SdgMouseMove事件,以让用户处理刚刚得到的数据。事件触发方式如下:

 

SdgMouseEventArgs smeaAllButtons;

object mySender = this;

OnSdgMouseMove(mySender, smeaAllButtons);

mice[i].OnMouseMove(smeaAllButtons);

 

       即首先定义了一个事件参数变量来表示鼠标按键,在SdgMouseMove事件中,都默认为无按键,然后调用SDG Toolkit内部的OnSdgMouseMove,函数首先处理事件的参数,然后完成实际的事件触发,然后利用Mice各成员函数改变储存的各鼠标状态的数据,以保存下刚获取的数据。这一点在鼠标按键的事件触发上也类似。

       对于鼠标按键的识别,SDG Toolkit处理的非常简单,源代码如下:

 

switch (ri.mouse.mouseUnion.usButtonData)

{

       case (int) Win32.RI_MOUSE.RI_MOUSE_LEFT_BUTTON_UP:

       case (int) Win32.RI_MOUSE.RI_MOUSE_MIDDLE_BUTTON_UP:

       case (int) Win32.RI_MOUSE.RI_MOUSE_RIGHT_BUTTON_UP:

       case (int) Win32.RI_MOUSE.RI_MOUSE_BUTTON_4_UP:

       case (int) Win32.RI_MOUSE.RI_MOUSE_BUTTON_5_UP:  

       OnSdgMouseUp(mySender, smea);

       fMouseUp = true;

       mice[i].OnMouseUp(smea);

       mice[i].setButtonState(SmeaToState(ri.mouse.mouseUnion.usButtonData), false);

       break;

       case (int) Win32.RI_MOUSE.RI_MOUSE_LEFT_BUTTON_DOWN:

       case (int) Win32.RI_MOUSE.RI_MOUSE_MIDDLE_BUTTON_DOWN:

       case (int) Win32.RI_MOUSE.RI_MOUSE_RIGHT_BUTTON_DOWN:

       case (int) Win32.RI_MOUSE.RI_MOUSE_BUTTON_4_DOWN:

       case (int) Win32.RI_MOUSE.RI_MOUSE_BUTTON_5_DOWN: 

       OnSdgMouseDown(mySender, smea);

       mice[i].OnMouseDown(smea);

       mice[i].setButtonState(SmeaToState(ri.mouse.mouseUnion.usButtonData), true);

       fMouseDown = true;

       break;

}

 

       SDG Toolkit中,调用GetRawInputData函数,并处理完鼠标的移动事件后,直接检测RawInput结构mouse.usButtonData的内容,发现有按键按下或松开,直接用相应的事件触发程序触发事件,然后用Mouse类的setButtonState函数来改变储存的各鼠标目前的数据,以保存目前的状态。这里有两个bool类型的成员变量fMouseUp fMouseDown ,原来它们都初始化为false,当鼠标有键按下后将fMouseDown 设为true,以表示有鼠标键按下,当鼠标键松开时将fMouseUp 设为true,以表示有鼠标键松开。 综上所述,SDG Toolkit各鼠标输入数据的识别原理利用的就是第2章所介绍的RawInput技术,仅仅是经过了一层包装,并将其带入了C#中,但是SDG Toolkit对数据的处理很有技术性,它自创了一套的带ID的事件系统,彻底解决了时分控制系统鼠标指针所带来的副作用,使得各鼠标的操作完全的不冲突,这也是这个技术最大的优点所在。

 

3.4.2 Single Display Groupware Toolkit鼠标指针绘制原理

       SDG Toolkit中,所有鼠标的信息都保存在Mouse类集合变量Mice中,Mice中每个成员都代表着一个鼠标对象,虽然Mice内每个数据状态的改变都由SdgManager控制,但由图3.3可以看到实际指针的绘制都由各Mice变量自己独立控制,也就是说,Mouse类内部在每次鼠标状态改变时,都进行了相应的内部处理。其中,最主要的就是在鼠标坐标改变时完成鼠标指针的绘制。

       要很好的绘制多个鼠标指针并且不损失程序的效率很难做到,前面介绍过一种绘制方法,每次鼠标的绘制都需要刷新程序,实际上大大的降低了程序的效率。在这里SDG Toolkit很有技巧性的完成了鼠标绘制工作,它为每个鼠标创建一个合适大小的顶层的透明窗口,并在此窗口中绘制出鼠标指针,每次移动鼠标的时候,SDG Toolkit实际移动的是这个透明的窗口,这样,每次鼠标移动的时候刷新的也就是这个很小的透明窗口而已,将对原有程序的影响降到可以接受的范围。具体做法是在每个Mouse类中定义了一个从Form继承过来的internal MouseImpl ,将其初始化为没有菜单,没有工具栏,没有边框,透明背景的窗口,并且此窗口的大小仅仅是正好可以绘制出一个鼠标指针。源代码如下:

 

public class Mouse

{

       internal class MouseImpl : Form

       {

              public void InitializeMouse()

              {

                     BackColor = Color.AliceBlue;

                     TransparencyKey = Color.AliceBlue;

                     ForeColor = Color.Black;

                     Cursor = null;

                     TopMost = true;

                     Capture = false;

                     FormBorderStyle = FormBorderStyle.None;

                     ShowInTaskbar = false;

                     m_Cursor = Cursors.Arrow;

                     Size = m_Cursor.Size;

                     Show();

                     Location = m_Location;

                     ResizeWindow();

                     Refresh();

                     Enabled = false;

                     m_BoundsTimer = new System.Threading.Timer (new                             System.Threading.TimerCallback (BoundsTimer_Tick), null, 0, 500);

              }

       }

}

 

       在以上源代码中,Location为窗口最后绘制的绘制位置,这个位置由Mouse类的成员变量m_Location决定,其实,这个M_Location就是鼠标指针应该在的点,通过这种定位窗口的方式来达到定位鼠标指针的效果。因此,利用此方法,每次鼠标指针的移动,只需要将窗口的Location属性值等于鼠标实际所在的位置m_Location值,然后刷新此窗口就可以完成了。在上面的代码中,ResizeWindow()函数是用来调整窗口大小,以适应鼠标指针大小或形状的改变,当鼠标指针属性发生改变时,也只需要调用此ResizeWindow()函数并刷新窗口就可以完成鼠标指针的改变过程。总之,每次此窗口的刷新,就完成了一次鼠标指针的绘制。而最后,SDK Toolkit利用一个500毫秒的定时器让程序每500毫秒检验窗口坐标是否超出边界,这样的安排纯粹是为了安全起见,因为每次鼠标坐标的更改其实都已经检查了边界,详细情况如下。

       如下源代码演示了当鼠标指针坐标改变时,绘制鼠标指针的过程。

 

public int X

{

       get

       {

              return m_Location.X;                             

       }

       set

       {

              if (CheckBounds(value, m_Location.Y)) 

              {

                     m_Location.X = value;

                     if (m_fVisible)

                     {

                            Location = m_Location;

                            Refresh();

                     }

              }

       }

}

public int Y

{

       get

       {

              return m_Location.Y;

       }

       set

       {

              if (CheckBounds(m_Location.X, value)) 

              {

                     m_Location.Y = value;

                     if (m_fVisible)

                     {

                            Location = m_Location;

                            Refresh();

                     }

              }

       }

}

 

       在这里,除了定位并刷新绘制鼠标的窗口外,还做了两件事,就是用CheckBounds函数检查此窗口有没有超出边界,另外就是检查此鼠标指针是否允许显示。

       综上所述,SDG Toolkit的多鼠标指针绘制原理非常有技巧性,利用子窗口绘制代替鼠标指针绘制的想法非常巧妙,利用这种技术,节省了每次鼠标指针移动时全屏幕刷新的需要,让鼠标指针的绘制更加有效率。因为我对C#代码的理解有限,假如有什么错误请指正,谢谢。

阅读全文....

Windows中多指针输入技术的实现与应用(4多鼠标输入的底层实现)

Windows中多指针输入技术的实现与应用(4多鼠标输入的底层实现)

湖南大学 谢祁衡

2 多鼠标输入的底层实现

2.1 通过开发过滤式鼠标驱动的实现

此技术最先由M.Westergaard[9]中提出,此技术主要思路为从驱动程序层面解决Windows本身不能识别多个指针设备的问题。通过设计单独的一套鼠标驱动程序和 API完成硬件与用户层应用程序的通信。主要的技术可以用图2.1来描述。

注:图左边为过滤式鼠标驱动程序的工作方式,右边为通常程序的运行方式,它们可以共存。

2.1 过滤式鼠标驱动实现原理图[9]

 

此驱动程序是通过过滤鼠标的输入,所有的鼠标输入信息都在通过此驱动程序的时候加入 ID信息,使得系统辨识不同鼠标,就像从各种不同的独立的驱动程序中接收数据一样。低层API主管在各驱动程序之间的通信和处理上一个层面的反馈。高层的API允许用户层的应用程序在一个有效的途径上接受鼠标的输入。

这个技术解决了支持多鼠标输入的原理问题,最主要的优点是开发人员拥有对鼠标输入最完全的控制,也可以进行最彻底的优化,不受他人开发程序效率的限制。并且当低层系统有改变,比如当Windows决定从低层开始支持多鼠标输入的时候,上层的软件可以几乎不改变就正常使用。但是缺点也很明显,进行核心编程和驱动程序的开发都

太过于复杂且难以控制。另外,虽然对于鼠标有着较为通用的驱动程序,但是对于不同的指针设备还必须提供不同的驱动程序,所以对于新的指针设备的识别也将会成为难点,使得此技术仅仅只能被框架级程序的专业开发所利用,比如Octopus frameworkCPNmouse[9]而且仅仅对于鼠标等有限的设备有着很好的支持。

 

2.2用户层利用RawInput的实现

       微软在Windows XP中在RawInput API中加入了鼠标的ID信息数据,因此,使得在Windows XP中通过直接分辨RawInput API中的鼠标ID信息非常方便地来识别不同的鼠标输入。

       虽然能够识别不同数据,但是对于不同鼠标指针的绘制完全要由编程人员控制。此技术有着对鼠标输入比较完全的控制,识别不同鼠标输入的数据较为容易,且仅仅通过微软的底层程序,因此效率很高。而且对于新加入的指针设备能有较快的反应,可以不考虑驱动程序就对新的指针设备提供直接支持。值得一提的是,微软对于RawInput提供了文档支持,且网上实现此技术的相关资料较为丰富,这点对于开发工作来说,必不可少。缺点是用户层上的开发工作并不是很容易,特别是很好的多鼠标输入数据处理方式和多鼠标指针的绘制技术比较难以掌握,而利用较为容易实现的时分方式会带来一些副作用,比如当多个虚拟指针同时拖动窗口的时候,将导致未定义的行为。[5] 而且,此技术只能在Windows 2000/XP中运用,Windows 9x 下已确认无法使用。但是因为此方法的优点明显,缺点却难以用其他方法弥补,使得很多各层次的程序都是利用此技术完成,比如SDG ToolkitMAME:Analog+,以及Reflexive Entertainment, Inc的大部分符合Mouse-Party的游戏。

 

2.3 RawInput实现的原理

         在此节,分各鼠标输入数据的识别和各鼠标独立指针的绘制两方面详细介绍了RawInput实现的原理。

2.3.1 RawInput各鼠标输入数据的识别原理

       虽然Windows并不直接支持多指针输入设备,但是通过原始的输入数据识别还是可以分辨不同的鼠标输入,在Windows XP中提供原始输入数据处理的方法是通过注册使用RawInput设备以获得RAWINPUT结构类型数据的方式。具体流程图如下:

开始

GetRawInputDeviceList

数获取

RawInput

设备数量

RegisterRawInputDevices

函数注册

RawInput

设备

调用

GetRawInputData

函数

获得

pData

大小

调用

GetRawInputData

函数

获得

RAWINPUT

数据

header.dwType

==

RIM_TYPEMOUSE

保存

RawInput

中的

data.mouse

数据

检查

data.mouse

据中的坐标边界

重新以新坐标

定位鼠标指针

结束

T

F

第一步

第二步

第三步

第四步

第五步

                                                                 2.2 RawInput各鼠标输入数据识别流程图

 

       第一步,用GetRawInputDeviceList函数获取RawInput设备数量。GetRawInputDeviceList的函数原型如下:

 

UINT GetRawInputDeviceList(

    PRAWINPUTDEVICELIST pRawInputDeviceList,

    PUINT puiNumDevices,

    UINT cbSize

);

 

       当第一参数为NULL的时候,第二参数为输出,输出的是RawInput设备数量。第三参数一般为RAWINPUTDEVICELIST的大小。所以可以以下方式调用以获得RawInput设备数量。

 

UINT nDevices;

GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST))

 

       调用以后,nDevices就是RawInput设备的数量。但是假如需要确定的鼠标RawInput设备数量,就还需要再一次以第一参数为输出调用GetRawInputDeviceList函数,这里首先需要了解RAWINPUTDEVICELIST结构。RAWINPUTDEVICELIST结构定义如下:

 

typedef struct tagRAWINPUTDEVICELIST {

    HANDLE hDevice;

    DWORD dwType;

} RAWINPUTDEVICELIST, *PRAWINPUTDEVICELIST;

 

       当在第二参数中给定RawInput设备的数量为输入时,第一参数就可以输出RAWINPUTDEVICELIST结构类型的输出。调用方式如下:

 

PRAWINPUTDEVICELIST pRawInputDeviceList;

pRawInputDeviceList = (RAWINPUTDEVICELIST *) calloc( nDevices,                           sizeof(RAWINPUTDEVICELIST));

GetRawInputDeviceList(pRawInputDeviceList,&nDevices,               sizeof(RAWINPUTDEVICELIST));

 

       这里的nDevices必须已经经过上一步的GetRawInputDeviceList调用,值已经是RawInput设备的数量。这时可以通过判断pRawInputDeviceList各成员dwType值是否等于 RIM_TYPEMOUSE来判断此RawInput设备是否为鼠标的输入。

       第二步:RegisterRawInputDevices函数注册RawInput设备。这一步最需要注意,在Windows中一般情况下并不会让用户获取原始输入数据RawInput,所以使用GetRawInputData函数前必须先注册RawInput设备。注册RawInput的步骤就是通过调用RegisterRawInputDevices函数完成的。RegisterRawInputDevices函数原型如下:

 

BOOL RegisterRawInputDevices(

       PCRAWINPUTDEVICE pRawInputDevices,

       UINT uiNumDevices,

       UINT cbSize

);

 

       第一参数为一个RAWINPUTDEVICE结构的指针,第二参数给定需要注册RawInput设备的数量,第三参数为第一参数的大小。在这里要知道怎么注册又需要先了解RAWINPUTDEVICE结构,其结构定义如下:

 

typedef struct tagRAWINPUTDEVICE {

    USHORT usUsagePage;

    USHORT usUsage;

    DWORD dwFlags;

    HWND hwndTarget;

} RAWINPUTDEVICE, *PRAWINPUTDEVICE, *LPRAWINPUTDEVICE;

 

       dwFlags设为NULL时,Windows追随输入焦点接受RawInput数据。而第四参数就是指定的输入焦点,假如第四参数为NULL,那么Windows接受RawInput数据的窗口就是目前的键盘焦点窗口。这两个参数设定就是一般时所需要的,比如以下的方式就注册了一个RawInput设备,并且当注册失败时弹出对话框提示:

 

RAWINPUTDEVICE Raw[1];

Rid[0].usUsagePage = 0x01;

Rid[0].usUsage = 0x02;

Rid[0].dwFlags = 0;

Rid[0].hwndTarget = NULL;

if (RegisterRawInputDevices(Rid, 1, sizeof (Rid [0])) == FALSE) {

       MessageBox(hWnd,"RawInput Register Error.","RawInput init failed!",MB_OK);

}

 

       这里需要注意的是并不是有多少个鼠标输入就需要注册多少个RawInput设备,一般来说,注册一个RawInput设备处理目前焦点的原始输入数据就足够了,同样可以获得所有的鼠标原始输入数据。除非想对不同焦点的原始输入数据进行处理,才需要注册多个usUsagePage usUsage 不同的RawInput设备。

       第三步:调用GetRawInputData函数获得pData大小。这里要说明的是GetRawInputData一般要在响应WM_INPUT消息时调用,不能随时调用。GetRawInputData函数原型如下:

 

UINT GetRawInputData(

    HRAWINPUT hRawInput,

    UINT uiCommand,

    LPVOID pData,

    PUINT pcbSize,

    UINT cbSizeHeader

);

 

       GetRawInputData第一参数为一个RAWINPUT结构的句柄,来自WM_INPUT消息的lParam 参数。要说明的是GetRawInputData并不是一个随时调用以检测有无RawInput设备输入的函数,实际上是一个对WM_INPUT消息的lParam 参数的解析函数,主要用途是从WM_INPUT消息的lParam 参数中提取出有用的信息,并加以组织成一个RAWINPUT结构输出,当然,由于WM_INPUT消息的lParam 参数传进来的是个句柄,实际上解析的是其代表的RAWINPUT结构数据。这也是GetRawInputData一般在响应WM_INPUT消息时调用的原因。第二参数为RID_INPUT时,GetRawInputData才输出RawInput信息。第三参数用以输出最后的RAWINPUT结构数据,当为NULL时在第四参数输出本参数大小。第四参数用以制定第三参数大小,第五个参数在只需要调用RAWINPUT结构输出的时候一般可以设为sizeof(RAWINPUTHEADER)。因为我们一开始不知道GetRawInputData第三参数的大小,必须以NULL为第三参数先调用一次GetRawInputData以从第四参数获得第三参数pData大小。调用方式如下:

 

UINT dwSize;

case WM_INPUT:

{

       GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize,

                                                 sizeof(RAWINPUTHEADER));

...

 

       这样调用GetRawInputData函数后,dwSize就指示了其第三参数pData的大小。为下一次调用以输出RAWINPUT数据做准备。

       第四步:调用GetRawInputData函数获得RAWINPUT数据。在获得GetRawInputData第三参数pData大小后就可以直接调用GetRawInputData获得RAWINPUT数据了。这里要说明的是第三参数为一个空指针,调用前先用刚才得到的dwSize参数分配足够的空间,并且调用完后必须强制转换才能得到想要的结果。实际调用源代码如下:

 

LPVOID lpb;

RAWINPUT *raw;

case WM_INPUT:

{

...

lpb = malloc(sizeof(LPVOID) * dwSize);

if (lpb == NULL)

{

       return 0;

}

GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize,

                             sizeof(RAWINPUTHEADER));

raw = (RAWINPUT*)lpb;

}

 

       第五步:这里我们已经可以通过调用以上四步得到的RAWINPUT数据来获得想要的数据了,这里以指针raw表示,首先看RAWINPUT结构的定义:

 

typedef struct tagRAWINPUT {

    RAWINPUTHEADER    header;

    union {

             RAWMOUSE    mouse;

             RAWKEYBOARD keyboard;

             RAWHID      hid;

            } data;

} RAWINPUT, *PRAWINPUT; *LPRAWINPUT;

 

       首先我们可以通过检测header是否等于RIM_TYPEMOUSE,了解是否这次RAWINPUT输入得到的是否是鼠标输入数据,只有等于时想要读取的是RAWINPUT结构才是鼠标的输入数据。这时我们才可以通过读取RAWMOUSE结构数据获得鼠标的具体输入信息,RAWMOUSE结构又很复杂,其本身又是RAWINPUT结构中定义的一个名为data的联合。首先看RAWMOUSE结构的定义:

 

typedef struct tagRAWMOUSE {

  USHORT    usFlags;

  union {

         ULONG    ulButtons;

             struct {

                       USHORT usButtonFlags;

                       USHORT usButtonData;

                       };

  };

  ULONG ulRawButtons;

  LONG  lLastX;

  LONG  lLastY;

  ULONG ulExtraInformation;

} RAWMOUSE, *PRAWMOUSE, *LPRAWMOUSE;

 

       其中主要用到的是以下几个数据,usFlags是用来指定鼠标状态的,usButtonFlags指示了按键状况,只需要通过与以下值的对比就可以知道有哪些按键按下与松开:

 

RI_MOUSE_LEFT_BUTTON_DOWN  左键按下

RI_MOUSE_LEFT_BUTTON_UP 左键松开

RI_MOUSE_MIDDLE_BUTTON_DOWN 中键按下

RI_MOUSE_MIDDLE_BUTTON_UP 中键松开

RI_MOUSE_RIGHT_BUTTON_DOWN 右键按下

RI_MOUSE_RIGHT_BUTTON_UP 右键松开

 

       lLastXlLastY指示了鼠标的移动,其值的含义由usFlags决定,当usFlags等于MOUSE_MOVE_RELATIVE时,lLastXlLastY值表示鼠标相对于上一点的移动,当usFlags等于MOUSE_MOVE_ABSOLUTE时,lLastXlLast的值表示鼠标的绝对移动,大概含义等于相对于某固定点的移动。要注意的是,无论是那种方式,lLastXlLast都指示的是一个相对移动的数据,以屏幕坐标轴来看,当向上移动lLastX为负,向下移动lLastX为正,向左移动lLastY为负,向右移动lLastY为正,其值大小表示的是偏差。实际要获得鼠标的绝对坐标还需要经过一定处理,代码如下:

 

long X;

long Y;

if (raw->header.dwType == RIM_TYPEMOUSE)

{

       raw->data.mouse.usFlags = MOUSE_MOVE_ABSOLUTE;

    X += raw->data.mouse.lLastX;

    Y += raw->data.mouse.lLastY;

}

 

       通过这样的调用,X,Y才是想要的鼠标坐标。另外,得到坐标后应该做边界检查,以鼠标指针在屏幕边界后X,Y超出有意义的坐标值或为负值。而且,在大部分情况下,当高速移动鼠标时,系统的消息处理会跟不上鼠标的移动,在处理完坐标后应该以此坐标重定位鼠标指针,以防消息丢失导致的实际坐标与指针的不一致。

       在以下的程序中,详细演示了利用RawInput获得各鼠标输入数据并加以处理显示在屏幕上的过程。

 

RawInput各鼠标输入数据识别测试程序具体代码(以下代码在Visual studio .NET 2005 上编译通过)

#include "stdafx.h"

#include <stdlib.h>

#include <string.h>

 

#define _WIN32_WINNT 0x0501   //定义此程序为Windows XP程序

#include <windows.h>

 

char mousemessage[256];          // 第一个字符串缓存主要储存原始设备输入信息

char mousemessage2[256]; // 第二个字符串缓存储存经过分析处理的输入信息

char rawinputdevices[256]; // 输入设备数量的字符串缓存

long X;   //鼠标的X坐标值

long Y;   //鼠标的Y坐标值

 

char mousemessage[256];          // 第一个字符串缓存主要储存原始设备输入信息

char mousemessage2[256]; // 第二个字符串缓存储存经过分析处理的输入信息

char rawinputdevices[256]; // 输入设备数量的字符串缓存

long X;   //鼠标的X坐标值

long Y;   //鼠标的Y坐标值

 

//RawInput初始化函数

void InitRawInput(HWND hwnd) 

{

       RAWINPUTDEVICE Rid[1]; //分配RawInput设备的空间

 

       UINT nDevices;          //输入设备的数量

       //获得输入设备的数量

       GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST));

       PRAWINPUTDEVICELIST pRawInputDeviceList;// = new RAWINPUTDEVICELIST[nDevices];

       pRawInputDeviceList=(RAWINPUTDEVICELIST *)calloc(nDevices, sizeof(RAWINPUTDEVICELIST));

       GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST));

       int nMouseNumber = 0;

       for(int i=0; i < nDevices; ++i)

       {

              if(pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE)

              {

                     ++nMouseNumber;    

              }

       }

 

       //将输入设备数量写入rawinputdevices缓存

       wsprintf(rawinputdevices,"Number of raw input devices: %i/n"

              "Number of raw input mouse: %i/n", nDevices,nMouseNumber);

       free(pRawInputDeviceList);

       //初始化第1RawInput设备

       Rid[0].usUsagePage = 0x01;

       Rid[0].usUsage = 0x02;

       Rid[0].dwFlags = 0;

       Rid[0].hwndTarget = NULL;

 

       //注册RawInput设备

       if (RegisterRawInputDevices(Rid, 1, sizeof (Rid [0])) == FALSE) {

              MessageBox(hwnd,"RawInput Register Error.","RawInput init failed!",MB_OK);

       }

       //初始化X,Y坐标信息

       POINT pt;

       GetCursorPos(&pt);

       X = pt.x;

       Y = pt.y;

       return ;

}

 

//检查边界的函数

void CheckBound(HDC hdc,long &x,long &y)

{

       if(x<0)

              x = 0;

       if(y<0)

              y = 0;

       //最大值由GetDeviceCaps函数获取以适应不同系统设置

       if(x>(long)GetDeviceCaps(hdc,HORZRES))

              x = (long)GetDeviceCaps(hdc,HORZRES);

       if(y>(long)GetDeviceCaps(hdc,VERTRES))

              y = (long)GetDeviceCaps(hdc,VERTRES);

}

 

//定义回调函数

LRESULT CALLBACK

MainWndProc (HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

{

       static HWND   hwndButton = 0;

    HDC           hdc;            

    PAINTSTRUCT   ps;

    RECT          rc;             

       LPVOID lpb;

       UINT dwSize;

       RAWINPUT *raw;

 

    switch (nMsg)

    {

              case WM_DESTROY:

                     PostQuitMessage (0);

                     return 0;

                     break;

             

              //响应WM_PAINT消息以绘制窗口,输出信息

              case WM_PAINT:

                     hdc = BeginPaint(hwnd, &ps);

 

                     //输出信息

                     GetClientRect(hwnd, &rc);

                     DrawText(hdc, mousemessage, strlen(mousemessage), &rc, DT_CENTER);

                     OffsetRect(&rc,0,150);

                     DrawText(hdc, rawinputdevices, strlen(rawinputdevices), &rc, DT_CENTER);

                     OffsetRect(&rc,0,40);

                     DrawText(hdc, mousemessage2, strlen(mousemessage2), &rc, DT_CENTER);

                     EndPaint(hwnd, &ps);

           return 0;

           break;

 

              //响应WM_INPUT消息为本程序的主要部分

              case WM_INPUT:

              {

                     GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize,

                                                 sizeof(RAWINPUTHEADER));

                     lpb = malloc(sizeof(LPVOID) * dwSize);

                     if (lpb == NULL)

                     {

                            return 0;

                     }

                     GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize,

                             sizeof(RAWINPUTHEADER));

                     raw = (RAWINPUT*)lpb;

                     if (raw->header.dwType == RIM_TYPEMOUSE) //判断是否为鼠标信息

                     {

                            //将鼠标的输入信息写入缓存mousemessage

                            wsprintf(mousemessage,"Mouse:hDevice %d /n usFlags=%04x /nulButtons=%04x /nusButtonFlags=%04x /nusButtonData=%04x /nulRawButtons=%04x /nlLastX=%ld /nlLastY=%ld /nulExtraInformation=%04x/n",                             

                                   raw->header.hDevice,

                                   raw->data.mouse.usFlags,

                                   raw->data.mouse.ulButtons,

                                   raw->data.mouse.usButtonFlags,

                                   raw->data.mouse.usButtonData,

                                   raw->data.mouse.ulRawButtons,

                                   raw->data.mouse.lLastX,

                                   raw->data.mouse.lLastY,

                                   raw->data.mouse.ulExtraInformation);

                           

                            //解析RAWMOUSE数据

                            raw->data.mouse.usFlags = MOUSE_MOVE_ABSOLUTE;

                         X += raw->data.mouse.lLastX;

                         Y += raw->data.mouse.lLastY;

                           

                            //检查边界,以防坐标为负或超过屏幕容许最大值

                            hdc = BeginPaint(hwnd, &ps);

                            CheckBound(hdc,X,Y);

                            EndPaint(hwnd, &ps);

                            //重新以坐标定位鼠标指针,以防系统忙导致的消息丢失

                            ::SetCursorPos(X,Y);

                           

                            wsprintf(mousemessage2,"X: %ld,Y: %ld/n",X,Y);

 

                            switch(raw->data.mouse.usButtonFlags)

                            {

                            case RI_MOUSE_LEFT_BUTTON_DOWN:

                                   strcat(mousemessage2,"Left button down/n");

                                   break;

                            case RI_MOUSE_LEFT_BUTTON_UP:

                                   strcat(mousemessage2,"Left button up/n");

                                   break;

                            case RI_MOUSE_MIDDLE_BUTTON_DOWN:

                                   strcat(mousemessage2,"Middle button down/n");

                                   break;

                            case RI_MOUSE_MIDDLE_BUTTON_UP:

                                   strcat(mousemessage2,"Middle button up/n");

                                   break;

                            case RI_MOUSE_RIGHT_BUTTON_DOWN:

                                   strcat(mousemessage2,"Right button down/n");

                                   break;

                            case RI_MOUSE_RIGHT_BUTTON_UP:

                                   strcat(mousemessage2,"Right button up/n");

                                   break;

                            }

                     }

                     //重绘窗口以输入数据

                     InvalidateRect(hwnd,0,TRUE);

                     SendMessage(hwnd,WM_PAINT,0,0);

                     free(lpb);  

                     return 0;

              }

    }

        return DefWindowProc (hwnd, nMsg, wParam, lParam);

}

 

int APIENTRY

WinMain (HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow)

{

        HWND         hwndMain;       

        MSG          msg;            

        WNDCLASSEX   wndclass;      

        char*        szMainWndClass = "szRawInputTest";

                                    

        memset (&wndclass, 0, sizeof(WNDCLASSEX));

        wndclass.lpszClassName = szMainWndClass;

        wndclass.cbSize = sizeof(WNDCLASSEX);

        wndclass.style = CS_HREDRAW | CS_VREDRAW;

        wndclass.lpfnWndProc = MainWndProc;

        wndclass.hInstance = hInst;

        wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION);

        wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

        wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);

       wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

        RegisterClassEx (&wndclass);

        hwndMain = CreateWindow (

                szMainWndClass,           

                "RawInputTest",           

                WS_OVERLAPPEDWINDOW,      

                CW_USEDEFAULT,            

                CW_USEDEFAULT,            

                CW_USEDEFAULT,            

                CW_USEDEFAULT,             

                NULL,                      

                NULL,                      

                hInst,                     

                NULL                       

                );

       

        ShowWindow (hwndMain, nShow);

        UpdateWindow (hwndMain);

 

               InitRawInput(hwndMain);

 

              //进入消息循环

        while (GetMessage (&msg, NULL, 0, 0))

        {

                TranslateMessage (&msg);

                DispatchMessage (&msg);

        }

        return msg.wParam;

}

 

程序实际运行效果如图2.3所示:

2.3 RawInput各鼠标输入数据识别测试程序截图

         其中不同鼠标的输入通过鼠标的hDevice不同来分辨,上面一部分是RAWINPUT结构的原始输入数据,下面一部分是解析过的数据。

 

2.3.2 RawInput多鼠标指针绘制原理

    虽然在此节介绍的多鼠标指针绘制原理中,绘制多鼠标指针的输入数据来源于上节所讲的RawInput技术,但是实际上无论用那种方法获得数据,本节所讲的多鼠标绘制技术都有实际意义,本节所讲的实际是一种非常典型的分时多鼠标指针绘制技术。

       首先,要绘制多个鼠标,首先要做到的就是保存下目前各鼠标的坐标状况。这里,可以用一个动态数组来完成,另外,要能识别此坐标到底是哪个鼠标的坐标,即应该给各鼠标加上一个ID号,并且这里可以用上节所讲的RAWINPUT结构中的header.hDevice来识别,因为代表各鼠标设备的句柄是唯一的。可以定义一个简单的struct来将两者组合在一起以方便调用。这里将其命名为SiRawMouse并声明一个它的指针,用来实际保存数据。实际代码如下:

 

struct SiRawMouse

{

       long X;

       long Y;

       HANDLE hDevice;

}*pSiRawMouse;

 

       另外,在上一节中已经通过第一步获得系统中RawInput鼠标的数量了。可以根据这个数量决定动态数组的大小。各鼠标的hDevice在得到的各RAWINPUTDEVICELIST结构中就有保存。

       这里需要注意的是,Windows中实际上接入的鼠标要将以上数量减1,因为它内置了一个虚拟的系统鼠标。在普通程序中,多个鼠标的操作都是移动同一个指针的效果就是通过这个虚拟的系统鼠标做到的。在这里,最重要的是分析出到底哪个鼠标是系统鼠标,这里提供的一种思路是通过鼠标的DeviceName来分辨,设备的DeviceName可以通过GetRawInputDeviceInfo函数得到。其函数原型如下:

 

UINT GetRawInputDeviceInfo( 

    HANDLE hDevice,

    UINT uiCommand,

    LPVOID pData,

    PUINT pcbSize

);

 

       在这里又要通过两次调用才能得到想要的结果,第一次以第三参数为NULL获得第四参数的大小,然后以空指针获得想要的结构。并且要获得DeviceName第二参数要设为RIDI_DEVICENAME。得到DeviceName以后可以通过比较前22个字符来分辨此设备是否是系统鼠标,因为系统鼠标前22位一般是固定为"//??//Root#RDP_MOU#0000#",具体比较方式可以用以下函数完成:

 

bool isRootMouse(char cDeviceString[])

{

       char cRootString[] = "//??//Root#RDP_MOU#0000#";

       if (strlen(cDeviceString) < 22)

       {

              return false;

       }

       for (int i = 0; i < 22; i++)

       {

              if (cRootString[i] != cDeviceString[i])

              {

              return false;

              }

       }

       return true;

}

 

       按以上所讲方式,完整的各鼠标坐标数据初始化代码如下:

 

       for(int i=0,j=1; i < nDevices && j < nMouseNumber; ++i)

       {

              if(pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE)

              {

                     GetRawInputDeviceInfo(pRawInputDeviceList[i].hDevice,                                                                               RIDI_DEVICENAME, NULL, &dwSize);

                     lpb = malloc(sizeof(LPVOID) * dwSize);

                     GetRawInputDeviceInfo(pRawInputDeviceList[i].hDevice,                                                                               RIDI_DEVICENAME, lpb, &dwSize);

                     char *deviceName = (char*)lpb;

                     if(isRootMouse(deviceName))

                     {

                            pSiRawMouse[0].hDevice = pRawInputDeviceList[i].hDevice;

                            pSiRawMouse[0].X = 0;

                            pSiRawMouse[0].Y = 0;

                     }

                     else

                     {

                            pSiRawMouse[j].hDevice = pRawInputDeviceList[i].hDevice;

                            pSiRawMouse[j].X = 0;

                            pSiRawMouse[j].Y = 0;

                            ++j;

                     }

                     free(lpb);

              }

       }

 

       通过以上方式初始化了保存鼠标坐标信息的动态数组以后,分别储存和处理不同hDevice的鼠标RawInput信息,具体方式如下:

 

if (raw->header.dwType == RIM_TYPEMOUSE) //判断是否为鼠标信息

{

       raw->data.mouse.usFlags = MOUSE_MOVE_ABSOLUTE;

       for(int i=1; i<nMouseNumber; ++i)

       {

              if(pSiRawMouse[i].hDevice == raw->header.hDevice)

              {

                     pSiRawMouse[i].X += raw->data.mouse.lLastX;

                     pSiRawMouse[i].Y += raw->data.mouse.lLastY;

                     hdc = GetDC(hwnd);

                     CheckBound(hdc,pSiRawMouse[i].X,pSiRawMouse[i].Y);

                     ReleaseDC(hwnd,hdc);

                     pSiRawMouse[0].X = pSiRawMouse[i].X;

                     pSiRawMouse[0].Y = pSiRawMouse[i].Y;

              }

              SetCursorPos(pSiRawMouse[0].X, pSiRawMouse[0].Y);

       }

}

 

       此处的raw为上节中所提到已经获取到鼠标原始输入信息的RAWINPUT结构数据。在这里,处理方式与上节中的不同是判断了鼠标输入信息的hDevice属性来分辨不同鼠标,并保存在各自的SiRawMouse结构中,在检查边界后再将其赋给系统鼠标,并利用此坐标定位系统鼠标指针。这样,就获得了各鼠标独立的,不同的坐标信息,为鼠标指针的绘制做了必要的工作。

       下一步就是利用各鼠标的坐标分别绘制鼠标指针,这里用图标代替鼠标指针的方式简化了一些处理。首先要设置一个自定义的图标资源,此资源储存着要绘制鼠标的样式。然后利用DrawIcon函数调用各鼠标的坐标,达到绘制鼠标指针的类似效果。要注意的是,DrawIcon函数调用的坐标信息是程序窗口的相对坐标,而以上方式获得的坐标信息为屏幕坐标,需要经过ScreenToClient函数进行坐标的转换。并且为了每次坐标的改变都要能够看到,而且以前绘制的图标应该消失,此过程应放置在响应WM_PAINT消息的响应过程中,具体方式如下:

 

case WM_PAINT:

       hdc = BeginPaint(hwnd, &ps);

       for(int i=1; i<nMouseNumber; ++i)

       {

              POINT pt = { pSiRawMouse[i].X, pSiRawMouse[i].Y };

              ScreenToClient(hwnd,&pt);

              DrawIcon(hdc,pt.x,pt.y,hicon);

       }

       EndPaint(hwnd, &ps);

       return 0;

       break;

       这样,每次响应WM_INPUT更改目前各鼠标的坐标状态后,都刷新一次程序,以调用WM_PAINT的响应过程,就可以完成各鼠标指针的绘制。全程序完整原代码如下所示:

 

RawInput多鼠标指针绘制演示程序具体代码(以下代码在Visual studio .NET 2005 上编译通过)

#include "stdafx.h"

#include "rawinput2.h"

#include <stdlib.h>

#include <string.h>

 

#define _WIN32_WINNT 0x0501   //定义此程序为Windows XP程序

#include <windows.h>

 

char mousePostion[256];    //储存经过分析处理的各鼠标坐标信息

HICON  hicon; //绘制的鼠标

int nMouseNumber = 0; //鼠标数量

 

//定义一个自定义的类型,保存坐标和设备的句柄

struct SiRawMouse

{

       long X;

       long Y;

       HANDLE hDevice;

}*pSiRawMouse;        //输入设备鼠标的自定义类型数组

 

//判定是否为系统鼠标的函数

bool isRootMouse(char cDeviceString[])

{

       //一般系统鼠标DeviceName的前22字节

       char cRootString[] = "//??//Root#RDP_MOU#0000#";

      

       //通过比较前22个字节判断是否为系统鼠标

       if (strlen(cDeviceString) < 22)

       {

              return false;

       }

       for (int i = 0; i < 22; i++)

       {

              if (cRootString[i] != cDeviceString[i])

              {

              return false;

              }

       }

       return true;

}

 

//RawInput初始化函数

void InitRawInput(HWND hwnd) 

{

 

       UINT nDevices;          //输入设备的数量

 

       //第一次调用GetRawInputDeviceList获得输入设备的数量

       GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST));

 

       //第二次调用GetRawInputDeviceList获得RawInputDeviceList数组

       PRAWINPUTDEVICELIST pRawInputDeviceList;

       pRawInputDeviceList = (RAWINPUTDEVICELIST *)calloc(nDevices,sizeof(RAWINPUTDEVICELIST));

       GetRawInputDeviceList(pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST));

 

       //获得输入设备中鼠标的数量

       for(int i=0; i < nDevices; ++i)

       {

              if(pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE)

              {

                     ++nMouseNumber;    

              }

       }

      

       //获得输入设备鼠标的自定义类型数组,其中索引为0的为系统鼠标

       pSiRawMouse = (SiRawMouse *)calloc(nMouseNumber,sizeof(SiRawMouse));

       LPVOID lpb;

       UINT dwSize;

 

       for(int i=0,j=1; i < nDevices && j < nMouseNumber; ++i)

       {

              if(pRawInputDeviceList[i].dwType == RIM_TYPEMOUSE)

              {

                     //连续两次调用GetRawInputDeviceInfo以读取RIDI_DEVICENAME

                     //并通过此值判断是否为系统鼠标

                     GetRawInputDeviceInfo(pRawInputDeviceList[i].hDevice,RIDI_DEVICENAME,NULL,&dwSize);

                     lpb = malloc(sizeof(LPVOID) * dwSize);

                     GetRawInputDeviceInfo(pRawInputDeviceList[i].hDevice,RIDI_DEVICENAME,lpb,&dwSize);

                     char *deviceName = (char*)lpb;

                    

                     //将系统鼠标的保存在索引0中,其他依次列在后面

                     if(isRootMouse(deviceName))

                     {

                            pSiRawMouse[0].hDevice = pRawInputDeviceList[i].hDevice;

                            pSiRawMouse[0].X = 0;

                            pSiRawMouse[0].Y = 0;

                     }

                     else

                     {

                            pSiRawMouse[j].hDevice = pRawInputDeviceList[i].hDevice;

                            pSiRawMouse[j].X = 0;

                            pSiRawMouse[j].Y = 0;

                            ++j;

                     }

                     free(lpb);

              }

       }

 

       free(pRawInputDeviceList);

       pRawInputDeviceList = NULL;

 

       //初始化1RawInput设备

       RAWINPUTDEVICE Rid[1]; //分配RawInput设备的空间

       Rid[0].usUsagePage = 0x01;

       Rid[0].usUsage = 0x02;

       Rid[0].dwFlags = 0;

       Rid[0].hwndTarget = NULL;

 

       //注册RawInput设备

       if (RegisterRawInputDevices(Rid, 1, sizeof (Rid [0])) == FALSE) {

              MessageBox(hwnd,"RawInput Register Error.","RawInput init failed!",MB_OK);

       }

 

       return ;

}

 

//检查边界的函数

void CheckBound(HDC hdc,long &x,long &y)

{

       if(x<0)

              x = 0;

       if(y<0)

              y = 0;

      

       //最大值由GetDeviceCaps函数获取以适应不同系统设置

       if(x>(long)GetDeviceCaps(hdc,HORZRES))

              x = (long)GetDeviceCaps(hdc,HORZRES);

       if(y>(long)GetDeviceCaps(hdc,VERTRES))

              y = (long)GetDeviceCaps(hdc,VERTRES);

}

 

//定义回调函数

LRESULT CALLBACK

MainWndProc (HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

{

       static HWND   hwndButton = 0;

    HDC           hdc;            

    PAINTSTRUCT   ps;

    RECT          rc;             

       LPVOID lpb;

       UINT dwSize;

       RAWINPUT *raw;

 

    switch (nMsg)

    {

              case WM_DESTROY:

                     free(pSiRawMouse);

                     DestroyIcon(hicon);

                     PostQuitMessage (0);

                     return 0;

                     break;

             

              //响应WM_PAINT消息以绘制窗口,输出信息

              case WM_PAINT:

 

                     hdc = BeginPaint(hwnd, &ps);

 

                     //输出信息

                     GetClientRect(hwnd, &rc);

                     OffsetRect(&rc,0,100);

                     DrawText(hdc, mousePostion, strlen(mousePostion), &rc, DT_CENTER);

 

                     for(int i=1; i<nMouseNumber; ++i)

                     {

                            POINT pt = { pSiRawMouse[i].X, pSiRawMouse[i].Y };

                            ScreenToClient(hwnd,&pt);

                            DrawIcon(hdc,pt.x,pt.y,hicon);

                     }

                    

                     EndPaint(hwnd, &ps);

            return 0;

            break;

 

              //响应WM_INPUT消息为本程序的主要部分

              case WM_INPUT:

              {

                     GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize,

                                                 sizeof(RAWINPUTHEADER));

 

                     lpb = malloc(sizeof(LPVOID) * dwSize);

                     if (lpb == NULL)

                     {

                            return 0;

                     }

 

                     GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize,

                             sizeof(RAWINPUTHEADER));

 

                     raw = (RAWINPUT*)lpb;

 

                     if (raw->header.dwType == RIM_TYPEMOUSE) //判断是否为鼠标信息

                     {

                            raw->data.mouse.usFlags = MOUSE_MOVE_ABSOLUTE;

                            for(int i=1; i<nMouseNumber; ++i)

                            {

                                   if(pSiRawMouse[i].hDevice == raw->header.hDevice)

                                   {

                                          pSiRawMouse[i].X += raw->data.mouse.lLastX;

                                          pSiRawMouse[i].Y += raw->data.mouse.lLastY;

                                          hdc = GetDC(hwnd);

                                  CheckBound(hdc,pSiRawMouse[i].X,pSiRawMouse[i].Y);

                                          ReleaseDC(hwnd,hdc);

                                          pSiRawMouse[0].X = pSiRawMouse[i].X;

                                          pSiRawMouse[0].Y = pSiRawMouse[i].Y;

 

                                   }

 

                            SetCursorPos(pSiRawMouse[0].X, pSiRawMouse[0].Y);

 

                            }

                            wsprintf(mousePostion,"Mouse %d-> X: %ld,Y: %ld/n", 0, pSiRawMouse[0].X, pSiRawMouse[0].Y);

                            for(int i=1; i<nMouseNumber; ++i)

                            {

                                   char cTemp[100];

                                   wsprintf(cTemp,"Mouse %d-> X: %ld,Y: %ld/n", i, pSiRawMouse[i].X, pSiRawMouse[i].Y);

                                   strcat(mousePostion,cTemp);

                            }

 

                     }

                    

                     //重绘窗口以显示输入数据及重绘指针

                     InvalidateRect(hwnd,0,TRUE);

                     SendMessage(hwnd,WM_PAINT,0,0);

                                         

                     free(lpb);  

                     return 0;

              }

        }

        return DefWindowProc (hwnd, nMsg, wParam, lParam);

}

 

int APIENTRY

WinMain (HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow)

{

             hicon = LoadIcon(hInst,MAKEINTRESOURCE(IDI_CURSOR));

      

              HWND         hwndMain;       

        MSG          msg;            

        WNDCLASSEX   wndclass;      

        char*        szMainWndClass = "szRawInputTest";

                                    

      

        memset (&wndclass, 0, sizeof(WNDCLASSEX));

        wndclass.lpszClassName = szMainWndClass;

        wndclass.cbSize = sizeof(WNDCLASSEX);

        wndclass.style = CS_HREDRAW | CS_VREDRAW;

        wndclass.lpfnWndProc = MainWndProc;

        wndclass.hInstance = hInst;

        wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION);

        wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

        wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);

       wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

        RegisterClassEx (&wndclass);

        hwndMain = CreateWindow (

                szMainWndClass,           

                "RawInput pointer draw",           

                WS_OVERLAPPEDWINDOW,      

                CW_USEDEFAULT,            

                CW_USEDEFAULT,            

                CW_USEDEFAULT,            

                CW_USEDEFAULT,             

                NULL,                      

                NULL,                      

                hInst,                     

                NULL                        

                );

              ShowCursor(FALSE);

              InitRawInput(hwndMain);

        ShowWindow (hwndMain, nShow);

        UpdateWindow (hwndMain);

 

              //进入消息循环

        while (GetMessage (&msg, NULL, 0, 0))

        {

                TranslateMessage (&msg);

                DispatchMessage (&msg);

        }

        return msg.wParam;

}

 

 

程序运行效果如图2.4所示:

2.4 RawInput各鼠标指针绘制演示程序截图

 

    中间显示的鼠标0即为系统鼠标,鼠标123分别为接入的实际鼠标,在这里为了更好的集中在鼠标指针的绘制问题上,忽略了所有关于鼠标按键的问题。程序源代码中IDI_CURSOR为一个自定义的图标资源。需要注意的是,因为各鼠标实际的操作是利用系统鼠标完成,所以同时操作的时候会有些混乱,这是分时控制系统鼠标的弊病所在,在介绍RawInput技术的时候已经讲过,另外,在接下来的第3章中,讲CPNMouse库部分时候会详细演示这种技术的弊端。

 

阅读全文....

Windows中多指针输入技术的实现与应用(3 绪论)

Windows中多指针输入技术的实现与应用(3 绪论)

湖南大学 谢祁衡

Windows中多指针输入技术的实现与应用

1 绪论

1.1 课题背景

计算机科学的是目前发展最快的几个领域之一,人机交互的方式也经历着重大的变革。众所周知,输入设备是人机交互最主要的设备。最开始的计算机使用的输入设备是非常不方便的读卡器和磁带驱动器, 那时用户不直接与计算机系统交互,而是准备好一个作业(由程序、数据和一些控制信息(控制卡片)组成)并提交给计算机操作员。作业一般以穿孔卡片的形式提交。[1,3-4]目前键盘和指针设备是微机上最常用的输入设备,扫描仪和光电笔等输入设备的应用也越来越广泛。键盘主要用来输入文字,而我们可以方便快捷地用指针设备指定光标在屏幕上的位置。尤其是在Windows环境里,操作几乎离不开指针设备。指针设备主要包括最常用的鼠标, 以及触控板,触摸屏轨迹球, 指点杆和光线枪。通常我们家用机使用的是鼠标,笔记本使用的是触控板,[2]而触摸屏在商业领域得到了很广泛的应用。一般来说使用单指针设备加键盘就能满足需求,其接口模型如图1.1

1.1 单用户输入的用户接口模型[3]

 

但是现在使用多个同时控制自己光标的指针设备的需求也越来越多。而微软的   Windows本身并不支持超过单个指针设备的操作,无论你接上多少指针设备,它也仅仅提供一个光标,多个指针设备只能通过这仅有的一个光标进行操作。于是让程序在Windows下支持多个指针设备,并且控制各自独立的光标就成了软件设计者问题。

 

1.2课题目的

多指针设备输入在很多情况下有很大的优势。比如当需要左右手同时操作指针设备时。也有可能是当需要利用不同的指针设备实现不同的功能时,比如当画Colored Petri Net[4]时,就有这种需求。在现代应用中,多人在大屏幕上共同协作完成同一个任务(特别在CAD等领域)的情况并不少见,这时也非常需要多指针技术。另外在游戏的设计上,多人同时操作自己的指针设备在同一台电脑上进行,也有着相当大的市场。而且,很多现代的人机交换技术,比如tool-glassesfloating patettes至少需要两个指针设备,一个用来移动工具板,另一个用来选择工具。在文献[5]中微软的技术人员通过详细地进行了学生行为真实的对比试验分析,证明了此技术在发展中国家教育行业中的巨大潜力。另外,在[3]B. B. Bederson,J. Stewart, A.Druin开拓性的一文中第一次提出了SDG(Single Display Groupware)的概念,并详细介绍了此技术将来可能的应用范围。而实际上,在Windows中,SDG概念实现的主要部分就是实现多指针设备的输入。其模型示意图如图1.2所示。

1.2 SDG输入的用户界面模型[3]

 

       本文希望通过对此技术原理的详细介绍,各种实现方法的简单介绍,和对SDG Toolkit具体使用方法的简单介绍,可以让人了解并可以更多的在多指针输入技术有优势的相关软件中使用此技术。

 

1.3研究状况

    自从E. A. BierS. Freeman 1991年在[6]中第一次真正实现了一个SDG系统,并由B. B. Bederson,J. Stewart, A.Druin[3]中真正建立了SDG的完整理论体系后,国外独立的多指针设备输入实现已经有很多。包括为满足Colored Petri Net建模而设计的CPN Tools Project (daimi.au.dk/CPNTools),为多人同时协作完成任务而设计的WorkSPACE (daimi.au.dk/workspace)为游戏而设计的Mouse-Party (mouse-party.com/)MAME:Analog+ (MAMEWorld.net),为节省教育成本投入而设计的MultiMice (MultiMice.cz)。以上的多种实现反映出多指针技术的巨大应用潜力。但是,每个想使用此技术的软件开发人员不得不自己独立实现一套完整的系统,非常大的增加了软件开发的成本,而且这些专门的开发技术难以维护,修改,也更难以被其他软件开发人员所利用。这些问题的解决都呼唤通用型的多指针技术出现,国外对通用的多指针技术的研究尚处于摸索阶段,但已经提出过很多解决方案。其中最成熟的应该是开源项目CPNmouse (cpnmouse.sourceforge.net)和加拿大calgary大学GroupLab开发的 Single Display Groupware Toolkit grouplab.cpsc.ucalgary.ca/software/SDGT。虽然还有很多兼容性及实现复杂的问题,但是已经进入了实际应用阶段。甚至还有两个专门研究相关技术的实验室GroupLab grouplab.cpsc.ucalgary.caEDGE lab (www.edgelab.ca),进行着很多相关的研究。另外在文献[7]我们可以看到,作者描述了通过改进 X window system,实现了在X window system很好的通用多指针设备输入的支持。虽然作者提出这个模型不仅在X Window system下可用,也适用于Windows MAC OS,可惜我们很难直接通过改进Windows 本身来利用这个模型。值得一提的是,最近,微软开发了一种叫MultiPoint的技术,以期可以让更多的发展中国家学生在电脑不足的情况下实际能操作电脑,并在2007625开始提供MultiPointSDK(Software Development Kit)下载,[8]不过目前还没有见到利用此技术实际开发出来的软件。

 

1.4 论文构成及研究内容

本文通过对多种可能的实现方案进行分析,对比,并详细介绍其中较好方案的实现和应用。因为各种指针设备在Windows下都当作鼠标来处理,这里以多鼠标输入为例,来分析多指针设备的输入,并且提到的支持平台仅为Windows

      本文第一部分主要介绍目前两种多鼠标输入的底层实现方案,分析其优劣,并详细介绍了一种较好的用户层利用RawInput实现的原理。第二部分介绍了三种目前主要的多鼠标输入框架软件,并对其进行了分析,对比,然后详细介绍了一种较好的框架类软件SDG Toolkit实现原理。第三部分简单介绍了SDG Toolkit的使用方法。第四部分分析了目前方案的不足之处及其可能的改进。第五部分进行了延伸性的探讨,讨论了怎么利用MFC实现此技术,并提供了参考的例子。然后,总结本文的工作。

阅读全文....

Windows中多指针输入技术的实现与应用(2摘要及参考论文)

Windows中多指针输入技术的实现与应用(2摘要及参考论文)

湖南大学 谢祁衡

Windows中多指针输入技术的实现与应用

 

摘 要

 

       多指针设备输入在很多情况下有很大的优势。但是微软的Windows本身并不支持此技术,让程序在Windows下支持多个指针设备,并且控制各自独立的光标就成了软件设计者问题。本文介绍了目前可行的,底层和利用框架软件实现的两大类方法。在底层实现中,详细讲述了利用RawInput技术实现多鼠标输入的原理。在框架软件实现中,详细讲述了一种多鼠标输入框架软件Single Display Groupware Toolkit的实现原理。并对各种方法进行了简单描述和总的对比,其中开发过滤式鼠标驱动的方法主要用来开发框架级程序;RawInput技术因为优点明显,被各层次的软件所利用;CPNmouse库是对通用多指针输入软件开发很好的尝试,但是并不是太成功;MultiPoint SDK虽然由微软推出,可是目前并不成熟;Single Display Groupware Toolkit综合了以上各技术的很多优点,缺点比较少,软件相对成熟,使用比较简单。本文推荐实现Windows中多指针输入技术的方法为使用Single Display Groupware Toolkit。最后简单介绍了Single Display Groupware的具体使用方法。因为Single Display Groupware Toolkit在MFC中使用不是那么方便,所以最后讨论一下在MFC中怎么自己实现和利用此技术,然后给出两个实例研究。

 

关键词:计算机;WindowsSDG;多指针设备;多鼠标

 

 

 

 

 

implementation and application of multiple pointing devices inputing in the Windows

 

Abstract

 

      There are comparative advantages of multiple pointing input devices in many conditions. But Microsoft Windows does not natively support this technique, so programmers have to control independent mice and cursors themselves. In this paper, I described two kinds of general solutions, included implementation from bottom level and applying the framworks. And I described how to use RawInput and represented how Single Display Groupware Toolkit works in detail. In this paper, I compared five solutions. Developing filter mouse drive mainly used by the framework software; Because of RawInput technique’s distinct advantages, this technique is used by various layers software; CPNmouse library is a good attempt to implement, but it is not all succeed; Though MultiPoint SDK developed by Microsoft, it is not mature; Single Display Groupware Toolkit integrated many advantages of the technique I mentioned before, and less shortcomings, more mature than them. And it is very easy to use, so I recommended the Single Display Groupware Toolkit in this paper to implement multiple pointing devices inputing in the Windows. In the end of this paper, I presented how to use it.

 

Key Words: computer;Windows;SDG;multiple pointing devices;multiple mice

 

因为在网上发布此文,所以为了方便网上阅读,我更改了论文的格式,添加了一些后来加入的信息,另外,首先就加入参考的文章,方便查阅,而且因为要修改和添加我没有办法一次将所有文章都发上来,先发参考文献也有助于感兴趣的朋友自己先去学习。参考文献的标注格式是按照湖南大学的论文标准做的,应该很好懂。


参考文献

[1]A. Silberschatz,P.Galvin,G.Gagne.操作系统概念[M].北京:高等教育出版社, 20023-4.

[2] 邓天卓.指针设备史.[EB/OL].

http://it.sohu.com/20041010/n222409012.shtml, 2004-10-10.

[3] B. B. Bederson,J. Stewart, A.Druin. Single Display GroupwareA Model for Co-present Collaboration [R]. Pittsburgh, PA, USA: HCIL Technical Report No. 98-14.1998.

[4] K. Jensen.Coloured Petri Nets.Basic Concepts,Analysis Methods and Practical Use.Volume 1, Basic Concepts.[M].Springer-Verlag,1992.

[5] U. S. Pawar, J. Pal, K. Toyama.Multiple Mice for Computers in Education in Developing Countries.[DB/OL]. http://tier.cs.berkeley.edu/docs/ict4d06/multiple_mice-jp.pdf.

[6] E. A. Bier,S. Freeman. MMM: A User Interface Architecture for Shared Editors on a Single Screen.[A]. Proceedings of the 4th annual ACM symposium on User interface software and technology.[C]. Hilton Head, South Carolina, United States: ACM Press New York, NY, USA,1991: 79-86.

[7] P.Hutterer,B. H. Thomas. Groupware Support in the Windowing System.[R]. Australia: Wearable Computer Laboratory School of Computer and Information Science,University of South Australia.

[8] R.Wash. With Windows MultiPoint, Youths in Developing-World Classrooms Learn 21st-Century Skills.[DB/OL].

http://www.microsoft.com/presspass/features/2006/dec06/12-14MultiPoint.mspx, 2006-12-4.

[9] M. Westergaard. Supporting Multiple Pointing Devices in Microsoft Windows.[R]. Aarhus:Department of Computer Science, University of Aarhus.2002.

[10] E.Tse,S. Greenberg.Rapidly prototyping Single Display Groupware through the SDGToolkit. [A].Proceedings of the Fifth Conference on Australasian User interface - Volume 28.[C].Dunedin, New Zealand: Cockburn, Ed. ACM International Conference Proceeding.101-110.2004.

[11] E. Tse,S. Greenberg.SDGToolkit: A Toolkit for Rapidly Prototyping Single Display Groupware.[R].Calgary, Alberta, Canada:Department of Computer Science University of Calgary.2002

[12]E. Tse.The Single Display Groupware Toolkit.[D].CALGARY, ALBERTA.2004

 

阅读全文....

数据结构与算法分析 C++描述(第3版) 习题2.8 详尽分析

数据结构与算法分析 C++描述(第3版) 习题2.8 详尽分析 

Data Structures and Algorithm Analysis In C++ Third Edition By Mark Allen Weiss 转载请注明出处及作者:九天雁翎

 

a:因为12很明显,所以不证明了。

至于算法3,可以用数学归纳法证明,x详细证明如下:

1.n=1时,a[0]=1,都是100%,成立;

2.n=2时,

for(i = 1; i < n; ++i)

   swap (a[i],a[ randInt(0,i)]);

 

第一次循环,当i=1时,此时a[0]=1,a[i]=a[1]

randInt(0,1)50%机会为050%机会为1,所以,swap(a[1],a[0])的机会为50%,即,序列为0,1; 1,0几率都为50%。 成立

3. n=3时,第二次循环时,当i=2时,此时有两种可能,原序列为0,1; 1,0的几率各为50%randInt(0,2)可能为0,1,2的几率各位1/3.此时,原序列为0,1,randInt(0,2)0,那么此序列经过swap(a[2],a[0]),最后序列为2,1,0,此序列的可能性为(1/2)*(1/3)=1/6; 同理易得,最后此序列为

210, 021, 012, 201, 120, 102的几率各位1/6,而此序列的所有排列可能为  = 3 * 2 = 6,所以 此时成立.

4.假设此命题在n = k时成立,那么就是说,k前面序列共有序列 种,且所有序列的几率为1/ .

5.n=k+1,k+1次循环时,前面序列正好为n=k时的情况,此时i = k. randInt(0,k)共可能为0~k,k+1种可能。所以以前的每个可能序列在置换后有k+1种可能,而以前共有k种等可能序列,那么最后可能的序列为k*(k+1)种可能,并且,因为原序列为等可能的,每个等可能序列产生的序列都是k+1种,所以最后的序列也是等可能的。而当n=k+1时,应该共有 =(k+1)*k种,所以,此命题得证。

即算法3产生的也是等置换序列。

这里最后要说明的是,证明时默认地利用了一个命题,

当原序列为互不相等的等可能序列时,新加入一个与原来序列任何数值都不相等的数值,无论这个数值放在原序列的哪个位置,都不可能使原不相等的序列相等,说的好复杂啊-_-!

基本就是这样,210, 021, 012, 201, 120, 102,这是你加入一个新的数值3,无论你把3插入序列的哪个位置,因为原来序列的排列不相等,所以,他们还是不会相等,这样,才保证了最后k个序列的k+1种可能都不相等,不会重复。

 

算法分析答案说的很详细,可以参考答案,即第一个算法为O( ),第二个算法为ONlogN,第三个为线性。

 

实际算法的实现如下:

#include "jtianling/jtianling.h"

#include "boost/dynamic_bitset.hpp"

using namespace std;

 

//2.8题目中假设存在的随机数生成器

//根据其定义,此算法生成从ij的随机数,而且包括边界i,j.i=<n<=j

int randInt(int i, int j)

{

   //确保i<j,不然就交换

   if(i > j)

   {

      int temp = i;

      i = j;

      j = temp;

   }

   //确保范围正确

   return rand()%(j-i+1) + i;

}

 

//算法,算法描述如题目.8所示,根据习惯,不对arr[]的大小做任何检验

//调用此函数的人应该确保这一点,arr的大小大于等于n

void RandArrayCreate1(int arr[], int n)

{

   int temp;

   for(int i=0; i<n; ++i)

   {

      while(true)

      {

         temp = randInt(1,n);

         int j = 0;

         while(j<i)

         {

            if(arr[j] == temp){   break;}

            ++j;

         }

         if(j == i)

         {

            arr[j] = temp;

            break;

         }

      }

   }

}

 

//算法,算法描述如题目.8所示,根据习惯,不对arr[]的大小做任何检验

//调用此函数的人应该确保这一点,arr的大小大于等于n

void RandArrayCreate2(int arr[], int n)

{

   //为了正好与数值一一对应,浪费位算了,默认就都为flase

   bool *used = new bool[n+1];

   memset(used, 0, sizeof(bool) * (n+1));

   int temp;

   for(int i=0; i<n; ++i)

   {

 

      while(true)

      {

         temp = randInt(1,n);

         if(used[temp] == false)

         {

            arr[i] = temp;

            used[temp] = true;

            break;

         }

         else

         {

            continue;

         }

      }

   }

   delete []used;

 

}

 

 

//算法,算法描述如题目.8所示,根据习惯,不对arr[]的大小做任何检验

//调用此函数的人应该确保这一点,arr的大小大于等于n

void RandArrayCreate3(int arr[], int n)

{

   for(int i=0; i<n; ++i)

   {

      arr[i] = i + 1;

   }

   for(int i=0; i<n; ++i)

   {

      swap(arr[i], arr[randInt(0, i)] );

   }

}

 

void RandArrayCreate2a(int arr[], int n)

{

   //为了正好与数值一一对应,浪费位算了,默认就都为flase

   boost::dynamic_bitset<> used(n+1);

   int temp;

   for(int i=0; i<n; ++i)

   {

 

      while(true)

      {

         temp = randInt(1,n);

         if(used[temp] == false)

         {

            arr[i] = temp;

            used.set(temp);

            break;

         }

         else

         {

            continue;

         }

      }

   }

 

}

void RandArrayCreate2b(int arr[], int n)

{

   //为了正好与数值一一对应,浪费位算了,默认就都为flase

   vector<bool> used(n+1);

   int temp;

   for(int i=0; i<n; ++i)

   {

 

      while(true)

      {

         temp = randInt(1,n);

         if(used[temp] == false)

         {

            arr[i] = temp;

            used[temp] = true;

            break;

         }

         else

         {

            continue;

         }

      }

   }

}

 

 

这里对算法的测试用另外一个测试函数来进行:

 

   //因为这里主要是比较不同算法的实现,所以这里不区分算法及其实现,都称为算法

   //比较算法的函数,其中算法函数的参数为数组(array)和整数(int)

   //简写为AI,为了防止混乱,这里我没有使用函数的重载

   //准备以后有新的不同参数的算法时再加入新的比较函数

   //比较函数的参数解释如下:

   //第一参数:各算法的用vector<>表示的函数指针数组

   //第二参数:vector<int>表示的一系列算法函数第二参数,用以做算法增长比较

   //主要注意的是,这里的第二参数实际也是算法函数第一参数数组的大小

   void CompareAlgorithmAI(const std::vector<pfAI> &pfAIVec,const std::vector<int>& vec)

   {

      double timeInAll = GetTime();  //这里保存所有比较花费的时间

      std::ofstream out("CompareAlogrithmAIResult.txt");//将结果保存在这里

     

      //在文件中输入第一行

      out <<"parameter" <<'/t';

      for(int i=0; i<pfAIVec.size(); ++i)

      {

         out <<"Algorithm " <<i+1 <<'/t';

      }

      out <<std::endl <<std::setprecision(4) <<std::scientific;

     

      double timeTaken;  //每个算法一次消耗的时间

 

      //以算法参数的数组个数为循环条件,每个数值让各个算法分别调用

      for(int i=0; i<vec.size(); ++i)

      {

         out <<vec[i] <<'/t';

         int *arr = new int[ vec[i] ];  //每次动态生成一个数组

         for(int j=0; j<pfAIVec.size(); ++j)

         {

           

            timeTaken = GetTime();

            pfAIVec[j](arr, vec[i]); //实际调用不同算法

            timeTaken = GetTime() - timeTaken;

            out <<timeTaken <<'/t';

         }

         delete []arr;   //删除arr,因为只考察算法的效率,所以根本不关心结果

                      //至于各算法是否正确由调用者保证

         out<<std::endl;

      }

      timeInAll = GetTime() - timeInAll;

      out <<"Compared time in all: " <<timeInAll <<std::endl;

   }

 

最后得到的结果用excel打开,实在是更加赏心悦目而且更好的可以保存下来,可以只测试一个,也可以一次测试一组,以后的算法比较中还可以重复的利用,这里利用了vector来传递参数,有利于减少参数的数量。关于GetTime的含义,请参考置顶文章

 

parameter

RandArrayCreate1

parameter

RandArrayCreate3

250

6.62E-04

100000

1.85E-02

500

4.61E-03

200000

3.78E-02

1000

1.59E-02

400000

5.83E-02

2000

6.70E-02

800000

1.17E-01

Compared time in all: 8.9593e-002

1600000

2.30E-01

   

3200000

4.61E-01

   

6400000

9.22E-01

   

Compared time in all: 1.9196e+000

parameter

RandArrayCreate2

RandArrayCreate2a

RandArrayCreate2b

250

1.30E-04

7.51E-04

1.03E-02

500

2.72E-04

1.64E-03

1.88E-02

1000

6.78E-04

3.49E-03

5.65E-02

2000

1.41E-03

9.19E-03

1.11E-01

4000

2.70E-03

1.47E-02

2.63E-01

8000

5.97E-03

3.84E-02

4.66E-01

16000

1.31E-02

7.39E-02

1.33E+00

32000

3.60E-02

1.91E-01

2.39E+00

Compared time in all: 5.0447e+000

   

 

 

很显然的结果,不多说了,不过书中给出要我运行的第2算法的参数实在是太大了,估计一天都运行不完,这里也可以看到,在动态bool位使用上,直接New最快,用Boost库的动态bit是其次,用标准库的vector<bool>只能用慢的出奇来形容了,当然,其实很多时候都是同样的结论,因为用new毕竟不是那么好管理,但是不需要建构析构,而用了类来管理,自然需要更多的时间资源,但是更好管理并且动态更改起来更加方便。这需要自己去权衡利弊了。

 

至于最E,最坏情况的分析,前两个在一直随机到重复数字的时候可以到无限大。。。汗!第三个算法是线性的,也无所谓最坏情况。 

阅读全文....

几种典型算法的快速比较函数

几种典型算法的快速比较函数

 

声明:

   //因为这里主要是比较不同算法的实现,所以这里不区分算法及其实现,都称为算法

   //比较算法的函数,其中算法函数的参数为数组(array)和整数(int)

   //简写为AI,为了防止混乱,这里我没有使用函数的重载

   //准备以后有新的不同参数的算法时再加入新的比较函数

   //方便最后查看对比我没有使用常规的屏幕输出而是输出到文件

   //输出文件名为CompareAlogorithmAIResult.txt

   //而且此文件可以用Excel打开,比较清晰,方便保存

   //比较函数的参数解释如下:

   //第一参数:各算法的用vector<>表示的函数指针数组

   //第二参数:vector<int>表示的一系列算法函数第二参数,用以做算法增长比较

   //主要注意的是,这里的第二参数实际也是算法函数第一参数数组的大小

   typedef void (*pfAI)(int [], int);

   void CompareAlgorithmAI(const std::vector<pfAI> &pfAIVec,const std::vector<int>& vec);

 

 

   //比较算法的函数,其中算法函数的参数为整数(int)

   //简写为I,方便最后查看对比我没有使用常规的屏幕输出而是输出到文件

   //输出文件名为CompareAlogrithmIResult.txt

   //而且此文件可以用Excel打开,比较清晰,方便保存

   //比较函数的参数解释如下:

   //第一参数:各算法的用vector<>表示的函数指针数组,

   //第二参数:vector<int>表示的一系列算法函数第二参数,用以做算法增长比较

   typedef void (*pfI)(int);

   void CompareAlgorithmI(const std::vector<pfI> &pfIVec,const std::vector<int>& vec);

 

 

实现:

 

   void CompareAlgorithmAI(const std::vector<pfAI> &pfAIVec,const std::vector<int>& vec)

   {

      double timeInAll = GetTime();  //这里保存所有比较花费的时间

      std::ofstream out("CompareAlogrithmAIResult.txt");//将结果保存在这里

     

      //在文件中输入第一行

      out <<"parameter" <<'/t';

      for(int i=0; i<pfAIVec.size(); ++i)

      {

         out <<"Algorithm " <<i+1 <<'/t';

      }

      out <<std::endl <<std::setprecision(4) <<std::scientific;

     

      double timeTaken;  //每个算法一次消耗的时间

 

      //以算法参数的数组个数为循环条件,每个数值让各个算法分别调用

      for(int i=0; i<vec.size(); ++i)

      {

         out <<vec[i] <<'/t';

         int *arr = new int[ vec[i] ];  //每次动态生成一个数组

         for(int j=0; j<pfAIVec.size(); ++j)

         {

           

            timeTaken = GetTime();

            pfAIVec[j](arr, vec[i]); //实际调用不同算法

            timeTaken = GetTime() - timeTaken;

            out <<timeTaken <<'/t';

         }

         delete []arr;   //删除arr,因为只考察算法的效率,所以根本不关心结果

                      //至于各算法是否正确由调用者保证

         out<<std::endl;

      }

      timeInAll = GetTime() - timeInAll;

      out <<"Compared time in all: " <<timeInAll <<std::endl;

   }

 

   void CompareAlgorithmI(const std::vector<pfI> &pfIVec,const std::vector<int>& vec)

   {

      double timeInAll = GetTime();  //这里保存所有比较花费的时间

      std::ofstream out("CompareAlogrithmIResult.txt");//将结果保存在这里

     

      //在文件中输入第一行

      out <<"parameter" <<'/t';

      for(int i=0; i<pfIVec.size(); ++i)

      {

         out <<"Algorithm " <<i+1 <<'/t';

      }

      out <<std::endl <<std::setprecision(4) <<std::scientific;

     

      double timeTaken;  //每个算法一次消耗的时间

 

      //以算法参数的数组个数为循环条件,每个数值让各个算法分别调用

      for(int i=0; i<vec.size(); ++i)

      {

         out <<vec[i] <<'/t';

         for(int j=0; j<pfIVec.size(); ++j)

         {

            timeTaken = GetTime();

            pfIVec[j](vec[i]); //实际调用不同算法

            timeTaken = GetTime() - timeTaken;

            out <<timeTaken <<'/t';

         }

         out<<std::endl;

      }

      timeInAll = GetTime() - timeInAll;

      out <<"Compared time in all: " <<timeInAll <<std::endl;

   }

 

 

实际用法示例如下:

 

int main()

{

   srand( long(jtianling::GetTime()) );

   typedef void (*pfAI)(int [], int);

   vector<pfAI> pfAIVec;

//插入算法

   pfAIVec.push_back(RandArrayCreate2);

   pfAIVec.push_back(RandArrayCreate2a);

   pfAIVec.push_back(RandArrayCreate2b);

//插入参数

   vector<int> vec;

   vec.push_back(250);

   for(int i=0; i<7; ++i)

   {

      vec.push_back(vec[i] * 2);

   }

//直接调用,然后就可以通过文件查看了,建议用excel打开

   jtianling::CompareAlgorithmAI(pfAIVec, vec);

}

 

类似的算法比较函数都可以很容易设计出来,这样在比较几个算法的优劣时就方便多了。

 

阅读全文....