Imageで描画したものが常にTop

解決


のい  2012-05-24 20:31:22  No: 42301

https://www.petitmonte.com/bbs/answers?question_id=7657
に続きたびたびすいません。
またつまづいてしまいました。

Panelの上にImageがあり、Panel自体に画像が貼り付けられたりするとImageで描画したものがPanelの画像の下になってしまいます。
Imageで描画したものは常にTopにあってほしいのですが、BringToFrontでは意味がなく…
何か方法ありますか?


Nov  2012-05-24 20:34:40  No: 42302

>Panelの上にImageがあり
ImageはPanelの子になってますか?


のい  2012-05-24 20:39:41  No: 42303

ImageはPanelの子です。
Panelの子にしないと、Image自体がPanelの上にないのでイベントがひろえないのです。
ImageがPanelの子でなくてもMouseDown等のイベントひろえますか?


Nov  2012-05-24 20:54:10  No: 42304

いえ、Panelの子にすれば解決、と思っただけですが、だめですか...
もしかして、
>Panel自体に画像が貼り付けられたりすると
っていうのは、PanelのCanvasに描画していると思い込んでましたが、
実際はどうしているのでしょう。


Nov  2012-05-24 20:56:25  No: 42305

失礼しました。PanelにCanvasなんてないですね。


Nov  2012-05-24 21:05:28  No: 42306

安易な手段としては、SetWindowPosでしょうか。
例えば、
SetWindowPos(Image1.Handle, HWND_TOP, 0, 0, 0, 0,
  SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW);
もしかしたら、オプションフラグは変更が必要かもしれません。


のい  2012-05-24 23:14:51  No: 42307

Novさんどうもです。

SetWindowPos(Image1.Handle, HWND_TOP, 0, 0, 0, 0,
  SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW);

Image1はHandleないですよね、
Image1.Canvas.Handleでやってみましたが、思い通りにはいきませんでした。
Panelに何も画像がない状態なら問題ありませんが。


のい  2012-05-24 23:50:51  No: 42308

再投稿すいません。

うーん。
ImageがPanelの子だからだめそうです。
最初にも書きましたが、ImageだとPanelの子にならないとPanelの後ろにかくれてしまい、イベントをひろえません。

Imageでなくてもいいので、
Panelの子にならずにPanel上で描画できる方法あれば
と思います。


Nov  2012-05-25 00:08:56  No: 42309

>Image1はHandleないですよね、
ですね、済みませんでした。
>Image1.Canvas.Handleでやってみましたが
このハンドルはデバイスコンテキストですので、ウィンドウ操作には使えません。
これ以上おバカなコメントをしないように、自分で試してから出直しますので、ご容赦を。


Nov  2012-05-25 00:31:25  No: 42310

試そうと思ってんですが、いきなりつまづきました。
>Panel自体に画像が貼り付けられたりすると
ってどういうことですか。この画像はTImageではないんですよね?


Nov  2012-05-25 01:49:14  No: 42311

もし、PanelにCanvasを割り当てているだけなら、
Panelの画像の描画の後に、Image.ReFreshをいれれば
Imageが再描画されます。


のい  2012-05-25 02:10:04  No: 42312

Novさん、説明不足ですいません。

Panelのハンドルを取得して、別アプリでビットマップ画像を常時貼り付けています。

そのため、一度Panel上にImageのイベントで描画してもすぐ画像にかくれてしまうわけです。

すいません、大事なとこでした。

色々試していますが、これ厳しそうですね


Nov  2012-05-25 02:26:07  No: 42313

>Panelのハンドルを取得して、別アプリでビットマップ画像を常時貼り付けています。
が、デバイスコンテキストのハンドルを指しているなら、
(ウィンドウハンドルを指している場合はだめですが)
メインフォームの定義の前に
  TPanel = class(Vcl.ExtCtrls.TPanel)  // XE2の場合
//  TPanel = class(TPanel)  // XE2以外の場合
  protected
    procedure WndProc(var AMsg: TMessage); override;
  end;
を入れて、
procedure TPanel.WndProc(var AMsg: TMessage);
begin
  inherited;
  if AMsg.Msg=WM_PAINT then Form1.Image1.Refresh;
end;
でどうでしょうか。


のい  2012-05-25 19:05:10  No: 42314

Novさん
ありがとうございます。

試しましたがだめでした。
この場合
> TPanel = class(TPanel)
ではなく
TMyPanel = class(TPanel)  
としてTPanelを動的に生成する方法ですかね?
と認識してためしましたが…

違うアプローチで考えた方がいいのでしょうかね。


Nov  2012-05-25 20:06:13  No: 42315

少し説明が足りなかったかもしれません。

まず、

>Panelのハンドル

これは、デバイスコンテキストのハンドルと考えてよいですか?

>としてTPanelを動的に生成する方法ですかね?

単に、現在のフォームの既存のTPanelのメッセージ処理を追加する
ためのものなので、クラス名は変更しないでお試しください。

具体的には、TPanel描画の最後に、Imageを再描画させようとしています。

>違うアプローチで考えた方がいいのでしょうかね。
この方法にこだわる必要はないです。
(状況がつかめていないので、うまくいく保証もできないので)

念のため、テストコードを載せておきます。
Panel1をクリックすると、画像をファイルから読み込んで、Panel1に表示します。Image1には、あらかじめ画像をプリロードしておきます。
bmpにロードする画像ファイル名は任意のものに差し替えてください。

type
  TPanel = class(Vcl.ExtCtrls.TPanel)
  protected
    procedure WndProc(var AMsg: TMessage); override;
  end;
  TForm1 = class(TForm)
    Panel1: TPanel;
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Panel1Click(Sender: TObject);
  private
    { Private 宣言 }
    dc: HDC;
    cv: TCanvas;
    bmp: TBitmap;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TPanel.WndProc(var AMsg: TMessage);
begin
  inherited;
  if AMsg.Msg=WM_PAINT then begin
    Form1.cv.StretchDraw(Rect(0, 0, Width, Height), Form1.bmp);
    Form1.Image1.Refresh;
  end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  cv.Free;
  ReleaseDC(Panel1.Handle, dc);
  bmp.Free;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  bmp := TBitmap.Create;
  dc := GetDC(Panel1.Handle);
  cv := TCanvas.Create;
  cv.Handle := dc;
end;

procedure TForm1.Panel1Click(Sender: TObject);
begin
  if bmp.Empty then bmp.LoadFromFile('..\..\hoge.bmp');
  Panel1.Refresh;
end;


のい  2012-05-25 20:25:01  No: 42316

サンプルありがとうございます。
ですが、、

TPanel = class(TPanel)  // XE2以外の場合
  protected
    procedure WndProc(var AMsg: TMessage); override;
  end;

まずこれがコンパイル通らないため、私の方でTPanelを変更した形で試したという経緯です。
'TPanel'型の宣言が完了していません
とでます。


Nov  2012-05-25 20:26:17  No: 42317

うまくいったら、の話ですが、WM_PAINTしかいじってないので、
>procedure WndProc(var AMsg: TMessage); override;
よりも
procedure EndOfPaint(var AMsg: TMessage); message WM_PAINT;
とかにした方が良いかも(条件文が削れるし)です。


のい  2012-05-25 20:46:42  No: 42318

すいません、やはり
  
  → TPanel = class(TPanel)
の「TPanel」そのままでいける方法を私は知りません。

TPanelA = class(TPanel)
とかにせず、
いけるものですか?


au  2012-05-25 20:59:23  No: 42319

TPanelの宣言はXE2じゃないなら下記ですね
  TPanel = class(ExtCtrls.TPanel)


Nov  2012-05-25 21:15:31  No: 42320

auさんフォローありがとうございます。
スコープは大事なんですね。


のい  2012-05-25 22:31:13  No: 42321

ありがとうございます。

> これは、デバイスコンテキストのハンドルと考えてよいですか?
そうです。

サンプル試しました。
ごめんなさい。結果がどうなればよいサンプルでしょうか?
私が試したのは、Image1にあらかじめ画像をプリロードしてあり、PanelクリックでBMPを読み込みます。
すると、Imageの下にBMPがきます。
ということが確認できるようですか?

ただ結果Imageに描画するとBMPが消えてしまうし、

> Panelのハンドルを取得して、別アプリでビットマップ画像を常時貼り付けをためすとPanelの画像が上に来ます。

私の方で納得できていなかったらすいません。


のい  2012-05-25 22:34:06  No: 42322

すいません、読みづらかったので。。。


> Panelのハンドルを取得して、別アプリでビットマップ画像を常時貼り付けをためすとPanelの画像が上に来ます。


> Panelのハンドルを取得して、別アプリでビットマップ画像を常時貼り付け

これをためすとPanelの画像が上に来ます。


Mr.XRAY  URL  2012-05-26 00:57:18  No: 42323

こんにちは,Mr.XRAYです.
途中からで,よくスレッドを読んではいなのですが,最初の質問文から判断して,

(1) フォームにTPanelを配置
(2) その上にTImageを配置,このTImageはPanelよりも小さいサイズ
(3) TImageにビットマップを描画する
(4) TPanelに何らかの方法で,ビットマップを描画する
(5) TPanelに描画した画像は全て見えるが,TImageの画像が見えなくなってしまう
(6) TImageの画像を前面に表示したい

ということですよね?
TPanelは,TGraphicControlの派生コンポではありません.
したがって,他のウィンドウが重なると,重なった部分は消えてしまいます.

>Panelのハンドルを取得して、別アプリでビットマップ画像を常時貼り付けています。
(プロセス越えの動作ですね.かなりの高級技です)
(一体,どういう仕様のアプリなんだろう?)

ということですので,それでいいんですよね?
一番簡単なのは,Panelに直接描画するのではなく,Panelの上に,
AlignをalClientにしたTImageを載せ,そこに描画することではないかと思います.
そうすれば,BringFront等も使用可能です.

どうしてもPanelに描画していのであれば,そのPanelへの描画の終了を検出して,
TImage.Refresh; を実行することではないかと思います.
他のアプリからの実際の描画方法が分かりませんので,このぐらいしか言えません.

以下のコードは,自アプリの場合の処理の1つの方法です.
動作確認環境は,Windows XP(SP3), Windows 7 U64(SP1) + Delphi XE です.

implementation

{$R *.dfm}

type
  TMyPanel = class(TPanel);  // Protected部へのアクセス用

//=================================================================
//  Image1にビットマップ画像を描画する
//  Panel1は,Image1の親で,サイズはPanel1より小さい
//=================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Image1.Picture.LoadFromFile('TestbmpImage.bmp');
end;

//=================================================================
//  パネルにビットマップ画像を描画する
//  TPanelは,TPaintBox同様,ウィンドウが上に重なると描画は消える
//=================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  Bmp : TBitmap;
begin
  Bmp := TBitmap.Create;
  try
    Bmp.LoadFromFile('Neko.bmp');
    TMyPanel(Panel1).Canvas.StretchDraw(Panel1.ClientRect, Bmp);
    //これがないと,描画が更新されない場合がある
    //Image1.Invalidate;でも可
    Image1.Refresh;
  finally
    Bmp.Free;
  end;
end;

//=================================================================
//  描画をクリア
//=================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
  Image1.Picture := nil;
  Panel1.Refresh;
end;

Panelに描画した画像が,「見掛け上」上に表示されてしまうのは,
TWinControlとTGraphicControlを混在して,画像表示に使用しているためです.
どちにか一方だけであれば,この現象は発生しません.


くうねる  2012-05-26 01:36:19  No: 42324

ちょっとだけですが、
他のアプリってのが曲者っぽいですね。
多分DirectX使ったフルスクリーンアプリだったりしませんか?
いずれにせよ、Windowsの描画方式は一種類だけではありません。
そのへんから調べないとキツイっすよ。


Nov  2012-05-26 03:39:41  No: 42325

>ただ結果Imageに描画するとBMPが消えてしまうし、

もしかして、Imageの背景としてBMP画像を残したままにしたいということなのでしょうか。

だとすると、Imageは不要で、こんな感じ?
procedure TPanel.WndProc(var AMsg: TMessage);
begin
  inherited;
  if AMsg.Msg=WM_PAINT then begin
    // 外部アプリのbmp書き込みの代わり
    Form1.cv.StretchDraw(Rect(0, 0, Width, Height), Form1.bmp);
    // ここに描画処理
    cv.Brush.Style := bsClear;
    cv.Pen.Color := clRed;
    cv.Rectangle(50, 50, 100, 100);
  end;
end;

テストでは描画できますが、実際にうまくいくかは、別アプリの'常時貼り付け'の方法とタイミングによります。


Mr.XRAY  2012-05-26 08:47:16  No: 42326

>他のアプリってのが曲者っぽいですね。

ですね.実際,他のアプリへは,デバイスコンテキストを取得すれば可能ですが,
恒久的というわけには行きません.
Delphiで言えば,TForm, TPanel や TPaintBox 等,TWinControl への描画と同じで,
常に再描画が必要となります.

したがつて,

>もしかして、Imageの背景としてBMP画像を残したままにしたいということなのでしょうか。

等と予想するしかないですね.
どのようなアプリで,どのような動作仕様にするかを考える必要がありますから.
難しいところです.


Mr.XRAY  2012-05-26 08:50:41  No: 42327

今見直したら,失礼しました.

>AlignをalClientにしたTImageを載せ,そこに描画することではないかと思います.

これは出来ないですね.
他のアプリから,TImageには描画できません.ハンドルがないですから.


KHE00221  2012-05-26 17:23:05  No: 42328

procedure TPanel.WndProc(var AMsg: TMessage);
begin
  inherited;
  if AMsg.Msg=WM_PAINT then Form1.Image1.Refresh;
end;

これループするだろ


Nov  2012-05-26 18:40:08  No: 42329

>これループするだろ
あ、ごめんなさい。気づきませんでした。
(キューが違うから意識してませんでした)


Nov  2012-05-26 18:45:54  No: 42330

>テストでは描画できますが、実際にうまくいくかは、別アプリの'常時貼り付け'の方法とタイミングによります。

これ、たぶん駄目です。
(画像描画が、背景消去と描画の間にうまく割り込むタイミングでない限り)


KHE00221  2012-05-27 01:25:57  No: 42331

>Panelのハンドルを取得して、別アプリでビットマップ画像を常時貼り付けています。

このアプリはなに?


のい  2012-05-28 17:51:12  No: 42332

みなさん、ありがとうございます。

結局ほかアプリはデバイスコンテキストを取得して、Panelに貼り付けを行っているものです。
貼り付け画像は常時変化しますので、タイミングとしてはその変化のあとPanelに画像をのこしたまま描画し、今度は描画をのこしたまま、画像を変化させる。
といった荒業?  になってしまいます。
→ここのアドバイスと合わせ、自分が試した結果そういったことは厳しいと判断しました。

すいません、当初できないだろうという思いはあったのですが、
みなさんならどういった切り抜け方をするのか、また自分が知らないアドバイス等もほしかったので質問しました。

結局自分としては、画像の上に描画する際は画像の更新は必要なかったので描画の際は更新を止めるようにしました。

色々勉強になりました。


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








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