Wallpaper的透视图实际上包含了两张图,一张是非透视图,即正常情况下能够被看到的图片,另一张是透视图,即鼠标移到上面才会部分显示的图片.
本文将使用Qt框架实现类似效果
 最终效果

 代码
 桌面子窗体
将自己的窗体设置成桌面的子窗体,其原理在之前的Wallpaper文章中已经介绍过,故直接放出代码,不再解释.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | void SetFather(HWND child){     HWND hwnd = GetBackground();     if(hwnd == NULL){         SendMessage(hwnd,0x052C,0,0);         hwnd = GetBackground();     }     if(hwnd != NULL){         SetParent(child,hwnd);     } }   HWND GetBackground(){     HWND hwnd = FindWindowA("progman","Program Manager");     HWND worker = NULL;     do{         worker = FindWindowExA(NULL,worker,"workerW",NULL);         if(worker != NULL){             char buff[200] = {0};             int ret = GetClassNameA(worker,(PCHAR)buff,sizeof(buff)*2);             if(ret == 0){                 return NULL;             }         }         if(GetParent(worker) == hwnd){             return worker;         }     }while(worker != NULL);     return NULL; }
  | 
 鼠标事件捕捉
由于将窗体设置成了背景层的子窗体,而背景层上面还有一层图标层,所以我们自己写的窗体将无法接受鼠标事件,也就无法对鼠标移动做出反应,因此我们需要使用HOOK拦截系统的鼠标事件,HOOK程序将会在鼠标移动事件发生之前优先执行,这样就可以捕捉到鼠标移动事件.
1 2 3 4 5 6 7 8 9 10 11 12
   | HHOOK hook; void GetHook(){     hook = SetWindowsHookEx(WH_MOUSE_LL,mouseProc,GetModuleHandle(NULL),NULL); } LRESULT CALLBACK mouseProc(int nCode,WPARAM wParam,LPARAM lParam){     if(nCode == HC_ACTION){         if(wParam == WM_MOUSEMOVE){             main->repaint();         }     }     return false; }
   | 
 绘图与蒙版
实现透视效果的原理是先绘制非透视图,然后根据鼠标位置绘制透视图的一小部分,为了方便,我们称非透视图为“背景图”,称透视图为“前景图”,因为透视图是覆盖在非透视图上面的.
全局变量
1 2 3 4 5 6 7 8 9 10 11
   | QPixmap *foreground; QPixmap *background; QPixmap *cut; QBitmap *maskBitmap; QPixmap *maskPic; QColor *color; int x1,x2,y1,y2; int startX,startY; int radius; int Desktop_width,Desktop_height; bool repaintable = true;
   | 
LoadPicture()函数用来加载图片
1 2 3 4 5 6 7 8 9 10 11 12 13
   | void Widget::LoadPicture(QString fore,QString back){     foreground = new QPixmap();     background = new QPixmap();     cut = new QPixmap();     foreground->load(fore);     background->load(back);     QDesktopWidget *desktop = QApplication::desktop();     QRect rect = desktop->screenGeometry();     Desktop_width = rect.width();     Desktop_height = rect.height();     *foreground = foreground->scaled(rect.width(),rect.height(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation);     *background = background->scaled(rect.width(),rect.height(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); }
  | 
在SetRadius()函数中,需要初始化蒙版,先定义一个长为2*radius的正方形QBitmap,然后画上颜色为color(黑色)的圆形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | void Widget::SetRadius(int r){     if(radius == r) return;     radius = r;     QSize size(2*radius, 2*radius);     maskBitmap = new QBitmap(size);     maskPic = new QPixmap(size);     color = new QColor(0,0,0);     QPainter painter(maskBitmap);     painter.setRenderHint(QPainter::Antialiasing);     painter.setRenderHint(QPainter::SmoothPixmapTransform);     painter.fillRect(0, 0, size.width(), size.height(), Qt::white);     painter.setBrush(*color);     painter.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius);     repaint(); }
  | 
根据鼠标位置计算出正确的区域大小,这是一个以鼠标为中心,2*radius为长的正方形,(x1,y1)是左上角坐标,(x2,y2)是右下角坐标,这两个坐标构成了一个裁剪框,但是这个坐标有可能会超出屏幕范围,一旦超出,就会导致裁剪的时候出现图片拉伸,所以需要加上判断语句来限制大小.
接着根据限制后的大小和位置,在前景图(透视图)上裁剪出相应区域.
startX和startY是蒙版的起始位置,如果(x1,y1)超出屏幕区域,就意味着裁剪框将不是正方形,而蒙版却是正方形的,所以必须对蒙版也进行裁剪,使蒙版的大小恰好等于裁剪框的大小.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
   | void Widget::paintEvent(QPaintEvent *){     GetCursorPos(&p);     x1 = p.x - radius;     y1 = p.y - radius;     x2 = p.x + radius;     y2 = p.y + radius;     if(x1 < 0) {         startX = -x1;         x1 = 0;     }else{         startX = 0;     }     if(y1 < 0) {         startY = -y1;         y1 = 0;     }else{         startY = 0;     }     if(x2 > Desktop_width) x2 = Desktop_width;     if(y2 > Desktop_height) y2 = Desktop_height;     *cut = foreground->copy(x1,y1,x2-x1,y2-y1);     *maskPic = maskBitmap->copy(startX,startY,x2-x1,y2-y1);     cut->setMask(maskPic->createMaskFromColor(*color,Qt::MaskOutColor));     QPainter painter(this);     painter.drawPixmap(0,0,width(),height(),*background);     painter.drawPixmap(x1,y1,x2-x1,y2-y1,*cut); }
  | 
 内存释放
HOOK会降低计算机效率,所以在使用完毕后必须释放,当关闭程序时windows系统会自动释放HOOK,但是我们希望用户在主动关闭壁纸但是还未退出程序时也要释放掉HOOK,同时摧毁窗体.
Dispose()函数的用途就是摧毁窗体,然后释放HOOK
1 2 3 4 5
   | void Widget::Dispose(){     this->hide();     UnhookWindowsHookEx(hook);     this->close(); }
  | 
 任务栏角标
之前文章已经介绍过,这里直接放出代码.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | void Set::AddTray(){          tray.setToolTip("X-Ray");     tray.setIcon(QIcon(":Icon/ico.ico"));     QMenu *menu = new QMenu();     QAction *action_showDialog = new QAction("打开主窗口");     QAction *action_free = new QAction("释放内存");     QAction *action_exit = new QAction("退出");     menu->addAction(action_showDialog);     menu->addAction(action_free);     menu->addSeparator();     menu->addAction(action_exit);     tray.setContextMenu(menu);     connect(action_showDialog,SIGNAL(triggered(bool)),this,SLOT(ShowDialog()));     connect(action_free,SIGNAL(triggered(bool)),this,SLOT(on_button_free_clicked()));     connect(action_exit,SIGNAL(triggered(bool)),this,SLOT(on_button_exit_clicked()));     tray.show(); }
  |