C语言/C++ - 实现上下左右功能选择菜单

版权声明:转载请注明出处 https://blog.csdn.net/qq_42292831/article/details/85240616

☆主要用到的函数:

//这个函数是确认输出的具体位置,通过COORD结构体)
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
//这个函数主要更改文字的前景色与背景色第二个参数0`15表示文字15种前景色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), wArr);  
//控制控制台上的光标显示信息
CONSOLE_CURSOR_INFO cci;
cci.dwSize = 1;    //1~100
cci.bVisible = FALSE;    //TRUE/FALSE
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE),&cci);
//Keyboard hit 键盘敲击
_kbhit()

************************************************************************************************************************************

一:实现 "定位指定颜色输出以及控制光标的厚度是否闪动"

代码仅仅作测试功能使用) 

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
	char *print_char = "123";
	COORD loc ;    //也可以使用COORD loc = {30,11};的方式赋值,只不过那样不利于参数的传递
	int x = 30;    //不能超过初始的Console的大小,否者这里的定位无效,这里的x轴就是水平方向
	int y = 11;
	loc.X = x;
	loc.Y = y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
	cout << "AAA";
	cout << "BBB" << endl;
	cout << "CCC" << endl;    //换行坐标的x坐标直接归零,y坐标+1

	//0~15十六个数表示十六种颜色,0表示纯黑,15表示纯白
	//SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0); cout << print_char << endl;   //纯黑前景色
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 1); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 2); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 3); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 5); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 6); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 8); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 9); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 10); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 11); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 12); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 13); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 14); cout << print_char << endl;
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 15); cout << print_char << endl;  //纯白前景色

	system("pause");    //这里的光标没有被隐藏,还在一闪一闪的

	CONSOLE_CURSOR_INFO cci;    //cci = console cursor info
	cci.dwSize = 1;   //光标的厚度(1~100),其余值直接默认厚度
	cci.bVisible = FALSE;    //光标不可见
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);    //这里可以理解为取地址,也可以理解为引用,概念不同但原理一致

	system("pause");    //这里的光标就不会出现了,因为上方指定了光标不显示
	return 0;
}





注释:

<一>

对于这里的SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),XXX);

这里的XXX参数也可以使用其他格式:

SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_BLUE | FOREGROUND_INTENSITY);

因为VS中在 wincon.h 头文件中给与了许多预编译定义(该头文件不用用户自己使用#include声明):

//前景(字体)
#define FOREGROUND_BLUE      0x0001 // text color contains blue.
#define FOREGROUND_GREEN     0x0002 // text color contains green.
#define FOREGROUND_RED       0x0004 // text color contains red.
#define FOREGROUND_INTENSITY 0x0008 // text color is intensified.

//背景
#define BACKGROUND_BLUE      0x0010 // background color contains blue.
#define BACKGROUND_GREEN     0x0020 // background color contains green.
#define BACKGROUND_RED       0x0040 // background color contains red.
#define BACKGROUND_INTENSITY 0x0080 // background color is intensified.

* 对于上面的"|"则是按位进行或运算,在上面语句中的含义即是将各个属性通过或的关系进行连接

<二>

对于CONSOLE_CURSOR_INFO结构体中的dwSize与bVisible(参见结构体原先定义):

typedef struct _CONSOLE_CURSOR_INFO {

//第二个属性设置的时候必须在第一个属性已经设置的前提下才能生效,第一个则不需要依靠第二个属性
    DWORD dwSize;// 光标百分比厚度(1~100) 
    BOOL  bVisible;// 是否可见
}CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

关于DWORD类型,word表示两个字节的数据类型(对比int为4个字节),下面给出一段测试:

WORD a = 1;
DWORD b = 1;
int c = 1;
cout << "sizeof(WORD:a) = " << sizeof(a) << endl;    //2
cout << "sizeof(DWORD:b) = " << sizeof(b) << endl;    //4
cout << "sizeof(int:b) = " << sizeof(c) << endl;    //4

二:指定清除某一行的输出信息

1> 原理

定位光标某一行的坐标(Y坐标),然后对X坐标使用循环语句用空格去替换原有的输出在控制台上的信息;

2> 测试源码

#include <iostream>
#include <windows.h>
using namespace std;

void CleanConsole(int x, int y)     //y是指定要删除的行数,x可以从0位置开始循环替换到的截止位置
{
	COORD loc;
	loc.Y = y;    //固定行数
	for (int i = 0; i < x; i++)     //x从指定位置开始循环进行替换
	{
		loc.X = i;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
		cout << " " << endl;;
	}
}

void CleanConsole( int y)     //y是指定要删除的行数,直接指定x:0~50的位置进行清空
{
	COORD loc;
	loc.Y = y;    //固定行数
	for (int i = 0; i < 50; i++)     //x从指定位置开始循环进行替换
	{
		loc.X = i;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
		cout << " " << endl;;
	}
}

int main()
{
	cout << "     12345" << endl;
	Sleep(1000);    //暂停1s
	CleanConsole(10,0);
	return 0;
}

三:这时候写一个定位输出函数

1. 函数原型

void WriteChar(int x,int y,char *pchar,char color);

函数解释:前两个参数是指定显示的坐标位置,第三个是待显示的字符数组,最后一个是用在SetConsoleTextAttribute()函数中作为控制字体颜色的参数使用;

void WriteChar(int x, int y, char *pchar, char color)
{
	COORD loc = { x,y };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
	cout << pchar;
}

2. 测试程序

#include <iostream>
#include <windows.h>
using namespace std;

void WriteChar(int x, int y, char *pchar, char color)
{
	COORD loc = { x,y };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
	cout << pchar;
}

int main()
{
	char *options[] = { "单人游戏" , "双人游戏","帮助","退出" };
	system("mode con cols=100 lines=30");
	WriteChar(18, 10, "奥特曼打小怪兽 v1.0", 10);
	WriteChar(16, 12, "→", 10);
	for (int i = 0; i < 4; i++)
	{
		WriteChar(18, 12+i, options[i], 10);
	}
	return 0;
}

3. 注意事项

这里使用了一个控制控制台大小的语句:system("mode con cols=100 lines=30");     //columns列lines行

4. 运行结果

四:接下来让箭头动起来

1. 原理

设置一个死循环,循环内部使用_kbhit()函数检测上下键或回车键(\r)是否按下,当上键按下,option选择变量--(起初为0),当下键按下,option选择变量++;

在option++/--的过程中需要同时调用WriteChar()函数在option所指定的下一行/上一行进行输出的擦除,设置一秒延迟后再将箭头和刚刚清楚的下一行/上一行数据一起进行输出显示;

这里比较巧妙的一点是将选项中的四个用数组存起来,当option=0的时候,options[0]也就对应着第一条数据;

当回车(\r)按下,return返回当前的option值,循环跳出;

2. 函数定义

int ShowOptions()
{
	char *options[] = { "单人游戏" , "双人游戏","帮助","退出" };    //这里这样定义是为了使得option的值能和options的下标对应,便于刷新输出
	system("mode con cols=100 lines=30");
	WriteChar(18, 10, "奥特曼打小怪兽 v1.0", 10);
	WriteChar(16, 12, "→", 10);
	for (int i = 0; i < 4; i++)
	{
		WriteChar(18, 12 + i, options[i], 10);
	}
	char ch;
	int option = 0;    //默认指向最上面的选项
	while (true)
	{
		if (_kbhit())
		{
			ch = _getch();  //不进行回显

			if (ch == 27)   //esc
			{
				return -1;
			}
			if (ch == 72 || ch == 80 || ch == '\r')  //只检测上下键+回车直接返回当前的option值
			{

				if (ch == 72)	//UP
				{
					WriteChar(16, 12 + option, "  ", 0);
					option--;
				}
				else if (ch == 80)	//DOWN
				{
					WriteChar(16, 12 + option, "  ", 0);
					option++;
				}
				if (option < 0)    //防止越界
				{
					option = 0;
				}
				else if (option >= 4)    //一直让其指向租后一个选项
				{
					option--;
				}
				//处理按上下键之后的显示
				WriteChar(16, 12 + option, "                        ", 0);
				Sleep(100);
				WriteChar(16, 12 + option, "→", 10);
				WriteChar(18, 12 + option, options[option], 10);

				if (ch == '\r')
				{
					return option;
				}
			}
		}
	}
}

3. 测试程序

#include <iostream>
#include <windows.h>
#include <conio.h>
using namespace std;

void CleanConsole(int x,int y)     //y是指定要删除的行数,x可以从0位置开始循环替换到的截止位置
{
	COORD loc;
	loc.Y = y;    //固定行数
	for (int i = 0; i < x; i++)     //x从指定位置开始循环进行替换
	{
		loc.X = i;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
		cout << " " << endl;;
	}
}
void CleanConsole( int y)     //y是指定要删除的行数,直接指定x:0~50的位置进行清空
{
	COORD loc;
	loc.Y = y;    //固定行数
	for (int i = 0; i < 50; i++)     //x从指定位置开始循环进行替换
	{
		loc.X = i;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
		cout << " " << endl;;
	}
}


void WriteChar(int x, int y, char *pchar, char color)
{
	CONSOLE_CURSOR_INFO cci;
	cci.dwSize = 1;   //光标的厚度
	cci.bVisible = FALSE;    //光标不可见
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);

	COORD loc = { x,y };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
	cout << pchar;
}


int ShowOptions()
{
	char *options[] = { "单人游戏" , "双人游戏","帮助","退出" };    //这里这样定义是为了使得option的值能和options的下标对应,便于刷新输出
	system("mode con cols=100 lines=30");
	WriteChar(18, 10, "奥特曼打小怪兽 v1.0", 10);
	WriteChar(16, 12, "→", 10);
	for (int i = 0; i < 4; i++)
	{
		WriteChar(18, 12 + i, options[i], 10);
	}
	char ch;
	int option = 0;    //默认指向最上面的选项
	while (true)
	{
		if (_kbhit())
		{
			ch = _getch();  //不进行回显

			if (ch == 27)   //esc
			{
				return -1;
			}
			if (ch == 72 || ch == 80 || ch == '\r')  //只检测上下键+回车直接返回当前的option值
			{

				if (ch == 72)	//UP
				{
					WriteChar(16, 12 + option, "  ", 0);
					option--;
				}
				else if (ch == 80)	//DOWN
				{
					WriteChar(16, 12 + option, "  ", 0);
					option++;
				}
				if (option < 0)    //防止越界
				{
					option = 0;
				}
				else if (option >= 4)    //一直让其指向租后一个选项
				{
					option--;
				}
				//处理按上下键之后的显示
				WriteChar(16, 12 + option, "                        ", 0);
				Sleep(100);
				WriteChar(16, 12 + option, "→", 10);
				WriteChar(18, 12 + option, options[option], 10);

				if (ch == '\r')
				{
					return option;
				}
			}
		}
	}
}

void Page_1()
{
	system("cls");
	WriteChar(1, 1, "Just a raw page_1...\n\n", 15);
}
void Page_2()
{
	system("cls");
	WriteChar(1, 1, "Just a raw page_2...\n\n", 15);
}
void Page_3()
{
	system("cls");
	WriteChar(0, 0, "Just a raw page_3...\n\n", 15);
}


int main()
{
	while (true)
	{
		int result = ShowOptions();
		if (result == 0)
		{
			Page_1();
			system("pause");
		}
		else if (result == 1)
		{
			Page_2();
			system("pause");
		}
		else if (result == 2)
		{
			Page_3();
			system("pause");
		}
		else
		{
			cout << endl << endl;
			return -1;
		}
	}
	
	return 0;
}

五:应用

示例:设置一个定时关机程序,可以通过方向键选择功能

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <windows.h>
#include <conio.h>

using namespace std;

void CleanConsole(int x, int y)     //y是指定要删除的行数,x可以从0位置开始循环替换到的截止位置
{
	COORD loc;
	loc.Y = y;    //固定行数
	for (int i = 0; i < x; i++)     //x从指定位置开始循环进行替换
	{
		loc.X = i;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
		cout << " " << endl;;
	}
}
void CleanConsole(int y)     //y是指定要删除的行数,直接指定x:0~50的位置进行清空
{
	COORD loc;
	loc.Y = y;    //固定行数
	for (int i = 0; i < 50; i++)     //x从指定位置开始循环进行替换
	{
		loc.X = i;
		SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
		cout << " " << endl;;
	}
}


void WriteChar(short x, short y, char *pchar, char color)
{
	CONSOLE_CURSOR_INFO cci;
	cci.dwSize = 1;   //光标的厚度
	cci.bVisible = FALSE;    //光标不可见
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);

	COORD loc = { x,y };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), loc);
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
	cout << pchar;
}


int ShowOptions()
{
	char *options[] = { "1.实现10分钟内的定时关闭计算机\n" , "2.立即关闭计算机\n","3.注销计算机","退出" };    //这里这样定义是为了使得option的值能和options的下标对应,便于刷新输出
	system("mode con cols=100 lines=30");
	WriteChar(18, 10, "关机程序 v1.0", 10);
	WriteChar(16, 12, "→", 10);
	for (int i = 0; i < 4; i++)
	{
		WriteChar(18, 12 + i, options[i], 10);
	}
	char ch;
	int option = 0;    //默认指向最上面的选项
	while (true)
	{
		if (_kbhit())
		{
			ch = _getch();  //不进行回显

			if (ch == 27)   //esc
			{
				return -1;
			}
			if (ch == 72 || ch == 80 || ch == '\r')  //只检测上下键+回车直接返回当前的option值
			{

				if (ch == 72)	//UP
				{
					WriteChar(16, 12 + option, "  ", 0);
					option--;
				}
				else if (ch == 80)	//DOWN
				{
					WriteChar(16, 12 + option, "  ", 0);
					option++;
				}
				if (option < 0)    //防止越界
				{
					option = 0;
				}
				else if (option >= 4)    //一直让其指向租后一个选项
				{
					option--;
				}
				//处理按上下键之后的显示
				WriteChar(16, 12 + option, "                                      ", 0);
				Sleep(100);
				WriteChar(16, 12 + option, "→", 10);
				WriteChar(18, 12 + option, options[option], 10);

				if (ch == '\r')
				{
					return option;
				}
			}
		}
	}
}

void Page_1()
{
	system("cls");
	char close_number[5] = {0};
	cout << "您想在多少秒后自动关闭计算机?(0~600)\n";
	cin >> close_number;
	char cmd[20] = "shutdown -s -t ";
	system(strcat(cmd,close_number));
}
void Page_2()
{	
	system("shutdown -p");
}
void Page_3()
{
	system("shutdown -l");
}


int main()
{
	while (true)
	{
		int result = ShowOptions();
		if (result == 0)
		{
			Page_1();
		}
		else if (result == 1)
		{
			Page_2();
		}
		else if(result == 2)
		{
			Page_3();
		}
		else
		{
			cout << endl << endl;
			return -1;
		}
	}

	return 0;
}

注释:

关于关机程序所用到的cmd指定(可在cmd窗口下使用help shutdown来进行查看):

shutdown -s -t     //定时关闭计算机

shutdown -p    //立刻关闭计算机

shutdown -l    //注销计算机

shutdown -f    //不提醒直接强制关闭所有进程

猜你喜欢

转载自blog.csdn.net/qq_42292831/article/details/85240616