CD管理程序现在我们已经了解了curses所提供了功能,我们可以继续开发我们的例子程序。在这里所展示是一个使用curses库的C语言版本。他提供了一些高级的特性,包括更为清晰的屏幕信息显示以及用于跟踪列表的滚动窗口。
完整的程序共页长,所以我们将其分为几部分,在每一部分中介绍一些函数。
试验--一个新的CD管理程序1 首先,我们包含所有的头文件以及一些全局常量。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <curses.h>
#define MAX_STRING 80 /* Longest allowed response */
#define MAX_ENTRY 1024 /* Longest allowed database entry */
#define MESSAGE_LINE 6 /* Misc. messages on this line */
#define ERROR_LINE 22 /* Line to use for errors */
#define Q_LINE 20 /* Line for questions */
#define PROMPT_LINE 18 /* Line for prompting on */
2 接下来,我们需要一些全局变量。变量current_cd用于存储当前我们所用的CD标题。对其进行初始化,从而其第一个字符为null表明"没有选中CD"。\0并不是严格必须的,但是他可以保证变量已经进行了初始化,这是一个很好的习惯。变量current_cat用于记录当前CD的分类。
static char current_cd[MAX_STRING] = “\0”;
static char current_cat[MAX_STRING];
3 现在需要定义一些文件名。为了简单,在这个版本中文件名都使用固定的文件名,包括临时文件名。当两个用户在相同的目录中运行这个程序时就会出现问题。
const char *title_file = “title.cdb”;
const char *tracks_file = “tracks.cdb”;
const char *temp_file = “cdb.tmp”;
4 最后,我们定义函数原型。
void clear_all_screen(void);
void get_return(void);
int get_confirm(void);
int getchoice(char *greet, char *choices[]);
void draw_menu(char *options[], int highlight,
int start_row, int start_col);
void insert_title(char *cdtitle);
void get_string(char *string);
void add_record(void);
void count_cds(void);
void find_cd(void);
void list_tracks(void);
void remove_tracks(void);
void remove_cd(void);
void update_cd(void);
5 在我们查看具体的实现之前,我们需要一些菜单结构(实际上为一个菜单选项的数组)。第一个字符是当菜单被选中时所返回的字符;其余的是要显示的字符。当一个CD被选中时会显示扩展菜单。
char *main_menu[] =
{
“add new CD”,
“find CD”,
“count CDs and tracks in the catalog”,
“quit”,
0,
};
char *extended_menu[] =
{
“add new CD”,
“find CD”,
“count CDs and tracks in the catalog”,
“list tracks on current CD”,
“remove current CD”,
“update track information”,
“quit”,
0,
};
这样就完成了所有的初始化工作。现在我们可以进入程序功能了,但是首先,我们需要总结一下函数之间的关系,所有16个函数,其功能可以分为:
绘制菜单
向数据库中添加CD
获取并显示CD数据
试验--主函数主函数允许我们由菜单中进行选择,直到我们选择退出。
int main()
{
int choice;
initscr();
do {
choice = getchoice(“Options:”,
current_cd[0] ? extended_menu : main_menu);
switch (choice) {
case ‘q’:
break;
case ‘a’:
add_record();
break;
case ‘c’:
count_cds();
break;
case ‘f’:
find_cd();
break;
case ‘l’:
list_tracks();
break;
case ‘r’:
remove_cd();
break;
case ‘u’:
update_cd();
break;
}
} while (choice != ‘q’);
endwin();
exit(EXIT_SUCCESS);
}
试验--菜单1 main函数所调用的getchoice函数是我们将在这一部介绍的主要函数。调用getchoice会传递给其一个greet以及choices,这会指向主菜单或是扩展菜单(依据是否选择了一个CD)。我们可以在前面的main函数中看到这些函数调用。
int getchoice(char *greet, char *choices[])
{
static int selected_row = 0;
int max_row = 0;
int start_screenrow = MESSAGE_LINE, start_screencol = 10;
char **option;
int selected;
int key = 0;
option = choices;
while (*option) {
max_row++;
option++;
}
/* protect against menu getting shorter when CD deleted */
if (selected_row >= max_row)
selected_row = 0;
clear_all_screen();
mvprintw(start_screenrow - 2, start_screencol, greet);
keypad(stdscr, TRUE);
cbreak();
noecho();
key = 0;
while (key != ‘q’ && key != KEY_ENTER && key != ‘\n’) {
if (key == KEY_UP) {
if (selected_row == 0)
selected_row = max_row - 1;
else
selected_row—;
}
if (key == KEY_DOWN) {
if (selected_row == (max_row - 1))
selected_row = 0;
else
selected_row++;
}
selected = *choices[selected_row];
draw_menu(choices, selected_row, start_screenrow,
start_screencol);
key = getch();
}
keypad(stdscr, FALSE);
nocbreak();
echo();
if (key == ‘q’)
selected = ‘q’;
return (selected);
}
2 在这里我们可以注意到在getchoice函数中调用了两个局部函数:clear_all_screen与draw_menu。我们首先来看一下draw_menu:
void draw_menu(char *options[], int current_highlight,
int start_row, int start_col)
{
int current_row = 0;
char **option_ptr;
char *txt_ptr;
option_ptr = options;
while (*option_ptr) {
if (current_row == current_highlight) attron(A_STANDOUT);
txt_ptr = options[current_row];
txt_ptr++;
mvprintw(start_row + current_row, start_col, “%s”, txt_ptr);
if (current_row == current_highlight) attroff(A_STANDOUT);
current_row++;
option_ptr++;
}
mvprintw(start_row + current_row + 3, start_col,
“Move highlight then press Return “);
refresh();
}
3 clear_all_screen函数用于清除屏幕并重新输出标题。如果选择了一个CD,则会显示其信息。
void clear_all_screen()
{
clear();
mvprintw(2, 20, “%s”, “CD Database Application”);
if (current_cd[0]) {
mvprintw(ERROR_LINE, 0, “Current CD: %s: %s\n”,
current_cat, current_cd);
}
refresh();
}
下面我们来看一下添加和更新CD数据库的函数。由main函数中调用的函数有add_record,update_cd以及remove_cd。这些函数都会调用一些我们在下面部分定义的函数。
试验--数据库文件操作1 首先,我们如何向数据库中添加一个新的CD记录呢?
void add_record()
{
char catalog_number[MAX_STRING];
char cd_title[MAX_STRING];
char cd_type[MAX_STRING];
char cd_artist[MAX_STRING];
char cd_entry[MAX_STRING];
int screenrow = MESSAGE_LINE;
int screencol = 10;
clear_all_screen();
mvprintw(screenrow, screencol, “Enter new CD details”);
screenrow += 2;
mvprintw(screenrow, screencol, “Catalog Number: “);
get_string(catalog_number);
screenrow++;
mvprintw(screenrow, screencol, “ CD Title: “);
get_string(cd_title);
screenrow++;
mvprintw(screenrow, screencol, “ CD Type: “);
get_string(cd_type);
screenrow++;
mvprintw(screenrow, screencol, “ Artist: “);
get_string(cd_artist);
screenrow++;
mvprintw(PROMPT_LINE-2, 5, “About to add this new entry:”);
sprintf(cd_entry, “%s,%s,%s,%s”,
catalog_number, cd_title, cd_type, cd_artist);
mvprintw(PROMPT_LINE, 5, “%s”, cd_entry);
refresh();
move(PROMPT_LINE, 0);
if (get_confirm()) {
insert_title(cd_entry);
strcpy(current_cd, cd_title);
strcpy(current_cat, catalog_number);
}
}
3 get_confirm函数提示并读取用户的确认信息。他会读取用户的输入字符串并且检测第一个字符是否为Y或是y。如果检测到的为其他字符,则不会给出确认。
int get_confirm()
{
int confirmed = 0;
char first_char;
mvprintw(Q_LINE, 5, “Are you sure? “);
clrtoeol();
refresh();
cbreak();
first_char = getch();
if (first_char == ‘Y’ || first_char == ‘y’) {
confirmed = 1;
}
nocbreak();
if (!confirmed) {
mvprintw(Q_LINE, 1, “ Cancelled”);
clrtoeol();
refresh();
sleep(1);
}
return confirmed;
}
4 最后,我们来看一下insert_title函数。这个函数会通过在标题文件的尾部添加一个标题字符串来向CD数据库中添加一个标题。
void insert_title(char *cdtitle)
{
FILE *fp = fopen(title_file, “a”);
if (!fp) {
mvprintw(ERROR_LINE, 0, “cannot open CD titles database”);
} else {
fprintf(fp, “%s\n”, cdtitle);
fclose(fp);
}
}
5 我们继续来讨论由main所调用的其他的文件操作函数。我们由update_cd函数开始。这个函数使用一个滚动子窗体,并且需要一些所定义的全局内容,因为在后面的list_tracks函数中会需要这些内容。他们是:
#define BOXED_LINES 11
#define BOXED_ROWS 60
#define BOX_LINE_POS 8
#define BOX_ROW_POS 2
update_cd函数允许用户重新输入当前CD的这些音轨信息。在删除以前的音轨记录以后,他会提示输入新的信息。
void update_cd()
{
FILE *tracks_fp;
char track_name[MAX_STRING];
int len;
int track = 1;
int screen_line = 1;
WINDOW *box_window_ptr;
WINDOW *sub_window_ptr;
clear_all_screen();
mvprintw(PROMPT_LINE, 0, “Re-entering tracks for CD. “);
if (!get_confirm())
return;
move(PROMPT_LINE, 0);
clrtoeol();
remove_tracks();
mvprintw(MESSAGE_LINE, 0, “Enter a blank line to finish”);
tracks_fp = fopen(tracks_file, “a”);
我们会在稍后继续列表函数的讨论;在这里我们会做一个简短的小结来强调我们是如何通过滚动窗体输入信息的。技巧就是设置一个子窗体,在边缘绘制一个盒子,然后在这个子窗体中添加一个新的滚动子窗体。
box_window_ptr = subwin(stdscr, BOXED_LINES + 2, BOXED_ROWS + 2,
BOX_LINE_POS - 1, BOX_ROW_POS - 1);
if (!box_window_ptr)
return;
box(box_window_ptr, ACS_VLINE, ACS_HLINE);
sub_window_ptr = subwin(stdscr, BOXED_LINES, BOXED_ROWS,
BOX_LINE_POS, BOX_ROW_POS);
if (!sub_window_ptr)
return;
scrollok(sub_window_ptr, TRUE);
werase(sub_window_ptr);
touchwin(stdscr);
do {
mvwprintw(sub_window_ptr, screen_line++, BOX_ROW_POS + 2,
“Track %d: “, track);
clrtoeol();
refresh();
wgetnstr(sub_window_ptr, track_name, MAX_STRING);
len = strlen(track_name);
if (len > 0 && track_name[len - 1] == ‘\n’)
track_name[len - 1] = ‘\0’;
if (*track_name)
fprintf(tracks_fp, “%s,%d,%s\n”, current_cat, track, track_name);
track++;
if (screen_line > BOXED_LINES - 1) {
/* time to start scrolling */
scroll(sub_window_ptr);
screen_line—;
}
} while (*track_name);
delwin(sub_window_ptr);
fclose(tracks_fp);
}
6 main函数所调用的最后一个函数为remove_cd函数。
void remove_cd()
{
FILE *titles_fp, *temp_fp;
char entry[MAX_ENTRY];
int cat_length;
if (current_cd[0] == ‘\0’)
return;
clear_all_screen();
mvprintw(PROMPT_LINE, 0, “About to remove CD %s: %s. “,
current_cat, current_cd);
if (!get_confirm())
return;
cat_length = strlen(current_cat);
/* Copy the titles file to a temporary, ignoring this CD */
titles_fp = fopen(title_file, “r”);
temp_fp = fopen(temp_file, “w”);
while (fgets(entry, MAX_ENTRY, titles_fp)) {
/* Compare catalog number and copy entry if no match */
if (strncmp(current_cat, entry, cat_length) != 0)
fputs(entry, temp_fp);
}
fclose(titles_fp);
fclose(temp_fp);
/* Delete the titles file, and rename the temporary file */
unlink(title_file);
rename(temp_file, title_file);
/* Now do the same for the tracks file */
remove_tracks();
/* Reset current CD to ‘None’ */
current_cd[0] = ‘\0’;
}
7 我们现在所需要只是列出remove_tracks函数,这个函数会由当前的CD删除音轨信息。他会由update_cd与remove_cd函数所调用。
void remove_tracks()
{
FILE *tracks_fp, *temp_fp;
char entry[MAX_ENTRY];
int cat_length;
if (current_cd[0] == ‘\0’)
return;
cat_length = strlen(current_cat);
tracks_fp = fopen(tracks_file, “r”);
if (tracks_fp == (FILE *)NULL) return;
temp_fp = fopen(temp_file, “w”);
while (fgets(entry, MAX_ENTRY, tracks_fp)) {
/* Compare catalog number and copy entry if no match */
if (strncmp(current_cat, entry, cat_length) != 0)
fputs(entry, temp_fp);
}
fclose(tracks_fp);
fclose(temp_fp);
/* Delete the tracks file, and rename the temporary file */
unlink(tracks_file);
rename(temp_file, tracks_file);
}
试验--查询CD数据库1 下面这个函数会搜索数据库,计数标题与音轨。
void count_cds()
{
FILE *titles_fp, *tracks_fp;
char entry[MAX_ENTRY];
int titles = 0;
int tracks = 0;
titles_fp = fopen(title_file, “r”);
if (titles_fp) {
while (fgets(entry, MAX_ENTRY, titles_fp))
titles++;
fclose(titles_fp);
}
tracks_fp = fopen(tracks_file, “r”);
if (tracks_fp) {
while (fgets(entry, MAX_ENTRY, tracks_fp))
tracks++;
fclose(tracks_fp);
}
mvprintw(ERROR_LINE, 0,
“Database contains %d titles, with a total of %d tracks.”,
titles, tracks);
get_return();
}
2 我们也许已经不记得我们最喜欢的CD了,不用担心!通过输入一些信息,我们可以通过find_cd来查进行查找。他会提示输入一个子串并且在数据库中进行匹配,并且将全局变量current_cd设置为所查找到的CD标题。
void find_cd()
{
char match[MAX_STRING], entry[MAX_ENTRY];
FILE *titles_fp;
int count = 0;
char *found, *title, *catalog;
mvprintw(Q_LINE, 0, “Enter a string to search for in CD titles: “);
get_string(match);
titles_fp = fopen(title_file, “r”);
if (titles_fp) {
while (fgets(entry, MAX_ENTRY, titles_fp)) {
/* Skip past catalog number */
catalog = entry;
if (found == strstr(catalog, “,”)) {
*found = ‘\0’;
title = found + 1;
/* Zap the next comma in the entry to reduce it to
title only */
if (found == strstr(title, “,”)) {
*found = ‘\0’;
/* Now see if the match substring is present */
if (found == strstr(title, match)) {
count++;
strcpy(current_cd, title);
strcpy(current_cat, catalog);
}
}
}
}
fclose(titles_fp);
}
if (count != 1) {
if (count == 0) {
mvprintw(ERROR_LINE, 0, “Sorry, no matching CD found. “);
}
if (count > 1) {
mvprintw(ERROR_LINE, 0,
“Sorry, match is ambiguous: %d CDs found. “, count);
}
current_cd[0] = ‘\0’;
get_return();
}
}
尽管catalog所向的数组要比current_cat大得多,并且可能会覆写内存,但是fgets中的检测避免了这种可能。
3 最后我们需要在屏幕上列出所选择的CD音轨信息。我们会在最后一部分中利用update_cd中所用的#define内容。
void list_tracks()
{
FILE *tracks_fp;
char entry[MAX_ENTRY];
int cat_length;
int lines_op = 0;
WINDOW *track_pad_ptr;
int tracks = 0;
int key;
int first_line = 0;
if (current_cd[0] == ‘\0’) {
mvprintw(ERROR_LINE, 0, “You must select a CD first. “);
get_return();
return;
}
clear_all_screen();
cat_length = strlen(current_cat);
/* First count the number of tracks for the current CD */
tracks_fp = fopen(tracks_file, “r”);
if (!tracks_fp)
return;
while (fgets(entry, MAX_ENTRY, tracks_fp)) {
if (strncmp(current_cat, entry, cat_length) == 0)
tracks++;
}
fclose(tracks_fp);
/* Make a new pad, ensure that even if there is only a single
track the PAD is large enough so the later prefresh() is always
valid. */
track_pad_ptr = newpad(tracks + 1 + BOXED_LINES, BOXED_ROWS + 1);
if (!track_pad_ptr)
return;
tracks_fp = fopen(tracks_file, “r”);
if (!tracks_fp)
return;
mvprintw(4, 0, “CD Track Listing\n”);
/* write the track information into the pad */
while (fgets(entry, MAX_ENTRY, tracks_fp)) {
/* Compare catalog number and output rest of entry */
if (strncmp(current_cat, entry, cat_length) == 0) {
mvwprintw(track_pad_ptr, lines_op++, 0, “%s”,
entry + cat_length + 1);
}
}
fclose(tracks_fp);
if (lines_op > BOXED_LINES) {
mvprintw(MESSAGE_LINE, 0,
“Cursor keys to scroll, RETURN or q to exit”);
} else {
mvprintw(MESSAGE_LINE, 0, “RETURN or q to exit”);
}
wrefresh(stdscr);
keypad(stdscr, TRUE);
cbreak();
noecho();
key = 0;
while (key != ‘q’ && key != KEY_ENTER && key != ‘\n’) {
if (key == KEY_UP) {
if (first_line > 0)
first_line—;
}
if (key == KEY_DOWN) {
if (first_line + BOXED_LINES + 1 < tracks)
first_line++;
}
/* now draw the appropriate part of the pad on the screen */
prefresh(track_pad_ptr, first_line, 0,
BOX_LINE_POS, BOX_ROW_POS,
BOX_LINE_POS + BOXED_LINES, BOX_ROW_POS + BOXED_ROWS);
key = getch();
}
delwin(track_pad_ptr);
keypad(stdscr, FALSE);
nocbreak();
echo();
}
4 最后两个函数调用get_return,这会提示并且读取一个回车,而忽略其他字符。
void get_return()
{
int ch;
mvprintw(23, 0, “%s”, “ Press return “);
refresh();
while ((ch = getchar()) != ‘\n’ && ch != EOF);
}
小结在这一章,我们探讨了curses库。curses库为基于文本的程序提供了一个很好的方法来控制屏幕与读取键盘。尽管curses库并没有提供像通用终端接口(GTI)和直接的termios访问那样多的控制,但是他很容易使用。如果我们正在编写一个全屏幕,基于文本的程序,我们应考虑使用curses库来为我们管理屏幕与键盘。
分享到:
相关推荐
### 使用curses管理基于文本的屏幕 #### 一、curses简介 curses是一个用于创建基于文本的用户界面的库,它在Linux和类UNIX环境中非常流行。curses的重要性在于它提供了一个介于简单的基于行的程序和复杂的图形界面...
即使是编写基于字符的全屏幕程序,使用curses函数库的方案也更简明,而程序本身也更独立于具体的终端。在编写这类程序时,使用curses函数库更比直接使用escape转义序列容易得多。curses函数库还可以对键盘进行管理,...
- 它提供了高级的文本输出和输入功能,比如分割屏幕、创建窗口、接受用户输入等,适合创建基于命令行的图形化界面。 - 在Windows系统上,curses库通常通过pdcurses或win32curses这样的移植版本来实现。 2. **whl...
**Python Curses**库是Python对原始C语言的`curses`库的封装,它提供了一套API,使得在基于文本的终端上创建图形用户界面成为可能。`curses`库可以处理键盘输入、屏幕输出和窗口管理,常用于控制台应用,如命令行...
curses库是用于处理文本模式用户界面的工具,特别是在没有图形窗口系统的情况下,它能够方便地在终端上进行屏幕管理和输入处理。通过使用curses,开发者可以更容易地控制屏幕输出和键盘输入,使得在命令行环境下也能...
在Python编程领域,curses库是一个至关重要的工具,尤其在开发基于终端的用户界面时。curses库源于C语言,但在Python中被广泛使用,因为它允许开发者创建全屏交互式的应用程序,使得在命令行界面下的用户体验得以...
通过对这些文件的分析和学习,我们可以更深入地理解如何使用`curses`库来开发一个基于文本的多人游戏。这个项目可能包括了游戏规则、角色移动、碰撞检测、用户输入处理以及网络通信等方面的具体实现,这些都是游戏...
在实际应用中,Python的curses库提供了许多函数和类,如`initscr()`用于初始化屏幕,`getch()`用于接收用户输入,`printw()`用于在屏幕上打印文本,以及`color_pair()`和`attron()`等用于设置颜色和文本属性。...
Python的curses库是用于创建基于终端的交互式应用程序的模块,它允许程序员在文本模式下处理用户输入和显示复杂的屏幕布局。`python_curses-2.2.2-cp39-cp39-win32.whl` 是一个专门为Python 3.9版本编译的curses库的...
它能够管理屏幕上的窗口,处理键盘输入,以及更新屏幕内容,使得开发基于终端的应用程序变得更加简单。`curses`库的使用主要分为以下几个核心概念: 1. **窗口管理**:curses将屏幕划分为多个窗口,每个窗口可以...
Python的curses库是一个在控制台上提供彩色和光标移动功能的模块,它使得开发者能够创建基于文本的用户界面。`python_curses-2.2.2-cp39-cp39-win_amd64.whl` 文件是这个库的一个预编译版本,专为Python 3.9构建,...
调试PyCurses程序通常涉及输出日志、使用调试器或专门的curses调试工具,以便跟踪屏幕更新和键盘事件。 总之,PyCurses为Python开发者提供了一个强大且灵活的工具,用于创建功能丰富的控制台应用程序,无论是在历史...
curses.h 头文件定义了 Curses 库的 API,提供了各种函数和宏,用于创建和管理基于文本的用户界面。curses 库是 Curses 库的核心部分,提供了各种函数,用于创建和管理基于文本的用户界面。form 库是 Curses 库的一...
Curses 提供了一系列 API 来处理文本屏幕的操作: - `initscr`: 初始化 curses 模式 - `printw`: 在屏幕指定位置打印字符串 - `getch`: 获取用户按键 - `endwin`: 结束 curses 模式 ##### 6.3 属性 除了 API 外,...
1. **curses**:curses库是此项目的核心,它提供了一系列函数,用于管理屏幕上的文本布局、颜色、键盘输入处理等功能。在curses模式下,程序可以动态地更新屏幕,创建窗口、面板,并响应用户的按键事件,使得在字符...
Curses 是一个在文本模式下运行的库,用于创建基于终端的应用程序。它提供了一组高级函数,使得程序员可以方便地处理终端的显示、输入和颜色等特性,而无需关心底层的终端控制代码。Curses-1.28.tgz 是这个库的一个...
【curses库】是UNIX环境下用于处理终端交互的高级库,尤其适合开发基于文本界面的程序。它使得程序员能够方便地控制光标移动、屏幕显示和键盘输入,从而实现滚屏、彩色打印等功能,适用于各种不同类型的终端。curses...
- **文本编辑器**:如vim、nano等命令行编辑器,这些编辑器的界面都是基于curses构建的。 - **游戏开发**:如经典的《roguelike》游戏,这类游戏通常需要在有限的空间内展示大量信息。 - **脚本自动化**:编写用于...
Linux下的curses编程是一种在基于文本的终端上创建交互式用户界面的技术,它允许程序员创建动态更新的屏幕,类似于简单的图形界面。在这个“魔岛寻宝”游戏中,curses库被用来控制屏幕上的字符输出、读取用户输入,...
5. **屏幕更新**:Curses 使用缓冲区来存储屏幕内容,当内容改变时,使用 `refresh` 函数将缓冲区的内容刷新到屏幕上,这优化了性能,避免了不必要的屏幕重绘。 6. **面板和多层窗口**:除了基本的窗口操作,Curses...