土木測量プログラムの開発 (数値入力)

2017/03/14


CASIO BASIC を使用したくない一番の理由が、数値の入力方法です。

ある数値を入力する際に、前回に入力された値を確認したいのと、前回と同じ値ならばそのまま [EXE] キーを押すことで続けて処理を行いたいのです。

また、前回に入力された値の一部を訂正したい場合など、すべての数値を入力しなおす作業を省きたいのが理由です。

具体的には、GetKey 関数を使用し、キー入力をスキャンしながら表示を行う関数を作成します。

なお、計算式の入力は出来ない仕様にします。


一文字入力関数を作る。

後で使用するのですが、デフォルトのサンプルにあるような GetKey 関数のループを関数にまとめます。

戻り値は数値ではなく char で返すようにします。 F3 と F5 は使用しない予定です。

char GetOneKey(void) {
	unsigned int key;
    char ret = 0;

    while(ret == 0) {
        GetKey(&key);

        switch(key) {
            case KEY_CHAR_0:
                ret = '0';
                break;
            case KEY_CHAR_1:
                ret = '1';
                break;
            case KEY_CHAR_2:
                ret = '2';
                break;
            case KEY_CHAR_3:
                ret = '3';
                break;
            case KEY_CHAR_4:
                ret = '4';
                break;
            case KEY_CHAR_5:
                ret = '5';
                break;
            case KEY_CHAR_6:
                ret = '6';
                break;
            case KEY_CHAR_7:
                ret = '7';
                break;
            case KEY_CHAR_8:
                ret = '8';
                break;
            case KEY_CHAR_9:
                ret = '9';
                break;
            case KEY_CHAR_DP:
                ret = '.';
                break;
            case KEY_CHAR_PMINUS:
                ret = '-';
                break;
            case KEY_CHAR_MINUS:
                ret = '-';
                break;
//          case KEY_CHAR_PLUS:
//              ret = '+';
//              break;
            case KEY_CTRL_EXE:
                ret = 'E';
                break;
            case KEY_CTRL_DEL:
                ret = 'D';
                break;
            case KEY_CTRL_AC:
                ret = 'C';
                break;
            case KEY_CTRL_F1:
                ret = 'a';
                break;
            case KEY_CTRL_F2:
                ret = 'b';
                break;
//          case KEY_CTRL_F3:
//              ret = 'c';
//              break;
            case KEY_CTRL_F4:
                ret = 'd';
                break;
//          case KEY_CTRL_F5:
//              ret = 'e';
//              break;
            case KEY_CTRL_F6:
                ret = 'f';
                break;
            defaule:
                ret = 0;
                break;
        }
    }

    return ret;
}
 

初めに前回の入力値を表示。

引数に入力したい場所の行位置(1~7)と初期値を指定し、初期値を書式文字列に従って表示します。 桁位置は固定で、入力できる値は -9999.9999 ~ 99999.9999 に限定します。

数値を sprintf文で文字列に変換します。 なお、小数点以下 4桁まで表示したいのですが四捨五入されると線形計算書と合わない場合があります。

例えば 0.12345 の数値を四捨五入して表示すると 0.1235 となりミリ単位に四捨五入すると 0.124 となります。 ここは切り捨てで 0.1234 と表示し四捨五入して 0.123 となるようにします。

四捨五入の関数もあるのですが文字列を操作して切り捨てを行います。

数値を文字列に変換した際に最後尾に _ (アンダーバー)を付けて左詰めで表示することで一般の表示との違いを分かるようにします。

アンダーバーの位置=文字数、を算出します。 文字数をカウントする関数は strlen ですが、プログラムの先頭で <string.h> をインクルードします。

後の処理に必要な文字数の計算を行った後、文字列の右側に空白を付け加え文字数を 14 文字に調整します。

#include <string.h>
double InputVal(int y, double d) {
    char txt[22];
    char pos;

    sprintf(txt, "%-.5f_", d);  // 数値を文字列に変換
    pos = strlen(tmp) - 2;
    tmp[pos] = '_';
    tmp[pos + 1] = 0;

    sprintf(txt, "%-14s", txt);  // 14文字の文字列にする

    locate(8, y);
    Print((unsigned char*)txt);

    return;
}

キー入力待ちまでの処理。

キー入力のループに入るのですが、AC キーを押したときに初期状態に戻りたいので、ラベルを付け goto文で戻れるようにしておきます。

数値入力のループは下品な方法ですが無限ループとし、goto文で脱出する方法にします。

double InputVal(int y, double d) {
    char txt[22];
    char pos;
    char c;

START:
    sprintf(txt, "%-.5f_", d);
    pos = strlen(txt) - 2;
    txt[pos] = '_';
    txt[pos + 1] = 0;

    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

// ループ
    while(1) {
        c = GetOneKey();
    }

    return;
}

戻り値を表示する。

EXE キーを押して関数が終了する際に、入力値を表示します。

初期値と表示と同じ処理ですが表示する数値は右詰めとなります。 小数点以下 5位の文字は画面外に表示されるので実質切り捨てで表示されます。

最後の表示の書式文字列でプラスの値とマイナスの値とで若干表示が違っていたので、if文で判定し表示を変えています。 符号の扱いに関する書式設定があるのかもしれませんが良くわからなかったので if を使用しました。

double InputVal(int y, double d) {
    double ret = d;  // 戻り値用
    char txt[22];
    char pos;
    char c;

START:
    sprintf(txt, "%-.5f_", ret);  // 少し修正
    pos = strlen(txt) - 2;
    txt[pos] = '_';
    txt[pos + 1] = 0;

    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    while(1) {
        c = GetOneKey();
    }

// 入力値を表示
    if (ret < 0) {
        sprintf(txt, "%14.5f", ret);
    } else {
        sprintf(txt, "%15.5f", ret);
    }
    locate(8, y);
    Print((unsigned char*)txt);

    return;
}

最初の一文字の処理

最初の一文字目が入力されると一度表示がクリアされ、入力された文字が表示されるようにします。

double InputVal(int y, double d) {
    double ret = d;
    char txt[22];
    char pos;
    char c;

START:
    sprintf(txt, "%-.5f_", ret);
    pos = strlen(txt) - 2;
    txt[pos] = '_';
    txt[pos + 1] = 0;

    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

// 初めに一文字読み込む
    c = GetOneKey();
    txt[0] = c;
    txt[1] = '_';
    txt[2] = 0;
    pos = 1;
    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    while(1) {
        c = GetOneKey();
    }

    if (ret < 0) {
        sprintf(txt, "%14.5f", ret);
    } else {
        sprintf(txt, "%15.5f", ret);
    }
    locate(8, y);
    Print((unsigned char*)txt);

    return ret;
}

最初の一文字の処理 (EXEキー)。

最初の一文字目が EXEキーの場合、初期値が入力値となります。 最初の一文字だけはループに入る前に処理します。

新規の数値を入力した時でも同じなのですが、入力が終了したら右詰めの数値を表示して関数を終了させます。

最初の文字入力で EXE キーを確認したら、ループ内の処理を行わずに終了処理を行います。 カッコ内での処理でも記述できますが、わかりにくいので終了処理まで goto文でジャンプします。

後のこともあるので、条件式は switch文を使用します。

double InputVal(int y, double d) {
    double ret = d;
    char txt[22];
    char pos;
    char c;

START:
    sprintf(txt, "%-.5f_", ret);
    pos = strlen(txt) - 2;
    txt[pos] = '_';
    txt[pos + 1] = 0;

    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    c = GetOneKey();
    switch (c) {
        case 'E':  // EXE キーの処理
            goto END;  // END までジャンプ
            break;
        default:
            txt[0] = c;
            txt[1] = '_';
            txt[2] = 0;
            pos = 1;
            break;
    }
    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    while(1) {
        c = GetOneKey();
    }

END:
    if (ret < 0) {
        sprintf(txt, "%14.5f", ret);
    } else {
        sprintf(txt, "%15.5f", ret);
    }
    locate(8, y);
    Print((unsigned char*)txt);

    return ret;
}

最初の一文字の処理 (DELキー)。

最初の一文字が DEL キーの場合、表示文字のクリアは行われず表示されている文字を一文字削る動作を行うので、表示がクリアされる場合の動作と異なります。

double InputVal(int y, double d) {
    double ret = d;
    char txt[22];
    char pos;
    char c;

START:
    sprintf(txt, "%-.5f_", ret);
    pos = strlen(txt) - 2;
    txt[pos] = '_';
    txt[pos + 1] = 0;

    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    c = GetOneKey();
    switch (c) {
        case 'E':
            goto END;
            break;
        case 'D':  // DELキーの処理
            pos--;
            txt[pos] = '_';
            txt[pos + 1] = 0;
            break;
        default:
            txt[0] = c;
            txt[1] = '_';
            txt[2] = 0;
            pos = 1;
            break;
    }
    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    while(1) {
        c = GetOneKey();
    }

END:
    if (ret < 0) {
        sprintf(txt, "%14.5f", ret);
    } else {
        sprintf(txt, "%15.5f", ret);
    }
    locate(8, y);
    Print((unsigned char*)txt);

    return ret;
}

ファンクションキーが押された場合の処理。

ファンクションキーが押された場合は、強制的に処理を中断してメニュー表示まで戻る必要があります。

メニュー表示まで戻る作業は、この関数を呼び出した関数の方で処理するので呼び出した関数側にファンクションキーが押された旨の通知をする必要がありますが、戻り値は自然数なので特定の数値でファンクションキーの状態を伝えることは出来ない為、別の変数を用意する必要があります。

一つは引数にファンクションキーの状態を伝える変数のポインタを与えることで、関数内で変数を書き換えることができます。 もう一つはグローバル変数を使用して値を返す方法ですが、関数の汎用性を損なうのであまり推奨されません。 ただ、ファンクションキーを押す場面は頻繁にあるので、単純で管理しやすいグローバル変数を使用します。 グローバル変数はメイン関数より前で宣言します。

// グローバル変数
char FuncKey;

//****************************************************************************
int AddIn_main(int isAppli, unsigned short OptionNum) {
double InputVal(int y, double d) {
    double ret = d;
    char txt[22];
    char pos;
    char c;

    FuncKey = 0;

START:
    sprintf(txt, "%-.5f_", ret);
    pos = strlen(txt) - 2;
    txt[pos] = '_';
    txt[pos + 1] = 0;

    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    c = GetOneKey();
    switch (c) {
        case 'E':
            goto END;
            break;
        case 'D':
            pos--;
            txt[pos] = '_';
            txt[pos + 1] = 0;
            break;
        case 'a':  // F1キー
        case 'b':  // F2キー
        case 'd':  // F4キー
        case 'f':  // F6キー
            FuncKey = c - 0x60;
            return ret;
            break;
        default:
            txt[0] = c;
            txt[1] = '_';
            txt[2] = 0;
            pos = 1;
            break;
    }
    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    while(1) {
        c = GetOneKey();
    }

END:
    if (ret < 0) {
        sprintf(txt, "%14.5f", ret);
    } else {
        sprintf(txt, "%15.5f", ret);
    }
    locate(8, y);
    Print((unsigned char*)txt);

    return;
}

ファンクションキーは、F1 ~ F2 のメニュー表示のほかに、F4 のオプション変更、 F6 の数値呼び出しで使用する予定なので予約しています。 それ以外のファンクションキーを使用する予定はありません。


ループ内での処理。

ループ中での処理では、変数 pos の値が 0 ~ 14 の値になるようにする事と、複数の小数点を付けない様チェックする必要があります。

小数点のチェックは、strstr関数を使用します。 <string.h> の宣言が必要ですが、既に strlen を使用するため宣言しているはずです。

プログラムが長くなってきたので、変更部分である while { } のループ部分のみ掲載します。

// ループ内の処理
    while(1) {
        c = GetOneKey();
        switch (c) {
            case 'E':  // EXEキー
                goto END;
                break;
            case 'C':  // ACキー
                goto START;
                break;
            case 'D':  // DELキー
                if (pos == 0 ) {
                    goto START;
                } else {
                    pos--;
                    txt[pos] = '_';
                    txt[pos + 1] = 0;
                }
                break;
            case 'a':  // F1キー
            case 'b':  // F2キー
            case 'd':  // F4キー
            case 'f':  // F6キー
                FuncKey = c - 0x60;
                return ret;
                break;
            case '.':  // ドットキー
                if (strstr(txt, ".") != 0) {  // ドットを検索
                    txt[pos] = '.';
                    pos++;
                    txt[pos] = '_';
                    txt[pos + 1] = 0;
                }
                break;
            case '-':  // マイナスキー(何もしない)
                break;
            default:
                txt[pos] = c;
                pos++;
                txt[pos] = '_';
                txt[pos + 1] = 0;
                break;
        }

        if (pos > 13) pos = 13;

        sprintf(txt, "%-14s", txt);
        locate(8, y);
        Print((unsigned char*)txt);

    }


入力範囲のチェック。

初めに書いた通り入力できる数値を -9999.9999 ~ 99999.9999 とします。

チェック作業は EXE キーを押して確定した戻り値の値で行います。 もし、範囲外であれば、START のラベルにジャンプします。

ループ中での処理では、変数 pos の値が 0 ~ 14 の値になるようにする事と、複数の小数点を付けない様チェックする必要があります。

上下のカーソルキーが有効になっているため、入力時には何もしない処理を付け加えます。

これで完成なので関数すべてを掲載します。

double InputVal(int y, double d) {
    double ret = d;
    char txt[22];
    char pos;
    char c;

    FuncKey = 0;

START:
    sprintf(txt, "%-.5f_", ret);
    pos = strlen(txt) - 2;
    txt[pos] = '_';
    txt[pos + 1] = 0;

    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    c = GetOneKey();
    switch (c) {
        case 'E':
            goto END;
            break;
        case 'D':
            pos--;
            txt[pos] = '_';
            txt[pos + 1] = 0;
            break;
        case 'a':  // F1キー
        case 'b':  // F2キー
        case 'd':  // F4キー
        case 'f':  // F6キー
            FuncKey = c - 0x60;
            return ret;
            break;
        case 'U':  // 上キー
        case 'W':  // 下キー
            goto START;
            break;
        default:
            txt[0] = c;
            txt[1] = '_';
            txt[2] = 0;
            pos = 1;
            break;
    }
    sprintf(txt, "%-14s", txt);
    locate(8, y);
    Print((unsigned char*)txt);

    while(1) {
        c = GetOneKey();
        switch (c) {
            case 'E':
                txt[pos] = 0;
                ret = atof(txt);
                goto END;
                break;
            case 'C':
                goto START;
                break;
            case 'D':
                if (pos == 0 ) {
                    goto START;
                } else {
                    pos--;
                    txt[pos] = '_';
                    txt[pos + 1] = 0;
                }
                break;
            case 'a':
            case 'b':
            case 'd':
            case 'f':
                FuncKey = c - 0x60;
                return ret;
                break;
            case '.':
                if (strstr(txt, ".") == 0) {
                    txt[pos] = c;
                    pos++;
                    txt[pos] = '_';
                    txt[pos + 1] = 0;
                }
                break;
            case 'U':
            case 'W':
            case '-':
                break;
            default:
                txt[pos] = c;
                pos++;
                txt[pos] = '_';
                txt[pos + 1] = 0;
                break;
        }

        if (pos > 13) pos = 13;

        sprintf(txt, "%-14s", txt);
        locate(8, y);
        Print((unsigned char*)txt);
    }

END:
    if (ret < -9999.9999 || ret > 99999.9999) goto START;
    if (ret < 0) {
        sprintf(txt, "%14.5f", ret);
    } else {
        sprintf(txt, "%15.5f", ret);
    }
    locate(8, y);
    Print((unsigned char*)txt);

    return ret;
}

汎用性を持たせるための修正。

いくつか汎用性を持たせるための修正を行います。

修正箇所

固定値で指定しているものを変数で行う。
 locate 文の x の値
 有効範囲の最小値と最大値

横位置(x)の指定に伴い文字数を変数化する。
 入力可能範囲 len (基本は 22 - x)

最小値が 0 の場合、マイナスのキー入力を制限する。
  マイナスの入力制限用にフラグを用意する。

整数入力に限定するためにドットの入力を制限する。
  ドットの入力制限用にフラグを用意する。

sprintf の書式文字列を変数化する。
  書式文字用変数 fmt[8]
  文字数 = len (22 - x)
  小数点以下の桁数 flen (表示桁数 +1)
double InputVal(int y, double d) {
    double ret = d;
    char txt[22];
    char pos;
    char c;
    int x = 8;  // 横位置の指定
    double min = -9999.9999;  // 有効範囲の最小値を定義
    double max = 99999.9999;  // 有効範囲の最大値を定義
    char flg_minus = 1;  // マイナス制御用フラグ。 0 でマイナスを使用する。
    char flg_dot = 0;  // ドット制御用フラグ。 0 でドットを使用する。
    char fmt[8];
    char len = (22 - x);  // 文字幅
    char flen = 5;  // 小数点の桁数 (+1)
    FuncKey = 0;
    if (min < 0) flg_minus = 0;  // マイナス制御用フラグを最小値で判定

START:
    sprintf(fmt, "%s%df_", "%-.", flen);  // 書式文字列を作成
    sprintf(txt, fmt, ret);  // 書式文字列を変数化
    pos = strlen(txt) - 2;
    txt[pos] = '_';
    txt[pos + 1] = 0;

    sprintf(fmt, "%s%ds", "%-", len);  // 書式文字列を作成
    sprintf(txt, fmt, txt);  // 書式文字列を変数化
    locate(x, y);  // 横位置を変数で指定
    Print((unsigned char*)txt);

    c = GetOneKey();
    switch (c) {
        case 'E':
            goto END;
            break;
        case 'D':
            pos--;
            txt[pos] = '_';
            txt[pos + 1] = 0;
            break;
        case 'a':  // F1キー
        case 'b':  // F2キー
        case 'd':  // F4キー
        case 'f':  // F6キー
            FuncKey = c - 0x60;
            return ret;
            break;
        case 'U':
        case 'W':
            goto START;
            break;
        case '-':  // マイナスキーの処理を追加
            if (flg_minus) goto START;
            txt[0] = c;
            txt[1] = '_';
            txt[2] = 0;
            pos = 1;
            break;
        case '.':  // ドット入力の処理を追加
            if (flg_dot) goto START;
            txt[0] = c;
            txt[1] = '_';
            txt[2] = 0;
            pos = 1;
            break;
        default:
            txt[0] = c;
            txt[1] = '_';
            txt[2] = 0;
            pos = 1;
            break;
    }
    sprintf(txt, fmt, txt);  // 書式文字列を変数化
    locate(x, y);  // 横位置を変数で指定
    Print((unsigned char*)txt);

    while(1) {
        c = GetOneKey();
        switch (c) {
            case 'E':
                txt[pos] = 0;
                ret = atof(txt);
                goto END;
                break;
            case 'C':
                goto START;
                break;
            case 'D':
                if (pos == 0 ) {
                    goto START;
                } else {
                    pos--;
                    txt[pos] = '_';
                    txt[pos + 1] = 0;
                }
                break;
            case 'a':
            case 'b':
            case 'd':
            case 'f':
                FuncKey = c - 0x60;
                return ret;
                break;
            case '.':
                if (flg_dot) break;

                if (strstr(txt, ".") == 0) {
                    txt[pos] = c;
                    pos++;
                    txt[pos] = '_';
                    txt[pos + 1] = 0;
                }
                break;
            case 'U':
            case 'W':
            case '-':
                break;
            default:
                txt[pos] = c;
                pos++;
                txt[pos] = '_';
                txt[pos + 1] = 0;
                break;
        }

        if (pos > (len - 1)) pos = len - 1;  // 文字幅を制限

        sprintf(txt, fmt, txt);  // 書式文字列を変数化
        locate(x, y);  // 横位置を変数で指定
        Print((unsigned char*)txt);
    }

END:
    if (ret < min || ret > max) goto START;  // 範囲を変数で指定
    if (ret < 0) {
        sprintf(fmt, "%s%d%s%df", "%", len, ".", flen);  // 書式文字列を作成
        sprintf(txt, fmt, ret);  // 書式文字列を変数化
    } else {
        sprintf(fmt, "%s%d%s%df", "%", len + 1, ".", flen);  // 書式文字列を作成
        sprintf(txt, fmt, ret);  // 書式文字列を変数化
    }
    locate(x, y);  // 横位置を変数で指定
    Print((unsigned char*)txt);

    return ret;
}

このプログラムでは、文字数を 22 - x で計算しないで直接指定した場合、 アンダーバーや表示したくない小数点以下の桁数が表示されてしまいます。(気が向いたときに修正しようと思います。)


少し特殊なものになりましたがこれで入力用の関数は完成です。

ファンクションキーの取り扱いがあるので呼び出す際に注意が必要です。