Как сделать подкомпоненты TAction доступными во время разработки?
В своем пользовательском компоненте я создал несколько TAction-ов в качестве подкомпонентов. Все они опубликованы, но я не мог назначить их во время разработки, так как они не были доступны через инспектор объектов.
Как сделать их "повторяемыми" инспектором объектов? Я пытался установить владельца действий для владельца пользовательского компонента (который является формой хостинга), но безуспешно.
РЕДАКТИРОВАТЬ: похоже, Embarcadero изменил поведение Delphi IDE, связанные с этой проблемой. Если вы используете версии Delphi до XE, вы должны использовать решение из моего собственного ответа. Для XE и выше, вы должны использовать решение от Крейга Петерсона.
РЕДАКТИРОВАТЬ: я добавил свой собственный ответ, который решает проблему, т. Е. Путем создания экземпляра TCustomActionList в моем пользовательском компоненте и установки его владельца на форму размещения (владелец пользовательского компонента). Однако я не слишком доволен этим решением, так как я думаю, что экземпляр TCustomActionList отчасти избыточен. Поэтому я все еще надеюсь получить лучшее решение.
РЕДАКТИРОВАТЬ: Добавить пример кода
uses
.., ActnList, ..;
type
TVrlFormCore = class(TComponent)
private
FCancelAction: TBasicAction;
FDefaultAction: TBasicAction;
FEditAction: TBasicAction;
protected
procedure DefaultActionExecute(ASender: TObject); virtual;
procedure CancelActionExecute(ASender: TObject); virtual;
procedure EditActionExecute(ASender: TObject); virtual;
public
constructor Create(AOwner: TComponent); override;
published
property DefaultAction: TBasicAction read FDefaultAction;
property CancelAction : TBasicAction read FCancelAction;
property EditAction : TBasicAction read FEditAction;
end;
implementation
constructor TVrlFormCore.Create(AOwner: TComponent);
begin
inherited;
FDefaultAction := TAction.Create(Self);
with FDefaultAction as TAction do
begin
SetSubComponent(True);
Caption := 'OK';
OnExecute := DefaultActionExecute;
end;
FCancelAction := TAction.Create(Self);
with FCancelAction as TAction do
begin
SetSubComponent(True);
Caption := 'Cancel';
OnExecute := Self.CancelActionExecute;
end;
FEditAction := TAction.Create(Self);
with FEditAction as TAction do
begin
SetSubComponent(True);
Caption := 'Edit';
OnExecute := Self.EditActionExecute;
end;
end;
3 ответа
Насколько я могу сказать, вы не должны делать это таким образом.
Самый простой способ сделать то, что вы хотите, это создать новые автономные действия, которые могут работать с любым TVrlFormCore
компонент и установить целевой объект в HandlesTarget
Перезвоните. Посмотрите в StdActns.pas для примеров. Действия не будут доступны автоматически, когда sommeone удалит ваш компонент в форме, но они могут добавить их в свой список действий вручную с помощью команды New Standard Actions.... Здесь есть хорошая статья о регистрации стандартных действий.
Если вы действительно хотите автоматически создавать действия, вам нужно установить действие Owner
свойство формы, и вам нужно установить Name
имущество. Это все, что необходимо, но это создает кучу проблем, которые нужно обойти:
- Форма владеет действиями, поэтому она добавит их в опубликованный раздел своей декларации и автоматически создаст их как часть процесса потоковой передачи. Чтобы обойти это, вы можете просто отключить потоковую передачу, переписав действие
WriteState
метод и пропустить унаследованное поведение. - Поскольку вы не пишете состояние, ни одно из свойств не будет сохранено. Чтобы не вводить пользователей в заблуждение, вы должны переключиться, чтобы действия происходили из
TCustomAction
вместоTAction
так что ничего не разоблачает. Может быть способ правильно создать поток действий, но вы не сказали, было ли это необходимо. - Вам нужно зарегистрироваться для бесплатных уведомлений на случай, если форма освободит действие, прежде чем вы сможете.
- Если кто-то добавит более одного вашего компонента в имена действий, произойдет конфликт. Есть несколько способов справиться с этим, но самым чистым, вероятно, было бы переопределение метода SetName компонента и использование его имени в качестве префикса для имен действий. Если вы сделаете это, вам нужно использовать RegisterNoIcon с новым классом, чтобы они не отображались в форме.
- В панели структуры IDE действия будут отображаться непосредственно под формой, а не вложенными, как показано в ActionList. Я не нашел способ обойти это; ни один из
SetSubComponent
,GetParentComponent
/HasParent
, или жеGetChildren
иметь какой-либо эффект, так что это может быть жестко запрограммированное поведение. Вы можете удалить действие из панели структуры, отдельно от компонента.
Я уверен, что это может быть улучшено, но это работает без каких-либо пользовательских редакторов свойств:
type
TVrlAction = class(TCustomAction)
protected
procedure WriteState(Writer: TWriter); override;
end;
TVrlFormCore = class(TComponent)
private
FDefaultAction: TVrlAction;
protected
procedure DefaultActionExecute(ASender: TObject); virtual;
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
procedure SetName(const NewName: TComponentName); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
public
property DefaultAction: TVrlAction read FDefaultAction;
end;
procedure Register;
implementation
// TVrlAction
procedure TVrlAction.WriteState(Writer: TWriter);
begin
// No-op
end;
// TVrlFormCore
constructor TVrlFormCore.Create(AOwner: TComponent);
begin
inherited;
FDefaultAction := TVrlAction.Create(AOwner);
with FDefaultAction do
begin
FreeNotification(Self);
Name := 'DefaultAction';
Caption := 'OK';
OnExecute := DefaultActionExecute;
end;
end;
destructor TVrlFormCore.Destroy;
begin
FDefaultAction.Free;
inherited;
end;
procedure TVrlFormCore.DefaultActionExecute(ASender: TObject);
begin
end;
procedure TVrlFormCore.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited;
if Operation = opRemove then
if AComponent = FDefaultAction then
FDefaultAction := nil;
end;
procedure TVrlFormCore.SetName(const NewName: TComponentName);
begin
inherited;
if FDefaultAction <> nil then
FDefaultAction.Name := NewName + '_DefaultAction';
end;
procedure Register;
begin
RegisterComponents('Samples', [TVrlFormCore]);
RegisterNoIcon([TVrlAction]);
end;
РЕДАКТИРОВАТЬ: Используйте это решение для версий Delphi до Delphi XE. Для XE и более поздних версий используйте ответ Крейга Петерсона (для которого не требуется избыточный экземпляр TCustomActionList).
После вмешательства и использования информации из ответа Крейга Петерсона я решил создать TCustomActionList в своем пользовательском компоненте. Пока что это единственный способ получить список действий в Object Inspector.
Вот код:
uses
..., ActnList, ...;
type
TVrlAction=class(TCustomAction)
protected
procedure WriteState(Writer: TWriter); override;
published
property Caption;
end;
TVrlActionList=class(TCustomActionList)
protected
procedure WriteState(Writer: TWriter); override;
end;
TVrlFormCore = class(TVrlItemSource)
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure SetName(const NewName: TComponentName); override;
public
constructor Create(AOwner: TComponent); override;
end;
implementation
{ TVrlAction }
procedure TVrlAction.WriteState(Writer: TWriter);
begin
end;
{ TVrlActionList }
procedure TVrlActionList.WriteState(Writer: TWriter);
begin
end;
{ TVrlFormCore }
constructor TVrlFormCore.Create(AOwner: TComponent);
begin
inherited;
FActions := TVrlActionList.Create(AOwner);
FDefaultAction := TVrlAction.Create(AOwner);
with FDefaultAction as TVrlAction do
begin
FreeNotification(Self);
Caption := 'OK';
OnExecute := DefaultActionExecute;
end;
FActions.AddAction(TContainedAction(FDefaultAction));
FCancelAction := TVrlAction.Create(AOwner);
with FCancelAction as TVrlAction do
begin
FreeNotification(Self);
Caption := 'Cancel';
OnExecute := Self.CancelActionExecute;
end;
FActions.AddAction(TContainedAction(FCancelAction));
FEditAction := TVrlAction.Create(AOwner);
with FEditAction as TVrlAction do
begin
FreeNotification(Self);
Caption := 'Edit';
OnExecute := Self.EditActionExecute;
end;
FActions.AddAction(TContainedAction(FEditAction));
end;
procedure TVrlFormCore.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited;
if Operation=opRemove then
begin
if AComponent = FMaster then
FMaster := nil
else if (AComponent is TVrlFormCore) then
FDetails.Remove(TVrlFormCore(AComponent))
else if AComponent=FDefaultAction then
FDefaultAction := nil
else if AComponent=FCancelAction then
FCancelAction := nil
else if AComponent=FEditAction then
FEditAction := nil;
end;
end;
procedure TVrlFormCore.SetName(const NewName: TComponentName);
begin
inherited;
if FActions<>nil then
FActions.Name := NewName + '_Actions';
if FDefaultAction <> nil then
FDefaultAction.Name := NewName + '_DefaultAction';
if FCancelAction <> nil then
FCancelAction.Name := NewName + '_CancelAction';
if FEditAction <> nil then
FEditAction.Name := NewName + '_EditAction';
end;
Вы не можете назначить их, потому что они предназначены только для чтения:
property DefaultAction: TBasicAction read FDefaultAction;
property CancelAction : TBasicAction read FCancelAction;
property EditAction : TBasicAction read FEditAction;
Вы должны изменить интерфейс вашего класса на:
property DefaultAction: TBasicAction read FDefaultAction write FDefaultAction;
property CancelAction : TBasicAction read FCancelAction write FCancelAction;
property EditAction : TBasicAction read FEditAction write FEditAction;
или напишите соответствующий сеттер для каждого действия.
Редактировать:
То, что вам нужно, то
реализовать ваши 3 пользовательских действия в качестве предопределенных действий (см.
StdActns.pas
для образцов).зарегистрировать их по телефону
ActnList.RegisterActions
, (См. Документацию RAD Studio)добавить в форму
TActionList
и / илиTActionManager
чтобы вашPredefined Actions
появляются в списке предопределенных действий в редакторе списка действий каждого потомка TControl.
Вы можете сделать обширный поиск в Google по этой теме и найти конкретный пример.