DevGUI 使用方法

· · 个人记录

DevGUI 使用方法

\leftarrow 返回 DevGUI 说明

一、最简单的 DevGUI 程序

安装完 DevGUI 后,打开 Dev-C++,新建源代码,输入以下程序:

#include <dgui.h>
int main(){
    setup();
    keepwindow();
    return 0;
}

运行效果:

那个白色的窗口就是著名的 GUI 界面。

这里面的 setup 是创建窗口,keepwindow 是等待窗口关闭后才返回。

就这么简单。

二、DevGUI 基本结构

DevGUI 图形库主要由几个头文件构成,所有内容都在一个名字空间 dgui_ns 中(v2.1 及之前为 dgui,旧版程序可以使用此方法)。

dgui.h 里有 using namespace dgui_ns;,所以程序中不需要写。如果不想自动包含,可以 #include <dgui.h> 之前 写上以下代码:

#define _DGUI_NOUSINGNAMESPACE_

DevGUI 图形库自动包含以下标准库头文件:

#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <cstdarg>
#include <cmath>
#include <vector>
#include <string>
#include <queue>
#include <map>

自动包含以下 Windows 头文件:

#include <process.h>
#include <windows.h>
#include <mmsystem.h>

自动 using 以下内容:

using std::vector;
using std::string;
using std::queue;
using std::map;

(不包含 using namespace std;

DevGUI 有两种模式:源代码和项目,Dev-C++ 直接新建出来的代码就是源代码,Dev-C++ 的 “文件 -> 新建” 里还可以新建项目。

源代码比较常用,因此大部分内容在源代码中测试。如果要在项目中使用,用法基本相同,但是需要 #include <dgui.h> 之前 加上以下代码:

#define PROJECT

注意:这个只能在项目中使用,在源代码中使用会导致大量编译错误。

三、DevGUI 中的函数等

注意:只列出常用的,不是 DevGUI 的所有内容。

1.窗口基本操作

setup 函数——创建窗口

setup 函数有多种重载的形式:

void setup(int x,int y,int w,int h,string title);
指定窗口横纵坐标、宽度、高度、标题。

void setup(int x,int y,int w,int h);
指定窗口横纵坐标、宽度、高度,标题为 无标题

void setup(int w,int h,string title);
指定窗口宽度、高度、标题,使用默认横纵坐标。

void setup(int w,int h);
指定窗口宽度、高度,使用默认横纵坐标,标题为 无标题

void setup(string title);
指定窗口标题,使用默认横纵坐标、宽度、高度。

void setup();
不指定窗口的任何信息,使用默认横纵坐标、宽度、高度,标题为 无标题

修改窗口属性的函数

void settitle(string wndtitle);
修改窗口标题。

void showwindow(int stype);
显示或隐藏窗口。
其中的 stype 有如下取值(定义在系统 winuser.h 的 143 \sim 157 行):

#define SW_HIDE 0
#define SW_SHOWNORMAL 1
#define SW_NORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
#define SW_MAXIMIZE 3
#define SW_SHOWNOACTIVATE 4
#define SW_SHOW 5
#define SW_MINIMIZE 6
#define SW_SHOWMINNOACTIVE 7
#define SW_SHOWNA 8
#define SW_RESTORE 9
#define SW_SHOWDEFAULT 10
#define SW_FORCEMINIMIZE 11
#define SW_MAX 11

它们的作用就是字面意思。

void showwindow();
显示窗口。

void movewindow(int x,int y,int w,int h);
移动窗口到指定坐标,并将窗口宽度、高度设置为指定数值。

void poswindow(int x,int y);
移动窗口到指定坐标。

void resizewindow(int w,int h);
设置窗口宽度、高度。

其他

void hideconsole();
隐藏控制台。
旧版 bug:旧版中如果屏幕上有其他控制台窗口,这个函数可能会隐藏其他控制台,最新版无此 bug。

bool windowexist();
判断程序创建的窗口是否存在。存在返回 1,不存在返回 0

void keepwindow();
一直等待,直到窗口被关闭。

long long winfo(string key);
获取窗口的某一项信息。
有效的调用方式有:

winfo("x");//窗口横坐标
winfo("x");//窗口纵坐标
winfo("window-x");//窗口横坐标 同 "x"
winfo("window-x");//窗口纵坐标 同 "y"
winfo("client-x");//窗口客户区横坐标
winfo("client-y");//窗口客户区纵坐标
winfo("width");//窗口客户区宽度
winfo("height");//窗口客户区高度

如果 key 无效,会返回 -1

void closewindow();
关闭窗口。

2.画图操作

数据类型

DPOINT 表示一个点

DPOINTS 表示一个 DPOINT 类型的数组

DRECT 表示一个矩形

DCOLORDCOL 表示一种颜色(相当于 unsinged int

函数

void ellipse(int x,int y,int r1,int r2);
指定中心坐标、横向半径、纵向半径,画椭圆。

void circle(int x,int y,int r);
指定圆心坐标、半径,画圆。

void rectangle(DPOINT p1,DPOINT p2);
指定左上角、右下角的点,画矩形。

void rectangle(int x,int y,int a,int b);
指定左上角坐标和横向、纵向边长,画矩形。

void square(int x,int y,int a);
指定左上角坐标和边长,画正方形。

void polygon(int len,DPOINTS pnts);
指定点数和各点,画多边形。

void poly(int len,...);
指定点数和各点坐标(两个数字一组),画多边形。

void line(int x1,int y1,int x2,int y2);
指定左上角、右下角坐标,画线段。

void polyl(int len,...);
指定点数和各点坐标(两个数字一组),画折线。

void textout(int x,int y,string txt);

指定左上角坐标和一个字符串,输出文本。 `void textoutf(int x,int y,string fmt,...);` 指定左上角坐标,格式化输出文本。(`fmt` 及之后的参数的写法类似于 `printf`) `void setpxcol(int x,int y,DCOLOR col);` 将指定坐标的像素修改为指定颜色。 `DCOLOR getRGB(int r,int g,int b);` 根据 RGB 值获取颜色。 `DCOLOR getcolor(string colname);` 获取一种颜色。目前支持如下写法: + `getcolor("#XXXXXX")` 其中 `XXXXXX` 为颜色的十六进制表示,字母必须大写 + `getcolor("#XYZ")` 相当于 `getcolor("#XXYYZZ")` + `getcolor("NAME")` 其中 `NAME` 为颜色的英文名,可以参考 [DevGUI 颜色名称表](https://www.luogu.com.cn/paste/7u8wg5jk) 或 DevGUI 中 duser.h 中的 `initcols` 函数。不区分大小写,忽略空格、Tab、换行符。 `DCOLOR makecolor(string colname);` 同 `getcolor`。 `DCOLOR getpxcol(int x,int y);` 获取指定坐标的像素的颜色。 `int colordif(DCOLOR a,DCOLOR b);` 计算 DevGUI 中两种颜色的差异程度,范围为 $[0,25500]$。 `fillarea` 中如果两种颜色的差异小于等于 `tolerance` 就会认为它们相同。 `void fillarea(int x,int y,int tolerance=0);` 指定开始点的坐标、容错程度(不写默认为 $0$),填充区域。 `void fillrect(int x,int y,int w,int h);` 填充矩形区域。 `void clear();` 清空窗口。 `void setpen(int type,DCOLOR col,int wgt);` 设置图形边框样式、颜色、粗细。 边框样式包括: ```cpp #define PNTYPE_TRANSPARENT 0 //无边框 #define PNTYPE_SOLID 1 //实线 #define PNTYPE_DASH 2 //虚线 #define PNTYPE_DOT 3 //点线 ``` `void setpentype(int type);` 设置图形边框样式。 `void setpencolor(DCOLOR col);` 设置图形边框颜色。 `void setpenwidth(int wgt);` 设置图形边框粗细。 `void setfillcolor(DCOLOR col);` 设置图形填充颜色。 `void setfont(string name,int w,int h,int weight,bool ita,bool und,bool del);` 设置字体名称、宽度、高度、粗细、是否倾斜、是否加下划线、是否加删除线。 `void setfontname(string name);` 设置字体名称。 ### 3.图片操作 #### 数据类型 `BMP`(又称 `IMAGE`、`PICTURE`、`DGUIPIC`)表示一张图片的信息 + `int w` 图片宽度 + `int h` 图片高度 + `HBITMAP hbm` 图片句柄(WinAPI 相关) + `bool valid();` 判断图片是否有效 + `void del();` 释放图片占用的系统资源(一个 `BMP` 在作用域结束时会自动释放) **注意:由于 `BMP` 自动释放,如果函数中用 `BMP` 作为参数,请使用指针或引用。** #### 函数 `void loadbmp(BMP &buf,string path,int w,int h);` 指定路径、宽度、高度,将图片文件读取到 `BMP` 类型的变量中。 第一个参数传入的是 `BMP` 类型变量的引用,也就是直接传入变量名,没有取地址符等。 **只支持 `bmp` 文件** `void drawbmp(const BMP &bmp,int x,int y);` 指定左上角坐标,在窗口上输出图片。 `void drawbmppara(const BMP &bmp,DPOINT p1,DPOINT p2,DPOINT p3);` 指定左上角、右上角、左下角坐标,把图片输出到平行四边形中。 `void drawbmprotate(const BMP &bmp,int x,int y,int deg);` 指定左上角坐标,把图片绕中心顺时针旋转 $\deg^{\circ}$ 后输出。 `void drawbmpscale(const BMP &bmp,int x,int y,int w,int h);` 指定左上角坐标和缩放后长宽,把图片缩放后输出。 `void drawbmpmirrorx(const BMP &bmp,int x,int y);` 指定左上角坐标,把图片水平翻转后输出。 `void drawbmpmirrory(const BMP &bmp,int x,int y);` 指定左上角坐标,把图片竖直翻转后输出。 `void windowbmp(BMP &buf,int x,int y,int w,int h);` 将整个窗口客户区中的一块区域存储到 `BMP` 变量。 `bool savebmp(const BMP &bmp,string path);` 把图片保存到文件里。 ### 4.事件操作 DevGUI 用“事件”实现交互功能。 #### 数据类型 `EVENT`:就是 `std::string`,表示事件名称。 `EPARA`:事件附带的参数,实际上是 `void*`,但在不同事件中有不同含义。 `EFUNC`:表示一个可以绑定到事件的函数,就是 `void(*)(EPARA)` 类型。 #### 函数 `void bind(EVENT ev,EFUNC fun);` 将函数绑定到一个事件。事件产生时会调用函数。 `void syncevent(EVENT ev,EPARA para);` 同步触发一个事件。(绑定的函数返回之后这个函数才会返回) `void asyncevent(EVENT ev,EPARA para);` 异步触发一个事件。(使用多线程,调用之后立即返回,绑定的函数在新的线程中运行) `T transepara(EPARA ep);` 将 `EPARA` 类型转换成其他类型。 例如,转 `int`:`transepara<int>(p);` #### 系统自动触发的事件 见 <https://www.luogu.com.cn/paste/vpcptoi2>。 ### 5.滤镜操作 #### 数据类型 `AREADATA`:指针,指向一个存储区域信息的 `AREADADTAMEM` 结构体。 #### 函数 `DCOLOR &getpxfromarea(AREADATA dta,int x,int y);` 获取区域中某个像素的颜色。 `void copyarea(AREADATA src,AREADATA buf);` 复制区域信息。 `void brighten(int x,int y,int w,int h,int deg);` 将指定区域变亮。前四个参数指定区域左上角坐标、宽度、高度,最后一个参数是变亮的程度。 `void darken(int x,int y,int w,int h,int deg);` 将指定区域变暗。`deg` 是变暗的程度。 `void grey(int x,int y,int w,int h);` 将指定区域变为黑白。 `void mosaic(int x,int y,int w,int h,int deg);` 将指定区域变为马赛克。`deg` 是马赛克中每一个正方形的边长。 `void blur(int x,int y,int w,int h,int deg);` 将指定区域变模糊。`deg` 是模糊程度。 #### 自定义滤镜 `void dofilter(FilterT fltr,int x,int y,int w,int h);` 运行自定义滤镜,第一个参数是滤镜,后面指定一个区域。 滤镜的定义方法类似于 `sort` 自定义比较方式。滤镜可以有: 1. 滤镜函数 自定义一个 `void xxx(AREADATA dat);` 这样函数,里面对传入的区域进行操作。 2. 滤镜对象 用结构体或者类,重载 `operator()` 运算符,类型、功能同滤镜函数。然后用这个结构体或类定义一个对象,就可以作为滤镜。 ### 6.菜单操作 #### 基本信息 DevGUI 中菜单用多叉树存储。 一般每一个菜单项上都有文字,是一个字符串。叶子结点的菜单项还可以有命令,命令是 `int` 类型,点击后触发异步事件 `Button-XX` 其中 `XX` 为设置的命令。 例如,一个菜单项命令为 $123$,点击后就会触发 `Button-123` 事件。 某些菜单中间的灰色分割线也视为菜单项。 对于非叶子结点,忽略命令。 对于根结点,忽略文字、命令。 对于分割线,忽略文字、命令。 #### 数据类型 `DMENUNODE` 菜单节点 `MENU`(又称 `DMENUTREE`、`DMENU`) 是指向 `DMENUNODE` 的指针,表示一棵树、一个菜单 如果 `m` 是 `MENU` 类型, - `m->child(idx)` 获取某一个子菜单,编号从 $0$ 开始 - `m->children()` 返回包含所有子菜单的 `vector` - `m->chcnt()` 返回子菜单数量 - `m->leaf()` 判断是否是叶子结点 - `m->settext(str)` 设置菜单项上的文字(`&` + 一个字符 可以实现字符下加下划线,并作为 `Alt` + 字母 的快捷键) - `m->setcmd(c)` 设置菜单项对应的命令 #### 函数 `void newmenu(string txt,uint cmd);` 指定文字、命令,创建菜单结点。 `void newmenu(string txt);` 指定文字,命令为 $0$,创建菜单结点。 `void newseparator();` 创建分割线菜单结点。 `void setmenu(MENU mn);` 将菜单应用到窗口。 `void delmenu(MENU mn);` 删除一个菜单结点及其所有子树,释放内存。 ### 7.网络操作 `int downloadfile(string url,string path);` 根据 URL 从网络上下载文件到指定路径。 ### 8.脚本操作 `void runcmd(string cmd);` 运行 cmd 命令。 `string runvbs(string prog);` 运行 vbs 命令,运行命令前会先在脚本里定义一个变量 `ret`,vbs 命令运行完成后函数会以字符串的方式返回这个变量,命令可以通过设置这个与主程序传递信息。 `string vbsinput(string info,string title="");` 用 vbs 脚本实现的输入框。第一个参数指定输入框文字提示内容;第二个参数指定输入框标题,可以不写,默认为空。返回输入的字符串,点击“取消”会返回空字符串。 `int vbsmsgbox(string info,string title,int mstyle);` 用 vbs 脚本实现的消息框。参数分别指定消息内容、标题、样式,返回对按钮的选择情况。 ### 9.控制台操作 $\color{red}{\bold{\text{仅限项目中使用。}}}

void openconsole(void);
创建控制台。

void consoleout(string str);
在控制台中输出字符串。

void cputs(const char *s);
相当于 puts

char cputc(char c);
相当于 putchar

int cprintf(const char *fmt,...);
相当于 printf

注意:关于速度,这些函数都是用 consoleout 实现的,理论上 consoleout 最快。

10.音乐操作

注意:如果涉及到音乐文件,只支持 .wav 文件,其他格式请转换成 WAV 后使用。

目前我发现比较好用的是 这个网站。

void beep(int freq,int tim);
发出频率为 \operatorname{freq}\ \operatorname{Hz},时长为 \operatorname{tim}\ \operatorname{ms} 的声音。

void playmusic(string path);
异步播放音乐,调用后立即返回,音乐与之后的程序同时运行。

void finishmusic(string path);
同步播放音乐,音乐播完后返回。

void playmusicuntilend(string path);
相当于 finishmusic

void playmusicloop(string path);
异步循环播放音乐,调用后立即返回,之后音乐循环播放。

void stopallmusic();
停止所有音乐。

11.版本信息

int verval(int idx);
获取版本信息第 \operatorname{idx} 位,位数从 1 开始。

string version();
获取简短的版本号字符串。

string longversion();
获取完整的版本号字符串。

dver.h 第一行注释
(v2.1 及以后)表示版本号。

12.杂项

UNUSED(var)
表示变量 var 没有使用。(防止编译器报警告)

(完)

放一个汉诺塔模拟器:

#include <dgui.h>
#include <algorithm>
#define N 67
#define NC 10
#define NCMX 17
#define INF 0x7fffffff
using std::sort;
int n=8;
int twr[5][N],cnt[5];
int sel;
int interv=200;
DCOLOR nodec[NCMX]={
    getcolor("red"),
    getcolor("orange"),
    getcolor("yellow"),
    getcolor("lime"),
    getcolor("green"),
    getcolor("blue"),
    getcolor("cyan"),
    getcolor("purple"),
    getcolor("pink"),
    getcolor("grey")
};
DCOLOR framec=getcolor("default");
void tpush(int pos,int id){
    twr[pos][++cnt[pos]]=id;
}
int ttop(int pos){
    return twr[pos][cnt[pos]];
}
void tpop(int pos){
    --cnt[pos];
}
void init(){
    cnt[1]=cnt[2]=cnt[3]=0;
    for(int i=1;i<=3;++i){
        twr[i][0]=INF;
    }
    for(int i=n;i>=1;--i){
        tpush(1,i);
    }
}
int getnw(int id){
    return 30+(id-1)*10;
}
DCOLOR getnc(int id){
    return nodec[(id-1)%NC];
}
void upd(){
    setfillcolor(framec);
    clear();
    rectangle(30,60,100,50);
    textout(45,63,"还 原");
    rectangle(30,130,100,50);
    textout(45,133,"自 动");
    rectangle(30,200,100,50);
    textout(45,203,"设 置");
    /*80 10 160 10 160 10 80*/
    rectangle(180,400,510,10);
    rectangle(260,100,10,300);
    rectangle(430,100,10,300);
    rectangle(600,100,10,300);
    for(int i=1;i<=3;++i){
        for(int j=1;j<=cnt[i];++j){
            setfillcolor(getnc(twr[i][j]));
            int w=getnw(twr[i][j]);
            if(i==sel&&j==cnt[i]){
                rectangle(180+85+(i-1)*170-(w>>1),50,w,20);
            }
            else{
                rectangle(180+85+(i-1)*170-(w>>1),400-j*20,w,20);
            }
        }
    }
}
void movenode(int src,int dst){
    tpush(dst,ttop(src));
    tpop(src);
}
void clickpos(int pos){
    if(sel){
        if(sel==pos){
            sel=0;
        }
        else{
            if(ttop(sel)<ttop(pos)){
                movenode(sel,pos);
                sel=0; 
            }
        }
    }
    else{
        if(cnt[pos]){
            sel=pos;
        }
    }
    upd();
}
void setting(){
    string s;
    while(1){
        s=vbsinput("请输入层数:","汉诺塔");
        if(isnumstr(s)){
            n=strtoint(s);
            break; 
        }
        else{
            vbsmsgbox("请输入数字!","汉诺塔",0);
        }
    }
    init();
    upd();
}
void cmove(int src,int dst){
    clickpos(src);
    sleep(interv);
    clickpos(dst);
    sleep(interv);
}
void solve(int src,int dst,int mcnt){
    if(mcnt==0){
        return;
    }
    if(mcnt==1){
        cmove(src,dst);
        return;
    }
    int mid=6-src-dst;
    solve(src,mid,mcnt-1);
    cmove(src,dst);
    solve(mid,dst,mcnt-1);
}
void autosolve(){
    init();
    upd();
    sleep(interv);
    solve(1,3,n);
}
void onmouse(EPARA p){
    UNUSED(p);
    static DRECT r1=DRECT(30,60,130,110),
                 r2=DRECT(30,130,130,180),
                 r3=DRECT(30,200,130,250);
    DPOINT pos=mousepos();
    int x=pos.x,y=pos.y;
    if(inrect(pos,r1)){
        init();
        upd();
    }
    if(inrect(pos,r2)){
        autosolve();
    }
    if(inrect(pos,r3)){
        setting();
    }
    if(x<180||x>690||y>410){
        return;
    }
    if(x==690){
        x=689;
    }
    clickpos((x-180)/170+1);
}
void onkey(EPARA p){
    int vk=transepara<int>(p);
    switch(vk){
        case '1':{
            clickpos(1);
            break;
        }
        case '2':{
            clickpos(2);
            break;
        }
        case '3':{
            clickpos(3);
            break;
        }
        case 'I':{
            init();
            upd();
            break;
        }
        case 'A':{
            autosolve();
            break;
        }
        case 'S':{
            setting();
            break;
        }
    }
}
int main(){
    hideconsole();
    setup(750,550,"汉诺塔");
    init();
    setting();
    upd();
    bind("MouseDown-L",onmouse);
    bind("KeyDown",onkey);
    keepwindow();
    return 0;
}