I'm learning Delphi, and I have a problem to show a dialog when I´m calling a private function which does the logic. It seems like an null pointer reference but I cannot find where it is.
Here is the code:
unit SandBox;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
AhojButton: TButton;
procedure AhojButtonClick(Sender: TObject);
private
procedure ShowDialog(amount: Integer);
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ShowDialog(amount: Integer);
var td: TTaskDialog;
var tb: TTaskDialogBaseButtonItem;
begin
try
td := TTaskDialog.Create(nil);
tb := TTaskDialogBaseButtonItem.Create(nil);
td.Caption := 'Warning';
td.Text := 'Continue or Close?';
td.MainIcon := tdiWarning;
td.CommonButtons := [];
tb := td.Buttons.Add;
tb.Caption := 'Continue';
tb.ModalResult := 100;
tb := td.Buttons.Add;
tb.Caption := 'Close';
tb.ModalResult := 101;
td.Execute;
if td.ModalResult = 100 then
ShowMessage('Continue')
else if td.ModalResult = 101 then
ShowMessage('Close');
finally
td.Free;
tb.Free;
end;
end;
procedure TForm1.AhojButtonClick(Sender: TObject);
begin
ShowDialog(100);
end;
end.
I tried to instantiate and free both TTaskDialog
and TTaskDialogBaseButtonItem
.
This line throws the error:
tb := TTaskDialogBaseButtonItem.Create(nil);
You do indeed have a nil
pointer dereference, and you can see the nil
right in the line of code that you mentioned:
tb := TTaskDialogBaseButtonItem.Create(nil);
^^^
TTaskDialogBaseButtonItem
is a TCollectionItem
descendant. Its constructor takes a TCollection
as input, but you are not passing one in. And although TCollectionItem
itself does not require a TCollection
, TTaskDialogBaseButtonItem
does, as you can see in its constructor:
constructor TTaskDialogBaseButtonItem.Create(Collection: TCollection);
begin
inherited;
FCaption := '';
FClient := TCustomtaskDialog(Collection.Owner); // <-- HERE
FEnabled := True;
FModalResult := ID + 100; // Avoid mrNone..mrYesToAll and IDOK..IDCONTINUE
end;
As you can see, the TCollection
cannot be nil
!
If you compile your project with Debug DCUs enabled, and then run your code inside the debugger, then the debugger would have taken you right to this line of code when the AV occurs.
So, to fix the error, you might think to pass in the dialog's Buttons
collection, eg:
tb := TTaskDialogBaseButtonItem.Create(td.Buttons);
But this will throw a different error at runtime:
Invalid property value
This is because the Buttons
collection actually expects TTaskDialogButtonItem
, which is a descendant of TTaskDialogBaseButtonItem
. So, change the Create
call accordingly:
tb := TTaskDialogButtonItem.Create(td.Buttons);
Now, the code will run without crashing. However, your dialog will have a 3rd unwanted button in it!
Your code is also creating a memory leak, because you are assigning your tb
variable to point at the button object that you create above, and then you re-assign tb
to point at a different button object which you ask the dialog to create, but you never free the button that you create manually:
tb := TTaskDialogButtonItem.Create(td.Buttons); // <-- leaked!
...
tb := td.Buttons.Add;
...
tb.Free; // <-- frees a button you don't own!
You don't need to create an actual button object just to declare a variable that will point at button objects. Let the dialog create the actual button objects for you, your tb
variable can merely point at them.
Try this instead:
procedure TForm1.ShowDialog(amount: Integer);
var
td: TTaskDialog;
tb: TTaskDialogBaseButtonItem;
begin
td := TTaskDialog.Create(nil);
try
td.Caption := 'Warning';
td.Text := 'Continue or Close?';
td.MainIcon := tdiWarning;
td.CommonButtons := [];
tb := td.Buttons.Add;
tb.Caption := 'Continue';
tb.ModalResult := 100;
tb := td.Buttons.Add;
tb.Caption := 'Close';
tb.ModalResult := 101;
if td.Execute then
begin
if td.ModalResult = 100 then
ShowMessage('Continue')
else if td.ModalResult = 101 then
ShowMessage('Close');
end;
finally
td.Free;
end;
end;
This will now show the desired dialog, will not crash, and will not leak memory: