Solved

ListBox Update/OnAdd/OnChange event capture

Posted on 2004-04-27
39
830 Views
Last Modified: 2010-04-05
I need to be notified if an item has been added to listbox. I tried capturing LB_ADDSTRING windows message, but that does not solve my problem because I need to perform some functions after values have been added to listbox not when they are going to be added (this is when windows message is sent).

Any idea how this can be achieved? Is there a listbox component that has already been developed with OnChange event (similar to that of a Memo which invokes OnChange after showing value in the Memo) or an OnAdd event?
0
Comment
Question by:Meghna172
  • 13
  • 11
  • 9
  • +2
39 Comments
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10933008
Use OnClick.  I dug around in the VCL source and found that TListBox
responds to LBN_SELCHANGE by calling the OnClick handler.

Shane
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10933027
TCustomListbox already traps LBN_SELCHANGE in
TCustomListBox.CNCommand (in StdCtrls.pas):

procedure TCustomListBox.CNCommand(var Message: TWMCommand);
begin
  case Message.NotifyCode of
    LBN_SELCHANGE:
      begin
        inherited Changed;
        Click;
      end;
    LBN_DBLCLK: DblClick;
  end;
end;

It looks like the Changed and Click events are being called for every
LBN_SELCHANGE notification.  If putting your code in the OnClick event dont work, I'm guessing that writing a custom
component derived from TCustomListBox and trapping LBN_SELCHANGE
won't work either (but that's just a guess).

There may be some other Windows message(s) you could trap, but I
don't know them off the top of my head.  

Shane
0
 
LVL 27

Expert Comment

by:kretzschmar
ID: 10935932
alternative you could replace your listbox with a listview
0
 
LVL 6

Expert Comment

by:pritaeas
ID: 10936054
Hi. Why don't you subclass TCustomListbox, and add a custom event after the Add procedure? That way you have full control.

hth, pritaeas
0
 
LVL 22

Expert Comment

by:Ferruccio Accalai
ID: 10939394
Why don't capture the specified LB messages?

go this way:

in TForm public section add as follows....

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    [...]    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    OldListBoxWndProc: TWndMethod;
    procedure ListBoxWndProc(var Message: TMessage);
  end;


then in implementation...

procedure TForm1.FormCreate(Sender: TObject);
begin
  OldListBoxWndProc := ListBox1.WindowProc;
  ListBox1.WindowProc := ListBoxWndProc;
end;

procedure TForm1.ListBoxWndProc(var Message: TMessage);
begin
  case Message.Msg of
    LB_INSERTSTRING: Showmessage('Inserted at ' + IntToStr(Message.WParam) + ' "' + string(Message.LParam) + '"');
    LB_DELETESTRING: Showmessage('Deleted at' + IntToStr(Message.WParam));
    LB_ADDSTRING   :Showmessage('Added at"' + string(Message.LParam) + '"');
  end;
  OldListBoxWndProc(Message);
end;

F68 ;-)
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10939523
OR as a component......

unit XListBox;

interface

uses
  SysUtils, Classes, Controls, StdCtrls, Messages;

type
  TXListBox = class(TListBox)
  private
    { Private declarations }
    fOnChange: TNotifyEvent;
  protected
    { Protected declarations }
   procedure WndProc(var Message: TMessage);override;
  public
    { Public declarations }
  published
    { Published declarations }
    property OnChange: TNotifyEvent read fOnChange write fOnChange;
  end;

procedure Register;

implementation

procedure TXListBox.WndProc(var Message: TMessage);
begin
  { tests to determine whether to continue processing }
  inherited WndProc(Message);
  case Message.Msg of
    LB_INSERTSTRING, LB_DELETESTRING, LB_ADDSTRING:
     if Assigned(fOnChange) then fOnChange(Self);
  end
end;

procedure Register;
begin
  RegisterComponents('Samples', [TXListBox]);
end;

end.
0
 
LVL 22

Expert Comment

by:Ferruccio Accalai
ID: 10939575
Yes....also a good way :)
F68 ;-)
0
 
LVL 22

Expert Comment

by:Ferruccio Accalai
ID: 10939875
Just to add some more futures in the VCL option:

unit XListBox;

interface

uses
  SysUtils, Classes, Controls, StdCtrls, Messages;

type
  TListBoxEvent = (Adding, Inserting, Deleting);
  TOnChangeevent = Procedure(Sender: TObject;Event: TListBoxEvent;Index: Integer) of object; //to pass the event and the index where it occurs
  TXListBox = class(TListBox)
  private
    { Private declarations }
    fOnChange: TOnChangeEvent;
  protected
    { Protected declarations }
   procedure WndProc(var Message: TMessage);override;
  public
    { Public declarations }
  published
    { Published declarations }
    property OnChange: TOnChangeEvent read fOnChange write fOnChange;
  end;

procedure Register;

implementation

procedure TXListBox.WndProc(var Message: TMessage);
begin
  { tests to determine whether to continue processing }
  inherited WndProc(Message);
  case Message.Msg of
    LB_INSERTSTRING: if Assigned(fOnChange) then fOnChange(Self,Inserting,Message.WParam);
    LB_DELETESTRING: if Assigned(fOnChange) then fOnChange(Self,Deleting,Message.WParam);
    LB_ADDSTRING: if Assigned(fOnChange) then fOnChange(Self,Adding,Items.count-1);
  end
end;

procedure Register;
begin
  RegisterComponents('Samples', [TXListBox]);
end;

end.

F68 ;-)
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10939904
Yeah, was going to do that, but i figured he wanted just the basic notification (onChange)  like in the other controls (TComboBox, etc.)

Nice!

Shane
0
 

Author Comment

by:Meghna172
ID: 10942645
Thankyou all for the help.

But the problem still exists. I'll explain it better this time.

I make a request to network. It replies by updating my listbox ( reply can only given to a listbox so listview cannot be used, I have to pass a listbox handle only). I want to know when listbox was updated inorder to make second request. First request has to be complete to make a subsequent request.

Now whats happening is, if we use windows messages to write an onchange event, as done in XListBox Component, the onchange event  is invoked first and then item is added to the listbox. But the first request is considered complete when item has been added to listbox. So the problem is, I am supposed to do second request in onchange event handler, which is invoked before the first request being completed. Hence my second request is never going through.

If I have not confused you enough, please help. How should I handle this.

Meghna
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10942676
ummm, maybe override the add procedure instead, then call an OnChange event

Shane
0
 

Author Comment

by:Meghna172
ID: 10942723
How does Memo's onchange event work? Does it Add the item first then invoke the event handler?
If yes, I probably need something like that.


Making a component by Subclassing TCustomListbox, and adding a custom event after the Add procedure, as suggested by piraetes, may help. But I dont know how to do that.

Can I have some sample code for this?

Meghna
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10942727
Working on an example now

Shane
0
 

Author Comment

by:Meghna172
ID: 10942742
Ya right, may be overriding add procedure is going to help. thats what i just thought.

Shane, Can you help me with this. Any sample available?

Meghna
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10942768
You cant override Add, cause there is no add method, it is a mthod of the TStrings class which is a property of TListBox (TCustomListBox)

However, you can create your own add & delete then replace all occurrences in your code

Items.Add & Items.Delete to use Add & Delete see code below....

Shane

unit XListBox;

interface

uses
  SysUtils, Classes, Controls, StdCtrls, Messages;

type
  TXListBox = class(TListBox)
  private
    { Private declarations }
    fOnChange: TNotifyEvent;
  protected
    { Protected declarations }
  public
    { Public declarations }
    function Add(Value: String): Integer;
    procedure Delete(Index: Integer);
  published
    { Published declarations }
    property OnChange: TNotifyEvent read fOnChange write fOnChange;
  end;

procedure Register;

implementation

function TXListBox.Add(Value: String): Integer;
begin
 result:= inherited Items.Add(Value);
 if Assigned(fOnChange) then fOnCHange(Self);
end;

procedure TXListBox.Delete(Index: Integer);
begin
 inherited Items.Delete(Index);
 if Assigned(fOnChange) then fOnCHange(Self);
end;

procedure Register;
begin
  RegisterComponents('SAH', [TXListBox]);
end;

end.

It you want, you could keep the old windows message as well and create two different events

OnBeforeChange

OnAfterChange

Shane
0
 

Author Comment

by:Meghna172
ID: 10942813

I am new to writing components (which Subclasses TCustomListbox  and adds custom events after the add procedure).

Any example in relation to this would be really nice.

Thanks,
Meghna
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10942831
WHat about my last post?

Shane
0
 

Author Comment

by:Meghna172
ID: 10942960
Ya, am working on it. I'll let u know in a while. I beleive this will surely help.

Thanks a millions
Meghna
0
Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

 

Author Comment

by:Meghna172
ID: 10943273
Hey Shane,

I was trying your component for all this while. Unfortunately it didnt work, when I installed it.

procedure TForm1.XListBox1Change(Sender: TObject);
begin
ShowMessage('here');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 XListBox1.Items.Add('added');
end;

Check the above code. It does not display the message 'here' on item adding the item!

Thanks
Meghna
0
 

Author Comment

by:Meghna172
ID: 10943302
I am guessing why it didn't work. I read this somewhere.

"The TStringList class has an OnChange and OnChanging event. OnChanging triggers first, then the list is modified and finally OnChange is triggered. Unfortunatelt a listbox doesn't use a stringlist but a TListBoxStrings class for the items. And this class communicates directly with the windows control itself through... messages!

Inheriting your own listbox class from the existing one as gmayo suggests is not a very good idea. You'd have to change the Items object in that case and the TListBoxStrings class is private to the StdCtrls unit. Means you'd have to rewrite the whole thing.
But the alternate solution would be to create a virtual listbox. Set the style to lbVirtual and assign code to the OnData and OnDataFind to retrieve the items from your own stringlist. It's also not an easy solution but it saves you from rewriting this control.
A final solution would be to write your own listbox class and capture the messages that are send to the Windows control. But I don't have a clue about how to do this correctly. "

Can TString and TStringList be a problem?

Meghna
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10943345
works for me,

procedure TForm1.XListBox1Change(Sender: TObject);
begin
 ShowMessage('Hello');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 XlistBox1.Add('Shane');
end;


DId you do something wrong, post me your components soorce

Shane
0
 

Author Comment

by:Meghna172
ID: 10943447
as you can see

1. XlistBox1.Add('Shane');  This works.

2**. XListBox1.Items.Add('added'); This does not. (  this is what I did)

the XlistBox onchange event doesn't show me this message when it is getting updated in my application, probably because the request's reply that is being sent to the listbox is using 2nd** option to add item to listbox. And I dont have any control over that part.

Now, what to do?

Meghna





0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10943469
Like i said in my above post.................., you have to replace all your Items.add code to just add
(using the new add procedure)


If you do not want to do this, then im sorry, then you will have to find an alternative method...

SHane
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10943479
or wait to see if someone else wants to post a solution for you....

Goodluck!

Shane
0
 
LVL 22

Expert Comment

by:Ferruccio Accalai
ID: 10943910
well...i was busy but now i'm back with my last solution....

The vcl with the same OnChangeEvent as above (without overriding the add procs), but performed in WM_Paint message so the event is triggered after the adding, inserting or deleting action (that's your needed, isn't it?)....

unit xlistbox;

interface

uses
  SysUtils, Classes, Controls, StdCtrls, Messages;

type
  TListBoxEvent = (Added, Inserted, Deleted,Nothing);
  TOnChangeevent = Procedure(Sender: TObject;Event: TListBoxEvent;Index:
  Integer) of object;
  TXListBox = class(TListBox)
  private
    { Private declarations }
    fOnChange: TOnChangeEvent;
  protected
    { Protected declarations }
    fLastSTate: TListBoxEVent;
    fLastItemIndex: Integer;
    fStarted: Boolean;
    procedure WndProc(var Message: TMessage);override;
  public
    { Public declarations }
    constructor Create(Aowner: TCOmponent); Override;
    destructor Destroy; override;
  published
    { Published declarations }
    property OnChange: TOnChangeEvent read fOnChange write fOnChange;
  end;

procedure Register;

implementation

constructor TXListBox.CReate(AOwner: TCOmponent);
begin
  inherited Create(Aowner);
  fstarted := False;
  fLastSTate := Nothing;
  fLastItemIndex := -1;
end;

destructor TXListBox.Destroy;
begin
inherited destroy;
end;

procedure TXListBox.WndProc(var Message: TMessage);
begin
  { tests to determine whether to continue processing }
  inherited WndProc(Message);

  if Message.Msg = LB_INSERTSTRING then begin
      flaststate := Inserted;
      fLastItemIndex := Message.WParam;
    end else
      if Message.Msg = LB_DELETESTRING then begin
          fLastState := Deleted;
          fLastItemIndex := Message.WParam;
        end else
          if Message.Msg = LB_ADDSTRING then begin
              fLastState := Added;
              fLastItemIndex := Items.count-1;
            end else
              if Message.msg = WM_Paint then begin
                if (flaststate <> nothing) then
                   If assigned(fOnChange) then begin
                      If fstarted then
                        fOnChange(Self,flaststate,flastitemIndex);
                      fstarted := true;
                      fLastSTate := Nothing;
                  end;
              end;
end;

procedure Register;
begin
  RegisterComponents('Samples', [TXListBox]);
end;

end.

F68 ;-)

0
 

Author Comment

by:Meghna172
ID: 10949099
Ferruccio68,

Using the above component, does make my second request go through and thats what I'm looking for. But it gives a fatal error of 'access violation' at

-->fstarted := true; in xlistbox code

Any idea what this could be? Whats the logic behind this fstarted, or could the error be at a line above or below?

Meghna


0
 
LVL 22

Expert Comment

by:Ferruccio Accalai
ID: 10949241
As the Wm_Paint is triggered after every item adding, inserting and deleting, so the fOnChange event is called also during the component creation....
the fstarted is a switch turned after the first paint
the only limitation is that the listbox must have at least 1 item added in design time....
BTW the access violation could be raised by the code in the OnChange event....what about it? how is the code in OnChange?
0
 

Author Comment

by:Meghna172
ID: 10949506
Code  in onchange event handler is the request which I want to make to the network when onchange occurs. I get the 'request going through' notification, which was not happening earlier as 1st request was incomplete. Buy after that notifiction i get the error. Shall try by adding 1 item at design item ? If we remove fstarted whats going to happen?
0
 
LVL 22

Expert Comment

by:Ferruccio Accalai
ID: 10949797
If i can understand you're using the listbox at first run with clearde items (no items)....
In this case you can remove the fstarted test as the vm_paint will never pass in that condition during creation.....
 if Message.msg = WM_Paint then begin
                if (flaststate <> nothing) then
                   If assigned(fOnChange) then begin
                      fOnChange(Self,flaststate,flastitemIndex);
                      fLastSTate := Nothing;
                  end;
0
 

Author Comment

by:Meghna172
ID: 10949895
If we remove fstarted part the onchange event doent trigger at all.
0
 
LVL 22

Expert Comment

by:Ferruccio Accalai
ID: 10950117
mmm...
i'm trying a workaround...
a little weird but it seems working.

Let stay the cmp with the fstarted part as above... then let's add in protected section this part

procedure Loaded; override;

and in implementation:

procedure TXListBox.Loaded;
var
i: Integer;
begin
inherited;
i := Items.Add('foo');
Items.Delete(i);
end;

with the following example it's working fine

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    XListBox1: TXListBox;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure XListBox1Change(Sender: TObject; Event: TListBoxEvent;
      Index: Integer);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.XListBox1Change(Sender: TObject; Event: TListBoxEvent;
  Index: Integer);
begin
case event of
   added: showmessage('added at '+inttostr(index));
   Inserted: showmessage('Inserted at '+inttostr(index));
   Deleted: showmessage('Deleted at '+inttostr(index));
   end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
xlistbox1.Items.Add('add');
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
xlistbox1.Items.Insert(1,'insert');
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
xlistbox1.DeleteSelected;
end;

end.
0
 

Author Comment

by:Meghna172
ID: 10950402
oh! removing fstarted didnt work earlier coz I didnt remove the condition so it was never going to fonchange (so silly of me).

But then the 'access violation' error shifted to

--> fLastSTate := Nothing;

and when i comment the above statement, I don't get error any more!

But am still testing the thing in my application. Could you please let mw know what effect is putting

//fLastSTate := Nothing; , in comments going to make? What should I expect coz i do see something wierd happening

0
 
LVL 22

Expert Comment

by:Ferruccio Accalai
ID: 10950513
the fOnchange is triggered when the state is different than Nothing...commenting it the state will be never Nothing so in every VM_Paint the fOnChange would be passed also if the ListBox isn't changed....
0
 

Author Comment

by:Meghna172
ID: 10950760


so you mean to say if someone scrolls the listbox, then also fonchange will trigger
0
 
LVL 22

Accepted Solution

by:
Ferruccio Accalai earned 125 total points
ID: 10950969
yes, that is...

btw try wuth the last modification posted....

for your facility i'm re-posting the final version...in my tests it works fine in both cases (with or without pre-designed items)...

unit xlistbox;

interface

uses
  SysUtils, Classes, Controls, StdCtrls, Messages;

type
  TListBoxEvent = (Added, Inserted, Deleted,Nothing);
  TOnChangeevent = Procedure(Sender: TObject;Event: TListBoxEvent;Index:
  Integer; Value: String) of object;
  TXListBox = class(TListBox)
  private
    { Private declarations }
    fOnChange: TOnChangeEvent;
  protected
    { Protected declarations }
    fLastSTate: TListBoxEVent;
    fLastItemIndex: Integer;
    fStarted: Boolean;
    fLastValue: String;
    procedure WndProc(var Message: TMessage);override;
    procedure Loaded; override;
  public
    { Public declarations }
    constructor Create(Aowner: TCOmponent); Override;
    destructor Destroy; override;
  published
    { Published declarations }
    property OnChange: TOnChangeEvent read fOnChange write fOnChange;
  end;

procedure Register;

implementation

constructor TXListBox.CReate(AOwner: TCOmponent);
begin
  inherited Create(Aowner);
  fstarted := False;
  fLastSTate := Nothing;
  fLastValue := '';
  fLastItemIndex := -1;
end;

destructor TXListBox.Destroy;
begin
inherited destroy;
end;

procedure TXListBox.WndProc(var Message: TMessage);
begin
  { tests to determine whether to continue processing }
  inherited WndProc(Message);

  if Message.Msg = LB_INSERTSTRING then begin
      flaststate := Inserted;
      fLastItemIndex := Message.WParam;
      fLastValue := string(Message.LParam);
    end else
      if Message.Msg = LB_DELETESTRING then begin
          fLastState := Deleted;
          fLastItemIndex := Message.WParam;
          fLastValue := string(Message.LParam);
        end else
          if Message.Msg = LB_ADDSTRING then begin
              fLastState := Added;
              fLastItemIndex := Items.count-1;
              fLastValue := string(Message.LParam);
            end else
              if Message.msg = WM_Paint then begin
                if (flaststate <> nothing) then
                   If assigned(fOnChange) then begin
                      If fstarted then
                        fOnChange(Self,flaststate,flastitemIndex,fLastValue);
                      fstarted := true;
                      fLastSTate := Nothing;
                  end;
              end;
end;

procedure TXListBox.Loaded;
var
i: Integer;
begin
inherited;
i := Items.Add('foo');
Items.Delete(i);
end;
procedure Register;
begin
  RegisterComponents('Samples', [TXListBox]);
end;

end.
0
 
LVL 6

Expert Comment

by:pritaeas
ID: 10966478
Hi all. Just an update concerning my suggestion:

Finally had time to look into that which I had suggested, and I was wrong indeed. I simply wanted to override the TListBoxStrings and add an event there. Now I've seen the code I know it's harder than I thought, because you'd have to copy the full implementation of that class. I didn't know the class definition was not in the interface.

My apologies,

Hans 'pritaeas' Pollaerts
0

Featured Post

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.

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…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

762 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

21 Experts available now in Live!

Get 1:1 Help Now