SetConsoleCursorPositionで正しい場所にカーソルが移動しない

[戻る]
桜川 2013/04/12(金) 16:59:22
#include<stdio.h>
#include<windows.h>

HANDLE g_console;
COORD g_begin_pos;

struct T_table{
    const char* str1;
    const char* str2;
}const table[]={
    {"AAAAAAAAAA","BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
    },
    {"CCCCCCCCCC","DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
    },
    {"EEEEEEEEEE","FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
    },
    {"GGGGGGGGGG","HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"
    },
    {NULL,NULL},
};

void show(const char*str1,const char*str2){
    SetConsoleCursorPosition(g_console,g_begin_pos);
    printf("%s\n",str1);
    printf("%s",str2);
}

int main(){
    CONSOLE_SCREEN_BUFFER_INFO csbi;

    g_console=GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleScreenBufferInfo(g_console,&csbi);
    g_begin_pos.X=csbi.dwCursorPosition.X;
    g_begin_pos.Y=csbi.dwCursorPosition.Y;

    int i=0;

    while(table[i].str1){
        show(table[i].str1,table[i].str2);
        Sleep(1000);
        i++;
    }

    return 0;
}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
上記をコマンドプロンプトより実行しますと、
AAA...
BBB...
が表示され、
CCC...
DDD...
がA..B..を上書きして表示され...
と言うように表示されます。

しかし、コマンドプロンプトにてCtrl+Cを押し続け、最後までスクロールした状態で実行
すると、
上書きされず、
A...
B...
C...
D...
...
のように表示されてしまいます。

Q.最下部でも上書きして表示するにはどのようにすればよいのでしょうか?

仲澤@失業者 2013/04/15(月) 10:34:23
レス付きませんねぇ(vv;)。

この分野はあまり詳しくないのですが、コンソールの最下行に
出力すると自動的にスクロールされてしてしまうのが、
原因ではないでしょうか。

基本的には、

 1.出力するとカーソル位置が変わる。改行すると
  当然、カーソルのY位置が更新される。
 2.最下行では、変更されたカーソルY位置に対して、
  それが画面内に収まるよう、スクロールされる(たぶん)。
  改行コードが出力されなくてもそうなる(のであろう)。

ので、

 3.常に同じ位置に表示し続けるためには、行表示の前に
  出力する位置(カーソル位置 = g_begin_posの内容)を
  毎回設定する必要がある。
 4.つまりそれぞれの出力の直前で g_begin_posの内容を設定して
  SetConsoleCursorPosition()を実行し。その後printf()する必要がある。

のではないかと、想像できます。

桜川 2013/04/16(火) 21:35:27
回答有難うございます。
問題はそのg_begin_posの設定値をどこから取得するか...と言うことですね。
海外のサイトも見て回りましたが、有益な情報を得られませんでした。

仲澤@失業者 2013/04/17(水) 09:52:05
ん。意味がわかりかねます。

g_begin_pos.X = 0;
g_begin_pos.Y = 10;
SetConsoleCursorPosition( g_console, g_begin_pos);

とかはだめなのですか(質問)。

桜川 2013/04/17(水) 12:59:16
お世話になります。

おっしゃる通り、y座標が10など、文字列を出力してもスクリーンバッファの範囲を超え
ない値であれば確かに大丈夫です。
しかし、現在実装しようと考えていますのは、
(例からは分かり難いですが)プログレスバーと、その下に処理中の内容を表示することです。
----------
準備中...
処理開始
20% =========
現在:C:\User\...(省略)...\hogehogeをコピー中...
---------
(最初の例のAAA...やCCC...がプログレスバーに当たります。)

つまり、プログレスバーのある行に座標を固定するにはどの様にすれば良いか、
が問題でして、その解決法が分からないでいます。

分かり難くて申し訳ないです。

tetrapod 2013/04/17(水) 16:48:16
2行にするからとたんに難しくなる。
1行だったら簡単。
printf("\r...."); で同一行に何度も表示することができる (UNIX と共通にできる)

AA グラフをあきらめて
 82% C:\User\...File.c をコピー中
とかだとだめかいな。

仲澤@失業者 2013/04/17(水) 16:59:23
まず、現在のshow()関数は、まんまでは使えないことは明らかですね。
理由は、1回のロケーション設定で、2行を出力しているので、
出力する位置を制御しづらいわけですね。
従って、別の出力関数を用意しなければなりません。
この認識はよろしいでしょうか。

従って、次に試してみるのは、既に以前の発言でふれている通り、
1行出力に対して事前にロケーション設定する方法ですね。
多分誰が設計しても次のようなやつになります。

void    OutputString_Width_Y(
    HANDLE        ex_H_Con,  // 出力コンソールのハンドル
    int           y,         // 指定行
    const char *  str)       // 対象文字列
{
    COORD location = { 0, y};
    SetConsoleCursorPosition( ex_H_Con, location);
    printf( "%s", str);
}

この関数に、プログレス表示、ファイル名項目などを渡して、出力します。
この場合、別のファイル項目に移行した場合の仕様によりますが、
適選スクロールする必要があるかもしれません。
この処理は簡単なので省略します。

これでも最下行で無用な改行がされてしまう場合は

1.最下行を認識した場合には、スクロールされることを
 予測して、出力y位置を加減する。
2.printf()の使用をあきらめて、putc()などを使う。

の検討が必要になるかもしれません。

紅' 2013/04/18(木) 12:22:14
こんにちわ。

あくまでもプログラム実行時点でのカーソル位置に出力したいのなら、
show() の前後でカーソルの位置を取得してスクロールの有無を判断すれば
いいのではないですか。※以下は検証してません。

コマンドプロンプトの縦横幅は標準だと 300 行と 80 桁だと思います。
例の場合だと 1 回の T_table の出力時に以下の改行が入ることになります。
・str1 は 80 文字を超えていないので \n の 1 回分
・str2 は 88 文字なので(2行に分かれて出力されるため) 1 回分
計 2 回の改行があることになります。

つまり、
最下行以外、例えば1行目に出力するとした場合、
出力後は 3 行目にカーソルがあると思われ、
 (出力後の位置)3 - (出力前の位置)1 = (自分で改行を数えた)2
が成立するのでスクロールは発生していないと判断できます。

最下行の場合は同様に
 300 - 300 != 2
になるのでスクロールが発生したと考えられます。
この場合は、スクロールが発生したので str1 は出力前後の
行番号と予定改行の差である 2 行上に表示されているはずです。

なので、2 回目に show() で出力位置を指定するときに
1 回目の位置の 2 行上、つまり 300 - 2 の位置に出力してあげれば
1 回目と同じ位置に出力することができるはずですね。
‥‥と言うようなことを自前で計算してあげる必要があるのではないかと。

ちなみにコマンドプロンプトの実際の幅高などは
CONSOLE_SCREEN_BUFFER_INFO に取れるはずなのでそちらを参照します。


‥‥めんどうなので、tetrapod さんや仲澤@失業者さんのおっしゃる方法を
採用した方が幸せだと思うのですが。

出力位置を絶対座標で指定すると出力済みのほかのテキストと被るかも、、、と
心配なら、コマンドプロンプトでいうところの cls コマンドを発行して
表示を初期化してから常に画面の最上行に出力するという手もありますし。

桜川 2013/04/19(金) 01:33:58
[[解決]]
返信ありがとうございます。
アドバイスを参考に試行錯誤したいと思います。
お世話になりました。

ツqケツゃケり^ス 2013/04/19(金) 02:44:07
前提覆して悪いんですがCtrl+CとかCtrl+Breakを
回避するAPIがあったはずです。
明日会社の本で確認してみます。

#あとコマンドプロンプトにはRAWモードとかあったような。
#もうやりたい放題だった気がします。

ツqケツゃケり^ス 2013/04/19(金) 10:34:50
なんか名前が化けているのですがこのままで
SetConsoleCtrlHandler APIでCtrl + CとかCtrl + Breakを
検出できるので無効に出来ると思います。

あと
GetConsoleScreenBufferInfoとかで位置情報とかが取得できるようです。

RAWモードについては
SetConsoleModeを参照してみて下さい。

古いスレッドには追加発言できません。