ModalResult 不在组件内部起作用
Posted
技术标签:
【中文标题】ModalResult 不在组件内部起作用【英文标题】:ModalResult not acting inside of a component 【发布时间】:2017-03-12 10:48:16 【问题描述】:您可以在下面看到一个组件的代码,该组件在 TPersistent 类中允许我分配一些 TCustomButtons(TButton 或 TBitBtn)。
我将我的组件放在一个模态表单上,并分配了 2 个按钮(确定和取消)。 通常,当我按下任何这些按钮时,我的表单应该会关闭。
我的问题是为什么表单没有关闭?
type
TMyComp = class;
TButtons = class;
TMyComp = class(TComponent)
private
FButtons: TButtons;
procedure SetButtons(Value: TButtons);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Buttons: TButtons read FButtons write SetButtons;
end;
TButtons = class(TPersistent)
private
FOwner: TMyComp;
FBtnOk: TCustomButton;
FBtnCancel: TCustomButton;
procedure SetCustomButton(Index: Integer; Value: TCustomButton);
procedure BtnOkOnClick(Sender: TObject);
procedure BtnCancelOnClick(Sender: TObject);
protected
public
constructor Create(AOwner: TMyComp); virtual;
procedure Assign(Source: TPersistent); override;
published
property BtnOk: TCustomButton index 0 read FBtnOk write SetCustomButton;
property BtnCancel: TCustomButton index 1 read FBtnCancel write SetCustomButton;
end;
implementation
constructor TMyComp.Create(AOwner: TComponent);
begin
inherited;
FButtons:= TButtons.Create(Self);
end;
destructor TMyComp.Destroy;
begin
FButtons.Free;
inherited;
end;
//------- TButtons ---------
constructor TButtons.Create(AOwner: TMyComp);
begin
inherited Create;
FOwner:= AOwner;
end;
procedure TButtons.Assign(Source: TPersistent);
begin
if Source is TButtons then
begin
FBtnOk:= TButtons(Source).BtnOk;
FBtnCancel:= TButtons(Source).BtnCancel;
end
else
inherited Assign(Source);
end;
procedure TButtons.SetCustomButton(Index: Integer; Value: TCustomButton);
begin
case Index of
0: if FBtnOk <> Value then
begin
FBtnOk:= Value;
if Assigned(FBtnOk) then
begin
//TBitBtn
if (FBtnOk is TBitBtn) then
(FBtnOk as TBitBtn).OnClick:= BtnOkOnClick;
//TButton
if (FBtnOk is TButton) then
(FBtnOk as TButton).OnClick:= BtnOkOnClick;
end;
end;
1: if FBtnCancel <> Value then
begin
FBtnCancel:= Value;
if Assigned(FBtnCancel) then
begin
//TBitBtn
if (FBtnCancel is TBitBtn) then
(FBtnCancel as TBitBtn).OnClick:= BtnCancelOnClick;
//TButton
if (FBtnCancel is TButton) then
(FBtnCancel as TButton).OnClick:= BtnCancelOnClick;
end;
end;
end;
if Assigned(Value) then Value.FreeNotification(FOwner);
end;
procedure TButtons.BtnCancelOnClick(Sender: TObject);
begin
showmessage('Cancel pressed!');
if Sender is TButton then
(Sender as TButton).ModalResult:= mrCancel;
if Sender is TBitBtn then
(Sender as TBitBtn).ModalResult:= mrCancel;
end;
procedure TButtons.BtnOkOnClick(Sender: TObject);
begin
//do some input validations here...
showmessage('Ok pressed!');
if Sender is TButton then
(Sender as TButton).ModalResult:= mrOk;
if Sender is TBitBtn then
(Sender as TBitBtn).ModalResult:= mrOk;
end;
【问题讨论】:
MCVE 意味着我们知道您的代码是什么。为什么要为按钮设置事件处理程序。为什么不设置 ModalResult。不过,这一切看起来有点粗略。 【参考方案1】:其他答案/cmets 已经解释了为什么代码不起作用 - 您设置按钮的 ModalResult
为时已晚,因此当您期望它不会传播到表单的 ModalResult
时。
我想建议一个替代实现,它也包含一个解决方案,并解决您的代码缺少的其他一些问题:
type
TButtons = class;
TMyComp = class(TComponent)
private
FButtons: TButtons;
procedure SetButtons(Value: TButtons);
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Buttons: TButtons read FButtons write SetButtons;
end;
TButtons = class(TPersistent)
private
FOwner: TMyComp;
FButtons: array[0..1] of TCustomButton;
FClickEvents: array[0..1] of TNotifyEvent;
function GetCustomButton(Index: Integer): TCustomButton;
procedure SetCustomButton(Index: Integer; Value: TCustomButton);
procedure BtnOkOnClick(Sender: TObject);
procedure BtnCancelOnClick(Sender: TObject);
public
constructor Create(AOwner: TMyComp);
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
published
property BtnOk: TCustomButton index 0 read GetCustomButton write SetCustomButton;
property BtnCancel: TCustomButton index 1 read GetCustomButton write SetCustomButton;
end;
implementation
//------- TMyComp ---------
constructor TMyComp.Create(AOwner: TComponent);
begin
inherited;
FButtons := TButtons.Create(Self);
end;
destructor TMyComp.Destroy;
begin
FButtons.Free;
inherited;
end;
procedure TMyComp.Notification(AComponent: TComponent; Operation: TOperation);
var
i: Index;
begin
inherited;
if Operation = opRemove then
begin
for i := Low(FButtons.FButtons) to High(FButtons.FButtons) do
begin
if AComponent = FButtons.FButtons[i] then
begin
FButtons.FButtons[i] := nil;
FButtons.FClickEvents[i] := nil;
Exit;
end;
end;
end;
end;
//------- TButtons ---------
constructor TButtons.Create(AOwner: TMyComp);
begin
inherited Create;
FOwner := AOwner;
end;
constructor TButtons.Destroy;
begin
Assign(nil);
inherited;
end;
procedure TButtons.Assign(Source: TPersistent);
var
i: Integer;
begin
if Source = nil then
begin
for i to Low(FButtons) to High(FButtons) do
SetCustomButton(i, nil);
end
else if Source is TButtons then
begin
for i to Low(FButtons) to High(FButtons) do
SetCustomButton(i, TButtons(Source).FButtons[i]);
end
else
inherited Assign(Source);
end;
function TButtons.GetCustomButton(Index: Integer): TCustomButton;
begin
Result := FButtons[Index];
end;
type
TCustomButtonAccess = class(TCustomButton)
end;
procedure TButtons.SetCustomButton(Index: Integer; Value: TCustomButton);
begin
if FButtons[Index] <> Value then
begin
if Assigned(FButtons[Index]) then
begin
TCustomButtonAccess(Value).OnClick := FClickEvents[Index];
FClickEvents[Index] := nil;
FButtons[Index].RemoveFreeNotification(FOwner);
end;
FButtons[Index] := Value;
if Assigned(Value) then
begin
Value.FreeNotification(FOwner);
FClickEvents[Index] := TCustomButtonAccess(Value).OnClick;
case Index of
0: TCustomButtonAccess(Value).OnClick := BtnOkOnClick;
1: TCustomButtonAccess(Value).OnClick := BtnCancelOnClick;
end;
end;
end;
end;
procedure TButtons.BtnOkOnClick(Sender: TObject);
var
Form: TCustomForm;
begin
//do some input validations here...
ShowMessage('Ok pressed!');
Form := GetParentForm(TControl(Sender));
if Form <> nil then
Form.ModalResult := mrOk;
// optional
if Assigned(FClickEvents[0]) then
FClickEvents[0](Sender);
end;
procedure TButtons.BtnCancelOnClick(Sender: TObject);
var
Form: TCustomForm;
begin
ShowMessage('Cancel pressed!');
Form := GetParentForm(TControl(Sender));
if Form <> nil then
Form.ModalResult := mrCancel;
// optional
if Assigned(FClickEvents[1]) then
FClickEvents[1](Sender);
end;
【讨论】:
GetParentForm(TControl(Sender))
这就是我要找的……干得好雷米!最初我尝试使用GetParentForm(Self)
,但编译器要求我提供TControl
。所以就像一个解决方案,根据你的回答,我刚刚使用了这个代码:form:= GetParentForm(TControl(Sender)); if form <> nil then form.ModalResult:= mrOk;
【参考方案2】:
在您的事件处理程序中,您正在设置按钮的ModalResult
。当 该表单的 ModalResult
设置时,模式表单将关闭。
button's ModalResult
的目的是框架将 父表单的 ModalResult
设置为相同的值。
设置按钮组件的 ModalResult 属性是一种使单击按钮关闭模式表单的简单方法。 单击按钮时,其父窗体的 ModalResult 属性设置为与按钮的 ModalResult 属性相同的值。 例如,如果一个对话框有 OK 和 Cancel 按钮,它们的 ModalResult 属性可以在设计时分别设置为 mrOk 和 mrCancel。在运行时,单击 OK 按钮然后将对话框的 ModalResult 属性更改为 mrOk,然后单击 Cancel 按钮将对话框的 ModalResult 属性更改为 mrCancel。 除非需要进一步处理,否则按钮不需要 OnClick 事件处理程序。
所以,基本上你做错的是你没有预定义按钮的ModalResult
。所以:
ModalResult
仍为mrNone
。
因此不会更改表单的ModalResult
。
(我怀疑如果您再次单击该按钮,它的行为会如您所愿。)
要解决您的问题,您有两种选择:
最好尽快设置按钮的ModalResult
。没有理由让“取消”按钮拥有ModalResult <> mrCancel
。这就是它通常在 DFM 中流式传输的原因。
如果你绝对必须使用事件处理程序;设置表单的 ModalResult
。
【讨论】:
【参考方案3】:一个更简单的复制案例可以是这样的:
创建一个包含两个表单的应用程序,从自动创建的表单列表中删除第二个表单。使用以下点击处理程序在每个表单上添加一个按钮。
在单元1中:
procedure TForm1.Button1Click(Sender: TObject);
var
F: TForm;
begin
F := TForm2.Create(nil);
try
F.ShowModal;
finally
F.Free;
end;
end;
在单元2中:
procedure TForm2.Button1Click(Sender: TObject);
begin
Button1.ModalResult := mrOk;
end;
运行应用程序,按下按钮启动模态表单。在第二个表单上按下按钮,表单不会关闭。
表单没有关闭的原因是按钮的点击处理程序来不及设置表单的模态结果。
你可以从'Vcl.StdCtrls.pas'中TCustomButton.Click;
的代码中看出为什么为时已晚。评论是我的。
procedure TCustomButton.Click;
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if Form <> nil then Form.ModalResult := ModalResult; // this is where modal result is checked
inherited Click; // this is where your click handler is run
end;
您会注意到第二次单击会关闭表单。那是因为按钮的模态结果在第一次点击后就已经设置好了。
【讨论】:
奇怪的想法是,在过去我用过很多次,它的工作原理!但是代替Button1.ModalResult := mrOk
,我一直使用ModalResult := mrOk
,它可以工作。
无论如何,当我SetCustomButton
时,我可以为按钮分配一个ModalResult
值。但是我要进行输入验证,如果验证失败,那么我需要保持表单打开......那么我该如何使用我的组件结构来做到这一点?
@real - 当然,按钮的模态结果所做的是设置表单的模态结果。您可以随时设置表单的模态结果。您可以在我的回答中的最后一个 sn-p 中查看如何访问父表单。当我注意到您明确询问“为什么表单没有关闭”时,我从我的 answer 中删除了该信息。
你可能想问一个关于测试用例的第二个问题的问题。
您的部分回答让我了解ModalResult
的行为方式,但它并没有解决我的问题。正如我在评论中解释的那样,GetParentForm(Self)
它在组件下不起作用(编译器无法识别自我)。 GetParentForm(TControl(Sender))
在这种情况下是最终解决方案。以上是关于ModalResult 不在组件内部起作用的主要内容,如果未能解决你的问题,请参考以下文章