Access violation and Abstract error - delphi

I have a TButton event handler which either throws an Access Violation or Abstract Error when the method has completed execution. Delphi then highlights end. in my project source file. The method runs successfully, deleting the correct row from the database.
I have components which are created at runtime. Each row contains a label, an 'update' button and a 'delete' button, which have the database row number they are associated with in their names. The event handler throwing the errors is for delete.
procedure TFormInventoryMngmnt.ProductDeleteClick(Sender: TObject);
var
button : TButton;
row : integer;
confirm : integer;
begin
// Assuming it is button..how else can this be called?
button := Sender as TButton;
// Get row
row := StrToInt(StringReplace(button.Name, 'Delete', '', [rfReplaceAll, rfIgnoreCase]));
// Confirm
confirm := MessageDlg('Are you sure?', mtInformation, [mbYes, mbNo], 0);
if confirm = mrYes then
begin
// Delete row
UnitSession.Query.SQL.Clear;
UnitSession.Query.SQL.Add(Format(' DELETE FROM Products WHERE Product_ID = %s ', [IntToStr(row)]));
UnitSession.Query.ExecSQL;
buildManagementSection;
end;
// FIXME: why is this throwing access / abstract violation
end;
If you need more code or explanations just leave a comment and I'll get back to you.
Thanks!
Updated with code for buildManagementSection
I commented out the call to buildManagementSection from the listener and it did not throw any exceptions. This error could be caused from the method below, but I've never had a problem with it until now.
procedure TFormInventoryMngmnt.buildManagementSection;
var
index : integer;
runningHeight : integer;
productName : TLabel;
productUpdate : TButton;
productDelete : TButton;
const
MARGIN_TOP = 35;
// Left value of labels
LABEL_LEFT = 0;
// Left value of update button
UPDATE_LEFT = 235;
// Left value of delete button
DELETE_LEFT = 315;
begin
// Run sql query
UnitSession.Query.SQL.Clear;
UnitSession.Query.SQL.Add('SELECT Product, Product_ID FROM Products ORDER BY Product_ID');
UnitSession.Query.Active := true;
// Remove all components in the manage section
for index := (ScrollBoxManage.ComponentCount - 1) downto 0 do
begin
ScrollBoxManage.Components[index].Free;
end;
// No items
if UnitSession.Query.RecordCount = 0 then
begin
productName := TLabel.Create(ScrollBoxManage);
productName.Parent := ScrollBoxManage;
productName.Caption := 'No items!';
productName.Font.Color := clRed;
productName.Visible := true;
exit;
end;
// Build form
UnitSession.Query.First;
runningHeight := 0;
for index := 0 to (UnitSession.Query.RecordCount - 1) do
begin
// Create components
productName := TLabel.Create(ScrollBoxManage);
productUpdate := TButton.Create(ScrollBoxManage);
productDelete := TButton.Create(ScrollBoxManage);
// Set parents
productName.Parent := ScrollBoxManage;
productUpdate.Parent := ScrollBoxManage;
productDelete.Parent := ScrollBoxManage;
// Set values
productName.Caption := UnitSession.Query.Fields[0].AsString;
productUpdate.Caption := 'Update';
productDelete.Caption := 'Delete';
// Set event handlers
productUpdate.OnClick := FormInventoryMngmnt.ProductUpdateClick;
productDelete.OnClick := FormInventoryMngmnt.ProductDeleteClick;
// Set top position
productName.Top := runningHeight + 3;
productUpdate.Top := runningHeight;
productDelete.Top := runningHeight;
// Set button association
productName.Name := 'Label' + UnitSession.Query.Fields[1].AsString;
productUpdate.Name := 'Update' + UnitSession.Query.Fields[1].AsString;
productDelete.Name := 'Delete' + UnitSession.Query.Fields[1].AsString;
// Set left position
productName.Left := LABEL_LEFT;
productUpdate.Left := UPDATE_LEFT;
productDelete.Left := DELETE_LEFT;
// Set as visible
productName.Visible := true;
productUpdate.Visible := true;
productDelete.Visible := true;
runningHeight := runningHeight + MARGIN_TOP;
UnitSession.Query.Next;
end;
end;

The button instance hasn't been created!
There is no need to say
button := Sender as Tbutton
just reference the original button in the code since it is in scope on the form
row := StrToInt(StringReplace(ProductDelete.Name, 'Delete', '', [rfReplaceAll, rfIgnoreCase]));
or do this
with TButton(sender) do
row := StrToInt(StringReplace(Name, 'Delete', '', [rfReplaceAll, rfIgnoreCase]));
Or if you really want to do it this way, with a variable holding the button instance, then
you must create it first
begin
button := TButton.Create(Self);
try
button := TButton(Sender)
// your code
finally
button.Free
end
end

Related

ListBoxItem Visible Error

There is something that I didn't understand with TListBox and TListBoxItem in Delphi 10.2 Tokyo.
Some values (TListBoxItem) are load to my ListBox, when the first letter change I add a TListBoxGroupHeader.
procedure TForm1.Button1Click(Sender: TObject);
var
lbItem: TListBoxItem;
Letter: string;
ListBoxGroupHeader: TListBoxGroupHeader;
i: integer;
ListValue: TStringList;
begin
Letter := '';
ListValue := TStringList.Create;
try
ListValue.Add('Germany');
ListValue.Add('Georgie');
ListValue.Add('France');
ListValue.Add('Venezuela');
ListValue.Add('Poland');
ListValue.Add('Russia');
ListValue.Add('Sweden');
ListValue.Add('Denmark');
ListBox1.BeginUpdate;
for i := 0 to ListValue.Count - 1 do
begin
if Letter <> Copy(ListValue[i], 0, 1).ToUpper then
begin
ListBoxGroupHeader := TListBoxGroupHeader.Create(ListBox1);
ListBoxGroupHeader.Text := Copy(ListValue[i], 0, 1).ToUpper;
ListBox1.AddObject(ListBoxGroupHeader);
end;
lbItem := TListBoxItem.Create(ListBox1);
lbItem.Text := ListValue[i];
lbItem.Tag := i;
ListBox1.AddObject(lbItem);
Letter := Copy(ListValue[i], 0, 1).ToUpper;
end;
finally
ListBox1.EndUpdate;
FreeAndNil(ListValue);
end;
end;
I use a TEdit to search in this ListBox. That's here that I have a problem. If ListBoxItem contain the content of the Edit I set Visible to True, else I set it to False.
procedure TForm1.Edit1ChangeTracking(Sender: TObject);
var
i : integer;
ListBoxItem: TListBoxItem;
begin
ListBox1.BeginUpdate;
try
for i := 0 to ListBox1.Items.Count - 1 do
begin
if ListBox1.ListItems[i] is TListBoxItem then
begin
ListBoxItem := TListBoxItem(ListBox1.ListItems[i]);
if Edit1.Text.Trim = '' then
begin
ListBoxItem.Visible := True
end
else
begin
if ListBox1.ListItems[i] is TListBoxGroupHeader then
ListBoxItem.Visible := False
else
ListBoxItem.Visible := ListBoxItem.Text.ToLower.Contains(Edit1.Text.Trim.ToLower);
end;
end;
end;
finally
ListBox1.EndUpdate;
end;
end;
The first GroupHeader (letter G) is always visible ! and it's look like there is a ListBoxItem behind the GroupHeader.. When I use a checkpoint Visible is set to false .. so I didn't understand..
If I write the letter "V" I only see the GroupHeader with letter "G".
I have evene try to change the text value if it's a GroupHeader.
if ListBox1.ListItems[i] is TListBoxGroupHeader then
ListBoxItem.Text := '>>' + ListBoxItem.Text + '<<'
Thats change text but not for the first GroupHeader (letter G) ...
Don't know if I use it bad, or if it's a bug ??
I could have reproduce what you've described and it has something to do with hiding header whilst keeping item under that header visible. In such case application shows header rather than the item. I haven't checked what's wrong inside but it seems it is not what you want. IMHO you want to keep visible items that match to a search text with their respective header and hide only headers with no items under.
If that is so, try this:
procedure FilterItems(const Text: string; ListBox: TListBox);
var
I: Integer; { ← loop variable }
Hide: Boolean; { ← flag indicating if we want to hide the last header we passed }
Item: TListBoxItem; { ← currently iterated item }
Head: TListBoxGroupHeader; { ← last header item we passed during iteration }
begin
Head := nil;
Hide := True;
ListBox.BeginUpdate;
try
{ if search text is empty, show all items }
if Text.IsEmpty then
for I := 0 to ListBox.Content.ControlsCount - 1 do
ListBox.ListItems[I].Visible := True
else
{ otherwise compare text in non header items }
begin
for I := 0 to ListBox.Content.ControlsCount - 1 do
begin
Item := ListBox.ListItems[I];
{ if the iterated item is header }
if Item is TListBoxGroupHeader then
begin
{ set the previous header visibility by at least one visible item }
if Assigned(Head) then
Head.Visible := not Hide;
{ assume hiding this header and store its reference }
Hide := True;
Head := TListBoxGroupHeader(Item);
end
else
{ if the iterated item is a regular item }
if Item is TListBoxItem then
begin
{ set the item visibility by matching text; if the item remains visible, it
means we don't want to hide the header, so set our flag variable as well }
if Item.Text.ToLower.Contains(Text) then
begin
Hide := False;
Item.Visible := True;
end
else
Item.Visible := False;
end;
end;
{ the iteration finished, so now setup visibility of the last header we passed }
if Assigned(Head) then
Head.Visible := not Hide;
end;
finally
ListBox.EndUpdate;
end;
end;
procedure TForm1.Edit1ChangeTracking(Sender: TObject);
begin
FilterItems(Edit1.Text.Trim.ToLower, ListBox1);
end;

Why is my TListBox getting blank when its last TListBoxItem is checked?

Problem
My TListBox is getting blank when its last TListBoxItem is programatically checked. To illustrate it better, hereby what I mean by getting blank:
Context
I'm generating a list from a TJSONArray. Each item looks like {"event_code","event_name"}.
Then, I compare if the event_code is written on a second TJSONArray : json_response_available_events. If it does, the ListBoxItem will be checked.
Code
procedure TFormHome.TimerGetEventsTimer(Sender: TObject);
var
K : Integer;
Z : Integer;
ListCount : Integer;
AvailableList_Count: Integer;
lb_item: TListBoxItem;
event_code_first_array: string;
event_code : string;
event_name : string;
begin
// Disable this timer for now
TimerGetEvents.Enabled := false;
// Get List of Notifications
json_response_events := DM_Auth0.ServerMethods1Client.GetEventsCodeAndDescription(communication_token);
json_response_available_events := DM_Auth0.ServerMethods1Client.GetAllowedNotificationsList(communication_token, genset_id);
ListCount := json_response_events.Count -1;
AvailableList_Count := json_response_available_events.Count - 1;
for K := 0 to (ListCount) do
begin
// Get complete Event Code and Name
event_name := json_response_events.Items[K].toString;
// Get Event Code
event_code_first_array := StringReplace(event_name.Split([':'])[0], '"', '', [rfReplaceAll]);
// Get Event Name
event_name := StringReplace(event_name.Split([':'])[1], '"', '', [rfReplaceAll]);
// Create ListBoxItem
lb_item := TListBoxItem.Create(self);
lb_item.Parent := lb_notifications;
lb_item.Text := event_name;
lb_item.StyleLookup := 'listboxitemleftdetail';
// Check if this Item code is available
for Z := 0 to (AvailableList_Count) do
begin
if json_response_available_events.Items[Z] <> nil then
begin
// Get Event Code
event_code := json_response_available_events.Items[Z].toString;
// Format
event_code := StringReplace(event_code, '"', '', [rfReplaceAll]);
if event_code_first_array.Contains(event_code) then
begin
if K <= ListCount then
begin
lb_item.IsChecked := true;
lb_item.IsSelected := false;
end;
end;
end;
end;
end;
end;
Analysis
If we set to < only, it displays the list correctly but the last item will remain unchecked.
if K < ListCount then
begin
lb_item.IsChecked := true;
lb_item.IsSelected := false;
end;
I can even change it's properties when its = like
if K = ListCount then
begin
lb_item.Text := 'Deadpool for President';
end;
and lb_item.isChecked := false works fine, but when setting lb_item.isChecked := true it gets all weirdly blank.
Why is it happening? And if there's a better way to do what I'm doing, the help will be appreciated.

Delphi: Adding Combobox Dropdown to TADVStringGrid

I have a form that has a TADVStringGrid. I am trying to add a combobox to some rows in a specific column (2); however, I can't get the data to show on the dropdown. I added the HasComboBox event, I set the DirectComboDrop, and it still did not show any data in the dropdown. They were just empty. I check the object that I am adding to the dropdown, and they had data. What am I missing here?
procedure UserForm.DisplayGrid(Sender: TObject);
var
J : Integer;
begin
... additional logic
...
if OutputList.Count > 2 then
begin
with UserGrid.Combobox do
begin
for J := 0 to OutputList.Count - 1 do
BEGIN
if not(OutputList[J] = '') then
begin
dValue := DropDownValue.Create;
dValue.ID := J + 1;
dvalue.Name := OutputList[J];
dvalue.TestValue := OutputList[J] + 'testvalue'; // where value will be a list to choose from
ListOfTest.Add(dValue); // this is a glabal variable where I for later reference
ItemIndex := dValue.ID;
end;
END;
end;
end;
//event
procedure UserForm.UserGridHasComboBox(Sender: TObject; ACol, ARow: Integer;
var HasComboBox: Boolean);
begin
HasComboBox := True;
end;
There is an event handle called EditorProp that needed to be added. Data that need to be added for a specific column have to be added when the EditorProp event is called. The piece of code below was moved into the editorprop event, and it's working fine since.
for J := 0 to OutputList.Count - 1 do
BEGIN
if not(OutputList[J] = '') then
begin
dValue := DropDownValue.Create;
dValue.ID := J + 1;
dvalue.Name := OutputList[J];
dvalue.TestValue := OutputList[J] + 'testvalue'; // where value will be a list to choose from
ListOfTest.Add(dValue); // this is a glabal variable where I for later reference
ItemIndex := dValue.ID;
end;

Create an on click event handler for an array of Radiobuttons

I have created an array of radiobuttons, which will be created in an event. I want to create an event, saying to make the radiobutton invisible when it is clicked and the show a message. But it has to happen on click. Can you help me?
This is how I created my radiobuttons
for k := 1 to 20 do
begin
rd[k] := TRadioButton.Create(Self);
rd[k].Parent := pgcVerkiesing;
rd[k].Caption := 'rs'+IntToStr(k);
rd[k].Left := 16;
if k = 1 then
rd[k].Top := 26
else
rd[k].Top := (k*24) ;
rd[k].OnClick := OnClick;
end;
Now I want to do something like this : rs1.clicked //procedure
rs1.disabled := true;
richedit1.lines.add := 'Name';
showmessage(names);
What to do?
If I understood correctly, you want to disable the clicked radiobutton.
Define an event for your radiobuttons:
procedure TForm1.OnRadioButtonClick(Sender : TObject);
When creating your radiobuttons, tie this event handler to the radiobuttons.
rd[k].OnClick := OnRadioButtonClick;
procedure TForm1.OnRadioButtonClick(Sender : TObject);
begin
TRadioButton(Sender).Enabled := false;
RichEdit1.Lines.Add( 'Name');
ShowMessage( names); // names not defined ??
end;

Forms creation and destroying in OnMouseEnter ; OnMouseLeave events in Delphi

Sorry if there is already made such question earlier, but I have no time at the moment to dig in stackoverflow db ...
So, I have this code:
procedure TForm1.GraphPrevBtnMouseEnter(Sender: TObject);
var frm_PrevBtn : TForm;
begin
GraphPrevBtn.Width := 75;
if z = 0 then begin
frm_PrevBtn := TForm.Create(nil);
with frm_PrevBtn do begin
Name := 'frm_PrevBtn';
BorderStyle := bsNone;
Position := poDesigned;
Top := Form1.Top + GraphprevBtn.Top + (form1.Height - Form1.ClientHeight) - 3;
Left := Form1.Left + GraphprevBtn.Left + 3;
Width := GraphprevBtn.Width; Height := GraphprevBtn.Height; transparentColor := True; TransparentColorValue := clbtnFace;
Show;
end;
GraphPrevBtn.Parent := frm_PrevBtn;
if GetLastError = 0 then z := frm_prevBtn.GetHashCode;
end;
end;
procedure TForm1.GraphPrevBtnMouseLeave(Sender: TObject);
var frm_PrevBtn_H : THandle;
begin
// if form is created then- if mouse is under button then- if z = formshashcode ( form is on creatin stage )
if not (FindVCLWindow(Mouse.CursorPos) = GraphPrevBtn) and ((FindControl(FindWindow('TForm','frm_PrevBtn')) as TForm).Visible = True) and (GraphPrevBtn.Parent = FindControl(FindWindow('TForm','frm_PrevBtn')) as TForm) then begin // if mouse is not under graphprevBtn
ShowMessage(FindVCLWindow(Mouse.CursorPos).Name); //
if z = 112 // then if form is created
then begin
GraphPrevBtn.Parent := Form1;
GraphPrevBtn.bringtoFront;
GraphPrevBtn.Top := 29; GraphPrevBtn.Left := 226;
(FindControl(FindWindow('TForm','frm_PrevBtn')) as TForm).Free;
if GetLastError = 0 then z := 0;
end;
end;
end;
So, my wish is the following:
When I enter this GraphPrevBtn with mouse, form is created. As for is created, the focus goes from Control to new form. As focus is to new form, the OnMouseLeave event is fired. As event is fired, it should destroy the form, BUT ONLY IF user ( NOT active control / focus ) actually leaves control by mouse.
What happens now is that either new forms is not destroyed at all or both events goes infinite loop ( *frm_PrevBtn* is created and destroyed again and again and again...).
What would be best solution?
My idea is to get new forms rect and check whenever mouse is inside this rect. If it is, then perform allow OnMouseLeave event, otherwise deattach it ... would it work?
As much I tried with these samples:
http://delphi.about.com/od/windowsshellapi/a/get-active-ctrl.htm
http://delphi.about.com/od/delphitips2010/qt/is-some-delphi-tcontrol-under-the-mouse.htm
No luck. Where is the problem ... ?
Remarks: global var z : byte;
P.S. Thanks for negative votes ... great motivation to use this site in future ...
Mouse enters on 'GraphPrevBtn', you create a form over the button. As soon as this form becomes visible, since mouse is not anymore over 'GraphPrevBtn', 'OnMouseLeave' is fired. You destroy the new form and now mouse is again on the button so 'OnMouseEnter' is fired, hence the infinite loop.
As a solution, you can move the form disposing code to 'OnMouseEnter' of Form1:
procedure TForm1.FormMouseEnter(Sender: TObject);
begin
if z = 112
then begin
GraphPrevBtn.Parent := Form1;
[...]
.. and what's with the 'GetLastError', it seems fully irrelevant. If you're going to use it, at least set last error to '0' by calling GetLastError or SetLastErrorbefore beginning your operation.
Maybe something more like this will help you:
var
frm_PrevBtn : TForm = nil;
procedure TForm1.GraphPrevBtnMouseEnter(Sender: TObject);
var
P: TPoint;
begin
GraphPrevBtn.Width := 75;
if frm_PrevBtn = nil then begin
P := GraphPrevBtn.ClientOrigin;
frm_PrevBtn := TForm.Create(nil);
with frm_PrevBtn do begin
BorderStyle := bsNone;
Position := poDesigned;
SetBounds(P.X, P.Y, GraphPrevBtn.Width, GraphPrevBtn.Height);
TransparentColor := True;
TransparentColorValue := clBtnFace;
GraphPrevBtn.Parent := frm_PrevBtn;
GraphPrevBtn.Top := 0;
GraphPrevBtn.Left := 0;
Show;
end;
end;
end;
procedure TForm1.GraphPrevBtnMouseLeave(Sender: TObject);
begin
if (FindVCLWindow(Mouse.CursorPos) <> GraphPrevBtn) and (frm_PrevBtn <> nil) then begin
GraphPrevBtn.Parent := Self;
GraphPrevBtn.BringToFront;
GraphPrevBtn.Top := 29;
GraphPrevBtn.Left := 226;
FreeAndNil(frm_PrevBtn);
end;
end;
Why don't you do it like this:
MainForm.OnMouseOver: Create a secondary form.
SecondaryForm.OnMouseOver: Set FLAG_ON_SECONDARY.
SecondaryForm.OnMouseLeave: Clear FLAG_ON_SECONDARY.
MainForm.OnMouseLeave: if not FLAG_ON_SECONDARY then destroy the secondary form.
This might not work in case SecondaryForm.OnMouseOver fires after MainForm.OnMouseLeave. Well, think of something similar. Another solution is to start a timer which destroys SecondaryForm and disables itself if mouse is neither on Main nor on SecondaryForm.

Resources