マイクロソフトワードのオートシェイプのように図形を表示したいと考えています。
「ボックス」はほぼ実現でき、次は「コネクタ」のような線(まずは直線)にトライしていますが、壁に当たってしまいました。
ポイントとなる要件は、次の2つです。
1)斜めに表示できる
2)他の図形を隠さないように背景を透明にできる
自分なりに調査、実験して、次のように考察しています。
a)ラベルはPaintイベントがないので、フォームに隠れたときなど
再描画できないから適さない
b)TImageは描画すると背景が着色されてしまうので適さない
c)TPaintBoxは背景が透明のまま線を描画できる
そこでPaintBoxでやってみましたが、生成したものには線を描画できません。
他のスレッドでこの件を質問していますが、もしかすると、もっと適したクラスがあるのかも?とも感じています。
TPaintBoxよりも適したクラスがあるでしょうか?・・・(A)
または、アプローチが根本的に間違えているのかも?と感じています。
そこで、例えばあらかじめ200個程度のPaintBoxを設計時にフォームに貼り付け、それを使って実装する方法も思いつきました。・・・(B)
Visibleで表示/非表示を制御するなどすればよいのかな?と考えています。
(A)(B)について、アドバイスをいただけますか?
どうぞ、よろしくお願いいたします。
>b)TImageは描画すると背景が着色されてしまうので適さない
そんなことはありませんよ.図形とその描画方法を工夫すれば可能です.
これはTPaintBoxでも同じです.
Mr.XRAYさん、アドバイスありがとうございます。
>そんなことはありませんよ.図形とその描画方法を工夫すれば可能です.
TImageのCanvasに線を描画しても、線以外の背景を透明にすることができるのですね?
どのような点がポイントになるのか、もしよろしければ、教えていただけますか?
たとえば以下のコードです.
ただし,図形などを扱うアプリは,どこに,どのように表示し,操作するかに
よって,かなりコーディングを変える必要があるでしょう.もう少し具体的な
方針が決まれば,また他の方法もあります.
Form1にTImageを2つ,TButtonを1つ配置して下さい.
procedure TForm1.Button1Click(Sender: TObject);
begin
with Image1 do begin
Transparent:=True;
Top :=0;
Left :=0;
Width :=100;
Height :=100;
Canvas.Brush.Style :=bsClear;
Canvas.Brush.Color :=clWhite;
Canvas.FillRect(ClientRect);
Canvas.Pen.Width:=5;
Canvas.Pen.Color:=clRed;
Canvas.MoveTo(0,0);
Canvas.LineTo(100,100);
end;
with Image2 do begin
Transparent:=True;
Top :=30;
Left :=30;
Width :=100;
Height :=100;
Canvas.Brush.Style :=bsClear;
Canvas.Brush.Color :=clWhite;
Canvas.FillRect(ClientRect);
Canvas.Pen.Width :=5;
Canvas.Pen.Color :=clBlack;
Canvas.MoveTo(20,0);
Canvas.LineTo(50,100);
end;
end;
>(A)(B)について、アドバイスをいただけますか?
これは現状の質問内容からは難しいです.例えばTLableだってPaintメソッドを
実装すれば以下のように図形,文字列も描画可能です.
type
TMyLable =class(TLabel)
protected
{ Protected 宣言 }
procedure Paint; override;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
var
ALable : TMyLable;
procedure TForm1.Button1Click(Sender: TObject);
begin
ALable:=TMyLable.Create(Self);
with ALable do begin
Parent:=Self;
Left :=100;
Top :=50;
Width :=200;
Height:=100;
end;
end;
{ TMyLable }
procedure TMyLable.Paint;
begin
inherited;
with ALable do begin
Canvas.Pen.Width:=3;
Canvas.Pen.Color:=clGreen;
Canvas.MoveTo(0,0);
Canvas.LineTo(200,100);
Canvas.Font.Size:=20;
Canvas.TextOut(20,30,'喫茶XRAY');
end;
end;
>オートシェイプのように図形を表示したいと考えています。
単に図形を表示するのであれば,TPaintBoxなりTImageのCanvasに描けば
OKなわけです.また
>あらかじめ200個程度のPaintBox...
一つのTPaintBox,TImageのCanvasにいくつでも図形は描画できます.
原理的に,図形ごとに用意する必要はありません.もちろん,これは作成
したいアプリにもよります.
つもり,デル太さんが一体どのような動作をするプログラムを作成したい
かが具体的にわからないとアドバイスは難しいということです.
> つもり,デル太さんが一体どのような動作をするプログラムを作成したい
> かが具体的にわからないとアドバイスは難しいということです.
ずばりその通り。
で、簡単に分けるなら、以下のような感じですか。
(1)
>あらかじめ200個程度のPaintBox...
という、それぞれの図形を一つのPaintBoxで扱う方法
(2)
描画は一個のPaintBoxを使用し、移動用には別のTPaintBoxを利用する方法。
(図形情報は、TList等でリスト化して保持する等)
(3)
TPaintBoxのような汎用ではなく、各図形を全て自作する
TTriangle:三角形 等
(3)であれば、自分の都合のいい設計にできるので、(A)の回答として、
TPaintBoxより適したクラスになる可能性はあります。
ちなみに、TPaintBoxへの描画は、200個程度なら大丈夫かもしれませんが、
数が増えれば増えるほど、再描画時の負担が増えます。
データ量が多くなると、極端にパフォーマンスが落ちる可能性があります。
(線・文字・円を含め、万単位の描画があるとき、画面を切り換えて戻ってきたときに、
数秒待たされたことがあるので、今ではTImageで描画するように変更しています。)
内部的なデータ構造等にも影響される部分もあるので、何が適切かは、担当している人が判断するしかないですね。
別スレッドにあった
(4)OnPaintで実行するコードを持たせたクラスを定義し、それを動的に生成する
について試してみました。大きめに PaintBox1 を貼って、Button1, Button2 を使います。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls,
Contnrs, StdCtrls;
type
TEnumZukei = (ezLine, ezEllipse); // 図形クラスの区別
TZukei = class(TObject) // 図形クラスのベース
public
ez: TEnumZukei;
color: TColor;
width: integer;
end;
TLine = class(TZukei) // 直線クラス
public
start, stop: TPoint;
constructor Create(strt, stp: TPoint); virtual;
end;
TEllipse = class(TZukei) // 楕円クラス
public
point1, point2: TPoint;
constructor Create(pt1, pt2: TPoint); virtual;
end;
TForm1 = class(TForm)
PaintBox1: TPaintBox;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private 宣言 }
public
ZukeiList: TObjectList;
h, w: integer;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TLine.Create(strt, stp: TPoint);
begin
ez := ezLine;
start := strt;
stop := stp;
end;
constructor TEllipse.Create(pt1, pt2: TPoint);
begin
ez := ezEllipse;
point1 := pt1;
point2 := pt2;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ZukeiList := TObjectList.Create(true);
w := PaintBox1.Width;
h := PaintBox1.Height;
Randomize;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
i: integer;
cvs: TCanvas;
p1, p2: TPoint;
begin
if ZukeiList.Count = 0 then exit;
cvs := PaintBox1.Canvas;
for i := 0 to ZukeiList.Count-1 do
begin
cvs.Pen.Color := TZukei(ZukeiList[i]).color;
cvs.Pen.Width := TZukei(ZukeiList[i]).width;
cvs.Brush.Style := bsClear;
case TZukei(ZukeiList[i]).ez of
ezLine: begin
p1 := TLine(ZukeiList[i]).start;
p2 := TLine(ZukeiList[i]).stop;
cvs.MoveTo(p1.X, p1.Y);
cvs.LineTo(p2.X, p2.Y);
end;
ezEllipse: begin
p1 := TEllipse(ZukeiList[i]).point1;
p2 := TEllipse(ZukeiList[i]).point2;
cvs.Ellipse(p1.X, p1.Y, p2.X, p2.Y);
end;
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
line: TLine;
p1, p2: TPoint;
begin
p1 := Point(Random(w), Random(h));
p2 := Point(Random(w), Random(h));
line := TLine.Create(p1, p2);
line.color := RGB(Random(256), Random(256), Random(256));
line.width := Random(6);
ZukeiList.Add(line);
PaintBox1.Refresh;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
ellipse: TEllipse;
p1, p2: TPoint;
begin
p1 := Point(Random(w), Random(h));
p2 := Point(Random(w), Random(h));
ellipse := TEllipse.Create(p1, p2);
ellipse.color := RGB(Random(256), Random(256), Random(256));
ellipse.width := Random(6);
ZukeiList.Add(ellipse);
PaintBox1.Refresh;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
ZukeiList.Free;
end;
end.
Button1Click では、直線クラスを動的に作って ZukeiList に Add したのち、PaintBox1.Refresh; ですべての図形を描きます。
Button2Click では、楕円クラスで同様にします。
PaintBox1Paint では、TLine TEllipse 側に描画メソッドをつくってキャンバスを渡して、オブジェクトに描いてもらうようにした方がスマートですね。描画メソッドのプロトタイプは TZukei で virtual;abstract; にしておいて、派生クラスで実装すると、図形の区別はしなくて済みますね。
図形クラス自身が描画メソッドを持つようにした前回の改良版です。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls,
Contnrs;
type
TZukei = class(TObject) // 図形クラスのベース
protected
Fcolor: TColor;
Fwidth: integer;
public
procedure Draw(cvs: TCanvas); virtual; abstract;
end;
TLine = class(TZukei) // 直線クラス
private
Fstart, Fstop: TPoint;
public
constructor Create(start, stop: TPoint; color: TColor; width: integer); virtual;
procedure Draw(cvs: TCanvas); override;
end;
TEllipse = class(TZukei) // 楕円クラス
private
Fpoint1, Fpoint2: TPoint;
public
constructor Create(pt1, pt2: TPoint; color: TColor; width: integer); virtual;
procedure Draw(cvs: TCanvas); override;
end;
TForm1 = class(TForm)
PaintBox1: TPaintBox;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
public
ZukeiList: TObjectList;
h, w: integer;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TLine.Create(start, stop: TPoint; color: TColor; width: integer);
begin
Fstart := start;
Fstop := stop;
Fcolor := color;
Fwidth := width;
end;
procedure TLine.Draw(cvs: TCanvas);
begin
cvs.Pen.Color := Fcolor;
cvs.Pen.Width := Fwidth;
cvs.MoveTo(Fstart.X, Fstart.Y);
cvs.LineTo(Fstop.X, Fstop.Y);
end;
constructor TEllipse.Create(pt1, pt2: TPoint; color: TColor; width: integer);
begin
Fpoint1 := pt1;
Fpoint2 := pt2;
Fcolor := color;
Fwidth := width;
end;
procedure TEllipse.Draw(cvs: TCanvas);
begin
cvs.Pen.Color := Fcolor;
cvs.Pen.Width := Fwidth;
cvs.Ellipse(Fpoint1.X, Fpoint1.Y,Fpoint2.X, Fpoint2.Y);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ZukeiList := TObjectList.Create(true);
w := PaintBox1.Width;
h := PaintBox1.Height;
Randomize;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
ZukeiList.Free;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
i: integer;
begin
if ZukeiList.Count = 0 then exit;
PaintBox1.Canvas.Brush.Style := bsClear;
for i := 0 to ZukeiList.Count-1 do
TZukei(ZukeiList[i]).Draw(PaintBox1.Canvas);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
line: TLine;
p1, p2: TPoint;
cl: TColor;
begin
p1 := Point(Random(w), Random(h));
p2 := Point(Random(w), Random(h));
cl := RGB(Random(256), Random(256), Random(256));
line := TLine.Create(p1, p2, cl, Random(6)+1);
ZukeiList.Add(line);
PaintBox1.Refresh;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
ellipse: TEllipse;
p1, p2: TPoint;
cl: TColor;
begin
p1 := Point(Random(w), Random(h));
p2 := Point(Random(w), Random(h));
cl := RGB(Random(256), Random(256), Random(256));
ellipse := TEllipse.Create(p1, p2, cl, Random(6)+1);
ZukeiList.Add(ellipse);
PaintBox1.Refresh;
end;
end.
Mr.XRAY様、は?様、えーと様、コメント&コードのご提示ありがとうございます。
別スレッドの方に一気に書き込みをしていて、こちらが進まずに申し訳ありません。
Mr.XRAY様のコードは、印刷して持ち歩き、何度も見て慣れるようにしています。
クラスのイベントを拡張して実装するテクニックを、きっと理解できるのでは?と期待しています。
は?様、情報不足にも関わらず、方針を考察していただきありがとうございます。とても参考になります。
特に自作クラスがTPaintBoxより適するかも?というご指摘と、TImage利用の示唆、参考にさせていただきます。
えーと様がご提示くださったコードが、その具体例として役立ってくれそうですよね?
Mr.XRAY様のコードを見返して、基本を理解しつつありますので、えーと様のコードにチャレンジすることができそうです。
えーと様、書き込みを終えてこちらのスレッドに来たら、さっき書いたばかりの課題がコードになっていて大変ビックリ!いたしました。
非常に分かりやすいコードで記述いただき、感謝の気持ちでいっぱいです。
私が別スレッドで直面している「クラスの構築」について、このサンプルを対象に自分の課題として取り組むことができます。
とても貴重な「教材」を手に入れた感じです。
みなさま、本当にありがとうございます!益々がんばりますね!
えーと様のコードが大変嬉しいタイミングでしたので、今、ソースを貼り付けて実行しています。
ところが、最初の段階でさっそくつまずいてしまいました。(苦笑)
PaintBox1.Width ←800
PaintBox1.Height ←500
としてPaintBox1とButton1,Button2を貼り付け、Unit1.pas全体を上書き、保存しました。
そして実行してみたのですが、Button1、Button2をクリックしても、ともにPaintBox1へ描画されませんでした。
p1 := Point(Random(w), Random(h));
p2 := Point(Random(w), Random(h));
の部分で、p1(x:0;y:0),p2(x:0;y:0)になっているようです。
何か必要な設定など不足または間違えてしまっていそうでしょうか?
もし、わかりましたら、情報をいただけると嬉しいです。
私自身は、このあと自分で解析をして理解を進め、問題点を見つけられれば解決していきたいと思います。
参考までに別スレッドを紹介いたします。
□スレッド:生成したTPaintBoxに線を描画するには?
https://www.petitmonte.com/bbs/answers?question_id=3774
procedure TForm1.FormCreate(Sender: TObject);
begin
ZukeiList := TObjectList.Create(true);
w := PaintBox1.Width; // これやってますか?
h := PaintBox1.Height;
Randomize;
end;
イベントハンドラがちゃんとオブジェクトインスペクタを使って生成してますか?
コードをコピペしただけとか?
宣言部やメソッドをひとつずつ確認しながら挿入してみました。
その際、イベントハンドラも、オブジェクトインスペクタを使って開き、コードをコピペしました。
コードに宣言部を挿入して、右クリックして表示される「カーソル位置のクラス定義保管」コマンドも利用しました。
その結果、次の2箇所に「inherited;」が自動挿入されました。
diffで確認し、この部分だけが「えーと」さんのコードと異なっていました。
私は「inherited;」があってもなくても良いのか、絶対必要なのか?までわからないのですが、この2行を削除しても問題なく動作し、図形が表示されました。
この点は、不思議なんですが・・。
-----
{ TLine }
procedure TLine.Draw(cvs: TCanvas);
begin
inherited;
cvs.Pen.Color := Fcolor;
cvs.Pen.Width := Fwidth;
cvs.MoveTo(Fstart.X, Fstart.Y);
cvs.LineTo(Fstop.X, Fstop.Y);
end;
{ TEllipse }
procedure TEllipse.Draw(cvs: TCanvas);
begin
inherited;
cvs.Pen.Color := Fcolor;
cvs.Pen.Width := Fwidth;
cvs.Ellipse(Fpoint1.X, Fpoint1.Y,Fpoint2.X, Fpoint2.Y);
end;
-----
ここまでまとめて、「えーと」さんが2件回答くださっていることに気づきました。
さっそくの回答、ありがとうございます!!
最初の段階では、オブジェクトインスペクタを利用せず、コード全体をコピペしました。これが原因かもしれませんね?
> w := PaintBox1.Width; // これやってますか?
はい、全体をコピペしたので、これはやっていました。
「inherited;」が必要かどうか?
については「継承元オブジェクトのメソッドを実行する」処理ですよね?
今回、継承もとのTZukeiのコンストラクタ(Createメソッド)にはコードがありませんから、あってもなくても同じなのでは?と感じます。
この解釈は、正しいでしょうか?
どなたか、正確な解釈をご指導いただけると嬉しいです。よろしくお願いいたします。
また「全体をコピペしたから動作しなかった」の証明として、次の実験をいたしましたので報告します。
1)動作したコード全体をエディタにコピーする
2)Delphiで新規アプリケーションを作成する
3)Button1,Button2,PaintBox1を配置する
4)自動生成されたUnit1.pas全体を削除し、上記1)で退避したコードを貼り付ける
5)実行する
この結果、オリジナルをコピペした場合と同じく、描画されない結果となりました。
「えーと」様のご指摘のように、コピペではダメなことがわかりました。
これは状況証拠だけなので、どなたか論理的な面で補足をいただけると助かります。
よろしくお願いいたします。
では、これから、いただいたソースをもう一度じっくり拝見し、ひとつずつ解釈して行こうと思います。
> > つもり,デル太さんが一体どのような動作をするプログラムを作成したい
> > かが具体的にわからないとアドバイスは難しいということです.
>
> ずばりその通り。
この点、情報不足で申し訳ありませんでした。
今回取り掛かっているプログラムは、簡単なチャートを描くプログラムです。
> 内部的なデータ構造等にも影響される部分もあるので、何が適切かは、担当している人が判断するしかないですね。
「は?」さんにご指摘いただいた、この点については「えーと」さんが汎用的な形かつ全体をコード例を示してくださいましたので、かなり有用と思います。
ありがとうございます。
TLabelから派生させて描画したボックスをクリックで移動させる予定で、クリックによるドラッグ開始と移動部分は試作済みです。
これに今回のえーとさんのコードを応用していきます。
ボックスの種類が2種類なので、「えーと」さんのコードを参考に「基本→派生×2」の応用ができると思います。
現状、キャプションが再描画されるのでTLabelを選びましたが、2つのスレッドで勉強させていただいたので他のクラスに変えることもできそうと、自信を持てています。
この点、みなさま、本当にありがとうございます。
線は、2つのボックスを結びます。
始点のボックスから終点のボックスへ線を描画します。
これもTLineがとても参考になります!
線は当面は直線で実現し、必要に応じて曲線やカギ型にトライします。
線に始点ボックスを持たせるか、ボックスが終点ボックスを保持するか、悩んでいるところです。
描画済みの線をクリックで選択したいので、TPaintBoxまたは他に適したクラスを元にしようと考えています。
ボックスはえーとさんのコードのTEllipse,線はTLineを応用して、TLabelやTPaintBoxなどをSetBoundsすればいけるかも?と期待しています。
また、描画結果はファイルに保存する予定です。
現状は、TLabelを配置したパネルをWriteComponentで一気に保存して、一気に読み込んでいますので、これを応用して行こうと考えています。
WriteComponenteではプロパティが保存されるので、TLineの属性もプロパティとして実装する必要があるかもしれません。
以上、簡単ですが、紹介させていただきます。
こうしてまとめると、まだまだ考察不足の感じがして参ります。
お気づきの点などございましたら、ご指導いただけると嬉しいです。
よろしくお願いいたします。
>「inherited;」が必要かどうか?
デル太さんの解釈であっています。この場合、上位クラスは abstract メソッドなので、ないほうがいいと思います。コンストラクタの場合はあってもなくてもいいと思います。
> 「えーと」様のご指摘のように、コピペではダメなことがわかりました。
コピペで動作しないのは、初歩の初歩です。イベントハンドラは、TForm 派生クラスのメソッドにすぎませんが、特別なことがあります。それは、オブジェクトインスペクタを通じて、イベントとハンドラが結びつけられていることです。この内容は *.dfm ファイルに記録されています。ですから、単なるコピペでは、この結びつきがないので、イベントが起きてもハンドラが呼び出されません。イベントハンドラをコピペするときは、まず、オブジェクトインスペクタで空のハンドラを生成し、それをコピペで上書きするといいです。
> >「inherited;」が必要かどうか?
>
> デル太さんの解釈であっています。この場合、上位クラスは abstract メソッドなので、ないほうがいいと思います。コンストラクタの場合はあってもなくてもいいと思います。
ありがとうございます!
abstractは「下位クラスで実装しなさい」でしたよね?
それであれば、inheritedはない方が誤解がないですね。
コンストラクタの場合は、必要なら呼ぶし、必要なければ呼ばない、と理解しました。
> > 「えーと」様のご指摘のように、コピペではダメなことがわかりました。
>
> コピペで動作しないのは、初歩の初歩です。
初歩の初歩なんですね。(苦笑)
もしかすると、IDEの基本を理解できていないのかもしれません。
みなさま、お勧めの書籍などございますか?
> イベントハンドラは、TForm 派生クラスのメソッドにすぎませんが、特別なことがあります。それは、オブジェクトインスペクタを通じて、イベントとハンドラが結びつけられていることです。この内容は *.dfm ファイルに記録されています。ですから、単なるコピペでは、この結びつきがないので、イベントが起きてもハンドラが呼び出されません。イベントハンドラをコピペするときは、まず、オブジェクトインスペクタで空のハンドラを生成し、それをコピペで上書きするといいです。
このご説明は、示唆に富み、考えるきっかけを与えてもらいました。
そして、とてもわかりやすいです。ありがとうございます。
別スレッドでの疑問のかなりの部分に関連していると思います。
そこで、別スレッドでも紹介させていただきました。
重複になってしまいますが、
> (1)イベントハンドラはTForm派生クラスのメソッドである
とあるので、TForm派生クラス内に宣言を書きました。
すると、オブジェクトインスペクタにMyClickメソッドが表示されるではありませんか!
それを選択してOnClickに関連付けて、保存すると、OnClick = MyClick が記録されています。
かなり理解を進めることができました。
まだ「イベント」と「イベントハンドラ」が別にあるのか?という疑問が残っています。
お伺いしてもよろしいでしょうか?
「イベントが起きてもハンドラが呼び出されない」
という記述から、イベントとハンドラは別のものと思います。
TButtonControl←TButton
を対象にClickを題材に調べてみたところ、
property OnClick;
となっています。
OnClickがイベントの実装部分か?と予想していたのですが、ここから先がたどれませんでした。
procedure TButton.Click;
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if Form <> nil then Form.ModalResult := ModalResult;
inherited Click;
end;
もみつけました。
ただ、上位クラスのTButtonControlにはClickがありませんでした。
さらに上位のTWInControlにも見つけられませんでした。
イベントはどこに記述されているのでしょう・・?
dfmファイルがあるのかな・・???
[このスレッドのまとめ]
直前の疑問「イベントはどこに記述されているのでしょう・・?」は解決しました。下記で紹介する別スレッドをご参照ください。
私が疑問を感じるままにスレッドの内容を展開してしまいましたので、最後に整理させていただきます。
タイトルに対する回答としては、Mr.XRAYさんが「Mr.XRAY [HomePage] 2006/03/18(土) 20:40:44」の発言を中心に示唆を与えてくださいました。
「は?」さんも「は? 2006/03/19(日) 11:07:40」で自作クラスが有効となる可能性を示してくださり、合わせてTPaintBoxとTImageのパフォーマンスについてご経験から情報を提供してくださいました。
おふたかたが示された方向性については、以下に紹介する中で、えーとさんの(4)の実装例が大変参考になると思います。
-----
別スレッドで私は次のように発言しました。
□別スレッド:生成したTPaintBoxに線を描画するには?
https://www.petitmonte.com/bbs/answers?question_id=3774
> 「PaintBoxで再描画が必要な処理は、OnPaintに描画する」について、次のようなケースが考えられると思います。
>
> (1)開発環境(IDE)を使いフォームにPaintBoxを配置した場合
> (2)PaintBoxを動的に生成した場合で、OnPaintに他のオブジェクトのメソッドを割り当てる場合
> (3)PaintBoxを動的に生成した場合で、OnPaintに独自に入力したメソッドを割り当てる場合
> (4)OnPaintで実行するコードを持たせたクラスを定義し、それを動的に生成する
> (5)クラスに新しいイベントを実装する
このうち(1)〜(3)について、みなさんの助言を受けて、サンプルコードを作成できました。
別スレッドの「デル太 2006/03/20(月) 18:30:02」の発言に掲載していますので、ご覧ください。
ここのスレッドで、(4)について「えーと」さんがサンプルコードを示してくださいました。
「えーと 2006/03/20(月) 19:20:28」で初期版を、「えーと 2006/03/20(月) 21:20:59」で改良版を示してくださっています。
初期版から改良版へ変更する過程も非常に参考になると思います。
最後に、(5)についてサンプルコードをまとめることができました。
別スレッドの「デル太 2006/03/21(火) 16:54:16」に掲載させていただきました。
その後の発言で、コメント部分を若干訂正しています。
(5)については、あえて新しいイベントを実装する理由がないことも、このスレッドを進める中で理解できるようになりました。
本スレッドのタイトルに関する有効な実装方法は(1)〜(4)で、状況に応じて選択することになると思います。
(5)については、独自イベント実装に関する技術的な資料とお考えください。
(1)〜(5)については実際に動作するものと全体のコードがまとまりましたので、私がそうであったようにきっと分析や考察のお役に立てると思います。
その際、イベントハンドラはコピーペーストでは動作しませんので、本文中の「えーと」さまの助言「えーと 2006/03/21(火) 00:06:16」を参考になさってください。
-----
長い間、お付き合いいただきありがとうございます。
ご指導いただいたみなさまにとても感謝しています。
上記(1)から(5)を通して理解できたことは、実装時の選択肢を考える上でとても貴重な体験になりました。
解決しているのですが、Mr.XRAYさんに提示いただいた2つのコードを実行確認していなかったことに気づき、遅ればせながら確認させていただきました。
「Mr.XRAY [HomePage] 2006/03/18(土) 13:24:40」で提示していただいたコードを実行し、確認できました。
> b)TImageは描画すると背景が着色されてしまうので適さない
と書きましたが、ご指摘のようにTImageでも背景を透明にできました。
Transparent:=True;
がポイントです。
この行をコメントアウトすると、背景が着色されることを確認しました。
「Mr.XRAY [HomePage] 2006/03/18(土) 20:40:44」で提示していただいたコードを実行し、確認できました。
> a)ラベルはPaintイベントがないので、フォームに隠れたときなど再描画できないから適さない
と書きましたが、ご指摘のように、Paintメソッドを実装し、図形、文字列が描画されました。
多くの実装方法が考えられること、とてもよくわかりました!
なお、今まで教えていただいた知識を元に、Paintメソッド内でグローバル変数を使わない方法も実現できました。
以下へ掲載させていただきます。
掲示板へ書き込む前の私には、いくつもの山があり、この形を導くことはできませんでした。
最初の書き込みはまだほんの5日前のことなのですよね。Mr.XRAYさんをはじめ、皆様のおかげです。
高度な技術を持った方々に直接の助言をいただけて、とても嬉しいです。
-----
//LabelにPaintメソッドを実装するサンプル
//アドバイス:Mr.XRAY様 2006/03/18(土) 20:40:44
//それをもとに、グローバル変数を使わない形にできた
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyLabel =class(TLabel)
protected
{ Protected 宣言 }
procedure Paint; override;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
ALabel: TMyLabel;
begin
ALabel:=TMyLabel.Create(Self);
with ALabel do begin
Parent:=Self;
Left :=100;
Top :=50;
Width :=200;
Height:=100;
end;
end;
procedure TMyLabel.Paint;
begin
inherited;
//メソッドの実装部分では,Self 識別子はそのメソッドが呼び出された
//オブジェクトを参照します(ヘルプより)
with Self do begin //←TMyLabel型のALabelのメソッドなのでSelfでよい
Canvas.Pen.Width:=3;
Canvas.Pen.Color:=clGreen;
Canvas.MoveTo(0,0);
Canvas.LineTo(200,100);
Canvas.Font.Size:=20;
Canvas.TextOut(20,30,'喫茶XRAY');
end;
end;
end.
ツイート | ![]() |