ビットマップ付きのメニューでショートカットに下線をつけるには?

解決


みけにゃん  2002-11-08 18:50:28  No: 1999

Delphi6だと標準で簡単にメニューの横にビットマップを付けたメニューが
作れるのですがそれを実行するとメニューのショートカットの下線が消えてしまい
切り取り(T)のように下線がなくてみっともないショートカットになってしまうの
ですがこれを回避する方法はないのでしょうか?


aiko  2002-11-08 19:17:40  No: 2000

切り取り(&T)とすると下線が入りますよ。


みけにゃん  2002-11-08 19:28:37  No: 2001

えっと・・・私の説明不足だったようです。
もちろん切り取り(&T)を入れたけれど実行してみると切り取り(T)に
なってしまいます。(ビットマップがないメニューの場合は下線付きで出ます)
開発環境でも同じ現象が起こっている(メニューを見ると切り取り(T)などになっています)ので
どうなのかなぁって思います。


にしの  2002-11-08 19:44:00  No: 2002

Windowsの設定で、Altを押さないと表示しない、になっていませんか?


にしの  2002-11-08 19:46:45  No: 2003

設定していなくてもそうなるみたいですね。
Delphiで作られた他のアプリケーションでもそうなってます。
気になるのであれば、OnDrawItemで自前で描画すればOKです。


みけにゃん  2002-11-08 20:39:16  No: 2004

>OnDrawItemで自前で描画すればOKです。

OnDrawItemプロシージャは分かったのですが詳しい内容(例)が
ヘルプになかったので他のページを探したのですが、そう情報が
なかったのですがどのように描画すれば良いのか教えていただけると
うれしいです。


にしの  2002-11-08 21:02:02  No: 2005

ヘルプを見てもわかりませんか?
どこがわからないのでしょうか。


みけにゃん  2002-11-08 21:29:55  No: 2006

例えばファイルのメニュー部分を再描画する場合はMainManu1の
OwnerDrawをTrueにしてFile1のOnDrawItemに記述すると言うところまでは
分かったのですが、描画する時に使う関数が分からないのです。
試しに各メニューのCaptionで試したのですが効果なしでした。

procedure TForm1.File1DrawItem(Sender: TObject; ACanvas: TCanvas;
  ARect: TRect; Selected: Boolean);
begin

end;


にしの  2002-11-08 22:01:30  No: 2007

まず、単純に、TMenuItem(「ファイル(F)」)を追加したTMainMenuをはり、そのプログラムを実行してみてください。
# OnDrawItemとは全く関係のないプログラムです

メニューに、「ファイル(F)」などと表示されますね。
これは、みけにゃんさんがメニューを表示させたんですか?
違いますよね。
自動で表示されています。
これを、独自の描画にしたい場合に、OnDrawItemを使います。
決して「手動で表示させる」ためのイベントではありません。

それとも、OSにやらせずにわざわざ再描画する必要があるんでしょうか。
VCLを使わずにAPIのみで作っているとか?

もしかして、上にあるように、何も処理しないOnDrawItemを定義して、「描画されない」と言っていますか?
何も処理しないのだから描画されないのは当たり前です。
OnDrawItemを使うなら、Captionの値も自前で描画する必要があります。


みけにゃん  2002-11-08 22:15:30  No: 2008

うまく伝えられなかったようなのでイメージをアップしました。
今の状態は以下の画像です。
http://www.geocities.co.jp/HeartLand-Suzuran/3604/menu1.png

これを次の画像のようにするにはどうすれば良いかという事です。
ちなみにこれはペイントでメニュー部分に下線を引いただけです。
http://www.geocities.co.jp/HeartLand-Suzuran/3604/menu2.png


aiko  2002-11-08 22:28:44  No: 2009

なぜ表示されないのでしょう?

Bitmapに直接読み込んでやっているのですか?
それともTImageListに読み込んでやっているのですか?

私のところで確認してみたのですがちゃんと表示されていますが…
にしのさんも表示されないってことは、私の表示方法が違う?

私の環境
delphi6 Pro
Win98 SE


にしの  2002-11-08 22:40:44  No: 2010

こちらこそ言葉が足らなかったようですね。

簡潔に申しましょう。
「Delphi+Win2000/XPの仕様です」
回避するには、
「TMenuItemクラスの、OnDrawItemなどを利用」
です。

Win2000/XP以外にもあるかもしれません。
少なくとも、Delphiで作られた既存のアプリケーション(ObjectBrowserなど)でも、同じ現象が起きているのでおそらく仕様でしょう。
大して大きなバグと言うほどでもなく、どうしても必要ならば、OnDrawItemで描画してやれば済むことです。
その場合は、Win2000以降の画面のプロパティにある、
「Altキーを押さないときはキーボードのナビゲーションインジケータを表示しない(&H)」
にチェックがあった場合には、_を表示しないようにしないといけませんね。
# DrawTextExがこの項目に対応しているかどうかは未確認です


みけにゃん  2002-11-08 22:41:32  No: 2011

>Bitmapに直接読み込んでやっているのですか?
>それともTImageListに読み込んでやっているのですか?

TImageListから読み込んでやっています。(BitmapはVS98に付属のものです)
環境はDelphi6 PersonalでWindows2000Proです。


みけにゃん  2002-11-08 23:16:12  No: 2012

>画面のプロパティのAltキーを押さないときは〜

確認しましたがチェックは入っていませんでした。
一度確認のために入れてみたのですがそうしたら影響のなかった
ソフトまでこの状態が発生しました。


みけにゃん  2002-11-09 01:15:01  No: 2013

やっぱり気になるのですがどのようなコードを書けば良いのでしょうか?
今は行指定などのコーディングを平行してやっています。
使用しているコンポーネントはTSonEditです。


たかみちえ  URL  2002-11-09 06:06:36  No: 2014

> やっぱり気になるのですがどのようなコードを書けば良いのでしょうか?
  どうしてもやりたいというのなら、
http://fukunm12.nucl.kyushu-u.ac.jp/~akasaka/memo/delphi/component.html#OwnerDrawMenu
このあたりを見ながら自分でやってみてください。

  ちなみに、これはたぶん2000/XPの仕様ではないですね、
Delphiのメニューコンポーネントが、自分で描画をしていることによる、
問題でしょう。
(証拠に、WindowsXPで下線が表示されなくなるのは、DelphiやBCBのソフトだけです)
  TGroupBoxなどで、本来XPなら角が丸くなるはずなのにならないというのと、同じ原因だと思います。

  たぶん、Delphi7なら修正されてるんじゃないかなとは思いますけど、
そんなに気にしないでもいいんじゃないでしょうか?

  ところで・・・。
  DelphianWorldだったか、それともその他の個人ホームページだったか、
どこかで"メニューの下線が出ないのに対処"と紹介文にかかれたメニューコンポーネントを、
どこかで見かけた記憶があるんですけど…。
日本語で見た記憶があるので、海外サイトではないと思います。


OverQ  2002-11-09 10:47:57  No: 2015

Win2k Menu 
http://homepage1.nifty.com/wizman/component/win2kmenu/index.html
これでしょう。
ずいぶん前から知られているバグですね。

初心者/中級者にだってOwnerDrawは難しいので
「OwnerDrawすれば済む」という答えでは、
やったことない人に簡単に出来るのかと勘違いされると思いますよ。


にしの  2002-11-09 20:52:37  No: 2016

OwnerDrawは難しくないと思います。
OwnerDrawが難しいということは、TPaintBoxの使い方も難しいということですか?
TCanvasの使い方さえわかれば、OwnerDrawもTPaintBoxも同じように実装できます。

もちろん、APIからOwnerDrawを実装する場合は難しいです。
デバイスコンテキストの管理を自前でしなければなりませんから。

TCanvasは、デバイスコンテキストの管理を包括してくれるので、簡単です。


たかみちえ  URL  2002-11-09 21:37:03  No: 2017

質問とは関係ないですけど。

>OverQさん
> 初心者/中級者にだってOwnerDrawは難しいので
  というのは、オーナードローが難しいということではなく、
オーナードローという語感が難しいのではといいたいのでは?

>にしのさん
>OwnerDrawが難しいということは、TPaintBoxの使い方も難しいということですか?
  PaintBoxと同じようにやればいいという発想は、
簡単にできるものじゃないと思いますよ。
  そうでなくても、キャンバスだけ切り離して、それを操作することでメニュー項目を書くという操作は、ほかの言語ではそう見かけませんし。

  ただ、にしのさんの言うとおりに、
PaintBoxとか、Formとかに書くのと同じような要領で描けばいいです。
  PaintBoxに描画するというのは、ほとんど実際に画用紙に絵を描くのと同じ要領なので、
すぐわかると思います。


OverQ  2002-11-11 02:53:09  No: 2018

MenuItemのCaptionに下線を描かせたいという希望を
オーナードローで実現するのは難しいですよ。

MenuItemの中にImageListから読み込んだ画像、
Captionのテキスト、ショートカットの文字を描かなければ
いけないですよね。しかも位置も考えながら。

TPaintBoxを使って
画面に好きな画像を書くのとはわけが違います。

DefaultDrawとの連携を考えると、
多くのOwnerDraw使いたい場面において
望んだ機能を実現するために
OwnerDrawが簡単と思えたことがありません。


にしの  2002-11-11 03:49:27  No: 2019

> MenuItemの中にImageListから読み込んだ画像、
> Captionのテキスト、ショートカットの文字を描かなければ
> いけないですよね。しかも位置も考えながら。
位置は、キャプションは左寄せ、ショートカットは右寄せするだけでOKでしょう。
画像の大きさは16x16のはずですから、縦方向ではそれが中心になるようにしてやれば問題ないです。
標準的でないメニューを書く場合は、OnMeasureItemでサイズを確定させて、その最大にあわせて書けばいいだけです。こっちは少し面倒ですけどね。

今回の場合は、ざっと書くとこんな感じです。
難しいですか?
どのあたりが難しいかおっしゃっていただければ、説明いたします。
# 対象は、PopDownするメニューです。メニューバーに直接表示されるTMenuItemだと、画像分ずれてしまうし、マウスが重なったときの枠が出なくなります

procedure TForm1.MenuItemDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect;
  Selected: Boolean);
const
  PADDING_WIDTH = 2;
  PADDING_RIGHT = 12;
var
  AMenuItem: TMenuItem;
  ACaption, AShortcutText: String;
  LeftImage_width: integer;
begin
  // 背景を埋める
  ACanvas.FillRect(ARect);

  // 画像は後から書くので、画像と隙間分足しておく
  LeftImage_width := ImageList1.Width + PADDING_WIDTH * 2;
  ARect.Right := ARect.Right - PADDING_RIGHT;
  ARect.Left := ARect.Left + LeftImage_width;

  //対象メニューと、キャプション・ショートカット名
  AMenuItem := Sender As TMenuItem;
  ACaption := AMenuItem.Caption;
  AShortcutText := ShortCutToText(AMenuItem.ShortCut);

  // 左寄せでキャプションを描画
  DrawTextEx(
    ACanvas.Handle,
    PChar(ACaption),
    Length(ACaption),
    ARect,
    DT_LEFT  Or DT_VCENTER Or DT_SINGLELINE,
    nil);
  // 右寄せでショートカット名を描画
  DrawTextEx(
    ACanvas.Handle,
    PChar(AShortcutText),
    Length(AShortcutText),
    ARect,
    DT_RIGHT  Or DT_VCENTER Or DT_SINGLELINE,
    nil);
  // 画像が設定されていれば、画像を描画
  if AMenuItem.ImageIndex >= 0 then ImageList1.Draw(ACanvas, ARect.Left - LeftImage_width, ARect.Top + ((ARect.Bottom - ARect.Top) - ImageList1.Height) div 2, AMenuItem.ImageIndex, AMenuItem.Enabled);
end;


にしの  2002-11-11 08:06:24  No: 2020

これじゃまずいですね。
ショートカット名の長さが一定でないので…。
OnMeasureItemを使わないとだめかも。もう少し考えます。


にしの  2002-11-11 08:42:34  No: 2021

オーナードローの横着版です。
上のだと、ショートカットの左位置がばらばらになるので、ショートカットの左位置があうようにしました。
OnMeasureItemで位置を取得し、OnDrawItemでその位置になるようにDrawTextExで描画します。

横着版の理由は、OnMeasureItemで、タブ位置を取得するのに、Length(Caption)+4  としているだけだからです。
本来なら、DrawTextExでDT_CALCRECTを使用してWidthとHeightを計算してやる必要があります。
でも、たぶんこれだけで大丈夫でしょう。+4としているのは、キャプションとショートカットの間を少しあけるための小細工です。

デフォルトの描画とやり方が違うため、ショートカット名がずれます。
画像部分の描画も違うので、このあたりを同じにするなら
 // 画像が設定されていれば、画像を描画
のあたりに少し手を加えればできます。

# TForm1にSavedTabStop:integerを用意する必要があります。

procedure TForm1.MenuItemDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect;
  Selected: Boolean);
const
  PADDING_WIDTH = 2;
  PADDING_RIGHT = 2;
var
  AMenuItem: TMenuItem;
  ACaption, AShortcutText: String;
  LeftImage_width: integer;

  DTP: TDrawTextParams;
begin
  // 背景を埋める
  ACanvas.FillRect(ARect);

  // 画像は後から書くので、画像と隙間分足しておく
  LeftImage_width := ImageList1.Width + PADDING_WIDTH * 2;
  ARect.Right := ARect.Right - PADDING_RIGHT;
  ARect.Left := ARect.Left + LeftImage_width;

  //対象メニューと、キャプション・ショートカット名
  AMenuItem := Sender As TMenuItem;
  ACaption := AMenuItem.Caption;
  AShortcutText := ShortCutToText(AMenuItem.ShortCut);
  ACaption := ACaption + #9 + AShortcutText;

  with DTP do
  begin
    cbSize := SizeOf(TDrawTextParams);
    iTabLength := SavedTabStop;
    iLeftMargin := 0;
    iRightMargin := 0;
    uiLengthDrawn := 0;
  end;

  // 左寄せでキャプションを描画
  DrawTextEx(
    ACanvas.Handle,
    PChar(ACaption),
    Length(ACaption),
    ARect,
    DT_LEFT  Or DT_VCENTER Or DT_SINGLELINE Or DT_EXPANDTABS Or DT_TABSTOP,
    addr(DTP)
  );

  // 画像が設定されていれば、画像を描画
  if AMenuItem.ImageIndex >= 0 then ImageList1.Draw(ACanvas, ARect.Left - LeftImage_width, ARect.Top + ((ARect.Bottom - ARect.Top) - ImageList1.Height) div 2, AMenuItem.ImageIndex, AMenuItem.Enabled);
end;

procedure TForm1.MenuItemMeasureItem(Sender: TObject; ACanvas: TCanvas;
  var Width, Height: Integer);
var
  tmpMenuShortcutLength: integer;

  AMenuItem: TMenuItem;
begin
  AMenuItem := Sender As TMenuItem;
  tmpMenuShortcutLength := ACanvas.TextWidth(ShortCutToText(AMenuItem.ShortCut));

  if tmpMenuShortcutLength > 0 then
  begin
    if SavedTabStop < Length(AMenuItem.Caption)+4 then SavedTabStop := Length(AMenuItem.Caption)+4;
  end;

end;


みけにゃん  2002-11-11 18:14:34  No: 2022

>http://homepage1.nifty.com/wizman/component/win2kmenu/index.html

このコンポーネントで解決しました。
でもこのコンポーネントでもバグがあるようです。
→Altを押さないときは〜のチェックをはずした時でも下線が出たまま
にしのさんのコードだと1つずつ書かなくてはいけないので
ちょっと面倒です。(>_<)

今回のは早く解決して本当に良かったです。
メールソフトもこのように早く進めばいいのですが・・・(T_T)


にしの  2002-11-11 20:08:19  No: 2023

> にしのさんのコードだと1つずつ書かなくてはいけないので
> ちょっと面倒です。(>_<)
1つずつという意味がわかりませんが、もしかしてVBのように1コンポーネント1イベントで書かないと行けないと思っていますか?

このイベント1つで、複数のメニューイベントに割り当てれば動きますよ。
# 他のイベントも然り
VBのように1つずつやっていたら、確かに面倒ですね。


ちゃーみー&せりーぬ  2002-11-14 14:41:46  No: 2024

チャーミ「すごいですね〜、にしのさんて。スラスラっと難しいコード書いちゃうんですから」
セリーヌ「今ごろ気づいたんかい。にしのさんDELPHIの達人やで」
チャーミ「あら?セリーヌさんて関西人だったんですか?」
セリーヌ「なにおっしゃるの?パリ〜生まれですのよ♪ワタクシ♪」
チャーミ「でもズーーーット道頓堀育ちだとコミミにはさみましたけど?」
セリーヌ「そや(ー.ー)文句あっか?」
チャーミ「いっいえ(^^;;別にいいんですけど…」
セリーヌ「チャーミ、何か にしのさんに頼みたいことあるってゆうてたやろ」
チャーミ「あのですね。ODMと同じカッコイイDelphi6用のメニュー表示が欲しいなっと..(^^ゞ」
セリーヌ「なんやそんなもんアタシ作ってあげる。にしのさんの OwnerDrawさんぷるにチョット書き足せばエエんやから。odm.pasの中のMakeGradationとDrawBarTextも必要やけんな」
チャーミ「えー?...でも〜...」
セリーヌ「あーっ信用せんなら...もう知らん」


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








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