横スクロールボタン
縦書きの手書き答案をスキャンして画像化し、設問毎に解答欄画像をかき集め、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)。
次に、ScrollBoxをクリックして選択して、その上にControlBarを1つ載せる。
このControlBarの各種プロパティは、以下のように設定。
- Alignは「alTop」を指定。
- このままだと存在感がありすぎるので、BevelKindプロパティを「bkNone」にして立体感(=境界線)を消す。
- AutoSizeプロパティを 「True」にして、ツールバーが複数ある場合に大きさ(幅と高さ)が自動的に変わるように設定。
- ドッキングを受け入れる側なので、DockSiteプロパティを「True」に設定。
構造ペインとオブジェクトインスペクタの様子は・・・
ちなみにペイン(Pane)とは、枠や区画のことなんだそうな。ずっと「痛(イテ)ぇ」だと思ってたのは私だけ? そっちは「Pain」で同音異義語とのこと。だから「構造痛ぇ」じゃなくて「構造枠・区画」でした・・・。英語もイロイロむずかしいな。
次に、ControlBarをクリックして選択し、その上にToolBarを1つ載せる。
ToolBarの各種プロパティは、以下のように設定。
- AlignプロパティをalNoneにして、大きさを小さくする。
- ShowCaptions プロパティを True にする。
- ToolBar1を右クリックし、表示されるポップアップメニューの「ボタンの新規作成」を選択。これでツールバーの上に[ToolButton1]が作成される。
- 続けて右クリックして、表示されるポップアップメニューから「セパレータ新規作成」を選択すると[ToolButton2]という名前のセパレータが出来る(名前は気にしない)。
- さらに右クリックして、表示されるポップアップメニューの「ボタンの新規作成」を選択。これでツールバーの上に[ToolButton3]が作成される。
- 構造ペインでToolBarをクリックし、オブジェクトインスペクタのWidthプロパティの値を「180」に変更(フローティングさせた時、ユーザーが扱いやすくなるよう、一工夫)
- ドッキングに対応させるため、DragKindプロパティを「dkDock」に設定。
- ドッキングに対応させるため、DragModeプロパティを「dmAutomatic」に設定。
4.でセパレータを作成するときの画面
5.で[ToolButton3]を作り、さらに6.で幅を広げた時の画面
構造ペインとオブジェクトインスペクタの様子は・・・
セパレータの名前が「どうしても気になる」場合は、構造ペインでToolButton2を選択し、オブジェクトインスペクタのNameプロパティやCaptionプロパティを「MySeparator」等に変更し、さらにToolButton3のNameプロパティやCaptionプロパティを「ToolButton2」にすると満足できるかもしれません・・・。が、説明の都合上、私はこのままで行きます(変更しません)。
7.と8.を設定した時の画面
へぇー。「おまじない」って漢字で書くと「お呪い」なんだー。
これ、読めって言われたら、私は間違いなく「おのろい」と読んだと思いますが。
日本語もイロイロですな・・・
コードはまだ何にも書いてないけど、この状態で保存して、実行すると・・・
オレ、プログラム1行も書いてないけど。Delphiすげー!!
このフローティングさせた時の動きが、なんか、気に入らない。チラついてる感があって、ぎくしゃくしていて、かつ、鈍重な感じ。調べてみると、これは改善できるらしい。ToolBar1 の OnStartDock イベントで、以下を記述。
procedure TForm1.ToolBar1StartDock(Sender: TObject;
var DragObject: TDragDockObject);
begin
DragObject := TToolDockObject.Create(Sender as TToolBar);
end;
保存して、実行。ToolBarをフローティングさせると、ちらつく枠線でなく、ToolBar本体が表示されたまま、スムーズに移動する。最初からこうしておいてほしかった!!
今はToolBarしかないから、特に必要じゃないけど、他にもVCLコンポーネントがある場合は、コントロールバーにドラッグされたVCLコンポーネントがToolBarであった場合のみ、ドッキング可としなければならない。これを実現するには、ControlBarの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
http://afsoft.jp/program/del2010/p11_033.html
で、最後にToolBarの受け入れ先をFormに設定(いつまでもフローティングさせておくわけにもいかない)。Formを選択しておいて、オブジェクトインスペクタのDockSiteプロパティを「True」にするだけでOK!
ここまでの設定をテストするには、何でもいいのでFormに設置したScrollBoxより大きな画像を用意(例:デスクトップをそのままキャプチャーして保存するとか)して、Image1のPictureプロパティでこの画像を指定する。で、Image1のAutoSizeプロパティを「True」に設定。また、ScrollBoxのAutoScrollプロパティがTrueに設定してあることも確認。
参考:画像なんて準備できない・・・という場合
ScrollBoxのAutoScrollプロパティがTrueに設定してあれば、内部に配置したVCLコンポーネントのサイズが枠内に表示しきれないほど大きくなると、自動でスクロールバーが表示される(AutoScrollプロパティがFalseだとスクロールバーは現れない)。
やってみた!
ScrollBoxの幅と高さより、Imageの幅と高さを大きく設定すれば、ScrollBarが自動的に表示されるはず。
参考:画像なんて準備できない・・・ は、ここまで
ScrollBoxのプロパティについて、学んだことをちょっとまとめた!
【ScrollBoxのRangeプロパティ】
スクロールボックス内部に作成される(仮想的な)表示領域のサイズと考えればいいようだ。このRangeのサイズがScrollBoxのサイズより大きくなると、スクロールバーが自動的に現れる。
【ScrollBoxのMarginプロパティ】
スクロールボックスの右下端の「余白」領域のことで、内部のコンポーネントとスクロールボックスの端との距離が、この値より小さくなると、スクロールバーが自動的に現れる。
【ScrollBoxのTrackingプロパティ】
ついでにScrollBoxの縦・横のスクロールバーのTrackingプロパティを「True」にして、スクロールバーを移動させた時、表示されている画像も同時に動くように設定を変更。もし、これを行わない(デフォルト設定のFalseのままだ)と、スクロールバーを動かしている最中は画像は動かず、バーを動かし終えた瞬間に、バーの移動量だけ、画像の表示位置が飛ぶようにずれるスクロールになる。
同じことを、コードで設定する場合は・・・
//滑らかぁーにスクロール
Scrollbox1.VertScrollBar.Tracking := True;
Scrollbox1.HorzScrollBar.Tracking := True;
保存して、実行する。スクロールボックスより十分に大きい画像を準備したので、縦・横ともにスクロールバーが自動的に表示される。
スクロールバーが自動的に表示されない時は・・・?(間違いかもしれない私の経験)
私がどこかで、なにかを、間違えているのかも、知れないが(自分が絶対に正しいという自信はまるでないけれど)、オブジェクトインスペクタで予め、ScrollBoxのAutoScrollプロパティを「True」に設定してあるにもかかわらず、画像を表示する際に、プログラムコードの中でこれを明示的に指定しないと、「横の」スクロールバーが表示されなくなる現象が、これまでに少なくても2回あった(両方出ないならまだしも、この現象に遭遇した時、縦のスクロールバーは2回とも自動的に表示されていた)。
私がどこかで、なにかを、間違えていたのかも、知れないが、原因が皆目わからず、もちろん検討もつかず、途方に暮れ、悩みに悩んでようやく発見したスクロールバーが自動で表示されなかったトラブルの解決方法なので、いちおう、ここに書いておきます・・・。
//オブジェクトインスペクタのプロパティでTrueに指定してあっても
//再指定しないと横スクロールバーは表示されない!
ScrollBox1.AutoScroll:=True;
もしかしたら、RangeプロパティやMarginプロパティ関係の設定値のどこかに真の原因があったのかもしれないが・・・。あの時、ものすごく、困ったことは本当で、この方法で解決できたことも、本当だから。もしかしたら、同じことで悩んでいる人が・・・どこかに・・・
ToolBarのフローティングとドッキングを確認!
2.ToolBarの「閉じる」ボタンを無効化
ここまでの設定だと、プログラム起動時のToolBarは、下のように、画面のTopにあるControlBar(見えない!)にドッキングしている状態で表示される。
実は、自分的にはこれがちょっと気に入らない。なぜかというと、作った採点プログラムは横書き答案でも、縦書き答案でも、どちらも採点可能なプログラム。
で、横書き答案を採点する場合には、マウスのホイールをブンブン廻す「縦のスクロール」で解答欄画像を次々に表示できるから、「ボタンでスクロール(表示を移動)」させる機能はオプション設定で、ユーザーが明示的に選択した場合だけ使えれば十分。必要がなければToolBarのVisibleはFalseに設定し、「最初から表示しない」くらいでちょうどいい。
一方、縦書き答案を採点する場合は、ToolButtonのCaptionを「左へ移動」・「右へ移動」に設定したToolBarを最初から表示し、これを使って横にスクロールする機能をユーザーに提供したい。それだとControlBarにドッキングさせた状態(=画像と一緒にスクロールしてしまう)ではなく、最初からフローティングさせて表示し、「横スクロールはこれだよ!」ってユーザーに積極的にPRしたい。
また、ToolBarが「フローティング」すること自体を知らないユーザーも当然いるはずだし、何より、ControlBarからフローティングさせる手順を説明して理解してもらい、ユーザーにそれをやってもらうのは、ユーザーの手間を増やすだけで、ユーザーにとってのメリットは何一つない。必ず使ってもらうなら、最初からフローティングさせておきたい。
最初からFormにドッキングさせておくのも却下。それだと地味で存在を見落としてしまいそうだし、何より、Formの任意の位置に「ユーザーがドッキングさせる」ことが理想的な使用方法だから、フローティング状態からのドッキング位置はユーザー自身が任意の位置に決めてほしい(解答欄の画像は千差万別で高さや幅がことごとく変化するから当然余白の位置も変わり、また、コントロールを操作しやすいと感じる上下左右の位置は、人により異なって当然だろう)。
そこで、PanelにButtonを一つ追加して、ToolBarをフローティング状態で表示できるように、次のコードを書いてみた・・・
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が非表示になるのを断固阻止しなければならない。よぉーし、オレはやるぞー
・・・ということで、どのように閉じるボタンの機能を停止するか、足りないアタマで考えた。
ボタンそのものを消してしまうのは、調べてみると難しそうだ。最初からグレイアウトさせるのは出来そうだけれど、なんとなーく、面白くない気がした。ちっちゃくても、アレだけ存在感のある [×] 閉じるボタンだもの。ユーザーの皆様にも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の閉じるボタンの無効化」は、こちらで紹介されていた記事を参考にして、さらに、この練習プログラムで必要なコードと情報を追加しました。質問者様と回答者様に厚く御礼申し上げます。
https://www.petitmonte.com/bbs/answers?question_id=4452
実際の採点プログラムでは、最初に設定画面で「採点する答案の書式」・「画面を横にスクロールさせる移動ボタン利用の有無」・「1クリックで移動する解答欄数(=人数)」を指定してもらい、採点を実行。
計算方法は簡単。
個々の解答欄画像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サイトにあった言葉が最も適切な気が・・・。
https://lostash.jp/sales/business-skill/1088812#toc17より引用
まぁ ここではとりあえず「枠」で行きます!
さっそくサイズ20×23のBevelをToolBarに配置。
Shapeプロパティは、「枠」なんだから迷わず「bsFrame」を指定。
Styleプロパティは、盛り下がる(=凹む)「bsLowered」と、盛り上がる「bsRaised」のふたつにひとつだから、有無を言わさず盛り上がる「bsRaised」に決定。
気になる実行時画面は・・・
Bevelさん、どちらも「しっかり」自己主張してます。
実に、イイ感じ。
誰がどう見ても、この枠内をクリックすれば、
何か、起きそうです。
ここをクリックしてドラッグ・・・なんて言わなくても、
ヒントも表示しなくても、
きっと、大丈夫。
ただ、Bevelさん、あなた・・・
盛り下がってる ようにしか、見えないのですが。
私の気のせいですか・・・?
それは、置いといて
実は、ものすごーく、大切なコトがあるのです。
このままではBevelさんをクリックしてもナニも起きません!
オブジェクト インスペクタに表示されてませんが、
Bevelさんには、なんと! Enabledプロパティがあって、
しかもそれはデフォルト:True (=有効)で、
このままでは、Bevelさんの下にいるToolBarくんに、
クリックが伝わらないのです。
では、どぉすればいいかと言うと・・・
FormCreate時に、
//クリックイベントをToolBarへ届ける
Bevel1.Enabled:=False;
これでOK!
Bevelさんは自己主張しつつ、自らを無効化。
「枠」という役割に専念してくれるようになります!
4.まとめ
- Formの上にControlBarを置き、ControlBarの上にToolBarを配置すれば、ToolBarをフローティングさせることができる。ToolBarプロパティは、DragKindプロパティを「dkDock」に、DragModeプロパティを「dmAutomatic」に設定する。
- ControlBarやFormなど、ドッキングを受け入れる側は、DockSiteプロパティをTrueに設定するだけでOK!
- ToolBarの閉じるボタンは、プログラムコードを書いて無効化できる。
- Bevelは「枠」として利用できるが、Enabledプロパティがデフォルトで有効(True)になっているので、クリックを下のVCLに通知するためには、FormCreate時にEnabledをFalseに設定して、Bevel自身を無効化する必要がある。
5.お願いとお断り
このサイトの内容を利用される場合は、自己責任でお願いします。記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。