月別アーカイブ: 2022年11月

Controlling Message Dialog Buttons

メッセージダイアログのボタンを制御する!

メッセージダイアログのボタンを「状況によってはクリックできないように設定」する必要に迫られて、その方法を調べてみたのだけれど、探した範囲では見つからず、よくよくコードを眺めたら、ボタンにはEnabledプロパティがあることに気付き、簡単に実現できちゃった・・・というお話。

1.ヒトはよく間違える
2.ボタンをクリック不可に設定
3.まとめ
4.お願いとお断り

1.ヒトはよく間違える

TImageに表示した画像上で、連続して矩形選択するプログラムを書いた。ユーザーが画像上で矩形選択する毎に次のメッセージダイアログを表示し、続けて矩形選択する場合は「はい」や「やり直し」、指定した選択範囲をすべて保存して終了する場合は「終了」、設定内容を保存せずに終了する場合は「キャンセル」を、それぞれ選択できるようにプログラミングしたのだが・・・

説明をよく読んで作業してもらえば大丈夫だと思ったんだけど・・・

50回とか、それくらい連続して矩形選択を繰り返すと、『終了』をクリックしなければならない場面で、つい・うっかり、『はい』をクリックしてしまうという、いかにも人間らしい失敗があちこちで発生。この問題が起きるのを防止するため、次のようにラベルに残りの選択数を表示して注意を促したが・・・

ラベルに残りの選択回数を表示

「あー!まちがえちゃったー☆」という声が増えはしても、減ることはなく、根本的な間違いクリック防止対策を施す必要性を痛感。で、行うべき対策はただひとつ。矩形選択が残り0回になったら、表示するメッセージダイアログの「はい」ボタンをクリック不可能に設定する & それまでは「はい」ボタンを初期選択状態としていたのを「終了」ボタンに変更する。これだけ!

早速、『Delphi メッセージダイアログ ボタン Enabled』を検索キーワードにしてGoogle先生にお伺いをたてたが探した範囲では、参考となる情報は見つからず。

仕方がないので、残り0回になったら「はい」ボタンのないメッセージダイアログを表示することにしようか・・・と思いつつ、コードを眺めていたら、

TButton(Dlg.FindComponent('YES')).Caption := 'はい';

TButton・・・? の
Captionプロパティを「はい」に設定してる・・・?

・・・ってコトは、当然、Enabledプロパティも設定できるはず! と気づき、早速設定☆

2.ボタンをクリック不可に設定

矩形選択の残りの回数がゼロになった時点で「はい」ボタンのEnabledプロパティをFalseに設定し、これをクリックできないようにする。さらにそれまでは「はい」ボタンが初期選択状態であったのを「終了」ボタンに変更してみた。その方法は次の通り。

var
  msg : string;
  rc : integer;
  Dlg : TForm;
begin
  Dlg:=CreateMessageDialog(msg,mtConfirmation,[mbYes,mbNo,mbOK,mbCancel]);
  try
    //フォームの中央に表示
    Dlg.Left:=Form1.Left+(Form1.Width -Dlg.Width ) div 2;
    Dlg.Top:=Form1.Top +(Form1.Height-Dlg.Height) div 2;
    //ボタンの文字を変更
    TButton(Dlg.FindComponent('YES')).Caption := 'はい';
    TButton(Dlg.FindComponent('NO')).Caption := 'やり直し';
    TButton(Dlg.FindComponent('OK')).Caption := '終了';
    TButton(Dlg.FindComponent('CANCEL')).Caption := 'キャンセル';
    //選択(クリック)の可否を設定
    if StrToInt(矩形設定数.Text) - 矩形選択数 <> 0 then
    begin
      //「はい」ボタンを選択できる
      TButton(Dlg.FindComponent('YES')).Enabled := True;
      //初期選択状態のボタンを「はい」にする
      Dlg.ActiveControl := TWinControl(Dlg.FindComponent('YES'));
    end else begin
      //「はい」ボタンは選べない
      TButton(Dlg.FindComponent('YES')).Enabled := False;
      //初期選択状態のボタンを「終了」にする
      Dlg.ActiveControl := TWinControl(Dlg.FindComponent('OK'));
    end;
    //表示
    rc := Dlg.ShowModal;
  finally
    Dlg.Free;
  end;

  //必要に応じて記述
  case rc of
    mrYes: begin
      //「はい」が選択された場合の処理

    end;
    ・・・省略・・・
  end;

end;

テストしてみました!
矩形選択数が残り0(ゼロ)になると・・・

「はい」ボタンはクリックできない!

これでもう大丈夫☆
このシーンで間違って「キャンセル」をクリックするヒトは、そんなに多くないはず・・・

3.まとめ

メッセージダイアログに表示した特定のボタンをクリックできないようにするには・・・

//「はい」ボタンをクリック不可に設定
TButton(Dlg.FindComponent('YES')).Enabled := False;

メッセージダイアログは何度も使ってきたけれど、ボタンのEnabledを設定したのは今回が初めての経験でした!!

4.お願いとお断り

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

If you want to show ScrollBar.

スクロールバーを表示したい時は・・・

複数のTImageを切り替えて表示し、あんな処理やこんな処理やそんな処理をそれぞれの場面で実行する場合、「あんな処理」ではGoodだったことが、「こんな処理」ではBadになっちゃうことがある。・・・んで、プログラムをイロイロいじって矛盾をなんとか解消し、さらに、エラー対策をこれでもか!と詰め込み、はたまた新たに要望のあった新機能を追加・・・、もう自分でも全体像がつかめないほど矜羯羅がってスパゲッティ状態になったプログラムに、総仕上げの「そんな処理」を書き足し、その中でTImageに画像を表示したら、自動的に出るはずの『スクロールバーが出ない!』みたいなー (T_T)

これまでにも、何度もこの問題で悩み、苦しんだ末に、とうとう解決方法を見つけたというお話。

1.問題が起きる状況
2.解決方法
3.お願いとお断り

1.問題が起きる状況

Formに、ScrollBoxをのせ、その上に複数のImageを載せた状態で、状況に応じてImageを切り替えながら作業するような場合、『どこか』で・『なにか』を(設定)やってしまっていて、FormCreate時にスクロールバーが自動的に出るよう、

ScrollBox1.AutoScroll:=True;

と、設定してあるにもかかわらず、ScrollBoxよりはるかに大きな画像を表示しても、垂直・水平両方向のスクロールバーが『出ない!』みたいな・・・

2.解決方法

どこで、なにをやったのか、徹底的に調べて問題を解決するのが本当なんだろうけれど(この正しい解決方法にチャレンジした結果)、あっちをイジったら、こっちがオカしくなり、こっちを直したら、あっちがコケた!みたいなことになるのが怖い。

そこで「これまでに書いたコードには一切変更を加えずに問題を解決する方法」を模索。

そもそも、ScrollBoxにScrollBarが表示される仕組み自体がわからない。その仕組みを調べてみると・・・

Vcl.Forms.TControlScrollBar.Range

https://docwiki.embarcadero.com/Libraries/Alexandria/ja/Vcl.Forms.TControlScrollBar.Rangeより引用

『水平スクロールバーの Range がフォームまたはスクロールボックスの幅より小さい場合,水平スクロールバーは表示されません。垂直スクロールバーの Range がフォームまたはスクロールボックスの高さより小さい場合,垂直スクロールバーは表示されません。』

・・・と説明されている。ってコトは、Imageコントロールに画像をセットした状態で、上の『スクロールバーが表示されない状態を明示的に回避(Rangeの値を手動で大きく設定)』してあげれば、スクロールバーが必ず表示されるはず。そう思って書いたのが次のコード。

  //水平スクロールバーの Range がスクロールボックスの幅より小さい場合,
  //水平スクロールバーは表示されない
  if ScrollBox1.HorzScrollBar.Range < ScrollBox1.Width then
  begin
    //表示したい画像の幅をRangeの値に設定
    ScrollBox1.HorzScrollBar.Range := Image1.Picture.Bitmap.Width;
  end;

  //垂直スクロールバーの Range がスクロールボックスの高さより小さい場合,
  //垂直スクロールバーは表示されない
  if ScrollBox1.VertScrollBar.Range < ScrollBox1.Height then
  begin
    //表示したい画像の高さをRangeの値に設定
    ScrollBox1.VertScrollBar.Range := Image1.Picture.Bitmap.Height;
  end;

  //ScrollBarを最も上へ&最も左へ移動
  ScrollBox1.VertScrollBar.Position := 0;
  ScrollBox1.HorzScrollBar.Position := 0;

  //ScrollBarを最も下へ&最も右へ移動
  //ScrollBox1.VertScrollBar.Position := VertPositionMax(ScrollBox1);
  //ScrollBox1.HorzScrollBar.Position := HorzPositionMax(ScrollBox1);

  //画像を表示
  Image1.Visible := True;

このように「これまでに書いたコードには一切変更を加えずに問題を解決する」ことに、
一応成功☆

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

3.お願いとお断り

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

Button to Scroll Horizontally

横スクロールボタン

縦書きの手書き答案をスキャンして画像化し、設問毎に解答欄画像をかき集め、PCとコラボして採点するプログラムを書いた。ちなみに実行時の画面はこんな感じ。

縦書き答案をスキャンして、PCとコラボして採点

縦書き答案は、基本的に国語のテストで使用されるアレだ(国語以外の教科・科目ではまず使わない?)。通常、設問1の解答欄が最も右側にあり、解答は縦書きで、左へ向かって順次記入する形式になっている。プログラムの仕様をこれに合わせる必要もないかと思ったが、取り敢えず、郷に入ればなんとやらで、答案の形式に倣って、スキャンした答案画像から設問1の解答欄画像をかき集めて右から左へ、出席番号順に並べて表示してみることにした。PCで何か測る場合の座標原点は、左上を(0,0)にするのが普通だから、計算式を考えるのに著しく、いや、激しく頭が混乱したが、頑張って、なんとかこれを実現。

西洋式機械文明と和の精神文化の見事なる融合がここに結実。よかよか *(^_^)*♪
えぇ天気じゃのー

・・・ってか、正直、なんか画面の並びにワタシ、微妙な違和感があるんですけど・・・
まぁ 今さら気にしても仕方ない。深く考えずに、次へ

困ったのはその次。横書き答案の場合は、マウスのホイールをクルクル回せば、画像が縦にどんどんスクロールするから何の問題もないのだが、縦書き答案の場合、採点する際、横のスクロールバーをいちいちドラッグして、画面を左へ・左へと動かさなくてはならない。これがいまいち、どうにも使いづらい(気がした)。

マウスのホイールを廻して、縦ではなく横方向にスクロール・・・って方法もあるんじゃないかと思い、調べてみると「Ctrl+Shift+ホイール回転」で横スクロールできるらしい。

実際に試してみると(何もプログラムを書かなくても)、右へ・左へ、答案画像は確かに横にスクロールする。つまり、これはWindowsの標準仕様なのだ。

でも、一般的に広く認知されている方法ではない(と思う)し、何よりMy採点プログラムは「スクロールは右手・採点は左手が担当する」ことが設計のポイント(こだわり)。私的には、ここでそれを曲げるわけにはいかない・・・(もちろん、ユーザーに「Ctrl+Shift+ホイール回転」で横スクロールすることは案内するつもりだけれど、それに頼らない方法も準備したい・・・)。

そこで思いついたのが、ボタンを使って移動できないか? ということ。設定部に用意したComboBoxで移動人数10名を指定すれば10名分、ScrollBoxではなく、Formにドッキングさせた「スクロールに追従しない」ボタンで、Imageに表示した解答欄画像を左へ(or 右へ)スクロールするプログラムが出来たら使い勝手がよかろーということで、これは、その作成にチャレンジした記録。

1.フローティングで行こう!
2.ToolBarの「閉じる」ボタンを無効化
3.初めてBevelを使う
4.まとめ
5.お願いとお断り

1.フローティングで行こう!

実は前からやってみたかったんだ。VCLコンポーネントのフローティング。
でも、その機会になかなか恵まれなくて、今回、初めて、それにチャレンジ!

移動用のボタンを作成するには、どうするのがいちばんイイのか、調べてみると、ControlBarの上にToolBarを載せ、このToolBar内にToolButtonを作る方法がいちばん良さそうだ。これでやってみてダメだったら、その他の方法を考えることにして、まず、この方法で作ってみることに決定。

復習を兼ねて、作り方を以下に再現。

解答欄の画像はScrollBoxの上に載せたImageに表示している。まず、練習用のFormにPanelを1つ載せて、AlignをalRightに設定。次に、FormにScrollBoxを1つ載せて、AlignをalClientに設定。さらに、このScrollBoxの上にImageを1つ載せる(AlignはalNone)。

FormにVCLコンポーネントを3つ載せ、それぞれにAlignを設定

次に、ScrollBoxをクリックして選択して、その上にControlBarを1つ載せる。

ScrollBoxにControlBarを載せ、そのAlignをalTopに指定

このControlBarの各種プロパティは、以下のように設定。

  1. Alignは「alTop」を指定。
  2. このままだと存在感がありすぎるので、BevelKindプロパティを「bkNone」にして立体感(=境界線)を消す。
  3. AutoSizeプロパティを 「True」にして、ツールバーが複数ある場合に大きさ(幅と高さ)が自動的に変わるように設定。
  4. ドッキングを受け入れる側なので、DockSiteプロパティを「True」に設定。

構造ペインとオブジェクトインスペクタの様子は・・・

構造ペイン

ちなみにペイン(Pane)とは、枠や区画のことなんだそうな。ずっと「痛(イテ)ぇ」だと思ってたのは私だけ? そっちは「Pain」で同音異義語とのこと。だから「構造痛ぇ」じゃなくて「構造枠・区画」でした・・・。英語もイロイロむずかしいな。

ControlBar1のオブジェクトインスペクタ(その1)
ControlBar1のオブジェクトインスペクタ(その2)

次に、ControlBarをクリックして選択し、その上にToolBarを1つ載せる。

ControlBar(見えない)の上に、ToolBarを載せたところ

ToolBarの各種プロパティは、以下のように設定。

  1. AlignプロパティをalNoneにして、大きさを小さくする。
  2. ShowCaptions プロパティを True にする。
  3. ToolBar1を右クリックし、表示されるポップアップメニューの「ボタンの新規作成」を選択。これでツールバーの上に[ToolButton1]が作成される。
  4. 続けて右クリックして、表示されるポップアップメニューから「セパレータ新規作成」を選択すると[ToolButton2]という名前のセパレータが出来る(名前は気にしない)。
  5. さらに右クリックして、表示されるポップアップメニューの「ボタンの新規作成」を選択。これでツールバーの上に[ToolButton3]が作成される。
  6. 構造ペインでToolBarをクリックし、オブジェクトインスペクタのWidthプロパティの値を「180」に変更(フローティングさせた時、ユーザーが扱いやすくなるよう、一工夫)
  7. ドッキングに対応させるため、DragKindプロパティを「dkDock」に設定。
  8. ドッキングに対応させるため、DragModeプロパティを「dmAutomatic」に設定。

4.でセパレータを作成するときの画面

5.で[ToolButton3]を作り、さらに6.で幅を広げた時の画面

構造ペインとオブジェクトインスペクタの様子は・・・

セパレータの名前が[ToolButton2]なのが気になるが、見なかったコトに・・・

セパレータの名前が「どうしても気になる」場合は、構造ペインでToolButton2を選択し、オブジェクトインスペクタのNameプロパティやCaptionプロパティを「MySeparator」等に変更し、さらにToolButton3のNameプロパティやCaptionプロパティを「ToolButton2」にすると満足できるかもしれません・・・。が、説明の都合上、私はこのままで行きます(変更しません)。

7.と8.を設定した時の画面

これはドッキングさせるためのお呪いみたいなもんかなー

へぇー。「おまじない」って漢字で書くと「お呪い」なんだー。
これ、読めって言われたら、私は間違いなく「おのろい」と読んだと思いますが。
日本語もイロイロですな・・・

コードはまだ何にも書いてないけど、この状態で保存して、実行すると・・・

ドッキング状態でToolBarが表示されている
ToolBarは、左右にドラッグして移動することが出来る。静かに感動。
ToolBarを取り外してフローティング状態にすることも出来る。感動を超えて感激。
(さらにフォーム画面上部に移動すれば、またドッキングしちゃったりする)

オレ、プログラム1行も書いてないけど。Delphiすげー!!

ToolBarをフローティングさせたり、移動する際、画面枠の線だけが表示される。
動きがスムーズでなく、かなり「ぎくしゃく」している。感動に疑問符が・・・

このフローティングさせた時の動きが、なんか、気に入らない。チラついてる感があって、ぎくしゃくしていて、かつ、鈍重な感じ。調べてみると、これは改善できるらしい。ToolBar1 の OnStartDock イベントで、以下を記述。

OnStartDock の右の空白部分をダブルクリック
procedure TForm1.ToolBar1StartDock(Sender: TObject;
  var DragObject: TDragDockObject);
begin
  DragObject := TToolDockObject.Create(Sender as TToolBar);
end;

保存して、実行。ToolBarをフローティングさせると、ちらつく枠線でなく、ToolBar本体が表示されたまま、スムーズに移動する。最初からこうしておいてほしかった!!

ちらつく枠線じゃなく、フローティングしているToolBarがスムーズに移動することを確認

今はToolBarしかないから、特に必要じゃないけど、他にもVCLコンポーネントがある場合は、コントロールバーにドラッグされたVCLコンポーネントがToolBarであった場合のみ、ドッキング可としなければならない。これを実現するには、ControlBarのOnGetSiteInfoイベントを次のように設定。

OnGetSiteInfoの右の空白部分をダブルクリック
procedure TForm1.ControlBar1GetSiteInfo(Sender: TObject; DockClient: TControl;
  var InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean);
begin
  if Not (DockClient is TToolBar) then
  Candock := False;
end;

ここまでの内容は、次のWebサイト様で紹介されていた内容を引用&参考にさせていただきました。作者様に心より感謝申し上げます。

ドッキングコントロールを使ってみる

http://www.surveytec.com/prog/delphi/del4rev/dock.html

Delphi2010 コントロールバー(ControlBar)

http://afsoft.jp/program/del2010/p11_033.html

で、最後にToolBarの受け入れ先をFormに設定(いつまでもフローティングさせておくわけにもいかない)。Formを選択しておいて、オブジェクトインスペクタのDockSiteプロパティを「True」にするだけでOK!

ToolBarのドッキング先(受け入れ先)をFormに設定

ここまでの設定をテストするには、何でもいいのでFormに設置したScrollBoxより大きな画像を用意(例:デスクトップをそのままキャプチャーして保存するとか)して、Image1のPictureプロパティでこの画像を指定する。で、Image1のAutoSizeプロパティを「True」に設定。また、ScrollBoxのAutoScrollプロパティがTrueに設定してあることも確認。

Image1のPictureプロパティで大きな画像を指定

参考:画像なんて準備できない・・・という場合

ScrollBoxのAutoScrollプロパティがTrueに設定してあれば、内部に配置したVCLコンポーネントのサイズが枠内に表示しきれないほど大きくなると、自動でスクロールバーが表示される(AutoScrollプロパティがFalseだとスクロールバーは現れない)。

やってみた!
ScrollBoxの幅と高さより、Imageの幅と高さを大きく設定すれば、ScrollBarが自動的に表示されるはず。

Imageが小さい場合、ScrollBarは表示されない
Imageの幅をScrollBoxの幅より大きくすると、横のスクロールバーが表示される
Imageの幅に加え、高さも大きく設定。すると縦のスクロールバーも追加される。
スクロールバーは、表示されるだけでなく、実際にスクロールすることもできる!

参考:画像なんて準備できない・・・ は、ここまで

ScrollBoxのプロパティについて、学んだことをちょっとまとめた!

【ScrollBoxのRangeプロパティ】
スクロールボックス内部に作成される(仮想的な)表示領域のサイズと考えればいいようだ。このRangeのサイズがScrollBoxのサイズより大きくなると、スクロールバーが自動的に現れる。

【ScrollBoxのMarginプロパティ】
スクロールボックスの右下端の「余白」領域のことで、内部のコンポーネントとスクロールボックスの端との距離が、この値より小さくなると、スクロールバーが自動的に現れる。

【ScrollBoxのTrackingプロパティ】
ついでにScrollBoxの縦・横のスクロールバーのTrackingプロパティを「True」にして、スクロールバーを移動させた時、表示されている画像も同時に動くように設定を変更。もし、これを行わない(デフォルト設定のFalseのままだ)と、スクロールバーを動かしている最中は画像は動かず、バーを動かし終えた瞬間に、バーの移動量だけ、画像の表示位置が飛ぶようにずれるスクロールになる。

ScrollBox1の縦のスクロールバーのTrackingプロパティをTrueに設定
ScrollBox1の横のスクロールバーのTrackingプロパティをTrueに設定

同じことを、コードで設定する場合は・・・

  //滑らかぁーにスクロール
  Scrollbox1.VertScrollBar.Tracking := True;
  Scrollbox1.HorzScrollBar.Tracking := True;

保存して、実行する。スクロールボックスより十分に大きい画像を準備したので、縦・横ともにスクロールバーが自動的に表示される。

スクロールバーが自動的に表示されない時は・・・?(間違いかもしれない私の経験)

私がどこかで、なにかを、間違えているのかも、知れないが(自分が絶対に正しいという自信はまるでないけれど)、オブジェクトインスペクタで予め、ScrollBoxのAutoScrollプロパティを「True」に設定してあるにもかかわらず、画像を表示する際に、プログラムコードの中でこれを明示的に指定しないと、「横の」スクロールバーが表示されなくなる現象が、これまでに少なくても2回あった(両方出ないならまだしも、この現象に遭遇した時、縦のスクロールバーは2回とも自動的に表示されていた)。

私がどこかで、なにかを、間違えていたのかも、知れないが、原因が皆目わからず、もちろん検討もつかず、途方に暮れ、悩みに悩んでようやく発見したスクロールバーが自動で表示されなかったトラブルの解決方法なので、いちおう、ここに書いておきます・・・。

  //オブジェクトインスペクタのプロパティでTrueに指定してあっても
  //再指定しないと横スクロールバーは表示されない!
  ScrollBox1.AutoScroll:=True;

もしかしたら、RangeプロパティやMarginプロパティ関係の設定値のどこかに真の原因があったのかもしれないが・・・。あの時、ものすごく、困ったことは本当で、この方法で解決できたことも、本当だから。もしかしたら、同じことで悩んでいる人が・・・どこかに・・・

ToolBarのフローティングとドッキングを確認!

ToolBarをフローティングさせ、Formにドッキングしたところ
画像をスクロールしても、ToolBarの位置は変わらない

2.ToolBarの「閉じる」ボタンを無効化

ここまでの設定だと、プログラム起動時のToolBarは、下のように、画面のTopにあるControlBar(見えない!)にドッキングしている状態で表示される。

プログラム起動時、ToolBarはControlBarにドッキングしている状態で表示される

実は、自分的にはこれがちょっと気に入らない。なぜかというと、作った採点プログラムは横書き答案でも、縦書き答案でも、どちらも採点可能なプログラム。

で、横書き答案を採点する場合には、マウスのホイールをブンブン廻す「縦のスクロール」で解答欄画像を次々に表示できるから、「ボタンでスクロール(表示を移動)」させる機能はオプション設定で、ユーザーが明示的に選択した場合だけ使えれば十分。必要がなければToolBarのVisibleはFalseに設定し、「最初から表示しない」くらいでちょうどいい。

一方、縦書き答案を採点する場合は、ToolButtonのCaptionを「左へ移動」・「右へ移動」に設定したToolBarを最初から表示し、これを使って横にスクロールする機能をユーザーに提供したい。それだとControlBarにドッキングさせた状態(=画像と一緒にスクロールしてしまう)ではなく、最初からフローティングさせて表示し、「横スクロールはこれだよ!」ってユーザーに積極的にPRしたい。

また、ToolBarが「フローティング」すること自体を知らないユーザーも当然いるはずだし、何より、ControlBarからフローティングさせる手順を説明して理解してもらい、ユーザーにそれをやってもらうのは、ユーザーの手間を増やすだけで、ユーザーにとってのメリットは何一つない。必ず使ってもらうなら、最初からフローティングさせておきたい。

最初からFormにドッキングさせておくのも却下。それだと地味で存在を見落としてしまいそうだし、何より、Formの任意の位置に「ユーザーがドッキングさせる」ことが理想的な使用方法だから、フローティング状態からのドッキング位置はユーザー自身が任意の位置に決めてほしい(解答欄の画像は千差万別で高さや幅がことごとく変化するから当然余白の位置も変わり、また、コントロールを操作しやすいと感じる上下左右の位置は、人により異なって当然だろう)。

そこで、PanelにButtonを一つ追加して、ToolBarをフローティング状態で表示できるように、次のコードを書いてみた・・・

Panelの上にButtonを一つ追加
procedure TForm1.Button1Click(Sender: TObject);
var
  r:TRect;
begin
  r.Left:=ScrollBox1.Width div 2;
  r.Top:=ScrollBox1.Height div 2;
  r.Right:=r.Left+ToolBar1.Width;
  r.Bottom:=r.Top+ToolBar1.Height;
  ToolBar1.ManualFloat(r);
end;

保存して実行し、ボタンをクリックすると・・・

実際のプログラムでは、「縦書き答案」が選ばれた時だけフローティング状態で表示する

無事、フローティングした状態でToolBarを表示することができた☆

重要 この逆をやってはイケナイ。

コードでドッキングさせる実験を面白半分でやったら大変なコトに!
エラーメッセージも何もなく、いきなりプログラムが落ちて(Delphiらしくないけど)、PCを再起動しないと、どーにもならなくなっちゃった T_T いったいナンだったんだろー。理由はわかりません。

皆様に謹んで報告申し上げます m(_ _)m

(追伸 アブナイからコードは書きません)

それよりなにより、ここでひとつ、すごく気になったコトがあって、それはせっかくフローティング状態で表示したToolBarだけど、もしユーザーが間違って(或いは故意に)「閉じる」ボタンを押しちゃったら、どう対応するか? ってコト。

地味な灰色のToolBar上で、由々しい存在感を示す「閉じる」ボタン

「由々しい」の意味:そのままにしてはおけない重大な事柄であるさま

実用日本語表現辞典より引用

上の画面で見る限り、ボタンに見えないボタンより、閉じるの [×] マークの方がよほど魅力的に感じます。つい、クリックしたくなるのが人情というもの。やはり、人間は「知」より「情」ですな・・・

で、[×] の誘惑に負けて、ついクリックすると・・・

「さよなら」も言わずに、ToolBarはあっさりと消えた・・・。最後まで無口なイイ奴だったが・・・。

この練習プログラムなら、右上のボタンをクリックすればToolBarは復活するけど、実際の採点プログラムには「ToolBarを表示する」ためだけのボタンを設置する場所はない。ことここに至っては、とるべき方法はただ一つ。何としてもToolBarの [×] ボタンの機能を停止し、ToolBarが非表示になるのを断固阻止しなければならない。よぉーし、オレはやるぞー

・・・ということで、どのように閉じるボタンの機能を停止するか、足りないアタマで考えた。

ボタンそのものを消してしまうのは、調べてみると難しそうだ。最初からグレイアウトさせるのは出来そうだけれど、なんとなーく、面白くない気がした。ちっちゃくても、アレだけ存在感のある [×] 閉じるボタンだもの。ユーザーの皆様にも1度はその真の勇姿を眺めていただき、ぜひ、ついクリックしたくなる「特別な衝動」を感じてもらいたい。

やっぱり、そのなんて言うか、そう!アレだ。アレ。アレで行こう!!
某OSを供給している世界的超有名大企業がよく使う「アンタの責任だよ!」って、アレだ。アレ。あの〇ACに代表される・・・、アレを真似しよう。

〇AC:解答欄(Answer Column)に〇という意味ではありません!

ユーザーが閉じるボタンをクリックしたら、「閉じるボタンを無効化しました!」とメッセージを表示して、それからあらためてボタンをグレイアウトしてクリックできなくする。最初から強制的に使えなくなってたら、「押したいのに、なんでだっ」と、気分を悪くする人もいるかもだけど、試しにクリックしたところ「無効化しました!」というメッセージが出て、それからグレイアウトすれば、きっと「使えたんだけど、無効にしたのはワタシなのね」と、主体的な観点で納得してもらえるんじゃないか? と。

実は「 閉じるボタンを無効化しますか? [はい]・[いいえ]・[キャンセル] 」でもよかったんだけど、さすがにアホらしい気がして、これはボツにしました☆

だって、ナニが選ばれても、ユーザーの意思に関係なく、全部「無効化するつもり」でしたから!

以下、ToolBarの閉じるボタンの機能を停止する方法。

  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  //ToolBarの閉じるボタンを無効化(赤字部分を書いてShift+Ctrl+C)
  TToolDockSite = class(TToolDockForm)
  private
    procedure WMSysCommand(var Msg: TWMSysCommand);
      message WM_SYSCOMMAND;
    end;

uses
  System.UITypes;

  //System.UITypesはMessageDlgを使うために追加

{$R *.dfm}
//手続きを記述
procedure TToolDockSite.WMSysCommand(var Msg: TWMSysCommand);
var
  hBarHandle : HMENU;
begin
  case Msg.CmdType of
    SC_CLOSE: begin
      //処理
      MessageDlg('閉じるボタンを無効化しました!', mtInformation, [mbOk] , 0);

      //閉じたい場合
      //inherited;

      //閉じたくない場合
      //ハンドルを取得
      hBarHandle := GetSystemMenu(Self.Handle,False);

      if hBarHandle <> 0 then
      begin
        //閉じるボタンを無効化する
        EnableMenuItem(hBarHandle, SC_CLOSE, 
          (MF_BYCOMMAND or MF_DISABLED or MF_GRAYED));
        //グレイアウトして無効化されるが、削除はできない
        //DeleteMenu(hBarHandle, SC_CLOSE, 
        //  (MF_BYCOMMAND or MF_DISABLED or MF_GRAYED));
      end;
      DrawMenuBar(Self.Handle);
      //メッセージは「なかった」ことにする
      Msg.Result:=0;

    end;
  else
    inherited;
  end;
end;

で、FormCreate時に、ドッキングを制御するクラスを指定。

procedure TForm1.FormCreate(Sender: TObject);
begin
  //ToolBarの閉じるボタンを無効化
  ToolBar1.FloatingDockSiteClass:=TToolDockSite;
end;

保存して、実行すると・・・

閉じるボタンをクリックすると「無効化」のメッセージを表示
[×]部分がグレイアウトして無効化されている

「ToolBarの閉じるボタンの無効化」は、こちらで紹介されていた記事を参考にして、さらに、この練習プログラムで必要なコードと情報を追加しました。質問者様と回答者様に厚く御礼申し上げます。

TOOLバーのFloating解除

https://www.petitmonte.com/bbs/answers?question_id=4452

実際の採点プログラムでは、最初に設定画面で「採点する答案の書式」・「画面を横にスクロールさせる移動ボタン利用の有無」・「1クリックで移動する解答欄数(=人数)」を指定してもらい、採点を実行。

縦書き答案で、移動ボタンを利用し、1クリックでの移動数は10名分を指定する例
Formにドッキングさせる前のToolBar
右から左へ採点、No,15まで来たら「左へ移動」ボタンをクリック
画面の右端がNo,1からNo,11になる(1クリックで10名分スクロールする)

計算方法は簡単。
個々の解答欄画像1個分の幅を記憶している動的配列から該当解答欄の幅を取得。
これにスクロールする解答欄数を掛ける。
この値を、現在の水平スクロールバーのPositionから引き、
得られた値を水平スクロールバーのPositionに代入する。これだけ!

//縦書き(左へ移動)
//現在、処理中の解答欄番号を取得(-1するのは動的配列要素を考慮)
int解答欄番号 := StrToInt(現在採点している解答欄番号.Text)-1;
//幅の増分 -> 幅の異なる解答欄に対応
XPlus := arryPX[解答欄番号] - arryIX[解答欄番号];
ScrollBox1.HorzScrollBar.Position :=
  ScrollBox1.HorzScrollBar.Position - (XPlus * StrToInt(移動人数.Text));

採点結果は、元の答案画像へ書き戻して、最後に合計点を自動計算。ユーザーが指示した場所へ書き込み。受験者へ返却する答案画像として印刷して、採点終了。やったー!

採点結果を書き戻した答案画像。これを印刷して受験者へ返却する。

3.初めてBevelを使う

目標は実現できた。あとはバグ取りはもちろんのこと、ユーザーになるべくやさしいインターフェイスを提供したい。そのようなユーザー目線で使ってみるといろんなことに気がつく(もちろん、ユーザーに言われて初めて気がつくことも、あるけれど・・・>_< )。

たとえば、コレ!

縦に長い解答欄画像の中に、移動ボタンが埋もれちゃった!

このような場合は、愛しの移動ボタンを「救出」して、他の適切な位置へ素早く配置転換しなければならない。

☆障害物がない場所へ無事移動できました☆

でも、ドッキング時のカタチがコレでは・・・

「かくれんぼ」なら、かなり強者になれそうだが・・・
普通のヒトは困ってしまうレベルのわかりにくさ。すでにボタンにも見えない。

「ここ」をクリックして、
ドラッグすればイイんだなー みたいなモノがあれば・・・☆
どうしたら、いいかなー?

そうだ☆ つかみどころが「ない」んだ!
つかみどころを作ろう!

でも、どぉすれば いい?

ワクだ。枠。枠がほしい。
でも、枠のVCLは、どこにある?

そういえば・・・パラパラっと、この前眺めた参考書になんかあったような・・・
確か、ベバルとか、ベビルとか、ベブルとか、んー! なんだったっけ?

正解は「Bevel(ベベル)」でしたー☆ 舌噛んだー

言葉の意味としては、多種多様な業界で使われ、実に様々な意味をとることが多いそうなのですが、PC業界ではおそらく「デザイン性を上げるための作業を指すのがベベルという言葉です。見た目の雰囲気を変えるのが面取りです・・・」というあるWebサイトにあった言葉が最も適切な気が・・・。

ベベルの意味を用語ごとに3種紹介|一般的な意味と辞書の意味とは

https://lostash.jp/sales/business-skill/1088812#toc17より引用

まぁ ここではとりあえず「枠」で行きます!
さっそくサイズ20×23のBevelをToolBarに配置。

ToolBarの右部分に余白を作っておいたのはこのためです。

Shapeプロパティは、「枠」なんだから迷わず「bsFrame」を指定。
Styleプロパティは、盛り下がる(=凹む)「bsLowered」と、盛り上がる「bsRaised」のふたつにひとつだから、有無を言わさず盛り上がる「bsRaised」に決定。

プロパティを設定

気になる実行時画面は・・・

フローティング時のBevelさん
Formにドッキングした時のBevelさん

Bevelさん、どちらも「しっかり」自己主張してます。

実に、イイ感じ。
誰がどう見ても、この枠内をクリックすれば、
何か、起きそうです。

ここをクリックしてドラッグ・・・なんて言わなくても、
ヒントも表示しなくても、
きっと、大丈夫。

ただ、Bevelさん、あなた・・・
盛り下がってる ようにしか、見えないのですが。
私の気のせいですか・・・?

それは、置いといて
実は、ものすごーく、大切なコトがあるのです。
このままではBevelさんをクリックしてもナニも起きません!

どこを探しても「Enabled」プロパティがありませんが・・・

オブジェクト インスペクタに表示されてませんが、
Bevelさんには、なんと! Enabledプロパティがあって、
しかもそれはデフォルト:True (=有効)で、
このままでは、Bevelさんの下にいるToolBarくんに、
クリックが伝わらないのです。

では、どぉすればいいかと言うと・・・
FormCreate時に、

  //クリックイベントをToolBarへ届ける
  Bevel1.Enabled:=False;

これでOK!
Bevelさんは自己主張しつつ、自らを無効化。
「枠」という役割に専念してくれるようになります!

4.まとめ

  1. Formの上にControlBarを置き、ControlBarの上にToolBarを配置すれば、ToolBarをフローティングさせることができる。ToolBarプロパティは、DragKindプロパティを「dkDock」に、DragModeプロパティを「dmAutomatic」に設定する。
  2. ControlBarやFormなど、ドッキングを受け入れる側は、DockSiteプロパティをTrueに設定するだけでOK!
  3. ToolBarの閉じるボタンは、プログラムコードを書いて無効化できる。
  4. Bevelは「枠」として利用できるが、Enabledプロパティがデフォルトで有効(True)になっているので、クリックを下のVCLに通知するためには、FormCreate時にEnabledをFalseに設定して、Bevel自身を無効化する必要がある。

5.お願いとお断り

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