フォームのスクロールバーが移動した事を知るには?

解決


nabe  2013-07-22 00:36:42  No: 44892

初めまして。
ここ2週間ほど仕事で悩んでおり、ふと自宅で思いついた事をやってみたびですが、ちょっと解決しなさそうなのでお力をお貸し下さい。

OS:Windows7ProSP1(32ビット版)
言語:Delphi7Pro

フォームの HorzScrollBar プロパティの Visible を True に設定し、フォーム中にフォームより横幅が大きいパネルコンポーネントを貼ってます。
パネルコンポーネントには同じ横幅を持つ StringGrid を貼り、垂直方向のスクロールバーを表示しています。
そこで、次のように、フォームの再描画イベントでグリッドの垂直方向のスクロールバーを表示しています。

procedure TForm1.FormPaint(Sender: TObject);
begin
  StringGrid1.Width := Form1.HorzScrollBar.Position + Form1.Width -18;
end;

ところが、フォームをリサイズしてパネルごと Height プロパティに値を代入すると、フォームの再描画イベントが発生しなくなります。具体的には、

procedure TForm1.FormResize(Sender: TObject);
begin
  Panel1.Height := Form1.Height;
  StringGrid1.Height := Panel1.Height;
end;

上記のコードを追加すると、再描画イベントが発生しなくなり、意図した動きが出来ません。
スクロールバーコンポーネントを使え、と言われそうですが、プログラムを複雑にしたくない(保守を容易にしたい)ので、なるべく簡潔で簡単な方法をとろうとしています。
OS と Delphi のバージョン(ちなみに Delphi7 はアップデータをかけていません。かけようと思うのですが、ボーランド社がなくなったので、出来ません)の問題なんでしょうか。

で、ちょっと考え方を変えて、フォームの水平スクロールバーが動いたタイミングで再描画をかければ問題ないと考え、色々調べましたが、自宅では限界です。
かと言って、職場でこの問題に割ける時間もないので、何かご存じの方、ヒントでもいいので教えていただけないでしょうか。


nabe  2013-07-22 04:34:39  No: 44893

ちょっと強引ですが・・・

type
  TForm1 = class(TForm)
・・・略・・・
  private
    { Private 宣言 }
    OriginalProc:TWndMethod;
    procedure SubclassProc(var Msg:TMessage);
    procedure WMHScroll(var Msg:TMessage);
  public
    { Public 宣言 }
  end;

・・・略・・・

procedure TForm1.FormCreate(Sender: TObject);
begin
  OriginalProc := Form1.WindowProc;
  Form1.WindowProc := SubclassProc;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  Panel1.Height := Form1.Height;
  StringGrid1.Height := Panel1.Height;
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  StringGrid1.Width := Form1.HorzScrollBar.Position + Form1.Width -18;
end;

procedure TForm1.WMHScroll(var Msg: TMessage);
begin
  FormPaint(nil);
end;

procedure TForm1.SubclassProc(var Msg: TMessage);
begin
  OriginalProc(Msg);
  case Msg.Msg of
    WM_HSCROLL: WMHScroll(TMessage(Msg));
  end;
end;

とコーディングしたところ、解決しました。
フォームの水平スクロールバーが動くと WM_HSCROLL メッセージが発生するので、そのタイミングでフォームの再描画イベントを強制的にコールしてます。
ハッキリ言って余り良い解決法ではありません。これよりもより良い解決法があれば、ご教授下さい。お願いします。


Harry  2013-07-22 16:59:18  No: 44894

とりあえず。

>ちなみに Delphi7 はアップデータをかけていません。かけようと思うのですが、ボーランド社がなくなったので、出来ません
正規ユーザーならEmbarcadero社が面倒見てくれてるので、問題なく出来ると思いますが…。
「Delphi7 アップデータ」で検索しましたか?

>上記のコードを追加すると、再描画イベントが発生しなくなり、意図した動きが出来ません。
FormResizeに Self.Invalidate; とか Self.Update; とか Self.Repaint; を追加じゃダメですか。。。。

私にはnabeさんの「意図した動き」がよく分からないんですが。
本質的にForm1.HorzScrollBarはユーザーに見せたいものなのか、それとも計算のためだけなのか??


nabe  2013-07-22 21:57:24  No: 44895

Harryさん、レスありがとうございます。
水平方向のスクロールバー(Form1.HorzScrollBar)は、ユーザに見せますし、操作もさせます。
垂直方向のスクロールバーは、グリッドの垂直スクロールバーで済ませようと思っています。
なるほど、フォームのリサイズイベントハンドラで Self.Repaint すると、ちゃんと再描画してくれますね。ありがとうございます。

もっと踏み込むと、フォームのスクロールバーを操作すると、何らかのイベントやメッセージが取れるのではないか?と考えます。
そこで昨日自宅で、

>フォームの水平スクロールバーが動くと WM_HSCROLL メッセージが発生するので、そのタイミングでフォームの再描画イベントを強制的にコールしてます。

というコーディングをしましたが、パネルにエディットコンポーネントが多数載っていて、TABキーによってフォームがスクロールしてしまう分には  WM_HSCROLL メッセージが発生しないようです。
この場合、どのように対処したら良いのでしょうか。
Mr.XRay さんのサイトも参考にはしたいのですが・・・あくまでもGUIベースで簡単に、を目指しているので、引き続きお知恵をお貸し下さい。


Harry  2013-07-23 07:50:31  No: 44896

あの、、、アップデータはどうなりましたでしょうか…。

私はnabeさんの目指してるUIの形をおそらく10%程度しか想像できていないのですが、、、
フォームの標準スクロールバー位置の変更は↓これで検知して利用できませんか?

  private
    procedure WMWindowPosChanging(var Message: TWMWindowPosMsg); message WM_WINDOWPOSCHANGING;
をしておいて、メッセージメソッド
procedure TForm1.WMWindowPosChanging(var Message: TWMWindowPosMsg);
begin
  if (Message.WindowPos.flags and SWP_NOSIZE)=0 then 
    Self.Caption:=Format('Horz=%d, Vert=%d',
                         [Self.HorzScrollBar.Position, Self.VertScrollBar.Position]);
  inherited;
end;

しかし、とりあえずこれでうまくいったとしても、また何か次の問題が発生するんじゃあ…泥沼化の懸念。

>あくまでもGUIベースで簡単に、を目指しているので
簡単に、と言うなら何か根本がおかしい気がします。私はUIの経験が乏しいのでハッキリとは指摘できませんが。
普通は適材適所のコンポを適切に配置したり、位置・サイズ関係のプロパティやイベントで対処するのではないかと。
(フォームのスクロールバーは必須ですか? StringGridのスクロールではダメ? EditはScrollBoxに納めるとか。)


nabe  2013-08-02 19:18:47  No: 44897

Harryさん、レスありがとうございます。
返信が遅くなりましたが、意図するような動作になりました。

元々のプログラムがフォームより大きいStringGridを貼っていて、そのグリッドの固定列のすぐ上にEditコンポーネントを列数分貼っているという、とんでもないモノだったため、プログラムはグジャグジャだし、メンテナンスに耐えるものではありませんでした(その上、固定列をマウスで可変させると、その上のEditコンポーネントの幅が連動して変わる、という仕様です)。
水平・垂直方向にスクロールバーコンポーネントを貼り、コードで制御しているので、他人が手をつけられたモノではありません。

そこで、垂直方向のスクロールバーをStringGridのスクロールバーにし、水平方向のスクロールバーをフォームに変更して、メンテナンスしやすいプログラムに作り替える必要がありました。
(グリッドが横スクロールすると、上にエディットも同様にスクロールしなければならないため。)
流石にこのUIは如何なものか?と言いたい所ですが、このプログラムには熱狂的ユーザがいるそうで、UIは変更出来なかったんです。

最後にアップデータの件ですが、間もなくXE2に移行する予定なので、放置状態です。


Harry  2013-08-03 06:27:04  No: 44898

>このプログラムには熱狂的ユーザがいるそうで、UIは変更出来なかったんです。
(^_^;;;;;;;;;;;;;;;;

WM_WINDOWPOSCHANGINGについて。
これは確かにフォームの標準スクロールバーの位置変更を捕捉できますが、同じ内容が複数回送られて来たり、
フォーム上のコントロール(ScrollBox等)の上にある標準スクロールバーの分も来たりします。
最もイマイチなのが、水平と垂直が同時に変化したとき、その変化途中の分まで送ってくる点です。
  水平70・垂直0 → 水平0・垂直0 → 水平0・垂直200 みたいな。(水平0・垂直0 は必要ないのに〜)

まあ、再描画のタイミング取りの用途なら問題にならないようですが。
手堅くするなら、単純に Application.OnIdle(または ApplicationEvents.OnIdle)の方が良いかも知れません。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加