Java下类似QQ截图的实现,支持热键,系统托盘

    QQ的截图蛮好用的,如果你使用Note发图片的话,只要Ctrl + Alt + A, QQ的截图就出来了,之后可以直接粘贴到邮件里面 ,图片也比较小。
    之前不知道Java已经早早支持截图了,原来有个Robot的类;JDK1.6也支持系统托盘了,Java实现这样的功能就剩下热键了。想当然只能靠Windows Hook钩子.
一。截图和保存
        BufferedImage是个好类,和ImageIO和Graphic可以对图片进行很多处理;至于把BufferedImage打到窗口上就是八仙过海,各显神通罗。
import  java.awt.AWTException;
import  java.awt.Rectangle;
import  java.awt.Robot;
import  java.awt.image.BufferedImage;
import  java.io.File;
import  java.io.IOException;

import  javax.imageio.ImageIO;

public   class  ImageUtil  {
    
    
public static BufferedImage captureScreen(Rectangle screenRect)
            
throws AWTException {
        Robot robot 
= new Robot();
        
return robot.createScreenCapture(screenRect);
    }

    
    
public static void saveImage(BufferedImage srcImage, String saveImagePath) throws IOException {
        File file 
= new File(saveImagePath);
        String suffix 
= saveImagePath.substring(saveImagePath.lastIndexOf('.')+1);
        ImageIO.write(srcImage, suffix, file);
    }

}


二。Keyboard hook的编写
    在这点上走了很多弯路,一开始很简单的想着, 点一个按钮启动热键, 之后把JNI的一些相关的变量保存到一下DLL的变量当中。
JNIEnv * env;                     //JNI的环境指针
jobject    callBackObject; //当热键出发时Java要回调的实例,假设是实现Interface {void response(){}}实例
int* hotKeyCodes;            //一序列的热键KeyCode,假设是恢复窗口以及截屏两个热键
int   hotKeyCount;             //热键个数
LRESULT CALLBACK KBProc(int nCode, WPARAM wParam, LPARAM lParam){
//wParam就是KeyCode
//lParam包含一些额外的按键的信息;请参考MSDN WH_KEYBORAD相应的回调函数。
//如果匹配利用JNIEnv* env, 以及jobject调用java代码,传入当前keyCode响应
}

JNIEXPORT jboolean JNICALL Java_com_gy_image_HotKeyHookManager_startHotKeyHook(
JNIEnv 
*  pEnv,jobject pHookManagerObj,jobject pCallBackObject,jobject pHotKeyCodes,
jint hotKeyCout) {
//保存到DLL的变量当中
//hhook = SetWindowsHookEx(WH_KEYBOARD, KBProc, hookDllModule, NULL);
}
    结果发现Hook能安装,在任意窗口当前按键的keyCode都能获取,但是只有在当前窗口状态下,hotKeyCodes指针的值和JNIEnv才不是空的。这些值在当前窗口按热键才能获得。
开始我以为是这个HOOK只绑定一个Process的,试试WH_KEYBOARD_LL底层的,情况更坏,无法到回调函数打印当前keyCode; 查看MSDN说是创建 HOOK和回调要分开,回调要写在DLL当中;继续试了,于上面的情况相同;只能在Java的应用的窗口上 获得到保存的热键值, 其他窗口出发热键保存的变量都是空空的。
    原本想放弃了,后来想了下,可能是因为不熟悉WINAPI DLL吧,想办法用VC自己来创建键盘钩子,而且保存一些变量在DLL看看怎么样子, 结果发觉对于钩子,当前的窗口触发热键是能获得到保存的变量的,其他窗口触发热键则不能。 可以粗略判断是由于不同Process导致的。 也就是说创建钩子的进程可以保存的值只能自己用,不能共享给其他进程。 但想想QQ为什么能?
    在codeproject查看到一些老文章,老外写文章还比较严谨,我看到了这样的一些编译头
#pragma  data_seg(".adshared")
HWND  targetWnd 
=  NULL;
#pragma  data_seg()
#pragma  comment(linker, "/SECTION:.adshared,RWS")

    HOOK是需要DLL的,因为DLL是会可能注入到Load到其他进程当中去的,但是每个Process有自己的内存空间,这些保存的变量默认是隔离开来的,DLL里面定义的变量值不是共享的。 使用上面的编译头能使用数据实现共享。 看到希望了。
#pragma  data_seg(".adshared")
int  hotKeyCode  =   0 ;
HWND  targetWnd 
=  NULL;
HWND  targetButton 
=  NULL;
#pragma  data_seg()
#pragma  comment(linker, "/SECTION:.adshared,RWS")
    但又郁闷了,之前一直保存int* hotKeyCodes;和int hotKeyCount; 发现hotKeyCodes一直不对路,但是个数hotKeyCount确实能共享到, 又再次乱搞,发现指针这种东西假设是在当前进程创建的,因为指针存的是地址,到另外一个进程当中就是指到乱七八糟的东西了; int 这类是值传递,可以共享; 那为什么HWND又可以呢, 我想HWND这些窗口句柄应该是全局的, 对于window来说, 他们要管理这些窗口,地址应该是全局的地址。 如果在Process当前new一些东西, 应该是进程本身的虚拟的地址, 那用 GlobalAlloc分配内存保存一些列的keyCode行不行? GlobalAlloc是全局分配内存的嘛?我猜没去试。 但是好像JNIEnv这些肯定是当前进程创建的了, 不可能给其他进程共享了, 只能放弃这种保存JNIEnv这些虚拟机的变量的想法了。

 三。 改变思路,使用窗口通信,
Swing这些也是窗口自然有HWND这些句柄了, 我能不能热键触发我就PostMessage过去呢。 为了简单就只保留一个热键默认Ctrl + Alt + a key code. 使用一个int keyCode;就可以共享这些热键了,HWND保存当前 Swing产生的窗口句柄
#pragma  data_seg(".adshared")
int  hotKeyCode  =   0 ;
HWND  targetWnd 
=  NULL;
HWND  targetButton 
=  NULL;
#pragma  data_seg()
#pragma  comment(linker, "/SECTION:.adshared,RWS")

用MFC写了点窗口测试这个方法可行, 确实能找到Swing的JFrame的父窗口句柄 HWND targetWnd; 但是很奇怪,这个JFrame就只有一个句柄, 没有其他任何子句柄了, 这个可以使用VC++附带的SPY++看到; 怎么回事? Swing是什么东西? 郁闷了吧, 查了下, 蛮有趣的东西。
http://ctrlest.spaces.live.com/blog/cns!DE2A74F00EB2B569!147.entry
AWT / Swing内幕 --- 技术发展从来就不关技术的事
谢谢你的回复。我很乐意给你提供Swing和SWT背后的一些信息,既然你还把我当作你秘密的“IBM内幕线人”。

要想弄清楚为什么一切都被弄得如此混乱,要从几年前只存在AWT的时候说起。SUN当时已经建立了一套基本的可移植控件类,这些类映射到不同操作系统上的原生窗口组件(native widget),显然下一步应该继续增强这套模型,除了初始的CUA 92组件(文字、按钮等等),再继续加上表格、树、记事本、滑块等等……当时的AWT还满是漏洞,远不能称为可靠,还需要SUN的coder们去修补。 SUN的developer们如Graham和Otto总是习惯于公开把他们的bug归咎为操作系统的差异,比如“Windows和OS
/ 2的焦点次序不同”或者“在……之间Ctrl - X的行为不一样”,以及其他苍白的托辞,好让批评的火力从SUN太早释出代码这个问题的真相上移开。然后Amy Fowler来到了SUN。不是我大男子主义,Amy是个聪明的美女,大多数呆头呆脑只懂技术的开发人员都要被她捏在手里。

Amy来自一家Smalltalk公司,叫做Objectshare,在那里她负责搞UI类库。跟Java相比Smalltalk的历史有些悲惨,曾几何时有3家庞大的Smalltalk公司——IBM、Parc
- Place和Digitalk。在90年代初期3家公司的市场份额大致相等,生活是美好的。Parc -  Place采用仿窗口部件(emulated widgets)的设计(即Swing的设计),IBM和Digitalk则采用原生窗口部件(native widgets)。后来IBM压倒了另外两家,因此他们打算合并成一家,假设叫做Parc - Place Digitalk。随后当他们试图将他们的产品融合到一个叫做Jigsaw的计划中时爆发了一场大战,计划由于政治原因失败了(开发人员实际上已经能让它运转起来),就因为原生和仿造两派的死战。Amy赢得了精神上的胜利,不过在IBM我们赢得了他们所有的生意,因为这两家公司在一整年里除了吵架什么都没做。当尘埃落定之后PPD(Parc - Place Digitalk当时已改名为Objectshare,跟Windscale改名为Sellafield的原因相同——让人们淡忘之前发生的灾难)的股票价格从60美元掉到了低于1美元1股。他们因为伪报收入被NASDAQ摘牌,从此消失。此时SUN正走上与PPD类似的技术方向,于是PDD的技术人员都把他们的简历投到了SUN。Amy被雇佣了,她承诺通过轻量级方案解决所有窗口组件的问题,因此说服SUN管理层让她当了GUI开发部门的头头。她是拿着 “这里原来的人都搞砸了,我是来解决的”的钥匙进来的。随后Amy雇佣了所有她过去在Parc - Place的旧朋友,让他们来开发Swing。

显然Swing应该做的是仅仅成为一个绘制框架,给那些希望创建地图软件或者绘图软件的人们使用,无论如何,应该围绕AWT类库来建造它,按钮之类的东西仍然交给AWT来管。SUN的人比如Philip和Mark已经让AWT能够处理表格、树和记事本(notebook,?),所以Swing的方向应该说很明显了。但那些毁了PDD的人不干,他们非要把一切都弄成轻量级的。由于SUN管理层的无知,再加上Amy无情的政治手段,造成了我们今天所见的混乱局面。Amy还使SUN相信Swing是作为Mozilla项目的一部分与Netscape联合开发的,事实上这只是她的宣传伎俩。

在IBM,我们从第一天起就憎恶Swing。庞大、满是错误,而且难看至极。原先我们的工具如VisualAge 
for  Java都是用Smalltalk(用的是原生窗口组件)写的,所以当我们将这些工具向Java代码库迁移时,我们需要一套窗口组件。IBM这边的开发人员都是原来搞Smalltalk的那一批人,我们对管理层要求用Swing来构建WebSphere Studio工具都非常不情愿。Swing是个可怕的充满缺陷的怪兽。在WebSphere Studio最初的预览中,当与Microsoft Visual Studio作对比演示的时候,我们所有的客户都讨厌它,就因为它的外观,而不管它的功能有多强。大多数消费者都不会买一辆让人觉得难看的车,哪怕这车有一台出色的引擎。因此我们开始了一个项目,是把我们的Smalltalk原生窗口组件移植到Java上去。这个项目是加拿大的Object Technology International小组做的。这个项目获得了成功,被运用在在我们发布的VisualAge Micro Edition产品中,VisualAge Micro Edition后来成为J2ME开发方面一个非常成功的IDE。但是OTI的人发现,Swing在读取Windows事件方面有极严重的缺陷,我们甚至无法进行SWT(S开始是Simple的缩写,不过后来变成了Standard的缩写)和Swing间的互操作。他们在读事件队列的时候用了一种可能留下内存漏洞的方式,所以我们不得不采用我们自己的查询Windows事件队列的循环,以纠正这个错误。我们试了一次又一次让SUN修复这个错误,但Amy就是听不进去,所以我们才决定SWT和AWT / Swing不能共存。我们甚至在SWT中定义了自己的Point和Rectangle类——整个工具包对AWT 或Swing都没有任何依赖。我们把这个工具包放到了Eclipse中,这是一个工具平台,它的总体设计目标就是要战胜Micrsoft和Visual Studio。Eclipse是开源的,所以任何人都可以在上面构建自己的东西,我们已经有像TogetherSoft和Rational这样的公司移植到了上面。我们的竞争者是Microsoft,所以我们所有努力和注意力都是从正面针对Microsoft。

不管怎么说SUN对此非常不满。他们的Netbeans跟Eclipse做的是相同的事,因此他们向IBM高层抱怨。他们认为SWT是要将你绑到Windows上,这纯粹是胡说,因为SWT能通过GTK在Mac
/ Linux上运行,以及一大堆嵌入式平台。他们拒绝让Eclipse获得Java认证,因为里面有原生代码,所以 Eclipse产品必须很小心地使用单词“Java”这个SUN的商标。Eclipse甚至不能把自己称为一个Java IDE,SUN已经威胁过要采取法律行动来制止IBM在任何时候把Eclipse称作一个Java IDE。结果之一就是IBM在Eclipse上创建的GUI设计工具,允许你构建Swing / AWT GUI,却不让你往里面拖放SWT窗口控件。

将SWT 从Eclipse中分离出来是完全可能的,只需要把DLL抠出来放到路径中,并使用窗口组件工具包来给你的银行或者保险或者其他什么应用程序开发GUI。再次说明,我们无法更进一步,因为SUN把我们的双手绑上了。虽然作为Eclipse开放源码协议的一部分,CPL允许我们提供这样的解决方案,但SUN 已经很清楚地表明他们不希望我们这样做。

对于用户社区来说,无论IBM和SUN的最终动机是什么,我发现有一点总是很有趣:喜爱 Swing的人总会说“一旦你花上几年时间去掌握它,你就能正确地使用它”,这基本上是他们在试图证明和维护他们辛苦得来的用途有限的专门技术;而SWT 的拥护者们说的是“哇,这真快,这跟原生的一样,还可以用XP皮肤……它还又轻又小”。有一句话是我喜欢的,我们的一个用户说,Swing就像Java决定不通过操作系统来实现原生的IO,而是通过磁头马达API自己来读磁盘的扇区。Swing基本上就是这样的,它拿着个底层的“paint (Graphics)”方法,自己来绘制所有的窗口组件。
--------------------------------

后记:现在的情况已经有所不同,SWT到底还是单独发布了,VE也承诺在1.0版的时候支持SWT的GUI设计。
 
 
译注:本文来自straight_talking_java@yahoogroups.com讨论组,已经是一年多前的文章。Alan Williamson是Java Developers Journal的编辑,下文来自他在IBM的一个消息来源。SWT和Swing的论争我见过不少,Netbeans和Eclipse的也同样多。译者翻译此文并不是要激起什么争执,也不是支持哪一方(虽然我的确是站在SWT一边的),更不是要攻击Amy。我最重要的理由是,这是一篇有趣的文章。里面有内幕、线人、公司政治、垄断巨头、美女、商界风云……足够拍一出电影。有趣,这就够了。不过此文反映了IBM对Swing的看法和SWT的由来,还是有一点营养的。

原来我郁闷了,SWING原来只是自己画组件的, 自己处理消息的。 看了下AWT的C源码, 发现AWT_COMPONENT所有AWT组件其实还是含有HWND的。 使用AWT的按钮罗,当做一个“Capture Screen”抓屏按钮, 结果用SPY++看,JFrame果然多了个子窗口句柄。 这下可以 continue了吧。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值