投稿者「サイト管理者」のアーカイブ

IMEでかな漢字変換すると文字が白くなる・見えない!

2024年2月上旬頃からか?
テキストボックス等を置いて、その中へ文字列を入力すると、かな→漢字変換するためのスペースキー押し下げで、変換候補の文字が白くなり、見えなくなる現象が発生。

PCを壊すような操作は、「なんにもしてない」から、これは(お決まりの?)Windows11のアップデートに伴う不具合かと、推測。

IME(Input Method Editor)の設定を変更して、とりあえず、安定動作するオールド・バージョンで、その場しのぎをしてみました☆

【もくじ】

1.かな→漢字変換がヘン!
2.IMEの設定を変更
3.まとめ
4.お願いとお断り

1.かな→漢字変換がヘン!

2024年2月上旬頃(?)から、テキストボックス等を置いて、その中へ「かな」入力して、スペースキー押し下げ、変換する漢字の候補が表示されるシーンで、候補の画面が全部白色で表示され、現在表示されているはずの変換先の文字列が見えなくなる現象が発生。

2023年12月にクリーンインストールしたばかりのWin11なのに、もう壊れたの?

・・・みたいな気もしたけど、なんか、似たような現象が前にもあった気が。
IMEの機能が更新されて不具合が出ているなら、確か、更新されたIMEを使わない設定があったはずと、思い返し、Win11の設定を変更して様子をみることに。

2.IMEの設定を変更

IMEの設定を変更する前の状況は、次の通り(ここへきてくださったみなさん、同じだと思いますが)


Wordで普通に入力している時は、何でもないんだけれど、テキストボックス等を置いて、その中へ文字列を入力すると、かな入力後のスペースキー押し下げで変換先候補の文字が白くなって消える・・・みたいな問題が発生。Enterキーを押して確定するまで変換先の文字列候補は見えなくなる。

困った・・・。

確か、バージョンアップされたIMEを使わない設定があったはず・・・と思い返し、設定を次のように変更。

① スタートボタン ⇨ 設定
② 左ペインの「時刻と言語」をクリック
③ 右ペインの「言語と地域」をクリック
④ 「日本語」項目の右側にある「・・・」をクリック
⑤ 下へスクロールして、「Microsoft IME」の項目の右側にある「・・・」をクリック
⑥ 表示されるサブメニューで「キーボードオプション」を選択(クリック)
⑦ いちばん上の「全般」をクリック
⑧ 下へスクロールして「互換性」の「以前のバージョンのMicrosoft IMEを使う」を「ON」に変更

デフォルト設定は「オフ」


機能上、そりゃデフォルト設定は「オフ」でなきゃいけませんね・・・。これがデフォルト「オン」なら最新版は永遠に使えません。

「以前のバージョンのMicrosoft IMEを使う」を「オン」に変更


⑨ 「オン」に変更した瞬間、次のメッセージが表示されるので、「OK」をクリック。


⑩ 設定の画面右上の閉じるボタンをクリック。

スペースキー押し下げで、変換候補が表示される以前の設定に戻ったことを確認!

3.まとめ

(1)IMEの不具合を感じたら、IMEの設定を安定動作した以前のバージョンに戻す。
(2)Win11の更新情報をこまめにチェック。
(3)不具合が解消されたらIMEの「互換性」の設定を元に戻す。

4.お願いとお断り

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

フローティングパネル

浮かんだままがいいときもあったりして!

【もくじ】

1.フローティングパネル
2.ドッキングさせたくない
3.メモリが解放できない
4.メモリが解放できた(その1)
5.メモリが解放できた(その2)
6.まとめ
7.お願いとお断り
8.【追記】

1.フローティングパネル

手書き答案の採点プログラムで、常にフォーム上に浮かべたまま、自由にその位置を変更可能な、作業補助用のGUI(フローティングパネル)が欲しくなった。

前回、縦書き答案の採点用に作成した横スクロールのフローティングGUIはControlBarの上にToolBarを置いて、その上にToolButtonやBevelを並べたけれど、今回はその名の通り、ベースとなるVCLはTPanelを選択。この上に効率よく採点作業を行うために最低限必要なButton他を置いて、ユーザーの視線やマウスの移動量を極めて限られた範囲に限定。操作に払う注意とストレスをできるだけ軽減して、より快適な作業環境を実現したい・・・。

そう考えて作成したGUIがコレ。

搭載した機能は、ほんとに必要最低限

画面をタッチして操作することも考え、各Buttonの高さは44ピクセルに設定。
これを設問毎に、答案画像から切り抜いた画像を並べて表示したTImageのとなりに表示する。

フローティング状態だとTPanelの上に「閉じる」ボタンのあるタイトルバー?(キャプションバー?)が表示される。この部分をクリックして、そのままドラッグすれば、TPanelをForm上の任意の位置へ移動できる。ただし、Formにドッキング(FormのDockSiteプロパティをTrueに設定)した瞬間、タイトルバー?はかき消されたかのように消えてしまう・・・

Formにドッキングしない状態ではPanelの上部にタイトルバーのようなものが表示される


以前作った(縦書き答案の採点を効率よく行うために左右方向のスクロールを行う)ドッキング・コントロールは、ドラッグ&ドロップでFormにドッキングするように設計。必要であれば、Bevelをクリックしてそのままドラッグすることでドッキングを解除。任意の位置へ移動できる仕様とした。そんなに頻繁に位置を変更するようなモノでもなかったし。

ところが、今回はFormにドッキングさせると、いろいろ不都合が起きることが判明。

例えば、解答欄の大きさ(特に幅)に合わせて、この入力補助Toolの位置を頻繁に変えるような場合、クリックしてそのままドラッグできる「タイトルバー(キャプションバー)」がないと極めて不便。Formにドッキングした瞬間にこれが消えてしまうと、掴みどころがなくてほんとうに困るのだ。

Formにドッキングして、掴みどころがなくなった状態(フローティングしてないパネル)


それから、Formへドッキングしている状態から、引きはがしてフローティング状態になるときの挙動がとにかく急で! うまく文字に表現できないのだけれど、感覚的には「うわっ」て感じ。GUIをクリックした瞬間に、「びっくりして飛び起きる」イメージでフローティングするのだ。

(数学風?に言えば、GUI上のクリックした位置が、GUIの左上の座標原点(0,0)の位置になるように、Button等のコントロールを載せているPanelの左上隅が瞬時にクリックした位置へ移動する)

また、最初の解答欄を表示している場合、1つ前に戻る「◀」ボタンはEnabledプロパティをFalseに設定し、クリックできないようにしてあるが、この Enabled:= False 状態のボタンをクリックすると上で述べたGUIが「びっくりして飛び起きる」ように瞬間移動する現象がおきてしまう。

さらに(原因はわからないけれど)、縦書き答案の採点時にFormにドッキングさせると、解答欄の表示が部分的にずれて二重に表示されてしまう(ドッキングさせなければ、この現象は起こらない)。

あれや、これやで、どぉーにもドッキング時の挙動が気に入らない。

じゃあ、
ドッキングさせなければいいじゃん!

・・・

そのとおり・・・

そのとおり、なんだけど・・・

そうすると・・・ コレが ・・・

フローティング状態で位置を変更するたびに、メモリーリークが・・・

コレが・・・ どぉーしても 消えなくて・・・

T_T

2.ドッキングさせたくない

メモリーリークの原因はわかってる。フローティングさせたTPanelをドラッグし始めた時に発生するStartDock手続きでCreateしているTToolDockObjectだ。

procedure TFormXXX.PanelXStartDock(Sender: TObject;
  var DragObject: TDragDockObject);
begin
  DragObject:= TToolDockObject.Create(Sender as TPanel);
end;

実は、これがなくても、フローティング動作は出来る。出来るんだけど、見た目に問題があって、高解像度画面で表示倍率を200%拡大のように設定している場合、ドラッグ(=移動)中は灰色の枠だけのフローティングパネルのゴースト?が現れる。(画面のハードコピーがうまくとれなかったので)ゴースト?は、次の図のような感じ。

ドラッグ時に現れるフローティングパネルのゴースト?
(これは練習用に作ったプログラムの実行時画面)


そして、このゴーストが目に痛い感じでちらつきながら移動する。さらに困ったことに、ドラッグ中はフローティングパネルの本体(ゴースト?でない方)は表示されない。ドラッグしてドロップした瞬間に、まるでテレポーテーションしたかのように(突如として)ドロップした位置にパネルが出現するのだ。

ただし、メモリーリークは起きない。
(なんにもCreateしていないから、起きるわけがない)

一方、メモリーリークを起こすとわかっていても、ドッキング可能なコントロールのドラッグ操作を専門的に管理するTToolDockObjectをCreateして動かすと、動きがたいへんスムーズ! 気持ちイイ☆

だから、どうしてもドッキングさせたくない!
フローティングさせたまま、使いたい!!

  DragObject:= TToolDockObject.Create(Sender as TPanel);

そうなると、この1行はどうしても削れない・・・。

ただし、裏側では「メモリーリーク」がフローティングさせたパネルをドラッグする(=位置を変える)度に、発生・・・

表面的には何事もなく、静かなんだけど。

3.メモリが解放できない

解決策はただひとつ。

ドラッグが終了した時点で、StartDock時に確保したメモリを解放すればいい。

ところが、FormのDockSiteプロパティがFalseのままだと
それが、どうにも、こうにも、難しい・・・

ちなみに次のように書いてみたんだけど、上の条件下では、これは、どうやら無効!!!のよう。

procedure TFormXXX.FormDockDrop(Sender: TObject;
  Source: TDragDockObject; X, Y: Integer);
begin
  if IsDragObject(Source) then
  begin
    Source.Free;
  end;
end;

procedure TFormXXX.FormDockOver(Sender: TObject;
  Source: TDragDockObject; X, Y: Integer; State: TDragState;
  var Accept: Boolean);
begin
  Accept:=IsDragObject(Source);
end;

OnDockDropは、「別のコントロールがコントロールにドッキングした際に発生」つまり「DockSite が True の際にのみ、発生する」ということで、そもそもFormのDockSiteプロパティは意図的にFalseに設定してあるんだから、OnDockDropイベントが起きるわけがない

OnDockOverもおんなじで、「DockSite が True の際にのみ、発生する」とのこと。100万回ドラッグしようとFormのDockSiteプロパティがFalseである限り、OnDockOverイベントも絶対に起きない。

ダメ元で、OnDragDrop手続きと、OnDragOver手続きを作成して上と同じコードを書いてみたんだけど、手続きの引数をよく見たら・・・

procedure TFormXXX.FormDragDrop(Sender, Source: TObject; X, Y: Integer);

Sourceが、TDragDockObjectじゃなくて、TObjectになってる・・・。ってコトは、こっちで受け取るためには、型キャストが必要ってコト? なのかなーって思いつつ、FormのDockSiteプロパティをFalseに設定したまま、型キャストなしで次のコードを書いてみたが、Panelをドロップしても反応がない。どうやら、この2つのイベントは、FormのDockSiteプロパティがFalseだとTToolDockObjectのドロップに対しては発生しないようだ。⇦ 間違いだったら、ごめんなさい!

procedure TFormXXX.FormDragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  if IsDragObject(Source) then
  begin
    Source.Free;
  end;
end;

procedure TFormXXX.FormDragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
begin
  Accept := IsDragObject(Source);
end;

その他、FormのDockSiteプロパティをFalseに設定したまま、フローティングさせたPanelをドッキングさせずにドロップ時にメモリを解放する方法はないかとさんざん 悩んだが、解決策が見つからない。まさに七転八倒。終いには、ナニをどういじったらそうなったのか、自分でもわからないのだけれど、フローティング状態のパネルをクリックしただけで一般保護違反のエラーが出るようになり、元に戻せなくなってしまった・・・。

フローティング部品がない状態のバックアップをとっていて、ほんとうによかった。

【これが間違いであっても、前に進むために出した、自分なりの結論】

TToolDockObjectを使いたいならOnDockDropとOnDockOverイベント側で、ドラッグ&ドロップを受け取るしかない。

4.メモリが解放できた(その1)

何かをCreateして使うプログラムを書くとき、FormCreate時に、次のようにメモリーリークがあれば検出する設定を僕は付け加えることに決めている。多数の画像を読み書きする答案処理のプログラムを書いた際に、Createしたオブジェクトの解放を書き忘れ、あとからCreateしている箇所を全部点検することになった「痛い経験」から学んだ予防的措置だ。

procedure TFormXXX.FormCreate(Sender: TObject);
begin
  //メモリーリークがあれば検出
  ReportMemoryLeaksOnShutdown:=True;
end;

メモリーリークがあった場合、実行(F9)したプログラムを終了させると(メインフォームを閉じると)、次の画面がリークを起こした回数付きで表示される。

回数なんか数えなくていいから、メモリーリークを止めてくれ!!

丸1日がんばって、(この方法じゃダメなんだ)ということがはっきりわかったところで、少し休もうと思い、ベッドに倒れて・・・ そのまま、眠ってしまったようだ。

なんにも考えない時間が数時間あって・・・

目覚めたのは日付が変わる直前。眠って疲れがとれたからか? その原因はわからないけど、目覚めた瞬間に、あることを思いついた。それは何かと言うと・・・

FormのDockSiteプロパティをTrueに設定すれば、OnDockDropとOnDockOverの2つのイベントが間違いなく? Panelのドラッグ&ドロップに反応してくれるから・・・

ドラッグしてるときだけ、FormのDockSiteプロパティをTrueに切り替えて、ドロップした瞬間の位置座標を取得し、そこへPanelをManualFloatさせて、最後にメモリを解放、DockSiteプロパティをFalseに戻せばいいんじゃない? ってこと。

いろいろ実験的に書いていて(プロパティをあちこち変更)、設定忘れがあると困るので、ドラッグ開始時、コードの中でFormのDockSiteプロパティをTrueに設定。

procedure TFormXXX.PanelXStartDock(Sender: TObject;
  var DragObject: TDragDockObject);
begin
  //これでちらつかなくなった
  DragObject:= TToolDockObject.Create(Sender as TPanel);
  //設定し忘れないための予防的措置
  if not FormXXX.DockSite then
  begin
    FormXXX.DockSite:=True;
  end;
end;

ドロップ時のOnDockDropイベントは・・・

procedure TFormXXX.FormDockDrop(Sender: TObject;
  Source: TDragDockObject; X, Y: Integer);
var
  r:TRect;
begin
  if IsDragObject(Source) then
  begin
    r.Left:=X;
    r.Top:=Y;
    r.Right:=X+PanelX.Width;
    r.Bottom:=Y+PanelX.Height;
    PanelX.ManualFloat(r);
    //解放
    Source.Free;
    if FormXXX.DockSite then
    begin
      FormXXX.DockSite:=False;
    end;
  end;
end;

FormのDockSiteプロパティはFalseに設定。

実行(F9)して、フローティングパネルをドラッグ&ドロップ。ちょっと気になったのはドロップ時のManualFloat(r)時の挙動。ドロップした場所でなく、ドロップ時にマウスポインタのカーソルがあった位置へ移動して表示されてしまう。次の画像の上がドロップ時、下がマウスの左ボタンを離した際にフローティングパネルが表示される位置。

マウスのカーソル位置を取得しているから、パネルのドロップ位置から少しずれて表示される。


ドロップ時、XにはPanelのLeftの値、YにはPanelのTopの値を取得するよう、プログラムを修正。


    r.Left:=PanelX.Left;
    r.Top:=PanelX.Top;
    r.Right:=r.Left+PanelX.Width;
    r.Bottom:=r.Top+PanelX.Height;
    PanelX.ManualFloat(r);
    //解放
    Source.Free;

これで表示位置に関する問題は解決できたが、今度は表示される際の挙動が気に入らない。ドロップ位置でPanelの画像が一瞬、最小化され(閉じるボタンだけになり)、それから全体が表示されるので、ドロップするたびにPanelが1回点滅するように見えるのだ。

それはそれで何とかするとして、いちばん何とかしなきゃいけないのはメモリーリーク。

恐る恐るプログラムを終了。

DelphiのIDE画面だけが表示され、リークの警告画面は出ない!
ようやく問題を解決できた・・・。

ただし、一難去ってまた一難。
表示方法を何とかしなきゃ・・・

5.メモリが解放できた(その2)

点滅の原因はわかっている。ドロップ時の ManualFloat(r) だ。

  DragObject:= TToolDockObject.Create(Sender as TPanel);

メモリーリークは起きるけど、上の一行だけでフローティングさせ、ドラッグ&ドロップしている時は点滅なんてしなかった。要は ManualFloat させなければいいのだ。

(かならず、解決方法はある)

そう信じて、OnStartDockイベントの手続き部分でShift+Ctrl+1、OnDockDropイベントの手続き部分でShift+Ctrl+2を実行して、Ctrl+1、Ctrl+2でそれぞれの手続きへ移動できるように設定。

2つの手続き間を行きつ戻りつしながら解決方法を考える・・・

どう考えてみても、OnDockDropイベント側でなんとかするのは無理そう・・・

TToolDockObjectをCreateして移動させてる時は、ドロップ時に点滅しなかった・・・

表示された位置も、ドロップした場所だった・・・ だから、ManualFloat は不要?

確保したメモリを最後に解放すれば、メモリーリークは起こらない・・・

『最後に解放』?

そうだ「 try ・・・ finally ・・・ end; 」があった。

プロシージャを思った通りに書き換える。

procedure TFormCollaboration.PanelXStartDock(Sender: TObject;
  var DragObject: TDragDockObject);
begin
  DragObject:= TToolDockObject.Create(Sender as TPanel);
  try
    if not FormXXX.DockSite then
    begin
      FormXXX.DockSite:=True;
      Application.ProcessMessages;  //おまじない
    end;
  finally
    DragObject.Free;  //メモリの解放
  end;
  FormXXX.DockSite:=False;
end;

実行(F9)

フローティングパネルは快適に移動し、ドロップ時の挙動もごく自然・・・

祈りながらFormをClose.

見慣れたIDEだけが現れる。

メモリーリークは 起きてない。

やっと、思っていたとおりの ・・・ プログラムになった?

・・・ってか、ちょっと待て。オレ、OnDockDrop手続きの ManualFloat 消してないぞ。

もしかして・・・ OnDockDrop手続き 呼ばれていないんじゃないか?

自分の書いたプログラムを、もう一度、よく読んで考える・・・

  DragObject:= TToolDockObject.Create(Sender as TPanel);
  try
    if not FormXXX.DockSite then
    begin
      FormXXX.DockSite:=True;
      Application.ProcessMessages;  //おまじない
    end;
  finally
    DragObject.Free;  //メモリの解放
  end;

DragObject を、最後に Free してるから、ドロップ時にはもう TToolDockObject は消えてる・・・。
消えてるんだから・・・ もう、『ない』んだから、呼ばれてないんじゃなくて・・・

OnDockDropイベントは起こらない!

・・・ってコトは、もしかして

DragObject:= TToolDockObject.Create(Sender as TPanel);
try
  //何もしない
Finally
  DragObject.Free;
end;  

Createして、Freeするだけで

よかったの?

6.まとめ

今回の状況を何かに例えるならそれはナニ?って、生成AIに訊ねたところ、

 “Still waters run deep”

そう答えてくれました。深く、静かに、感動。

7.お願いとお断り

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

8.【追記】

  finally
    //DragObject.Free;  //メモリの解放のみ->インスタンス(オブジェクトの実体)はあるので参照可能
    FreeAndNil(DragObject);  //インスタンスも消える!
  end;

.Freeでなく、FreeAndNilすると、何もしなかったコトと同じになってしまいます。
注意してください!!

デバッグ出力: clientcore\windows\advcore\ctf\shellhandwriting\client\handwritingclient.cpp(287)\Msctf.dll!77A3FC28: (caller: 77A345FD) LogHr(3) tid(988) 8007007E 指定されたモジュールが見つかりません。

Delphiでプログラムを書いて実行(F9)すると、必ずこれが・・・。

さんざん、悩みましたが、
ようやく原因と解決方法がわかりました。

【もくじ】

1.再発
2.Copilotさんが言うには・・・
3.ViVeToolをダウンロードして実行
4.まとめ
5.お願いとお断り

1.再発

以前、こちらの記事で「問題は解決した」と書きましたが、OSからクリーン・インストールし直したPCで同じ現象が・・・いつの間にか、また発生するようになり・・・


この内部エラーだけは再現してくれなくても、全然イイんだけど。ほんとに・・・ T_T

だって、解決策の糸口もつかめないから。

それに、なんで再発すんの???

Msctf.dllをキーワードにGoogle先生にお伺いをたてても全然埒があかないし・・・

その前の handwritingclient.cpp ? 手書きクライアント? MyPCにそんなアプリあった?

アルワケナイ。

ってか、拡張子が cpp って、コレ、C++のファイルでしょ?

オレ、C++ いじった記憶ないんだけど・・・

確かに一昨年、手書き文字認識にハマって、Pythonのいろんなライブラリを扱ったけど、でもあれはSDカードや外付けのSSDにインストールしたWinPythonに入れたはずで・・・。

しかも、MyPCは23年12月にOSからクリーン・インストール。
溜まってた余計なモノはきれいさっぱり、消えたはず・・・。

もう、何がどうして、こうなるのか・・・

自分にはどうすることもできないと、ほとんどあきらめかけていたのですが、ふと、Win11の23H2から使えるようになった生成AI、Copilot(プレビュー)が、いつも、とても良いアドバイスをしてくれることを思い出し、・・・ひょっとしたら・・・問題解決のヒントを教えてくれるかも・・・と、突然、思えてきて。

こんなイイコトに、なんで今まで気がつかなかったんだろー。

2.Copilotさんが言うには・・・

さっそく、訊いてみました!(同じ内容をGoogle先生に訊ねると画像が9枚紹介されて、おしまい)
キーワードはそのものズバリ。

clientcore\windows\advcore\ctfshellhandwriting\client\handwritingclient.cpp(296)がエラーになる理由は?

単刀直入に、いちばん、知りたいコトを入力して、Enter。

Copilotさんは、しばらく、考え(検索し)ているようでしたが、やがて驚くべき返答が☆

(返答はそのまま載せたらいけないらしいので要約です)

『エラーは、Windowsの新しい「シェル手書き」機能(MTestAbSh1, 41799415)のバグに関連している可能性あり』

で、

『ViVeToolを使えば当該機能が有効になっているか・どうかを確認できる』

とのこと。まぁ 状況から考えて、MyPCでは絶対!有効になってると思うし、それより・・・。

ViVeTool(ビーブツール)ってナンだか、さっぱりわかんないけど、

Copilotさん、すごすぎます!

さらに、「詳細情報」として stackoverflow.com の記事へのリンクを示してくれました。
こちらがその記事です。

MSCTF.dll complains ‘An assertion failure has occurred’

https://stackoverflow.com/questions/77556770/msctf-dll-complains-an-assertion-failure-has-occurred

以下、リンク先記事の要約です。

(1)原因は、現在実験段階にある新しい「シェル手書き」機能(MTestAbSh1、41799415)のバグ。
(2)ViVeToolをダウンロードして、機能が有効になっているかどうかを確認。
(3)確認コマンドは、「vivetool /query /id:41799415」
(4)機能が有効であった場合に無効化するコマンドは、「vivetool /disable /id:41799415」
(5)機能を無効に設定したら、システムを再起動する。

完璧すぎ。これこそ知りたかった究極の解決策。問題の解答そのものです!!

リンク先記事の質問の回答者様に心から感謝。

Copilotさん、ありがとう!

3.ViVeToolをダウンロードして実行

【ダウンロード】

さらにCopilotさんは「ViVeToolとは何か?」についても教えてくれました☆
以下、その要約です。

(1)ViVeToolは、Windowsの隠し機能を有効化・無効化できるサードパーティ製のツール。
(2)Windows Insiderでも利用できない機能を有効にしたいときに使用する。
(3)ViVeToolを使えば、Windows 11の新機能(隠し機能)を公開前に使用できる。

そぉなんだー♪

私自身は、別に公開前でなくてもかまいませんから、安定した機能を安心・安全に使いたいだけですが、すごいToolがあるんですね。ほんと、びっくり!!です。

で、ViVeToolのダウンロード先と、使い方の詳細は・・・(こちらも要約)

(1)GitHub からzipファイルをダウンロード。
(2)ダウンロードしたzipファイルを展開。
(3)コマンドプロンプトを起動、ViVeToolのフォルダーに移動
   ↑
  管理者として実行する(じゃないと有効状態の確認はできても、設定の変更ができない)

(4)ViVeToolコマンドを実行

もちろん実行するコマンドは、機能が有効であるか確認するための「vivetool /query /id:41799415」と、有効であった場合にそれを無効化する「vivetool /disable /id:41799415」です。

ViVeToolのダウンロードは、こちら

ViVeTool v0.3.3

https://github.com/thebookisclosed/ViVe/releases/tag/v0.3.3

【実行】

手順通りにViVeToolをダウンロード&展開(解凍)して、コマンドプロンプトを管理者権限で実行(真実はちょっと違って、最初は普通にコマンドプロンプトを呼び出して、問題の機能が有効であることを確認。続けて無効化しようとしたら、エラーに。T_T )

なので再度、コマンドプロンプトを管理者権限で起動して、上記コマンドを実行し、問題の機能の無効化に成功。

以下、問題の機能の状態を確認&無効化作業時、MyPCのコマンドプロンプトに表示された内容です。
※ 赤字は、状態の確認と機能の無効化のコマンド

【コマンドプロンプト】※ 管理者として実行します!

Microsoft Windows [Version 10.0.22631.3007]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\System32>cd \

C:\>cd C:\Users\XXX\Downloads\ViVeTool-v0.3.3

C:\Users\XXX\Downloads\ViVeTool-v0.3.3>vivetool /query /id:41799415
ViVeTool v0.3.3 - Windows feature configuration tool

[41799415]
Priority        : Service (4)
State           : Enabled (2)
Type            : Experiment (1)

C:\Users\XXX\Downloads\ViVeTool-v0.3.3>vivetool /disable /id:41799415
ViVeTool v0.3.3 - Windows feature configuration tool

Successfully set feature configuration(s)

C:\Users\XXX\Downloads\ViVeTool-v0.3.3>

で、Successfullyの表示を信じ、祈るような気持ちでシステムを再起動。
これでダメなら、イチから出直しです。

・・・

Delphiを起動。現在、開発中のプロジェクトを読み込んで、実行(F9)。

【結果】

スレッドの開始 : スレッド ID: 30832. プロセス XXX.exe (32324)
プロセスの開始: C:\Users\XXX\Win32\Release\XXX.exe. ベースアドレス: $004E0000. プロセス XXX.exe (32324)
モジュールの読み込み: XXX.exe. デバッグ情報あり. ベースアドレス: $004E0000. プロセス XXX.exe (32324)
モジュールの読み込み: ntdll.dll. デバッグ情報なし. ベースアドレス: $77890000. プロセス XXX.exe (32324)
モジュールの読み込み: KERNEL32.dll. デバッグ情報なし. ベースアドレス: $75990000. プロセス XXX.exe (32324)
モジュールの読み込み: KERNELBASE.dll. デバッグ情報なし. ベースアドレス: $77560000. プロセス XXX.exe (32324)
モジュールの読み込み: apphelp.dll. デバッグ情報なし. ベースアドレス: $74550000. プロセス XXX.exe (32324)
モジュールの読み込み: COMDLG32.dll. デバッグ情報なし. ベースアドレス: $76290000. プロセス XXX.exe (32324)
スレッドの開始 : スレッド ID: 15468. プロセス XXX.exe (32324)
モジュールの読み込み: msvcp_win.dll. デバッグ情報なし. ベースアドレス: $75D70000. プロセス XXX.exe (32324)
スレッドの開始 : スレッド ID: 21404. プロセス XXX.exe (32324)
モジュールの読み込み: ucrtbase.dll. デバッグ情報なし. ベースアドレス: $76BD0000. プロセス XXX.exe (32324)
スレッドの開始 : スレッド ID: 30816. プロセス XXX.exe (32324)
モジュールの読み込み: combase.dll. デバッグ情報なし. ベースアドレス: $75A80000. プロセス XXX.exe (32324)
モジュールの読み込み: WINSPOOL.DRV. デバッグ情報なし. ベースアドレス: $70E00000. プロセス XXX.exe (32324)
モジュールの読み込み: WINMM.dll. デバッグ情報なし. ベースアドレス: $74600000. プロセス XXX.exe (32324)
モジュールの読み込み: d3d9.dll. デバッグ情報なし. ベースアドレス: $66C20000. プロセス XXX.exe (32324)
モジュールの読み込み: RPCRT4.dll. デバッグ情報なし. ベースアドレス: $76E70000. プロセス XXX.exe (32324)
モジュールの読み込み: msvcrt.dll. デバッグ情報なし. ベースアドレス: $75760000. プロセス XXX.exe (32324)
モジュールの読み込み: SECHOST.dll. デバッグ情報なし. ベースアドレス: $77250000. プロセス XXX.exe (32324)
モジュールの読み込み: SHCORE.dll. デバッグ情報なし. ベースアドレス: $77330000. プロセス XXX.exe (32324)
モジュールの読み込み: SHCORE.dll. デバッグ情報なし. ベースアドレス: $01BF0000. プロセス XXX.exe (32324)
モジュールの読み込み: bcrypt.dll. デバッグ情報なし. ベースアドレス: $76CF0000. プロセス XXX.exe (32324)
モジュールの読み込み解除 : SHCORE.dll. プロセス XXX.exe (32324)
モジュールの読み込み: USER32.dll. デバッグ情報なし. ベースアドレス: $76F30000. プロセス XXX.exe (32324)
モジュールの読み込み: GDI32.dll. デバッグ情報なし. ベースアドレス: $755C0000. プロセス XXX.exe (32324)
モジュールの読み込み: win32u.dll. デバッグ情報なし. ベースアドレス: $77400000. プロセス XXX.exe (32324)
モジュールの読み込み: win32u.dll. デバッグ情報なし. ベースアドレス: $01740000. プロセス XXX.exe (32324)
モジュールの読み込み解除 : win32u.dll. プロセス XXX.exe (32324)
モジュールの読み込み: SHLWAPI.dll. デバッグ情報なし. ベースアドレス: $772E0000. プロセス XXX.exe (32324)
モジュールの読み込み: gdi32full.dll. デバッグ情報なし. ベースアドレス: $758A0000. プロセス XXX.exe (32324)
モジュールの読み込み: SHELL32.dll. デバッグ情報なし. ベースアドレス: $76350000. プロセス XXX.exe (32324)
モジュールの読み込み: dwmapi.dll. デバッグ情報なし. ベースアドレス: $71030000. プロセス XXX.exe (32324)
モジュールの読み込み: COMCTL32.dll. デバッグ情報なし. ベースアドレス: $74070000. プロセス XXX.exe (32324)
モジュールの読み込み: OLEAUT32.dll. デバッグ情報なし. ベースアドレス: $754F0000. プロセス XXX.exe (32324)
モジュールの読み込み: ADVAPI32.dll. デバッグ情報なし. ベースアドレス: $755F0000. プロセス XXX.exe (32324)
モジュールの読み込み: ole32.dll. デバッグ情報なし. ベースアドレス: $76D10000. プロセス XXX.exe (32324)
モジュールの読み込み: VERSION.dll. デバッグ情報なし. ベースアドレス: $74B00000. プロセス XXX.exe (32324)
モジュールの読み込み: WTSAPI32.dll. デバッグ情報なし. ベースアドレス: $74AD0000. プロセス XXX.exe (32324)
モジュールの読み込み: dxcore.dll. デバッグ情報なし. ベースアドレス: $690D0000. プロセス XXX.exe (32324)
モジュールの読み込み: IMM32.dll. デバッグ情報なし. ベースアドレス: $75590000. プロセス XXX.exe (32324)
モジュールの読み込み: MSCTF.dll. デバッグ情報なし. ベースアドレス: $77150000. プロセス XXX.exe (32324)
モジュールの読み込み: UxTheme.dll. デバッグ情報なし. ベースアドレス: $73FF0000. プロセス XXX.exe (32324)
モジュールの読み込み: AppCore.dll. デバッグ情報なし. ベースアドレス: $748E0000. プロセス XXX.exe (32324)
モジュールの読み込み: bcryptPrimitives.dll. デバッグ情報なし. ベースアドレス: $770E0000. プロセス XXX.exe (32324)
モジュールの読み込み: WINSTA.dll. デバッグ情報なし. ベースアドレス: $747D0000. プロセス XXX.exe (32324)
モジュールの読み込み: gdiplus.dll. デバッグ情報なし. ベースアドレス: $6ED10000. プロセス XXX.exe (32324)
スレッドの開始 : スレッド ID: 32320. プロセス XXX.exe (32324)
モジュールの読み込み: TextShaping.dll. デバッグ情報なし. ベースアドレス: $6D760000. プロセス XXX.exe (32324)
モジュールの読み込み: CLBCatQ.DLL. デバッグ情報なし. ベースアドレス: $75670000. プロセス XXX.exe (32324)
モジュールの読み込み: MMDevAPI.DLL. デバッグ情報なし. ベースアドレス: $5BA00000. プロセス XXX.exe (32324)
モジュールの読み込み: DEVOBJ.dll. デバッグ情報なし. ベースアドレス: $74690000. プロセス XXX.exe (32324)
モジュールの読み込み: CFGMGR32.dll. デバッグ情報なし. ベースアドレス: $06450000. プロセス XXX.exe (32324)
モジュールの読み込み: CFGMGR32.dll. デバッグ情報なし. ベースアドレス: $06490000. プロセス XXX.exe (32324)
モジュールの読み込み解除 : CFGMGR32.dll. プロセス XXX.exe (32324)
モジュールの読み込み: CFGMGR32.dll. デバッグ情報なし. ベースアドレス: $74650000. プロセス XXX.exe (32324)
モジュールの読み込み解除 : CFGMGR32.dll. プロセス XXX.exe (32324)
モジュールの読み込み: AudioSes.DLL. デバッグ情報なし. ベースアドレス: $5B870000. プロセス XXX.exe (32324)
スレッドの開始 : スレッド ID: 11740. プロセス XXX.exe (32324)
モジュールの読み込み: python39.dll. デバッグ情報なし. ベースアドレス: $79C60000. プロセス XXX.exe (32324)
モジュールの読み込み: WS2_32.dll. デバッグ情報なし. ベースアドレス: $76230000. プロセス XXX.exe (32324)
モジュールの読み込み: VCRUNTIME140.dll. デバッグ情報なし. ベースアドレス: $79C40000. プロセス XXX.exe (32324)
モジュールの読み込み: CRYPTSP.dll. デバッグ情報なし. ベースアドレス: $71F20000. プロセス XXX.exe (32324)
モジュールの読み込み: RSAENH.dll. デバッグ情報なし. ベースアドレス: $71EC0000. プロセス XXX.exe (32324)
モジュールの読み込み: CRYPTBASE.dll. デバッグ情報なし. ベースアドレス: $74540000. プロセス XXX.exe (32324)
モジュールの読み込み: TextInputFramework.dll. デバッグ情報なし. ベースアドレス: $6E6A0000. プロセス XXX.exe (32324)
モジュールの読み込み: CoreMessaging.dll. デバッグ情報なし. ベースアドレス: $63E10000. プロセス XXX.exe (32324)
モジュールの読み込み: CoreUIComponents.dll. デバッグ情報なし. ベースアドレス: $63B70000. プロセス XXX.exe (32324)
モジュールの読み込み: WinTypes.dll. デバッグ情報なし. ベースアドレス: $74D30000. プロセス XXX.exe (32324)
モジュールの読み込み: OLEACC.dll. デバッグ情報なし. ベースアドレス: $6ECB0000. プロセス XXX.exe (32324)
モジュールの読み込み: tophdll.dll. デバッグ情報なし. ベースアドレス: $5BE70000. プロセス XXX.exe (32324)
スレッドの開始 : スレッド ID: 14212. プロセス XXX.exe (32324)
スレッドの開始 : スレッド ID: 31268. プロセス XXX.exe (32324)

エラーは完全に消えました!

でも、長かったなー。
解決できる気は『まったくしなかった』し、
解決できたのは、ほんとに偶然だったけど・・・

もしかしたら、同じ問題で悩んでいる人が、この世界のどこかに・・・

解決策がここにあります。
届け、このメッセージ!

そう思って、これを書いています。

4.まとめ

(1)Windows11には、隠し機能があって、しかもそれは有効な状態だったりする。
(2)GitHubから入手できるViVeToolを使えば、その有効/無効を確認できる。
(3)当該機能が有効である場合は、同ツールで無効化できる。
(4)有効状態を無効化した場合は、システムの再起動が必要。

Win11の隠し機能と、Delphiのコンパイラとの関係は謎のままですが・・・。Msctf.dllを読み込む設定だと、そこで問題が起きるのかな? みたいな・・・

5.お願いとお断り

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

ながーい手続きの途中で変数を宣言したい!

これまでながーい手続きを書いていて、変数を宣言する必要がある時はいつもShift+Ctrl+1(数字は任意)でその行へすぐに戻れるように「しおり」を設定してから、手続きの最初までスクロール、Var宣言部に変数を追加、Ctrl+1(数字は任意)で編集中の行に戻ることを繰り返してきました。

いちいち、ここへ戻るのはめんどくさい!


きっと、Var宣言部に戻らなくても、その場所で変数を宣言できるショートカットキーがあるんだろうーなーって、ずうぅぅぅぅぅぅっと思っていたんだけれど、Delphi 12.0 をインストールした際に、今まで入れてなかったHelpもインストールしたので、入れた以上は使わなきゃ!と、

メニューのヘルプ ⇨ RAD Studio ヘルプ ⇨ メインヘルプ ⇨ 検索キーワード「ショートカットキー」で検索 ⇨ メインショートカット ⇨ デフォルトのキーボードショートカットとたどって(なが!)、大量にあるショートカットキーの項目を一つずつ上から下へ丹念に探します。すると・・・

あった!

Shift+Ctrl+V 
 [リファクタリング|変数の宣言...](Delphi のみ) 
 [新規変数の宣言]ウィンドウが開き、手続き内にローカル変数を宣言することができます。(上記ヘルプより引用)

これです。コレ!
やっぱり、ありました☆

【やってみた】

ある既存の手続きのいちばん最後にMessageBox関数でメッセージを表示するコードを追加します。この関数で使用する文字列型変数が strMsg ですが、まだ変数として宣言していないので、書いた途端に次のように注意の表示?が出ます。

文字列型変数として宣言したいstrMsgは、書いた途端に赤い波線と!マークが付く


構造ペインには・・・


もちろん、実行(F9)できません!

未定義の識別子エラーに加えて、「致命的エラー」という文言も登場


コンパイル結果の案内も!

Delphiのこういうところが大好き!
ここを確実に乗り越えて行けば、エラーの発生しないプログラムになる☆


とりあえず、strMsgを書いた行にカーソル(キャレット?)を置いて、Shift+Ctrl+V してみましたが・・・

(変化なし。なんにも起きない。なんでかなー?)

再挑戦。今度は str と Msg の間をクリックして、そこにカーソル(キャレット?)を表示しておいて(str|Msgみたいな)、んで、覚えたばかりの Shift+Ctrl+V を実行。すると・・・ (ここは strMsg を選択状態にしておいて Shift+Ctrl+V しても同じ)。

(なんか出た!!)

望み通り種類は「文字列」だから、迷わず「OK」をクリック
(望み通りでない場合は、ここで型を指定)


で、「OK」をクリックしたら、未定義の識別子を示す赤い波線が消えた!

Var宣言部へ行って見ると・・・

文字列型の変数として宣言されていました☆


元に戻って・・・


実行!

【まとめ】

新規変数の宣言は、その場で「Shift+Ctrl+V」

こんなイイ方法があったんだ☆

今度から、毎回使おう!!!

マークシートリーダーを教科「情報」用に設定

マークシートリーダー関連の第4回は、ゼロ始まりの選択肢への対応 です。

マークシートの選択肢には「1」始まりと「0」始まりが存在します。教科「情報」の試験で使用されるマークシートは「0」始まりが標準?のようです。そこで自作のマークシートリーダー及びマークシートを、それぞれ選択肢「ゼロ(0)」始まりにも対応できるよう改良しました。

選択肢「ゼロ(0)」始まりに対応したマークシートリーダーはこちらからダウンロードできます。

Version 1.1.1は、選択肢のゼロ始まりに対応していますが、マークシートの種類の表示に誤りがあります。
Version 1.1.2で、マークシートの種類の表示の誤りを修正しました(数学->数学/情報)

追記(20240929)

当Blogで紹介してきた自作のデジタル採点プログラムを一つにまとめました。次のリンク先にその紹介とダウンロードリンクがあります。

筆者が作成したマークシートリーダーの詳しい使い方を紹介しています。

重要 MS_Reader の使用はあくまでも自己責任でお願いします。動作保証は一切ありません!


マークシートの読み取り速度をPython環境を組み込んで高速化します。

Python環境を組み込むとマークシートリーダーをさらに高速化できます。
重要 組み込みPython環境のご使用もあくまでも自己責任でお願いします。動作保証は一切ありません!

【もくじ】

1.教科「情報」用マークシートのダウンロード
2.選択肢ゼロ始まりの設定方法
3.重要!テンプレート作成時の注意事項
4.お願いとお断り

1.教科「情報」用マークシートのダウンロード

教科「情報」用マークシートはこちらからダウンロードできます。

追記(20240929)

上記リンク先でダウンロードできる「デジタル採点 All in One !」は、ここからダウンロードできる教科「情報」用マークシートも同梱しています。「デジタル採点 All in One !」には、マークシートリーダーの他、マークの読み取りを高速化するPython環境、手書き答案の採点プログラム、受験者に採点結果を通知する個票及び成績一覧表の作成プログラム、実際の採点現場で要請に応じて作成した各種のマークシート等を同梱しています。何の保証もサポートもありませんし、「All 自己責任でお願いします」という制約はありますが、すべて無料でお使いいただけます。


選択肢はゼロ始まりで、1設問あたり16個まで設定可能です。

ゼロ始まりで15まで、選択肢数16の教科「情報」用マークシート(Version 1)。
ダウンロードできるのは、これを改良した Version 2 です。

2.選択肢ゼロ始まりの設定方法

MS_Readerを起動すると、画面上部のパラメータ設定入力用コントロールの左側に、選択肢の開始番号を指定するComboBoxがあります。教科「情報」用のマークシートを読み取る場合は、ここに「0」を指定(∨マークをクリックして選択)してください。

教科「情報」用である場合、選択肢は「0」始まりを指定

3.重要!テンプレート作成時の注意事項

マークシート情報を記録するテンプレートを作成する際には、選択肢の枠幅を間違って設定しないよう、十分注意してください。指定は、次の図のように行ってください。

列枠そのものではなく、設問番号部分は含めずに、選択肢部分のみを指定してください。
指定する際は、左上の「⊥」マークから、右下の「T」マークまでの範囲をドラッグします。


この枠の指定が何を意味するかと言うと、マーク読み取りの際、プログラムはまず、上で指定された範囲の「高さ」を行数で割って1行分を切り出し、次に「幅」を選択肢数で割ってマークを1つずつ切り出します。次の図のようなイメージです。


ですので、マークシートを作成する際は、このことを考慮して、各マークの幅と余白が均等になるように作成します。逆に言えば、各マークを上手く切り出せるように選択肢の枠幅を指定する必要があるわけです。

Wordで作成したマークシートの設問01欄の選択肢番号部分を範囲選択すると、
各マークの幅と余白が均等になっているか、どうか、確認することができます。


プログラムは、この切り出したマークの塗りつぶし面積を計算して、それが最大であるものを「マークあり」と判定しています。

各マーク間の余白も重要です。広すぎても、狭すぎても、判定に影響します。広すぎると計算が遅くなり、塗りつぶし面積の閾値の設定が難しくなります。逆に、狭すぎると大きめにマークされた場合、隣までマークしたことになり、複数マークの判定が出やすくなります。選択肢数が多くなると、どうしても各マーク間の余白は狭くなります。大きめにマークされた場合のことを考え、故意にマーク位置を変えて、どれくらいズレに強いか、試行してみました。

ちゃんと読めています!


上の図にある程度のズレであれば、プログラムは正しくマークを読み取れるようです。

もう少し、ズレ幅を大きくしてみました。

「99」は「複数マークあり」を意味します。


さすがに、ここまでズレると判定に影響します。しかし、完全な誤判定でなく、「複数マークあり」という判定なので、読み取り後のチェックで採点者の目視による判断が求められることになります(私はこの結果を見て、安心して使えると判断しました)。

マークシートの作り方については、MS_Readerの操作・設定マニュアルにも詳しい解説があります。オリジナルのマークシートを作られる際は、どうかそちらもご参照いただけますようお願い致します。

マニュアルはPDFです!

4.お願いとお断り

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

マークシートリーダーを数学用に設定

マークシートリーダー第3弾!
今回は MS_Readerを数学の試験の採点用途に使用するための設定セット を紹介します。
数学用途での採点は、その解答方法から複数選択肢を抱き合わせて採点する設定が必須となります。
MS_Reader.exe単体では、その実現が困難であるため、マークシートの読み取り結果を出力するエクセル・ブックをこの用途向きに専用プログラムで最適化して利用します。
なお、専用エクセル・ブックは大問6個までの数学採点用途に対応しています。

追記(20240929)

当Blogで紹介してきた自作のデジタル採点プログラムを一つにまとめました。次のリンク先にその紹介とダウンロードリンクがあります。

【もくじ】

1.数学用設定セットのダウンロード
2.数学用設定セットの使い方
3.お願いとお断り

1.数学用設定セットのダウンロード

数学用設定セットは、数学採点用のExcel Bookのマクロ有効テンプレート、このテンプレートから生成したExcel Bookに対して採点設定を書き込む専用プログラム、数学用マークシートのテンプレート3種類(1枚につき大問3個、各問について25設問ア行~カ行~サ行~タ行~ナ行まで設定可能、選択肢は-、±、0~9、a~dの16選択肢 ⇨ これを2枚、大問6まで使用可能)、A3サイズのマークシート画像をA4横サイズの画像2枚に分割・指定フォルダに保存する専用プログラム、練習用フォルダ(2つ)と練習用画像、各プログラムの使い方のPDFファイル等から出来ています。

【数学用設定セットの内容】

数学用設定セットの内容


数学用設定セットは、こちからからダウンロードしてください。なお、ご使用にあたっては同梱の「必ずお読みください_Math.txt」にあります免責事項への同意が必須となります。

追記(20240929)

上記リンク先で入手できる「デジタル採点All_in_One!」は、ここでダウンロードできる数学設定セットのすべてのコンテンツと、細かなバグを修正する等のバージョンアップを行ったMS_Reader.exe 1.1.4を同梱しています。また、マークの読み取りを高速化するPython環境もダウンロードZIPファイルの中に含まれています。特別の理由がない限り、上記リンク先で入手できる「デジタル採点All_in_One!」をご利用ください。

2.数学用設定セットの使い方

プログラムの使用順序は、次の通りです。

  1. MS_Reader.exeのバージョンを確認。必要であれば同梱のVersion 1.1.2を古いMS_Reader.exeに上書きします。
  2. ImageCutter.exe、MathFunctionCreator_2024.exeをMS_Reader.exeがあるフォルダにコピーします。
  3. 同梱の数学用採点シート_2024.xltm(マクロ有効テンプレートファイル)をダブルクリックして生成されるマクロが無効な「.xlsx」ファイルに適切な名前を付け、その拡張子を「.xlsm(マクロ有効Book)」に変えて、MS_Reader.exeがあるフォルダ内のeFileフォルダ内に保存してください。同様に「マークシートのテンプレート」フォルダ内のファイルも全てWordのテンプレートです。ダブルクリックすると新しいWord文書が生成されますので、適切な場所に適切な名前を付けて保存し、ご活用ください。
  4. 保存したExcel Bookに試験の受験者のクラス、番号、氏名、ふりがな、性別、試験の正解となる選択肢の番号、配点、観点別評価を入力し、上書き保存します。
  5. MathFunctionCreator_2024.exeを起動し、4.のExcel Bookに対して、抱き合わせ採点の設定を追加します。⇨ 抱き合わせ採点の設定方法は、同梱の「MathFunctionCreator_2024の使い方.pdf」を参照してください。
  6. 同梱のマークシートのテンプレートから作成したマークシートを印刷、試験(試行)を実施(欠席者がいる場合は未使用のマークシートを適切な位置に挿入する等の事後処理を行い)、スキャナーでスキャンして画像化、MS_Reader.exeがあるフォルダ内のScanDataフォルダ内に適切な名前を付けたフォルダを作成し、そこに保存します。
  7. 使用したマークシートがA3サイズである場合は、ImageCutter.exeを起動してマークシート画像をMS_Readerで読み取り可能なA4横サイズの画像2枚に分割します。分割した画像は、MS_Reader.exeがあるフォルダ内のProcDataフォルダ内に自動的に作成されるフォルダに保存します。⇨ A3判画像の分割方法は、同梱の「ImageCutterの使い方.pdf」を参照してください。
  8. MS_Readerでマークシートを読み取り、読み取り結果のチェックを実行後、結果をCSVファイル、及び5.で抱き合わせ採点を設定済みのExcel Bookに書き込みます。
  9. 読み取り結果を書き込んだExcel Bookを開き、成績一覧表及び採点結果通知用の成績個票を印刷します。
  10. 試験の受験者にマークシート及び成績個票を返却し、マークの読み取り結果と成績を本人が確認(マークシートの画像があるので不正は絶対に不可能であることを受験者に周知した上で、マークシートは返却してもOKですが、必要であれば確認作業後、回収して保管。完全に不要となった時点でシュレッダーにかけて処分してください)。採点結果の訂正が必要な場合は、MS_Readerを再起動・マークシート情報を記録したテンプレートを選択・採点対象のマークシートがあるフォルダを選択すると8.で保存したCSVのデータのデータが表示されるので、必要な個所を訂正し、Excel Bookへ再出力、必要な成績個票を印刷して返却し、再確認作業を行ってください。

3.お願いとお断り

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

無効な前方参照か、コンパイルされていない種類への参照です。

2023年12月30日、Excelファイル(.xlsm)の読み書きで、見たことないエラーメッセージに遭遇。
それがコレ!

朝から晩まで、右往左往、ほぼ1日ハマりました T_T

これは、その解決方法です。

このエラーに遭遇した際、改良していたのはこちらのプログラムから読み取ったマークをExcelへ出力するコードです。もし、よかったらあわせてご参照ください。

自作のマークシートリーダーです。プログラムは上記リンク先から無料でダウンロードできます。単体でも動作しますが、別途組み込みPython環境を追加するとさらに高速動作します。
自作のマークシートリーダーの読み取り速度をPython4Delphiで高速化。組み込み用Pythonのプログラム一式は上記リンク先から無料でダウンロードできます。

【もくじ】

1.まったく同じコードが動かない!
2.朝からのできごと
3.Web上の情報は?
4.壊れてたのはプログラムじゃなくて・・・
5.まとめ
6.お願いとお断り

1.まったく同じコードが動かない!

Delphiで作成した Excel Book 読み書き練習プログラムでは『何の問題もなく動いたコード』。これを、そのまま本番用プログラムにコピペしたら、なぜか動かない!・・・という、これまでに経験したことのない問題に遭遇しました。

解決するための唯一の手掛かりは、冒頭のエラーメッセージ。

(でも・・・このエラーは、これまでに 一度も見たことない・・・ T_T )

目を皿のようにしてコードを見ても、練習用プログラムと本番用プログラムで、一字一句違ってない。てか、コピペしたんだから、違ってるわけがない・・・。
なのに、練習プログラムは期待通りに動作し、本番用プログラムは絶対に動かない。
必ず、このエラーで停止する・・・

これまで、いろいろなエラーに遭遇したけれど、このエラーは未経験。
もちろん、周囲に頼れる人はいない。自分で何とかするしか、ない。

なんでこんなコトに・・・

2.朝からのできごと

朝、目覚めてからずっと、むかし書いたExcelのマクロ有効Bookを読み書きするDelphiのプログラムを改良してたら、なんかこのエラーが出るようになって・・・。それから、データの書き込みが出来ない!

今、思えば、この時点で気がつくべきことがあったんだけれど、その時の僕は自分で書いたコードの何処かに間違いがあるはずだと思い込んでいるので、その「間違い」をさがすことに夢中になっていて、その他のことはまるで眼中にない。

ってか、Excel関連のプログラムはそこがやっかいなんだけど、大ゴケすると起動したExcelのプロセスが残ってしまい、Ctrl+Alt+Delで残ってるExcelのプロセス探して終了させてって、その繰り返しになってしまう。

この日はエラーの原因がわからなくて、これを際限なく繰り返してしまい、システムも何度か再起動。

で、さんざん頑張って彷徨った挙句、考えたことは・・・

問題の切り分けのため、シンプルなプログラムで実験してみよう!

・・・ということ。

Delphiから接続するExcel Bookはマクロ有効テンプレートにしてあるから、このおおもとのテンプレートをダブルクリックするたびに新しいマクロ有効Excel Book(拡張子はxlsm)が生成される。

そうやって新しいマクロ有効Excel Bookを作成。これを入れる所定のフォルダを作り、保存。準備万端にして、新しい Windows VCLアプリケーションも作成。で、これまで勉強した中で、いちばん動作が確実と思えるコードで「ワークシート間で式をコピーする」手続きを作成、コンパイル、そして「実行」。期待通りに、エラーなく、データのコピー(読み出しと書き込み)終了。胸がすっきり。Bookを開いて結果を確認。データはちゃんと書き込まれ、ワークシートが初期化されてる。もちろん、Excelもきれいに終了。タスクマネージャーで確認してもプロセスは残ってない。

改良中の元のアプリに戻って、新しくボタンを一つ作成。ダブルクリックして新しい手続きを一つ、作り、ここへ、ついさっき動くことを確認したばかりのコードをコピペする。で、実行すると・・・

なんでだよ!!

みたいな・・・

3.Web上の情報は?

もう、こうなったら Googleせんせー に聞きまくるしか、ない。

いくつかのそれらしい情報がヒット。例えば・・・


『sheet名はマクロの実行順に並んでいる必要がある』

(ほんとにー?)

さっそくSheetの順番をそれっぽく入れ替えて実行。結果、変化なし。エラーは普通に出る。


『解決策はモジュール単位の再コンパイル』

(そうなのか?)

ビルドし直しても、変化なし。これも違った。少なくても僕のエラーには効かない。


『あんなことや、そんなことや、こんなことも』全部ダメ。

(マジ、泣きそう T_T )

(このプログラムが動かないと、約束が・・・)


徹底抗戦の覚悟も新たに、腰を据えて、検索結果を、上から順番に、全部開けて熟読。
そしたら次のリンク先ページに、今日一日考えもしなかった情報を発見!

Excel VBA ― マクロ実行時に謎のオートメーションエラー“-2147319767”が発生する場合の対処法

https://ippeintel.com/archives/4644 より引用

解決方法は二つあり、そのひとつは・・・

ブックを新しく作り直す!

これを読んだ僕は、おおきな声で
「あ っ」といいました。

もうひとつは・・・

「開いて修復する」でファイルを開く

とのこと。

4.壊れてたのはプログラムじゃなくて・・・

ファイルだったんですね。

それからExcelの名誉のために言うけど、
壊れたんじゃなくて、僕が壊しちゃったんですね・・・

エラーが出るようになった あの時から。

あわててテンプレートから新しくファイルを生成。今までのと入れ替えて書き込んでみると・・・

これはExcelからのメッセージ?

これは僕が用意した書き込み完了のメッセージ。

今日の苦労は、いったい なんだったんだ・・・

IPPEIさん、ほんとに、ほんとに、ありがとー!!

5.まとめ

(1)ソフトウェアはプログラムとデータからできています。
(2)コードの誤りでエラーになることは僕の場合「日常茶飯事」です。
(3)データが壊れても(壊せばもっと確実に)エラーになります。

・総合的に、僕の場合、エラーがでないことのほうが異常でした!!

そぉか、今日はいつも通りの
普通の日だったんだ☆

みなさん、よいお年を!!

6.お願いとお断り

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

マークシートリーダーをP4Dで高速化

マークシートリーダー第2弾!
今回は Python環境を組み込んで、マークの読み取り速度を高速化 します。
出来る限り丁寧に組み込み方法を説明しますので、どうか最後までお付き合いください。

前回の記事はこちらからどうぞ

追記(20240929)

当Blogで紹介してきた自作のデジタル採点プログラムを一つにまとめました。次のリンク先にその紹介とダウンロードリンクがあります。

【追記_P4D環境で読み取り実行時、エラーが発生するときは?】

Python環境を組み込んで、これを利用してマークシートの読み取りを実行する場合、次のエラーが発生することがあります。エラーの内容からは推測すると、エラーはテンプレートマッチングの際に利用するテンプレート画像のサイズに起因して起きているように見えますが、ほんとうの原因は違います。

このマークシートリーダーは、Python環境を利用して動作する際は、マークの有無を読み取るJpeg画像の名称(及びフォルダの階層)が次の規則に従っていることを前提としています。

ProcData\XXX\Sample-01.jpg
ProcData\XXX\Sample-02.jpg
ProcData\XXX\Sample-03.jpg
・・・
ProcData\XXX\Sample-40.jpg
ProcData\XXX\Sample-41.jpg

この命名規則にJpeg画像のファイル名(及びフォルダの階層)が従っていない場合、読み取りエラーが発生します。例えば、次のような場合です。

ProcData\XXX\Sample-01a.jpg
ProcData\XXX\Sample-01b.jpg
ProcData\XXX\Sample-02a.jpg
ProcData\XXX\Sample-02b.jpg
・・・
ProcData\XXX\Sample-40a.jpg
ProcData\XXX\Sample-40b.jpg
ProcData\XXX\Sample-41a.jpg
ProcData\XXX\Sample-41b.jpg

特に数学(や情報)用途で2枚1セットのJpeg画像を処理する際は、注意してください。このエラーを防止するには、ファイルメニューの「1 画像変換」⇨「専用画像を作成」を利用してファイル名が必ず連番になるように読み取り専用Jpeg画像を生成して、この画像に対して、マークの読み取りを実行してください。

以下、発生するエラーメッセージの一覧です。

このメッセージは2回表示されます


なお、Python環境を利用しないモード(P4Dを使用のチェックボックスをOFF:下図右上)であれば、読み取り対象Jpeg画像ファイルの名称は動作に関係しないので(読み取り速度は低下しますが)、読み取り可能です。

画面右上の □ P4Dを使用のチェックを外して、Delphi用のOpenCVで読み取りを実行。
読み取り速度は低下しますが、マークを正しく読み取っています。


【読み取り実行前に、選択肢の始まり番号も指定してください】

選択肢の番号は、デフォルト1始まりに設定してあります。教科「情報」用途で読み取りを実行する場合は、読み取り実行前に、選択肢が「1」始まりであるのか、「0」始まりであるのか、その指定を画面上の設定欄で必ず指定してください。

【もくじ】

1.Python環境を準備する組み込み用Pythonのダウンロードリンクがあります
2.Python環境のドッキング
3.高速化の確認
4.システムにC++ランタイムライブラリがない場合は?
5.Python Engine の初期化の問題?他
6.まとめ
7.お願いとお断り

1.Python環境を準備する

Qiita の記事で「 Embeddable Python 」なるものの存在を知り、ほぼ同時に Delphi に Python のスクリプトを埋め込んで、VCL で GUI を作成、内部的に Python のスクリプトを実行する方法を学びました。

この辺の詳しい経緯は、かなり前に記事として書いた通りです。

2022/01/01
2022/01/02


こうして出来上がった、マークシート読み取りに必要なライブラリだけをインストールした、組み込み用のPython環境の内容は、こんな感じです(組み込み用途に作成した Embeddable Python があるフォルダをコマンドプロンプトで開き、「 Python -m pip list 」コマンドを実行した結果です)

ライブラリの主役は Numpy と OpenCV-Python。
Pillow は、日本語を含む Path を読むためにインストール。


最初に用意した Embeddable Python が14MBくらいで(おー!ちいさい☆)と喜んだけど、上記のライブラリを三つ入れたら 158MB に・・・。

ライブラリを構成しているファイルの依存関係がわかれば、必要ないファイルを消しまくって、もっと小さく出来ると思うのですが・・・、その具体的な方法がわかりません!!

仕方がないので、そのまま組み込み用の「Python39-32」フォルダを作成。

フォルダ名の Python は、「Python関連のフォルダだよ!」ってコトが一目でわかるように工夫(?)しました。その次の 39 はVersion番号、ハイフンで繋いだ 32 は 32bit 用って意味です。

これを前回紹介したマークシートリーダーにドッキングさせます。

展開に少々時間がかかりますが、もし、よかったら使ってください。
MS_Reader 組み込み用 Embeddable Python です。

2.Python環境のドッキング

ダウンロードした「Python39-32.zip」を MS_Reader.exe のあるフォルダにコピー・貼り付け、展開してください。※ 動作確認が完了したら「Python39-32.zip」は削除しても OK です!

【展開前】

MS_Reader.exe とダウンロードした Python39-32.zip を同じ階層に置き、
zipファイルを展開(右クリックして「すべて展開」を選択)してください。
展開にはしばらく(1~2分)時間がかかります。

展開時のPC環境?によっては「ものすごく(20~30分)」時間がかかることが実際にありました!!(原因はわかりませんが、時間がかかるだけで、展開そのものは正しく行われました)

【展開後】

重要 MS_Reader.exe と Python39-32 フォルダは同じ階層に置いてください。

MS_Reader.exe と Python39-32 フォルダは必ず同じ階層に置いてください。


ここで念のため「Python39-32」フォルダの構造を必ず確認してください。

〇:Pathに注目してください。これならOK!

MS_Reader\Python39-32\Lib であり、また、
MS_Reader\Python39-32\Scripts であります。

これはダメです。Pathが二重になってます。

MS_Reader\Python39-32\Python39-32\Lib
MS_Reader\Python39-32\Python39-32\Scripts

上の「ダメな例のようにならない」ようにPython39-32.zipを作成しましたから、大丈夫だと思いますが・・・念のため、必ずご確認いただけますようお願いいたします。

以上が『 ドッキング作業 』です!!

MS_Reader.exe と同じフォルダに、Python39-32.zip をコピペして、展開すれば Python環境のドッキングは完了です。

これを夢見て、ンか月。マジ、挫けそうな時もあった・・・ けど。

MS_Reader.exe をダブルクリックして、マークシートリーダーを起動してみてください。

僕のマークシートリーダーは、自動的に、高速動作モードで、起動します。

3.高速化の確認

Python環境がないと(MS_Reader.exe がある場所に Python39-32 フォルダがない場合)・・・

MS_Reader 起動時、マークシートの読み取りを高速化するP4D(PythonForDelphi)モードは利用できませんが、

Python環境があれば(MS_Reader.exe がある場所に Python39-32 フォルダがある場合)・・・

マークシートの読み取りを高速化するP4D(PythonForDelphi)モードを利用する状態で、MS_Reader は起動します。

当たり前ですが、ダミー(中が空っぽ)の「Python39-32」フォルダを作成し、設定を偽ってMS_Readerを起動しても、メリットは何一つありません!

エラーが2つ出るだけです。

実際に、空の「 Python39-32 」フォルダを作成して実験してみました!


もう一つ。


こんなコトする方は皆無と思いますが。あくまでも、プログラムの動作検証として、ご参考まで。

【動作確認】

前回、設定したテンプレートを利用して動作確認します。

いったん、「P4Dを使用」のチェックを外して読み取りを実行します。前回試行した3列25行8選択肢の1枚あたり600マークあるシート3枚の読み取りにかかる時間は・・・


1枚0.805秒で読んでます(PC環境により、数値は当然異なります)が・・・

「P4Dを使用」のチェックを ON にして再び読み取りを実行します。私の PC での結果は・・・


1枚0.245秒強で読みました。

これが速度的に「はやい」か・どうか、このソフトウェアをお使いいただく方により、その判断基準は異なりますから、その思い(感じ方)は違って当然ですが、Python環境を利用しない場合に比較して、Python環境を組み込み、これを利用した場合は(PC環境により、その数値は悉く異なると思われますが)マークの読み取り速度は間違いなく高速化されるはずです(僕の環境では、「それがない」場合に比較して、「それがある」場合は3.3倍速で動作しました)。

ただ、Python環境を組み込んだ場合、プログラム全体の大きさは、12倍以上に巨大化します・・・

プログラムサイズを選ぶか、動作速度を優先するか、
ご使用目的、お使いのPC環境に合わせて選択していただけたら幸いです。

僕は・・・

今日の空みたいな・・・

プログラムを書きたかった・・・だけです *(^_^)*♪

僕が、この世から消えたあとも、動く。

いつか、夢みたとおりの・・・ プログラムを。

だいすきな・・・

大好きな Delphi と・・・

僕の Object Pascal で。

4.システムに Visual C++ランタイムライブラリがない場合は?

お使いのシステムに Visual C++ランタイムライブラリがインストールされていない場合は、MS_Reader 起動時に次のエラーが発生します。

『アプリケーションを正しく初期化できませんでした(0xc0150002)。「OK」をクリックしてアプリケーションを終了してください。』

英文の場合もあるようです。

PCの解像度の関係だと思うのですが、画像がボケていてごめんなさい!


このエラーが発生する原因を調べてみたところ、組み込みPython環境内にある「Python39.dll」が Visual C++ランタイムライブラリを必要とするようで、これがシステムにない場合は、プログラム起動時にバックグラウンドで行っているPython Engine の初期化に失敗して、上記のエラーメッセージが表示されることがわかりました。

お使いのPCで、Visual C++ ランタイム ライブラリのインストール状況を確認するには、[スタート] ボタンを右クリックし、「ファイル名を指定して実行」をクリックして、appwiz.cpl と入力して[Enter]を押します。Python環境を組み込んだ MS_Reader が動作する環境であれば、システムにインストールされている Microsoft Visual C++ ランタイム ライブラリが以下のように表示されるはずです。

現在、私のシステム(Windows 11 Pro 23H2)にインストールされているC++ランタイムライブラリの一覧。
もちろん、このシステムでPython環境を組み込んだマークシートリーダーが正常に動作しています。


システム内で起きていた別のエラーを解決するために、2023年12月上旬に工場出荷状態に戻すリカバリ作業を行いました。同時にOSを最新のバージョンに更新しました。それ以前のシステムの状態は次の通りです(OS のバージョンは 22H2)。※ 私のPCでの話です。

現在の状況とは異なっています。
この状態でもPython環境を組み込んだマークシートリーダーは正常に動作していました。


エラーを解決するには、Visual C++ランタイムライブラリをインストールすればいいわけですが、上の例のように Visual C++ ランタイムはたくさんあるので、手動でひとつひとつダウンロードしてインストールするより、Visual C++ ランタイムインストーラーを使って全ての Visual C++ ランタイムを一括インストールする方が簡単です。

システムをリカバリする前は、次のようにして Visual C++ ランタイムをインストールしていました。

【ご注意願います!】
ここで紹介する方法で Visual C++ ランタイムをインストールする場合、他のプログラムの実行環境との整合性は、一切保証できません。また、最悪の場合、Windowsが起動しなくなるトラブルが発生することも十分に考えられます。インストール作業の全てが自己責任であることを十分ご理解の上、重大な問題が発生した場合は元の環境に戻せるよう、システムのバックアップを取る・現在の設定をメモに記録する等、不具合の発生に備え、必要かつ十分な準備を整えた上で、Visual C++ ランタイムのインストールを行ってください。

以下のサイトから「Visual C++ v56.exe」をダウンロードしてインストール(私の環境にインストールする分には、なんの問題も起きませんでした。もちろん、マークシートリーダーも問題なく起動し、安定動作しました)。

Visual C++ Runtime Installer (All-In-One) v56

https://www.majorgeeks.com/files/details/visual_c_runtime_installer.html

こちらのWebサイトでも(次のリンク先Webページの下の方で)、このインストーラが紹介されています。

Microsoft Visual C ++ 再頒布可能ファイルを削除して再インストールする方法

https://www.autodesk.co.jp/support/technical/article/caas/sfdcarticles/sfdcarticles/JPN/How-to-remove-and-reinstall-Microsoft-Visual-C-Runtime-Libraries.html

インストーラーを立ち上げると、本当にインストールするかどうかを「YES」か「No」かで尋ねられるので、インストールする場合は「Y」をタイプします。その後はPCの画面に表示される英文の指示にしたがって操作してください。

ここから先は、上記のインストーラーを用いて Visual C++ ランタイムをインストールした際、私が実際に経験したトラブル?です(最終的にインストールは成功しました)。

お決まりのUAC起動後(PCの設定によっては)管理者ID 及びパスワードの入力が求められますが、これを入力すると、そのままPCがフリーズしたような状態になり、数分待機しても進展が見られないので、いったん作業を Ctrl+Alt+Delete でキャンセルし、再度、「Visual C++ v56.exe」を起動して Visual C++ ランタイムのインストール作業を実行、今度はトラブルなくインストールに成功する事例です。これは「ある特定のAD環境下にあるPCのすべてに共通して見られた」現象です。現在もその原因はわかりませんが、ご参考まで。

また、システムの状態によっては(現在システムにあるランタイムをアンインストールしているのか?)複数回(と言っても最高2回ですが)、再起動を求められることも(何度も)経験しました。

C++ランタイムライブラリのインストールについて、経験を加味して私がわかるのはここまでです(実は、何もわかってないのとイコールなのですが)。これ以外のエラーメッセージが表示されてインストーラーが起動しない場合も、もしかしたらあり得るかもしれません。大変恐縮ですが、そのような場合は原因の究明を含めて、自己責任でご対応ください。

5.Python Engine の初期化の問題?他

MS_Reader では、マーク読み取り時の体感速度を上げるため、FormCreate時にバックグラウンドで Python Engine の初期化を行っています。MS_Reader.exe のあるフォルダに小さなマークシートの画像とマーカー画像があるのにお気づきになった方がいらっしゃるかもしれません。これは Python Engine 初期化用に用意した画像です。

Python Engine 初期化用の画像をリソースに埋め込み、もし、それがない場合は再生して、
プログラム起動時に Python Engine の初期化が必ず行われるようにしています。


この初期化を「するか・しないか」で、MS_Reader 起動後、初めてマークを「読む」ボタンをクリックした際のプログラムの挙動がまるで違ったものになります。初期化を行った場合は、ごくスムーズにマーク読み取りが始まるのに対し、行わなかった場合は PC が一瞬フリーズしたような状態になり、その後、息を吹き返すかのようにマークの読み取りが始まります。

Python Engine の初期化コードです。

  AppDataDir:=ExtractFilePath(Application.ExeName)+'Python39-32';

  if DirectoryExists(AppDataDir) then
  begin
    //フォルダが存在したときの処理
    CheckPython.Enabled:=True;
    CheckPython.Checked:=True;
    PythonEngine1.AutoLoad:=True;
    PythonEngine1.IO:=PythonGUIInputOutput1;
    PythonEngine1.DllPath:=AppDataDir;
    PythonEngine1.SetPythonHome(PythonEngine1.DllPath);
    PythonEngine1.LoadDll;
    PythonDelphiVar1.Engine:=PythonEngine1;
    PythonDelphiVar1.VarName:=AnsiString('var1');
    PythonEngine1.Py_Initialize;
    //イニシャライズされたことを記憶
    P4D_ini:=True;
  end else begin
    CheckPython.Checked:=False;
    CheckPython.Enabled:=False;
    PythonEngine1.AutoLoad:=False;
    P4D_ini:=False;
  end;

(どこに問題があるのでしょうか?)

PC によっては、この Python Engine の初期化に非常に長い時間を要することがあるようです(エラーメッセージは出ません。この沈黙の時間が終わった後、プログラムは問題なく動作します)。偶然、ある PC でこの現象に巡り合い、あわてて時間を計ってみたところ、その PC では初期化に4分必要でした! なぜ、このような現象が発生するのか、その理由がわからないのですが、「そのようなことがある」ことだけは経験的に明らかですので、ここに書いておくことにしました。

また、マーク読み取り開始時に、マーカー画像の位置をテンプレートマッチングで確認して、それが「本当に見えている」ことをユーザーに明示的に知らせていますが、ここでもその処理に少し時間が必要なことがあります。私のPCでも、この現象は「起きたり・起きなかったり」するような気が・・・。エラーが出るわけでもなく、ただ・・・「ん?」みたいな時間があるだけなのですが・・・。こちらもその原因がよくわかりません。

以上が、現象としてはわかっているのですが、原因が解明できていないPython環境を使う上での問題点です。

それから、私の想定外の操作が行われた場合、メモリーリークが起きる可能性があります。Python環境をドッキングさせた当初は、このメモリーリークにかなり悩まされました。どう頑張っても小さなメモリーリークが発生するのを取り切れず、( Python環境はそういうもの? )と割り切ってしまおうかと思ったこともあったくらいです。

そのたびに思い直し、メモリーリークが発生する原因を突き止めて対応することを繰り返しました。なので、私が想定した操作範囲内でのメモリーリークは全て取り切れたと思います。が、もし、それが発生した場合は、その発生を知らせるメッセージがプログラム終了直後に表示されます( FormCreate時に実行されるコードの中にメモリーリークがあれば検出するコードを残してあります)。

  //メモリーリークがあれば検出
  ReportMemoryLeaksOnShutdown:=True;
メモリーリークが起きたことを伝えるメッセージ
(上の例のメモリーリークは故意に発生させたものです)

6.まとめ

(1)Python環境を利用するとマークシートリーダーは高速化できる。
(2)高速化できるかわりに、プログラム全体のサイズは大きくなる。
(3)原因不明のフリーズのような現象が発生することがある。

7.お願いとお断り

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

マークシートリーダー

自分的に必要と思った機能は全部搭載しました・・・が、
プロが作った有償販売できるレベルのソフトウェアではありません。
見た目も、使い勝手も、よくないと思います。
もちろん、無料でお使いただけますが、サポートも、動作保証もありません。
ダウンロードから設定まで、ALL自己責任でお願いします。

快適と感じる速度で動作させるには、かなり高性能なCPU搭載のマシンが必要です。
私のプログラミング技術が足りない部分を、CPUパワーでカバーしてもらってます。
マシンによっては、読み取り結果のチェック等がかなりトロいかもですが・・・

それでも、もし、よかったら使ってください。
Delphiで作ったマークシートリーダーです。

【ご案内】追記(20240929)

当Blogで紹介してきた自作のデジタル採点プログラムを一つにまとめました。次のリンク先にその紹介とダウンロードリンクがあります。

当Blogで紹介したデジタル採点プログラムのすべてをまとめました!

【使い方のご案内】

1.デスクトップにMS_Reader.zipを展開(解凍)
2.高解像度ディスプレイへの対応
3.マークシート画像の読み取り準備
4.テンプレートを作成
5.マークの読み取りを実行
6.読み取り結果のチェック
7.CSVファイルへの書き出し
8.Excel Book の準備作業
9.Excel Book への書き出し
10.マークシート印刷用紙について
11.まとめ
12.お願いとお断り

どんな環境でも、100%動作する保証はできません・・・が、
私と同じ環境・条件を揃えていただければ、きっと動くと思います。

使用したPC及びOS、開発環境は、次の通りです。

・プロセッサ 11th Gen Intel(R) Core(TM) i7-1185G7 3.00 GHz
・実装 RAM 32.0 GB

・Windows 11 Pro 64ビット版
・バージョン 23H2
・OS ビルド 22631.2861

・Embarcadero® Delphi 12 バージョン 29.0.50491.5718

・設計時の画面解像度は「1366 × 768」です。これ以上の解像度でお使いください。

使い方をなるべく丁寧に説明しますので(マニュアルも同梱してありますが)、まず、ここに書かれている順番で、一通り操作してみていただけたら幸いです。

1.デスクトップにMS_Reader.zipを展開(解凍)

ダウンロードした MS_Reader.zip をお使いのPCのデスクトップにコピペして右クリックするとサブメニューが表示されます。この中の「すべて展開」をクリックしてください。

デスクトップに MS_Reader.zip を展開します。


無事、展開に成功したら、MS_Readerフォルダをダブルクリックして開きます。

フォルダ内に展開されたファイルの中に MS_Reader.exe があります。これをダブルクリックしてマークシートリーダーを起動します。


次のメッセージが表示された場合は、「詳細情報」(画像中、赤い枠で囲んで示した部分)をクリックします(プログラムの発行元が不明である場合に、Windows のDefender機能である SmartScreen がこの表示を出すそうです。自分の責任で実行すれば、次回からこのメッセージは表示されなくなります)。

「詳細情報」をクリックします。


すると、次の画面が表示されます。「実行」(画像中、赤い枠で囲んで示した部分)をクリックしてMS_Readerを起動してください。

「実行」をクリックします。

2.高解像度ディスプレイへの対応

高解像度ディスプレイをお使いの場合の対応方法です。高解像度ディスプレイをお使いの場合、設定から「システム」⇨「ディスプレイ」と順にクリックすると、次のように表示されると思います。

拡大縮小に「150~200」という値が設定されていれば、高解像度ディスプレイです。


この場合、起動したマークシートリーダーの画面が小さくて見えにくいと感じることがあるかもしれません。その場合は、次のように操作してください。

MS_Reader.exe を右クリックして、表示されるサブメニューのプロパティをクリックします。

MS_Reader.exe のプロパティを表示します。


「互換性」タブをクリックします。


高DPI設定の変更をクリックします。


「高いDPIスケールの動作を上書きします。」にチェックを入れて、「拡大縮小の実行元:」は「システム」をComboBoxの選択肢から選択して指定。OKボタンをクリックします。


続けて「適用」⇨「OK」とクリックして設定は終了です。これで画面が見やすい大きさで表示されるようになります。

3.マークシート画像の読み取り準備

デスクトップに展開した MS_Reader フォルダ内に「ScanData」フォルダがあります。この中に練習用のサンプル画像が2種類(解像度150dpiと200dpi)入っています。この画像を用いて説明します。

重要 マークシートは、解像度150~200dpiでスキャンしてください。

重要 1回の操作で読み取り可能な枚数は最大99枚です。

MS_Reader.exe をダブルクリックしてマークシートリーダーを起動したら、画面左上の「画像変換」をクリックし、表示されるメニューの「専用画像を作成」をクリックします。


画像変換用のWindowが表示されたら、画面右上の「選択」ボタンをクリックします。


「フォルダの選択」ダイアログが表示されます。ここでは「Scanner_A」フォルダを選択します。フォルダ名をクリックして、下のFolder欄に「Scanner_A」と表示されたことを確認し、「OK」をクリックします。

スキャンしたマークシート画像は「ScanData」内に適切な名前を付けたフォルダを作成し、必ずその中に保存してください!

重要 フォルダ名にハイフン(-)を使わないでください。

参考 フォルダ名には、文字の他、アンダースコア(_)が使用できます。

注意してください。選択するのは「フォルダ」で、「ファイル」ではありません。
(Scanner_Aをダブルクリックして開いても何も表示されません)


画面は、次のようになります。赤い枠で囲んだ部分にマークシート画像のサムネイルが表示されます。回転の必要性の有無と回転方向を確認してください。


この場合は、回転の必要性「有り」で、回転方向は左90°です。これを「画像の回転」のオプションボタンをクリックして指定します。

左90°のオプションボタンをクリックします。


必要であれば、次に画像のリサイズ指定を行います。リサイズを指定「する・しない」の判定基準は、スキャナーでマークシート画像をスキャンした際の解像度の数値で判断してください。

「Scanner_A」フォルダ内のマークシート画像は、ScanSnap iX1500 のノーマルモードでスキャンした画像で、その解像度は 150dpi です。この場合は、ちょうどよい大きさでマークシート画像が表示されますので、画像をリサイズする必要はありません。

重要 解像度150dpi ・A4横置きの場合、リサイズは必要ありません!

重要 解像度200dpi ・A4横置き・解答マーク欄4列の場合、80%の大きさにリサイズしてください。読み取り後のチェックまで含めて、作業しやすくなります。

マークシート画像の読み取り解像度が 200dpi でも、マークシートがA4横置き、解答マーク欄の列数が3列の場合、リサイズは必要ありません

また、A4以外の大きさのマークシートは使ったことがありません!
(用紙の左上にマーカー画像■■■を入れ、その他はここでダウンロードできるサンプルと同様に作成していただければ、用紙サイズに関係なく動作すると思いますが、試行したことがありませんので確かなことは言えません。ただ、画像のサイズが大きくなればなるほど、動作速度は間違いなく低下します。また、複合機のスキャナーを用いて、マークシートを画像化する際も、B4やA3の大きさだと私が使用している機材ではメモリがいっぱいになるのでしょうか? 30枚程度読み込んだあたりで一旦動作が停止します。数百枚単位での読み取りにはそれなりに時間がかかります。そのような理由から、マークシートに使う紙の大きさはA4サイズ以下が適切だと思います。)

参考:プログラムを書いた本人が言うのもナンですが、自動でのリサイズはおまけ程度にお考えください。
ScanDataフォルダのScanner_Bフォルダに保存されたサンプル画像の大きさは、2338 × 1653
これを自動リサイズオプションボタンを指定して、変換してみます。
ProcDataフォルダのScanner_Bフォルダに保存されたサンプル画像の大きさは、1760 × 1248
いちおう、これでマークシート画像が横方向のはみ出し「なし」で表示されました。

重要 画像のリサイズの有無を必ずメモ(記録)してください!

⇨ 複数クラスのマークシート読み取り時に、同じ設定を適用する必要があります。

重要 大きな解像度の画像を扱う場合、動作速度が大幅に低下します!

回転の有無と方向、リサイズの有無を指定したら、画面中央右にある「参照」ボタンをクリックして、保存先のフォルダを選択します。


「フォルダの選択」ダイアログが開きます。Pathを見ると「ProcData」フォルダが指定されていることがわかると思います。なお、Procは「Processed(加工済み)」という意味です。

プログラムは「ScanData」フォルダで指定したフォルダと同名のフォルダを「ProcData」フォルダに自動作成します。この自動作成されたフォルダをクリックして選択します(しつこいようですが、選択するのは「フォルダ」で、「ファイル」ではありません)。下のFolder欄に「Scanner_A」と表示されたことを確認し、「OK」をクリックします。

読み取り用のマークシート画像は、必ず「ProcData」内の自動作成されたフォルダに保存してください!

重要 ProcData以外のフォルダには画像を保存しないでください。

読み取り用画像を保存するフォルダは自動で作成されます!
(自動作成されたフォルダをクリックして選択してください)


「変換実行」をクリックします。

回転とリサイズの有無を指定して「変換実行」をクリック!


次に表示される案内メッセージには「いいえ」を選択してください。


このマークシートリーダーとは別に、手書き答案の採点プログラムを作成しました(準備が整い次第、公開する予定です)。このマークシートリーダーは、そちらと連動しての動作も可能な設計にしてあるため、このメッセージが表示されます。

画像の変換が完了すると、メッセージが表示されますので、OKをクリックします。


変換された読み取り専用画像のサムネイルが表示されます。作成された読み取り用の画像ファイルには連番の名前が自動的に付きます(自動生成されたファイル名は変更しないでください)。

重要 Python環境を利用する場合はファイル名は必ず連番にしてください。

画像処理のアルゴリズムは、GDI+を利用しています。画像の回転とリサイズが伴う場合は、変換に時間がかかります。処理が完了するまでお待ちください。

(後日、別途ご案内する予定の)手書き答案の採点プログラムと併用する場合は、採点やり直しのために必要な画像もここで作成します(Loopが二重にまわり、時間も2倍かかります)。

クラス別に処理する場合は、「画面の初期化」ボタンをクリックします。
変換元フォルダの選択から、画像の変換処理を再実行できます。

画像の変換処理が完了したら、「終了」ボタンをクリックして、この画面を閉じます。

参考:画像を変換する理由は以下の3つです!
(1)Jpeg画像のサイズを最適化するため(全体が画面内に収まるようリサイズしてください)。
(2)画像の名前が連番になるよう、自動的にリネームするため。
(3)証拠画像としてのオリジナルを残したまま、読み取りに最適な大きさの画像を生成するため。

4.テンプレートを作成

次に、マークシートの情報を記録した読み取り用のテンプレートを作成します。これを作成することにより、同じ採点を複数クラスに対して実行したり、設定(縮小処理の有無を含む)が同じマークシートを異なる考査での使いまわしが可能となる・・・

・・・ように設計したのですが、実際には使いまわしがなんとなく不安なので、考査毎にテンプレートを再生成して運用しています。ですので、同じ設定(大きさ)のマークシート画像の情報を記録したテンプレートの使いまわしが可能か・どうか、これについては未確認です。

「確実なマークシート読み取りを実行する」ためには、お手数をおかけしますが、試験ごとに使用したマークシートのテンプレートを作成していただくのが最良の方法であると思います。

メニューの「2 テンプレート」をクリックして表示されるサブメニューの「テンプレートの新規登録」をクリックしてください。別のWindowが開きます。


画面右上の「取得」ボタンをクリックします。


今度は「ファイルを選択」するダイアログが表示されます。任意のマークシート画像を選んでください(1番のファイルを選ぶ方が多いのではないでしょうか?)。ファイルをクリックしてファイル名を取得し、「開く」をクリックします。

マークシート画像のサムネイルをクリックするとファイル名が取得できます。


画面は次のようになります。

このプログラムでは、マーカー(特徴点)画像を利用してマークシートのマーク位置を計算しています。ですので、このプログラムで処理するマークシートには必ずマーカー(特徴点)画像が必要です。

重要 マークシート左上にマーカー画像(■■■)を必ず用意します。

重要 マーカー画像は、マークシート1枚に1つだけ用意します。

画面右の操作パネル上段にある「マーカー」オプションボタンをクリックして選択状態にします。

「選択対象」の「マーカー」オプションボタンをクリックしてください。


マークシートの画像が拡大表示され、マウスのカーソルが大きな「+」になります。

マーカー画像の「左上」をクリックし、ボタンを押したまま「右下」へドラッグしてください。画像上には点線のラバーバンドが表示されます。

マーカー画像の左上を左クリックして、マウスの左ボタンを押したまま、マーカー画像の右下へドラッグ。
点線のラバーバンドでマーカー画像が囲まれます。


ドラッグ中の画像です(わかりやすさのため、マーカー画像より大きめにドラッグしています)。

黒点線がラバーバンドを示します。


マーカー画像の座標を正しく取得できる例です。

マーカー画像とラバーバンドがぴったり重なるようドラッグしてください。


マウスの左ボタンから指を離すと、取得できたマーカー画像が画面右側に表示されます。

数値は、画像左上からの距離です。

マークの読み取り時、プログラムは、コンピュータの眼である「OpenCV」のテンプレートマッチングの機能を利用して、まず、最初にマークシート画像中にあるこのマーカー(特徴点)画像を探し出します(これはマークシート画像1枚1枚について必ず行います)。

次に、マーカー(特徴点)画像左上隅を原点(0,0)として、テンプレートに記録されたマーク欄の座標からマーク一つ一つの位置を割り出して、これを切り抜いて画像化(正確に言うと、マークの切り抜き処理前に、ボカシ・二値化・白黒反転の各処理を行い、マークの切り抜き後に白面積計算処理を行って)、マークの有無を判定しています。

この方式の利点は、印刷そのものが左右にズレでも、マーカー画像と解答欄の相対的な位置関係は一定で変わりませんから、印刷がズレすぎて解答欄が印刷されなかった場合以外は、必ずマークの位置を探し出せる(=マークの有無を判定できる)ことです。

事実、輪転機で印刷(非推奨ですが!)して、チェックから漏れた(チェックしなかった?)、正しい位置から印刷が5cmくらいズレたマークシートも、このプログラムでなんの問題もなく読み取れました・・・。印刷のズレを申告せず、そのまま解答して提出する受験者も受験者ですが・・・。A4横・4列のシートで、解答には3列めまでしか使わなかったから「4列めはなくてもOK! 大丈夫」と思ったのでしょうか? それともただ単にめんどくさかったのでしょうか? たぶん、後者だと思いますが・・・

次は、そのテンプレートマッチングの機能をテストします。画面右にある「マーカー画像の読み取りテスト」ボタンをクリックしてください。テンプレートマッチングが正しく実行されると、マーカー(特徴点)画像が太い赤枠で囲まれます。


表示されるメッセージをお読みいただき、「OK」をクリックしてメッセージを閉じてください。


結果が良好であれば「選択対象」グループの「解答欄」をクリックします。


次に、マークシートのマーク(解答)欄の「行数」と「列数」及び「選択肢の数」を指定します。


マークシートの列数・行数・選択肢数の数え方は次の通りです(Scanner_Aフォルダにあるマークシート画像は、A4横置き・3列・25行・8選択肢の形式です)。


ですので、これを次のように設定します。


ComboBox に正しく設定を入力したら、その下の「採点方法の設定」の座標「1列」のオプションボタンをクリックして選択状態にします。マウスのカーソルが大きな「+」になります。


第1列目のマーク(解答)欄の座標を取得します。マーカー(特徴点)画像の時と同様、第1列の枠のうち、設問番号欄の矩形を除いた選択肢のマークが印刷されている欄の矩形の左上隅を(左)クリックして、そのままボタンを離さずに、枠の右下隅へドラッグします。この作業は正確に、慎重に行ってください。この作業の良し悪しでマークの読み取りの可否が決まります。

極めて重要 設問番号欄を含めて指定してはいけません!

極めて重要 指定するのはマーク欄のみ!

プログラムは、ここで取得した座標値(矩形の高さ)を行数で割り算して列を設問毎1行ずつに切り出し、さらに切り出した1行を選択肢数で割って1つ1つのマークを切り出し、その塗りつぶし面積を計算して、マークの有無を判定しています。

マーク(解答)欄の枠線と、表示されるラバーバンドがぴったり重なるようにドラッグしてください。

※ 下図は2つともドラッグ直後の結果を示しています(〇はドラッグ開始点と終了点です)。

マーク欄第1列めの左上隅を(左)クリックしてそのまま指を離さずに右下隅へドラッグ


ドラッグ中は、黒点線のラバーバンドが表示されます。これを目安に位置決めを行ってください、

画像中の〇印の位置までドラッグします。


指を離すと、ドラッグした範囲が赤い矩形で囲まれます。画面右側に取得できた座標が表示されます。


「再範囲選択」ボタンをクリックして、座標の取得をやり直すこともできます。


1列目が済んだら、同様にして2列目の座標を取得します。この作業を「マークシートの列数」分だけ繰り返します。

すべての列の座標を取得できたら、「保存」ボタンをクリックして取得した座標を保存します。


「保存」処理が完了すると、次のメッセージが表示されます。

参考:テンプレートの名前について
例 N_R25C03S08
N:ノーマル(通常の大きさ:解像度150~200dpi)
R:Row(行数)は25行
C:Col(列数)は3列
S:Selection(選択)は8個


「二値化テストの実行」ボタンをクリックすると、第1列めを「平滑化(ぼかし)処理&白黒反転して二値化」した画像の状態が確認できます。「マークあり」の部分が白く表示されていればOKです!
(プログラムは、この白部分の面積を計算して、マークの有無を判定しています)

「終了」ボタンをクリックして画面を閉じ、マーク(解答)欄座標の取得作業を終了します。

二値化テストを実行した場合は、終了ボタンをクリックする前に、保存ボタンをクリックすることを忘れないでください!

二値化の閾値と平滑化(ぼかし処理)のパラメータは、まずデフォルト設定でお試しください。

5.マークの読み取りを実行

これでマークを読む準備ができました。メニューの「テンプレート」をクリックし、表示されるサブメニューの「テンプレートの選択」をクリックします。


次のように、テンプレートを選択するWindowが表示されます。マークシートの形式に合ったテンプレートをクリックして選択し、決定をクリックします。

シングル/ダブルとあるのは、数学や教科「情報」のテストで、マークシート2枚1セットの採点を行うための設定です。選択肢数が16のマークシートを選ぶと、この設定も選択できるようになります(選択肢数が16未満のマークシートでは、この設定は利用できません)。

数学及び教科「情報」用の設定は、後日別記事として掲載する予定です。


次のメッセージが表示されます。「はい」をクリックしてください。


マークの読み取りを実行したいマークシート画像のあるフォルダを選択し、「Ok」ボタンをクリックしてください。


保存してあるマーカー(特徴点)画像をもとに、自動的にテンプレートマッチングが行われ、見つかったマーカー(特徴点)画像から、マークシートのマーク(解答)欄第1列第1行目の座標が計算され、それぞれが赤い矩形で囲まれて表示されることを確認してください。

Python環境を利用する場合(ここでワンクッション置くような感じで)テンプレートマッチングにしばらく時間がかかることがあります。同じプログラムを走らせているのですが、PCにより、このフリーズしたような時間の長さが極端に違うようです・・・、その辺の理由が私にはさっぱりわかりませんが・・・。

Python環境利用時に、この画面が表示されるまで、フリーズしたようになることがあります!


ここまでの設定操作が順調に進行していれば(抜け・落ち・欠けがなければ)、間違いなくテンプレートマッチングが成功し、マーカーと1列1行目が赤い矩形で囲まれるはずです。次のメッセージが表示されますので、お読みになったら「OK」ボタンをクリックしてください。


「読む」ボタンをクリックすると、マークシートの読み取りがスタートします。


画面下部の StringGrid に読み取り結果がリアルタイムで表示されます。また、読み取り完了後、処理にかかった時間が画面左下に表示されます。


8選択肢・25行・3列だから、合計600マーク ×3枚=1800マークの読み取りで、早ければ2013ミリ秒、遅くて2467ミリ秒で読んでます(PCの性能により、この値は変わります)。


遅かった方で1マークあたりの読み取り時間を計算すると、

2.467秒 ÷3≒ 0.82秒/枚
0.82 ÷ 600 ≒ 1.4ミリ秒/1マーク

そう書くと、すごく早いような気がしますが・・・

600マーク3枚で2.5秒だから、30枚ならその10倍で25秒かかります。平均的な高校の1学年分の生徒数を1学年8クラス320名とすると、さらに10倍で280秒程度、約5分処理時間が必要です。

300名分、5分だと慣れてくるとちょっと遅く感じてしまうかな? みたいな気が・・・

このプログラムには、内部的にPython環境を組み込んで高速動作させるモードがあります。数学用途の16選択肢・25行・3列で1200マーク/枚のマークシートで処理速度を計算・比較してみます。
(組み込みPythonの利用方法は後日ご案内します)

まず、Python環境を利用しない場合、1200マーク×40枚=1クラス分の48000マークを読むのにかかった時間が・・・


約78秒です。2枚1セットのダブルモードならその倍になります。
1枚(1200マーク)読むのに1.95秒かかってます。

次に、Python環境を利用した場合です。同じ読み取り条件で実験すると・・・


約11.5秒。8クラスあっても2分かかりません。ダブルモードでも4分未満。
1枚0.3秒未満で読み取ってます。

何やってもダメな自分にしては、よく頑張ったって正直、思います・・・。
よほど、びみょーなマークでない限り、期待した通り、ほぼ正しく読み取ってるし・・・。
かあさん、オレ、がんばったよ☆☆☆

まぁ このプログラム作成そのものに50万枚くらい採点できる時間をかけてますから・・・

それと合算すれば、
たぶん、プラマイ0ですー!!

6.読み取り結果のチェック

マークシートリーダーで最も重要な部分は、マーク読み取りの正確さであることは言うまでもありませんが、読み取り結果のチェック機能も非常に重要であると考えます。

人によってマークの濃さや大きさは少しずつ異なり、また、マークを訂正した箇所に残る消し跡も判定に少なからぬ影響を及ぼします。常に100%正しい読み取り結果が保証されないのが現実ですから、如何に効率よく、読み取り結果をチェックできるかで、プログラムの使用感はずいぶん変わってくると思います(CPUパワーにかなり依存したプログラムを書いておいて、そう言うのもナンですが・・・)。

自分自身の書いたものがベストだなんて、到底、思えませんが、このプログラムを書くにあたり、マークの読み取り部分と同等か、それ以上に頑張って書いたのが、この読み取り結果のチェック部分です。

機械との協働。機械との融和。これをテーマに、ヒトと機械とが一体化しての「快適なチェック作業」の実現を目指しました。

・・・が、プログラミング技術の未熟。自分自身の勉強不足。見い出した妥協点。等々の理由により、視覚による機械と協働してのチェックも、聴覚(音声出力)による機械と協働してのチェックも、いずれも全面的にマシンのCPUパワーに依存した、もっさりした感のある処理となってしまいました・・・。

処理性能の高いマシンなら、それなりに快適に作業できると思うのですが。以下、チェック機能の使い方です。

マーク読み取り結果のチェックを実行。


上の図の左のGUIから説明します。

白紙にチェックすると、マークがひとつもないシートのチェックは行わない(飛ばす)設定で動作します。この機能はデフォルトでON(チェックあり)です。

マーク(解答)がなかった場合の読み取り結果の表示が「999」です(デフォルトOFFです。このプログラムでは、「空欄」のフラグを「999」としています。マークの番号にも、得点にも「999」は通常ないことがその理由です。ちなみに複数マークは「99」と表示しています。色は「999」が「青」、「99」が「赤」です。少しでも視覚に訴えた方がチェックしやすいと考えました)。

ごく薄い色でマークされた答案が混じっていないことが大前提ですが、答案全体(1クラス分!)のマークの濃さが十分「濃い」と保証されていれば、チェック開始時のみ「999」のチェックを外してチェック(機械がきちんと空欄を識別していることをヒトが目視して確認)、で、確実に空欄を見分けていることが確認できたら、「999」にチェックして続行。こうすれば大変スムーズな確認作業を実現することができます。あくまでもごく薄くマークされたシートがないことが大前提ですが・・・

いずれにしても「Check!」ボタンをクリックすると、プログラムは次の「空欄(999)」もしくは「複数マーク(99)」を探し、それが見つかった場合は該当箇所を赤い矩形で囲んで表示します。処理性能の高い(CPUパワーのある)マシンであれば、それなりに快適に動作しますが、そうでない場合は、かなり「もっさり」した動作になりますので、イライラするかも知れません。ごめんなさい。

【空欄と判定した場合】

マークがない場合の表示例(設問番号25が空欄であった場合)

【複数マークありと判定した場合】

複数マークと判定した場合の表示例(設問番号43が複数マークであると判定)


複数マークの判定はパラメータ設定を厳し目にしてあります(上の図はそれがわかるよう、大きめに表示しました)。ごく小さなシミは「平滑化(ぼかし)」処理である程度消えますが、ある程度の面積があるシミや汚れは上のように複数マークと判定されます。

いずれの場合も、ヒトの眼で確認して、訂正の必要がなければ「Check!」ボタンをクリックしてチェックを続行。読み取り結果の訂正が必要な場合は、正しい値を直接入力します(上の場合であれば「2」と入力してください)。


【処理をスキップして次のシートへ】

「Skip」ボタンをクリックすると、現在チェックしているシートの残りの部分のチェックを省略し、次のシートのチェックへ移動します。チェック対象シートの残りの行が全部空欄であった場合などに利用してください。


【チェックの再実行】

「ReDo」をチェックすると、初めからチェックを再実行できます。

ReDoにチェックすると表示されるメッセージ①
ReDoにチェックすると表示されるメッセージ②


【音声読み上げ】

読み取り結果が表示されているStringGridの任意の行をクリックして、「▶」ボタンを押すとWindowsに標準搭載されている日本語の音声合成エンジン(Microsoft Haruka Desktop)の音声で読み取り結果をアナウンスしてくれます。

マークの読み取りが正しく行われているか・どうか、少しでもラクに確認できないかと考え、この機能を搭載しました。処理性能の高いマシンでないと快適な動作は期待できませんが、CPUパワーのあるマシンであればそれなりに使えると思います。

「▶」ボタンの下にある「×」ボタンをクリックすると、音声読み上げを途中で中止することができます。


【列を指定して、任意の行からその列の最後の行までのチェックをスキップ】

数学用のマークシート等で、第1問の解答をシート第1列にマーク、第2問の解答をシート第2列にマーク、第3問の解答を・・・というような設定にしたい場合、「指定列の任意の行から最後の行までをチェックの対象から外す」ことができます。以下、その方法です。

任意の行を指定して、その行以降のチェックをスキップできます。


図のいちばん左にある「Skip」にチェックすると、この機能が有効になり、続けて「Check!」ボタンをクリックすると、ここでの設定に基づいたチェックを実行できます。

上の例であれば、1列目25設問あるうちの20設問目以降25設問目までのチェックをスキップ(チェックは19設問まで実行)、2列目は設問番号26から始まるので34設問目以降50設問目までを、3列目は設問番号51から始まるので70設問目以降75設問目までのチェックをそれぞれスキップします。スキップの設定はComboBoxへ入力した指定値「以降」であることにご注意ください。

また、シートの型式により、列の指定の可否をプログラムが自動的に判断し、ComboBoxのEnabled プロパティが設定されます(上の例では4列目は指定不可)。

「覚」ボタンをクリックすると、現在の設定を ini ファイルに書き込んで記憶します。「消」ボタンをクリックすると「設定なし」の状態に初期化できます。

数学用途等で2枚1セットの処理を実行する場合は、1枚目と2枚目を分けてスキップ処理の設定を行うことができます(数学用途の処理方法は後日掲載します)。

7.CSVファイルへの書き出し

マークの有無の読み取り結果は、CSVファイルとExcel Book への書き出しが可能です。

【CSVファイルへの書き出し】


「ファイルへの出力」にある「CSV」をクリックして選択し、「書き出し」ボタンをクリックしてください。


上記の場所にCSV形式で、読み取り結果が出力されます。

フィールド名として1行目に「設問番号」、レコード名としてA列に「マークシート番号」が書き込まれます。

8.Excel Book の準備作業

【Excel Bookへの書き出し準備】

Excel Book への読み取り結果の書き出しは、自分用に(あれば便利かなー☆)と思って作成したものです。ですので、式の入ったセルを保護する等、第三者が使うことへの配慮は何一つ行っていません。セルに入力された式やVBAの内容をご自身でメンテナンスできる方なら、お使いいだけるかな? という程度のシロモノです。

添付した Excel Book はこれまでに何度も「実際に使用して動作に誤りがないことを確認済み」ですが、誤って式を削除したりした場合は(当然ですが)意図した通りに動作しません。ですので、こちらも動作保証は一切ありません。ご使用はあくまでも自己責任でお願いします。この Excel Book に対しても、このプログラムの使用要件にあります免責事項がそのまま適用されますことを申し添えます。

以下、試験実施前に行っておくとよい採点準備作業です。

eFile フォルダに「一般用マークと手書き併用採点シート.xltm」というマクロ有効テンプレートがあります。これをダブルクリックすると「一般用マークと手書き併用採点シート1.xlsx」という名前で新しい Excel Book が作られます。拡張子に注意してください。「.xlsx」です。このままでは期待通りに動作しませんので、適切な名前を付け、拡張子を「.xlsm」(マクロが有効な Excel Book )に変更して eFile フォルダ(必ずこのフォルダに保存してください!)に保存します。

ここでは test.xlsm という名前で保存したことにして説明を続けます。

「コンテンツの有効化」をクリックしてマクロが実行できるようにしてください。


【インターネットからダウンロードしたマクロ有効 Excel Book の取り扱い】

いつからこうなったのか、わかりませんが、インターネットからダウンロードした拡張子 xlsm の Excel Book をダブルクリックして開くと、次のメッセージが表示されるようになりました。

「編集を有効にする」をクリックすると・・・
マクロを動かすことができません!


こうなった時は、いったん Book を閉じて、その Excel ファイルを右クリックして表示されるサブメニューのプロパティをクリックして、全般タブのいちばん下にある「セキュリティ:」の「許可する」にチェックします(チェックする=マクロの実行をご自身の責任で行うことになります。どうか、ご注意ください)。

全般タブの下の方にあるセキュリティの設定。
マクロの実行をご自身の責任で行う場合は、「許可する」にチェックしてください。


「許可する」にチェックした状態で、「適用」をクリックすると「セキュリティ」の表示そのものが消えます。「あなたの責任でマクロの実行が可能になりました」ということなのでしょう。「OK」をクリックしてプロパティの設定画面を閉じます。


これでマクロが実行できるようになります。


【欠席者がいた場合】

Excel Book を利用して採点する場合、大変重要な注意事項があります。それは欠席者がいた場合の処理です。該当試験に欠席者がいる場合は、その欠席者の出席番号位置に未使用のマークシートを挿入し、シートが確実に出席番号順に並んでいることを確認してから、スキャナーでスキャンしてください。
※ 可能であれば、この用途専用に未使用のマークシートを複数枚、最初から手元に準備しておくとよいと思います。

重要 未使用のマークシートを欠席者の出席番号位置に挿入しておく!

これを忘れると、あとから「すーぱーめんどくさい」コトになります(もし、忘れたらマークシートのスキャンからもう一度、採点をやり直した方が効率がいいかもしれません)。


【受験者の氏名データを準備する】

test.xlsm をダブルクリックして開き、「コンテンツの有効化」を行ったら、いちばん最初に「名票への貼付元名票」シートをクリックして開き、ここに「採点対象者全員分の氏名」を準備してください。

もっとわかりやすく言うと、採点したいテストを受験した生徒全員の「クラス・出席番号・氏名・ふりがな・性別」データを「クラスごと」に「出席番号順」で、「名票への貼付元名票」シートに用意します。なんで「ふりがな」まで必要なのか? 疑問に思う方もいらっしゃるかもしれませんが、最近の若い方々のお名前は難読である場合が多く、採点結果を個票でお知らせする際に、個票の氏名欄のところに「ふりがな」も印刷しておくとスムーズに答案返却が行えます。そのための「ふりがな」準備です。

また、テストの受験者全員分の氏名データを1シートに準備する理由は、次のような使い方を想定しているからです。

(1)同じテストを受験 ⇨ クラス毎に採点用 Excel Book を用意するのは非効率的。
(2)採点用 Excel Book は1個だけ作成し、これをコピーして全クラス分を作る。

具体的には、eFile フォルダの Excel Book(test.xlsm)をコピーして、クラス別(AHR.xlsm)に名前を変えて MS_Reader.exe がある場所に保存。採点結果もコピーした Excel Book(AHR.xlsm) に書き込みます。さらに、この作業はすべてプログラムから自動実行します。

採点者は、採点結果が書き込まれた Excel Book(AHR.xlsm)を開いて、「名票への貼付元名票」シートに用意した氏名データから「A組の受験者の氏名データ(クラス・出席番号・氏名・ふりがな・性別)を範囲選択してコピーし、「名票」シートに値のみ貼り付けます。

こうすることで同じ内容のファイルを複数個準備することなく、言わば「採点原本」として利用する Excel Book を1つ作成するだけで、試験を実施した全クラス分の採点が可能となります。

ここでは「クラス」と表現しましたが、用意する氏名データを適宜変更すれば「講座」等の採点もまったく同じように行えます。※ プログラムの仕様としては、1回の採点作業で採点する人数を100名以下と想定していますが、実際の採点作業は1採点40名程度で行っています。ですので、40名程度を1つのまとまりとして採点していただく方向でお考えください。


【正解を入力】

氏名データの準備が完了したら、「正解」シートをクリックして表示し、設問毎に「正解」の選択肢の番号を入力します。設問がない場合(無解答でよい設問番号の欄)は空欄のままにしておきます。入力したら、入力内容に間違いがないか、よく確認し、上書き保存してください。

正解の入力を間違えるとたいへんなコトになります!
慎重に入力し、最低2回は間違いがないことを確認してください。


【配点を入力】

次に、「マークシート配点」シートをクリックして「配点」を入力します。入力と同時に合計が自動的に計算されます。入力が完了したら上書き保存してください。なお、この配点表の下には観点別評価の表もありますが、この表には一切入力しないでください(観点別評価の表は入力禁止です)。

配点を入力すると「合計」が自動計算されます。
確認作業にお役立てください。


【観点別評価の区分を入力】

次に、「マーク&手書き観点別評価」シートをクリックして「観点別評価の区分」を入力します。
「知識・技能 ⇨ 1」、「思考・判断・表現 ⇨ 2」として設問毎に、半角数字で入力してください。デフォルト設定では、すべての設問に「1」が入っています。解答を要しない設問は「空欄」にしてください。入力したら上書き保存します。


以上で、試験実施前の準備は終了です!

9.Excel Book への書き出し

重要 すべての Excel Book を閉じてから実行してください!

危険 Excelが起動した状態で実行すると重大なエラーが発生します!

Excel へデータを書き込む際は、上記注意事項を必ずお守りください。この注意を忘れて Excel が起動したまま、Excel Book への書き込みを実行すると最悪の場合、Excel のプロセスが幽霊のように残り、これを終了することが出来なくなって、復旧するには、システムの再起動しかない状態になります。未保存の重要なデータがあるような場合、当然そのデータは失われます。Excel Book へのデータ書き込み時は、Excel が起動していないことを(タスクバーに眠っている Excel Book がないことも含めて)十分確認した上で、書き込み作業を行ってください。


【書き出し処理】

マークシートを読み取り後、読み取り結果のチェックまで完了したら、Excel Book への読み取り結果の書き出しが可能となります。次のようにマークシートリーダーを操作してください。

最初に、ファイルへ出力の Excel のオプションボタンをクリックして選択します。すると、その右側にある「選択」ボタンがクリックできるようになりますから、このボタンをクリックしてください。


ファイル選択のダイアログが表示されますので、読み取り結果を書き込む Excel Book をクリックして選択し、その後、下にある「開く」ボタンをクリックします。Pathの指定は、デフォルトで eFile フォルダになっています。準備作業で作成した test.xlsm を eFile フォルダに保存したのは、この読み取り結果を書き込む Excel Book を選択する作業を円滑に実行するためです。


次のメッセージが表示されます。

重要 ここで Excel が起動していないことを必ず確認してください!

選択した Excel Book が書き込み先として表示されていることを確認し、「書き出し」ボタンをクリックします。


書き込みには、しばらく時間がかかります。次のメッセージが表示されるまでお待ちください。


すぐに書き込み結果を確認する場合は、「はい」をクリックします(ここでは「はい」をクリックしたものとして説明を続けます)。

「はい」をクリックした場合は、エクスプローラーが自動的に開きます。先ほど選択した「test.xlsm」のコピーが「Scanner_A.xlsm」として、eFile フォルダではなく、MS_Reader.exe のあるフォルダに生成されています。


ファイル名がなぜ「Scanner_A.xlsm」になったかというと、マークシートの読み取り元フォルダとして選択したのが、ProcData\Scanner_A であったためです。プログラムは、マークシートの読み取り元フォルダの名称をそのまま、原本「test.xlsm」をコピーして生成する読み取り結果書き込み先 Excel Book の名称として利用します。

マークシートの読み取り元フォルダの名称が、Excel Book の名称になります!


マークシートの読み取り元フォルダの名称が「R05_情報Ⅰ_1A」であれば、MS_Reader.exe のあるフォルダに「R05_情報Ⅰ_1A.xlsm」が生成されます。

ここは、この仕様に慣れるまで混乱が生じやすいところと思われます。しかし、この仕様(仕組み)を十分に理解して、マークシートリーダーを使いこなしている職場の同僚からは「よく考えられた採点システムだと思います」と言ってもらえました。うれしかったなー!!


【成績一覧表の印刷】

生成された Excel Book をダブルクリックして起動します。起動したら「名票への貼付元名票」タブをクリックして開き、採点対象クラス(等)の氏名データを範囲選択してコピーし、「名票」タブをクリックして B3 セルに値のみ貼り付けます。次に「採点」タブをクリックしてください。次のような画面が表示されます。「氏名がある場合のみチェックする」ボタンをクリックしてください。画面上方に表示されている平均点が正しく再計算されます。なお、欠席者の得点は「0」と表示されていますので、この場合は手動で「受験確認」のチェックを外し、平均点の計算対象から除外してください。

「氏名がある場合のみチェックする」をクリックしてください。


このシートは通常の印刷操作で印刷できます。ただし、デフォルト設定で100名分を2枚に分けて印刷する仕様となっているため、成績一覧表が1枚でよい場合は、次のように指定して1ページ分のみ印刷を実行してください。

成績一覧表を1枚だけ印刷したい場合の設定


【観点別評価を行う場合】

観点別評価を行う場合は、「正答率」タブをクリックして、上と同様の操作を行ってください。欠席者がいた場合の処理も同じです(このシートは印刷しません)。


【個票の印刷】

最後に、試験の採点結果を受験者に知らせる成績個票を印刷します。よー書いた。さすがに私も疲れました。あと、もぉちょっとです!

「個人表」タブをクリックします。次のような画面が表示されます(表示倍率は異なります)。まず、考査名と科目名を入力してください(忘れやすい部分です! ご注意願います)。印刷はVBAでマクロを組んであります。設問数に合った「印刷(QXX)」ボタンをクリックしてください。

重要 セルを保護していません。誤って式を消さないでください!


次の印刷フォームが表示されます。開始番号と終了番号を入力し、「印刷実行」をクリックします。

重要 印刷は途中で中止できません!

VBAではプログラム書いてない!のに、Engterキー押し下げでフォーカスが移動します・・・

この印刷は Excel の仕様上、印刷データをためてからイッキにプリンタへ送信という方法が取れません。1枚ずつ送信しますので、ちょっとギクシャクした感じで印刷が実行されます。プリンタが壊れているわけではありません。


【個票を個別に確認したい場合は?】

受験者個々の個票を確認したい場合は、A2 セルに「採点」シートの通番を入力します。いろいろなクラスの生徒が混在した講座の処理に対応するため、入力値は「出席番号とは異なる」ことにご注意願います。

採点シートの通番を入力します!


個票を確認したい受験者の通番は「採点」シートを表示して確認してください。

受験者の通番を確認します。


【壊しちゃったときは?】

個人表シートを壊してしまった時は、次のようにすれば直せます。「個人票_Back」タブをクリックします(このシートは絶対に非表示にしないでください)。A 列の左、1行めの上(図の〇印を付けた部分)を右クリックしてシート全体を選択し、表示されるサブメニューのコピーをクリックします。

A 列の左・1行めの上を右クリック!
シート全部をコピーします。


個人表シートに戻って、先ほどと同じ A 列の左・1行目の上を右クリックして表示されるサブメニューの「数式fx」をクリックします(罫線データ等を壊してしまった場合は、すべてを貼り付けます)。

数式が壊れた場合は数式を貼り付けます。
面倒な場合は、いちばん左の全部「貼り付け」でもOK!

10.マークシート印刷用紙について

紙の「白さ」の度合いを「白色度」というそうです。このマークシートリーダーで読み取りに使用したマークシートはすべて「再生紙 or 再生コピー用紙」と呼ばれる紙に印刷したものです。

ですから、ここで紹介したマークシートの読み取り結果は、すべて「白色度70%」前後の「再生紙」に印刷してのもので、ホームセンターで一般的に販売されているような「白色度」が「再生紙」よりはるかに高い「真っ白に見える」用紙を用いての読み取り結果ではないことに、十分ご注意願います。

マークシートの印刷に使用する紙の「白色度」によっては、読み取りパラメータ設定の見直しが必要になるかもしれません(私自身は、実験・試行していませんので正確なことはわかりませんが)。入手可能なすべての紙について、実験することは現実的に無理でありますので、マークシートを印刷する用紙については、本ソフトウェア使用者の責任で十分な試行を行い、確実に動作するパラメータ設定を行った上で、このプログラムをお使いいただけますよう、お願いいたします。

印刷はインクジェットプリンタで行うことを推奨しましたが、長期にわたって使用していない(メンテナンスもしていない)インクジェットプリンタ(複合機)では、インクの吸い込みに問題が生じ、「いくら調整しても・何度クリーニングを行っても」期待した濃度での印刷ができないということも経験しました。サービスマンの方に伺ったところ、「こういう状態になると通常のクリーニングではなかなか復旧しない」と教えていただき、あらためて日常的に使用してインクを動かすことと、不具合が見えたらすぐにメンテナンスをお願いすることの大切さに気づいたこともあります。

そのサービスマンの方からは、マークシートに付着していた消しゴムの「屑」がスキャナーのローラー等可動部の動きを悪くして、マークシートがやや斜めにスキャンされたりする原因となり得ることも教えていただきました。実際に大量のマークシートを読み取ってきた複合機のスキャナー部分からは、かなりの量の消しゴム屑が・・・。受験者には消しゴム屑をよーく落としてから答案(マークシート)を提出するよう注意しておく必要があります。まさに塵も積もればなんとやら・・・です。

また、ご使用のスキャナーの読み取り設定によっては(デフォルトの読み取り設定が)0~255段階のグレースケールでなく、カラーであったり、ある閾値で白黒二値化しての読み取りであったりという、私の想定外の設定であることも、当然のようにあり得ると思います。それがカラー画像であった場合の影響はほとんどないと思われますが、ある閾値での白黒二値化画像であった場合は、判定に重大な影響を及ぼす可能性があります。ですので、マークシートの読み取りに、使用されるスキャナーの読み取り設定に関して、予め、使用者様の責任で十分ご確認いただけますよう、併せてお願い申し上げます。

11.まとめ

このマークシートリーダーで出来ること、出来ないことをまとめました。

【出来ること】

・マークシートのJpeg画像を回転&適切なサイズに縮小
・マークシート画像のマーク読み取り(1設問当たり最大16選択肢まで対応)
・読み取り結果の確認(GUI & 音声出力)
・読み取り結果のCSVファイル出力
・読み取り結果を採点結果通知用Excel Bookへ出力(新教育課程に対応)
・共通テスト形式の数学試験に対応(選択肢:-、±、0-9、記号:a~d)※ 後日掲載します。
・共通テスト形式の情報Ⅰ試験に対応(選択肢:0始まりの設定も可能)※ 後日掲載します。
・使用環境に合わせて各種パラメータ設定を変更可能
 ⇨ ScanSnap iX1500のノーマルモード(解像度150dpi相当?)、もしくはEPSON PX-M7110F(解像度200dpi)でスキャンしたJpeg画像のマーク読み取りに最適化した値をデフォルト値に設定済み。

【出来ないこと】

・1設問について、複数の解答が設定された採点
・前問の解答内容に応じて、次の問いの解答が変わる採点
・その他、答案1枚のみの採点等、このプログラムで想定外の採点全て
・1回の読み取り操作で処理できるJpeg画像は99枚までで、100枚を超える枚数は処理できません。

【その他の使用方法】

MS_Reader.exe の「ヘルプ」にある「PDFを表示」をクリックすると利用方法の手引きがお使いのPDFリーダーで表示されます。マークシートの作り方等、このブログの記事にないことも書いてありますので、必要に応じてこちらも併せてご参照いただけますよう、お願いいたします。

12.お願いとお断り

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

ini ファイルの Section の有無を確認

レジストリを汚したくない気持ちと、ini ファイルの手軽さから、僕はパラメータの設定を、いつも ini ファイルに記録してしまう。

今回、あるプログラムを見直していて、これまで ini ファイル内のセクション自体の存在を「確認」するようなプログラムを書いた経験がないことに気がついた。

ini ファイル内に「XXX」という名前のセクションが、あるか・ないか?
これは、それを確認する方法の覚え書き。

【もくじ】

1.もし、セクションがなかったら?
2.セクションの存在を確認
3.まとめ
4.お願いとお断り

1.もし、セクションがなかったら?

この秋から、マークシートリーダーのプログラムを見直す作業を、ヒマを見つけては行ってきた。
なぜ、そんな作業をすることにしたかというと、読み取り速度に目をつぶってもいいから、ファイル容量的に軽い(=小さな)マークシートリーダーも用意したくなったというのがその理由。

そもそも、自作のマークシートリーダーを作ろうと思い立ったのが、2020年の2月。最初はDelphi用のOpenCVライブラリを利用。読み取り速度より何より、「読めるかどうか」それすらわからない中でのチャレンジ。読み取り性能に重きを置いたのは言うまでもない。結果、読み取り性能的には十分満足できる、夢見た通りのプログラムを完成させることができた。その動作速度は(PCの性能にも左右されるが、My環境では)1枚あたり1200マークあるシートを、1枚あたり約1.2秒で処理。これだと40名分のマークシート読み取りに48秒、200名分ならば6分。

MyPCのスペック・・・プロセッサ:11th Gen Intel(R) Core(TM) i7-1185G7 3.00GHz / 実装RAM 32GB

その後、がむしゃらに高速化を目指した時期があって、その学びの中で、僕は embeddable Python の存在を知った。これにPython用のOpenCVその他のライブラリをインストールして、Pythonの力を借りることで上記のシートを0.34秒/枚、40名分を14秒以下、200名分を1分強で読み取れるマークシートリーダーへ、プログラムは進化。きちんとマークしてあれば、読み取りミスはもちろん「0」。マークの濃さや塗りつぶし面積をさまざまに変えて、読み取りパラメータの設定を調整した結果、相当薄いが明らかにマークしてあるものや、逆に、消し方が不十分でうっすらとマークの痕跡が残ったものにもそれなりに対応、さらに、読み取り結果をヒトと協働して確認できるチェック機能も搭載(非力なPCではややもっさりした動作になるが・・・)。ついでに音声読み上げ機能も欲しくなり、Windowsに標準搭載されている日本語の音声合成エンジン(Microsoft Haruka Desktop)のHarukaさんにも登場してもらって(Windowsの機能の利用だから著作権的には問題ないと思うんだけど・・・)ユーザーが選択した任意のシートのマーク読み取り結果を、簡単にチェックできるプログラムとした。

性能的な部分では、十分、満足した・・・けど、そのかわり、プログラム全体のサイズは超巨大化。exe それ自体は3.81MB程度なのだが、Python用のOpenCVをインストールしたフォルダの容量が、なんと158MB!(フォルダのプロパティで確認したところ、ファイル数: 2,820、フォルダー数: 283 とのこと)

これら、プログラムの動作に必要な一式をZipファイル一つにまとめて、他のPCにコピー&展開すると、PCによってはその展開(解凍)処理だけで20分くらいかかってしまう・・・。ライブラリには相当数、このプログラムには必要のないファイルも含まれているはず、そう思えても、どれが不要なファイルなのかが僕にはわからない。

(多少、動作速度は遅くてもよいから、サクっとコピーして、すぐに動かせるマークシートリーダーも欲しい・・・ )

ってか、exeのある場所にPython環境があれば、Python環境を利用して高速動作、Python環境がなければ(自動的に)Delphi用のOpenCVを利用して動作するような、Python環境を「後付けで追加することも可能」(ユーザーが動作環境を選べる)ようなカタチにしたいって、いつの間にか思い始めて・・・。

そのような経緯から僕は、プログラムの起動設定の見直し作業に着手した。それが冒頭に書いたようにこの秋の始まりの頃だった。

まず、行ったことはPython環境の完全な切り離し。せっかく、くっつけたものを切り離すのは大変だったけど、あっちこっちをいじくりまわして、なんとか切り離しに成功☆ Delphi用のOpenCVだけで動作する状態に戻せた。

内部的にはPython環境を利用する際に必要なコードはすべて残してあるので、次に exe と同じ場所にPython39-32フォルダがあった場合には、Python環境を利用して動作するようにプログラムを修正。これも何とか実現できた。

次に、ミニマム構成でプログラムを動かすために最低限必要なDLL等を確認。実際にミニマム(と思える)構成を作ってみて、動作テストを繰り返し行った。その中で、起動時に設定する読み取りパラメータに関して解決しておいた方がいい問題があることを発見。

Delphi用とPython用のOpenCVでは、起動時に設定するパラメータの一部が異なっている。Python環境の有無で(具体的にはPython39-32フォルダの有無で判断)、当然デフォルト・パラメータ設定を変えて起動させなければならない。その部分のプログラムを見直していて ini ファイルに「もし、読みだすべきセクション(名)そのものがなかったら?」という場合も想定しておいた方がいいことに、僕は初めて気がついた。

ちなみに、これまで書いてきたのは次のコード。これでも第2引数に指定したキーがなかった場合に加え、第1引数に指定したセクションそのものがなかった場合にも、第3引数に指定したデフォルト値が変数にセットされるから、エラーにはならないのだけれど・・・、ユーザーには「セクションそのものがない」ということが伝わらない。ユーザーがデフォルト値として設定されたパラメータを調整・保存して初めて ini ファイルに「Section1」が生まれる・・・。

uses
  System.IniFiles;

procedure TFrmMain.FormCreate(Sender: TObject);
var
  Ini: TIniFile;
  str01, str02: String;
begin
  //iniファイルからデータを読込み
  Ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  try
    str01:=Ini.ReadString('Section1', '文字列型_XXX', 'ABC');
    str02:=Ini.ReadString('Section1', '文字列型_YYY', 'DEF');
  finally
    Ini.Free;
  end;
  AAA.Text:=str01;
  BBB.Text:=str02;
end;

つまり、これまでの僕のプログラムは、各パラメータ値の設定とデフォルト・パラメータの設定を記録したセクションが「必ず ini ファイル内にある」という大前提で動いていたわけだ。Ini.ReadString の第3引数で「セクション」や「キー」がなかった場合のデフォルト値を指定してあるから、ini ファイル内にそれらがなくてもエラーは発生しないのだけれど、ほんとうにそれでいいのか? って、そう考えるとそれは「よくない」気がして・・・ならないし。

いちばん気になる、各パラメータ値の設定を記録した「セクションそのものがない」場合にはどうするか?、その対処方法を、知ってるか? って問われたら、その答えを僕はもちろん、知らない。そのことに、ここでようやく気付いた。この場合、知らないでは済ませるのは「すーぱー嫌」なので、良い機会だと思い、それを調べてみることにした。

2.セクションの存在を確認

調べてみると、やはりini ファイルにセクションが存在するか・どうかを調べるメソッドがちゃんと用意されていた。TIniFile の仕様的にエラーが発生しないから、このメソッドの必要性をこれまで僕は感じなかったんだろう・・・。

System.IniFiles.TCustomIniFile.SectionExists

https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.IniFiles.TCustomIniFile.SectionExists
SectionExists メソッドを使用すると,FileName で指定した INI ファイル内にセクションが存在するかどうかがわかります。Section は,SectionExists が存在することを決定する,INI ファイルのセクションです。SectionExists は Boolean 値を返して,対象のセクションが存在するかどうかを示します。 (上記リンク先より引用)


で、書いてみたのがこちら。

  //iniファイルから設定データを読込み
  Ini := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  try
    //Settingsセクションの有無を調査
    if Ini.SectionExists('Settings') then
    begin
      str1:=Ini.ReadString('Settings', '文字列型_閾値', '180');
      str2:=Ini.ReadString('Settings', '文字列型_平滑化強度', '41');
      str3:=Ini.ReadString('Settings', '文字列型_判定係数', '3');
      //Python39-32フォルダの有無を調査
      if System.SysUtils.DirectoryExists('Python39-32') then
      begin
        str4:=Ini.ReadString('Settings', '文字列型_手法', '正規化相互相関');
      end else begin
        str4:=Ini.ReadString('Settings', '文字列型_手法', '差分相関');
      end;
      str5:=Ini.ReadString('Settings', '文字列型_選択肢の始まり', '1');
      //str5:=Ini.ReadString('Settings', '文字列型_左補正', '');
      //str6:=Ini.ReadString('Settings', '文字列型_右補正', '');
      //str7:=Ini.ReadString('Settings', '文字列型_上下補正', '');
      str8:=Ini.ReadString('Settings', '文字列型_判定領域', '70');
      str9:=Ini.ReadString('Settings', '文字列型_読上速度', '3');
      //追加
      CheckZahyo.Checked:=
        Ini.ReadBool('Settings', '論理値型_座標チェック', True);
      CheckVolMixer.Checked:=
        Ini.ReadBool('Settings', '論理値型_音量ミキサー表示', False);
      boolSpeed:=Ini.ReadBool('Settings2', '文字列型_速度', False);
    end else begin
      //デフォルトのパラメータを表示
      str1:='180';
      str2:='41';
      str3:='3';
      //Python39-32フォルダの有無を調査
      if System.SysUtils.DirectoryExists('Python39-32') then
      begin
        str4:='正規化相互相関';
      end else begin
        str4:='差分相関';
      end;
      str5:='1';
      str8:='70';
      str9:='3';
      //追加
      CheckZahyo.Checked:=True;
      CheckVolMixer.Checked:=False;
      boolSpeed:=False;
      //設定を保存するようユーザーに案内するためのフラグを設定
      P4D_Exist:=False;
    end;
  finally
    Ini.Free;
  end;

プログラムが冗長化しただけ・・・のような気もするし、「セクションがない」場合には勝手にそれを作成して保存してしまう手もあるんだけど・・・。デフォルト・パラメータはあくまでも僕の環境(マークシートの紙質、画像化する際に利用するスキャナー、読み取り解像度、その他いろいろ)でのベストな値であって、環境が異なれば当然調整しなければならない場合もあり得る。そう考えると、もし、ini ファイル内にセクションそのものが存在しなかった場合は、それをユーザーに案内してきちんとセクションとして保存してもらえる仕様にするのがいちばんいいと思えてきた。

そこで、上記のように、ユーザーへの案内(通知)の要/不要を判断するフラグを用意。さらに、ユーザーへの案内(通知)は、Formが表示されてからの方が、なんとなく安心感があるかな? って思ったので、もし、セクションがなかった場合は、Form が表示された直後に案内(通知)するようにプログラミング。

private
    { Private 宣言 }
    //Python4Delphiの有無を知るフラグ
    P4D_Exist:Boolean;

procedure TFormMSReader.CMShowingChanged(var Msg: TMessage);
var
  strMsg: string;
begin
  inherited; {通常の CMShowingChagenedをまず実行}
  if Visible then
  begin
    Update; {完全に描画}
    if not P4D_Exist then
    begin
      strMsg:='読み取りパラメータの設定が、デフォルト値となっています。'+#13#10+
      '必要に応じて読み取りパラメータの調整を行い、'+
      '「設定を保存」ボタンがアクティブな状態で保存してください。';
      Application.MessageBox(PChar(strMsg), PChar('情報'), MB_ICONINFORMATION);
    end;
  end;
end;

セクションそのものがない場合には、i マークのアイコン付きでメッセージボックスを表示する。

3.まとめ

今回、初めて知った SectionExists メソッド。使わなくてもエラーは発生しないのだけれど、このメソッドについて調べたことで、あらためて「プログラムの仕様」について考える大変良い機会を得ることができました。いつか先輩に言われた言葉が胸によみがえります。

「きちんと動くプログラムが、いいプログラムなんだ。」

きちんと動く・・・って、その意味は、僕が思っていた以上に、深いようです。

4.お願いとお断り

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

クリーン・インストール

【20240204追記】

この記事で述べた方法では、問題は解決できません!(再発します)
この記事で述べている問題の正しい解決方法は、下記リンク先の記事をご参照ください。


Delphi のF9(実行)押下時に、原因がわからないエラーが発生(デバッグ出力)することに気づいた。
アプリケーションは何の問題もなく起動し、期待通りに動作、表面上はエラーも一切出ないのだけれど、水面下で「何か良くないこと」が起こってる感じ。

エラーを抱えたままの開発環境が作成した exe はさすがにヒトには配れない。なので、なんとかするべくいろいろ調べたが、なんと!情報の『欠片』すら見つからない・・・。困りに困って、頼るべき最終手段、サポートセンターに相談すると、親切丁寧なアドバイスを複数いただき、その中でDelphi自身の再インストールも状況改善のための選択肢の一つと知る。

「なるほど!」、早速実行。

再インストールは順調に終了。(再インストール時の)エラーの発生は皆無。
で、VCLアプリケーションを新規に作成し、実行(F9)すると

デバッグ出力:
clientcore\windows\advcore\ctf\shellhandwriting\client\handwritingclient.cpp(287)\Msctf.dll!77A3FC28: (caller: 77A345FD) LogHr(3) tid(988) 8007007E 指定されたモジュールが見つかりません。
    Msg:[onecore\internal\sdk\inc\wil\opensource/wil/winrt.h(1686)\Msctf.dll!77A37442: (caller: 77A3F94D) Exception(1) tid(988) 8007007E 指定されたモジュールが見つかりません。
]

プロセス Project1.exe (6408)

状況は変わらず・・・。まったく同じエラーが出現・・・。
そうか・・・。原因はDelphiじゃない。

Windows なんだ・・・。

もくじ

1.handwritingclient.cppって何だ?
2.インストールメディアを作成
3.クリーン・インストール
4.登録回数の上限に達しました
5.エラーが消えた!
6.まとめ
7.お願いとお断り

1.handwritingclient.cppって何だ?

去年の今頃、僕は「手書きカタカナ文字認識機能」を自作の手書き答案採点補助プログラムに搭載できないかと考え、毎日夢中で認識率100%を実現できる学習モデル作りに取り組んでいた。

Pythonのスクリプトもたくさん書いたけど、それ以外に、いろんなアプリも試した。
もしかして、それがエラーの原因に?

embeddable Python を内部的に動かして手書きカタカナ文字をPCに認識させるため、Lobeで学習モデルを作成し、GUIはDelphiで作って自分的には(OK!)と思えるレベルを達成(できたんだけど認識率100%じゃなかったから公開はしなかった)、あの時、相当トライ&エラーを繰り返してPCをフリーズさせたり、いろんなライブラリを思い出せないくらいインストールしたりしたからなー。でも、基本的にSDカードに仕込んだWinPythonで実験したから、Cドライブには影響ないと思うんだけど・・・。

(後に読み書き速度を向上させるため、使わなくなったノートPCから取り外したSSDをMyPCに外付けして、そちらにWinPythonを入れて実験を継続。もちろん、動作速度はめちゃめちゃ速くなった!)

でも、LobeはCドライブにインストールして学習モデル作ったしなー。
もしかして、原因はコレかなー?

いずれにしても、Windowsのどこかに不具合があることは間違いない。handwritingclient.cpp がどこからやってきたC++のソースファイルなのか? さえわかれば、手の打ちようもあると思うんだけど、それも解明できない。

僕の力では、この不具合は直せない。・・・とすれば、残された方法は一つ。
リカバリーメディアを作成して、PCを工場出荷時の状態に戻す。

そう、クリーン・インストールだ。

2.インストールメディアを作成

幸いにして日付時刻は週末の早朝。急いでしなければならない仕事はない。今日の用事を強いてあげれば、バイクのエンジンを動かすこと、それから、イケナイ水を買いに行くことくらいだ。それは夕方、いっぺんにできる。

( そう言えば、このPCのリカバリー用インストールメディア、作ってなかった・・・ )

僕はずっとPanasonic製の Let’s Note を使っている。現在の使用機種は CF-QV だ。高価なマシンだけど、頑丈で壊れないし、すごく入力しやすいし、重さも動作もすこぶる軽快。あの世に持っていけるものを一つだけ選べと言われたら、僕は間違いなく、Delphi を入れた Let’s Note を選ぶ。

スタートボタン ⇨ Panasonic PCリカバリーディスク 作成ユーティリティを起動。

初めて拝んだリカバリーディスク作成ユーティリティの起動画面(引用)。

昔はPC購入直後に必ずリカバリーディスク作って大切に保管してたんだけど・・・

いつのまにか・・・


手近にDVD-R DL(2層)メディアが数枚あった。これを外付けドライブに挿入して、リカバリーディスクの作成を開始。

( 80分かぁ ちょっと長いなー )

1枚目のディスクのチェックの進み具合を示すプログレスバーが半分くらいまで進んだとき、予期しないエラーメッセージが・・・

『・・・メディアの作成に失敗しました・・・』

ちゃんと made in Japan って書いてあるディスク使ったのにー T_T

仕方ないから、別のディスクに入れ替えて、再チャレンジ。

ところが、またチェックの進み具合を示すプログレスバーが半分くらいまで進んだところで、

『・・・メディアの作成に失敗しました・・・』

なんでー!

さすがに三度目の正直を目指そうとは思いませんでした。

PCを乗せた机が振動した可能性(はないと思ったけど、いちおう)も考え、PCの置き場所を変更。

さらに、外付けドライブも、むかし使ってた別のちょっと古い機種に変更。

さらに、使用するメディアも DVD-R( Made in Vietnam )に変更。

今度は順調に進行。1時間半ほどかけて無事DVD-R4枚のリカバリー用ディスクが用意できた。あとは、重要なデータをバックアップして、リカバリーするだけだ。

これまでに数限りなく、データのバックアップでイタいめにあってきて、今の僕は深い階層には一切データを置かないようになった。もうトシだし、ほんとにバカだから、どこに、なにを置いたのか、すぐに忘れる・・・、メモしてあっても、そのメモの存在を忘れてしまう。だから、リンク先確認等で起動に多少時間がかかろうと、そんなのは一切無視。重要なデータは全部デスクトップにおいている。

・・・とは言っても、現在、僕にとって重要なデータは2種類しかない。一つは、Delphiで書いたプログラムのソースを保存してあるフォルダとその中のファイルたち。もう一つは、その時々で遭遇したプログラミングする上で解決しなければならなかった問題と、その解決方法を記録したNaNaTreeのファイルたちだ。これがPictureやDBなど、分野別に分類して21ファイルある。更新し続けてもう20年・・・。Delphiを続ける限り、このファイルが完成することはない。僕が死んだその日に、更新が永遠に止まるだけだ・・・。

僕は、必要に応じて、この21個のファイルを、とっかえひっかえ表示しながら、ほとんどコピペを繰り返すようなスタイルでDelphiのプログラムを書いて(?)いる。だから、この21個のファイルには、僕とDelphiのこれまでのすべてが詰まっている。そう、僕のいちばんの『たからもの』だ。

これをバックアップすればPC本体はいつでもリカバリーできる。そこで別の外付けSSDを接続して現在デスクトップにある重要なファイルとフォルダのバックアップを新規に作成。リカバリーに成功したら、SSDのバックアップからデータをPCのデスクトップに書き戻す。

以前はPythonで書いたスクリプトも保存してたけど、Pythonの方はDelphi以上にコピペの集合体だし、バックアップを探すより、Webにある情報を検索した方が便利なことに気づいたことと、何よりPythonはスクリプト本体より、そこから呼び出しているライブラリが膨大なファイルを含んでいるため、バックアップに時間と容量が必要だったり、かつ、ライブラリのフォルダ階層が深すぎてコピー時にエラーが起きたり、とにかくバックアップそれ自体が何かと面倒で、いつの間にか、僕はWinPythonを仕込んだSDカードのバックアップをとるのをやめてしまった。

3.クリーン・インストール

外付けDVDドライブにリカバリー用のディスクを入れて、PCを再起動。なんか起こるかと思ったら、普通にWindows11が起動してしまった。そうだ、BIOSの起動設定を変更するのを忘れてた!

再起動してF2を連打。BIOS画面が表示される。こちらもMy PCで拝むのは、もしかして初めてかも。外付けDVDドライブの起動を最上位に持ってきて設定を保存。再々起動。

今度は見たことのない画面が表示され、リカバリーが始まった。ひたすら1枚目のDVDディスクを読んでいるようだ。じっと見ていても仕方ないので、いずれ「2枚目のディスクを入れてください」みたいな表示が出ると見込んで、別のPCをいじって過ごす。リカバリー完了までのインターネット接続はメインで使用しているPCの前に使っていたB5サイズの Let’s Note があるので、こちらを久々に起動して必要な情報を取得する。ただ、このPC。もう長いこと、OSのアップデートを行っていない。それどころか、前回、いつ、電源をONにしたのか・・・それすら記憶にない。

( このPCもメンテナンスしなきゃ )

気がつくと、リカバリーしているPCの画面に2枚目のディスクを求めるメッセージが表示されている。2枚目を入れると、また延々とデータの読み込みが始まった。これを3枚目、4枚目と繰り返す。

4枚目のディスクを挿入後、なんだか安心して、しばらくまどろんでしまった・・・。目が覚めたら、リカバリーは無事完了。PCは工場出荷状態に戻った。昔は、ここからの設定がほんとにたいへんだったけど、今はWeb上にほとんどすべてのMy設定が記録されているので、本当にセットアップが簡単になった。何回か、IDとパスワードなどを入力してWindows11が正常に起動する状態まで戻せた。

リカバリーする前のWindows11のバージョンは 22H2 だった。前に使っていたPCでWindowsの更新情報を検索して、最新のWindows11は23H2であることを知る(2023年12月上旬現在)。どうせならWindowsも最新の状態にアップデートしたい。そう思ってWindowsの更新をいくつか入れてるうちにWindows Update に 23H2 へのアップデートのリンクが表示されるようになった。ラッキー☆

迷うことなく、23H2へのアップデートを実行。PCは無事、復活。それも最新のOSを身にまとって。

4.登録回数の上限に達しました

今回のリカバリーの最大の目的は、Delphiのデバッグ出力で表示されるエラーを解消すること。OSは無事リカバリーできたから、エラーも解消されるはず。次は Delphi 12.0 のインストールだ。

my.embarcadero.comへ行き、Web Install のリンクをクリック!


インストーラーを取得して、起動。必要事項を入力して先へ進もうとすると・・・

登録回数の上限に達しました

でたー T_T また、コレかぁ・・・ まだ 12.0 インストールした回数は新規で1回、PythonEngineの登録で実験的にインストールしなおして1回、今回のリカバリー後の再インストールで1回、合計3回だと思うんだけど・・・。

しかも日付は土曜日。サポートセンターは間違いなくお休みの日。もちろん、明日も。

( 制限を解除してもらえるのは月曜日かぁ・・・ )

とりあえず、サポートフォームを開いて、「製品登録(使用許諾)の上限更新依頼」だけしておこう。

カチャカチャ・・・

・・・というわけで、Delphiのインストールは翌週に持ち越し(持ち越され)て、その他の必要なソフトウェアを導入して週末の1日を過ごすことに。

( 今、どれくらい空き容量が残ってるんだろう? )

ふと気になって、Cドライブの空き容量を確認。なんと!驚いたことに全460GBあるうち、100GB程度しか使ってない。あと350GBも、空きが残ってる。

( 確か・・・ リカバリーする前は、残りが100GBくらいだったぞ。いったいナニが入ってたんだ? )

容量を食いつぶすデータといえば、その筆頭はもちろん動画。しかし、僕はバイク関連のごくわずかな動画しかPCに保存してない。アプリはそれなりに入れてたけど・・・250GB分もそれがあったとは到底思えない・・・。

そんなことを考えていたら、突然、デスクトップに見たことのあるフォルダが次々に『全自動で生成』されて行く現象が発生・・・。あっけにとられた感じで、しばし呆然とこれを見つめる・・・

( そうか、OneDriveのデスクトップとデフォルトで同期がONなんだ・・・ )

( 今、クラウドにあるOneDriveのデータは、いずれ、そのうちMyPCに・・・ )

( これまではOneDriveになんでも投げ込んでたから・・・ ちりも積もればナントカで・・・ )

うわー☆ 同期の設定OFFにしないと
またすぐ空き容量がなくなるー!!

慌てて画面右下の雲のアイコンを右クリックして設定を開き、同期とバックアップの「バックアップを管理」ボタンをクリックして、「このPCのフォルダーをバックアップする」の設定をすべてOFFにする。僕は、僕自身が選んだデータだけ、OneDriveに置いておきたいんだ。バックアップは自己責任で行うよ。Windowsに全部、面倒を見てもらいたくない。PCのストレージ容量が「無制限」で、OneDriveも保存容量に「制限がない」なら、全自動バックアップ大歓迎だけど・・・。


これまで気がつかなかったけど、空き容量が減ってたのは、たぶん、この設定も原因の一つだ・・・。PCをリカバリーして、今までぼんやりとしか見えてなかったものが初めて『はっきり・くっきり見えてきた』ような・・・気がする。

あとは月曜日を待って、Delphiをインストール。
F9押下時のエラーが消えれば(絶対消えるはずだけど)・・・

もっと『すっきり』するんだけどなー☆

5.エラーが消えた!

月曜日、「製品登録(使用許諾)の上限更新依頼」に対する返信があり、登録回数の上限が更新されたことを知る。Delphi 12.0 を再インストール。

Python4Delphiがない状態で確認。

新しくVCLプロジェクトを作成。保存して、F9(実行)押下。

デバッグ出力にエラーは出ない。

( やった。多分、もう、大丈夫だ )

いったん、Delphiを終了。

Delphiを再起動して、GetItパッケージマネージャからPython4Delphiをインストール。

既存のプロジェクト(マークシートリーダーのプロジェクト)を呼び出す。

F9(実行)押下。

エラーメッセージは表示されない!(P4DのインストールもOK!)

Delphiが直ったー☆☆☆

6.まとめ

エラーの根本的な原因がわからないまま、リカバリーという最も強引で、安全、確実な方法で問題を解決することになったが、目的外に実にいろんなことを経験できたリカバリー作業だった。

なぜ、DVD-R DLの書き込みに2回連続失敗したのか?

気になったので調べてみると、あちこちで同じような経験をした人がかなりいるようで、「片面2層」の DVD-9 でディスクの枚数を減らすより、ディスクの枚数は増えても、片面1層(約4.7GB)の DVD-5 を使用した方が書き込みエラーが発生する頻度は低いようだ。

メディアと機械の相性とか、いろんな問題がありそうだけれど、画質にこだわりつつ、ディスク1枚に動画を記録するのとは明らかに目的が異なる(今回のリカバリーディスク作成みたいに)単にデータが記録できればOKって場合は、片面1層のメディアを選んだ方がいい。DVD-R DLへの書き込みに失敗した本当の原因は、こちらもわからないままなんだけど、経験としてリカバリー用途のメディアには何を使ったら良いのかがとてもよくわかった。

それから、今まで「わかっているようで、実はわかっていなかった」OneDriveの使い方を本気で考えるイイきっかけにもなった。

今回のリカバリー前後で比較したCドライブの空き容量の違いに気づいたことで、自分にとっていちばん便利なOneDriveの使い方(=付き合い方)が見いだせたのはとてもラッキーだった。なんでもかんでもOneDriveに保存することは止めて、いつでも・どこでも参照したい必要最小限度のデータのみを保存し、その他の重要なデータは複数の外付けのSSDにバックアップを取る。そうすれば、僕にとって必要なSSDの容量は、150GB程度であることまで明らかにできた。今後、新しいPCの購入を考える際にも、この数字は役に立ちそう。

あと、今後、役立つことはまぁないと思うけど、Delpjiの登録回数の上限もなんとなくわかってしまった・・・。これを知るためだけにインストールとアンインストールを繰り返す人はまずいないだろうから、ある意味、これは貴重な情報と言えば言えるかもしれない。

僕はDelphiという開発環境と、Object Pascal というプログラミング言語が好きだし、その文化の存続を願うから、例えそれを高額と感じても正規の利用料を支払って、Delphiのブラッシュアップを続けてくれている Embarcadero Technologies さんの発展を支えたい。

何より、最大の目的だったDelphiのF9(実行)押下時のエラーを解消できた。これで安心してプログラムが書ける。それがいちばんうれしい。プログラムを書いていると、自分自身がよい方向に歩いているような気がしてくる。それが何よりもうれしくて、僕はプログラムを書くのかもしれない。

僕がこの世から消えた後も、それが動くことを願って。

7.お願いとお断り

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

RAD Studio 12.0にPython4Delphiをインストールする!(その2)

前回、Delphi 12.0 に Python4Delphi をインストールする記事を書いた。

次は、その過去記事へのリンク。


実は、もっと、イイ方法が・・・☆☆☆

今まで知らなかったのですが、ウェルカムページの右上にある「GetItパッケージマネージャ」からインストールすれば、さらにカンタンでした!!

【もくじ】

1.エラーが消えない!
2.GetItパッケージマネージャからP4Dをインストール
3.まとめ
4.お願いとお断り

1.エラーが消えない!

いつからそうなったのか、わからないのだけれど、Delphi 11.2 & 12.0 で F9(実行)するとデバッグ出力にエラーがいくつか表示されるようになった・・・。

デバッグ出力:
clientcore\windows\advcore\ctf\uim\tim.cpp(800)\Msctf.dll!7577215A: (caller: 7576D910) LogHr(1) tid(1ff8) 8007029C アサーション エラーが発生しました。

プロセス XXX.exe (5568)

デバッグ出力:
clientcore\windows\advcore\ctf\uim\tim.cpp(800)\Msctf.dll!7577215A: (caller: 7576D910) LogHr(2) tid(1ff8) 8007029C アサーション エラーが発生しました。

プロセス XXX.exe (5568)

デバッグ出力:
onecore\internal\sdk\inc\wil\opensource/wil/winrt.h(1686)\Msctf.dll!757A7442: (caller: 757AF94D) Exception(1) tid(1ff8) 8007007E 指定されたモジュールが見つかりません。

プロセス XXX.exe (5568)

デバッグ出力:
clientcore\windows\advcore\ctf\shellhandwriting\client\handwritingclient.cpp(287)\Msctf.dll!757AFC28: (caller: 757A45FD) LogHr(3) tid(1ff8) 8007007E 指定されたモジュールが見つかりません。
    Msg:[onecore\internal\sdk\inc\wil\opensource/wil/winrt.h(1686)\Msctf.dll!757A7442: (caller: 757AF94D) Exception(1) tid(1ff8) 8007007E 指定されたモジュールが見つかりません。
] 

プロセス XXX.exe (5568)

「handwritingclient.cpp」ってなんだろ? ・・・と、Google先生にお伺いをたてても、関連する情報は何一つ得られず、「Msctf.dll」の方は検索結果にいろんな情報が出てくることは出てきても、なんかみんな的外れな感じで、走召!困った。

どうにもならないので、サポートセンターに援けを乞う。

サポートセンターの担当者の方と何度かメールでのやりとりを行った結果、Delphi 11.2 & 12.0 をどちらもアンインストールして、12.0 のみを再インストールすることに。

アンインストール & 再インストール作業は何のエラーもなく進行。

で、結果から言うと、F9(実行)で「エラーはそのまま表示され、消えません」でした。

T_T

エラーは消えなかったのですが・・・

ふと、思いついて update の有無を確認しに行った GetItパッケージマネージャで「愛しのP4D」を発見!

( こんなところにあったんだ。何で今まで気づかなかったんだろう? )

そう思いながら「インストール」ボタンを「ポチ」っとクリック。

結果・・・

今までの大苦労はなんだったんだ・・・
そう思うくらい、あっけなく
P4Dがインストールされました!

2.GetItパッケージマネージャからP4Dをインストール

転んでもタダでは起きない・・・とはまさにこのこと? 以下、GetItパッケージマネージャからP4Dをインストールする方法です。

Delphi 12.0 を起動すると表示される(デフォルト設定)、ウェルカムページ右上の

 ↑
これをクリック。

すると、次の画面が表示されるので、オプションボタンを図のようにクリック。

カテゴリ「Python」のオプションボタンをクリックすると、そこにP4Dがある。


ちなみに、著者の方のお名前の部分をクリックすると、GitHub の Python4Delphi のページに飛びます。

GitHub の Python4Delphi のページ

https://github.com/pyscripter/python4delphi


続けて、「インストール」をクリック。

「すべて同意する」をクリック。


インストールはすぐ完了。

カンタンなこと、この上なし!
七匹のヘビを無事発見!

3.まとめ

Delphi に Python4Delphi をインストールするなら、GetItパッケージマネージャからインストールするのが「走召」カンタンで便利!

さて、Delphi の「消えないエラー」をどうしよう・・・


追記(20241005)

2024年2月4日に、この問題の解決策を見つけていました!

僕のPCは、上記リンク先の方法で、正常な状態に復旧できました。
(ここに追記するのを忘れてました。ごめんなさい!!)

4.お願いとお断り

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

VCLのMessageBox

Delphi 12.0を使い始めて、最初に気づいたことは、メッセージダイアログの異変だ。

( なんだか、シンプルになったなー )

Delphi 12.0 のメッセージダイアログ


そう。MessageDlgからアイコンが消えちゃった!

【もくじ】

1.Delphi 12.0 のメッセージダイアログ
2.MessageBoxがあった!
3.MessageBoxの使い方を学ぶ
4.MessageBoxの使用例
5.ダイアログからの戻り値
6.まとめ
7.お願いとお断り

1.Delphi 12.0 のメッセージダイアログ

今まで( Delphi 11.2 )なら・・・

i マークのアイコンがあるメッセージダイアログを表示するコード


実行すると・・・

表示されたメッセージダイアログ


System.UITypes を uses してないと・・・

「System.UITypesをusesしなさい」とアドバイスされる(これは 12.0 も同じ)。

Delphi 12.0 では・・・、System.UITypes は、もちろん uses して、

Delphi 12.0 でメッセージダイアログを表示


11.2 と同じコードを実行すると・・・

青地に白の i マークのアイコンが「ない」


それに、なんかエラーも。

僕には理解不能


( どうする? )

取り敢えず、Help でVCLのライブラリ・リファレンスを調べてみた。

キーワードを指定してEnterキーを押すと・・・


表示されたリファレンスの下の方に、次の一文を発見!

mtInformation じゃなくて、mtConfirmation だけど「Microsoftは、・・・削除してしまいました。」とある・・・


( これかー T_T )

mtInformation ではなく、mtConfirmation で試してみると・・・

引数を mtConfirmation に変更


実行すると・・・

アイコンは表示されないが、タイトルは「確認」に変わる


タイトルが「情報」から「確認」に変わるから、mtConfirmation は確かに効いてる。

「このアイコンがメッセージの具体的な種類を明確に表さない」って、んじゃもっと良いアイコンに変更するとか・・・、元々、アイコンはキャプションの補佐的役割を果たしているだけだから、それが「ない」よりは「あった」方が親切だと思うんだけれど・・・。

リファレンスによれば、第2引数に指定可能な値とその意味は次の通り。

Delphi 12.0 の Help の VCL のライブラリ・リファレンスを引用


リファレンスには「以前のダイアログ ボックスの概観を使用するには、Vcl.Dialogs ユニットの UseLatestCommonDialogs 変数を False に設定しなければなりません。」と但し書きがある。

以前の・・・って、XPの時代のことかな?・・・なんて思いながら、次のように指定して、

UseLatestCommonDialogs 変数を False に設定


実行すると・・・

さらにシンプルに・・・T_T


僕的には「シンプルじゃないのがよかった」ので、
とても「Simple is Best 」的な気持ちにはなれない!

あぁアイコンが欲しい!

もう、二度と・・・


きみに会えないかと思うと。

魂が折れそうなくらいの、さみしさがこみあげて・・・くる。

なんでだよー!

そう思いながら、ついでに mtError も試してみる。すると・・・

エラーのアイコンは表示される


「 × 」マークはMicrosoftさん的には、全世界共通「ダメです」って通じるってこと?


mtWarning もやってみた!

警告もアイコンは表示された


黄色の三角形に「i」が表示されたら、世界中の人が「警告」だって思うのかなー☆
Microsoftさん的には、「思う」ってことなんだろうなー。


ともあれ、これで確認できた。「情報」と「確認」がダメなんだ・・・。

でも、ない袖は振れない。

自分専用のダイアログを作るという、奥の手がないわけじゃないけど、それは最終手段。

MessageDlg のかわりになるものは・・・、ShowMessage 以外になんか、なかったっけ?

2.MessageBoxがあった!

ものごころついた時から・・・ってわけじゃないけど、僕はずっと MessageDlg関数を使ってきた。
ダイアログで使用する Font の大きさや色を変えたいなど、余程の理由がない限り、ユーザーに何かを伝えるいちばんの方法は、僕にとっては MessageDlg関数 で表示するメッセージだった。

それが ShowMessage ではない最大の理由は、テキストの他にアイコンも表示できるから。

だから、階層化テキストエディターの NaNaTree に書き溜めてきた、さまざまなメッセージの表示方法に関する覚書も、そのほとんどが MessageDlg関数 についてのもので、これにかわる代替手段など、これまで僕は考えたこともなかった。

( バージョンアップとかすると、毎回、いろんな落とし穴があるけど・・・ )

( 今回のも、これまで普通に使ってきたダイアログの仕様変更だもんなー )

( すーぱー困るけど、元に戻してくれるわけないし・・・ )

( なんか、代替手段、なかったっけ? )

そう思いながら、NaNaTree に保存した「メッセージ」に関する記録を下のほうへスクロールしてみる。

すると・・・

MessageBoxなる文字が・・・


あんまり使ったことのない、未整理の項目が下の方にあって、その中にMessageBoxという文字を発見。

( ほぼ使った記憶がないけれど、そんな関数もそう言えばあった・・・ )

さっそく、Helpを読んで、書き方を確認し、次のコードを実行してみた。

MessageBox関数を試す


はたして、アイコンは表示されるか?

祈るような気持ちで実行すると・・・

アイコン付きのダイアログが表示された!

何にも(追加で) uses しなくてイイし、いきなり書いて、すぐ動く!

コレだ! コレ!!

今度からコレで行こう☆

自分でもあきれるくらいの変わり身の早さ・・・

MessageDlg が泣いてるぞ。

愛してたんじゃなかったのかい・・・?

3.MessageBoxの使い方を学ぶ

VCL ライブラリの Help を読んでわかったことは、「MessageBox は、Windows API MessageBox 関数をカプセル化したものである」ということ。

このカプセル化で「具体的に何がどうなったか」と言うと、Windows.MessageBox とした場合には必要であったウィンドウ ハンドル パラメータが欠けていても、自動的に補完される、つまり、所有者ウィンドウへのハンドルは引数内で指定しなくてもよいということ。

ありがたき しあわせ。

ちなみに、Win32APIのリファレンス(C++)では、次のようになっているが、

int MessageBox(
  [in, optional] HWND    hWnd,
  [in, optional] LPCTSTR lpText,
  [in, optional] LPCTSTR lpCaption,
  [in]           UINT    uType
);

Delphi の Application.MessageBox のリファレンスでは、次のように

function MessageBox(const Text, Caption: PChar; Flags: Longint = MB_OK): Integer;

所有者ウィンドウへのハンドルが確かに省略されている。

実際のコードで、Windows.MessageBox とした場合には・・・

procedure TForm1.Button4Click(Sender: TObject);
begin
  Winapi.Windows.MessageBox(Handle, PChar('Do you know Delphi?'), PChar('情報'), MB_OK or MB_ICONINFORMATION);
end;

だったのが、第一引数のHandle は必要なくなり、( OK ボタンのみの表示でよければ)MB_OK も省略できるようなので、次のように

procedure TForm1.Button3Click(Sender: TObject);
begin
  Application.MessageBox(PChar('Do you know Delphi?'), PChar('情報'), MB_ICONINFORMATION);
end;

・・・とずい分、短くなる。それどころか、PChar型への型変換も省略可能なようで・・・

procedure TForm1.Button5Click(Sender: TObject);
begin
  Application.MessageBox('Do you know Delphi?', '情報', MB_ICONINFORMATION);
end;

型変換も内部で自動的に処理してくれるようだ(このまま実行可能。エラーも、警告も、ヒントも表示されない)。

Good! Gooder! Goodest!

これからは MessageBox で行こう!

4.MessageBoxの使用例

「 i 」マークのアイコンで、OK ボタンのみ表示する場合は、

Application.MessageBox(PChar('メッセージ'), PChar('情報'), MB_ICONINFORMATION);


警告マーク(黄)

Application.MessageBox(PChar('メッセージ'), PChar('警告'), MB_ICONWARNING);


禁止・エラー・停止マーク(赤地に白)

× の意味は、第3引数にSTOPとあるから「停止」が正しいのかな?

Application.MessageBox(PChar('メッセージ'), PChar('禁止'), MB_ICONSTOP);
Application.MessageBox(PChar('メッセージ'), PChar('エラー'), MB_ICONSTOP);


?マーク(青地に白)

Application.MessageBox(PChar('メッセージ'), PChar('質問'), MB_ICONQUESTION);

文字列型の変数を用意して、

var
  strMsg:string;
begin
  strMsg:='メッセージ';
  Application.MessageBox(PChar(strMsg), PChar('情報'), MB_ICONINFORMATION);
end;


別の文字列型変数をさらに代入したり、また、改行を含む表示も、

procedure TForm1.Button2Click(Sender: TObject);
var
  strMsg, strPath:string;
begin
  strPath:='C:\abc\def';
  strMsg:='出力先は次の場所です。' + #13#10 + #13#10 + strPath;
  Application.MessageBox(PChar(strMsg), PChar('情報'), MB_ICONINFORMATION);
end;

複数のボタンを表示。例えば、「はい」・「いいえ」の二択なら、

procedure TForm1.Button3Click(Sender: TObject);
begin
  //Information
  if Application.MessageBox(PChar('Do you know Delphi?'), PChar('情報'), MB_YESNO or MB_ICONINFORMATION) = mrYes then
  begin
    //[はい]が選ばれた時
    Application.MessageBox(PChar('Gooooooooooooood!'), PChar('情報'), MB_ICONINFORMATION);
  end else begin
    //[いいえ]が選ばれた時
    Application.MessageBox(PChar('No!'), PChar('情報'), MB_ICONINFORMATION);
  end;
end;
二択だから「キャンセルはない」
(閉じるボタンは自動的に無効になる)

ユーザーに「キャンセル」も許可するなら、

procedure TForm1.Button4Click(Sender: TObject);
var
  StrMsg: String;
  intRet: Integer;
begin
  StrMsg := 'Do you know Delphi?';
  intRet := Application.MessageBox(PChar(StrMsg), PChar('情報'),
                         MB_YESNOCANCEL or MB_ICONQUESTION);
  if intRet = mrYes then begin
    //[はい]を選択した時の処理

  end else
  if intRet = mrNo then begin
    //[いいえ]を選択した時の処理

  end else
  if intRet = mrCancel then begin
    //[キャンセル]を選択した時の処理
    Application.MessageBox(PChar('ユーザーによる処理のキャンセル'), PChar('情報'), MB_ICONINFORMATION);
  end;
end;
閉じるボタンは自動的に有効化されている


ちなみに「キャンセル」ボタンではなく、ダイアログ右上の「閉じる」ボタンをクリックすると・・・

閉じるボタンをクリックした場合は、キャンセル扱いになるようだ。

デフォルトでフォーカスを与えるボタンの指定方法は、第3引数の中で MB_DEFBUTTON3 のように、MB_DEFBUTTON の後ろにフォーカスを与えるボタンの番号を付けて指定する。番号はダイアログに表示するボタンの左から順番に1、2、3、4となるようだ。

第3引数に MB_YESNOCANCEL or MB_DEFBUTTON3 or MB_ICONQUESTION を指定すると、

左から三つめの「キャンセル」ボタンにフォーカスされた状態でダイアログが表示される


数字を付けない MB_DEFBUTTON や、数字を付けても MB_DEFBUTTON0(ゼロ)や MB_DEFBUTTON5 は未定義の識別子エラーになることから、指定可能なボタンの数は1~4の範囲内と決まっているようだ。

MB_DEFBUTTON5 は未定義の識別子エラーになる


「はい・いいえ・キャンセル」の三つボタンを表示する設定で MB_DEFBUTTON4 を指定しても未定義の識別子エラーにはならないし、実行時コンパイラはヒントも警告も表示しないが、フォーカスは最も左のボタンに当たるようだ。

intRet := Application.MessageBox(PChar(StrMsg), PChar('情報'),
                         MB_YESNOCANCEL or MB_DEFBUTTON4 or MB_ICONQUESTION);
MB_DEFBUTTON4 を指定してもエラーにはならない。
MB_DEFBUTTON4 を指定したにもかかわらず、
4つめのボタンがない場合には、最も左のボタンにフォーカスされる。

ってか、四つ目のボタンでナニ?

想像したけど、ちょっと思いつかない。表示できるボタンの種類を調べてみた。

MessageBox に表示できるボタンの種類は全部で七つあるようで、Flags パラメータで指定可能な値は、次の通り(Delphi 12.0 の VCL リファレンスより引用)

意味
MB_ABORTRETRYIGNORE メッセージ ボックスには、次の 3 つのボタンが配置されます: 中止、再試行、無視。
MB_OK メッセージ ボックスには、次のボタンが配置されます: OK。 これがデフォルトの設定です。
MB_OKCANCEL メッセージ ボックスには、次の 2 つのボタンが配置されます: OK、キャンセル。
MB_RETRYCANCEL メッセージ ボックスには、次の 2 つのボタンが配置されます: 再試行、キャンセル。
MB_YESNO メッセージ ボックスには、次の 2 つのボタンが配置されます: はい、いいえ。
MB_YESNOCANCEL メッセージ ボックスには、次の 3 つのボタンが配置されます: はい、いいえ、キャンセル。
Flags パラメータで指定可能な値

リファレンスには、さらに「これらの値は、希望する効果を得るため、組み合わせて使うこともできます。」とある。組み合わせるって、どういうこと?

ちなみに、MB_OK と MB_RETRYCANCEL を組み合わせてみると・・・

procedure TForm1.Button5Click(Sender: TObject);
begin
  Application.MessageBox('Do you know Delphi?', '情報', MB_OK or MB_RETRYCANCEL or MB_ICONINFORMATION);
end;

OK と 再試行、キャンセル 三つのボタンが表示されるのかなー? って思ったけど・・・

OK ボタンは表示されませんでした!


これは MB_OK と MB_ABORTRETRYIGNORE を組み合わせても同じで、MB_OK はやはり表示されない。MB_DEFBUTTON4 の役割は何なんだろう???

Web上の資料を調べてみた!

MessageBox 関数 (winuser.h)

https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-messagebox

上記リンク先の説明によれば MB_HELP もあるらしい。

Application.MessageBox('Do you know Delphi?', '情報', MB_OK or MB_HELP or MB_ICONINFORMATION);

実行してみると・・・

OK とヘルプは共存できるようだ

ただ、ヘルプをクリックしても、なんにも起きなかったが・・・
(処理を書いてないんだから、当然と言えば、当然)

しかし、戻り値の中に「ヘルプ」に相当する値がない・・・ような気が。上記の資料には「ユーザーが [ヘルプ ] ボタンをクリックするか F1 キーを押すと、 WM_HELPメッセージが 所有者に送信されます。」とあったけど、これ以上、追いかけると深みにはまりそうな気がするので、ヘルプに関する勉強は後日それが必要となった時にあらためてすることにして、ここではお茶を濁す。

結局、MB_DEFBUTTON4 の果たす役割はわからずじまい。残念!

それから、MessageBox関数には「すべてはい・すべていいえ」のボタンがないようだ。これもまぁ、それが必要になった時、考えることにする。僕が書くプログラムでそれが必要になることは、まず、ないだろう・・・。

あとわかったことは、「Text パラメータの値はメッセージで、必要なら 255 文字以上になっても構いません。 長文メッセージは、メッセージ ボックスでは自動的に改行されます。」ふむふむ、なるほど。

さらに「Caption パラメータの値はキャプションで、ダイアログボックスのタイトル バー上に表示されます。 Captions は 225 文字より長く指定できますが、改行されません。長文キャプションの場合、メッセージ ボックスの幅が広げられます。」とのこと。

5.ダイアログからの戻り値

ダイアログからの戻り値は、次の通り(Delphi 12.0 の VCL リファレンスより引用)

定数意味
mrNone0 なしユーザーが終了する前にデフォルト値として使用される
mrOkidOK ユーザーが[OK]ボタンで終了した
mrCancelidCancelユーザーが[キャンセル]ボタンで終了した
mrAbortidAbortユーザーが[中止]ボタンで終了した
mrRetryidRetryユーザーが[再試行]ボタンで終了した
mrIgnoreidIgnoreユーザーが[無視]ボタンで終了した
mrYesidYesユーザーが[はい]ボタンで終了した
mrNoidNoユーザーが[いいえ]ボタンで終了した
ダイアログからの戻り値

6.まとめ

プログラムからユーザーに対してメッセージを表示する方法は、他にもいろいろあるみたいだけれど、MessageBox関数最大の強みは「すべての OS で利用可能」なこと。

OK と、はい・いいえ、キャンセル の各ボタンが利用できれば十分というのであれば、アイコンが表示されなくなった MessageDlg関数のかわりに MessageBox関数が使える。もちろん、ダイアログにアイコンも表示される。

ただ、これまでに書いてきたプログラムを Delphi 12.0 で修正・更新する際にはMessageDlg関数を、MessageBox関数に変更するという、地道な作業が待ってることだけがちょっと気になるが。

まっ ヒマだから、いいか!

7.お願いとお断り

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

RAD Studio 12.0にPython4Delphiをインストールする!

追記(20231208)

さらにカンタンな方法がありました!

https://coding-tips-memoranda.com/rad-studio-12-0%e3%81%abpython4delphi%e3%82%92%e3%82%a4%e3%83%b3%e3%82%b9%e3%83%88%e3%83%bc%e3%83%ab%e3%81%99%e3%82%8b%ef%bc%81%ef%bc%88%e3%81%9d%e3%81%ae%ef%bc%92%ef%bc%89/

以下、苦労を伴うインストール方法の記録です(お読みいただく価値のない情報です)。 T_T

2023年11月8日、RAD Studio 12.0(僕にとってはDelphi 12.0)がリリースされた(ようです)。
アップデート・サブスクリプションの支払いを終え(個人で購入しているのは僕くらいだろうが・・・)、届いたメールの製品アップデートリンクをクリックして、最新の更新をチェックしたら、12.0が!

(誰も教えてくれないから、リリースされたこと自体、まったく知りませんでした! T_T )

そういえば・・・RAD Studioのメジャーアップデートは毎年この時期だったような。

あわわわわわわわわわわ ひー!ひー!(驚愕的感動を表現)

早速、Web Installを実行。

Delphi 12.0 のインストールは何の問題もなく、15分くらいで終了(XEの頃は時間がかかったけど)。

11.2 が入っている環境にインストールしたためか(?)、シリアルナンバーの入力なども一切ありませんでした! カンタン。気持ちいい。

続けて、Python4Delphiも最新版(RAD Studio 12.0対応版)をインストール。

以下、その時のメモです!

【もくじ】

1.Python4Delphiの最新版をダウンロードして展開する
2.フォルダ構成を整える
3.Python4Delphiの最新版(RAD Studio 12.0対応版)をインストール
4.ライブラリパスを確認
5.まとめ
6.お願いとお断り

1.Python4Delphiの最新版をダウンロードして展開する

まず最初に、Python for Delphi(P4D)をGitHubから入手してDelphiにインストール。

P4Dの入手先URL https://github.com/pyscripter/python4delphi

Codeをクリックすると表示されるサブメニューのいちばん下にDownLoad ZIPがあるので、これをクリックしてZIPファイルをダウンロードし、任意の場所(フォルダ)に解凍する(ここではダウンロードするフォルダの名前を「P4D」として説明)。

Download ZIPをクリックして最新版を入手する

ダウンロードが完了したら、ダウンロード先フォルダにはコレがあるはず。

python4delphi-master.zipを任意の場所に「P4D」フォルダを作成して、そこへコピペする

P4D」フォルダを作成するのは、できればあまり階層の深くない、絶対に忘れない場所がよいと思います。理由は、後からそこにライブラリパスを通すから。バックアップなど取る時にも、忘れないような場所に作成してください。

zipファイルを右クリックして、表示されるサブメニューの「すべて展開」をクリック。

zipファイルを展開(解凍)

そのまま P4D フォルダの直下に展開(解凍)する。

Pathは敢えていじらずに、そのまま「展開」をクリック

展開(解凍)が完了すると、P4D フォルダの下に「python4delphi-master」フォルダができ、その下に同じ名前でもうひとつ「python4delphi-master」フォルダができる

この中に7匹のヘビがいる。はやく会いたい。

2.フォルダ構成を整える

この時点でフォルダ構成は・・・ちょっとややこしいが、次のようになっている(はず)。

¥任意の場所¥P4Dpython4delphi-masterpython4delphi-master

とりあえず、いちばん下の python4delphi-master フォルダをダブルクリックして開き、中にあるものすべてを CTRL+A で全選択して、CTRL+X で切り取り、ひとつ上の階層の python4delphi-master フォルダ内に CTRL+V(貼り付け)する。

で、いちばん下の階層の python4delphi-master フォルダは不要なので消去(削除)する。

さらに、上の階層の python4delphi-master フォルダの名前を手動で「P4D」に変更(リネーム)する。

これでフォルダ構成は、次のようになる。

¥任意の場所¥P4DP4D

いちばん下の P4D フォルダをダブルクリックして開くと・・・

Install フォルダ内にある「README.md」に、実は重要な情報が書かれている

【README.md】※ 原文のまま

## P4D Installation using [MultiInstaller](https://github.com/pyscripter/MultiInstaller)

Use for Delphi Seattle (10.4) or later to install all packages in one step. 

1. Clone or copy the Python4Delphi git repository to a folder of your choice.  **The setup.ini file assumes that the folder is called "P4D"**.  If you chose to name your folder differently then modify the "Folder" option in setup.ini.
2. Close all Delphi IDEs running.
3. Run MultiInstaller.exe
4. Select the packages you want and press Next
5. In the dialog box specify the _**parent folder**_ of "P4D" (i.e. the folder containing the directory to which you have copied Python4Delphi) and the Delphi target version.  Then press Next to install the components

Google先生、曰く・・・(文字に色付けしたのは僕です)

## [MultiInstaller]を使用したP4Dのインストール(https://github.com/pyscripter/MultiInstaller)

Delphi Seattle (10.4) 以降の場合は、すべてのパッケージを 1 ステップでインストールするために使用します。

1. Python4Delphi git リポジトリを選択したフォルダーにクローンまたはコピーします。 **setup.ini ファイルでは、フォルダーの名前が「P4D」であると想定しています**。フォルダーに別の名前を付けることを選択した場合は、setup.ini の「フォルダー」オプションを変更します。
2. 実行中のすべての Delphi IDE を閉じます。※ コレも重要!な注意点のひとつかと・・・
3. MultiInstaller.exe を実行します。
4. 必要なパッケージを選択し、「次へ」を押します
5. ダイアログ ボックスで、「P4D」の _**親フォルダー**_ (つまり、Python4Delphi をコピーしたディレクトリを含むフォルダー) と Delphi ターゲット バージョンを指定します。次に、「次へ」を押してコンポーネントをインストールします

僕なりの解釈は(間違ってるカモだけど)・・・

Python4Delphi をコピーしたフォルダ名は「P4D」であり(であることを想定しており)、
さらに、インストール時に表示されるダイアログボックスでは・・・

P4D」の _**親フォルダー**_ を指定

つまり、「その親フォルダ(階層がいちばん上の P4D )を指定せよ」

と言っている・・・。

フォルダ構成を README.md の指示通りに整えたところで、

¥任意の場所¥P4DP4D¥Install フォルダを開き、

そこにある MultiInstaller.exe をダブルクリックして実行する。

Install フォルダにある MultiInstaller.exe をダブルクリック

ちなみに、拡張子md は、「Web 用のドキュメントの作成によく使用される、読み書きしやすいように設計されたプレーンテキスト」に使う拡張子だそう。

ちなみに「プレーンテキスト」は、「文字だけで構成され、レイアウト情報や装飾情報などを持たないデータのこと」だそうで。

勉強になりますー。

3.Python4Delphiの最新版(RAD Studio 12.0対応版)をインストール

こうしてインストール前の最大の難関?を乗り越え、早速、Python4Delphi をインストール。

Install フォルダにある MultiInstaller.exe をダブルクリック(再掲)

次の画面が表示される。

フォルダの選択ダイアログ

Select Destination directory to install all the component packages. ・・・

こちらもGoogle先生曰く、

「すべてのコンポーネント パッケージをインストールするには、宛先ディレクトリを選択します。」

どうも、この、「宛先」という訳がピンとこないけど・・・。

まぁ、「宛先」は「参照元」に読み替えて・・・。

それが、先ほどの「README.md」に書かれていた「Python4Delphi をコピーしたディレクトリを含むフォルダー」・・・つまり、「P4D」フォルダなんだろうな・・・ みたいな・・・

ってか、もっと正直に言うと・・・、RAD Studio 12.0 をインストールしたから、唯一、僕が必要とするサードパーティー製コンポーネント Python4Delphi も入れなきゃって思って、前回の(11.2 への)インストール作業後、大切に保存しておいた P4D¥Installフォルダ内の MultiInstaller.exe を起動したら・・・

RAD Studio 12 Athens がインストール先の候補として出てこない!

つまり、この MultiInstaller.exe は RAD Studio 12 Athens のインストールパスを拾って・・・「ない」。

このダイアログを見たとき、一瞬、(もうダメだ・・・)と思ったのですが、その直後、このインストーラー自体が1年前のものだったことを思い出し、・・・だとすれば、RAD Studio 12 Athens が表示されなくて、むしろ当然・・・。ここで初めて Python4Delphi も最新版が必要だと気づき・・・

さらに、オプションボタンがこのダイアログに「1つしかない」意味まで見えた気が・・・

(オプションボタンだから、インストール対象としてパスを通すのは、1バージョンに限定ってことなんだろうけれど・・・)

(それよりも・・・、ダイアログのCompile packages ~の余白が有り余ってるのは、RAD Studio のメジャーバージョンアップを見越して、後からボタンを追加できるよう、予め余裕を持って設計したから?)

(・・・もし、そうなら12.0対応版があるに違いない。いや、きっとある!)

あわてて GitHub へ行って12.0 対応版の有無を確かめたというのが事の真相。

思った通り、GitHub の Python4Delphi は、12.0のリリースに合わせて最新版にアップデートされてました・・・。作者の方に心から感謝!

Go To 「3.Python4Delphiの最新版(RAD Studio 12.0対応版)をインストール

これで無限Loop に。Blogまでスパゲッティ化しちゃった・・・。

↑ コレは古い時代のプログラマーにしか、通じない言葉かな?

取り敢えず、無限Loopはなんとかして乗り越えたコトにして・・・

宇宙のはじまりだって、トンネル効果が起きた時、虚数時間が流れていて、上り坂が下り坂になった・・・みたいな話を、聴いたような・・・。聴かなかったような。で、宇宙って、通れないはずの壁から果たして沁み出すものなんだろーか。

ハイゼンベルクさんは、連合軍の科学者たちのことを、どう思っていたんだろう・・・

RAD Studio 12.0 対応版のP4D付属 MultiInstaller.exe を起動して表示されるフォルダの選択ダイアログは、前掲の通り。

Browseボタンをクリックして・・・

Compile packages ~の欄には RAD Studio 12 Athens が増えましたが、欄の上下に「まだまだ余裕」があります。これを見て、先ほどの予感は大きく自信を得て・・・「これはつまり、今後数十年以上先までDelphiのメジャーバージョンアップが続々と行われることを見通して、必要十分と思われる余白を予め用意した先見の明溢れる非常に大胆な先進的設計である」という確信に変わりました。

是非、そうであって欲しい・・・と、心から願っています!

ダイアログがこのままの大きさでも、

1、2、3・・・と、近未来、確実にそこに入るであろうオプションボタン位置を予想してみると、Delphi のメジャーバージョンアップにあと10回は余裕で対応できそうです*(^_^)*♪

いいぞ。さすが、P4D!

こういう応援の仕方もあったのか・・・

こんどから、

僕も真似しよー!!

解凍先フォルダの階層Topにある P4D フォルダを指定
Compile packages and install on IDEにチェックして、RAD Studio 12 Athensを選択

あとは Next ボタンをクリックしてインストーラーにすべておまかせで、P4Dをインストールするだけ。
無事完了すれば、次のようなダイアログが表示される(画像は前バージョンのもの)。

作業の記録をとり忘れたので、これは 11.2 に P4D をインストールしたときの画像

最後に Finish をクリックしてインストール作業終了。

Delphi 12.0 のIDEを起動して、パレットを確認。

7匹のヘビを無事発見。

4.ライブラリパスを確認

Delphi のIDEを起動し、「ツール」→「オプション」→「言語」→「Delphi」→「ライブラリ」の順にクリックして下の画面を表示。

プラットフォームを選択して、ライブラリパスの「…」ボタンをクリックする

ライブラリパスの一覧が表示されるので、そのいちばん下に P4D へのパスの設定があることを確認する(パスはインストール時に自動で設定されるようだ)。

Library パスの一覧の下から3つが P4D へのパス(自動で設定される)

上の画面では 「Windows 32ビット」 のプラットフォームに対する設定を確認している。念のため、「Windows 64ビット」 のプラットフォームに対しての設定も確認する。

プログラムのコンパイルを実行すると、Delphiはいちばん最初にプロジェクトファイル(.dproj)のあるフォルダ(ここはパスが通っているから登録は不要)を検索し、必要なユニットファイル等の有無を確認。もし、そこに必要なファイルがなければ、この画面に登録したライブラリパスを検索するようだ。

5.まとめ

(1)RAD Studio 12.0 のリリースに合わせ、Python4Delphi もアップデートされていた。
(2)Python4Delphi のインストールは専用の「MultiInstaller.exe」で実行する。
(3)Python4Delphi のデータは「¥任意のフォルダ¥P4D¥P4D」フォルダ内に置く。
(4)インストール後、念のため、ライブラリへのパスが設定されていることを確認する。

6.お願いとお断り

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

浮動小数点

学生の頃から、2進数が苦手だった・・・。
浮動小数点も、本当のところはよくわからなかった・・・。

それがわからなくても、日常生活で苦労することは皆無だったし、
PCに触れている時でも、これと言って計算に困るような出来事もなかった。

そう、これまでは・・・

ただ、職場が変わって、状況が変化。
苦手だった2進数や、浮動小数点を、ほっとけなくなっちゃった・・・。

もくじ

1.10進数を2進数に変換
2.浮動小数点
3.0.1(10)を2進数に変換
4.まとめ
5.お願いとお断り

1.10進数を2進数に変換

これが苦手で、小テストとかあって、本当に困ってるひとが、もし、いたら、
僕なりに苦しい中から見出した絶対忘れない変換方法をお伝え出来たらと思って、
この記事を書いています。

まず、僕は、よく見るコレが『なぜか苦手』でした(今でも)。
例えば7(10)を2進数に変換する場合、

10進数を2進数に変換(その1)

答えは、図の矢印の順に読んで、0111(2) 。だけど・・・
この書き方。すごく、思い出しにくい。
特に焦ったりすると、何か、どこかで混乱して、僕は必ず間違えてしまう。

自分なりに考えて出した「結論」は・・・この書き方、普段ほとんど使わないから・・・かな? みたいな

そこで、小学校以来慣れ親しんだ書き方がよかろうと思い、
自分では、より『素直』に思えるカタチに書き方を変更。
結果、これなら絶対間違えないと思えました。
自分的には、この書き方の方が、なぜかとても安心感があります。なんでかな?

10進数を2進数に変換(その2)

自分に対して、自信が持てない本当の理由は、わかってます。
答えを出す方法だけが知りたくて、『なんで2で割り算するのか?』考えたことがなかったからです。
つまり、『ほんとうのこと』から、僕は目を反らし続けてきた・・・から。

理由はいくらでもあげられます。

めんどくさかったから
考えたくなかったから
試験に通ればそれでよかったし
そんなこと、どうでもよかったカラ

でも、とうとう、ここで、それは通用しなくなりました。
『2で割り算する』、その理由を僕は説明しなければならない。

誰もがわかるように・・・。

とりあえず、わかりやすい10進数で考えてみます。
10進数の基数は「10」、では基数で割り算するってどういうことなのか?

基数で割り算した余りが、元の10進数の一の位、十の位、百の位、千の位と一致します。
つまり、各「位(くらい)」の数を求めていたわけですね!

2進数の場合、基数は「2」ですから、基数で割り算すれば、余りは「0」か、「1」のいずれかになります。これが、その桁の2が「あるか・ないか」を教えてくれるわけです。

例えば、10進数の「123」を2進数に変換する場合、


01111011(2)の0と1はそれぞれ、その桁の2が「あるか・ないか」だから

電卓の種類を「プログラマー」に変更して、ラクして確認。

こんなイイモノがあったんだ!

手計算でも確認(これが16進数で桁数が多かったら、挫けそうだなー)。

2進数でも、余りは、そのまま、各位(くらい)の数になりました。
よかった。よかった。

2.浮動小数点

数値のどこまでが信頼できる桁であるのかを表すのに、有効数字を用いますが、例えば


有効数字の 1.234 の部分を「仮数(かすう)」というそうです。

2進法では、次のような場合を考えると、


2倍すると1桁、位が上がりますから、2の2乗倍=4倍すれば2桁、位(くらい)が上がることになります。これを小数点の位置で言えば、2をかけるたびに、小数点の位置が右へ移動するわけです。

(だから「浮動小数点」って言うのかぁ・・・)

ここでの仮数は「1.0111」ですが、実は仮数には重要な決まりがあって、「整数部分を1桁とし、そこに0以外のいちばん上の位の数を置く」のだそうです(⇨ IEEE754 という方式に準拠した場合?)。

・・・と、いうことは、2進数なら「0」と「1」しかないから、仮数の整数部分は必ず「1」になることになります。

64ビットの浮動小数点数(2進数の場合)では、符号ビットは1ビット、指数部は11ビット、仮数部は52ビットで表わされることが多いそうで、符号は+とーのどちらかだから1ビットでOKとして、仮数部ではその整数部分「1」を省略してしまうとのこと。なんで?

仮数部で整数部分の「1」を省略する理由がわからなかったので調べてみました。結果を知って納得。

整数部分の1を省略すれば、仮数部を1ビット増やすことができるので、浮動小数点形式の精度を向上させることができる。

(すごーい!)

仮数の整数部分を必ず「1」とすることで、そんなことも可能になるわけですね!

3.0.1(10)を2進数に変換

すーぱー 苦しんだのが、コレです。

10進数の小数を2進数に変換するなんて、もう長いことやってない。
やったことがあるとしても、15年以上前です。その記憶の欠片すら、残ってません。

表し方を調べてみると・・・、整数でやったコレを、そのまま2のマイナス1乗みたく、右側へ拡張して小数点以下を表現するとのこと。


そー言えば、はるか、むかし。
なんで2のゼロ乗が1になるのか、さっぱりわからず、悩んだことがあったよーな・・・。
わかってみれば、カンタンだったけど。


この関係を含めて10進数の小数を位(くらい)の数として表せば、


2進数でも、考え方は同じ。
違うのは、それがいくつあるか? ではなく、単に「ある」か・「ない」か が、「ある」=「1」、「ない」=0で示されること。あとあと、これがかなり重要な理解のポイントになります!


特に、0.X とした場合の小数点以下の部分 は(1の位が0であることを明示して表せば)、


この関係がそのまま使えれば、話はカンタン。
例えば10進数の 0.625 を2進数に変換したい場合、0.625 が 0.5 と 0.125の和であることに気付けば、


んじゃ、10進数の 0.1 を2進数に変換するには?
理解のために、全桁「1」で数値が「ある」ものとし、位(くらい)の数を書き出して考えます・・・


2進数で計算すると、このような誤差が必ず生まれてしまう!

だから、0.1 に少しずつ近づくように(0.1 を超えないように)、足し算可能な、よりちいさな数を求め、どこまでもこれを繰り返して(=循環する理由)行くわけですか・・・

(それはわかったけど、0.1(10) を2進数に変換するわかりやすい方法は・・・)

Google先生に、いろいろたずねても、コレだぁ!・・・みたいな答えは教えてくれない!!

それでも、いろんなWebサイトさんの断片的な情報を集めて、ようやく変換方法だけはわかりました☆

それが、こちら(10進数の 0.1 を2進数に変換)

基数で割り算でなく、基数を「掛け算」!


なんで、これで変換できるのか?
次の式を書き出して、ひたすら考えます・・・(この場合、左辺のxは もちろん 0.1)。


10進数の 0.1 を2進数に変換するとき、なんで割り算でなく、基数を掛け算してるのか? まず、それがさっぱりわからないけれど、ここでまず思い出したことは、これは2進数表現だから、an は必ず0か1のいずれかになるということ。

つまり、an が「1」ならその部分は数値が「あり」で、an が「0」なら「ない」わけです。

一歩、前進。

問題は基数を「掛け算」する理由。

基数を掛け算する・・・、つまり、2倍するってことは、意味的にはナニをしているのかというと、2倍して1桁、位を上げていることになる。おそらく、この部分に重大な意味があるような気が・・・。

てか、ナンで桁を上げる必要があるのか?

それがアタマにこびりついて、離れません。

(あぁ ナニを見ても0と1に見える・・・)

誰か、教えてくれないかなー

ブラウザのタブを切り替えながら、既に何度も眺めたWeb上の情報にもう一度目を通します。

(なにか、見落としてること、あるんじゃないかなー)

あるWebサイトにあった次の文がなぜかキラキラ輝いて見えます。でも、なんで輝いて見えるのか、それがわかりません。

2進数で表された1.0と、10進数で表された1.0は、数の重みが等しい(同じ数!)

なんか、ものすごく大切なことを伝えてくれている気がするのですが、その「ものすごく大切なこと」がナンなのか? それがどうしてもわかりません!

0.1 かぁ・・・ これは10進数の小数で・・・

それを2進数にしたいんだよなぁ・・・


左辺の 0.1 は、10進数の領域にあって・・・、右辺は、2進数の領域にあると考えれば・・・

右辺のan は絶対に「1」か「0」のどちらかになる(2進数だから)。

「1」なら「数値あり」で、「0」なら「ない」。

で・・・

1 は2倍すれば、一の位に現れる・・・

2 は22=4倍すれば、一の位に現れる・・・

3 は23=8倍すれば、一の位に現れる・・・

2進数も、10進数も、一の位だけは・・・1が意味合い的に同じ「ひとつだけあるの1(イチ)」。

・・・って、コトは、

二つの異なる領域をイコールで結ぶために必要な、その条件が一の位・・・

そうか、一の位じゃないと・・・「0」か、「1」かが、きっと 見えない んだ!

だから、どんどん2倍して、
1、a2、a3・・・が、次々に一の位になるように、桁を上げるんだ☆

一の位になって初めて、それが「0」なのか、「1」なのかが 見える んだ!!

あー☆ わかったカモー!!

だから、(上の計算で)基数を掛け算した結果、整数部に「1」が出てきた時は、それが「ある」ことさえわかればイイから、その存在を無視して、小数部だけを基数倍(2倍)してるんだ!

コレ、考えた人、天才だ☆

4.まとめ

今回は内容が多岐にわたり、コレが最も適切かと・・・思われます。

5.お願いとお断り

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

FireMonkeyのMessageDialog

FMX事始め

1.FMXでMessageDialogを表示する
2.TMsgDlgTypeに指定できる値はVCLと同じ
3.表示できるボタン
4.表示できないボタン
5.ダイアログ右上の閉じるボタンの挙動
6.まとめ
7.お願いとお断り

1.FMXでMessageDialogを表示する

いろいろな事情からFMXプラットフォームで、あるプログラムを書くことになった。使い慣れたVCLと違って、FireMonkeyはずっと以前に一度だけWAVファイルの再生プログラムを作った時触れたことがあるだけで、まともに触るのは今回が初めて。

最初にいちばん困ったのはユーザーへのメッセージの出し方。なんでかわからないけれど、普通にShowMessageすると、その直後、FMXプラットフォームでは、結構な頻度でエラーが発生する気が・・・。

だから、最初に書いたデータベース接続のプログラムは、極力ShowMessageを使わない方向で書いたんだけど、2作目のテキスト入力練習プログラムではそうも行かず、良い機会だと思ってShowMessageより見た目が華やかなMessageDialogの正しい使い方を調べてみた。

思った以上に情報が少ない気がしたので、学んだことを備忘録として、まとめておく。

まずは、iマーク付きのMessageDialogの出し方。

implementation

uses
  FMX.Platform, FMX.DialogService;

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
var
  ASyncService:IFMXDialogServiceASync;
begin
  //mtConfirmationだとBeep音が鳴らないが、mtInformationだとBeep音が鳴る
  if TPlatformServices.Current.SupportsPlatformService (IFMXDialogServiceAsync,
    IInterface(ASyncService)) then
  begin
    TDialogService.MessageDialog('Do you know Delphi?',
      TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0,
      procedure(const AResult: TModalResult)
      begin
        if AResult = mrOK then
        begin

        end;
      end);
  end;
end;

実行すると・・・

ここにたどり着くまで、結構長かった・・・
ほんとに、ようやくって感じ。

調べてわかったことは・・・

var
  ASyncService:IFMXDialogServiceASync;

・・・と宣言するためには、

uses
  FMX.Platform;

uses に FMX.Platform が必要で、さらに、サポートの有無を調査するif文の・・・

if TPlatformServices.Current.SupportsPlatformService (IFMXDialogServiceAsync,
    IInterface(ASyncService)) then

TPlatformServices も FMX.Platform を参照している。

で、本命の MessageDialog を表示するには、さらに・・・

uses
  FMX.Platform, FMX.DialogService;

uses に FMX.DialogService も追加しなければならない。

2.TMsgDlgTypeに指定できる値はVCLと同じ

上のユーザーへの情報提供(Info)に加えて、ユーザーに確認する場合は、

TMsgDlgType.mtConfirmation

ユーザーに警告。

TMsgDlgType.mtWarning

ユーザーにエラーを報告。

TMsgDlgType.mtError

mtCustomってのもあったけど・・・

TMsgDlgType.mtCustom
画像は、何も出てこなかった・・・。
キャプションもProject1(アプリケーション名)になってる・・・。

実質、「情報提供・確認・警告・エラー」の4つ型があり、これはVCLと変わらない。

3.表示できるボタン

ユーザーの応答が「OKボタン押し下げのみ」であれば、MessageDialogの最後の引数を別手続きにして、それを呼び出す形にすればいいのかと・・・

  private
    { private 宣言 }
    procedure MsgDlgProc(const AResult: TModalResult);

として、Shift+Ctrl+Cで手続きを作成。

procedure TForm1.MsgDlgProc(const AResult: TModalResult);
begin
  //何もしない手続き

end;

応答が「OK」のみの場合は、これを呼び出し。

procedure TForm1.Button2Click(Sender: TObject);
var
  ASyncService:IFMXDialogServiceASync;
begin
  if TPlatformServices.Current.SupportsPlatformService (IFMXDialogServiceAsync, IInterface(ASyncService)) then begin
    TDialogService.MessageDialog('Do you know Delphi?',
      TMsgDlgType.mtInformation, 
      [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0, MsgDlgProc);
  end;
end;

コードが短くなって、なんとなくすっきりした。

でも、「はい」・「いいえ」・「キャンセル」のようにボタンを複数表示するとそうもいかない。

procedure TForm1.Button3Click(Sender: TObject);
var
  ASyncService:IFMXDialogServiceASync;
begin
  if TPlatformServices.Current.SupportsPlatformService (IFMXDialogServiceAsync, IInterface(ASyncService)) then
  begin
    TDialogService.MessageDialog('Do you know Delphi?',
      TMsgDlgType.mtInformation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo, TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbYes, 0,
      procedure(const AResult: TModalResult)
      begin
        if AResult = mrYes then
        begin
          ShowMessage('Goooooooood!');
        end;
        if AResult = mrNo then
        begin
          ShowMessage('No Good!');
        end;
        if AResult = mrCancel then
        begin
          ShowMessage('Cancel');
        end;
      end);
  end;
end;

case文でもよいようだ。

procedure TForm1.Button3Click(Sender: TObject);
var
  ASyncService:IFMXDialogServiceASync;
begin
  if TPlatformServices.Current.SupportsPlatformService (IFMXDialogServiceAsync, IInterface(ASyncService)) then
  begin
    TDialogService.MessageDialog('Do you know Delphi?',
      TMsgDlgType.mtInformation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo, TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbYes, 0,
      procedure(const AResult: TModalResult)
      begin
        case AResult of
          mrYes:ShowMessage('Goooooooood!');
          mrNo:ShowMessage('No Good!');
          mrCancel:ShowMessage('Cancel');
        end;
      end);
  end;
end;

caseのリストが表す値は、case文内で一意、部分範囲またはリストの重複がなければ昇順とかリストの並びは関係ないようだ。また、このようによく使用されるボタン値は、セットになった定数として用意されていて、例えば上の場合は次のように指定できる。

TMsgDlgType.mtInformation, mbYesNoCancel, TMsgDlgBtn.mbYes, 0,
こっちの方がカンタン!

embarcaderoさんのWebサイトでは、TMsgDlgBtnは種類がたくさん紹介されていて、

定数意味
mrNone0結果なし。ユーザーがフォームを終了するまでのデフォルト値として使用されます。
mrOkidOK = 1ユーザーは[OK]ボタンでフォームを終了しました。
mrCancelidCancel = 2ユーザーは[キャンセル]ボタンでフォームを終了しました。
mrAbortidAbort = 3ユーザーは[中止]ボタンでフォームを終了しました。
mrRetryidRetry = 4ユーザーは[再試行]ボタンでフォームを終了しました。
mrIgnoreidIgnore = 5ユーザーは[無視]ボタンでフォームを終了しました。
mrYesidYes = 6ユーザーは[はい]ボタンでフォームを終了しました。
mrNoidNo = 7ユーザーは[いいえ]ボタンでフォームを終了しました。
mrCloseidClose = 8ユーザーは[閉じる]ボタンでフォームを終了しました。
mrHelpidHelp = 9ユーザーは[ヘルプ]ボタンでフォームを終了しました。
mrTryAgainidTryAgain = 10ユーザーは[やり直し]ボタンでフォームを終了しました。
mrContinueidContinue = 11ユーザーは[続行]ボタンでフォームを終了しました。
mrAllmrContinue + 1(12 つまり $C)ユーザーは[すべて]ボタンでフォームを終了しました。
mrNoToAllmrAll + 1(13 つまり $D)ユーザーは[すべていいえ]ボタンでフォームを終了しました。
mrYesToAllmrNoToAll + 1(14 つまり $E)ユーザーは[すべてはい]ボタンでフォームを終了しました。
https://docwiki.embarcadero.com/Libraries/Sydney/ja/FMX.StdCtrls.TCustomButton.ModalResultより引用

さらに、セットになった定数が5つあるとのこと。

定数意味
mbYesNoCancelmbYes、mbNo、および mbCancel
mbYesAllNoAllCancelmbYes、mbYesToAll、mbNo、mbNoToAll、および mbCancel
mbOKCancelmbOK および mbCancel
mbAbortRetryIgnorembAbort、mbRetry、および mbIgnore
mbAbortIgnorembAbort、mbIgnore
https://docwiki.embarcadero.com/Libraries/Alexandria/ja/Vcl.Dialogs.TMsgDlgBtnより引用

4.表示できないボタン

僕のPCだけ、そうなのかもしれないけど。中には表示できないボタンが・・・。例えば、

procedure TForm1.Button7Click(Sender: TObject);
var
  ASyncService:IFMXDialogServiceASync;
begin
  if TPlatformServices.Current.SupportsPlatformService (IFMXDialogServiceAsync, IInterface(ASyncService)) then
  begin
    TDialogService.MessageDialog('Do you know Delphi?',
      TMsgDlgType.mtInformation,[TMsgDlgBtn.mbRetry],TMsgDlgBtn.mbRetry,0,
      procedure(const AResult: TModalResult)
      begin
        case AResult of
          mrOK:ShowMessage('OK!:了解');
          mrCancel:ShowMessage('Cancel:取消');
          mrAbort:ShowMessage('Abort:中止');
          mrRetry:ShowMessage('Retry:再試行');
          mrIgnore:ShowMessage('Ignore:無視');
          mrYes:ShowMessage('Yes:はい');
          mrNo:ShowMessage('No:いいえ');
          mrClose:ShowMessage('Close:閉じる');
          mrHelp:ShowMessage('Help:要援助');
          mrAll:ShowMessage('All:すべて');
          mrNoToAll:ShowMessage('NoToAll:すべていいえ');
          mrYesToAll:ShowMessage('YesToAll:すべてはい');
        else
          //ないと思うけど、
          ShowMessage(IntToStr(AResult));
        end;
      end);
  end;
end;

ボタンに mbRetry を指定しても、上の手続きを実行すると表示されたダイアログは・・・

普通のOKボタン!

で、OKを押し下げ。・・・ると

なんでかなー?

でも、次のように指定すると、

TMsgDlgType.mtInformation, [TMsgDlgBtn.mbCancel,TMsgDlgBtn.mbRetry]
指定した順番と並びが逆だけど、「再試行」ボタンが表示された!

で、「再試行(R)」を押し下げ。・・・ると

表示できる場合とできない場合があるらしい。つまり、これはダイアログに表示可能なボタンの設定(組み合わせ)を FMX の MessageDialog は内部的に持っているということ? それから、キャンセルボタンは必ず右側へ設置される?・・・から、ボタンの表示される順番もまた、決まっているという理解でいいのかな?・・・みたいな。

他にも、mbAbortRetryIgnore を指定して、デフォルトで選択状態にするボタンに mbRetry を指定しても・・・

TMsgDlgType.mtInformation, mbAbortRetryIgnore, TMsgDlgBtn.mbRetry, 0,
なぜか「再試行」ボタンが表示されない!

しかも、ダイアログ右上の「閉じる」ボタンが押せなくなってる(勝手にEnabled?がFalseに設定されてしまう)。これは、キャンセルがないから、閉じるボタンはその必要がないという意味に思えてくる・・・。だから、閉じるボタンの無効化も、VCLならその方法が紹介されているんだけど、FMXでの情報は見当たらないのか・・・

実は、この記事を書こうと思ったのは、この閉じるボタンをクリックした時の戻り値が何なのか、どんなに調べても(僕が調べた範囲では)見つけることが出来なかったので、実験してみた結果を記録しておこうと思ったことがきっかけというか、はじまり。

FMX の MessageDialog を設計した人の気持ちがだんだん、わかってきた!

5.ダイアログ右上の閉じるボタンの挙動

実験結果から見えてきたこと。それは MessageDialog の閉じるボタンは、そのクリックの可否がダイアログに表示するボタンの組み合わせによって、内部的に制御されているんじゃないか?ってこと。

まず、OKボタンのみの場合、

procedure TForm1.Button9Click(Sender: TObject);
var
  ASyncService:IFMXDialogServiceASync;
begin
  if TPlatformServices.Current.SupportsPlatformService (IFMXDialogServiceAsync, IInterface(ASyncService)) then
  begin
    TDialogService.MessageDialog('Do you know Delphi?',
      TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0,
      procedure(const AResult: TModalResult)
      begin
        case AResult of
          mrOK:ShowMessage('OK!:了解');
          mrCancel:ShowMessage('Cancel:取消');
          mrAbort:ShowMessage('Abort:中止');
          mrRetry:ShowMessage('Retry:再試行');
          mrIgnore:ShowMessage('Ignore:無視');
          mrYes:ShowMessage('Yes:はい');
          mrNo:ShowMessage('No:いいえ');
          mrClose:ShowMessage('Close:閉じる');
          mrHelp:ShowMessage('Help:要援助');
          mrAll:ShowMessage('All:すべて');
          mrNoToAll:ShowMessage('NoToAll:すべていいえ');
          mrYesToAll:ShowMessage('YesToAll:すべてはい');
        else
          ShowMessage(IntToStr(AResult));
        end;
      end);
  end;
end;


ダイアログ右上の「×」をクリックすると表示されたのは・・・

AResult は mrOKだった!

つまり、OKをクリックするしか、選択肢がない(未来をプログラマ自身が選択した)のだから、閉じるボタンが押された時の戻り値もmrOKでよい・・・ということか!

次に、表示するボタンを「OK」と「キャンセル」にして、デフォルト選択ボタンは「キャンセル」に指定して、再度実行。

TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK, TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbCancel, 0,

右上の「×」をクリックすると・・・

AResult は mrCancel だった!

思った通り、mrCancel が設定されていた! プログラマが「キャンセルという選択肢を未来に与えた」んだから、閉じるボタンが押された時は「キャンセル」と判断してよい・・・ということ?

デフォルト選択ボタンをOKに変えてみた・・・

TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK, TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbOK, 0,

右上の「×」をクリックすると・・・

AResult はmrCancel!

思った通りだ。

ボタンを「はい」・「いいえ」・「キャンセル」の3つにし、デフォルト選択を「はい」に指定して実験・・・

TMsgDlgType.mtInformation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo, TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbYes, 0,

右上の「×」をクリックすると・・・

AResult は mrCancel!

やっぱり、mrCancelが戻り値に設定されている!

ならば、ボタンを「はい」・「いいえ」の2つだけにすると、閉じるボタンは使用不可になるはずだ・・・。だって、プログラマの意向として、未来に「キャンセル」という選択肢は与えられていないから!

実際に動かして確認。

TMsgDlgType.mtInformation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], TMsgDlgBtn.mbYes, 0,
思った通り「×」はクリックできない!

僕の中に生まれた予測は、ここで「確信」に変わった!

これはVCLのMessageDlgでも同じなんだろうか?
今度、実験だ。

6.まとめ

(1)OKボタンのみ設置したダイアログでは、閉じるボタンクリックでmrOKが返る。
(2)ダイアログにキャンセルボタンを設置した場合は、閉じるボタンもクリック可能。
(3)キャンセルボタンがある場合、閉じるボタンクリックで返る値はmrCancelになる。
(4)キャンセルボタンがない場合、閉じるボタンはクリックできない。
(5)ボタンの組み合わせは内部的に不可もある(不可でもエラーにはならない)。

7.お願いとお断り

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

範囲チェックエラーが出た時は?

{$R-}で範囲チェックさせない!

Delphiで、画像をグレースケール変換するプログラムを作成。実行すると、

表示されたエラーメッセージ

プログラムのコードは、次の通り。
Image1に表示した画像をグレースケールに変換してImage2に表示するというモノ。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Jpeg,
  Vcl.ComCtrls;

type
  TForm1 = class(TForm)
    Image1: TImage;
    Image2: TImage;
    Button1: TButton;
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function CreateGrayScalePalette(Tone:Byte): HPALETTE;
var
  Palette: ^TLogPalette;
  i: Integer;
begin
  GetMem(Palette, SizeOf(TLogPalette) + SizeOf(TPaletteEntry) * Tone );
  Palette^.palNumEntries:=Tone+1;
  Palette^.palVersion:=$0300;
  for i := 0 to Tone - 1 do begin
    Palette^.palPalEntry[i].peRed:= Tone - i;
    Palette^.palPalEntry[i].peGreen:= Tone - i;
    Palette^.palPalEntry[i].peBlue:= Tone - i;
    Palette^.palPalEntry[i].peFlags:= 0;
  end;
  Result:=CreatePalette(Palette^);
  FreeMem(Palette);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  X, Y: Integer;
  Bmp: TBitmap;
  P: PByte;
begin
  Bmp := TBitmap.Create;
  try
   Bmp.Assign(Image1.Picture.Bitmap);
   Bmp.PixelFormat := pf8bit;
   Bmp.Palette := CreateGrayScalePalette(255);
   Image2.Picture.Bitmap := Bmp;
  finally
   Bmp.Free;
  end;
  Image2.Width:=Image2.Picture.Bitmap.Width;
  Image2.Height:=Image2.Picture.Bitmap.Height;
  Image2.Visible:=True;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  jpg: TJPEGImage;
begin
  StatusBar1.SimplePanel:=true;
  // TJPEGImageオブジェクトをインスタンス化
  jpg := TJPEGImage.Create;
  try
    // Jpegファイル読み込み
    jpg.LoadFromFile('Image.jpg');
    // Image1に割り当てる
    Image1.Picture.Bitmap.Assign(jpg);
    Image1.Width:=Image1.Picture.Bitmap.Width;
    Image1.Height:=Image1.Picture.Bitmap.Height;
    //StatusBar1.SimpleText:=IntToStr(Image1.Width)+'/'+IntToStr(Image1.Height);
  finally
    // TJPEGImageオブジェクトを破棄
    jpg.Free;
  end;
end;

end.

グレースケール変換実行のボタン(Button1)をクリックすると・・・
このButton1Click手続き内で呼び出しているCreateGrayScalePalette関数でエラーが発生。

ブレークして確認すると、エラーになるのはココ。

でも、なんでエラーになるのか、わからない・・・

Google先生に訊くと、次のような情報を発見。

[Delphi?][ネタ]透明に見えるパターンを描く

https://qiita.com/pik/items/25276e49fb131425db07

早速、範囲チェックさせないコンパイラ指令 {$R-} を追加。

ナニがどうして、そうなるのか?
原因も、理由も、皆目わからないけれど・・・

範囲チェックエラーは出なくなりました!

範囲チェックを実行しないというコンパイラ指令 {$R-} は知りませんでした。
同じ理由で困ってる方もいるかもしれないと思い、記録だけUpしました。

なお、画像のグレースケール化にあたっては、次のWebサイト様にあった情報を使わせていただきました。24bitのフルカラー画像を256階調のモノクロ画像に変換(グレースケール変換)する処理を行う際に役立つ情報が、そのアルゴリズムも含めて、多数紹介されています。

カラー画像をモノクロ画像に変換

http://rakasaka.fc2web.com/delphi/grayscale.html

また、上のWebサイト様で紹介されている配列要素を動的に確保する必要のない、Delphiが独自に定義しているTMaxLogPalette構造体を使用したCreateGrayScalePalette関数を利用した場合は、範囲チェックエラーは発生しませんでした。

お願いとお断り

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

1番が死んじゃった!

【もくじ】

1.1気筒、死んでる!
2.キャブクリーナーを吹く
3.1番復活!
4.まとめ
5.お願いとお断り

1.1気筒、死んでる!

ゼファー400のエンジン。ずっと元気だったんだけど・・・
今日、プリンタのインクを買いに行こうと思って、火を入れたら、なんか、音がヘンで、いつもならエンジンが暖まると「ドゥルルル」って軽快な連続音でアイドリングするのに、今日は「ルルル」がなくて、「ドゥル、ドゥル、ドゥル、ドゥル」って息切れしてる感じ。走り始めても、全然パワーの盛り上がりが感じられない・・・

(もしかして、1気筒死んでる?)

走れないわけじゃないけど、動けなくなってからでは手遅れになるし、ケータイも家に置いてきた。とりあえずUターンして自宅に戻り、エンジンをかけたまま、赤外線温度計でマフラーの温度を測ってみる。

4番(なんとなく測りやすいから)を最初に計測。エンジンとマフラーの接合部で200℃以上、その少し下でも150℃くらいある。とても素手では触れない。

3番も、2番も、同様。でも1番は・・・

エンジンとマフラーの接合部で75℃くらい、その少し下だと45℃くらい(写真撮ってないから説得力に欠けるけど)

ヤバイ。1番、間違いなく死んでる・・・。数km走って自宅へUターンしたから、周りから熱もらって、少しだけ暖まった・・・いや・・・調子が悪いのは、低回転の時だけで、ある程度回すと普通にフケてたような・・・そんな気もする。

・・・ってコトは、原因はもしかして、キャブのスロージェットの詰まり?

とりあえず、1番(バイクに乗った状態で見ていちばん左側の気筒)のプラグを外して、エンジンのフィンに押し当て、スタータースイッチを押してスパークすることを確認。

(大丈夫。火花は飛んでる・・・)

同時に、1番の気筒から元気よく空気が吹き出してくるのを掌に感じる。

(圧縮がないわけでもなさそう・・・)

2、3、4番は元気で、1番もスパークするから、多分電装系のトラブルじゃない。
原因はおそらく、吸気系のどこか・・・

2.キャブクリーナーを吹く

エンジンの左後ろ側をよーく見ると、ナンか液体が流れたような跡も見える・・・。コレって、もしかしてキャブのオーバーフローの跡? 気づかないうちに、また、起きてた?

とりあえず、エアクリーナーを取り外して吸気口を見てみよう。いつか、キャブがオーバーフローした時は、漏れたガソリンがたまってた・・・。

シートを外し、タンクを止めているネジ2本を外し、給油ホースを慎重に抜いて、タンクを外す。ガソリンを給油したばかりだからかなり重たい。傷つけないように、そっとカーペット(廃品をバイク用に使用)の上へ移動。で、さらにタンクを固定するためのステーを外す(このステーがあるとエアクリーナーを入れるケースのカバーが脱着できない)。

ステーを外した後、エアクリーナーケースのカバーを外して、2年前に交換したエアクリーナーと久しぶりにご対面。湿式でまだしっとりしていて、表面にほんの少しだけ埃が付いてる。ついでだから埃を払う。とりあえず、エアクリーナーはキレイになった。

LEDライトを点灯し、エアクリーナーの装着口から1番のキャブの吸気口を覗き見る。

(ガソリンは漏れてないみたい・・・)

さて、どうするか?

いちばん、イイのは、このままキャブを外して、分解・洗浄することだ。エイプ100の単気筒のキャブならやったことがあるんだけど・・・。ゼファー400の4連キャブレターは外したことはあっても、バラしたことはない。自信はまるでないけど、やるなら新品のガスケットを用意したり、それなりの準備が必要だ。素直にバイク屋さんへ、もって行こうか・・・。

(でも、せっかくここまでバラしたから、キャブレタークリーナー吹いてみようか?)

ガレージの工具箱の隣に、メンテナンス用の各種スプレー缶を並べてあるんだけど、確かその中に買い置き未使用のキャブレタークリーナーが1個あったはずだ。いつか、キャブがオーバーフローしたとき、ガソリン抜いて、給油ホースにクリーナー繋いでキャブ内にクリーナー液を噴射。しばらく放置し、さらにクリーナー液を追加(噴射)して、キャブレター内部の汚れを含んだクリーナー液を排出。これを数回繰り返したら、オーバーフローしなくなったことがあって、あの時、使わなかった残りの1缶をとっておいたんだ。

注意! 上の方法で僕のバイクのオーバーフローが直ったことは事実ですが、正しい修理方法ではありません。絶対に真似しないでください。

ガソリンの通り道が詰まってるんじゃなくて、空気の通り道が詰まってるなら、もしかしたらキャブレタークリーナー吹いたら直るかも・・・。このまま、何にもしないで元に戻すよりはいい・・・。

エンジンをかけて、バイクの左側に立ち、右手じゃなく(通常と逆の)左手でアクセルを少し開けながら、右手に持ったキャブレタークリーナーのスプレー缶のノズルを1番の吸気口に向けて、泡状のクリーニング液を噴射する。燃料じゃないから、当然、回転は落ちる。その分だけアクセルを回してエンジンを吹かす。マフラーからは大量の白煙が・・・。事情を知らない人が見たらエンジンが壊れているとしか思えないんじゃないか?

ついでに2、3、4番の吸気口にもクリーニング液を噴射。祈るような気持ちでしばらく放置して、2回、3回と繰り返す。最後に、エアクリーナーケースの底に残って溜まったクリーニング液をきれいにふき取って、清掃完了。

結果は、走ればわかる。エアクリーナーをセットしてカバーを被せ、3箇所ねじ止め。タンクのステーを付けて、タンクを装着、給油ホース等を元通りに接続。

シートを装着しようとして、ふとシートをとめる金具の可動部のあちこちにまるで油分がないことに気づく。ついでだから多目的潤滑剤のスプレー缶を持ってきて、少しずつ潤滑剤を吹きかける。それから可動部を動かして、潤滑剤をなじませる・・・。

なんだか必要以上に可動部が動く気が・・・。よく見るとシートを外すためのキー差込口自体がグラグラしてる。(なんで?)と思い、キー差込口の部品とバイクの接合部を見ると、2本あるネジがどちらも5ミリくらい緩んでる。これもついでに直す。正直、キャブの不具合の原因がどこにあるのか、それはまったくわからないけれど、こっちはネジを増し締めするだけだから簡単だ。1番のトラブルが直ったかどうかはわからないけれど、この不具合に気づいて直せただけでも、今回、キャブレタークリーナーを使った価値があったと思えてくる。あとは、エンジンが元気よく廻ってくれれば・・・。

3.1番復活!

ウェスの上に並べて置いたネジ類が全部元の場所へ戻ったことを、ウェスを眺めて確認。シートを装着して、エンジンをかけてみる。

「ドゥルルルルルルルルルルルルルルルルルルルルル」

聴きなれた、いつも通りの音だ。もしかして・・・、信じられないけど、直った?

祈るような気持ちで空ぶかし。

「フォーン!」

回転計の針は気持ちよくフケ上がる。回転の落ちもイイ。早速、乗って確認。

アクセルを開けた分だけ、思い通りに加速する。

家の近くをグルっと一周。自宅に帰って、赤外線温度計でエンジンとマフラーの接合部の温度を計測。今度は1番から4番まで、全部200℃以上になってる。

やったー! なおったー☆

この後、本来の目的だったプリンタのインクを買いに10kmくらい走ったけど、何の問題もなくエンジンは気持ちよく回る、1番は完全復活した模様。本当の原因は何だったのか、皆目わからないけれど、キャブレタークリーナーを吹いて直ったから、空気系の通り道のどこかが詰まっていたのかな? みたいな・・・

追記(20231029)

エンジンは復活したから、状況的には問題は解決。OK!なんだけれど、燃料系もキレイにしたくなって、雨の日曜日に次の作業を実行。ただし、これがメンテナンス方法として正しいか、どうか・・・と言えば、間違いなく「間違い」な気が。でも、どうしてもやってみたくなって、自己責任でやったことなので、こちらも絶対に真似しないでください。

では、ナニをしたかというと「使い残りのキャブレタークリーナーをガソリンに混ぜてキャブ内を通過させ、多少なりとも燃料系を洗浄した気になりたい」という作業。

「満タンのガソリンタンクにキャブレタークリーナーを1缶全部噴射、燃料系の汚れをゆっくり落とす」という記事がWebにあり、雨でほかにすることもなかったし、これをやってみようか・・・と思ったのです(もちろん、結果は自己責任で)。

ただ、キャブレタークリーナーは先の作業で半分くらい使ってしまい、かつ、ガソリンタンクは満タン状態なので、このままWebにあった情報を適用しても効果が薄い気がしたことと、ガソリン使いたくても外は雨で、ガソリンを減らすためだけに走る気がしなかった等々の理由から、バイクからガソリンタンクを外し、ペットボトルにタンクから抜いたガソリンを入れ、エンジンがギリギリ回るくらいの濃さでキャブレタークリーナーを相当量混ぜ、エンジンを始動、ペットボトルに入れた燃料がなくなるまでアクセルを開閉してガソリンの流量を変化させつつ、エンジンを回転させれば、クリーナーが溜まった汚れを溶かすと同時に、燃料が流れる勢いそのもので燃料系を多少なりとも洗浄できるのではないかと・・・

まず、ペットボトルのキャップ(PEだから耐ガソリン性あり)にドリルでフューエルホースと同じ径の穴を開けて、たまたまあった同じポリエチレン製のフィルターを装着したフューエルホースを通し、1.5Lのペットボトルにこのキャップを被せて、今回の清掃作業専用のガソリンタンクを製作。

フィルター下部の出っ張りがフューエルホースの内径を押し広げ、ペットボトルキャップと密着してガソリンが漏れないことを期待したが、ちょっと無理だったようで、試してみるとガソリンがわずかながらも漏れてしまう・・・。やっぱり、水とは全然違って、ガソリンの粘性ってすごく小さいんだなーって。あらためて実感。ガソリン、恐るべし。

(絶対に、引火しないように。細心の注意を払って・・・作業しなきゃ)

対応策として、キャップの外側にビニールテープを巻いてキャップとフューエルホースを一体化させ、ガソリンが漏れないようにする。

ここで心配したのは、キャップから染み出したガソリンで溶けたビニールテープの「糊」が、どれくらい燃料に混入し、キャブ内に流れ込むのか・・・ということ。実際の程はわからないけれど、いったんペットボトル外に染み出たガソリンだから、ほとんど影響はなかろーと勝手に判断。作業を進める・・・

ペットボトル側のフューエルホースと、バイクのキャブレターに繋がっているフューエルホースをどう接続するかも(やったことないから)ここで大問題化。大いに悩む。

手持ちの物品で何かないかといろいろ探してみたが、径がやや小さかったり、材質が不明で耐ガソリン性が心配だったり・・・、あれもダメ、これもダメ、そうこうしているうちに、なんか、以前、そういう目的用の「部品」を近くのホームセンター内で見たことがあるような気がしたので、家のすぐ近くのホームセンターへ直行。店内をウロウロするうちに径7mmのエア配管用の「ホース継手」を発見。(あったー! コレだ。コレだ。) 見た感じ真鍮製(?)なので耐ガソリン性に問題はない。即、購入する。

バイクからガソリンタンクを外し、キャブレター側のフューエルホースを、今、購入してきたホース継手でペットボトル側のフューエルホースに接続。ガソリンタンクから50ccくらいガソリンを抜いてペットボトルに入れ、エンジンを始動する。

ガソリンを入れたペットボトルは、針金で三脚に吊り下げて、バイクの近くにセット。

外はどしゃ降りの雨。

エンジンをかけて、しばらく暖気(キャブ車だから、暖気しないとアクセルを開けられない・・・ってか、エンジンが冷えてるうちは、アクセルを開けても回転がついてこない。でも、それが楽しくて、うれしい。バイクに乗るのは休日Onlyだから、急いで発進する必要などさらさらない。エンジンを暖める時間そのものが休日らしくて、すごく楽しい)。数分後、頼りなかったアイドリング音が少しずつ力強い音に変化する。頃合い良し。油温計は低いままだけど、きっともう大丈夫。エンジンは暖まった。試しにアクセルを開けると、結構な勢いで、ペットボトル内の燃料が減ってゆく。

(こんなにガソリンが流れるんだ・・・)

心配になるほどの減り方だ(初めて見たけど)。とりあえず、キャブレタークリーナーをペットボトル内に噴射してガソリンに混ぜ、アクセルを開け続けてクリーナーを混入したガソリンがキャブレターを通過してエンジンに入ると回転にどのような変化が生まれるのか、検証する。

ペットボトル(内のクリーナーを混入した燃料)がカラになり、透明なフューエルホース内をエンジンに向かう燃料の油面はどんどん低くなる。やがて燃料がキャブレターに吸い込まれて行くと、思った通りアイドリングが不安定になる。慌ててアクセルを開け、4000回転くらいを維持する。エンジンはなんとか止まらずに、回り続けてくれる。相当濃くクリーナーを混ぜても、回転数を上げればエンジンは止まらないようだ。

次は自分的な本番だ。ペットボトル内に300cc程度ガソリンを追加。相当濃い量になったと思われるまでキャブレタークリーナーを思い切り、大量に噴射して、ガソリンに混ぜあわせる(量は申し訳ないけど、ホントに適当)。エンジンをかけ、アクセルを開けて、クリーナーを含んだ燃料がキャブレターを通過し、エンジンが爆発不調になったことを確認していったんエンジンを停止。キャブレター内の燃料通路の汚れが多少なりとも分解されてくれることを祈りながら、このまま15分ほど休憩。

15分後、(かかってくれー)と願いながらスターターボタンを押すと、調子悪そうな感じでエンジンが始動。アクセルを開けていないとエンジンは今にも止まりそう・・・。この状態でペットボトル内にさらにキャブレタークリーナーを追加噴射。油面が下がるのを眺めつつ、エンジンが停止しないようアクセルを開け続ける。

ガレージの外は結構激しい雨。ドアを解放して、バイクをバックさせて、直管マフラーのおしまいの部分だけガレージの外へ出し、排気ガスが屋外へ出るようにして作業してるから、一酸化炭素中毒にはならないと思うんだけど、ちょっと心配。

クリーナー成分を大量に含んだガソリンがキャブレターを通過、と同時にアクセルをさらに開け続けないと回転が維持できない状態に。エンジンを吹かして4000回転程度をなんとか維持しながら、燃料が尽きてエンジンが自然に停止するのを待つ。

フューエルホース内の油面が見えなくなってからも、キャブレター内には相当量の燃料があるようだ。なかなかエンジンが止まらない。アクセルを開けながら自然停止を待つこと、数十秒。ようやくエンジンが停止する。

これで燃料系がキレイになったか、どうか、まるで確信は持てないけれど、本人が「洗浄した気になる(?)」レベルのクリーニングが完了。

フューエルホース内の燃料が見えなくなったところでエンジンを停止し、キャブレター内にクリーナー成分を含んだガソリンを滞留させて数日間置いた方がさらによかったのかもしれないけど、ガスケットその他の部品への影響が心配で、敢えてそれはしなかった・・・

雨続きで走れなかったので、実際に走れたのは作業の二日後、エンジンはスターターボタンを押すのと同時に一発でかかり、暖気後は気持ちよく回るから、とりあえず、これでよかったのかなー、みたいな。もちろん、今日も、全気筒が生きてます!

4.まとめ

キャブレタークリーナーを直接吸気口へ向けて吹く形式の簡単なクリーニングで、1気筒死んでいたエンジンが直ることあるみたいだ。本当の原因は今でもわからないけれど。

5.お願いとお断り

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

矩形検出器を改良

今までのアルゴリズムで僕の矩形検出器がユーザーに提示する「次に採点する解答欄候補」の順番は、だいたい、こんなイメージ・・・

これまでの僕のアルゴリズムでは、実際の採点順とは、まるで違う順番で
「次の採点候補」とする解答欄をユーザーへ提示してしまう・・・

今回、ほぼ採点する順番の通りに「次の採点候補」の解答欄を赤枠で囲んでユーザーに提示できるよう、解答欄矩形の座標を採点順に並べ替えるアルゴリズムを改良。その結果、矢印キー押し下げ時の「次の採点候補とする解答欄座標」を示す赤枠矩形の動きのイメージは次のようになった。

こんな僕の書いた稚拙で、頼りないプログラムでも、喜んで使ってくださる方がいる。
こんなに重たい事実はない。

プログラムが良くなることは、きっと・・・、僕自身が良くなることだ。
そう思いつつ、遅ればせながら、矩形検出器のアルゴリズムをようやく改良できた。

(矩形検出の成功率は100%で、OpenCVの性能は最高!です)

解答用紙の様式パターンの研究がまったく足りていなかったことが今回の改良が必要になったいちばんの原因。

開発の最初の段階で、解答欄矩形を余すところなく認識出来て、舞い上がってしまった自分が、幼かったんだなー。

【もくじ】

1.僕のアルゴリズムの問題点
2.解答欄をブロック化して認識処理を実行
 【間違えポイント①:範囲を指定して画像を切り出す】
 【間違えポイント②:OpenCVのfilenameはPAnsiChar型】
3.まとめ
4.お願いとお断り

1.僕のアルゴリズムの問題点

手書き答案をスキャンして得た画像データから同一設問の解答欄のみを抽出して一覧表示し、採点後、返却用答案画像に採点結果を書き戻すプログラムを書いた。

その際、スキャンした答案画像の解答欄を自動認識する矩形検出器も作成。採点プログラムに同梱して配布。同僚に使ってもらったのだけれど・・・。

解答用紙の解答欄が複数列(?)存在するような形式の解答用紙では、問題が発生。

それは、どんな問題かと言うと・・・

例えば、次のような横書き形式の解答用紙であった場合に、Myプログラムで矩形検出を実行すると・・・

検出した矩形データ(座標群)のうち、最も左上の矩形を最初に赤枠(ラバーバンドで囲って)表示、ユーザーが解答欄であるか・どうかを判定(選択)、座標自体はMemoに数値で一覧表示してあるから ↓ 矢印キーで次の矩形へ・・・という流れで、採点に必要な解答欄座標のみを取得するように設定。

解答欄と、その座標をGUIで表示する(実際の画像)。

ところが、次のような解答用紙の場合・・・

左の画像を右へコピペしたので、設問番号がオカシイのは無視してください・・・

・・・のですが、矩形検出を実行後、検出された座標群から、僕のアルゴリズムで解答欄の選択を実行すると・・・

解答欄矩形の座標自体は、確実に取得できているから、矢印キーを駆使して、採点したい向きに解答欄の座標が並ぶように、座標を選択していけばいいだけの話なんだけれど。

これが・・・

超絶。すーぱーめんどくさい!

さらに、どんなにまっすぐ解答用紙をセットしても、必ず右肩上がり(画像が左に0.05度くらい傾いた状態で)でスキャンしてくれるという、メインで使用しているスキャナーならではのヘンなクセもあり、しかも、その画像に対して「Y座標の小さい順に赤枠で囲む」という僕のアルゴリズムは「正しく」機能するから、解答欄を上から下へ、行単位では左から右へという夢見た処理の流れは完全に逆転。採点候補の解答欄は左右に飛び、行単位でも右から左へ、想定とは真逆の順番で次の採点候補矩形が延々と表示される結果に・・・。こんな状態で、提示(表示)された解答欄矩形の座標を、採点順に正しく選択することは、年配の同僚にはほぼ不可能・・・

解答欄矩形、それ自体は 100% 正しく検出できているのですが・・・
あまりにも、こちらの気持ちを無視したプログラムの挙動を目の当たりにして・・・

責任者を出せ!って
怒鳴りたくなるんだけど・・・

ちょっと・・・、待って。

責任者。オレじゃん、
みたいな・・・

解答用紙によっては、さらに・・・

この例だと、Y座標がムチャだから、解答欄の選択作業はさらに困難を極め・・・

もっと・・・、発展(?)して

もぉ T_T

2.解答欄をブロック化して認識処理を実行

どうすればいいか?

答えはひとつしかありません。そうです!

解答用紙の解答欄を、採点順になるよう「まとまりのブロック」に分けて、ブロックごとに解答欄座標の取得手続きを行えばいいのです。

これで「一部がCutされた解答欄」は、矩形として認識されないので、最後に手動で座標データを追加すれば、なんとかなります(手動設定のプログラムは期待通りに動作しているから安心だし、上の図のような特殊な解答欄は最後の方にある場合が多い)。

この新方針のもとで、プログラムを見直してみると・・・

もともとのアルゴリズムは画像全体を1ブロックとして扱って、解答欄の矩形座標を検出しているから・・・

procedure TForm1.btnGetSquareClick(Sender: TObject);
var
  //PythonのScriptを入れる
  strScrList:TStringList;
  //Pythonから送られたデータを保存する -> グローバル変数化
  //strAnsList:TStringList;
  //Sort
  i,j:integer;
  //strFileName:string;
  strList:TStringList;
begin

  //画像分割処理なし(初期状態)

  //初期化
  Memo1.Clear;
  //Scriptを入れるStringList
  strScrList:=TStringList.Create;
  //結果を保存するStringList
  strAnsList:=TStringList.Create;

  try
    //Python Script
    strScrList.Add('import cv2');
    strScrList.Add('import numpy as np');
    strScrList.Add('from PIL import Image');
    //strScrList.Add('img = cv2.imread("./ProcData/sample2.jpg")');
    //strScrList.Add('img = cv2.imread(r"'+StatusBar1.SimpleText+'")');
    strScrList.Add('pil_img = Image.open(r"'+StatusBar1.SimpleText+'")');
    strScrList.Add('img = np.array(pil_img)');
    strScrList.Add('gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)');
    strScrList.Add('gray = 255 - gray');
    strScrList.Add('ret, bin_img = cv2.threshold(gray, 20, 255, cv2.THRESH_BINARY)');
    strScrList.Add('contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)');
    strScrList.Add('contours = list(filter(lambda x: cv2.contourArea(x) > '+cmbThreshold.Text+', contours))');
    strScrList.Add('for i in range(len(contours)):');
    strScrList.Add('    im_con = img.copy()');
    strScrList.Add('    x, y, w, h = cv2.boundingRect(contours[i])');
    strScrList.Add('    var1.Value =str(x)+","+str(y)+","+str(x+w)+","+str(y+h)');
    //Scriptを表示
    Memo1.Lines.Assign(strScrList);
    //「0による浮動小数点数除算」のエラーを出ないようにするおまじない
    MaskFPUExceptions(True);
    //Execute
    PythonEngine1.ExecStrings(Memo1.Lines);
    //結果を表示
    Memo2.Lines.Assign(strAnsList);
  finally
    //StringListの解放
    strAnsList.Free;
    strScrList.Free;
  end;

end;

・・・という感じで、かなりシンプル!

このあと、横書き・縦書きという解答欄の書き方に応じて、解答欄座標の並べ替えを行っている。どちらかというと、解答欄矩形の検出作業はOpenCVにおまかせで、並べ替えのアルゴリズムの方を工夫した記憶が・・・。

とりあえず・・・

横書き解答用紙が選択された時のみ、解答欄を何ブロックに分割して処理するか、GUIで選択できるようにして・・・

ブロック数分Loopを廻す中で、OpenCVのcvSetImageROI関数を用いて答案画像を分割、結果を一時Memoに書き込んで、2ブロック以降の座標値に対しては、そのx座標を答案画像上での値に修正(←実はコレを忘れていて、動作確認の際 ??? なことになり、初めて取得した座標値の修正作業の必要性に気づく)、で、最後に採点する順番になるよう座標を並べ替えてユーザーに提示する準備を実行。さらに、横書き解答用紙の場合は、一時Memoから座標データ提示用Memoへデータを移動して終了。・・・みたいな手続きのカタチにプログラムを半日程度かけて修正。

【間違えポイント①:範囲を指定して画像を切り出す】

cvSetImageROI関数の使い方は、その内部に入れてるcvRect関数の第1引数が切り出し位置の左上x座標、第2引数が切り出し位置左上のy座標、第3引数が切り出す幅、第4引数が切り出す高さとなっている。

最初、よく考えずにcvRect関数の第3、4引数を切り出し位置右下のx、y座標だと思い込んで設定し、切り出した画像の幅が変化することから、設定の誤りに気づく。前にマークシートリーダーを開発した時(Python環境を導入する前の段階で)、Windows用のOpenCVで画像処理していたときに、この関数のお世話になったはずなんだけど、もぉすっかり忘れてしまっていたようです。

  //指定範囲の画像を切り出して保存
  //cvRect(x, y, Width, Height)
  cvSetImageROI(sourceImage, cvRect(top_x, top_y, xWidth, yHeight));

【間違えポイント②:OpenCVのfilenameはPAnsiChar型】

それから、画像データへのPathとファイル名を入れる変数p1が PAnsiChar 型であることを、こちらもすっかり忘れていて、String型で引数を指定してエラーになって初めてそれを思い出す。変数に値を代入する際、いったん AnsiString 型でキャストして更に PAnsiChar でキャスト。

  //画像データのファイル名
  p1:PAnsiChar;

begin

  ・・・

  //String 型の文字列を PAnsiChar 型の文字列に変換
  //AnsiString 型でキャストして更に PAnsiChar でキャスト
  p1:=PAnsiChar(AnsiString('CutImage0'+IntToStr(i)+'.jpg'));

  //画像を保存する
  cvSaveImage(p1, sourceImage);

  ・・・

end;

完成した手続きがこちら(変数名等は思いつくまま、意図した通りに動けば 可 とした)

procedure TForm1.btnGetSquareClick(Sender: TObject);
var
  //PythonのScriptを入れる
  strScrList:TStringList;
  //Pythonから送られたデータを保存する -> グローバル変数化
  //strAnsList:TStringList;
  //Sort
  i,j:integer;
  //strFileName:string;
  strList:TStringList;

  //画像の等幅分割
  //切り出し領域
  top_x, top_y:integer;
  yHeight:integer;
  //xの増分
  xWidth, iMax:integer;
  //for Imageの読み込み
  sourceImage: PIplImage;
  //画像データのファイル名
  p1:PAnsiChar;

  //x座標の補正
  str1, str2, str3, str4:string;

begin

  //画像分割処理なし(初期状態)
  {
  //初期化
  Memo1.Clear;
  //Scriptを入れるStringList
  strScrList:=TStringList.Create;
  //結果を保存するStringList
  strAnsList:=TStringList.Create;

  try
    //Python Script
    strScrList.Add('import cv2');
    strScrList.Add('import numpy as np');
    strScrList.Add('from PIL import Image');
    //strScrList.Add('img = cv2.imread("./ProcData/sample2.jpg")');
    //strScrList.Add('img = cv2.imread(r"'+StatusBar1.SimpleText+'")');
    strScrList.Add('pil_img = Image.open(r"'+StatusBar1.SimpleText+'")');
    strScrList.Add('img = np.array(pil_img)');
    strScrList.Add('gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)');
    strScrList.Add('gray = 255 - gray');
    strScrList.Add('ret, bin_img = cv2.threshold(gray, 20, 255, cv2.THRESH_BINARY)');
    strScrList.Add('contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)');
    strScrList.Add('contours = list(filter(lambda x: cv2.contourArea(x) > '+cmbThreshold.Text+', contours))');
    strScrList.Add('for i in range(len(contours)):');
    strScrList.Add('    im_con = img.copy()');
    strScrList.Add('    x, y, w, h = cv2.boundingRect(contours[i])');
    strScrList.Add('    var1.Value =str(x)+","+str(y)+","+str(x+w)+","+str(y+h)');
    //Scriptを表示
    Memo1.Lines.Assign(strScrList);
    //「0による浮動小数点数除算」のエラーを出ないようにするおまじない
    MaskFPUExceptions(True);
    //Execute
    PythonEngine1.ExecStrings(Memo1.Lines);
    //結果を表示
    Memo2.Lines.Assign(strAnsList);
  finally
    //StringListの解放
    strAnsList.Free;
    strScrList.Free;
  end;
  }

  //画像分割処理ここから

  //初期化
  //Memo1.Clear;
  Memo2.Clear;
  MemoTemp.Clear;

  //初期化(定数的に利用する)
  top_y:=0;

  //分割数
  iMax:=StrToInt(cmbPartition.Text);

  //結果を保存するStringList
  strAnsList:=TStringList.Create;

  //初期化
  xWidth:=0;

  try

    for i := 0 to iMax-1 do
    begin

      //画像を読み込む(Bitmap・JPEGどちらも読み込み可能)
      p1:=PAnsiChar(AnsiString(StatusBar1.SimpleText));
      sourceImage := cvLoadImage(p1, CV_LOAD_IMAGE_ANYDEPTH or CV_LOAD_IMAGE_ANYCOLOR);

      //intとTruncは小数点以下を切り捨て。異なるのは、戻り値がintは実数、Truncは整数になること
      xWidth:=Trunc(SimpleRoundTo(sourceImage.Width/iMax,0));
      yHeight:=sourceImage.Height;

      //切り出す座標を指定
      top_x:= xWidth * i;

      try

        //指定範囲の画像を切り出して保存
        //cvRect(x, y, Width, Height)
        cvSetImageROI(sourceImage,cvRect(top_x, top_y, xWidth, yHeight));

        //String 型の文字列を PAnsiChar 型の文字列に変換
        //AnsiString 型でキャストして更に PAnsiChar でキャスト
        p1:=PAnsiChar(AnsiString('CutImage0'+IntToStr(i)+'.jpg'));
        //画像を保存する
        cvSaveImage(p1, sourceImage);

      finally
        //イメージの解放
        cvReleaseImage(sourceImage);
      end;

    end;

    for i := 0 to iMax-1 do
    begin

      //Scriptを入れるStringList
      strScrList:=TStringList.Create;

      //x座標の補正値を計算
      top_x:= xWidth * i;

      try
        //Python Script
        strScrList.Add('import cv2');
        strScrList.Add('import numpy as np');
        strScrList.Add('from PIL import Image');
        //strScrList.Add('img = cv2.imread("./ProcData/sample2.jpg")');
        //strScrList.Add('img = cv2.imread(r"'+StatusBar1.SimpleText+'")');
        //strScrList.Add('pil_img = Image.open(r"'+StatusBar1.SimpleText+'")');
        strScrList.Add('pil_img = Image.open(r"'+'CutImage0'+IntToStr(i)+'.jpg'+'")');
        strScrList.Add('img = np.array(pil_img)');
        strScrList.Add('gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)');
        strScrList.Add('gray = 255 - gray');
        strScrList.Add('ret, bin_img = cv2.threshold(gray, 20, 255, cv2.THRESH_BINARY)');
        strScrList.Add('contours, hierarchy = cv2.findContours(bin_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)');
        strScrList.Add('contours = list(filter(lambda x: cv2.contourArea(x) > '+cmbThreshold.Text+', contours))');
        strScrList.Add('for i in range(len(contours)):');
        strScrList.Add('    im_con = img.copy()');
        strScrList.Add('    x, y, w, h = cv2.boundingRect(contours[i])');
        strScrList.Add('    var1.Value =str(x)+","+str(y)+","+str(x+w)+","+str(y+h)');
        //Scriptを表示
        Memo1.Clear;
        Memo1.Lines.Assign(strScrList);
        //「0による浮動小数点数除算」のエラーを出ないようにするおまじない
        MaskFPUExceptions(True);
        //Execute
        PythonEngine1.ExecStrings(Memo1.Lines);
        //結果を表示
        if RadioButton1.Checked then
        begin
          //x座標を補正する
          MemoTemp.Lines.Assign(strAnsList);
          if i<>0 then
          begin
            for j := 0 to MemoTemp.Lines.Count-1 do
            begin
              //値を取得
              str1:=GetTokenIndex(MemoTemp.Lines[j],',',0);
              str2:=GetTokenIndex(MemoTemp.Lines[j],',',1);
              str3:=GetTokenIndex(MemoTemp.Lines[j],',',2);
              str4:=GetTokenIndex(MemoTemp.Lines[j],',',3);
              //カンマ区切りの文字列の1,3番目にtop_x値を加える(座標を修正)
              str1:=IntToStr(StrToInt(str1)+top_x);
              str3:=IntToStr(StrToInt(str3)+top_x);
              //書き戻し
              MemoTemp.Lines[j]:=str1+','+str2+','+str3+','+str4;
            end;
          end;
        end else begin
          Memo2.Lines.Assign(strAnsList);
        end;
      finally
        //StringListの解放
        //strAnsList.Free;
        strAnsList.Clear;
        strScrList.Free;
      end;

      //横書きの場合のみ実行
      if RadioButton1.Checked then
      begin

        //strFileName:=ExtractFilePath(StatusBar1.SimpleText)+'Temp.csv';
        //MemoTemp.Lines.SaveToFile(strFileName);

        strList := TStringList.Create;
        try
          for j := 0 to MemoTemp.Lines.Count-1 do
          begin
            strList.Add(MemoTemp.Lines[j]);
          end;
          //並び替え 降順 -> True
          //if RadioButton1.Checked then
          //begin
            fAscending := False;
            fIndex := 1; //2番目の項目を
            fStyle := ssInteger; //整数型でソート
            strList.CustomSort(MyCustomSort); //ソート開始
          //end else begin
          //  fAscending := True;
          //  fIndex := 0; //1番目の項目を
          //  fStyle := ssInteger; //整数型でソート
          //  strList.CustomSort(MyCustomSort); //ソート開始
          //end;

          //データ抽出
          //Memo2.Clear;
          for j := 0 to strList.Count - 1 do
          begin
            Memo2.Lines.Add(strList[j]);
          end;
        finally
          MemoTemp.Clear;
          strList.Free;
        end;
      end;
    end;

  finally
    //StringListの解放
    strAnsList.Free;
  end;

  //画像分割処理ここまで

  //縦書きの場合のみ実行
  if RadioButton2.Checked then
  begin

    //strFileName:=ExtractFilePath(StatusBar1.SimpleText)+'Temp.csv';
    //Memo2.Lines.SaveToFile(strFileName);

    strList := TStringList.Create;
    try
      for i := 0 to Memo2.Lines.Count-1 do
      begin
        strList.Add(Memo2.Lines[i]);
      end;
      //並び替え 降順 -> True
      //if RadioButton2.Checked then
      //begin
      //  fAscending := False;
      //  fIndex := 1; //2番目の項目を
      //  fStyle := ssInteger; //整数型でソート
      //  strList.CustomSort(MyCustomSort); //ソート開始
      //end else begin
        fAscending := True;
        fIndex := 0; //1番目の項目を
        fStyle := ssInteger; //整数型でソート
        strList.CustomSort(MyCustomSort); //ソート開始
      //end;

      //データ抽出
      Memo2.Clear;
      for i := 0 to strList.Count - 1 do
      begin
        //Memo2.Lines.Add(GetCommaText(strList.Strings[i],fIndex));
        Memo2.Lines.Add(strList[i]);
      end;
    finally
      strList.Free;
    end;

  end;

  if RadioButton2.Checked then
  begin
    ScrollBox1.HorzScrollBar.Position:=ScrollBox1.HorzScrollBar.Range;
  end else begin
    //ScrollBarが表示されていなくてもエラーにならない
    ScrollBox1.HorzScrollBar.Position:=0;
  end;

  //表示
  LBRow.Visible:=True;
  LBRow2.Visible:=True;

  //操作可能に設定
  btnOpen.Enabled:=True;
  btnSave.Enabled:=True;

  //操作不可に設定
  btnGetSquare.Enabled:=False;

  //先頭へスクロール
  Memo2.Perform(WM_VSCROLL,SB_TOP,0);

  //先頭行へ
  Memo2.SelStart:=SendMessage(Memo2.Handle, EM_LineIndex, 0, 0);
  Memo2.Perform(EM_SCROLLCARET, 0, 0);  //キャレット位置までスクロール
  Memo2.SetFocus;

  GetLinePos;

  //矩形を表示
  Memo2Click(Sender);

end;

ちなみに、最後の解答欄矩形を表示する処理は・・・

procedure TForm1.Memo2Click(Sender: TObject);
var
  i:integer;
  //x1,x2,x3,x4:integer;
  //y1,y2,y3,y4:integer;
  p1,p2:TPoint;
  //文字列切り分け///////////////////////////////////////////////////////////////
  function RemoveToken(var s:string;delimiter:string):string;
  var
    p:Integer;
  begin
    p:=Pos(delimiter,s);
    if p=0 then Result:=s
    else Result:=Copy(s,1,p-1);
    s:=Copy(s,Length(Result)+Length(delimiter)+1,Length(s));
  end;

  function GetTokenIndex(s:string;delimiter:string;index:Integer):string;
  var
    i:Integer;
  begin
    Result:='';
    for i:=0 to index do
      Result:=RemoveToken(s,delimiter);
  end;

begin

  if not EditTF then
  begin

    //座標を取得
    i:=Memo2.Perform(EM_LINEFROMCHAR, Memo2.SelStart, 0);
    //ShowMessage(IntToStr(i));

    //エラー対策
    if Memo2.Lines[i]='' then Exit;

    x1:=StrToInt(GetTokenIndex(Memo2.Lines[i],',',0));
    y1:=StrToInt(GetTokenIndex(Memo2.Lines[i],',',1));
    x2:=StrToInt(GetTokenIndex(Memo2.Lines[i],',',2));
    y2:=StrToInt(GetTokenIndex(Memo2.Lines[i],',',3));

    if Assigned(plImage1) then begin
      FreeAndNil(plImage1);
    end;

    //コンポーネントを生成し,イベントを定義し,位置を指定して画像を表示
    plImage1:=TplResizeImage.Create(Self);
    plImage1.Parent:=ScrollBox1;
    plImage1.TransEvent:=True;
    //クライアント座標をスクリーン座標へ変換
    //GetSystemMetrics(SM_CYCAPTION) -> タイトルバーの高さ
    //GetSystemMetrics(SM_CYFRAME) -> ウィンドウの枠幅
    p1.X:=x1-(GetSystemMetrics(SM_CYFRAME) div 2);
    p1.Y:=y1-GetSystemMetrics(SM_CYCAPTION)-(GetSystemMetrics(SM_CYFRAME) div 2);
    p2.X:=x2-(GetSystemMetrics(SM_CYFRAME) div 2);
    p2.Y:=y2-GetSystemMetrics(SM_CYCAPTION)-(GetSystemMetrics(SM_CYFRAME) div 2);
    p1:=Image1.ClientToScreen(p1);
    p2:=Image1.ClientToScreen(p2);
    plImage1.SetBounds(p1.X, p1.Y, p2.X-p1.X, p2.Y-p1.Y);

    //SelectedプロパティをTrueにするとラバーバンドとグラブハンドルが表示される
    plImage1.Selected := True;
    plImage1.BringToFront;

  end;

end;

最終的に完成したコードはまわりくどくて、汚いけど、動きは期待したとおり、例えば3ブロックある解答用紙での処理は・・・

1ブロックめの最初。

このまま、下方向へ解答欄矩形の座標データを選択して、いちばん下の座標まで移動すると、次の矢印キー押し下げと同時に赤枠は2ブロックめの先頭へ移動。

2ブロックめは上の例だと2列分あるので、ちょっと処理が面倒だけど、実際の解答用紙ではこんな例はまずないので大丈夫ということにしておいて、とりあえず、いちばん下の座標まで移動したところで次の矢印キー押し下げ、同時に赤枠は3ブロックめの先頭へ移動。

で、3ブロックめの解答欄矩形も余すところなく、選択。実に、イイかんじ。

コレだ! コレだ!!
コレを実現したかったんだ☆

やったー!!
できた!!!

3.まとめ

複数ブロックからなる解答用紙の解答欄矩形検出は(考えてみれば当たり前ですが)、次のように処理するとうまく行きます。

(1)解答用紙の画像を予め複数ブロックに分割して別画像として保存
(2)それぞれのブロックごとに解答欄の矩形を検出&採点する順番に並べ替え
(3)ブロックごとに取得した座標値を解答用紙画像全体の中での座標値に変換
(4)全座標値を結合して定義ファイル等に保存

今回は、上の(3)の処理を失念してプログラミングしていたので、必要だった修正は、ブロックごとの値として取得したx座標を、解答用紙画像全体の中でのx座標に変換する処理を追加するだけという、この修正わずか1回で期待したとおりに動作するプログラムを完成できました。これは僕的には極めて稀有な例で、言うのも恥ずかしい事実ですが、いつも七転八倒状態を延々と繰り返してなんとか思ったとおりの動作を実現しているので、たまにはこんなコトがあってもいいかなー。みたいな♪

4.お願いとお断り

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

風を切ろう

ゼファー400のレストア動画を見た。

13編全部、最高だったけど。

何より胸に残ったのは・・・

「もう一度、風を切ろう・・・」って、言葉。

この言葉のほんとうは、二輪車乗りにしか、わからないんじゃないかな・・・

( いい言葉だな )って、

つい ・・・ 瞳がうるんでしまった。

ドキドキしながら

きらめく風の中・・・

初めてアクセルを開けた日・・・

僕は、まだ16歳だった・・・。

このレストア動画の中に・・・ そんなシーンは、1秒もないのに。

なぜか、懐かしい・・・風の匂いまで、僕は思い出せた・・・。

「必死だった」って、

レストアシリーズとは別の動画で、この動画の作者は、実にさりげなく語ってたけど、

こんなに「オートバイが好き」って、その気持ちが伝わってくる映像を・・・

僕は、これまでに、ほんとうに・・・ ほんとうに見たことがない。

もちろん、これからも、ないだろう。

そう、断言できるくらい、心が動いたから、言う。

しせい、てんにつうず。

相手が人でなく、それが「機械」であっても・・・ それはきっと同じ。

真心を伝えたい 相手が、人でなく、たとえ機械であったとしても、

伝えたい「想い」が、間違いなく、「ほんとう」なら・・・

嘘も、偽りもない、その「想い」に勝るものなんか、絶対にない。

だから、敬意を込めてその総集編へのリンクを、ここに貼ります。

ふみっちょさん。心から、本気で、ほんとうの、ありがとうです。

あなたの動画を見て、

オートバイに乗りたかった気持ちの原点を、僕は、思い出しました。

16歳だった・・・ 初めて、風を切った日の・・・

アクセルを開けた、あの一瞬の・・・記憶。

そう。あの瞬間から、僕は・・・ オートバイが大好きに・・・

そんな想いを抱きしめて、眠ったから・・・ かな・・・?

8月のある朝、目が覚めていちばん最初に思い出したのは・・・

まだ、バイクで行ったことのない、家のすぐ近くにある、大好きな場所だった。

何年も前から、クルマでは、何度も訪れた場所なのに、なぜか・・・

そこへは一度も、バイクで行ったことはなかった・・・。

( 今日のために、とっておいた? )

まぁ、いい。今、考えるのはよそう。

人生には、いろんな不思議があっていい。

きみと、そこへ行こう。

そう、風を切って。

何度も、何度も、ここで、近くの空港に降りる飛行機を数えた。
初めてここへきたきみは、はしゃいでるみたいに輝いて。

でも、33歳。

いっぱい、壊れたよな・・・

フロントフォークからのオイル漏れ
キャブレターのオーバーフロー

そうだ、コンビニへ入ろうとしたら
サイドスタンドが落ちてなかったこともあった。

ウインカーのプラスチックが経年劣化して、根本から折れたことも
リアショックのオイルが全部抜けたこともあった。

セルが廻らなくなったことも
ブレーキが噛んだことも

立ちゴケしてクラッチレバーを折ったり、
エンジンを傷つけたこともあった・・・

レバーは交換すればよかったけど
エンジンは、涙目になりながら、必死で磨いて再生したんだ・・・

そうだ

カムチェーンテンショナーの押しが足りなくなって
アクセル戻す度にエンジンからすごいガラガラ音が聞こえてきた時は
心が折れそうになったな・・・

やっとの思いで、一度も外したことがないキャブレターを外して・・・
取り出せたカムチェーンテンショナーは、Webで見た写真とは全然違う旧型で・・・
どうやって調整したらいいのか、
まったく、わからなかった。

自分でもなんとかなりそうな、C3型以降用(?)のテンショナーを手に入れて・・・
ノッチの押し込みを調整しながら・・・震える手で装着。
汗まみれになってキャブレターを元に戻し、
祈るような気持ちで、スターターボタンを押したんだ。

エンジンのガラガラ音が完全に消えた時は、天にも昇る心地だった。

でも、熱にさらされる場所は、どうしても酸化が進んで・・・

何度も磨いて、耐熱塗料を塗り直したマフラー。
それほど高温にならない後ろ部分は耐熱塗料でなくてもOKなことを
経験から知ったけど。

さすがに、エンジンに、それは通用せず・・・
オリジナルの塗装は、もうほとんど残ってない・・・。

みんな、サビとの戦いで、消えてしまった・・・

でも、まだ、走れるよね。

走れる限り、ふたりで、風を切ろう。

もうすぐ、秋だね。

ふたり、黄金色の風の中、

アクセルを開けて・・・

どこまでも、

そして、いつまでも・・・

風を切って・・・

走ろう☆