У меня есть форма, которая создаётся при запуске приложения. Эта форма всегда скрыта и пользователь её никогда не увидит. На этой форме в Design-Time я создаю всякие панели инструментов. Т.е. в привычном мне WYSIWYG режиме, я создаю панельки (или тулбары), на которых размещаю кнопки, комбобоксы и прочие компоненты. Панелька (тулбар) в этой форме ни с чем не связана, она нужна лишь для определения внешнего вида (Чтобы дальше было понятнее, назовём эту панель например так: pnlMyToolBar.) Ну и плюс к этому, я пишу класс-обёртку, которая будет работать с копией такой панельки. (Назовём для примера обёртку так: TMyToolbarWrapper.)
Затем, в любой из своих форм/фрейм я могу сделать так:
FToolBarWrapper := TMyToolbarWrapper.Create(Self); FToolBarWrapper.pnlToolBar.Align := alTop; FToolBarWrapper.pnlToolBar.Parent := Self; FToolBarWrapper.OnChange := OnToolbarChange;и т.д.
При этом в конструкторе TMyToolbarWrapper.Create происходит дублирование панели pnlMyToolBar, ну примерно таким вызовом:
constructor TMyToolbarWrapper.Create(AOwner: TComponent); begin inherited Create(AOwner); FpnlToolbar := TPanel(DuplicateComponents(pnlMyToolBar, Self, nil)); ... end;По большому счёту, можно обойтись и без дублирования компонентов, а просто создавать их в Run-Time в конструкторе обёртки. В простых случаях я так и делаю. Однако для сложных панелей очень удобно размещать компоненты именно в Design-Time - запустил приложение, попользовался, посмотрел как это выглядит в разных местах, закрыл приложение. Подвигал компоненты, что-то добавил/удалил - снова запускаю, смотрю и т.д.
Ещё один плюс, в сторону дублирования кмпонентов в Run-Time - это масштабирование. Например, если в OS логическое разрешение установлено в 120 dpi вместо обычных 96 dpi, то при создании моей скрытой формы, все находящиеся на ней компоненты автоматически промасштабируются (чего не произойдёт при создании компонентов в Run-Time). (Про масштабирование компонентов в Delphi (делфи это делает не всегда корректно) я планирую ещё написать.)
Прочитав всё это, Вы, возможно, подумаете: "Зачем так сложно? Ведь в делфи есть фреймы, разместил компоненты на фрейме, и вставляй эту фрейму куда душе угодно!".
На это у меня тоже есть отвтет. У фрейм есть возможность встраивать их в Design-Time. Да, это удобно. Но у этого удобства есть и обратная сторона медали. Приведу такой пример.
Есть фрейма, назовём её frameToolBar. Встраиваем её в форму formSomeTask (у встроенной фреймы будет имя frameToolBar1). Если подвигать (случайно) компоненты на frameToolBar1 (или изменить размеры), то ресурс этой фреймы сохранится в dfm-файл формы formSomeTask. И если в будущем что-то изменить на фрейме frameToolBar, то при открытии формы formSomeTask могут возникнуть конфликты (ресурс frameToolBar1 не соответствует родительскому ресурсу frameToolBar), вплоть до отказа работы формы. Вы наверняка с таким сталкивались, если работали с фреймами.
"Хорошо, встраивать фреймы в Design-Time не есть хорошо, но нам никто не запрещает встраивать их в Run-Time! Зачем нам дублирование компонентов?" - скажете Вы. Да, это так, но сам факт того, что фреймы можно встраивать в Design-Time балует программистов, и если себя ещё можно как-то дисциплинировать, то заставить это делать других программистов почти не реально. Поэтому в нашей компании я нашёл такой выход - дублирование компонентов в Run-Time.
P.S.: Кстати, если фреймы встраивать в Run-Time, то масштабирование не сработает - это баг Delphi (в Delphi 7 и ранних версиях, поздние версии я не проверял... обязательно напишу про масштабирование).
Вот код функции:
unit DelphiNotesDuplicate;
interface
uses
Classes;
// дублирование компонентов с явным заданием нового владельца и нового родителя
function DuplicateComponents(AComponent, NewOwner, NewParent: TComponent): TComponent; overload;
// дублирование компонентов, владелец и родитель останутся прежними
function DuplicateComponents(AComponent: TComponent): TComponent; overload;
procedure RegisterComponentClasses(AComponent: TComponent);
implementation
uses
SysUtils, Controls;
const
DupBufferSize = 4096; // 4K, это число используется в Classes.pas (Delphi 7)
type
TDuplicator = class(TObject)
private
FRefNames: TStringList;
FResult: TComponent;
procedure OnRead(Component: TComponent);
procedure OnSetName(Reader: TReader; Component: TComponent; var Name: string);
procedure OnReferenceName(Reader: TReader; var Name: string);
procedure WriteComponents(Stream: TStream; Root: TComponent);
procedure ReadComponents(Stream: TStream; Owner, Parent: TComponent);
public
destructor Destroy; override;
function Duplicate(Component, Owner, Parent: TComponent): TComponent;
end;
{ TDuplicator }
destructor TDuplicator.Destroy;
begin
FreeAndNil(FRefNames);
inherited Destroy;
end;
procedure TDuplicator.OnRead(Component: TComponent);
begin
// сохраняем ссылку на только что считанный компонент
// самым последним считывается компонент верхнего уровня
FResult := Component;
end;
procedure TDuplicator.OnSetName(Reader: TReader; Component: TComponent; var Name: string);
var
i: Integer;
Tmp: string;
begin
// добиваемся уникальности имени компонента
i := 0;
Tmp := Name;
while Component.Owner.FindComponent(Name) <> nil do
begin
Inc(i);
Name := Tmp + IntToStr(i);
end;
if Tmp <> Name then
begin
if not Assigned(FRefNames) then
begin
FRefNames := TStringList.Create;
FRefNames.CaseSensitive := True;
FRefNames.Duplicates := dupError;
FRefNames.Sorted := True;
FRefNames.NameValueSeparator := '=';
end;
FRefNames.Add(Tmp + '=' + Name);
end;
end;
procedure TDuplicator.OnReferenceName(Reader: TReader; var Name: string);
var
i: Integer;
begin
if Assigned(FRefNames) then
begin
i := FRefNames.IndexOfName(Name);
if i >= 0 then
Name := FRefNames.ValueFromIndex[i];
end;
end;
procedure TDuplicator.WriteComponents(Stream: TStream; Root: TComponent);
var
Writer: TWriter;
begin
Writer := TWriter.Create(Stream, DupBufferSize);
try
Writer.Root := Root.Owner;
Writer.WriteSignature;
Writer.WriteComponent(Root);
Writer.WriteListEnd;
finally
Writer.Free;
end;
end;
procedure TDuplicator.ReadComponents(Stream: TStream; Owner, Parent: TComponent);
var
Reader: TReader;
begin
Reader := TReader.Create(Stream, DupBufferSize);
try
Reader.OnSetName := OnSetName;
Reader.OnReferenceName := OnReferenceName;
Reader.ReadComponents(Owner, Parent, OnRead);
finally
Reader.Free;
end;
end;
function TDuplicator.Duplicate(Component, Owner, Parent: TComponent): TComponent;
var
Stream: TMemoryStream;
begin
FResult := nil;
// регистрация классов, необходима для случаев, когда новый владелец не знает о каком-то классе
RegisterComponentClasses(Component);
Stream := TMemoryStream.Create;
try
WriteComponents(Stream, Component);
Stream.Position := 0;
ReadComponents(Stream, Owner, Parent);
finally
Stream.Free;
end;
Result := FResult;
end;
{ /TDuplicator }
procedure RegisterComponentClasses(AComponent: TComponent);
var
i: Integer;
begin
RegisterClass(TPersistentClass(AComponent.ClassType));
if AComponent is TWinControl then
for i := 0 to TWinControl(AComponent).ControlCount - 1 do
RegisterComponentClasses(TWinControl(AComponent).Controls[i]);
end;
function DuplicateComponents(AComponent, NewOwner, NewParent: TComponent): TComponent;
begin
with TDuplicator.Create do
try
Result := Duplicate(AComponent, NewOwner, NewParent);
finally
Free;
end;
end;
function DuplicateComponents(AComponent: TComponent): TComponent;
var
NewOwner, NewParent: TComponent;
begin
NewOwner := AComponent.Owner;
if AComponent is TWinControl
then NewParent := TWinControl(AComponent).Parent
else NewParent := nil;
Result := DuplicateComponents(AComponent, NewOwner, NewParent);
end;
end.
Пример использования, на уровне формы:procedure TForm1.Button1Click(Sender: TObject); begin DuplicateComponents(Self, Self, nil); // дублируем форму end;
0 коммент.:
Отправить комментарий