【问题标题】:Pointer to a Component指向组件的指针
【发布时间】:2012-07-31 12:25:37
【问题描述】:

我有一个 PopupMenu 项目,其中 TMenuItem 标签根据调用它的按钮而变化。每个按钮都有自己的 TEdit(ResultTFile1、ResultTFile2 和 ResultTFile3)。这些 TEdit 是使用表单设计器创建的。

var
 TempResultFile : TEdit;
begin
 // Find the right TEdit
 case (Sender as TMenuItem).Tag of
  1: TempResultFile := ResultTFile1;
  2: TempResultFile := ResultTFile2;
  3: TempResultFile := ResultTFile3;
 end;

 // Call save function
 if (SaveDialog1.Execute) then TempResultFile.Text := SaveDialog1.FileName;
end;

但是,它不起作用。对话框打开,但在执行后,FileName 变量并未提供给真正的 TEdit.Text(ResultTFile1、ResultTFile2 和 ResultTFile3)。没有错误。什么都没有。调试器似乎没有反应——但是,我可能是错的。如果我很好理解这一点,那是因为该值是给 TempResultFile 而不是真正的 TEdit。如何以 TempResultFile 指向 到组件的方式对其进行编码?

【问题讨论】:

  • 您没有提供足够的信息。显示ResultTFile1 等的声明位置。并告诉我们“它不起作用”是什么意思。那是永远不够的信息。需要完整的错误消息和引发错误的代码点。
  • 好的,没有错误,什么都没有。文件对话框是否显示?在调试器下运行代码会发生什么?走什么路?
  • TempResultFile 确实指向该组件。对象引用就是这样,一个引用。将 if/then 语句分成两行,以便检查 SaveDialog1.Execute 是否返回 True 或 False。在调试器下执行此操作。
  • 一些调试逻辑:当对话框出现(因此例程运行)并且没有填充编辑时,那么,给定上面的代码,唯一可以得出的结论是SaveDialog1.FileName 为空或 SaveDialog1.Execute 返回 False。否则TempResultFile 不应指向任何会导致访问冲突的编辑。
  • @Arioch'The:这里没有理由启用调试 DCU。只需在调试器中按原样单步执行代码就会显示问题;完全没有必要进入 VCL/RTL。

标签: delphi pointers reference components


【解决方案1】:

正如其他人所说,您显示的代码只有这么多可能出错的地方:

1) TMenuItem.Tag 可能包含错误的值。

2) TempResultFile 可能未分配有效的 TEdit 指针。不管其他人怎么说,保持变量未初始化并不能保证会发生访问冲突,尽管它很可能发生。还有一种可能是,如果TEdit 没有正确创建或已被释放,则分配的指针可能为零。如果您尝试使用它会导致 AV。

3) SaveDialog1.Execute() 可能返回 False。如果您取消对话框,就会发生这种情况,但如果对话框有内部错误,也会发生这种情况。在某些情况下,您可以使用CommDlgExtendedError() 来检查该情况。

4) SaveDialog1.FileName 为空,如果 SaveDialog1.Execute() 返回 true,则不会发生这种情况,但是如果您使用相当现代的 Delphi 版本,在 Windows Vista 或更高版本上运行您的应用程序,并选择非文件系统文件。

在调试期间,请确保检查所有这些条件,例如:

var         
  Item: TMenuItem;
  TempResultFile : TEdit;         
  S: String;
begin         
  Item := Sender as TMenuItem;

  case Item.Tag of         
    1: TempResultFile := ResultTFile1;         
    2: TempResultFile := ResultTFile2;         
    3: TempResultFile := ResultTFile3;         
  else
    raise Exception.CreateFmt('%s.Tag (%d) is not an expected value!', [Item.Name, Item.Tag]);
  end;         

  if TempResultFile = nil then
    raise Exception.Create('TempResultFile is nil!');

  if not SaveDialog1.Execute then
    raise Exception.CreateFmt('SaveDialog1.Execute returned false! Possible CommDlg error? (%d)', [CommDlgExtendedError()]);

  S := SaveDialog1.FileName;
  if S = '' then
    raise Exception.Create('SaveDialog1.FileName is empty!');

  TempResultFile.Text := S;
end;   

作为使用TMenuItem.Tag 的替代方法,TPopupMenu.PopupComponent 属性将告诉您哪个按钮显示了PopupMenu。您可以将TButton.Tag 属性设置为指向与该按钮对应的TEdit 组件,然后您不必再使用TMenuItem.Tag 属性来寻找TEdit 组件,例如:

procedure TForm1.FormCreate(Sender: TObject);
begin
  ResultTButton1.Tag := NativeInt(ResultTFile1);
  ResultTButton2.Tag := NativeInt(ResultTFile2);
  ResultTButton3.Tag := NativeInt(ResultTFile3);
end;

procedure TForm1.MenuItemClick(Sender: TObject);
var 
  ResultTButton : TButton; 
  TempResultFile : TEdit; 
begin 
  ResultTButton := PopupMenu.PopupComponent as TButton; 
  TempResultFile := TEdit(ResultTButton.Tag); 
  if TempResultFile <> nil then begin
    if SaveDialog1.Execute then
      TempResultFile.Text := SaveDialog1.FileName; 
  end;
end; 

【讨论】:

  • -1 这也没有解释Q中的代码有什么问题
  • 显然您不喜欢 VanillaH 的问题,但请不要再否决每个人的答案!你现在惹怒了所有人。这是本网站不可接受的行为。
  • 我的代码可能无法为原始问题提供解决方案,但它是一种可行的替代方案,它不需要为它可以查看的可能的 TEdit 控件对事件处理程序进行硬编码。跨度>
  • “我的代码可能无法解决原始问题”这就是我的观点。我同意您对 PopupComponent 的使用比 Q 中的方法更好。但是答案是为了回答问题。正如您自己所说,这不是。对不起,但如果这不值得一票否决,那是什么?与其生气,不如纠正你的答案。
  • @David,也许因为每个人都不同意你,你应该退后一步,看看你在这里采取的荒谬立场。我们明白了 - 问题的文本不符合您的批准。这不会使帮助提问者的尝试自动出错。你不仅给这里的新用户留下了负面印象,而且你对每个真正试图帮助解决方案的人都刻薄。也许你应该重新审视你的态度。 (我不同意 Remy 的回答,因为这张海报对 Delphi 来说显然是新的,我认为这个解决方案有点先进,但没有反对意见。)
【解决方案2】:

为了调试您的代码并找出问题所在,请完全更改您的代码 - cmets 解释了每个代码如何帮助您解决问题。

var
  TempResultFile : TEdit;
begin
  if SaveDialog1.Execute then    // Only try assignment if file chosen
  begin
    // Find the right TEdit
    // The default branch will be used if the wrong menu item is 
    // connected to the event, or you forgot to set the tag value
    case (Sender as TMenuItem).Tag of
      1: TempResultFile := ResultTFile1;
      2: TempResultFile := ResultTFile2;
      3: TempResultFile := ResultTFile3;
    else                     // If none of the above match
      raise Exception.CreateFmt('Invalid tag %d in %s', 
         [TMenuItem(Sender).Tag, TMenuItem(Sender).Name]));
    end;
    // At this point, we know TempResultFile has been assigned a value
    TempFileResult.Text := SaveDialog1.FileName;
  end
  else // User clicked cancel on SaveDialog1.
    ShowMessage('SaveDialog1.Execute returned False`);
end;

这有几件事情会帮助你:

  • 如果SaveDialog 没有出现,您就知道您没有为菜单项分配 OnClick 处理程序。

  • 如果用户在SaveDialog 上选择Cancel,它将返回false,这将显示一个对话框来指示该事实(以消除该问题)。

  • 1234563被点击的组件的名称)。

此外,它比原始问题中的代码更安全,如果标记不是 case 语句中的值之一并且 SaveDialog1.Execute 返回 true,则可能导致访问冲突。 (当您访问 Text 属性时,可能没有分配给 TempResultFile 的任何内容。)

一旦你弄清楚你做错了什么,你可以删除最后的

end
else 
  ShowMessage('SaveDialog1.Execute returned False`);

【讨论】:

  • @David,这是完全不同的代码,将识别发布者使用自己的代码遇到的问题。这比发布关于质量差的问题的 cmets 和对提供解决方案的有效尝试投反对票要好。您已经发布了数十个质量低于此问题的问题的答案,并收集了数万个代表点。
  • 不,代码的所有意图和目的都与 Q 中的相同。如果没有分配 TEdit 本地,那么就会有一个 AV。如果 Execute 返回 False,则用户在对话框中单击了 Cancel。这个问题没有意义。这不是一个真正的问题。添加大量虚假答案无济于事。
  • @David:首先,这段代码不一样。它只在SaveDialog.Execute返回true时尝试读取标签并进行赋值,如果返回false则通知用户;这有助于将问题识别为最后一行(原始代码)未执行的问题。如果返回为真,它会处理不同菜单项或忘记Tag 分配的情况(异常)。第三,它通过确保对TempFileResult 的分配实际上已经完成(在case 语句中使用默认值)来防止任何AV。这是完全不同的。没关系。把自己打晕。
  • 由于没有 AV,显然分配了局部变量。根据 cmets 的说法,没有 AV 可以预防。我们无法想象用户正在单击取消。因此 Q 中的代码与报告的症状不匹配。正确的行动方案是接近的,而不是真正的问题。
  • 这意味着在原始代码中,如果 SaveDialog 返回 false,则无法进行分配并且用户不会知道,这正是我所指出的完全现在给你两次。任何。这个问题显然存在问题 - 大卫没有批准它的内容,因此所有试图提供帮助的尝试都必须受到惩罚,直到发布者遵守。你不喜欢这个问题,投反对票 it 而不是惩罚试图提供帮助的人。
【解决方案3】:

你可以这样写:

begin
  if (SaveDialog1.Execute) then 
  begin
    case (Sender as TMenuItem).Tag of    
       1: ResultTFile1.text := SaveDialog1.FileName;    
       2: ResultTFile2.text := SaveDialog1.FileName;    
       3: ResultTFile3.text := SaveDialog1.FileName;   
    end;  
  end; 
end;

【讨论】:

  • -1 这相当于问题中的代码。暂时无法回答这个问题。
【解决方案4】:

嗯...实际上只有一个原因导致您的代码无法正常工作:没有分配 TempResultFile,这意味着“case”语句无法找到匹配项。要么:

  • 发件人不是 TMenuItem 的实例
  • Sender 不是您期望的 TMenuItem 实例
  • 发件人是您的 TMenuItem 但您没有正确分配标签

每个案例都非常容易验证。开始工作吧!

哦,顺便说一句,适当的防御风格会将代码重写为:

var
 TempResultFile : TEdit;
begin
 {$IFDEF DEBUG}
 // preconditions
 Assert(sender is TMenuItem) ;
 {$ENDIF}
 // Find the right TEdit
 case (Sender as TMenuItem).Tag of
     1: TempResultFile := ResultTFile1;
     2: TempResultFile := ResultTFile2;
     3: TempResultFile := ResultTFile3;
     else Assert(false,'Failed to locate the proper TEdit') ;
 end;

 // Call save function
 if (SaveDialog1.Execute) then 
     TempResultFile.Text := SaveDialog1.FileName;

结束;

【讨论】:

  • (1) (Sender as TMenuItem) 会引发 AV,(2) 对话框不会打开,(3),访问未实例化的 TempResultFile 的 Text 会引发 AV。
  • -1 原因是 sertac 列出的。这个 Q 让我失去了很多代表!
  • Sertac/David:好吧,好吧,随便。 HTH 你知道分配 NIL TempResultFile 时没有引发 AV 吗?心理调试能力?因为最初的问题没有表明外部全局异常处理程序是否到位,IDE 是否设置为中断所有异常或只是未处理的异常等。我和肯一起 - 拒绝尝试帮助解决问题是荒谬的这个错误的请求。大卫:你说得对,这个问题让你付出了很多代价,因为你处理这个特定问题的方式让我失去了对你的尊重。
  • 很遗憾你这样反应。我没有评价你的答案并且投了反对票。这就是它的工作方式。如果问题被错误地提出,那么最好的回应是要求澄清。它没有来,然后投票关闭。同样,这就是 SO 的工作原理。
  • > ".. 当 NIL TempResultFile .." > 这有什么关系?
猜你喜欢
  • 2021-02-07
  • 1970-01-01
  • 2016-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多