Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Referencing pointers in TListItem.Data 3

Status
Not open for further replies.

djjd47130

Programmer
Nov 1, 2010
480
US
I'm still a little fuzzy with using pointers, and although I thought I had this down right, I'm having a strange issue. I have a TListView. The items are added in run-time when the app starts and the data is also assigned to each item. Then I have a thread which runs in the background. The thread reads the database and gets some specific information. When new info is available in the thread, it triggers an event which tells the app to refresh the item to reflect the new info.

The thread IS triggering the proper event with no doubt. The trouble is when it comes to identifying which TListItem in the TListView needs to be updated. For some reason, only the first item in the list gets updated, all the rest remain untouched. It's like the loop which looks for the item can only find the first item, not the others...

Declaration of type stored in item data pointer:
Code:
  PAlertType = ^TAlertType;
  TAlertType = (atSalesMonthLast, atSalesMonthYear, atSalesQuarter, 
    atSalesYear, atOverdueApprovals, atUnlinkedBO, atPODueToday, 
    atOverduePO, atPurchConsign, atPickupDelivery);

Populating items of list:
Code:
procedure TfrmMain.LoadListItems;
var
  X: Integer;
  I: TListItem;
  procedure A(const S: String; const T: TAlertType);
  begin
    I:= Lst.Items.Add;
    I.Caption:= S;
    I.Data:= @T; //DATA ASSIGNED HERE
  end;
begin
  Lst.Items.Clear;
  //Eventually I will be reading user preferences here
  //  for which items to list and what order to list them,
  //  therefore I need to use the pointer to know which item is which.
  //  Otherwise, if the list never changes, I wouldn't care about this.
  A('This month vs last month',               atSalesMonthLast);
  A('This month vs this month last year',     atSalesMonthYear);
  A('This quarter vs this quarter last year', atSalesQuarter);
  A('This year vs last year',                 atSalesYear);
  A('Overdue Approval Invoices',              atOverdueApprovals);
  A('Unlinked Back Order Items',              atUnlinkedBO);
  A('Purchase Orders Due Today',              atPODueToday);
  A('Overdue Purchase Orders',                atOverduePO);
  A('Consignments to be Purchased',           atPurchConsign);
  A('Pickups and Deliveries',                 atPickupDelivery);
end;

And refreshing an item in the list:
Code:
procedure TfrmMain.ThreadAlert(Sender: TObject; const T: TAlertType;
  const S: String; const ID: Integer);
var
  I: TListItem;
  A: PAlertType;
  X: Integer;
begin
  //Find the item in the list which needs to be updated
  for X:= 0 to Lst.Items.Count - 1 do begin
    I:= Lst.Items[X];
    A:= PAlertType(I.Data); //DATA READ HERE
    if A^ = T then begin //COMPARE ITEM DATA WITH DESIRED VALUE
      I.Caption:= S;
      I.ImageIndex:= ID;
      Break;
    end;
  end;
end;

Now the problem I'm facing is that only the first item gets refreshed, all the rest are not. In the loop, I'm checking each item to find the item which I need to refresh. The caption is then updated to reflect the new information (which comes from a thread). The procedure "ThreadAlert" is an event handler which comes from this thread, triggered when there's new information available (thus needing to refresh that info).



JD Solutions
 
This writes the contents of a ListView to a file. You can use something similar to parse the TListView to update the data. If I remember right, all the properties are writeable as well as readable.

Code:
procedure TForm1.Button2Click(Sender: TObject);
  var
    a: TListItem;
    outfile: TextFile;
    i: integer;
  begin
    if SaveDialog1.Execute then
      begin
        if FileExists(SaveDialog1.FileName) then
        if MessageDlg('Overwrite this file?', mtWarning, [mbYes, mbNo], 0) = mrNo then
          exit;
        AssignFile(outfile, SaveDialog1.FileName);
        Rewrite(outfile);
        writeln(outfile, '"', ListView1.Columns[0].Caption, '","',
                              ListView1.Columns[1].Caption, '","',
                              ListView1.Columns[2].Caption, '","',
                              ListView1.Columns[3].Caption, '","',
                              ListView1.Columns[4].Caption, '"');
        for i := 0 to (ListView1.Items.Count - 1) do
          begin
            A := ListView1.Items[i];
            writeln(outfile, '"', A.Caption, '","',
                            A.SubItems.Strings[0], '","',
                            A.SubItems.Strings[1], '","',
                            A.SubItems.Strings[2], '","',
                            A.SubItems.Strings[3], '"');
          end;
        CloseFile(outfile);
        MessageDlg('CSV file saved.', mtInformation, [mbOK], 0);
      end;
  end;

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
I just noticed you had the break in there. By design it will stop the loop. Remove it and it should work for everything in the ListView.

Code:
for X:= 0 to Lst.Items.Count - 1 do 
  begin
    I:= Lst.Items[X];
    A:= PAlertType(I.Data); //DATA READ HERE
    if A^ = T then 
      begin //COMPARE ITEM DATA WITH DESIRED VALUE
        I.Caption:= S;
        I.ImageIndex:= ID;
        [b]Break;[/b] //<---stops the loop
      end;
  end;

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
No, the break doesn't necessarily matter, the loop is finding one item out of all the items. It's not refreshing every item in the list, it's refreshing just one item in the list at a time. I'm using the data pointers to keep track of which item is which. When this procedure is called, the loop only intends to find just one of the items in the list. I put the break in there because once that item is found, there's no need to continue with the rest of the items.

JD Solutions
 
I'm using the TAlertType in the item's data pointer so that the loop knows which item to find to refresh its info. However it has to be converted to a pointer first in order to place it in the item's data field. Then when looping, I have to read that value, convert it back to just a TAlertType, and then identify if it is the item that needs to be updated. Each item in the list is unique, and has its own TAlertType assigned to its data pointer. This way, I can find out which item I need to update.

JD Solutions
 
Just did some debugging, and realized that when I convert TListItem.Data back to the TAlertType, it always comes out as atSalesMonthLast for every item, even though I set each one to be different. So when I read "A" in that loop, A always comes out to atSalesMonthLast for all the items. Somewhere along the line the pointers get messed up for each list item.

JD Solutions
 
So the problem is you aren't getting the right data back out of the item data pointer?

Code:
var
  p: Pointer;
  c: TAlertType;
begin
  // for a pointer to have data it needs to point to usable memory
  GetMem(p, Sizeof(c));
  // set TAlertType
  c := atPickupDelivery;
  // set the pointer CONTENTS to TAlertType
  PAlertType(p)^ := c;
  // show the value of the contents, enumerated types are bytes, otherwise typecast TAlertType and use case statement to write text string.
  ShowMessage(IntToStr(Byte(p^)));
  // must free memory
  FreeMem(p);
end;

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
although glenn's answer is 100% correct, there is no need to mess with pointers in this case as TAlertype is an enum here.


you can get away with:

Code:
// write
I.Data := Pointer(Integer(T));
//read
T := TAlertType(Integer(I.Data));

/Daddy



-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
to clarify my last post:

a pointer is a 32bit integer that points to a location in memory.
I use the pointer as an integer value, hence the typecasting.
(oh, and a star for you Glenn)

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
whosrdaddy said:
there is no need to mess with pointers in this case as TAlertype is an enum here.

a pointer is a 32bit integer that points to a location in memory.
I use the pointer as an integer value, hence the typecasting.

Yes I would probably do it this same way if I were coding this given that the enum is only a byte and can be stored there (along with a heavy commenting indicating this). But I thought I'd show the "standard" intention for using a pointer, so you can put data greater than 4 bytes there.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Nice trick, never thought of that (and probably wouldn't have ever thought of it myself). Thanks for the help, this website could use more Glenns and Daddys.

- JD


JD Solutions
 
also keep in mind you can use objects.

Code:
 var Obj : TMyObject; 
begin
 Obj := TMyObject.Create;
 I.Data := Pointer(Obj);

just make sure you keep track of them (with an objectlist or so)

/Daddy

-----------------------------------------------------
What You See Is What You Get
Never underestimate tha powah of tha google!
 
Thanks, I did use objects actually when I couldn't get the pointers to work. I just didn't like the weight of it, having to create/free them, etc. I needed something quicker and using lower memory.

JD Solutions
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top