ヒューマンインタフェース’99 ＃ 1
久野靖∗

1999.4.9

はじめに• Human Computer Interaction (HCI) という言い方が一般的に□ 本科目の目的→人間と計算機のインタフェースを担うソ• CHI (Computer Human Interaction) ---フトウェアの基本的な考え方/枠組み/実現技術について

SIGCHI, CHI’99 (カ ン ファレ ン ス) → 語 呂 がいいから (人間より計算機が高価だったなごり?)学ぶこと。
□ 各回の進め方□ HCI とかいうと「認知科学的に」とか「実験」とかが欠• 前半: レクチャー (各回ごとに別テーマ)

かせない気がして…→比較的「古典的な」UI という用• 後半: 実習 (各回のテーマを題材にする)語を使う (久野)□ 成績評価→出席、各回の課題内容

3
1

ユーザインタフェースの基礎概念□ 計算機の…

• 計算機の処理速度?

□ ユーザインタフェースとは?

• 計算機の所要記憶容量?

□ ユーザインタフェースの歴史• 計算機のプログラミングの複雑さ?

□ ユーザインタフェースで何が問題か?

□ 人間の…

□ ユーザインタフェースの評価方法• 人間の操作所要時間?

□ 古典的なユーザインタフェース• 人間の記憶負荷?

• コマンド入力• 人間の学習/習熟負荷?

• フォーム入力• 人間の疲労?

• メニュー入力□ 実験:• 人間のたのしさ?

古典的なユーザインタフェースの実装と比較

42

ユーザインタフェースで何が問題?

ユーザインタフェースとは?

□ 計算機科学基礎でやりましたが…
□ 計算機のはじまり→コンソールパネルのランプとスイッチ□ インタフェース (界面) →「接するところ」• ユーザインタフェース?

• 現在でも「マシン語やハードウェアを教えるにはこれがベスト」と言っている人が沢山いるヒューマンインタフェース?

ユーザ (人間) と「何の」インタフェース?

• それはなぜ?

□ 「計算機と人間がやりとりする部分」• 「対話的入出力」(cf.
…)

ユーザインタフェースの歴史

ファイル入出力、プリンタ、何が重要なの?

• 直接操作感 vs 計算機内部のイメージ→プログラミ
ングの得手不得手にも通じる?
□ バッチシステムの時代→パンチカードで「ジョブ」を作成→提出→オペレータが実行→出力を返却 (数時間のターンアラウンドタイム)∗ 筑波大学大学院経営システム科学専攻1• これは対話性という点では最悪だが…

6

モデルとメタファ• 「プログラムをじっくり見直す」という点ではよかっ□ ユーザに一貫したモデルを提示→ユーザの認知負荷軽たという節も。
減、学習しやすさ、記憶しやすさ、予測しやすさに貢献• 結局、何がどうであれば「よりよい」と考えるか?
• 物理法則に従う、等…
• とはいえ、バッチシステムは「計算機の利用効率」に• しかし、計算機で扱うものすべてにユーザと共有で偏した方式。

きるモデルが存在するわけではない□ 対話的に計算機を使う===「TSS」「ミニコン」の時代□ メタファ→ユーザがよく知っているものに「比喩」• 入出力機器→テレタイプ端末• デスクトップメタファ、等…

• 圧倒的に出力がのろい!(毎秒 10 文字)…でも「いい• 完全に「もの」と同じにはできない (机の上を整理こと」も。

するのと同じくらい労力が掛かるのでは無意味) →• 対話的な計算機の使用→画期的

計算機を使うなりの何らかの「magic」が必要 (でな• 対話型エディタ (テレタイプエディタ) → QED、teco、

ければ計算機を使う意味はない)。

…→「めくらでの編集」しかしそれでも以前のパンチカード打ち直しや紙テープ切り貼りよりずっと嬉しい。

7□ その後→画面端末、回線の高速化

ユーザインタフェースの評価方法□ 前述の目標を多く満足するほどよい• 「画面エディタ」→「見ながら」編集できる→画期的• どうやって達成するか考えるだけでなく、そのことをどうやって評価/検証するかが問題。

□ 画面端末…「決まった位置に決まった大きさの文字」しか表示できない□ 明らかな答えの 1 つ:

□ これに対する変革→ビットマップディスプレイ。ALTO(最主観的評価• 侮るべきでないが、やはりこれだけでは困る。

初のワークステーション) → XEROX の WS、PERQ、Sun、Apollo、…(ワークステーション文明)、LISA → Mac → □ 明らかな答えの 1 つ: 実験をする
Windows(パソコン文明)• どうやって実験をするか?
• 例:• この辺に関する話題は＃ 2 で。

決まった動作をさせて、時間を計測する。

• 実験で測っているのは何を測っているのか?

□ ともかく、ユーザインタフェースに関するトレンドは• 測っているものは本当に測りたいものか?

「計算機の資源は豊富に使ってよいから、ユーザにサー□ この先は次回の「おたのしみ」です (宿題あり)。

ビスする」方向。昔の「計算機が貴重だった時代」とは対照的。

5

GUI 以前の代表的なユーザインタフ
ェース方式• だからとってむやみに飾ればいいわけではない…

8

ユーザインタフェースの課題□ とりあえず、画面端末まででデータ入力などに使われていたものでいうと:□ 一般に目標とされること…

• コマンド方式:• 単純さ/簡潔さ

コマンドやオペランドを 1 行ないし複数行で打ち込んで行く• 表現能力 (コマンドのバリエーション、…)• 書式 (フォーム) 方式:• ユーザの満足 (制御感、親切、…)

記入欄を持つ画面に必要項目を記入し行く• コスト (実現コスト、学習コスト、…)• メニュー方式: メニューが表示され、その中から項目を選択して行く□ それぞれの利点/欠点は何だと思います?

2

8.1

コマンド方式• 「どれか 1 つを選択するだけ」だから極めて簡単、何も覚えておく必要がない→初心者むけ□ コマンド、オペランドを順に打ち込んで行く• その時点で選択可能でない選択肢は始めから選べない
Q> ship prod=QS190241 cust=15141 qty=50 date=4/30/99• その代りのろい!
面→見る…

• パラメタ、書き方などすべて覚えている必要がある• その代り高速にできる (設計しだい)• 簡単に迷子になってしまう• エラー検出のワザが多数使用できる• 最後まで全部メニューには普通できない (データ入力とか…)□ その他の特性:• なのでフォームと組み合わせることが多い• 「位置パラメタ」と「キーワードパラメタ」がある。

□ その他の特性:• 現在ではコンプリーション (補完) のようなワザも使える。

8.2

見る→考える→選択操作→次の画• GUI の (マウスで選択する) メニューとはちょっと違う (マウスだとさらに遅い)

フォーム方式設計演習

9□ 穴埋め書式が画面に表示され、それを埋めて行く□ あるシステムのユーザインタフェースを上記の 3 方式で__________________________________

設計する
Command: _____□ シナリオ:

Prod-ID: _____QTY:_______

Cust-ID: _____

Date: _______• 備わっている機能: 「発送処理」「補充注文」「在庫確認」「終了」• どの処理かの選択は行うが、実際には「発送処理」の部分だけ実装する__________________________________• 「何を打ち込むか」は覚えてなくてよい• 「発送処理」に必要な項目:「顧客番号」「発送日」• 「どのように打ち込むか」は覚える必要がある (しかし大抵は自明)• 必要なら「どのように」の説明もフォームに含める

9.1

ことができる「商品番号」「数量」

データ例□ データの例を示す (設計に当たって本物のデータを見る□ その他の特性:事は重要!)• 欄によっては「あけたまま」にしておくこともある• 商品番号の例:
かれている)• 打ち込む位置の移動はタブなどのキー操作で行うのが普通

8.3

専門のオペレータが使うインタフェース(入力伝票には番号と名前が両方書

0151 Steel Desk (Large)
0152 Steel Desk (Small)1044 Pine Table
1241 Pine Bench (Long)
2415 Pine Chair
5514 Old Fashoned Swing
4156 Color Pencil Set
0843 Convertible Sofa
5141 Magazine Rack

メニュー方式□ 画面がメニューになっていて、そこから項目を選択すると次の画面へ進む__________________________________• 顧客番号の例:かれている)

Command:
1: Ship
2: Order3: Query Stock
9: EXIT__________________________________(入力伝票には番号と名前が両方書

A0012 Jack Lennon
A1021 Loan Channon
A1581 Susan McDonald
B0141 Julian Garland3

for(i = 0; i < l; ++i) {if(isspace(buf[i]) && p < i) {buf[i] = 0; c = strlen(buf+p); s = malloc(c+1);
strcpy(s, buf+p); if(j < n) words[j++] = s;p = i+1;} else if(isspace(buf[i])) {p = i+1;}}
return j;

B1042 Suna Alford
A4015 Canon Hufford
B1411 Judy Barman
C4105 Lenox Bullis
B0015 Alenn Gummon

演習

9.2

□ 上記のような条件で、 3 方式 (コマンド、フォーム、メ}ニュー) の入力アプリケーションを設計せよ。
cmdprefix(char *p, char *s) {int i;
for(i = 0; ; ++i) {if(p[i] == 0 || p[i] == ’=’) return i;if(p[i] != s[i]) return -1;}}

• 画面例を描くこと (画面をスケッチすることはよい設計の手段)□ 自分/他人の設計を次の点から検討せよ• その設計だとどのような入力間違いが発生するか• その設計だとどの部分で時間が掛かると思うか

cmdrecog(char *cmd, char *words[], int n) {int p = 0, len = 0, i, k;• その設計だと 1 項目あたりの入力時間はどれくらい
for(i = 0; i < n; ++i) {int k = cmdprefix(cmd, words[i]);だと思うかprintf("(%s,%s,%d)\n", words[i],cmd,k);if(k < 0) {□ 上記の検討で問題となった事柄を解消する手段を検討; /* do nothing */せよ} else if(k > len) {len = k; p = i+1;
} else if(k == len) {p = -1;
10 コマンド入力}}□ コマンド入力のためのライブラリルーチンを作ってみた。
return p;}• cmdinput: プロンプトを出し、1 行入力。入力した行の内容を空白で区切り、各かたまりを 1 つの文字列にして渡された配列に入れる。

• cmdprefix:

10.2

ある文字列が別の文字列のプレフィク

コード: t30.c — テストドライバ

static char *cmds[] = {"exit", "edit", "edu", "bye", "good"};

スになっているかどうかを調べる。
main() {char *w[100];
int i, l;while(1) {while((l = cmdinput("Q>", w, 100)) == 0) ;for(i = 0; i < l; ++i) printf("%s\n", w[i]);i = cmdrecog(w[0], cmds, sizeof(cmds)/sizeof(char*));printf("%d\n", i);if(i == 1) exit(0);}}• cmdrecog: ある文字列が指定した複数の語のどれのプレフィクスになっているかを調べる。

10.1

コード: command.c#include <stdlib.h>#include <stdio.h>#include <string.h>#include <ctype.h>#define MAXBUF 4096
cmdinput(char *msg, char *words[], int n) {static char buf[MAXBUF];
int c, i, j = 0, p = 0, l = 0;char *s;printf("%s ", msg); fflush(stdout);
while((c = getchar()) != EOF && c != ’\n’) {if(l+2 < MAXBUF) buf[l++] = c;}buf[l++] = ’ ’;

10.3

演習

□ 上記のライブラリを利用して先のデータ入力プログラムを作成してみよ。

4画面端末の制御

11□ 画面端末:fprintf(stderr, "cannot find terminfo entry.\n"); exit(1);li = tgetnum("li");co = tgetnum("co");line = (int**)malloc(sizeof(char*)*li+1);
lineb = (int**)malloc(sizeof(char*)*li+1);
for(i = 1; i <= li; ++i) {line[i] = (int*)malloc(sizeof(int)*(co+1));
lineb[i] = (int*)malloc(sizeof(int)*(co+1));}
scrclear();tcsetattr(0, TCSANOW, &ttb2);

エスケープシーケンスで制御• あくまで最後に描いたものが残っている• プログラム内部:と言いたい「これこれの位置に文字を描く」□ 使いやすい界面と効率よい描き直しを実現するライブラ
リを作ってみた}• 内部バッファに画面の写しを保持し、変更箇所のみ /* scrend -- finalize screen */scrend() {を端末に送信• scrinit, scrend:
定を変更)

画面を全クリア• scrclear:• scrputc, scrputs:
き込む

指定位置に文字/文字列を書 /* scrlines -- return no. of lines */scrlines() { return li; }/* scrcols -- return no. of columns */scrcols() { return co; }scrputs と同様だが行末までクリア• scrupdate:

11.1/* scrgetc -- read 1 char from keyboard */scrgetc() {char buf[1]; read(0, buf, 1); return buf[0];}画面の行数、カラム数を取得• scrlines, scrcols:• scrpos:}

1 文字入力• scrgetc:• scrputl:

tcsetattr(0, TCSADRAIN, &ttb1);

初期設定、後しまつ (端末の設

変更内容を画面に反映/* scrclear -- clear screen */scrclear() {int i, j;for(i = 1; i <= li; ++i) {for(j = 1; j <= co; ++j) line[i][j] = ’ ’;for(j = 1; j <= co; ++j) lineb[i][j] = ’ ’;}
printf("\033[2J"); fflush(stdout);}

カーソルを指定位置に置く

コード: screen.c/* screen.c --- screen package, input handling */#include <stdio.h>#include <termios.h>static char tibuf[1024];
static int **line;
static int **lineb;
static int co, li;
static struct termios ttb1, ttb2;#define HL 0x80000000/* scrputc -- put a charcter on the screen */scrputc(int l, int c, int x, int h) {x &= 0x7f; if(h) x |= HL;if(1 <= l && l <= li && 1 <= c && c <= co) line[l][c] = x;}/* scrinit -- initialize screen *//* scrputs -- put a string on the screen */scrinit() {scrputs(int l, int c, char *s, int h) {int i;
while(c <= co && *s) {if(tcgetattr(0, &ttb1) != 0) {scrputc(l, c, *s, h); ++c; ++s; }fprintf(stderr, "cannot get termnal controls.\n"); }exit(1); }ttb2 = ttb1;/* scrputl -- put a string on the screen */ttb2.c_iflag |= IGNBRK;
scrputl(int l, int c, char *s, int h) {ttb2.c_iflag &= ~ICRNL&~ISTRIP&~IXOFF;
while(c <= co && *s) {ttb2.c_oflag &= ~ONLCR;
scrputc(l, c, *s, h); ++c; ++s; }ttb2.c_cflag |= CS8;
while(c <= co) {ttb2.c_cflag &= ~PARENB;
scrputc(l, c, ’ ’, 0); ++c; }ttb2.c_lflag |= TOSTOP;}
ttb2.c_lflag &= ~ISIG&~ICANON&~ECHO&~ECHOE&~ECHOK&~ECHONL&~ECHOCTL&~ECHOPRT;/* scrupdate -- update screen actually */for(i = 0; i < NCCS; ++i) ttb2.c_cc[i] = -1;
scrupdate() {ttb2.c_cc[VMIN] = 1;
int i, j, l, r, h = 0;
ttb2.c_cc[VTIME] = 0;
for(i = 1; i <= li; ++i) {if(tgetent(tibuf, "ansi") <= 0) {5• layout sets:for(l = 1; l <= co; ++l)if(line[i][l] != lineb[i][l]) break;for(r = co; r >= l; --r)if(line[i][r] != lineb[i][r]) break;if(l <= r) {printf("\033[%d;%dH", i, l);for(j = l; j <= r; ++j) {if(line[i][j]&HL) {if(!h) { printf("\033[7m"); h = 1; } }else {
if(h) { printf("\033[0m"); h = 0; } }putchar(line[i][j]&0x7f);
lineb[i][j] = line[i][j];}}• layout toggle:
転をトグル

i 番目の「文字列」の反転/非反• layout update:

変更を画面上に反映

12.1

コード: layout.c

typedef struct unit {int l, c, h; char *s; } unit_t;
typedef struct layout {int size;
unit_t *body; } layout_t;}
if(h) printf("\033[0m");

layout_t *layout_create(int size) {layout_t *lay = (layout_t*)malloc(sizeof(layout_t));lay->size = size;lay->body = (unit_t*)malloc(sizeof(unit_t)*size);
return lay;}}/* scrpos -- position cursor */scrpos(int l, int c) {printf("\033[%d;%dH", l, c); fflush(stdout);}

11.2

i 番目の「文字列」を設定/変更

layout_sets(layout_t *lay, int n, int l, int c, char *s) {if(n >= 0 && n < lay->size) {lay->body[n].s = s;lay->body[n].l = l;lay->body[n].c = c;lay->body[n].h = 0;}}

コード: t47c.c — 画面お絵描き/* t47c.c --- screen demo */#include <stdio.h>main() {int i, j, c, co, li;
int mode = 1; /* 1:draw, 2:erase, 0:pass */scrinit();co = scrcols();
li = scrlines();i = j = 10; scrpos(i, j);while((c = scrgetc()) != ’q’) {if(c == ’d’) mode = 1;
if(c == ’e’) mode = 2;
if(c == ’p’) mode = 0;if(c == ’h’) --j;if(c == ’j’) ++i;if(c == ’k’) --i;if(c == ’l’) ++j;if(mode == 1)
scrputc(i, j, ’*’, 0);else if(mode == 2)
scrputc(i, j, ’ ’, 0);scrupdate(); scrpos(i, j);}scrpos(li, 1); scrend();}layout_toggle(layout_t *lay, int n) {if(n >= 0 && n < lay->size)lay->body[n].h = !lay->body[n].h;}layout_update(layout_t *lay) {int i;
for(i = 0; i < lay->size; ++i) {unit_t *u = &lay->body[i];scrputl(u->l, u->c, u->s, u->h);
}
}

12.2

コード: t51.c — 配置のデモ/* t51.c -- layout interface test */typedef void *layout_t;
layout_t *layout_create();main() {int c, li;
layout_t *lay = layout_create(4);12 大きな単位での配置
layout_sets(lay, 0, 2, 5, "aaaa");layout_sets(lay, 1, 4, 5, "bbbb");□ 任意の文字列を画面上の任意の場所に置くようにした。
layout_sets(lay, 2, 6, 5, "cccc");layout_sets(lay, 3, 8, 5, "dddd");• layout create: N 個の「文字列」が置けるようにscrinit();用意li = scrlines();6scrinit();i = formfill("Fill your info.", f1, 3);scrend();printf("your name:%s, age:%s, occupation:%s\n",f_name, f_age, f_occ);

layout_update(lay);scrupdate(); scrpos(li, 1);
while((c = scrgetc()) != ’q’) {layout_toggle(lay, 2); layout_update(lay);scrupdate(); scrpos(li, 1);}
scrend();}

}

13.3

フォーム機能13演習□ 上記のフォームライブラリを使ってデータ入力プログラムを作れ。

□ レイアウト機能を利用すればフォームは簡単• formfill: フォームを表示し記入。渡す配列の奇数番目はタイトル、偶数番目は入力欄になっている

メニュー

14(呼ぶ側で配列をそのように設定して呼ぶ)□ メニューも同様にできる (フォームより簡単)• menuselect:13.1

配列にメニューの項目を用意コード: form.c

formfill(char *mes, char *form[], int n) {14.1 コード: menu.c
int sel = 0;
int li = scrlines();menuselect(char *mes, char *menu[], int n) {int i, c;
int sel = 0;char *s;
int li = scrlines();
scrclear();int i, c;
scrputs(1, 1, "[TAB]: next, [RET]: end", 0);scrclear();
scrputs(li, 1, mes);
scrputs(1, 1, "^N: down, ^P: up, [RET]: select", 0);
while(1) {scrputs(li, 1, mes);for(i = 0; i < n; ++i) {while(1) {int h = (sel == i);for(i = 0; i < n; ++i) {scrputs(i*2+3, 15-strlen(form[2*i]), form[2*i], h);
int h = (sel == i);
scrputl(i*2+3, 17, form[2*i+1], h);scrputs(i*2+3, 4, menu[i], h);}}scrupdate(); scrpos(li, 1); c = scrgetc();
scrupdate(); scrpos(li, 1); c = scrgetc();s = form[2*sel+1];
if(c == ’\r’) {if(isprint(c)) {break; }i = strlen(s); s[i] = c; s[i+1] = 0; }else if(c == (’P’ - ’@’)) {else if(c == ’\b’ || c == ’\177’) {--sel; if(sel < 0) sel = n-1; }i = strlen(s)-1; if(i >= 0) s[i] = 0; }else if(c == (’N’ - ’@’)) {else if(c == ’\t’) {++sel; if(sel >= n) sel = 0; }++sel; if(sel >= n) sel = 0; }}else if(c == ’\r’) {return sel;break; }}
}
}

14.2
13.2

コード: t52.c — フォームのデモ

コード: t52.c — メニューのデモ/* t52.c --- menuselect test. *//* t53.c -- form filling test. */char *m1[] = {"0. Male.",char f_name[100] = "", f_age[100] = "", f_occ[100] = ""; "1. Female.",char *f1[] = {"2. Neutral" };"Name:", f_name,"Age:", f_age,
main() {"Occupation:", f_occ };int i;
scrinit();i = menuselect("Choose your sex.", m1, 3);scrend();main() {int i;

7

printf("your selection was: %d\n", i);
}

14.3

演習

□ メニューを使ったデータ入力プログラムを作れ。
□ 3 つのバージョンでデータ入力を行って結果を比較せよ。

8