C/C++语言实现图形化排序计算器

前言

        在C语言的学习中,一直面对黑色命令框一定十分令人乏味。本次我将带领大家使用easyx实现图形化排序计算器,我使用的版本是VS2022+EasyX_2023大暑版。不同版本可能细节上处理不同,大家记得修改,否则不能成功运行。

QQ录屏20240317145000

功能

        首先我们要明白我们这次设计的计算器要实现什么功能,要做什么?顾名思义,我们想要做的也十分的简单,就是输入一堆有效数字,然后让程序给我们计算出来正确的顺序并且输出出来。

        具体实现完如下

绘制计算器轮廓

        既然是叫做图形化界面,那么必然有相应的图像才可以。我们依据以往的计算器模样大致的可以猜想我们要实现计算器的模样,在这里我推荐大家在纸上或者是电脑上画出大致的轮廓。如下图。

创建窗口

        首先我们将创建一个窗口用于显示计算器,在官网EasyX Graphics Library for C++下载好后,只需要包含头文件#include<graphics.h>就可以使用Eaxy函数了。在Eaxy中创建一个窗口十分简单,只需要使用如下函数

HWND initgraph(
	int width,
	int height,
	int flag = NULL
);

这里的 width指绘图窗口的宽度。heigh纸t绘图窗口的高度。注意这与我们日常不同的是他的原点在左上角即他的坐标轴是如下的.这点要尤其注意

        经过调试创建了500*600的窗口大小,直接使用initgraph(500, 600);就可以了。

画直线

        如果我们仔细地观察不难发现,我们1,2,3,……按钮与上方显示器区分的线条都是直线,那么我们便可以通过画直线的方式确定我们按钮的位置。

        在Easyx中画直线要用到如下函数

void line(
	int x1,
	int y1,
	int x2,
	int y2
);

        这个也十分容易理解,在一个平面中,两点确定一条直线,给出两点的坐标显然可以画出这个直线。

        接下来重要i的是划分每条线在哪里。我们可以提前规划好每个按钮的大下。根据我们最初设想的样子,可以画出如下图规划。

        有了这张坐标图当我们在使用一些函数绘图时便可以了然于心。画直线的代码也十分简单了,如下。

//画下方按钮
line(0, 300, 500, 300);
line(0, 375, 500, 375);
line(0, 450, 500, 450);
line(0, 525, 500, 525);

line(125, 300, 125, 600);
line(250, 300, 250, 600);
line(375, 300, 375, 600);
//画正上方输出屏
line(170, 0, 170, 300);
line(340, 0, 340, 300);

 画按钮

        光有空白十分不方便我们以后使用,如果在具体的位置有文字便更好了。于是接下来我们便输出按钮内的文字。

drawtext

        首先我们要了解一个函数drawtext。它可以在窗口内输出文字。但不能用printf,他是在黑色命令框内输出文字。

int drawtext(
	LPCTSTR str,
	RECT* pRect,
	UINT uFormat
);

    首先看第一个参数,str,他的类型其实就是双字节指针,与我们日常用的char 不同,我们不加处理“”用双引号括出来的是单字节字符串,不能直接传参,这也是2023版的改动。大家要注意不同版本的细节。我们可以将VS配置调整或采用以下方式转换。将单字节转换为双字节。但也要注意的是双字节的编码也兼容ASCII表,相当于在ASCII表示扩容。后续会用到这一特点。

L"确认"
_T("确认")

        第二个参数显然是一个指针,但其指向的是什么呢?如下例,它RECT其实就是一个矩形的位置坐标,在平面中只要确定两个相对顶点的坐标,矩形便确定了。这个类型其实就是矩形左上与右下顶点的坐标,我们输出文字其实就是在一个个矩形内输出。

RECT r = {0, 0, 639, 479};

        他的第三个参数是矩形内文字的处理,左对齐,右对齐,独行等,只截取如下供读者查看,用到的时候在上官网查看即可。

结构体

        我们输出文字后,后面还会用到按钮的内容,位置信息么?显然是会的,我们在最后还要通过鼠标输入数据,点击一下鼠标,只有检测鼠标在那个按钮内我们才可以确定输入的内容。我们每次检验,输出都可以通过手写RECT r = {0, 0, 639, 479};

drawtext(_T("Hello World"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);来实现,但这太过麻烦了!!于是我们便可以通过定义一个结构体来存储一次位置,内容信息,以后直接通过结构体访问,减小记忆数据的负担。我们定义如下结构体

//按钮
struct button
{
	int x_left;
	int y_left;
	int x_right;
	int y_right;
	COLORREF color;
	WCHAR str[100];
}; 

        细心的读者一定发现按钮的结构体多了个COLORREF color;颜色成员,这个我们在下文再具体讲。 str[100]是为了考虑后面可能的需要。设置成10也可以。

初始化按钮

位置

        有了结构体后我们可以开始定义结构体了,然后对其进行初始化。根据我们的规划,我们下半窗口一共有16个按钮,相信大家第一反应便是如下创建结构体

struct button b0;
struct button b1;
struct button b2;
struct button b3;
struct button b4;
struct button b5;
struct button b6;
struct button b7;………………

        一共有16个,创建16次便可以了,然后依次初始化。这样固然可以但其实我们还有更好的方法便是创建一个结构体数组。我们在仔细观察下表。我们要初始化的内容无非就是坐标信息和文字,文字我们肯定要一个个的初始化。但坐标可以简单些么?看这下面的图是不是特别像二维数组,再联想一些。在第一行的遍历中每一行的x是每次加125,y不变,在第二行x还是每次加125,y相较于最初的300到375,加了75.这仿佛是某种规律!!如果我们把行设为i1,列设为i2,每一个按钮左顶点x就是i2 * 125,y就是300 + 75 * i1;!!!这样我们就可以写出如下代码简化初始化的过程。

//初始化
count = 0;
for (i1 = 0; i1 < 4; i1++)
{
	for (i2 = 0; i2 < 4; i2++)
	{
		arr[count].x_left = i2 * 125;
		arr[count].x_right = 125+i2*125;
		arr[count].y_left = 300 + 75 * i1;
		arr[count].y_right = 375 + 75 * i1;
		count++;
	}
}

        这样做大大简化了代码,简直就是太库拉。

文字

        接下来就是初始化文字内容了。 面对数字我们可以使用如下方法。

for (i = 0; i < 3; i++)
{
	arr[i].str[0] = i + L'1';
	arr[i].str[1] = '\0';
}
for (i = 0; i < 3; i++)
{
	arr[i+4].str[0] = i + L'4';
	arr[i+4].str[1] = '\0';
}
for (i = 0; i < 3; i++)
{
	arr[i + 8].str[0] = i + L'7';
	arr[i + 8].str[1] = '\0';
}

        在这里要注意的是数字并不是紧挨着的,我们采用了数组的方式存储,那么按钮的位置应该如下图分布。

        而对于文字我们可以我们不可以简单的用以下方式,arr[13].str = L"开始",应为此时的str是一个数组的地址,他是不可修改的左值,这种初始化只有在定义的时候才可以用。我们便有如下的两种方式

第一种

        我们知道一个汉字就是一个双字节大小,便可以如下初始化。

arr[14].str[0] = L'删';
arr[14].str[1] = L'除';
arr[14].str[2] = L'\0';

        经管他是一个双字节字符串他任然以\0作为字符串结束标志。‘\0‘千万不能忘记加,否则会产生难以预料的错误。

第二种

         我们可以单独写一个Copy函数来解决它,注意不能用strcpy,因为strcpy的参数是单字节!!但两者实现的思路一模一样。

//复制字符串
void Copy(WCHAR* des, const WCHAR* src)
{
	while (*des++ = *src++);
}

        注意while中=是赋值而不是判断,这也是精妙之处!Copy(arr[14].str, L"删除");假设我们调用了这个函数,src就指向了"删除",des在数值上等于str,str是数组名不可改变,但此时des是变量可以改变,将src解引用后的内容不断赋值给des,这个赋值表达式的结果就是*des,当没遇到\0是,这个循环就成立接着进入循环,当*des为\0是赋值停止。

//按钮文字
Copy(arr[3].str, L"确认");
Copy(arr[7].str, L"升序");
Copy(arr[11].str, L"降序");
Copy(arr[15].str, L"开始");
Copy(arr[14].str, L"删除");

        按照上述的方法便可以全部初始化完按钮了。

输出文字

        经过上面的初始化后我们便可以输出对应的文字了。settextstyle是设置文字的大小,字体。

//输出按钮文字
void DrawButtonDown(ST *pa)
{
	RECT r = {0,0,0,0 };
	int i = 0;
	for (i = 0; i < 16; i++)
	{
		settextstyle(40, 0, L"楷体");
		RECT r = { arr[i].x_left,arr[i].y_left,arr[i].x_right,arr[i].y_right };
		fillrectangle(arr[i].x_left, arr[i].y_left, arr[i].x_right, arr[i].y_right);
		drawtext(arr[i].str, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	}
	
}

        我们便可以得到下图

思考

    写到这如果我们一直都在main函数中写那么,大概有小100行了,接下来一定还有很多的代码,如果我们都在main中写固然可以,但程序的主逻辑必然难以观察,那个功能写错了进行维护也十分困难,对应的代码可能混乱无序。于是我们便可以采用如下方法

模块化编程

   我们可以将一些实现特点功能的代码封装在函数中,然后再main函数中调用即可,简化主函数中的代码,尽量的体现逻辑性。

 结构体维护

        然后我们还要注意的是,我们必定要定义一些变量存储信息,在函数调用中传递相应的参数,在这里我推荐把必须的变量放在一起,封装成结构体,在传参数就可以传一个结构体指针来调用想要的变量,而不是一个一个传所需要的参数。

        后面排序必定会有升序和降序两种,我们可以用枚举的方式增加代码可读性。

其次接下来我们为了可以持续的维持程序运行,知道按下开始,可以采用一个while死循环的方式,当检测到鼠标点击后结束程序。我们可以换种方法维护程序的运行。增加个运行状态成员,同样采用枚举的方式如下。

//排序方式
enum sort
{
	UP,
	DOWN
};

//运行状态
enum status
{
	OK,
	START
};


//整个界面对象
typedef struct syetem
{
	struct button arr[16];
	enum status status;
	enum sort sort;
}ST;

用如下while方式保持程序的运行

while (a.status == OK)
{
	……
    ………
    …

}

鼠标提示

        如果我们的鼠标在按钮的上方,按钮就会出现变化,那么就可以得到很好的反馈。接下来我们便开始实现这一功能。

        在此我们可以用一个函数实现这一功能,之前绘画按钮也可以封装成一个函数,变量通过传入的指针使用。

int main()
{
	ST a;//以结构体维护系统


	//初始化
	init(&a);
	
	while (a.status == OK)
	{
		//跟踪鼠标
		CheckInButton(&a);

	}

	return 0;
}

鼠标信息

        ExMessage变量用于存储获取信息,他的内容有以下。可以表示鼠标和键盘的信息。

struct ExMessage
{
	USHORT message;					// 消息标识
	union
	{
		// 鼠标消息的数据
		struct
		{
			bool ctrl		:1;		// Ctrl 键是否按下
			bool shift		:1;		// Shift 键是否按下
			bool lbutton	:1;		// 鼠标左键是否按下
			bool mbutton	:1;		// 鼠标中键是否按下
			bool rbutton	:1;		// 鼠标右键
			short x;				// 鼠标的 x 坐标
			short y;				// 鼠标的 y 坐标
			short wheel;			// 鼠标滚轮滚动值,为 120 的倍数
		};

		// 按键消息的数据
		struct
		{
			BYTE vkcode;			// 按键的虚拟键码
			BYTE scancode;			// 按键的扫描码(依赖于 OEM)
			bool extended	:1;		// 按键是否是扩展键
			bool prevdown	:1;		// 按键的前一个状态是否按下
		};

		// 字符消息的数据
		TCHAR ch;

		// 窗口消息的数据
		struct
		{
			WPARAM wParam;
			LPARAM lParam;
		};
	};
};

        getmessage用于获取当前的输入信息。其中的参数可以不写,这是C++重载的方式。

ExMessage getmessage(BYTE filter = -1);

        参数不同,获取的信息不同。

遍历比较

        鼠标在按钮内如何判断呢?只需要检测鼠标的坐标是不是在按钮的矩形框内就可以了。

因为我们采用了数组的方式存储按钮结构体,一个for循环便可以遍历了。此时还记得我们之前按钮结构体加了个颜色成员么?如果检测到鼠标在按钮内就改变其结构体内的颜色。主义此时输出还没有改变。

        在之前绘画按钮的程序可以在封装成一个函数,在最后绘画按钮即可体现变化。

相关知识

setfillcolor是设置填充颜色。我们可以由此画出灰色显示鼠标的位置。

fillrectangle是矩形填充,可以将原来的白色覆盖

void fillrectangle(
	int left,
	int top,
	int right,
	int bottom
);

       RGB可以用于合成某一种颜色

COLORREF RGB(
	BYTE byRed,		// 颜色的红色部分
	BYTE byGreen,	// 颜色的绿色部分
	BYTE byBlue		// 颜色的蓝色部分
);

        每一种颜色都可以通过红绿蓝合成。当然Easyx也提供了一些简单颜色。REN BLACK等

        由此我们便可以写出如下封装好的函数

//输出按钮文字
void DrawButtonDown(ST *pa)
{
	RECT r = {0,0,0,0 };
	int i = 0;
	for (i = 0; i < 16; i++)
	{
		settextstyle(40, 0, L"楷体");
		RECT r = { pa->arr[i].x_left,pa->arr[i].y_left,pa->arr[i].x_right,pa->arr[i].y_right };
		setfillcolor(pa->arr[i].color);
		fillrectangle(pa->arr[i].x_left, pa->arr[i].y_left, pa->arr[i].x_right, pa->arr[i].y_right);
		drawtext(pa->arr[i].str, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	}
	
}

        经过上述准备便可以写出如下的代码。

//检测鼠标在按钮内
void CheckInButton(ST * pa )
{
	//获取鼠标信息
	flushmessage();
	pa->m = getmessage();

	//检测在下方的按钮,在则表示为灰色
	ExMessage m = pa->m;
	int i1 = 0, i2 = 0;
	int count = 0;
	for (i1 = 0; i1 < 4; i1++)
	{
		for (i2 = 0; i2 < 4; i2++)
		{
			if (pa->arr[count].x_left < m.x && m.x < pa->arr[count].x_right
				&& pa->arr[count].y_left<m.y && pa->arr[count].y_right>m.y)
			{
				pa->arr[count].color = RGB(127,127,127);
			}
			else
			{
				pa->arr[count].color = WHITE;
			}
			count++;
		}
	}
	DrawButtonDown(pa);

	//刷新缓冲区
	FlushBatchDraw();
}

        注意看最后有个FlushBatchDraw();可以理解为我们画的图不会直接画在窗口上,而是先储存在缓存区,当缓存区满的时候在输出到窗口,而 FlushBatchDraw();可以刷新缓冲区,让目前缓冲区的内容输出到窗口上。这个有利于我们观察代码,否则可能代码执行完,窗口却毫无变化。

        可以实现鼠标在哪,那个按钮颜色变化。(截图无法截取到鼠标)

处理点击信息

        这一块显然是整个程序的核心。数据的输入,输出,排序的方式都依靠这个实现,同样我们可以将它封装成一个函数。这样写我们程序的整体思想便清晰可见。

int main()
{
	ST a;//以结构体维护系统

	//初始化
	init(&a);
	
	while (a.status == OK)
	{
		//跟踪鼠标
		CheckInButton(&a);
		
		//获取点击信息
		Get(&a);

	}

	return 0;
}

        接下来便是具体实现的过程了。

        首先我们要检测的便是鼠标左键有没有按下,如果按下pa->m.lbutton 就为1,便执行下面的程序。不为1就没点击不需要处理。于是我们便可以采用如下的框架。

//获取鼠标信息
void Get(ST *pa)
{
	int i = 0;
	//检测鼠标按下
	if (pa->m.lbutton == 1)
	{
		//检测鼠标位置
		for (i = 0; i < 16; i++)
		{
			if (pa->arr[i].color != WHITE)
				break;
		}

		if (i!=3 && i!=7 && i!=11 && i!=15 && i!=14)//输入数字
		{
			
		}
		else if(i==3)//确认
		{
			
		}
		else if (i == 14)//删除
		{
		
		}
		else if (i == 11)//降序
		{
			
		}
		else if (i == 7)//升序
		{
			
		}
		else if (i == 15)//开始
		{
			
		}
	}
	
	FlushBatchDraw();
}

        接下来只需要按功能填写就可以了。

        首先我们点击一个一次1我们获取的信息最终要输出在窗口上,drawtext不像printf一样可以格式化输出,只能输出字符串,也就是我们输出的数字要被视为是字符串。所以我们要以双字节的形式存储我们的点击信息,便于后续的输出。

        此时我们便可以在struct syetem中添加成员来记录输入数据。

        我们最终要输入多个双字节字符串然后排序,可以使用二维数组的方式,为了方便清除我们已经输入到第几个数组,可以加个size来记录。其次我们对于字符串不好排序,可以用一个浮点数组储存数字,便于比较。对于这几个紧密关联的成员,我们可以在将他们封装成一个结构体,最终有如下代码。(WCHAR是宽字符,也就是双字节)

        这基本包含了我们代码前的所有准备。

//按钮
struct button
{
	int x_left;
	int y_left;
	int x_right;
	int y_right;
	COLORREF color;
	WCHAR str[100];
}; 



//输出控制
struct out
{
	WCHAR str[20][30] = { 0 };
	int size;
	double strnum[20];
};

//运行状态
enum status
{
	OK,
	START
};

//排序方式
enum sort
{
	UP,
	DOWN
};

//整个界面对象
typedef struct syetem
{
	struct out out;
	struct button arr[16];
	ExMessage m;
	enum status status;
	enum sort sort;
}ST;

        在VS中结构体中的双字节数组自动全部初始化为\0,在这里就不需要在手动初始化了。

点击数字

        

        因为我们设置了鼠标在按钮上方,按钮的颜色改变,所以我们可以通过颜色判断鼠标在那个按钮。如果i!=3 && i!=7 && i!=11 && i!=15 && i!=14说明鼠标点击的是数字的组成部分我们应当将其存储在字符数组中。此时我们要把这个字符存在哪里呢?显然他是在数组

    pa->out.str[pa->out.size]中但在那个位置呢?是在有效字符后的第一个\0,此时我们就可以先写一个函数,求数组有效字符数,注意不能用strlen,双字节与单字节类型不匹配。

//求字符串长度
int Len(WCHAR* str)
{
	int sz = 0;
	while (*str++)
	{
		sz++;
	}
	return sz;
}

        然后有了上述函数我们便可以知道字符添加的位置了。

if (i!=3 && i!=7 && i!=11 && i!=15 && i!=14)//输入数字
{
	int sz = Len(pa->out.str[pa->out.size]);
	pa->out.str[pa->out.size][sz++] = pa->arr[i].str[0];
	pa->out.str[pa->out.size][sz] = '\0';
}

     arr是按钮结构体数组。

确认

        当i==3的时候就是确认了,他表示当前的一个数字已经输入完毕,可以开始下一次输入了。

在这里先介绍sscanf函数,便于我们后续的操作。

sscanf

int sscanf ( const char * s, const char * format, ...);

        看名字就知道他与scanf有十分紧密的关系,当我们在黑色命令框输入1253时我们输入的其实对于计算器而言是字符串,我们的格式控制符决定了计算器如何看待这些字符串。同样sscanf它可以从一个字符串中格式化读取数据。于scanf从命令框得到字符串不同,sscanf是用户自己提供字符串。于是便可写出如下的代码。

sscanf("12.3", "%lf", &a);

        读取的数据就会存储在a中,与scanf用法基本相同。

        当点击确认时,我们要将输入的字符串转为浮点数存储,便可以用到sscanf,当然也可以自己手写一个函数,但不推荐。其次我们要注意的是sscanf类型为单字节,而我们存储的都是双字节,在使用前要先进性转换。

Swap

        在此我们可以设计一个函数,让其从双字节转换为单字节。

        下面这个代码仿佛什么都没做,但却是将双字节转换为单字节。还记得我们之前讲的么,双字节的Unicode实在ASCII表上扩充的,我们输入的数字点号都在ASCII表中。1在ASCII表中的值为49,在Unicode编码表中也是49,这是为了兼容ASCII表。由此下面的程序才是正确的。

//将宽字符转为窄字符
void swap(char *des, WCHAR* str)
{
	while (*des++ = (char)*str++);
}

        综上我们便可以写出如下的确认代码。注意此时的size要加一,表示输入下一个数据。

else if(i==3)//确认
{
	//字符转数字,便于比较
	char tmp[100] = { 0 };
	swap(tmp, pa->out.str[pa->out.size]);
	sscanf(tmp, "%lf", &pa->out.strnum[pa->out.size]);
	pa->out.size++;
}

删除

        删除十分简单只要将当前的接受数组第一个字符设置为\0即可。然后将当前位置填空。

else if (i == 14)//删除
{
	pa->out.str[pa->out.size][0] = '\0';
	setfillcolor(WHITE);
	solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
}

        这里的solidrectangle与之前的fillrectangle十分像,区别是前者是无边框填充,后者是有边框填充填充。两者用哪一个都可以。

void solidrectangle(
	int left,
	int top,
	int right,
	int bottom
);

降序,升序

        这里的降序只是个标志,决定我们后面如何排序,所以代码也十分简单。

else if (i == 11)//降序
{
	pa->sort = DOWN;
}
else if (i == 7)//升序
{
	pa->sort=UP;
}

开始

        这个标志着我们程序的结束,我们按要求输出最后排序的结果就可以了。在这里我们可以封装一个函数用来处理,排序等操作。结构如下

else if (i == 15)//开始
{
	pa->status = START;
	Start(pa);
}

        在这里我们只需要改变结构体内的状态,当再一次a.status == OK判断时,循环就会结束。

接下来我们开始Start的实现。

      Start

        首先我们便要根据升序还是降序来对数组进行排序,还记得我们之前sscanf(tmp, "%lf", &pa->out.strnum[pa->out.size]);么,我们把输入的字符数组转换为浮点数多保存一份,这无疑方便我们接下来的排序。

        首先我们认识一个函数qsort,它可以按照我们的意思排序任何类型的数据。base是要排序数组地址,num是要排序的元素个数,size是元素大小,cmoper是自定义比较函数。

void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));

        我们着重看下cmoper,我们可以把他的返回值分为三类,等于0,不交换,大于0,p1在p2之后。小于0,p1在p2之前。具体的运用要具体分析。

        我们便可以写出如下排序的框架。

//排序
if (pa->sort == DOWN)
{
	qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpdown);
}
else
{
	qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpup);
}

        接下来就是实现cmpdown函数,由于这是降序,最大的一定在第一个,注意这里的参数必须按要求设置为const void *不能直接解引用,要先强制转换为某种类型。

        *(double*)p2 > *(double*)p1如果进行上述比较,假设p2大,按照我们降序的排布,p1的元素应该在p2后面,所以返回1正数,如果*(double*)p2 > *(double*)p1不为真返回-1。

//排序
int cmpdown(const void* p1, const void* p2)
{

	if (*(double*)p2 > *(double*)p1)
	{
		return 1;
	}
	else
	{
		return -1;
	}

}

        这里的第二个返回值不要写成0,尽管在某些时候负数与0的效果一样。假设就该返回0,返回-1交换一下也无伤大雅,但一但你通通返回0,原来要交换的地方可能就由于返回0就不交换了。

        同理升序的比较函数也可以如下写。

int cmpup(const void* p1, const void* p2)
{

	if (*(double*)p1 > *(double*)p2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
}
   输出调整

        当我们比较完大小后便要再将数字转换为字符,方便我们drawtext输出。

//数字转字符
void chtonum(WCHAR* str,  double m, ST* pa,int i)
{
	char arr[100] = { 0 };
	int flag = 0;

	//检测有无小数
	if (pa->out.strnum[i] - (int)pa->out.strnum[i] > 0.000001 || pa->out.strnum[i] - (int)pa->out.strnum[i] < -0.000001)
	{
		flag = 1;
	}
	flag==0?sprintf(arr, "%.0lf", m): sprintf(arr, "%.4lf", m);
	char* p = arr;
	while (*str++ = (WCHAR)*p++);
}

        上面的if是用来判断是小数还是整数,小数则读取4位小数,sprintf与printf十分像,不过前者是输出在字符串内。最后我们在用个while循环复制。

        最终代码如下

//开始排序
void Start(ST*pa)
{
	//排序
	if (pa->sort == DOWN)
	{
		qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpdown);
	}
	else
	{
		qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpup);
	}
	int i = 0;
	//数字转字符输出
	for (i = 0; i < pa->out.size; i++)
	{
		chtonum(pa->out.str[i], pa->out.strnum[i], pa, i);
	}
	DrawNumUp(pa);
	FlushBatchDraw();
}

        到这里我们的点击函数基本完成了,具体如下。

//获取鼠标信息
void Get(ST *pa)
{
	int i = 0;
	//检测鼠标按下
	if (pa->m.lbutton == 1)
	{
		//检测鼠标位置
		for (i = 0; i < 16; i++)
		{
			if (pa->arr[i].color != WHITE)
				break;
		}

		if (i!=3 && i!=7 && i!=11 && i!=15 && i!=14)//输入数字
		{
			int sz = Len(pa->out.str[pa->out.size]);
			pa->out.str[pa->out.size][sz++] = pa->arr[i].str[0];
			pa->out.str[pa->out.size][sz] = '\0';
		}
		else if(i==3)//确认
		{
			//字符转数字,便于比较
			char tmp[100] = { 0 };
			swap(tmp, pa->out.str[pa->out.size]);
			sscanf(tmp, "%lf", &pa->out.strnum[pa->out.size]);
			pa->out.size++;
		}
		else if (i == 14)//删除
		{
			pa->out.str[pa->out.size][0] = '\0';
			setfillcolor(WHITE);
			solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		}
		else if (i == 11)//降序
		{
			pa->sort = DOWN;
		}
		else if (i == 7)//升序
		{
			pa->sort=UP;
		}
		else if (i == 15)//开始
		{
			pa->status = START;
			Start(pa);
		}
		
	}
	
	
	FlushBatchDraw();
}

    屏幕上方的数字

        到这里我们就要考虑输出最后要排序的数字了。同样我们可以类比画下方按钮的方法,将屏幕上方划分为15个按钮,每个里面有对应的数字字符串。

        此时加入这个新变量的方法也十分的简单,只需要在结构体中加一个成员就可以了,之前写的函数参数也不用做任何改变,只需要通过结构体指针就可以了。

//整个界面对象
typedef struct syetem
{
	struct out out;
	struct button arr[16];
	ExMessage m;
	enum status status;
	enum sort sort;
	struct button num[15];
}ST;

        至此,我们全部的结构体成员以及完成了。我们可以在init(&a);中初始化num数组,init的参数无需改变,在我们之前写的初始化按钮下方写即可。

int i1 = 0, i2 = 0, i = 0;
int count = 0;
//初始化数字按钮
for (i1 = 0; i1 < 3; i1++)
{
	for (i2 = 0; i2 < 5; i2++)
	{
		pa->num[count].x_left = i1 * 170;
		pa->num[count].x_right = 170 + i1 * 170;
		pa->num[count].y_left = i2 * 60;
		pa->num[count].y_right = 60 + i2 * 60;
		pa->num[count].color = WHITE;
		count++;
	}
}

        此时便可以模仿上面的按钮输出,写出下方的输出数字。这里要注意的是加了个动态设计字体大小,主要是预防数字太多,屏幕装不下就减小字体。具体的动态参数要根据情况调整。

//绘画数字
void DrawNumUp(ST* pa)
{
	int i = 0;
	for (i = 0; i < pa->out.size; i++)
	{
		int sz = Len(pa->out.str[i]);
		if (sz > 5)
		{
			settextstyle(170 / sz * 1.8, 0, L"楷体");
		}
		else
		{
			settextstyle(40, 0, L"楷体");
		}

		//设置坐标填充写字
		RECT r = { pa->num[i].x_left,pa->num[i].y_left,pa->num[i].x_right,pa->num[i].y_right };
		setfillcolor(pa->num[i].color);
		fillrectangle(pa->num[i].x_left, pa->num[i].y_left, pa->num[i].x_right, pa->num[i].y_right);
		drawtext(pa->out.str[i], &r, DT_LEFT||DT_SINGLELINE);
	}

	FlushBatchDraw();
}

        到了这里我们基本完成了整个程序的设计但,还有些问题。

        

优化一

       正在输入数字时,屏幕没反应,等到点击确认屏幕才出现数字。这原因也十分简单,没有点击确认前pa->out.size没有加一,当前数字不一定是最终的数字。

        在Get函数中封装一个函数用来实时显示数字。DrawNumUp最多显示到pa->out.size-1的数字,我们只需要让这个函数显示pa->out.size的数字就可以了。

        在这里我们还添加了一个下划线表示当前数字是正在输入的数字。

//输出最后一个元素
void DrawLast(ST* pa)
{
	int sz = Len(pa->out.str[pa->out.size]);
	if (sz != 0)
	{
		if (sz > 5)
		{
			settextstyle(170 / sz * 1.8, 0, L"楷体");
		}
		else
		{
			settextstyle(40, 0, L"楷体");
		}

		RECT r = { pa->num[pa->out.size].x_left,pa->num[pa->out.size].y_left,pa->num[pa->out.size].x_right,pa->num[pa->out.size].y_right };
		setfillcolor(pa->num[pa->out.size].color);
		fillrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		drawtext(pa->out.str[pa->out.size], &r, DT_LEFT || DT_SINGLELINE);
	}
	line(pa->num[pa->out.size].x_left + 60, pa->num[pa->out.size].y_left + 55, pa->num[pa->out.size].x_left + 100, pa->num[pa->out.size].y_left + 55);
}

优化二

        我们可能出现以下的情况,明明鼠标点击一次6,为什么会出现多次结果输入多个6??

        其实原因是我们电脑的性能都十分优秀,跑完一遍代码可能只需要十几毫秒,但我们鼠标从点击到弹起可能需要60ms,这导致了我们点击一次而检测成多次。

        

        为了杜绝这一现象,我们可以先暂停60ms,再用个循环判断解决。

if (pa->m.lbutton == 1)
{

    ……………………
    ……………………
    //消除连续点击鼠标
    Sleep(60);
    while (pa->m.lbutton == 1)
    {
	    flushmessage();
	    pa->m= getmessage();
    }
}

优化三

        单纯的依靠窗口上的删除键,我们只可以删除正在输入的数字,可能我们之前输入的数字有错误,我们便可以提供一种鼠标右键的删除功能,帮助我们快速删除指定数字。

        同样我们可以在Get中封装一个DelMouse(pa);函数来执行这个操作。

        还记得我们之前写的鼠标提示么!数字按钮也是按钮,我们就可以在CheckInButton(&a);函数扩充检测数字现实的功能。具体代码如下。

//检测上方的数字
count = 0;
for (i1 = 0; i1 < 3; i1++)
{
	for (i2 = 0; i2 < 5; i2++)
	{
		if (pa->num[count].x_left < m.x && m.x < pa->num[count].x_right
			&& pa->num[count].y_left<m.y && pa->num[count].y_right>m.y)
		{
			pa->num[count].color = RGB(127, 127, 127);
		}
		else
		{
			pa->num[count].color = WHITE;
		}
		count++;
	}
}
DrawNumUp(pa);

        此时我们在找到要删除的数字。pa->m.rbutton == 1表示右键按下,删除也十分的简单,将对应数组pa->out.str[i][0] = '\0';然后空白填充当前区域。

//鼠标右键删除
void DelMouse(ST* pa)
{
	int i = 0;
	if (pa->m.rbutton == 1)
	{
		for (i = 0; i < 16; i++)
		{
			if (pa->num[i].color != WHITE)
				break;
		}
		pa->out.str[i][0] = '\0';
		pa->num[i].color = WHITE;

		//最后的元素清空
		setfillcolor(WHITE);
		solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		//消除连续点击鼠标
		Sleep(60);
		while (pa->m.lbutton == 1)
		{
			flushmessage();
			pa->m = getmessage();
		}
	}
    //调整删除数字
    Adjust(pa);
}

        当然仅这样,最后删除的地方会留下一片空白,为了美观性,我们在封装一个Adjust(pa);函数将删除元素后面的数字都往前移动一个位置。注意要将原本最后一个数字的位置填充空白。


//调整删除后的数字
void Adjust(ST* pa)
{
	int i = 0;

	for (i = 0; i < pa->out.size; i++)
	{
		if (pa->out.str[i][0] == L'\0')
		{
			//覆盖最后的数字
			pa->num[pa->out.size - 1].color = WHITE;
			setfillcolor(pa->num[pa->out.size - 1].color);
			fillrectangle(pa->num[pa->out.size - 1].x_left, pa->num[pa->out.size - 1].y_left, pa->num[pa->out.size - 1].x_right, pa->num[pa->out.size - 1].y_right);
			FlushBatchDraw();

			for (int j = i; j < pa->out.size - 1; j++)
			{
				Copy(pa->out.str[j], pa->out.str[j + 1]);
			}
			pa->out.str[pa->out.size - 1][0] = L'\0';
			pa->out.size--;
		}
	}

}

      对于一些常用代码可以封装成一个函数方便使用,在这里就不一一列举了。

开始界面

        当做完整个功能的实现后,可以在添加一些开始与结束界面的绘制。这里我们封装一个Welcome();用来输出欢迎界面。

图片输出

        我们可以通过loadimage加载一个图片到变量img中,这是Easyx提供的变量。500,600表示将原来图片压缩到500*600大小,最后再putimage输出图片。它前两个参数是输出图片左上角位置,然后图像按照矩形输出。

        这里的./表示当前目录即.cpp所在的目录,../表示上一级目录

//加载图片
IMAGE img;

loadimage(&img, _T("../../开始界面.jpg"),500,600);
putimage(0, 0, &img);

        然后我们便可以在上面输出一些文字,具体位置要不断尝试调整。

        最后当前页面结束时按下Enter,ExMessage m = getmessage();可以获取外部设备信息,m.vkcode代表虚拟键码虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn大家可以上官网查看。一个while循环直到读取到Enter按键结束。

//第一层进入
RECT r = { 0, 0,500, 600 };
settextcolor(RED);
setbkmode(TRANSPARENT);//透明文字
settextstyle(40, 0, L"楷体");
drawtext(_T("欢迎使用排序计算器!"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
 r = { 0, 400,500, 600 };
drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
FlushBatchDraw();
ExMessage m = getmessage();
while (m.vkcode != VK_RETURN)
{
	m = getmessage();
}

Sleep(100);//弹起按钮需要时间,程序运行几ms,避免一次确认识别多次

        注意在这了Sleep(100),休眠0.1秒,否则enter可能多次读取并使用。

        接下来第二页用来大于一些使用规范

//第二次规则
putimage(0, 0, &img);
r = {10, 100,500, 200 };
drawtext(_T("1.不要连续快速点击按钮"), &r, DT_LEFT);
r = { 10, 200,500, 300 };
drawtext(_T("2.鼠标左键确认,右键可删除数字"), &r, DT_LEFT| DT_WORDBREAK);
r = {10, 300,500, 400 };
drawtext(_T("3.可识别操作间隔是60ms"), &r, DT_LEFT);
r = {10, 400,500,500 };
drawtext(_T("4.如果1秒后无结果请重新按"), &r, DT_LEFT);

r = { 0, 400,500, 600 };
drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
FlushBatchDraw();

flushmessage();
m.vkcode = 0;
while (m.vkcode != VK_RETURN)
{
	m = getmessage();
}

结束界面

        结束的时候就停留在我们输出完排序后数字的界面,然后按enter结束。

void End(void)
{
	RECT r = { 0, 400,500, 600 };
	drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	//不断检测信息,直到按下enter
	ExMessage m = getmessage();
	while (m.vkcode != VK_RETURN)
	{
		m = getmessage();
	}
	closegraph();
}

结语

        到这里我们的项目算是做完了,整个写下来也是十分的不容易。采用不断封装的方式,我们最终的主函数代码如下

        整个资源如下压缩包

int main()
{
	ST a;//以结构体维护系统

	//欢迎界面
	Welcome();

	//初始化
	init(&a);
	
	while (a.status == OK)
	{
		//跟踪鼠标
		CheckInButton(&a);
		
		//获取点击信息
		Get(&a);

	}

	//结束标识
	End();

	return 0;
}

        我们可以快速的了解整体思路,接下来每个功能只需要具体看某个函数就可以了。 剩下的就是不断规范代码了。

        写到这里十分的不容易,大家点点关注。

        完整代码如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<graphics.h>

/*
	版权所有CSDN 卫胡迪
	喜欢的点点关注
*/
//按钮
struct button
{
	int x_left;
	int y_left;
	int x_right;
	int y_right;
	COLORREF color;
	WCHAR str[100];
}; 



//输出控制
struct out
{
	WCHAR str[20][30] = { 0 };
	int size;
	double strnum[20];
};

//运行状态
enum status
{
	OK,
	START
};

//排序方式
enum sort
{
	UP,
	DOWN
};

//整个界面对象
typedef struct syetem
{
	struct out out;
	struct button arr[16];
	ExMessage m;
	enum status status;
	enum sort sort;
	struct button num[15];
}ST;


//复制字符串
void Copy(WCHAR* des, const WCHAR* src)
{
	while (*des++ = *src++);
}

//求字符串长度
int Len(WCHAR* str)
{
	int sz = 0;
	while (*str++)
	{
		sz++;
	}
	return sz;
}

//将宽字符转为窄字符
void swap(char *des, WCHAR* str)
{
	while (*des++ = (char)*str++);
}

//初始化按钮
void InitButton(ST*pa)
{
	int i1 = 0, i2 = 0, i = 0;
	int count = 0;
	//初始化数字按钮
	for (i1 = 0; i1 < 3; i1++)
	{
		for (i2 = 0; i2 < 5; i2++)
		{
			pa->num[count].x_left = i1 * 170;
			pa->num[count].x_right = 170 + i1 * 170;
			pa->num[count].y_left = i2 * 60;
			pa->num[count].y_right = 60 + i2 * 60;
			pa->num[count].color = WHITE;
			count++;
		}
	}
	//初始化输出上屏字符数组
	struct out* ps = &pa->out;
	ps->size = 0;
	//初始化
	count = 0;
	for (i1 = 0; i1 < 4; i1++)
	{
		for (i2 = 0; i2 < 4; i2++)
		{
			pa->arr[count].x_left = i2 * 125;
			pa->arr[count].x_right = 125+i2*125;
			pa->arr[count].y_left = 300 + 75 * i1;
			pa->arr[count].y_right = 375 + 75 * i1;
			pa->arr[count].color = WHITE;
			count++;
		}
	}

	for (i = 0; i < 3; i++)
	{
		pa->arr[i].str[0] = i + L'1';
		pa->arr[i].str[1] = '\0';
	}
	for (i = 0; i < 3; i++)
	{
		pa->arr[i+4].str[0] = i + L'4';
		pa->arr[i+4].str[1] = '\0';
	}
	for (i = 0; i < 3; i++)
	{
		pa->arr[i + 8].str[0] = i + L'7';
		pa->arr[i + 8].str[1] = '\0';
	}
	//按钮文字
	Copy(pa->arr[3].str, L"确认");
	Copy(pa->arr[7].str, L"升序");
	Copy(pa->arr[11].str, L"降序");
	Copy(pa->arr[15].str, L"开始");
	Copy(pa->arr[14].str, L"删除");
	
	pa->arr[13].str[0] = L'.';
	pa->arr[13].str[1]= '\0';
	pa->arr[12].str[0] = L'0';
	pa->arr[12].str[1] = '\0';
	FlushBatchDraw();
}

//输出按钮文字
void DrawButtonDown(ST *pa)
{
	RECT r = {0,0,0,0 };
	int i = 0;
	for (i = 0; i < 16; i++)
	{
		settextstyle(40, 0, L"楷体");
		RECT r = { pa->arr[i].x_left,pa->arr[i].y_left,pa->arr[i].x_right,pa->arr[i].y_right };
		setfillcolor(pa->arr[i].color);
		fillrectangle(pa->arr[i].x_left, pa->arr[i].y_left, pa->arr[i].x_right, pa->arr[i].y_right);
		drawtext(pa->arr[i].str, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	}
	
}

//绘画数字
void DrawNumUp(ST* pa)
{
	int i = 0;
	for (i = 0; i < pa->out.size; i++)
	{
		int sz = Len(pa->out.str[i]);
		if (sz > 5)
		{
			settextstyle(170 / sz * 1.8, 0, L"楷体");
		}
		else
		{
			settextstyle(40, 0, L"楷体");
		}

		//设置坐标填充写字
		RECT r = { pa->num[i].x_left,pa->num[i].y_left,pa->num[i].x_right,pa->num[i].y_right };
		setfillcolor(pa->num[i].color);
		fillrectangle(pa->num[i].x_left, pa->num[i].y_left, pa->num[i].x_right, pa->num[i].y_right);
		drawtext(pa->out.str[i], &r, DT_LEFT||DT_SINGLELINE);
	}

	FlushBatchDraw();
}

//画界面线条框
void DrawLine()
{
	//画下方按钮
	line(0, 300, 500, 300);
	line(0, 375, 500, 375);
	line(0, 450, 500, 450);
	line(0, 525, 500, 525);

	line(125, 300, 125, 600);
	line(250, 300, 250, 600);
	line(375, 300, 375, 600);
	//画正上方输出屏
	line(170, 0, 170, 300);
	line(340, 0, 340, 300);
	FlushBatchDraw();
}

//初始化界面
void init(ST *pa)
{
	struct out* ps = &pa->out;
	pa->status = OK;
	//设置输出格式
	initgraph(500, 600);
	setbkcolor(WHITE);
	setlinecolor(BLACK);
	settextcolor(BLACK);

	settextstyle(40,0,L"楷体");
	setbkmode(TRANSPARENT);//透明文字
	setlinestyle(PS_SOLID,3 , 0, 0);
	cleardevice();

	//默认降序
	pa->sort = DOWN;
	FlushBatchDraw();
	//画边框线
	DrawLine();

	//初始化按钮
	InitButton(pa);

	//输出文字与按钮
	DrawButtonDown(pa);
	
	FlushBatchDraw();
}

//检测鼠标在按钮内
void CheckInButton(ST * pa )
{
	//获取鼠标信息
	flushmessage();
	pa->m = getmessage();

	//检测在下方的按钮,在则表示为灰色
	ExMessage m = pa->m;
	int i1 = 0, i2 = 0;
	int count = 0;
	for (i1 = 0; i1 < 4; i1++)
	{
		for (i2 = 0; i2 < 4; i2++)
		{
			if (pa->arr[count].x_left < m.x && m.x < pa->arr[count].x_right
				&& pa->arr[count].y_left<m.y && pa->arr[count].y_right>m.y)
			{
				pa->arr[count].color = RGB(127,127,127);
			}
			else
			{
				pa->arr[count].color = WHITE;
			}
			count++;
		}
	}
	DrawButtonDown(pa);

	//检测上方的数字
	count = 0;
	for (i1 = 0; i1 < 3; i1++)
	{
		for (i2 = 0; i2 < 5; i2++)
		{
			if (pa->num[count].x_left < m.x && m.x < pa->num[count].x_right
				&& pa->num[count].y_left<m.y && pa->num[count].y_right>m.y)
			{
				pa->num[count].color = RGB(127, 127, 127);
			}
			else
			{
				pa->num[count].color = WHITE;
			}
			count++;
		}
	}
	DrawNumUp(pa);

	//刷新缓冲区
	FlushBatchDraw();
}


//排序
int cmpdown(const void* p1, const void* p2)
{

	if (*(double*)p2 > *(double*)p1)
	{
		return 1;
	}
	else
	{
		return -1;
	}

}
int cmpup(const void* p1, const void* p2)
{

	if (*(double*)p1 > *(double*)p2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
}

//数字转字符
void chtonum(WCHAR* str,  double m, ST* pa,int i)
{
	char arr[100] = { 0 };
	int flag = 0;

	//检测有无小数
	if (pa->out.strnum[i] - (int)pa->out.strnum[i] > 0.000001 || pa->out.strnum[i] - (int)pa->out.strnum[i] < -0.000001)
	{
		flag = 1;
	}
	flag==0?sprintf(arr, "%.0lf", m): sprintf(arr, "%.4lf", m);
	char* p = arr;
	while (*str++ = (WCHAR)*p++);
}

//调整删除后的数字
void Adjust(ST* pa)
{
	int i = 0;

	for (i = 0; i < pa->out.size; i++)
	{
		if (pa->out.str[i][0] == L'\0')
		{
			//覆盖最后的数字
			pa->num[pa->out.size - 1].color = WHITE;
			setfillcolor(pa->num[pa->out.size - 1].color);
			fillrectangle(pa->num[pa->out.size - 1].x_left, pa->num[pa->out.size - 1].y_left, pa->num[pa->out.size - 1].x_right, pa->num[pa->out.size - 1].y_right);
			FlushBatchDraw();

			for (int j = i; j < pa->out.size - 1; j++)
			{
				Copy(pa->out.str[j], pa->out.str[j + 1]);
			}
			pa->out.str[pa->out.size - 1][0] = L'\0';
			pa->out.size--;
		}
	}

}

//开始排序
void Start(ST*pa)
{
	//排序
	if (pa->sort == DOWN)
	{
		qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpdown);
	}
	else
	{
		qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpup);
	}
	int i = 0;
	//数字转字符输出
	for (i = 0; i < pa->out.size; i++)
	{
		chtonum(pa->out.str[i], pa->out.strnum[i], pa, i);
	}
	DrawNumUp(pa);
	FlushBatchDraw();
}

//输出最后一个元素
void DrawLast(ST* pa)
{
	int sz = Len(pa->out.str[pa->out.size]);
	if (sz != 0)
	{
		if (sz > 5)
		{
			settextstyle(170 / sz * 1.8, 0, L"楷体");
		}
		else
		{
			settextstyle(40, 0, L"楷体");
		}

		RECT r = { pa->num[pa->out.size].x_left,pa->num[pa->out.size].y_left,pa->num[pa->out.size].x_right,pa->num[pa->out.size].y_right };
		setfillcolor(pa->num[pa->out.size].color);
		fillrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		drawtext(pa->out.str[pa->out.size], &r, DT_LEFT || DT_SINGLELINE);
	}
	line(pa->num[pa->out.size].x_left + 60, pa->num[pa->out.size].y_left + 55, pa->num[pa->out.size].x_left + 100, pa->num[pa->out.size].y_left + 55);
}


//鼠标右键删除
void DelMouse(ST* pa)
{
	int i = 0;
	if (pa->m.rbutton == 1)
	{
		for (i = 0; i < 16; i++)
		{
			if (pa->num[i].color != WHITE)
				break;
		}
		pa->out.str[i][0] = '\0';
		pa->num[i].color = WHITE;

		//最后的元素清空
		setfillcolor(WHITE);
		solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		//消除连续点击鼠标
		Sleep(60);
		while (pa->m.lbutton == 1)
		{
			flushmessage();
			pa->m = getmessage();
		}
	}

	//调整删除数字
	Adjust(pa);
}

//获取鼠标信息
void Get(ST *pa)
{
	int i = 0;
	//检测鼠标按下
	if (pa->m.lbutton == 1)
	{
		//检测鼠标位置
		for (i = 0; i < 16; i++)
		{
			if (pa->arr[i].color != WHITE)
				break;
		}

		if (i!=3 && i!=7 && i!=11 && i!=15 && i!=14)//输入数字
		{
			int sz = Len(pa->out.str[pa->out.size]);
			pa->out.str[pa->out.size][sz++] = pa->arr[i].str[0];
			pa->out.str[pa->out.size][sz] = '\0';
		}
		else if(i==3)//确认
		{
			//字符转数字,便于比较
			char tmp[100] = { 0 };
			swap(tmp, pa->out.str[pa->out.size]);
			sscanf(tmp, "%lf", &pa->out.strnum[pa->out.size]);
			pa->out.size++;
		}
		else if (i == 14)//删除
		{
			pa->out.str[pa->out.size][0] = '\0';
			setfillcolor(WHITE);
			solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		}
		else if (i == 11)//降序
		{
			pa->sort = DOWN;
		}
		else if (i == 7)//升序
		{
			pa->sort=UP;
		}
		else if (i == 15)//开始
		{
			pa->status = START;
			Start(pa);
		}
		
		//消除连续点击鼠标
		Sleep(60);
		while (pa->m.lbutton == 1)
		{
			flushmessage();
			pa->m= getmessage();
		}
	}
	
	//右键删除
	DelMouse(pa);

	//输出最后输入的数字
	DrawLast(pa);
	
	FlushBatchDraw();
}

void End(void)
{
	RECT r = { 0, 400,500, 600 };
	drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	//不断检测信息,直到按下enter
	ExMessage m = getmessage();
	while (m.vkcode != VK_RETURN)
	{
		m = getmessage();
	}
	closegraph();
}


void Welcome(void)
{
	//创建窗口
	initgraph(500, 600);
	setbkcolor(WHITE);

	//加载图片
	IMAGE img;

	loadimage(&img, _T("../../开始界面.jpg"),500,600);
	putimage(0, 0, &img);

	//第一层进入
	RECT r = { 0, 0,500, 600 };
	settextcolor(RED);
	setbkmode(TRANSPARENT);//透明文字
	settextstyle(40, 0, L"楷体");
	drawtext(_T("欢迎使用排序计算器!"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	 r = { 0, 400,500, 600 };
	drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	FlushBatchDraw();
	ExMessage m = getmessage();
	while (m.vkcode != VK_RETURN)
	{
		m = getmessage();
	}

	Sleep(100);//弹起按钮需要时间,程序运行几ms,避免一次确认识别多次

	//第二次规则
	putimage(0, 0, &img);
	r = {10, 100,500, 200 };
	drawtext(_T("1.不要连续快速点击按钮"), &r, DT_LEFT);
	r = { 10, 200,500, 300 };
	drawtext(_T("2.鼠标左键确认,右键可删除数字"), &r, DT_LEFT| DT_WORDBREAK);
	r = {10, 300,500, 400 };
	drawtext(_T("3.可识别操作间隔是60ms"), &r, DT_LEFT);
	r = {10, 400,500,500 };
	drawtext(_T("4.如果1秒后无结果请重新按"), &r, DT_LEFT);

	r = { 0, 400,500, 600 };
	drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	FlushBatchDraw();

	flushmessage();
	m.vkcode = 0;
	while (m.vkcode != VK_RETURN)
	{
		m = getmessage();
	}
}

int main()
{
	ST a;//以结构体维护系统

	//欢迎界面
	Welcome();

	//初始化
	init(&a);
	
	while (a.status == OK)
	{
		//跟踪鼠标
		CheckInButton(&a);
		
		//获取点击信息
		Get(&a);

	}

	//结束标识
	End();

	return 0;
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值