在xlib所提供的接口中,没有"控件"这种高级的概念。在Linux系统使用xlib进行GUI应用程序开发,如果我们想要在xlib创建的界面中放置按钮、文本框,我们需要使用xlib提供的接口在界面中自己绘制出控件,并为控件提供鼠标、键盘响应事件。在xlib创建的界面中我们可以采用直接绘制的方式或是子窗口的方式实现这些高级控件。这篇文章中我们将介绍这两种实现"按钮"控件的方式。xlib api提供的绘制文本、绘制矩形、鼠标、键盘这些基本元素,可以认为是七巧板的每一块,我们可以利用七巧板的每一块进行组合形成复杂的图形。
首先我们使用直接在主界面中绘制的方式,绘制一个矩形,在矩形中绘制文本。这样在外观上看起来就是一个简单的按钮了。在具体实现代码之前我们先定义一个结构体用于存储按钮的基本信息如下坐标、大小、文本、颜色等。如下
#include
#include
#include
using namespace ststruct UIButton_s {
int x;
int y;
int width;
int height;
string text;
ulong textcolor;
ulong borderColor;
ulong backgroundColor;
};
x,y用于表示按钮左上角在窗口中的坐标,width、height表示按钮宽度、高度。text表示按钮的文本,这里直接使用c++的string进行存储,主要不想管理动态内存。textcolor和bordercolor、backgroundColor分别用于存储按钮文本、边框的颜色和背景色。
示例代码如下:
#include
#include
#include
using namespace std;
struct UIButton_s {
int x;
int y;
int width;
int height;
string text;
ulong textcolor;
ulong borderColor;
ulong backgroundColor;
};
void DrawButton(struct UIButton_s button,Display *display, Window window ,GC gc) {
// 设置边框颜色
XSetForeground(display, gc, button.borderColor);
// 绘制边框
XDrawRectangle(display, window, gc, button.x, button.y, button.width, button.height);
// 设置填充颜色
XSetForeground(display, gc, button.backgroundColor);
// 填充矩形
XFillRectangle(display, window, gc, button.x+1, button.y+1, button.width-2, button.height-2);
XSetForeground(display,gc, button.textcolor);
XDrawString(display,window,gc, button.x + 30, button.y + 20,button.text.c_str(),button.text.length());
}
int main() {
Display *display = XOpenDisplay(NULL);
Window win = XCreateSimpleWindow(display, DefaultRootWindow(display), 10, 10, 300, 200, 1,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
GC gc = XCreateGC(display, win, 0, 0);
XSelectInput(display, win, ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
XMapWindow(display, win);
struct UIButton_s button{0};
button.x = 50;
button.y = 40;
button.width = 100;
button.height = 30;
button.text = "Button";
button.textcolor = 0;
button.borderColor = 0;
button.backgroundColor = 0xffececec;
while (1) {
XEvent e;
XNextEvent(display, &e);
if (e.type == Expose) {
DrawButton(button,display,win,gc);
}
}
XFreeGC(display,gc);
XDestroyWindow(display,win);
XCloseDisplay(display);
return 0;
}
使用g++或是clang++编译运行以上代码。运行效果如下:
我们的按钮控件显示完成了,但是现在把鼠标移上去,或是点击鼠标没有任何的反应。接下我们为“按钮控件”添加鼠标事件。
在上面我们绘制出了一个按钮,接下来我们给按钮添加当鼠标移动悬停在按钮之上时,让鼠标的光标显示为手形的光标。xlib提供了加载鼠标光标和设置鼠标光标的方法。分别是XCreateFontCursor和XDefineCursor,两个函数的原型如下
extern Cursor XCreateFontCursor(
Display* /* display */,
unsigned int /* shape */
);
extern int XDefineCursor(
Display* /* display */,
Window /* w */,
Cursor /* cursor */
);
加载光标
利用XCreateFontCursor加载一个正常的箭头鼠标光标,一个手形光标。
Cursor normalCursor = XCreateFontCursor(display,XC_arrow);
Cursor hoverCursor = XCreateFontCursor(display,XC_hand1);
接下在while循环中我们需要处理鼠标移动事件,当鼠标移动到按钮上时鼠标显示为手形,离开按钮区域时显示为正常形状。
while (1) {
XEvent e;
XNextEvent(display, &e);
if (e.type == MotionNotify) {
if (e.xmotion.x >= button.x && e.xmotion.x<=button.x+button.width && e.xmotion.y>=button.y && e.xmotion.y<=button.y+button.height) {
XDefineCursor(display,win,hoverCursor);
}else {
XDefineCursor(display,win,normalCursor);
}
}
}
完整代码如下:
#include
#include
#include
#include
using namespace std;
struct UIButton_s {
int x;
int y;
int width;
int height;
string text;
ulong textcolor;
ulong borderColor;
ulong backgroundColor;
};
void DrawButton(struct UIButton_s button,Display *display, Window window ,GC gc) {
// 设置边框颜色
XSetForeground(display, gc, button.borderColor);
// 绘制边框
XDrawRectangle(display, window, gc, button.x, button.y, button.width, button.height);
// 设置填充颜色
XSetForeground(display, gc, button.backgroundColor);
// 填充矩形
XFillRectangle(display, window, gc, button.x+1, button.y+1, button.width-2, button.height-2);
XSetForeground(display,gc, button.textcolor);
XDrawString(display,window,gc, button.x + 30, button.y + 20,button.text.c_str(),button.text.length());
}int main() {
Display *display = XOpenDisplay(NULL);
Cursor normalCursor = XCreateFontCursor(display,XC_arrow);
Cursor hoverCursor = XCreateFontCursor(display,XC_hand1);
Window win = XCreateSimpleWindow(display, DefaultRootWindow(display), 10, 10, 300, 200, 1,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
GC gc = XCreateGC(display, win, 0, 0);
XSelectInput(display, win, ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
XMapWindow(display, win);
struct UIButton_s button{0};
button.x = 50;
button.y = 40;
button.width = 100;
button.height = 30;
button.text = "Button";
button.textcolor = 0;
button.borderColor = 0;
button.backgroundColor = 0xffececec;
while (1) {
XEvent e;
XNextEvent(display, &e);
if (e.type == Expose) {
DrawButton(button,display,win,gc);
}
if (e.type == MotionNotify) {
if (e.xmotion.x >= button.x && e.xmotion.x<=button.x+button.width && e.xmotion.y>=button.y && e.xmotion.y<=button.y+button.height) {
XDefineCursor(display,win,hoverCursor);
}else {
XDefineCursor(display,win,normalCursor);
}
}
}
XFreeCursor(display,normalCursor);
XFreeCursor(display,hoverCursor);
XFreeGC(display,gc);
XDestroyWindow(display,win);
XCloseDisplay(display);
return 0;
}
编译运行后,当我们把鼠标移动到按钮上时,鼠标显示为手形,在按钮之外显示为正常形状。
改变按钮颜色
当鼠标悬停在按钮之上时,我们可以改变按钮的背景颜色、边框和文本颜色。来增强用户交互体验效果。
if (e.type == MotionNotify) {
if (e.xmotion.x >= button.x && e.xmotion.x<=button.x+button.width && e.xmotion.y>=button.y && e.xmotion.y<=button.y+button.height) {
XDefineCursor(display,win,hoverCursor);
button.borderColor = 0x555555; //鼠标悬停,改变边框,背景色
button.backgroundColor = 0xfff6f6f6;
DrawButton(button,display,win,gc);
}else {
XDefineCursor(display,win,normalCursor);
button.borderColor = 0;
button.backgroundColor = 0xffececec;
DrawButton(button,display,win,gc);
}
}
事件处理函数中的代码越来越多,对代码进行重构提取出DrawNormalButton和DrawHoverButton两个函数用于绘制正常情况情况下的按钮和鼠标悬停时的按钮。完整可运行代码如下
#include
#include
#include
#include
using namespace std;
struct UIButton_s {
int x;
int y;
int width;
int height;
string text;
ulong textcolor;
ulong borderColor;
ulong backgroundColor;
};
void DrawButton(struct UIButton_s button,Display *display, Window window ,GC gc) {
// 设置边框颜色
XSetForeground(display, gc, button.borderColor);
// 绘制边框
XDrawRectangle(display, window, gc, button.x, button.y, button.width, button.height);
// 设置填充颜色
XSetForeground(display, gc, button.backgroundColor);
// 填充矩形
XFillRectangle(display, window, gc, button.x+1, button.y+1, button.width-2, button.height-2);
XSetForeground(display,gc, button.textcolor);
XDrawString(display,window,gc, button.x + 30, button.y + 20,button.text.c_str(),button.text.length());
}
static void DrawNormalButton(UIButton_s button, Display *display, Window window,GC gc) {
button.borderColor = 0;
button.backgroundColor = 0xffececec;
button.textcolor = 0;
DrawButton(button,display,window,gc);
}
static void DrawHoverButton(UIButton_s button,Display *display, Window window,GC gc) {
button.borderColor = 0x555555; //鼠标悬停,改变边框,背景色
button.backgroundColor = 0xfff6f6f6;
button.textcolor = 0;
DrawButton(button,display,window,gc);
}
int main() {
Display *display = XOpenDisplay(NULL);
Cursor normalCursor = XCreateFontCursor(display,XC_arrow);
Cursor hoverCursor = XCreateFontCursor(display,XC_hand1);
Window win = XCreateSimpleWindow(display, DefaultRootWindow(display), 10, 10, 300, 200, 1,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
GC gc = XCreateGC(display, win, 0, 0);
XSelectInput(display, win, ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
XMapWindow(display, win);
struct UIButton_s button{0};
button.x = 50;
button.y = 40;
button.width = 100;
button.height = 30;
button.text = "Button";
button.textcolor = 0;
button.borderColor = 0;
button.backgroundColor = 0xffececec;
while (1) {
XEvent e;
XNextEvent(display, &e);
if (e.type == Expose) {
DrawNormalButton(button,display,win,gc);
}
if (e.type == MotionNotify) {
if (e.xmotion.x >= button.x && e.xmotion.x<=button.x+button.width && e.xmotion.y>=button.y && e.xmotion.y<=button.y+button.height) {
XDefineCursor(display,win,hoverCursor);
DrawHoverButton(button,display,win,gc);
}else {
XDefineCursor(display,win,normalCursor);
DrawNormalButton(button,display,win,gc);
}
}
}
XFreeCursor(display,normalCursor);
XFreeCursor(display,hoverCursor);
XFreeGC(display,gc);
XDestroyWindow(display,win);
XCloseDisplay(display);
return 0;
}
编译运行以上代码,随着鼠标移入、移出按钮区域,鼠标的光标我按钮的颜色跟着发生改变。
当鼠标在按钮区域按下左键时,我们可以再次改变按钮的显示颜色。来进一步加强用户交互效果。同时当鼠标在按钮区域松开按钮时,我们应该让按钮恢复到悬停状态。这里需要处理鼠标左键单击和松开事件。处理逻辑如下
if (e.type == ButtonPress && e.xbutton.button == Button1) {
if (e.xmotion.x >= button.x && e.xmotion.x<=button.x+button.width && e.xmotion.y>=button.y && e.xmotion.y<=button.y+button.height) {
//鼠标单击事件
}
}
if (e.type == ButtonRelease && e.xbutton.button == Button1) {
if (e.xmotion.x >= button.x && e.xmotion.x<=button.x+button.width && e.xmotion.y>=button.y && e.xmotion.y<=button.y+button.height) {
//鼠标单击后松开事件
}
}
完整代码如下:
#include
#include
#include
#include
using namespace std;
struct UIButton_s {
int x;
int y;
int width;
int height;
string text;
ulong textcolor;
ulong borderColor;
ulong backgroundColor;
};
void DrawButton(struct UIButton_s button,Display *display, Window window ,GC gc) {
// 设置边框颜色
XSetForeground(display, gc, button.borderColor);
// 绘制边框
XDrawRectangle(display, window, gc, button.x, button.y, button.width, button.height);
// 设置填充颜色
XSetForeground(display, gc, button.backgroundColor);
// 填充矩形
XFillRectangle(display, window, gc, button.x+1, button.y+1, button.width-2, button.height-2);
XSetForeground(display,gc, button.textcolor);
XDrawString(display,window,gc, button.x + 30, button.y + 20,button.text.c_str(),button.text.length());
}
static void DrawNormalButton(UIButton_s button, Display *display, Window window,GC gc) {
button.borderColor = 0;
button.backgroundColor = 0xffececec;
button.textcolor = 0;
DrawButton(button,display,window,gc);
}
static void DrawHoverButton(UIButton_s button,Display *display, Window window,GC gc) {
button.borderColor = 0x555555; //鼠标悬停,改变边框,背景色
button.backgroundColor = 0xfff6f6f6;
button.textcolor = 0;
DrawButton(button,display,window,gc);
}
static void DrawPushedButton(UIButton_s button,Display *display,Window window, GC gc) {
button.borderColor = 0xcccccc;
button.backgroundColor = 0xffffffff;
button.textcolor = 0;
DrawButton(button,display,window,gc);
}
int main() {
Display *display = XOpenDisplay(NULL);
Cursor normalCursor = XCreateFontCursor(display,XC_arrow);
Cursor hoverCursor = XCreateFontCursor(display,XC_hand1);
Window win = XCreateSimpleWindow(display, DefaultRootWindow(display), 10, 10, 300, 200, 1,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
GC gc = XCreateGC(display, win, 0, 0);
XSelectInput(display, win, ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
XMapWindow(display, win);
struct UIButton_s button{0};
button.x = 50;
button.y = 40;
button.width = 100;
button.height = 30;
button.text = "Button";
button.textcolor = 0;
button.borderColor = 0;
button.backgroundColor = 0xffececec;
while (1) {
XEvent e;
XNextEvent(display, &e);
if (e.type == Expose) {
DrawNormalButton(button,display,win,gc);
}
if (e.type == MotionNotify) {
if (e.xmotion.x >= button.x && e.xmotion.x<=button.x+button.width && e.xmotion.y>=button.y && e.xmotion.y<=button.y+button.height) {
XDefineCursor(display,win,hoverCursor);
DrawHoverButton(button,display,win,gc);
}else {
XDefineCursor(display,win,normalCursor);
DrawNormalButton(button,display,win,gc);
}
}
if (e.type == ButtonPress && e.xbutton.button == Button1) {
if (e.xmotion.x >= button.x && e.xmotion.x<=button.x+button.width && e.xmotion.y>=button.y && e.xmotion.y<=button.y+button.height) {
DrawPushedButton(button,display,win,gc);
}
}
if (e.type == ButtonRelease && e.xbutton.button == Button1) {
if (e.xmotion.x >= button.x && e.xmotion.x<=button.x+button.width && e.xmotion.y>=button.y && e.xmotion.y<=button.y+button.height) {
DrawHoverButton(button,display,win,gc);
}
}
}
XFreeCursor(display,normalCursor);
XFreeCursor(display,hoverCursor);
XFreeGC(display,gc);
XDestroyWindow(display,win);
XCloseDisplay(display);
return 0;
}
使用g++编译运行以上代码。运行结果如下:
上面几步我们通过直接在主界面上绘制,并处理主界面的鼠标事件实现按钮控件。我们也可以使用子窗口的方式实现按钮控件。
在主窗口上创建一个按钮子窗口的代码逻辑如下:
Window CreateButtonWindow(Display *display, Window parent, UIButton_s button) {
// 创建子窗口作为按钮
Window buttonWindow = XCreateSimpleWindow(display, parent, button.x, button.y, button.width+1, button.height+1, 0,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
XSelectInput(display, buttonWindow, ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
return buttonWindow;
}
最后关于事件的处理逻辑,前面的示例中我们所有的鼠标事件处理逻辑都是在主窗口中实现的。那时只有一个窗口,但是现在有两窗口,我们需要判断鼠标的移动、单击操作是由哪个窗口触发的。通过子窗口和直接绘制在实现逻辑上没有太大区别,在主窗口直接绘制的方式中我们需要根据当前鼠标的坐标位置来判断鼠标的操作是作用中按钮矩形区域内,而采用子窗口的方式,当我们按下按钮时,由X Server产生子窗口的鼠标事件。子窗口实现代码逻辑如下
#include
#include
#include
#include
using namespace std;
struct UIButton_s {
int x;
int y;
int width;
int height;
string text;
ulong textcolor;
ulong borderColor;
ulong backgroundColor;
};
void DrawButton(struct UIButton_s button,Display *display, Window window ,GC gc) {
// 设置边框颜色
XSetForeground(display, gc, button.borderColor);
// 绘制边框
XDrawRectangle(display, window, gc, 0, 0, button.width, button.height);
// 设置填充颜色
XSetForeground(display, gc, button.backgroundColor);
// 填充矩形
XFillRectangle(display, window, gc, 1, 1, button.width-2, button.height-2);
XSetForeground(display,gc, button.textcolor);
XDrawString(display,window,gc, 30, 20,button.text.c_str(),button.text.length());
}
static void DrawNormalButton(UIButton_s button, Display *display, Window window) {
button.borderColor = 0;
button.backgroundColor = 0xffececec;
button.textcolor = 0;
GC gc = XCreateGC(display,window,0,nullptr);
DrawButton(button,display,window,gc);
XFreeGC(display,gc);
}
static void DrawHoverButton(UIButton_s button,Display *display, Window window) {
button.borderColor = 0x555555; //鼠标悬停,改变边框,背景色
button.backgroundColor = 0xfff6f6f6;
button.textcolor = 0;
GC gc = XCreateGC(display,window,0,nullptr);
DrawButton(button,display,window,gc);
XFreeGC(display,gc);
}
static void DrawPushedButton(UIButton_s button,Display *display,Window window) {
button.borderColor = 0xcccccc;
button.backgroundColor = 0xffffffff;
button.textcolor = 0;
GC gc = XCreateGC(display,window,0,nullptr);
DrawButton(button,display,window,gc);
XFreeGC(display,gc);
}
Window CreateButtonWindow(Display *display, Window parent, UIButton_s button) {
// 创建子窗口作为按钮
Window buttonWindow = XCreateSimpleWindow(display, parent, button.x, button.y, button.width+1, button.height+1, 0,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
XSelectInput(display, buttonWindow, ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
return buttonWindow;
}
int main() {
Display *display = XOpenDisplay(NULL);
Cursor normalCursor = XCreateFontCursor(display,XC_arrow);
Cursor hoverCursor = XCreateFontCursor(display,XC_hand1);
Window win = XCreateSimpleWindow(display, DefaultRootWindow(display), 10, 10, 300, 200, 1,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
XSelectInput(display, win, ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
XMapWindow(display, win);
struct UIButton_s button{0};
button.x = 50;
button.y = 40;
button.width = 100;
button.height = 30;
button.text = "Button";
button.textcolor = 0;
button.borderColor = 0;
button.backgroundColor = 0xffececec;
Window buttonWindow = CreateButtonWindow(display,win,button);
XMapWindow(display,buttonWindow);
while (1) {
XEvent e;
XNextEvent(display, &e);
if (e.type == Expose && e.xexpose.window == win) {
DrawNormalButton(button,display,buttonWindow);
}
if (e.type == MotionNotify) {
if (e.xmotion.window == buttonWindow) {
XDefineCursor(display,win,hoverCursor);
DrawHoverButton(button,display,buttonWindow);
}else if (e.xmotion.window == win){
XDefineCursor(display,win,normalCursor);
DrawNormalButton(button,display,buttonWindow);
}
}
if (e.type == ButtonPress && e.xbutton.button == Button1 && e.xbutton.window == buttonWindow) {
DrawPushedButton(button,display,buttonWindow);
}
if (e.type == ButtonRelease && e.xbutton.button == Button1 && e.xbutton.window == buttonWindow) {
DrawHoverButton(button,display,buttonWindow);
}
}
XFreeCursor(display,normalCursor);
XFreeCursor(display,hoverCursor);
XDestroyWindow(display,buttonWindow);
XDestroyWindow(display,win);
XCloseDisplay(display);
return 0;
}
使用g++编译以上代码,运行结果与直接在主窗口绘制没有本质区别。
如果我们把while(1)循环中的代码提取出来放到一个函数或类中,就实现了一个窗体程序的事件主循环功能,这个功能就是gtk中gtk_main,Qt中QApplication::exec()所完成的工作。把所有绘制Button、处理Button事件的代码进行封装放到c++的类中,定义一个虚函数OnClick,在while循环中当检测到按钮事件时,调用button的OnClick函数,这样可以实现一个可复用的按钮类,这也是Qt中QPushButton的基本原理。