漂亮的C++控制台界面(格式化输入输出)

快要放假了,作为一个萌萌嗒的软件专业的小学生,终于要和“课设”这个家伙说hello了~
但是学校总是让做一些系统,比如图书管理系统啦,成绩管理系统啦,lol段位管理系统啦,炉石卡包管理系统啦。。。哈哈,又在瞎掰了,竟然被你们看出来了害羞~
可是我觉得这虽然能够锻炼我们的编程能力,但是界面一点都不美观,一直黑不隆咚的,觉得好别扭。于是突发奇想,可不可以自己写一个类,然后提供一些方法,便于生成漂亮的界面呢,说干就干~~~

先来亮一下输出界面吧:




怎么样,是不是比以前看到的漂亮多了,那它是怎么实现的呢?


void H_Student::showInfo()
{
	IO_Table io;
	io.setNavigation("学生类型,学生ID,学生姓名,性别,年龄,班级",6);
	io.setTitle("学生管理系统");
	H_Student *temp;
	for (temp = mHead; temp != NULL; temp = temp->mNext)
	{
		string data[] = { "大学生", to_string(temp->mID), string(temp->mName),
			string(((temp->mSex == 1) ? "男" : "女")), to_string(temp->mAge), to_string(temp->mClass) };
		io.addRow(data);
	}
	temp = NULL;
}


再看看输入界面:




IO_Table io;
string data[] = { "学生类型", " 学生ID", " 学生姓名", "性别", "年龄", "班级" };
io.inputData(data,6);

就这么简单的几行代码,是不是很方便呢?


下面来看下这个类的实现方法~

这是头文件IO_Table.h:

#pragma once
#include <windows.h>
#include <string>
#include <iostream>
#include <iomanip>
using namespace std;
class IO_Table
{
public:
	/*********************************
	┌───────┬───────┐
	│ 标题1 │ 标题2 │
	├───────┼───────┤
	│ 内容1 │ 内容2 │
	└───────┴───────┘
	*********************************/
private:
	typedef struct DATA
	{
		string *mRowData;
		struct DATA *mNext;
	}Data;

	static Data *mHead;
	static Data *mTemp;

private:
	int mLeft;				//起始x坐标(为了居中显示,所以动态获取)
	static const int mTop = 1;		//起始y坐标
	int mRow;				//逻辑行数
	int mCol;				//逻辑列数
	static int *mWidth;		//每一列的宽度
	int mAllWidth;			//总宽度(包括边框)

	static string *mInput;	//输入内容
	string mTitle;			//标题

private:
	HANDLE mHand;									//控制台句柄

	static CONSOLE_CURSOR_INFO cursor_true ;		//光标可见
	static CONSOLE_CURSOR_INFO cursor_false;		//光标不可见
	
	void gotoxy(int x,int y);						//广角定位
	int getAllWidth();								//获取表格总宽度(包括边框)

	void printNavigation(string *navigation);			//打印导航栏
	void printRow(string *content);						//打印行
	void refresh();										//刷新
	void clear();										//清空
	void delPoint(Data*);								//删除某个节点

public:			
	IO_Table(void);										//构造函数
	~IO_Table();										//析构函数

	void setTitle(string title);						//设置标题栏

	/*************************************************************
	* 对于char型数组,可以用string(char *)转换成为string类型
	* 对于其他的数据类型,可以利用to_string()函数来转换到string类型
	*************************************************************/
	void setNavigation(string *navigation, int col);	//设置导航栏
	void setNavigation(string navigation, int col);		//设置导航栏
	void addRow(string *content);						//插入一整行

	//用完以后一定要用delete[]删除,切记切记!
	string *inputData(string *navigation, int row);		//输入数据
	string *inputData(string navigation, int row);		//输入数据

	void setInputResult(string strTips);				//输入提示
	
};


这是IO_Table.cpp:

#include "IO_Table.h"

/*char *mHalo[] = { "──", "┌", "┬", "┐", "├", "┼", "┤", "└", "┴", "┘", "│" }; */

IO_Table::Data *IO_Table::mHead = NULL;
IO_Table::Data *IO_Table::mTemp = NULL;

string *IO_Table::mInput = NULL;	//输出初始化
int *IO_Table::mWidth = NULL;		//宽度初始化

CONSOLE_CURSOR_INFO IO_Table::cursor_true = { 5, TRUE };
CONSOLE_CURSOR_INFO IO_Table::cursor_false = { 5, FALSE };

IO_Table::IO_Table()
{
	clear();
	mHand = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorInfo(mHand, &cursor_false);
}
//==================================设置标题====================================
void IO_Table::setTitle(string title)
{
	if (mCol == 0) return;

	mTitle = title;		

	int surplus = title.length() / 2 - (getAllWidth() - 2);	

	gotoxy(mLeft + 1, mTop + 1);		//清空标题框
	cout << setw(getAllWidth()-2) << "";

	//判断标题文字是否超过最大宽度
	if (surplus <= 0)
	{
		SetConsoleTextAttribute(mHand, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
		gotoxy(mLeft + abs(int((getAllWidth() - title.length()/2) / 2)), mTop + 1);
		cout << title;
	}
	else{
		int minIndex=0;
		for (int c = 0; c < mCol; c++)
		{
			mWidth[c] += (surplus / mCol);
			if (mWidth[minIndex]>mWidth[c]) minIndex = c;
		}
		mWidth[minIndex] += surplus % mCol;
		refresh();

		SetConsoleTextAttribute(mHand, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
		gotoxy(mLeft + (getAllWidth() - title.length() / 2) / 2, mTop + 1);
		cout << title;
	}
}
//=========================添加导航栏=============================
void IO_Table::setNavigation(string *navigation, int col)
{
	mCol = col;

	//链表重置,添加导航栏到表头
	if (mHead != NULL) clear();
	mHead = mTemp = (Data*)malloc(sizeof(Data));
	mTemp->mNext = NULL;
	
	mTemp->mRowData = new string[col];

	//宽度重置,根据所给值分配
	if (mWidth != NULL)
	{
		delete[] mWidth;
		mWidth = NULL;
	}
	mWidth = new int[mCol];
	for (int c = 0; c < mCol; c++)
	{
		mWidth[c] = int(navigation[c].length() / 2 + 0.5);
		mTemp->mRowData[c] = navigation[c];		//把数据存储在链表中
	}

	printNavigation(navigation);
}
void IO_Table::setNavigation(string navigation, int col)
{
	mCol = col;
	
	//链表重置,添加导航栏到表头
	if (mHead != NULL) clear();
	mHead = mTemp = (Data*)malloc(sizeof(Data));
	mTemp->mNext = NULL;

	mTemp->mRowData = new string[col];

	//宽度重置,根据所给值分配
	if (mWidth != NULL)
	{
		delete[] mWidth;
		mWidth = NULL;
	}
	mWidth = new int[mCol];

	string strTemp = "";	//临时字符
	int index = 0;			//当前元素
	for (int c = 0; c < navigation.length(); c++)
	{
		if (navigation[c] != ',' && navigation[c] != ',')
		{
			strTemp += navigation[c];
		}
		else{
			mTemp->mRowData[index] = strTemp;
			mWidth[index] = int(strTemp.length() / 2 + 0.5);
			strTemp.clear();
			index++;
		}
	}
	mTemp->mRowData[index] = strTemp;
	mWidth[index] = int(strTemp.length() / 2 + 0.5);

	printNavigation(mTemp->mRowData);
}
//绘制表格边框
void IO_Table::printNavigation(string *naviation)
{
	system("cls");
	//每次重绘表头时,行数都归零
	mRow = 0;
	//获取水平位置起始点,以居中显示表格
	mLeft = (40 - getAllWidth()) / 2;
	
	SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
	//第1行
	gotoxy(mLeft, mTop);
	cout << "┌";
	for (int c = 0; c < mCol; c++)
	{
		for (int w = 0; w < mWidth[c]; w++)
			cout << "─";
		cout << (c == mCol - 1 ? "┐" : "─");
	}
	//第2行
	gotoxy(mLeft, mTop + 1);
	cout << "│";
	cout << setw(getAllWidth()*2 - 4) << "";
	cout << "│";
	//第3行
	gotoxy(mLeft, mTop + 2);
	cout << "├";
	for (int c = 0; c < mCol; c++)
	{
		for (int w = 0; w < mWidth[c]; w++)
			cout << "─";
		cout << (c == mCol - 1 ? "┤" : "┬");
	}
	//第4行
	gotoxy(mLeft, mTop + 3);
	for (int c = 0; c < mCol; c++)
	{
		cout << "│";
		SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
		cout << setw(mWidth[c]*2) << naviation[c];
		SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
	}
	cout << "│";
	//第5行
	gotoxy(mLeft, mTop + 4);
	cout << "└";
	for (int c = 0; c < mCol; c++)
	{
		for (int w = 0; w < mWidth[c]; w++)
			cout << "─";
		cout << (c == mCol - 1 ? "┘" : "┴");
	}
}
//==========================添加行======================================
void IO_Table::addRow(string *content)
{
	if (mCol == 0) return;
	bool needRefresh = false;	//是否需要刷新(当某个数据超过当前列的宽度时需要刷新)
	if (mHead == NULL)
		return;	//如果没有表头,直接退出
	else{
		mTemp->mNext = (Data*)malloc(sizeof(Data));
		mTemp = mTemp->mNext;
		mTemp->mNext = NULL;
	}
	mTemp->mRowData = new string[mCol];
	for (int c = 0; c < mCol; c++)
	{
		mTemp->mRowData[c] = content[c];

		if ((content[c].length()/2) > mWidth[c])
		{
			mWidth[c] = (content[c].length() % 2 == 0 ? content[c].length()/2 : content[c].length()/2 + 1);
			needRefresh = true;
		}
	}

	if (needRefresh)
		refresh();
	else
		printRow(content);
}
void IO_Table::printRow(string *content)
{
	SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
	gotoxy(mLeft, mTop + mRow * 2 + 4);
	cout << "├";
	for (int c = 0; c < mCol; c++)
	{
		for (int w = 0; w < mWidth[c] ; w++) 
			cout << "─";
		cout << (c == mCol - 1 ? "┤" : "┼");
	}

	gotoxy(mLeft, mTop + mRow * 2 + 5);
	for (int c = 0; c < mCol; c++)
	{
		cout << "│";
		SetConsoleTextAttribute(mHand, 0x0f);
		cout << setw(mWidth[c]*2) << content[c];
		SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
	}
	cout << "│";

	gotoxy(mLeft, mTop + mRow * 2 + 6);
	cout << "└";
	for (int c = 0; c < mCol; c++)
	{
		for (int w = 0; w < mWidth[c] ; w++) 
			cout << "─";
		cout << (c == mCol - 1 ? "┘" : "┴");
	}

	mRow++;
}
//===========================输入=================================
string *IO_Table::inputData(string *navigation, int row/*数据个数*/)
{
	clear();
	system("cls");
	
	mRow = row;
	mLeft = 10;

	for (int r = 0; r <= mRow; r++)
	{
		SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_GREEN |FOREGROUND_INTENSITY);
		gotoxy(mLeft, mTop + r * 2 );
		cout << (r == 0 ? "┌" : "├");
		for (int c = 0; c < 20; c++)
		{
			cout << (c == 5 ? (r == 0 ? "┬" : (r == mRow ? "┴" : "┼")) : "─");
		}
		cout << (r == 0 ? "┐" : "┤");

		gotoxy(mLeft, mTop + r * 2 + 1);
		cout << "│";
		if (r != mRow)
		{
			SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
			cout << setw(10) << navigation[r];
			SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_GREEN |FOREGROUND_INTENSITY);
			cout << "│";
			cout << setw(28) << "";
		}
		else
			cout << setw(40) << "";
		cout << "│";

		gotoxy(mLeft, mTop + r * 2 + 2);
		cout << "└";
		for (int c = 0; c < 20; c++)
			cout << (c == 5 && r != mRow ? "┴" : "─");
		cout << "┘";
	}

	int index = 0;		//当前输入焦点行
	string strTemp;		//临时数据的数据
	string *strInput = new string[row];	//所有输入数据的集合

	SetConsoleCursorInfo(mHand, &cursor_true);
	SetConsoleTextAttribute(mHand, 0x0f);
	do
	{
		gotoxy(mLeft + 7, mTop + index * 2 + 1);
		fflush(stdin);
		getline(cin, strTemp);
		if (strTemp.length() == 0) 
			continue;
		strInput[index] = strTemp;
	} while (++index < row);

	SetConsoleCursorInfo(mHand, &cursor_false);
	gotoxy(mLeft + 1, mTop + index * 2 + 1);
	SetConsoleTextAttribute(mHand, 0x0f);
	cout << "  输入成功!  请按任意键继续...";

	return strInput;
}
string *IO_Table::inputData(string navigation, int row)
{
	clear();
	system("cls");

	int width = 5;
	mRow = row;
	mLeft = 10;

	string *strNavigation = new string[row];
	string strTemp = "";	//临时字符
	int index = 0;			//当前元素
	for (int c = 0; c < navigation.length(); c++)
	{
		if (navigation[c] != ',' && navigation[c] != ',')
		{
			strTemp += navigation[c];
		}
		else{
			width = width < strTemp.length()/2 ? width : strTemp.length()/2;
			strNavigation[index++] = strTemp;
			strTemp.clear();
		}
	}
	strNavigation[index] = strTemp;
	width = width < strTemp.length()/2 ? width : strTemp.length()/2;

	for (int r = 0; r <= mRow; r++)
	{
		SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
		gotoxy(mLeft, mTop + r * 2);
		cout << (r == 0 ? "┌" : "├");
		for (int c = 0; c < 20; c++)
		{
			cout << (c == width ? (r == 0 ? "┬" : (r == mRow ? "┴" : "┼")) : "─");
		}
		cout << (r == 0 ? "┐" : "┤");

		gotoxy(mLeft, mTop + r * 2 + 1);
		cout << "│";
		if (r != mRow)
		{
			SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
			cout << setw(10) << strNavigation[r];
			SetConsoleTextAttribute(mHand, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
			cout << "│";
			cout << setw(28) << "";
		}
		else
			cout << setw(40) << "";
		cout << "│";

		gotoxy(mLeft, mTop + r * 2 + 2);
		cout << "└";
		for (int c = 0; c < 20; c++)
			cout << (c == width && r != mRow ? "┴" : "─");
		cout << "┘";
	}

	delete[] strNavigation;

	index = 0;		//当前输入焦点行
	string *strInput = new string[row];	//所有输入数据的集合

	SetConsoleCursorInfo(mHand, &cursor_true);
	SetConsoleTextAttribute(mHand, 0x0f);
	do
	{
		gotoxy(mLeft + 7, mTop + index * 2 + 1);
		fflush(stdin);
		getline(cin, strTemp);
		if (strTemp.length() == 0)
			continue;
		strInput[index] = strTemp;
	} while (++index < row);
	setInputResult("输入成功!");
	return strInput;
}
void IO_Table::setInputResult(string strTips)
{
	if (mRow == 0) return;
	SetConsoleCursorInfo(mHand, &cursor_false);
	gotoxy(mLeft + 1, mTop + mRow * 2 + 1);
	cout << setw(40) << "";
	gotoxy(mLeft + 1, mTop + mRow * 2 + 1);
	SetConsoleTextAttribute(mHand, 0x0f);
	cout << strTips;
}

void IO_Table::refresh()
{
	Data *temp;
	for (temp = mHead; temp != NULL; temp = temp->mNext)
	{
		if (temp == mHead)
			printNavigation(temp->mRowData);
		else
			printRow(temp->mRowData);
	}

	if (!mTitle.empty()) setTitle(mTitle);
}

inline void IO_Table::gotoxy(int x, int y)
{
	COORD cd;
	cd.X = x << 1;
	cd.Y = y;
	SetConsoleCursorPosition(mHand,cd);
}

int IO_Table::getAllWidth()
{
	int allWidth = mCol + 1;
	for (int c = 0; c < mCol; c++)
		allWidth += mWidth[c];
	return allWidth;
}

void IO_Table::clear()
{

	mRow = 0;
	mCol = 0;
	mLeft = 0;

	if (mWidth != NULL)
	{
		delete[] mWidth;
		mWidth = NULL;
	}

	if (mInput != NULL)
	{
		delete[] mInput;
		mInput = NULL;
	}

	Data *next;
	mTemp = mHead;
	while (mTemp != NULL)
	{
		//先把行数据清空
		delete[] mTemp->mRowData;
		mTemp->mRowData = NULL;
		//释放节点并删除
		next = mTemp->mNext;
		free(mTemp);
		mTemp = next;
	}
	mHead = mTemp = next = NULL;
}

IO_Table::~IO_Table()
{
	clear();
}

现在呢,它不仅可以格式化输出,还可以格式化输入,并且可以根据数据的长度自由控制表格的宽度,标题和表格都是居中显示的。但是,它也有一些缺点,就是如果数据过于长且超过窗口的最大宽度,它会乱行吐舌头

由于技术有限,目前只能写到这里,当然程序中一定会有一些bug,所以这里分享给大家,希望大家能够和我一起改进它,让它成为一个可以被广泛使用的类。



这是第三版,增加了大量关于键盘缓冲区操作和输入、输出操作的知识讲解,并修改了多处前两版中文字、语句错误的地方。 前两个版本由于我等级不够无法删除,此处留下前两版的地址,希望对大家有用。 第一版:http://download.csdn.net/source/3056070 第二版:http://download.csdn.net/source/3332359 以下为第三版本的目录: C/C++控制台界面编程(V 3) 1 目录 - 1 - 第一部分 控制台界面编程预备知识 1 1) Visual Studio 2005中控制台程序的类型 1 2) 转义字符及格式化输入、输出 1 a) 制表符\t 2 b) 回退字符\b 4 c) ASCII码表 6 d) 以%开头的格式控制符 9 e) 数据流的格式设置 10 3) C和C++库的输入、输出操作 12 a) stdio.h中的常用输入、输出函数 13 b) basic_stream中的输入、输出操作 13 4) 键盘缓冲区处理 15 5) 关于C/C++中的字符串拼接问题 17 6) 怎样从控制台复制粘贴文字 18 7) 将批处理bat转换为exe程序 18 8) 在Visual Studio 2005中设置控制台程序的图标 18 9) 重定向控制台程序的输出 19 第二部分 控制台界面编程详解 20 1) 概述 20 2) 控制台文本窗口编程的一般控制步骤 21 3) 控制台窗口操作函数 21 4) 文本属性操作 25 5) 文本输出 28 6) 文本操作示例 28 7) 滚动和移动 34 8) 光标操作 36 9) 读取键盘信息 37 10) 读取鼠标信息 44 11) 结束语 46 第三部分 附录 1 1) 分数等级划分工具 1 a) controlio.h文件 1 b) Main.c文件 5 2) 简易俄罗斯方块 6 a) 代码Main.c文件 7 3) 模拟实现可用鼠标、键盘控制的菜单和窗口 11 这是第三版,增加了大量关于键盘缓冲区操作和输入、输出操作的知识讲解,并修改了多处前两版中文字、语句错误的地方。 前两个版本由于我等级不够无法删除,此处留下前两版的地址,希望对大家有用。 第一版:http://download.csdn.net/source/3056070 第二版:http://download.csdn.net/source/3332359
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值