TMemoの垂直スクロールバー自動表示

解決


白いおもち  2006-01-02 05:58:48  No: 19448

TMemoの(行数)*(一行の高さ) が ClientHeightを超えるか、
Splitterをいどうして、ClientHeightが変わったら、
自動で垂直方向のスクロールバーを必要に応じて
自動で表示するというのを作っています。
一応、下にあるコードで実現できているのですが、
IMEがONの状態で、スクロールバー表示する境目の所で、
始めの一文字しか入力されません。
どのようにすれば、正しく動作するようになるのでしょうか?

//-------------------------------------
// メモの行数に対して自動でスクロールバー表示
procedure TForm1.Memo1Change(Sender: TObject);
begin
  MemoScrollBarsAutoShow;
end;

procedure TForm1.Splitter3Moved(Sender: TObject);
begin
  MemoScrollBarsAutoShow;
end;

procedure TForm1.MemoScrollBarsAutoShow;
var
  xPos: Integer;
begin
    // スクロールバーの設定が変わり、再描画されると
    // キャレットの位置が始めの位置に戻ってしまうので一時保存して戻す
    // ★IMEがONで直接日本語を入力するとキャレットの位置が更新、取得できないため?、始めの1文字しか入力されない

    if ((Canvas.TextHeight('H') * (Memo1.Lines.Count +1)) > Memo1.ClientHeight)
      and (Memo1.ScrollBars = ssNone) then
    begin
      xPos := Memo1.SelStart;
      Memo1.ScrollBars := ssVertical;
      Memo1.SelStart := xPos;
    end
    else
    if ((Canvas.TextHeight('H') * (Memo1.Lines.Count +1)) <= Memo1.ClientHeight)
      and (Memo1.ScrollBars = ssVertical) then
    begin
      xPos := Memo1.SelStart;
      Memo1.ScrollBars := ssNone;
      Memo1.SelStart := xPos;
    end;
end;

//-------------------------------------


ママん  2006-01-08 03:01:33  No: 19449

すみませんが再現できません。
WinXp+Delphi7 xp style非適用
>> IMEがONの状態で、スクロールバー表示する境目の所で、
>> 始めの一文字しか入力されません。
IMEがON Memoのスクロールバーは無し(Height > 行数*行高)
Memoにカーソル状態でsplitterを動かす
Height < 行数*行高  になりスクロールバー出現
文字入力

でよいのですか?これですと再現できません。


白いおもち  2006-01-09 07:26:11  No: 19450

splitterは動かしません

1. IMEがON、Memoのスクロールバーは無し(Height > 行数*行高)
2. splitterは動かさないで、適当に改行しながら文字を入力していく
3. やがてMemoのClient領域の一番下の行にきて、次に文字を入力すると(Height < 行数*行高)となり、スクロールバーが表示される行頭にくる。ここで、文字を入力すると変換後には最初の一文字しか表示されない

という状態です。

WinXP(xp style非適用) Delphi6Per


えーと  2006-01-09 10:54:26  No: 19451

ずっと以前からあちこちで、この問題を追求されているようですが、
初めからスクロールバーを表示しておくのはなにか重大な不都合が
あるのでしょうか。


HOta  2006-01-09 19:14:55  No: 19452

Memo1のChangイベントで動作させているようですが、これだと最初の1文字が入った時点で動作してしまい一文字しか表示されなくなります。
changイベントで行数が変わるのを調べて動作させればうまくいくようです。
ex.
  private
    { Private 宣言 }
    lCount  : Longint;

procedure TForm1.Memo1Change(Sender: TObject);
begin
  if lCount <> Memo1.Lines.Count then
  begin
    lCount := Memo1.Lines.Count;
    MemoScrollBarsAutoShow;
  end;


白いおもち  2006-01-09 23:52:51  No: 19453

>>えーと さん
>ずっと以前からあちこちで、
これが初めての投稿です。
検索しても見つからなかったので質問したのですが、
同じような質問が過去にあったのでしょうか?
調査不足ですみません。
もし、すでに解決したことならばリンク先を教えてくれないでしょうか。

>初めからスクロールバーを表示しておくのはなにか重大な不都合が
>あるのでしょうか。
重大な不都合 ではありませんが、
デザイン的、少しでも画面を有効活用したい
などの理由です。
だから、ちゃんと動く方法がないのならば、それはそれで別に困らないのですが、
だれか、知っていれば教えてくれないかなと思い質問してみたのです。

>>HOta さん
示してくれたコードを試してみましたが、
同じく、文字変換後に ScrollBars が表示される場合
始めの1文字しか入力されませんでした。

行数のプロパティーが変わるのが、Enterキーを押したときではなく、
あたらしいラインに文字を入力したあとに生じるためだと考えられます。


HOta  2006-01-10 01:23:59  No: 19454

すみません。
Changイベントだとおなじですね。

procedure TForm1.Memo1KeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if key in [VK_RETURN,VK_BACK,VK_DELETE] then
  begin
    lCount := Memo1.Lines.Count;
    MemoScrollBarsAutoShow;
  end;
end;
でどうでしょう。


白いおもち  2006-01-10 01:54:18  No: 19455

HOta さんの示したコードで、
スクロールバーが表示されるときも正しく動作しました。
みなさん、ありがとうございました。


ママん  2006-01-10 03:56:40  No: 19456

うはっ。出遅れた…
既に不要かとは思いますが、コンポーネント作成しました。
この方法ですとグレースクロールバーがほぼ出ません。
WMCharで2バイトチェックしてないのはご愛嬌で。

unit MemoEx;

interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  StdCtrls, imm;

type
  TMemoEx = class(TCustomMemo)
  private
    { Private 宣言 }
    siIMEWCharCount:integer;
    siLineHeight   :integer;
    siStopFlag     :boolean;
    procedure siCheckLineHeight;
    procedure siScrollBarsAutoShow(CharCode:Word);
    procedure CMFontChanged(var Msg: TMessage);     message CM_FONTCHANGED;
    procedure WMChar(var Msg: TWMChar);             message WM_CHAR;
    procedure WMImeComposition(var Msg : TMessage); message WM_IME_COMPOSITION;
    procedure WMSize      (var Msg: TWMSize);       message WM_SIZE;
  protected
    procedure KeyUp   (var Key:Word; Shift:TShiftState); override;
  public
    { Public 宣言 }
    constructor Create(AOwner: TComponent); override;
  end;

implementation

{ TMemoEx }

procedure TMemoEx.CMFontChanged(var Msg: TMessage);
begin
  inherited;
  siCheckLineHeight;
end;

constructor TMemoEx.Create(AOwner: TComponent);
begin
  inherited;
  siIMEWCharCount :=0;
  siLineHeight    :=0;
  siCheckLineHeight;
end;

procedure TMemoEx.KeyUp(var Key: Word; Shift: TShiftState);
begin
  inherited;
  siStopFlag:=True;
  case key of
    //本当は他の方法で実装したいけどVK_DELETEがここでしか検出できなかった。
    VK_DELETE:siScrollBarsAutoShow(8);
  end;
  siStopFlag:=False;

end;

procedure TMemoEx.siCheckLineHeight;
var bufBMP:TBitmap;
begin
  //手抜き
  bufBmp:=TBitmap.Create;
  bufBmp.Canvas.Font.Assign(Font);
  siLineHeight:=bufBmp.Canvas.TextHeight('H');
  bufBmp.Free;
end;

procedure TMemoEx.siScrollBarsAutoShow(CharCode:Word);
var
  xPos,i   : Integer;
  LCount : Integer;
begin
  case CharCode of
    13:LCount:=Lines.Count+1;
    else begin
      if (Lines.Text<>'') and (Lines.Text[Length(Lines.Text)-1]=#13) then
        LCount:=Lines.Count+1
        else
        LCount:=Lines.Count;
    end;
  end;
    //+1は微調整 原因不明 orz
    if ((siLineHeight * LCount +1) >= ClientHeight) and
       (ScrollBars = ssNone) then
    begin
      xPos       := SelStart;
      ScrollBars := ssVertical;
      SelStart   := xPos;
      for i:=0 to Lines.Count-1 do
      Perform(EM_SCROLL, SB_VERT, Lines.Count);
      //SendMessage(Handle, EM_SCROLL, SB_LINEDOWN, Lines.Count);
    end
    else
    //+1は微調整 原因不明 orz
    if ((siLineHeight * LCount +1) < ClientHeight)
      and (ScrollBars = ssVertical) then
    begin
      xPos := SelStart;
      ScrollBars := ssNone;
      SelStart   := xPos;
    end;

end;

procedure TMemoEx.WMChar(var Msg: TWMChar);
begin
  inherited;

  if siIMEWCharCount>0 then
  begin
    dec(siIMEWCharCount);
    exit;
  end else begin
    siStopFlag:=True;
    siScrollBarsAutoShow(Msg.CharCode);
    siStopFlag:=False;
  end;
end;

procedure TMemoEx.WMImeComposition(var Msg: TMessage);
var
  IMC               : HIMC;
begin
 inherited;

 if Msg.lParam and GCS_RESULTSTR <> 0 then
 begin
   IMC := ImmGetContext(Handle);
   if IMC <> 0 then
     siIMEWCharCount:= ImmGetCompositionStringW(IMC, GCS_RESULTSTR, nil, 0) div
                         SizeOf(WideChar) -1;
   ImmReleaseContext(Handle, IMC);
 end;

end;

procedure TMemoEx.WMSize(var Msg: TWMSize);
begin
  inherited;
  if siStopFlag=True then exit;
  siStopFlag:=True;
  siScrollBarsAutoShow(0);
  siStopFlag:=False;
end;

end.


白いおもち  2006-01-10 07:16:44  No: 19457

>>ママん さん
コンポーネントまで作成してくれて
ありがとうございます。

じつは、Memo1KeyUpで行の変化を検出する方法だけでは
コピペのとき検出できなかったり、
グレースクロールバーが表示されたり、
キャレットが隠れたり、などいろいろ不具合があったのですが、
unit MemoEx を試したところ、
私の想定以上の動作をしていて感心しました。

少しの不具合に対処するために、コードの多くの割合を割く
ということ(2:8の法則?)を実感しました。


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








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