漂亮的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 
#include 
#include 
#include 
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,所以这里分享给大家,希望大家能够和我一起改进它,让它成为一个可以被广泛使用的类。



你可能感兴趣的:(C++控制台程序)