在 C 程序中移动光标

Posted

技术标签:

【中文标题】在 C 程序中移动光标【英文标题】:Move the cursor in a C program 【发布时间】:2016-01-06 15:42:43 【问题描述】:

我想在 C 程序中前后移动光标。我正在循环读取整行,但我希望如果按下光标键,屏幕上的光标会改变位置,而不会阻塞循环。我试过getwch(),但它会阻止调用者,直到它被按下。我正在寻找的是类似于 bash 提示符的行为。我正在阅读与此类似的代码:

while (TRUE) 
   printf("%s", PROMPT);
   fgets(input, 1024, stdin);
   do_something(input);

我正在尝试让上面的函数像 readline(PROMPT) 一样在 readline.h 库中工作

【问题讨论】:

如果没有 ncurses 库之类的东西就无法做到这一点 听起来你需要readline,这也是bash使用的。 @Ed:我想只打印一个回车,然后输入行的第一部分在很大程度上取决于终端的类型。 @user3121023:这些代码并没有真正标准化。他应该为此使用图书馆。 在实践中,在 Posix 上,这些已经足够标准了;他们将在任何重要的地方工作。虽然,它在 Windows 上完全不同(而且 IMO 更好......)。不过,棘手的部分是按时获得输入。这意味着将 tty 更改为原始模式 (tcsetattr) 并可能使用 select 或循环中的某些内容检查输入。但是我在这里建议 readline 库类似于 bash - 它使用相同的库,并且在 C 代码中也很容易使用。请注意,它是 GPL。 【参考方案1】:

ANSI 转义序列允许您随意在屏幕上移动光标。这对于在 shell 下运行的程序生成的全屏用户界面很有用,但也可以在提示中使用。这不适用于不接受保存和恢复光标位置代码的终端仿真器。更多关于移动光标的转义序列,请查看cursor movement。

如果您需要更便携的解决方案,请使用curses,它是一个终端控制库,用于管理应用程序在类 Unix 系统的字符单元终端上的显示。执行系统上的 curses 库根据终端类型发送正确的控制字符。在ncurses 中使用int wmove(WINDOW* win, int y, int x),将光标移动到新位置。有关如何在 ncurses 下编程的更多信息,请参阅 NCurses-programing-howto。

【讨论】:

【参考方案2】:

左右移动光标。在换行符或字符过多时停止输入

#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/select.h>
#include <sys/ioctl.h>

#define ESC          27
#define INSERT       50
#define DELETE       51
#define PGUP         53
#define PGDN         54
#define ARROWRIGHT   67
#define ARROWLEFT    68
#define END          70
#define HOME         72
#define OTHER        79
#define BRACKETLEFT  91
#define TILDE       126
#define BACKSPACE   127

#define SIZE         30

static const int STDIN = 0;

int kbhit(void)

    int bytesWaiting;

    ioctl(STDIN, FIONREAD, &bytesWaiting);
    return bytesWaiting;


int main ( ) 
    char input[SIZE] = '\0';
    int insert = 0;
    int each = 0;
    int end = 0;
    int to = 0;
    int ch = 0;
    int row = 0;
    int col = 0;
    struct termios oldattr, newattr;

    //set terminal
    tcgetattr( STDIN, &oldattr );
    newattr = oldattr;
    newattr.c_lflag &= ~( ICANON | ECHO );
    tcsetattr( STDIN, TCSANOW, &newattr );
    setbuf(stdin, NULL);

    printf ( "\033[2J");//clear screen
    printf ( "\033[25;1H");//move cursor to row 25 col 1
    printf ( "OVW");
    printf ( "\033[9;1H");//move cursor to row 9 col 1
    printf ( "enter your text ");//prompt
    //printf ( "%s", input);
    printf ( "\033[9;17H");//move cursor to row 9 col 17
    col = 17;
    row = 9;
    while ( ( ch = getchar ()) != '\n') 
        if ( isprint( ch)) 
            if ( insert && each < end && end < SIZE-3) 
                //expand
                end++;
                for ( to = end; to >= each; to--) 
                    input[to + 1] = input[to];
                
                printf ( "\033[9;17H");//move cursor to row 9 col 12
                printf ( "\033[K");//erase to end of line
                printf ( "%s", input);
            
            printf ( "\033[%d;%dH", row, col);
            printf ( "%c", ch);
            input[each] = ch;
            each++;
            if ( each > end) 
                end = each;
            
            col++;
            if ( each == end) 
                input[each] = '\0';
            
            if ( each >= SIZE-1) 
                break;
            
            continue;
        

        if ( ch == BACKSPACE) 
            if ( each) 
                each--;
                col--;
                //contract
                for ( to = each; to <= end; to++) 
                    input[to] = input[to + 1];
                
                end--;
                printf ( "\033[9;17H");//move cursor to row 1 col 7
                printf ( "\033[K");//erase to end of line
                printf ( "%s", input);
                printf ( "\033[%d;%dH", row, col);
            
        
        if ( ch == ESC) 
            if ( !kbhit ( )) 
                continue;
            
            ch = getchar ( );
            if ( ch == OTHER) 
                ch = getchar ( );
                if ( ch == HOME) 
                    col -= each;
                    each = 0;
                    printf ( "\033[%d;%dH", row, col);
                    ch = getchar ( );
                
                if ( ch == END) 
                    col += end - each;
                    each = end;
                    printf ( "\033[%d;%dH", row, col);
                    ch = getchar ( );
                
            
            if ( ch == BRACKETLEFT) 
                ch = getchar ( );
                if ( ch == INSERT) 
                    ch = getchar ( );
                    if ( ch == TILDE) 
                        insert = !insert;
                        printf ( "\033[25;1H");//move cursor to row 25 col 1
                        if ( insert) 
                            printf ( "INS");
                        
                        else 
                            printf ( "OVW");
                        
                        printf ( "\033[%d;%dH", row, col);
                    
                
                if ( ch == DELETE) 
                    ch = getchar ( );
                    if ( ch == TILDE) 
                        //contract
                        for ( to = each; to <= end; to++) 
                            input[to] = input[to + 1];
                        
                        end--;
                        printf ( "\033[9;17H");//move cursor to row 10 col 1
                        printf ( "\033[K");//erase to end of line
                        printf ( "%s", input);
                        printf ( "\033[%d;%dH", row, col);
                    
                
                if ( ch == ARROWRIGHT) 
                    if ( each < end) 
                        printf ( "\033[C");//cursor right
                        each++;
                        col++;
                    
                
                if ( ch == ARROWLEFT) 
                    if ( each) 
                        printf ( "\033[D");//cursor left
                        each--;
                        col--;
                    
                
            
            else 
                ungetc ( ch, stdin);
            
        
    
    printf ( "\n\ninput was [%s]\n", input);
    printf ( "\n\nbye\n");

    //restore terminal
    tcsetattr( STDIN, TCSANOW, &oldattr );

    return 0;

【讨论】:

【参考方案3】:

使用termios 和控制台代码(VT100 兼容 - 不可移植):

#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#define cursorforward(x) printf("\033[%dC", (x))
#define cursorbackward(x) printf("\033[%dD", (x))

#define KEY_ESCAPE  0x001b
#define KEY_ENTER   0x000a
#define KEY_UP      0x0105
#define KEY_DOWN    0x0106
#define KEY_LEFT    0x0107
#define KEY_RIGHT   0x0108

static struct termios term, oterm;

static int getch(void);
static int kbhit(void);
static int kbesc(void);
static int kbget(void);

static int getch(void)

    int c = 0;

    tcgetattr(0, &oterm);
    memcpy(&term, &oterm, sizeof(term));
    term.c_lflag &= ~(ICANON | ECHO);
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;
    tcsetattr(0, TCSANOW, &term);
    c = getchar();
    tcsetattr(0, TCSANOW, &oterm);
    return c;


static int kbhit(void)

    int c = 0;

    tcgetattr(0, &oterm);
    memcpy(&term, &oterm, sizeof(term));
    term.c_lflag &= ~(ICANON | ECHO);
    term.c_cc[VMIN] = 0;
    term.c_cc[VTIME] = 1;
    tcsetattr(0, TCSANOW, &term);
    c = getchar();
    tcsetattr(0, TCSANOW, &oterm);
    if (c != -1) ungetc(c, stdin);
    return ((c != -1) ? 1 : 0);


static int kbesc(void)

    int c;

    if (!kbhit()) return KEY_ESCAPE;
    c = getch();
    if (c == '[') 
        switch (getch()) 
            case 'A':
                c = KEY_UP;
                break;
            case 'B':
                c = KEY_DOWN;
                break;
            case 'C':
                c = KEY_LEFT;
                break;
            case 'D':
                c = KEY_RIGHT;
                break;
            default:
                c = 0;
                break;
        
     else 
        c = 0;
    
    if (c == 0) while (kbhit()) getch();
    return c;


static int kbget(void)

    int c;

    c = getch();
    return (c == KEY_ESCAPE) ? kbesc() : c;


int main(void)

    int c;

    while (1) 
        c = kbget();
        if (c == KEY_ENTER || c == KEY_ESCAPE || c == KEY_UP || c == KEY_DOWN) 
            break;
         else
        if (c == KEY_RIGHT) 
            cursorbackward(1);
         else
        if (c == KEY_LEFT) 
            cursorforward(1);
         else 
            putchar(c);
        
    
    printf("\n");
    return 0;

【讨论】:

【参考方案4】:

使用 ANSI 转义序列的简单示例:

#include <stdio.h>


int main()

    char *string = "this is a string";
    char input[1024] =  0 ;
    printf("%s", string);
    /* move the cursor back 5 spaces */
    printf("\033[D");
    printf("\033[D");
    printf("\033[D");
    printf("\033[D");
    printf("\033[D");
    fgets(input, 1024, stdin);
    return 0;

为了非常有用,终端需要使用 termios.h 和/或 curses.h/ncurses.h 进入规范模式。这样,退格键代码可以被捕获并立即响应,并将缓冲区相应地绘制到屏幕上。以下是如何使用tcsetattr() 将终端设置为规范模式的示例:

struct termios info;
tcgetattr(0, &info);
info.c_lflag &= ~ICANON;
info.c_cc[VMIN] = 1;
info.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &info);

另一个选项可能是使用readline()editline() 库。要使用 readline 库,请在编译器中指定 -lreadline。下面的代码sn -p可以用

编译
cc -lreadline some.c -o some


#include <stdio.h>

#include <readline/readline.h>
#include <readline/history.h>

int main()

    char *inpt;
    int i = 0;

    while ( i < 10 )
    
        inpt = readline("Enter text: ");
        add_history(inpt);
        printf("%s", inpt);
        printf("\n");
        ++i;
    
    return 0;

【讨论】:

我正在寻找它的行为类似于 readline,但没有导入 readline.h。我现在正在检查源代码,但欢迎提供一些关于如何实现函数 readline() 的帮助。 实际上,终端通常以规范模式启动,您希望将其设置为非规范模式以使其返回退格而不是解释退格或等待换行符(这就是上面的tcsetattr 代码确实) @user4832408-规范模式是什么意思?

以上是关于在 C 程序中移动光标的主要内容,如果未能解决你的问题,请参考以下文章

C语言光标的移动程序是啥 要详细一点

目标 C 中 OTP 的自动移动键盘光标

无法使用箭头键在诅咒中移动光标 (C)

如何实现C语言中用键盘控制光标移动?

如何在 DataGridView c# 中移动光标

为啥两个光标同时在嵌入式应用程序中运行和移动?