Mouse Down Event Usage Example

「MouseDownイベントの活用例」

手書き答案をスキャナーで読み込んで採点するプログラムを書いた。Gridコントロールへの入力に対し、各々の解答欄の画像に ○ や × や得点を表示できるようにしたら、合計点がなければ採点済み答案とは言えないコトに気付いた。でも、それを「どこ」に記入するかは答案ごとに違うし、採点ミスがあった場合は個別の修正に対応する必要もある。そこでMouseDownイベントが役に立ったというお話。

1.合計点はいつ・どこに書く?
2.画像に ○ や × それから得点も表示
3.「やっぱりココ!」に対応
4.まとめ
5.お願いとお断り

1.合計点はいつ・どこに書く?

多くの場合、それは答案の右上か、右下に書かれている。左上や左下、まして「ど真ん中」ってのはまず見たコトない・・・ケド。とりあえず、合計点を書く場所は、まったく採点者の自由で、法的に「ココじゃなきゃダメ」って、決められているなんて話は聞かない。だから、合計点を書く(プログラム的には「置く」の方がしっくりするが)場所は、採点者が「ココ!」ってクリックした位置にすることにした。100%自由ってステキ。

それから採点者も人間である以上、当然のように間違える。採点ミスがあれば、もちろん合計点も変わる。・・・ってコトは、PCと協働作業する以上、合計点の計算はPCに任せるからイイとして、それを答案画像に「二度と修正できない」カタチで「埋め込んで」しまうわけにはいかない。合計点は、返却用の答案画像を印刷するときに、「どっかから持ってきて」、答案画像の上に一時的に「置く」くらいがちょうどイイ。

・・・ということで、基本方針だけを決め、新しいチャレンジがはじまった!

2.画像に や × それから得点も表示

最初は正誤の表示(○と×)だけだったけれど、得点も表示することにした

上のような画面に、スキャンした答案画像から設問ごとにかき集めた解答欄を表示してイッキに採点する。上のように全員が同じ解答なら得点の一括入力も可能だ。採点が済んだら、読み込み元の答案画像とは別に用意した返却用の答案画像に、集めてきた時とは逆のアルゴリズムで書き戻す。

答案に書き戻してみたところ(もっとズレるかと思ったが案外ずれてない)

で、決定的に足りないモノがあることに気づく・・・。ここまでやって「合計点がない」というのは、仏作って魂入れず & 画竜点睛を欠く & ツメが甘い & 九仞の功を一簣に虧く 以外の何者でもない。日本語の豊かさに感動しつつ、もっと簡単な言い方をすれば、プログラムは、どう考えても不完全。元よりこれを売るつもりはまったくナイけど(買うヒトがいるとも思えない)、合計点が「出ない」採点プログラムなんて詐欺だ。

なにより、この答案は、なんだかさみしい・・・

恐るべし。合計点の存在感。

・・・ということで、合計点も入れることに。

3.「やっぱりココ!」に対応

StringGridに見えない列を1つ追加して、ここに計算した合計点を書き込んでおけばいつでも印刷に使える。合計点の計算そのものはカンタン。何も問題はない。

var
  i, j, k:integer;
begin
  //初期化
  k:=0;
  //合計点を計算
  for i := 1 to StringGrid1.RowCount-1 do
  begin
    for j := 1 to ( 解答欄の数 ) do
    begin
      if StringGrid1.Cells[j,i] <> '' then
      begin
        k := K + StrToInt(StringGrid1.Cells[j, i]);
      end;
    end;
    //StringGrid.Cells[, ]
    StringGrid1.Cells[( 解答欄の数 ) + 1, i]:= IntToStr(k);
    //初期化
    k:=0;
  end;

これで合計点の準備はOK!
あとは「いつ」印刷するか・・・ってか、採点ミスがあった時、カンタンに合計点も修正できるようにしなければならない。これは結構、難しい。合計点を答案画像に画像データとして埋め込んでしまうと、まず修正はできない。どぉーするか・・・

よく考えたら(よく考えなくても)、この解答用紙には合計点を記入する場所すらない。まぁもともとがマークシートで、そこに無理やり手書き用の解答欄を付け加えたのがほんとうだから、ないのが当然なんだが。

こうなったらもう、面倒なことは全部やめて、答案画像を印刷する直前に、採点者が適当に「ココ!」ってクリックした場所に合計点を置いて、印刷はするけど、その後、画像は保存しない仕様で行こう。

印刷日が異なると、ビミョー(ヒトによっては大きく)に合計点印刷位置がズレる・・・という問題?は、「気にしない」ことにしよう。法的には何の問題もない。返却された答案に合計点が「ある」ことが大切なのだ。

フォントの大きさは、得点入力のところで使った指定をそのまま使えばイイ。

FontのSizeは50

これでアルゴリズムは決まり。あとは採点者に「ココ!」って指定してもらうプログラムをDelphiで書くだけ。でも、その「ココ!」はきっと、やっぱりもぉちょっと上とか、左とか、位置指定をやり直したい場合が絶対あるよなー。どぉするか・・・

TImageの上でマウスのボタンを押すたびにMouseDownイベントが起こるから、コレをうまく活用すればイイ。きっとそれだなー。で、書いたのがコレです。

合計点を印刷したい場所をクリックするとサンプル99を表示
procedure TFormCollaboration.Image1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  //表示倍率
  rate:Double;
  ・・・ 省略 ・・・

  //普通の四捨五入を行う関数を設定
  function Roundoff(X: Extended): Longint;
  begin
    if x >= 0 then Result := Trunc(x + 0.5)
              else Result := Trunc(x - 0.5);
  end;

  //合計点印刷位置の座標を取得する手続き
  procedure GetXY(iX,iY:Integer);
  begin
    //合計点印刷位置の座標を取得
    iX:= Roundoff(iX/(TrackBar1.Position/100));
    iY:= Roundoff(iY/(TrackBar1.Position/100));
    //表示倍率を計算(答案画像は縮小してWidth1000にセットしている)
    rate:= 1000/Image1.Picture.Bitmap.Width;
    合計点のX座標:= Trunc(iX/rate);
    合計点のY座標:= Trunc(iY/rate);
    //矩形を描画
    with Image1 do
    begin
      //Canvas.Brush.Style:= bsClear;  //Pythonを使っていない時はこれでOK!
      //Pythonを使っている時は明示的に書く(Python.pasにもbsClearが定義されている)
      Canvas.Brush.Style:= Vcl.Graphics.bsClear;  
      Canvas.Pen.Color:=clRed;
      Canvas.Pen.Width:=3;
      //矩形を描画
      (サンプル99はLabelのCaption).Font.Size:=StrToInt(ComboBox.Text);
      Canvas.Rectangle(合計点のX座標, 
                       合計点のY座標, 
                       合計点のX座標 + Label.Width, 
                       合計点のY座標 + Label.Height);
      //サンプル合計点を描画
      Canvas.Font.Color:=clRed;
      Canvas.Font.Size:=StrToInt(ComboBox.Text);
      Canvas.TextOut(合計点のX座標, 合計点のY座標, Label.Caption);
    end;
  end;

begin
  //座標を指定する手続きを呼び出し
  GetXY(X,Y);
  //Information
  if MessageDlg('印刷位置は、この位置でよろしいですか?' + #13#10 + #13#10 +
    '(左寄せ・数字はサンプル。矩形は印刷されません。)', 
    mtInformation, [mbYes, mbNo], 0) = mrYes then
  begin
    //[はい]が選ばれた時
    //案内
    MessageDlg('印刷ボタンをクリックしてください。', mtInformation,[mbOK],0);
    //バルーンヒントを表示
    BalloonHint1.Title := '印刷ボタン';
    BalloonHint1.Description := 'ココです!';
    BalloonHint1.HideAfter := 3000; //表示時間(単位:ms)
    BalloonHint1.ShowHint(button.ClientToScreen(CenterPoint(button.ClientRect)));
    //案内アイコンも追加
    BalloonHint1.ImageIndex := 0;
    //カーソルを元に戻す
    Screen.Cursor:=crDefault;
    Image1.Visible:=False;
    Image1.Picture.Assign(nil);
    //SetFocus
    button.Enabled:=True;
    button.SetFocus;
  end else begin
    //[いいえ]が選ばれた時
    with Image1 do
    begin
      //Canvas.Brush.Style:=bsClear;  //Pythonを使っていない時はこれでOK!
      //Pythonを使っている時(Python.pasにもbsClearが定義されている)
      Canvas.Brush.Style:=Vcl.Graphics.bsClear;  
      Canvas.Pen.Color:=clWhite;
      Canvas.Pen.Width:=3;
      //矩形を描画
      (サンプル99はLabelのCaption).Font.Size:=StrToInt(ComboBox.Text);
      Canvas.Rectangle(合計点のX座標, 
                       合計点のY座標, 
                       合計点のX座標 + Label.Width, 
                       合計点のY座標 + Label.Height);
      //サンプル合計点を描画
      Canvas.Font.Color:=clWhite;
      Canvas.Font.Size:=StrToInt(ComboBox.Text);
      Canvas.TextOut(合計点のX座標, 合計点のY座標, Label.Caption);
    end;
  end;
end;

ユーザーが「ココ!じゃない」=「いいえ」を選択した場合は、サンプルとして表示した合計点を消去しなければならない。Undoの実装方法をいろいろ調べてみたのだが、よくわからない。で、思いついたのが上の方法。「いいえ」が選択された場合は、サンプルを「赤」じゃなくて「白」で書いちゃう。正直、完全に消えるわけじゃなくて、なぜか、よく見るとうっすらと赤が残っているけれど、気にしない。これで全然イケます。

合計点も印刷できるようになりました!

【追記 20221003】

合計点はサンプル「99」ではなく、個々の合計点を取得して表示できるよう、プログラムを修正しました。下のリンク先をご参照ください。

4.まとめ

そこにTImageがあれば、彼はいつでも OnMouseDown を待ち続けているから、このイベントをうまく利用すれば、再帰的な処理(?)が実現できてしまう。画像として保存したくは「ない」んだけれど、印刷時には「ちょっとイジりたい」時にはこんな方法もあります・・・というお話でした。

5.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。ここに記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。