Solved

TBitBtn Access Violations - Bug?

Posted on 2004-04-26
13
571 Views
Last Modified: 2010-04-05
Is this a bug, or am i doing somthing i can't see?

This works great for TSpeedbuttons:

procedure TForm1.ClearButtons;
var
 I: integer;
begin
 for I:= Scrollbox1.ControlCount -1 downto 0 do
  if ScrollBox1.COntrols[I] is TSpeedbutton then
   TSpeedbutton(ScrollBox1.COntrols[I]).Free;
end;

procedure TForm1.UpdateCategories;
var
 I, X, Y: integer;
 Btn: TSpeedbutton;
begin
 X:= 0; Y:= 0;
 for I:= 0 to 10 do
 begin
  Btn:= TSpeedbutton.Create(ScrollBox1);
  Btn.Parent:= ScrollBox1;
  Btn.Width:= 50;
  Btn.Height:= 50;
  Btn.Left:= X;
  Btn.Top:= Y;
  Btn.OnClick:= BitBtn2Click;
  X:= X + Btn.Width;
 end;
end;

procedure TForm1.UpdateItems;
var
 I, X, Y: integer;
 Btn: TSpeedbutton;
begin
 X:= 0; Y:= 0;
 for I:= 0 to 5 do
 begin
  Btn:= TSpeedbutton.Create(ScrollBox1);
  Btn.Parent:= ScrollBox1;
  Btn.Width:= 50;
  Btn.Height:= 50;
  Btn.Left:= X;
  Btn.Top:= Y;
  Btn.OnClick:= BitBtn1Click;
  X:= X + Btn.Width;
 end;
end;

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
 ClearButtons;
 UpdateCategories;
end;

procedure TForm1.BitBtn2Click(Sender: TObject);
begin
ClearButtons;
UpdateItems;
end;

end.



but if I change TSpeedbuttons to TbitBtns, I get Access Violations:

Im using Delphi 7 on Win XP


I need to use Raize BitBtns, but I get Access violations as well. So I went to see if I go them using the Standard Delphi BitBtn - and sure enough still.

So I tried SpeedButtons - And no Access Violations.

Shane
0
Comment
Question by:shaneholmes
  • 6
  • 5
  • 2
13 Comments
 
LVL 26

Expert Comment

by:Russell Libby
ID: 10923670

The speedbutton does not generate the violation because the actual "click" event handling is done as the very last statement in the MouseUp event. Regardless, its still wrong to do what you are doing...

You should NEVER be releasing a component in its own event handler. Per the Delphi help:

Warning:      Never explicitly free a component within one of its own event handlers or free a component from the event handler of a component it owns or contains. For example, don’t free a button in its OnClick event handler or free the form that owns the button from the button's OnClick event.

----

What you should be doing is posting a WM_USER+{your message} message to the main window, then in the message handler for your custom message, handle the button removal.

Regards,
Russell
0
 
LVL 17

Expert Comment

by:mokule
ID: 10923689
When I copy/paste Your code and press Ctrl+Shift+C these procedures
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
declarations went to private section - causing access violation

When I moved them to right place everything is OK both for TSpeedBtn and bitbtn

Maybe You've got the same problem
0
 
LVL 17

Expert Comment

by:mokule
ID: 10923750
rlibby is of course right.
I didn't make all needed changes so at first there were no access violations
0
 
LVL 11

Author Comment

by:shaneholmes
ID: 10923753
no, they weren't in the private section, they were above it...

but when i remove them and put them in the protected section, i got access violations, then when i cut and pasted them back into above the private section, i no longer get access violations - totally weired....

0
 
LVL 11

Author Comment

by:shaneholmes
ID: 10923760

Can you give me an example:

"What you should be doing is posting a WM_USER+{your message} message to the main window, then in the message handler for your custom message, handle the button removal."

Shane
0
 
LVL 11

Author Comment

by:shaneholmes
ID: 10923791
That is totally weired....


If I cut
 
  procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);

TForm1 = class(TForm)
    ScrollBox1: TScrollBox;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
  private
    { Private declarations }
  protected
  public
    { Public declarations }
  end;


 and paste to private section

TForm1 = class(TForm)
    ScrollBox1: TScrollBox;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
  private
    { Private declarations }
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
  protected
  public
    { Public declarations }
  end;

of course i get the access violations, but when i cut & paste them back to top, and run again, it works fine.

However, If I close doen and re-run it again, I get access violations again!

Shane
0
6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

 
LVL 26

Accepted Solution

by:
Russell Libby earned 100 total points
ID: 10923826

It has to do with the way Delphi's memory manager works. For example, you can free an object, and in the next statement, peform a call on the object and get away with it, because the mem manager does not clear out the memory where the object existed.

Anyways, suffice it to say that regardless of throwing the access violation or not, it is STILL wrong.

Regarding the message handling, its very simple:

const
  WM_DOSOMETHING    =  WM_USER+100;

type
  TForm1         =  class(TForm)
     Button1:    TButton;
     procedure   Button1Click(Sender: TObject);
  private
     // Private declarations
  protected
     // Handle the cleanup
     procedure   DoSomething(var Message: TMessage); message WM_DOSOMETHING;
  public
     // Public declarations
  end;

var
  Form1: TForm1;

implementation
{$R *.DFM}

procedure TForm1.DoSomething(var Message: TMessage);
begin
  Button1.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  PostMessage(Handle, WM_DOSOMETHING, 0, 0);
end;

Regards,
Russell

0
 
LVL 11

Author Comment

by:shaneholmes
ID: 10923853
hmmm, you learn something new everyday, let me try that....

Shane
0
 
LVL 11

Author Comment

by:shaneholmes
ID: 10923919
Works Perfect - Thanks Russell!

Learn something new everyday!

Shane


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, RzButton;


 const
  WM_CLEARBUTTONS    =  WM_USER+100;
  WM_UPDATECATS    =  WM_USER+200;
  WM_UPDATEITEMS    =  WM_USER+300;

type
  TForm1 = class(TForm)
    ScrollBox1: TScrollBox;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
  private
    { Private declarations }
  protected
  procedure  ClearButtons(var Message: TMessage); message WM_CLEARBUTTONS ;
  procedure  UpdateCats(var Message: TMessage); message WM_UPDATECATS ;
  procedure  UpdateItems(var Message: TMessage); message WM_UPDATEITEMS ;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

procedure TForm1.ClearButtons(var Message: TMessage);
var
 I: integer;
begin
 for I:= Scrollbox1.ControlCount -1 downto 0 do
  if ScrollBox1.COntrols[I] is TBitBtn then
    TBitBtn(ScrollBox1.COntrols[I]).Free;
end;

procedure  TForm1.UpdateCats(var Message: TMessage);
var
 I, X, Y: integer;
 Btn: TBitBtn;
begin
 X:= 0; Y:= 0;
 for I:= 0 to 10 do
 begin
  Btn:= TBitBtn.Create(ScrollBox1);
  Btn.Parent:= ScrollBox1;
  Btn.Width:= 50;
  Btn.Height:= 50;
  Btn.Left:= X;
  Btn.Top:= Y;
  Btn.OnClick:= BitBtn2Click;
  X:= X + Btn.Width;
 end;
end;

procedure  TForm1.UpdateItems(var Message: TMessage);
var
 I, X, Y: integer;
 Btn: TBitBtn;
begin
 X:= 0; Y:= 0;
 for I:= 0 to 5 do
 begin
  Btn:= TBitBtn.Create(ScrollBox1);
  Btn.Parent:= ScrollBox1;
  Btn.Width:= 50;
  Btn.Height:= 50;
  Btn.Left:= X;
  Btn.Top:= Y;
  Btn.OnClick:= BitBtn1Click;
  X:= X + Btn.Width;
 end;
end;

{$R *.dfm}

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
 PostMessage(Handle, WM_CLEARBUTTONS, 0, 0);
 PostMessage(Handle, WM_UPDATECATS, 0, 0);
end;

procedure TForm1.BitBtn2Click(Sender: TObject);
begin
 PostMessage(Handle, WM_CLEARBUTTONS, 0, 0);
 PostMessage(Handle, WM_UPDATEITEMS, 0, 0);
end;

end.
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 10924081

Glad I could help. Your code above is definitely the preferred way to handle component "self-suicide".

Russell
0
 
LVL 11

Author Comment

by:shaneholmes
ID: 10924094
WHat if I had to perform a Routine which requires parameters...?

procedure  UpdateItems(Cat: Category)
begin
 //
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
 // PostMessage
  UpdateItems(Cat: Category);
end;


Shane
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 10924636

If the params are numeric in nature, then they could be passed in the wParam and lParam. If strings are involved, or more than 2 params are required, then the following could be done (as the message handling is all done within the same app).

1.) Define a record structure, eg:

type
 TMyRecord          = packed record
   lpszParam1:    PChar;
   dwParam2:     Integer;
   szData:          Array [0..MAX_PATH] of Char;
 end;

2) Allocate a block of memory for a structure, and fill the parameters in.
3.) Pass the structure with a cast, eg

var
  MyRecord: TMyRecord;
begin

 // Allocate and set
 // ....

 // Pass into PostMessage
 PostMessage(Handle, Message, wParam, Integer(@MyRecord));

end;

4.) In the message handler, recast back to the correct structure, do whatever needs to be done, then free the memory.

It doesn't have to be a record pointer either, as it could be an object, ect. Just as long as the record/object/ect exists when the post message is processed.

Using PostMessage, the one type of coding that you need to avoid is something along the lines of PChar passing, eg:

var s: String;
begin
 
 s:='Something';  
 PostMessage(Handle, Message, wParam, Integer(PChar(s)));

end;

Because the data may be cleared, the stack may be popped, etc, before the message is processed.


------------

Russell







0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 10924643
Actually, in my above example, it should have been:

3.) Pass the structure with a cast, eg

type
 PMyRecord = ^TMyRecord;

var
  lpMyRecord: PMyRecord;
begin

 // Allocate and set
 // ....
 lpMyRecord:=AllocMem(SizeOf(TMyRecord));

 // Pass into PostMessage
 PostMessage(Handle, Message, wParam, Integer(lpMyRecord));

--------

Russell
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now