Bu yazımın amacı program kullanıcılarının çalışma zamanında kendine özgü bir ekran görüntüsünü oluşturup bunu kayıt etmek ve bu kaydı tekrar yüklemeye yarayan bildiğim bir tekniği paylaşıma açmaktır. Bu duruma daha çok rapor tasarım araçlarında rastlanmaktadır. Hatta bilindik delphi IDE'si ve VCL'i bile tasarlanan formları .dfm dosyalarında buna benzer bir tekniği kullanarak kayıt edip kullanmaktadır. Bu yazıda tasarım ve görüntü organizasyonu üzerine fazla yoğunlaşamayacağım. Sadece kayıt ve yükleme işleminden bahsedecğim.
Bu konuya giriş yapmadan önce TComponent mimarisi hakkında bilinmesi gereken önemli bir kaç noktadan bahsetmek gerekmektedir. TComponent sınıfı bileşen sınıfı oluşturmak için gereken temel sınıftır. Tüm Delphi bileşenleri bu sınıftan türetilerek oluşturulmaktadır. Bu bileşenin bariz bir özelliği vardır, her bileşen tıpkı disk üzerindeki klasörler gibidir. Nasıl ki bir klasör içinde başka klasörler hatta onların içinde de başka başka klasörler oluşturabiliyorsak aynı durum bileşenler içinde geçerlidir. Her bileşen sahibi olduğu bileşenleri kendi bünyesinde liste halinde tutmaktadır. Haliyle de bir bileşenin silinmesi de bu bileşenin bünyesinde bulunan tüm bileşenlerin silinmesini sağlar (Bir klasör silinince tüm alt klasörler de silinir). Mesela çalışma zamanında şu kodu test edersek formumuzun üzerinde bulunan Panelin içinde bir TEdit bileşeni belirecektir.
Kod: Tümünü seç
procedure TForm1.Button1Click(Sender: TObject);
begin
with TEdit.Create(Self) do begin
Parent:=Panel1;
Show;
end;
end;
Application
....|
....|-->DataModul Bileşeni
....|............|
....|............|-->Görsel Olmayan Bileşenler (TTable,TQuery vs. vs. gibi bileşenler)
....|
....|-->Form Bileşeni
................|
................|-->Görsel veya Görsel Olmayan Bileşenler (Tüm delphi bileşenleri)
Bu yapımıza göre Application bileşeni altında aynı isme sahip TForm veya TDataModul bileşeni bulundurulamaz ve aynı mantığa göre her DataModul veya Form bileşenlerinin kendi bünyesinde aynı isme sahip iki bileşen bulundurulamaz.
Kısacası anlatmaya çalıştığım nokta tasarım anında oluşturulacak nesneler için temel bir bileşen oluşturmak ve bu tasarım nesnelerini bu temel nesnenin bünyesinde tutmak. Dosya kayıt işlemleri için ise Name özelliğine değer ataması yapabilmek. Bu 2 maddeyi uygulamak o kadar da önemli değil aslında ama işlem organizasyonumuzun kolaylığı ve örneklerin anlaşılabilmesi için gerekli. Mesela Design isimli bir bileşen bünyesinde oluşturulmuş bir projeyi Dosya->Yeni diyerek boşaltmak için yapılması gereken tek şey Design.Free; kodunu yazarak tüm tasarımı silmek olacak.
Asıl konumuza dönecek olursak; burada anahtar nesne ve metotlarımız bileşen tasarımını kayıt etmek için TStream.WriteComponent(TComponent); yapılan kayıtlardan bileşenleri tekrar oluşturmak için TStream.ReadComponent(TComponent);'dir. Bunları kullanabilmek için dikkat edilmesi gereken noktalar bulunmaktadır. Bunlar bana göre olmazsa olmaz dediğimiz türden kurallardır:
1- .dfm dosyasına yazılan özellik ve değerler published bloğunda tanımlıdır. Hatta onClick:=Button1Click; biçimindeki bir değerde Button1Click'te nereden geldi denilebilir. Bu da aslında published bloğunda tanımlanmıştır. Delphinin özellikler penceresinde (Object Inspector) olayların listeye gelebilmesi published olarak tanımlanan aynı tipteki olayların varlığına bağlıdır. Başka bir blok public,protected,private içinde tanımlanan bir olay bile kullanılabilsede bu ancak kodla yapılabilir. Bunu ne Object Inspector'dan ayarlayabilirsiniz ne de .dfm dosyasına kayıt edip kullanabilirsiniz. İllaki published bloğunda olacak.
2- .dfm biçiminde yapılacak kayıtlarda ve bundan tekrar oluşturmada kullanılacak sınıfların muhakkak RegisterClass(TBitBtn); prosedürü ile kayda geçmesi gerekmektedir.WriteComponent ile kaydetme esnasında her ne kadar sorun çıkarmasa da, ReadComponent oluşturma esnasında bu sınıf bulunamadı diye bir hata oluşturacaktır.
3- Oluşturulan tasarım nesnelerine ait olaylarında düzgün kayıt edilip okunabilmesi için kayıt edilecek kök bileşende tanımlı olması gerekmektedir. Haydaaa bu da nedemek dediğinizi duyar gibi oldum. Mesela bir form tanımı TForm1=class(TForm) şeklinde başlar. Buraya yerleştirilen her bileşen ve olayları TForm1 sınıfına aittir. Button1 nesnesine ait onClick olayı procedure TForm1.Button1Click(Sender: TObject); şeklinde tanımlıdır. Dikkat edilecek olursa Button1Click TForm1'e ait bir metotdur. Stream.WriteComponent(Form1); komutu bilindik .dfm dosyasını ortaya çıkaracaktır. Stream.WriteComponent(Button1); kullanılırsa tüm buton özelliklerinin .dfm biçiminde kayıt edilmesini sağlayacaktır fakat biri hariç. onClick=Button1Click; Bu değer elde edilemez çünkü Button1Click TForm1'e ait bir metotdur, Buttton1'e değil.
4- WriteComponent .dfm biçiminde kayıt edebilir ancak bu .dfm'nin yapısı ikilik biçimindedir. Bu ikilik biçimi metin biçiminde görebilmek için dönüştürücü ObjectBinaryToText fonksiyonuna ihtiyacımız olacak. ObjectTextToBinary ise bu metin biçimindeki .dfm değerlerini tekrar ikilik biçimine dönüştürür ve ReadComponent için kullanıma hazır hale getirir. Zaten örneğimizde bu dönüşümleri yapacak kodlar mevcut.
Not:Bu 4 maddeye değişik yorumlar getirenler olabilir, malum kaynak ve yabancı dil sıkıntısı çektiğim için ben bu kadarını buraya kadar çözüp, üretebildim. Ayrıca bu kadar detaylı bir anlatım ve kaynağa ihtiyaç muhakkak var kendimden biliyorum.
Hikaye yeter, uygulama isteriz dediğinizi duyar gibiyim. Eh!.. Ne yapalım, bu noktaya gelebilmek için yukarıdaki bilgilerin gerekli olması gerektiğini düşünüyorum. İlk yapılması gereken işlemimiz kayıt işlemini gerçekleştireceğimiz bölgesel ana sınıfı tanımlamak. Mesela bir formu kayıt etmek için oluşturulan bölgesel ana sınıf TForm1 gibi bir sınıftır. Bizde buna benzer bir sınıf oluşturacağız. Fakat bir sorunumuz var. Sınıfı hangi nesneden türetmemiz gerekiyor? Benim aklıma en yatkın olanı TScroolBox'tır. Yazacağımız tüm olayları (bilgileri kayıt edilecek tasarım nesnelerine ait olan olaylarda dahil onClick, onKeyDown vs.) bu sınıf içinde tanımlamamız gerekecek.
Kod: Tümünü seç
unit unit_designer;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const WM_DESIGNER_RELEASE=WM_USER+4;
type
TDesigner=class(TScrollBox)
//<-burası her ne kadar belirtilmese de published bloğudur->\\
procedure ScrClick(Sender:TObject);
procedure lblClick(Sender:TObject);
private
procedure SetHintActive(const Active:Boolean);
procedure WMRelease(var Message: TMessage); message WM_DESIGNER_RELEASE;
public
constructor Create(AOwner:TComponent);override;
destructor Destroy; override;
function Ekle(const Sinif:TControlClass):TControl;
property HintActive:Boolean write SetHintActive;
protected
published
end;
TControlCrack=class(TControl);
function DesignerLoad(const OwnerAndParent:TWinControl):TDesigner;
implementation
function DesignerLoad(const OwnerAndParent:TWinControl):TDesigner;
var i:Integer; S,Name:String;
begin
Result:=TDesigner.Create(OwnerAndParent);
Name:='Designer';i:=0;
repeat
Inc(i);
S:=Format('%s%d',[Name,i]);
if not Assigned(OwnerAndParent.FindComponent(S)) then Break;
until False;
Result.Name:=Name;
Result.Parent:=OwnerAndParent;
Result.Align:=alClient;
Result.Color:=clWindow;
Result.Show;
Result.HintActive:=True;
end;
{ TDesigner }
constructor TDesigner.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
OnClick:=ScrClick;
end;
destructor TDesigner.Destroy;
begin
inherited Destroy;
end;
function TDesigner.Ekle(const Sinif: TControlClass): TControl;
begin
Result:=Sinif.Create(Self);
with TControlCrack(Result) do begin
Parent:=Self;
OnClick:=lblClick;
Hint:=ClassName+#13#10+Name+'->'+Owner.Name+'('+Owner.ClassName+')';//Result.Owner=Self'tir
Show;
end;
end;
procedure TDesigner.WMRelease(var Message: TMessage);
begin
Free;
end;
procedure TDesigner.SetHintActive(const Active: Boolean);
var i:Integer;
begin
if Active then begin
Hint:=ClassName+#13#10+Name+'->';
if Assigned(Owner) then Hint:=Hint+Owner.Name+'('+Owner.ClassName+')'
else Hint:=Hint+'(Boş)'
end;
ShowHint:=Active;
for i:=0 to ComponentCount-1 do
if Components[i] is TControl then
with TControlCrack(Components[i]) do ShowHint:=Active;
end;
procedure TDesigner.lblClick(Sender: TObject);
begin
HintActive:=False;
PostMessage(Self.Handle,WM_DESIGNER_RELEASE,0,0);
end;
procedure TDesigner.ScrClick(Sender: TObject);
begin
ShowMessage(IntToStr(ComponentCount));
end;
initialization
RegisterClass(TDesigner);
RegisterClass(TLabel);
end.
Kod: Tümünü seç
object FormAna: TFormAna
Left = 192
Top = 114
Width = 696
Height = 480
Caption = 'FormAna'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
ShowHint = True
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 688
Height = 281
Align = alTop
TabOrder = 0
object Memo1: TMemo
Left = 96
Top = 40
Width = 273
Height = 201
TabOrder = 0
end
object Button1: TButton
Left = 384
Top = 216
Width = 75
Height = 25
Caption = '>>'
TabOrder = 1
OnClick = Button1Click
end
object Button2: TButton
Left = 8
Top = 56
Width = 75
Height = 25
Caption = '>>'
TabOrder = 2
OnClick = Button2Click
end
end
end
Kod: Tümünü seç
unit form_ana;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, unit_designer, ExtCtrls;
type
TFormAna = class(TForm)
Panel1: TPanel;
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
Designer:TDesigner;
end;
TDesignerCrack=class(TDesigner);
var
FormAna: TFormAna;
implementation
{$R *.dfm}
function ComponentToString(Component: TComponent): string;
var
BinStream:TMemoryStream;
StrStream: TStringStream;
s: string;
begin
BinStream := TMemoryStream.Create;
try
StrStream := TStringStream.Create(s);
try
BinStream.WriteComponent(Component);
BinStream.Seek(0, soFromBeginning);
ObjectBinaryToText(BinStream, StrStream);
StrStream.Seek(0, soFromBeginning);
Result:= StrStream.DataString;
finally
StrStream.Free;
end;
finally
BinStream.Free
end;
end;
function StringToComponent(Value: string;const Instance:TComponent=nil): TComponent;
var
StrStream:TStringStream;
BinStream: TMemoryStream;
begin
StrStream := TStringStream.Create(Value);
try
BinStream := TMemoryStream.Create;
try
ObjectTextToBinary(StrStream, BinStream);
BinStream.Seek(0, soFromBeginning);
Result := BinStream.ReadComponent(Instance);
finally
BinStream.Free;
end;
finally
StrStream.Free;
end;
end;
procedure TFormAna.FormCreate(Sender: TObject);
begin
ShowHint:=True;Hint:=ClassName;//test formu için
Designer:=DesignerLoad(Self);
with Designer.Ekle(TLabel) do begin
Name:='Label1';
Left:=20;
Top:=10;
end;
end;
procedure TFormAna.Button1Click(Sender: TObject);
begin
Designer:=TDesigner(StringToComponent(Memo1.Lines.Text));
Designer.Parent:=Self;
end;
procedure TFormAna.Button2Click(Sender: TObject);
begin
Memo1.Lines.Text:=ComponentToString(Designer);
end;
end.
Kod: Tümünü seç
Designer:=TDesigner(StringToComponent(Memo1.Lines.Text));
Designer.Parent:=Self;
Diyelimki tasarımlarınızı ComponentToString ve StringToComponent fonksiyonlarını kullanarak değil, direk dosyaya yazarak ve okuyarak kullanmak istiyorsunuz o zaman şu kodları önerebilirim.
Kod: Tümünü seç
procedure ComponentSaveToFile(Component: TComponent;const FileName:String);
var
FileStream:TFileStream;
begin
FileStream:=TFileStream.Create(FileName,fmCreate);
try
FileStream.WriteComponent(Component);
finally
FileStream.Free
end;
end;
function ComponentLoadFromFile(const FileName:String): TComponent;
var
FileStream:TFileStream;
begin
FileStream:=TFileStream.Create(FileName,fmOpenRead or fmShareDenyWrite);
try
Result := FileStream.ReadComponent(nil);
finally
FileStream.Free;
end;
end;


Tüm yazının amacı sadece bu ComponentSaveToFile prosedürü ve ComponentLoadFromFile fonksiyonu içindi. Her ne kadar basit gibi görünse de yukarıda belirttiğim 4 maddeye dikkat edilmezse işlem başarıyla gerçekleştirilemeyebilir. Konu ile ilgilenenlere yardımcı olabildiysem ne mutlu. Eğer atladığım, anlaşılmayan veya hatalı yerler varsa lütfen belirtin. Bu sayede bende birşeyler öğrenmeye devam ederim. Ayrıca bu yazı 2006'nın son günü ve bayramın ilk günü hediyem olsun. Böyle bir kaynak inşallah işinize yarar. Herkese çalışmalarında başarılar dilerim.