Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: TListView.Repaint() horizontal scroll bar problem


This question is answered. Helpful answers available: 0. Correct answers available: 1.


Permlink Replies: 7 - Last Post: Nov 8, 2016 6:09 PM Last Post By: Remy Lebeau (Te...
Dan Ambrose

Posts: 87
Registered: 12/11/03
TListView.Repaint() horizontal scroll bar problem  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Nov 7, 2016 3:16 AM
TListView.Repaint() horizontal scroll bar problem

How can I save and restore the horizontal position before and after the call to TListView.Repaint() ?

I'm using a TListView. ViewStyle property = vsReport; ColumnClick property=true. There is a horizontal scroll bar because of 15 entries to TListView.Columns collection. There are 15 columns which go past the
physical screen size. If you horizontal scroll over to the higher columns. I have a solar micro inverter monitoring application. It receives real time solar micro inverter data from a zigbee wifi. On average 50 micro inverters are reporting in once a minute with an update to 15 columns of real time data. It needs to refresh the screen every time a unit reports in with an update.

I'm using the TListView.Repaint() function to refresh a single row which represents one inverter reporting in an update. I'm using the TListView.Repaint() function because I don't know any better. TListView.Repaint() causes the horizontal scroll position to change to 1st column. This causes an anoying screen flicker.

Since there are 15 columns you must horitontal scroll to the right to see past the 8th column. If you do this then the next refresh call to TListView.Repaint() causes the horizontal focus position to be lost.
I would like to save and restore the horizontial scroll bar position before calling TListView.Repaint() and then restore it after the call to TListView.Repaint(). I cant find the name of the object within the
TListView VCL which represents the horizontal scroll bar. What is it called and how would you access it from the C++ parent form which owns the TListView?

When 50 inverters get really busy there can be as many as 100 updates a minute. I dont want screen flicker problems. To refresh a single row is TListView.Repaint() the best method ?


PS I wanted to attach a screen shot of the app but I don't see how to do that. For future reference Is it possible ?

Dan Ambrose
Saint Louis Missouri USA

Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: TListView.Repaint() horizontal scroll bar problem
Helpful
Click to report abuse...   Click to reply to this thread Reply
  Posted: Nov 7, 2016 1:01 PM   in response to: Dan Ambrose in response to: Dan Ambrose
Dan wrote:

I'm using the TListView.Repaint() function to refresh a single row
which represents one inverter reporting in an update. I'm using the
TListView.Repaint() function because I don't know any better.

You should use Invalidate() instead of Repaint(). That asks the OS to repaint
the ListView at its earliest convenience, rather than force it immediately.

Or even better, you can use the Win32 API InvalidateRect() directly, then
you can invalidate a specific list item instead of the entire ListView as
a whole.

I cant find the name of the object within the TListView VCL which
represents the horizontal scroll bar.

Because there isn't one, as the TListView VCL component is not the one managing
the scrollbar. The underlying ListView control at the OS layer is managing
it.

What is it called and how would you access it from the C++ parent
form which owns the TListView?

Look at the Win32 API GetScrollInfo() and SetScrollInfo() functions:

https://msdn.microsoft.com/en-us/library/windows/desktop/bb787583.aspx

https://msdn.microsoft.com/en-us/library/windows/desktop/bb787595.aspx

When 50 inverters get really busy there can be as many as 100
updates a minute. I dont want screen flicker problems.

All the more reason to use Invalidate() instead of Repaint(). Invalidating
allows the OS to consolidate multiple repaints into a single paint operation,
instead of painting them individually.

To refresh a single row is TListView.Repaint() the best method ?

No.

PS I wanted to attach a screen shot of the app but I don't see
how to do that. For future reference Is it possible ?

You can post it to the Attachments forum.

--
Remy Lebeau (TeamB)
Dan Ambrose

Posts: 87
Registered: 12/11/03
Re: TListView.Repaint() horizontal scroll bar problem  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Nov 7, 2016 3:38 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy,

Thanks for the reply. I'm interested in using the win32 InvalidateRect function for a single item.

BOOL InvalidateRect(
In HWND hWnd,
In const RECT *lpRect,
In BOOL bErase
);

The first parameter. HWND hWnd, Where do I get that from ? Is a VCL base class storing a copy ? Where should I look to get the HWND param ?

The second parameter. RECT *lpRect, How would I calculate / correlate this parameter ? Lets say I wanted to do this for ListView1->ItemIndex which is the currently focused record. Is there some place in ListView or a base VCL class that stores a RECT for each item in the list ?


Thanks Dan Ambrose

Remy Lebeau (TeamB) wrote:
Dan wrote:

I'm using the TListView.Repaint() function to refresh a single row
which represents one inverter reporting in an update. I'm using the
TListView.Repaint() function because I don't know any better.

You should use Invalidate() instead of Repaint(). That asks the OS to repaint
the ListView at its earliest convenience, rather than force it immediately.

Or even better, you can use the Win32 API InvalidateRect() directly, then
you can invalidate a specific list item instead of the entire ListView as
a whole.

I cant find the name of the object within the TListView VCL which
represents the horizontal scroll bar.

Because there isn't one, as the TListView VCL component is not the one managing
the scrollbar. The underlying ListView control at the OS layer is managing
it.

What is it called and how would you access it from the C++ parent
form which owns the TListView?

Look at the Win32 API GetScrollInfo() and SetScrollInfo() functions:

https://msdn.microsoft.com/en-us/library/windows/desktop/bb787583.aspx

https://msdn.microsoft.com/en-us/library/windows/desktop/bb787595.aspx

When 50 inverters get really busy there can be as many as 100
updates a minute. I dont want screen flicker problems.

All the more reason to use Invalidate() instead of Repaint(). Invalidating
allows the OS to consolidate multiple repaints into a single paint operation,
instead of painting them individually.

To refresh a single row is TListView.Repaint() the best method ?

No.

PS I wanted to attach a screen shot of the app but I don't see
how to do that. For future reference Is it possible ?

You can post it to the Attachments forum.

--
Remy Lebeau (TeamB)
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: TListView.Repaint() horizontal scroll bar problem  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Nov 7, 2016 4:02 PM   in response to: Dan Ambrose in response to: Dan Ambrose
Dan wrote:

The first parameter. HWND hWnd, Where do I get that from ?

The TListView::Handle property.

The second parameter. RECT *lpRect, How would I calculate /
correlate this parameter ?

The TListItem::DisplayRect() method.

Lets say I wanted to do this for ListView1->ItemIndex which is
the currently focused record.

Use the TListView::Selected or TListView::ItemFocused property instead.

Is there some place in ListView or a base VCL class that stores
a RECT for each item in the list ?

See above.

--
Remy Lebeau (TeamB)

Dan Ambrose

Posts: 87
Registered: 12/11/03
Re: TListView.Repaint() horizontal scroll bar problem  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Nov 8, 2016 7:59 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Thank you very much for your replies. They are quite helpful. There is one part that I don't understand. I should not have used ListView1->ItemIndex as an example.
I apologize - I work from home while watching our 2 1/2 year old daughter that demands allot of attention span. Let me start over - Forget ListView1->ItemIndex - Lets use base zero sort offset. If I know the row number how to I calculate / get the RECT * to pass to the win32 function?

class TInverterRecord
{
public:
.....
....
}

When an inverter reports in the form checks a list container class for membership of TInverterRecord - If no record exists a new TInverterRecord is created and then added to the list container. This local container class uses a sorting function linked to the TListview tabs. When a column header is clicked it causes records to be sorted by that column. It works quite nice and looks great. The zigbee nodes will randomly transmit updated data which comes into the main form via an event handler. The transmissions include a serial number. There is a local function that takes a serial number and gives back a sorted row number. The main form has a ListOnData handler which passes a TListItem *Item pointer. This used the local container class to lookup the real time data by row number - It returns a structure which as the data to display.

Does TListItem * provide a means to get to the RECT * ? I f yes then I could add the necessary additional items like RECT * to the TInverterRecord class.
During ListOnData(TObject *Sender, TListItem *Item) I could always save/copy the latest RECT * to the TInverterRecord - Then when the zigbee refresh event hander gets called it could limit the invalidate to just that one records and could efficiently call the windows api using the saved values.

Can you teach me how and when and where to copy the RECT * and Window handle pointers ?


Remy Lebeau (TeamB) wrote:
Dan wrote:

The first parameter. HWND hWnd, Where do I get that from ?

The TListView::Handle property.

The second parameter. RECT *lpRect, How would I calculate /
correlate this parameter ?

The TListItem::DisplayRect() method.

Lets say I wanted to do this for ListView1->ItemIndex which is
the currently focused record.

Use the TListView::Selected or TListView::ItemFocused property instead.

Is there some place in ListView or a base VCL class that stores
a RECT for each item in the list ?

See above.

--
Remy Lebeau (TeamB)

Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: TListView.Repaint() horizontal scroll bar problem
Helpful
Click to report abuse...   Click to reply to this thread Reply
  Posted: Nov 8, 2016 12:28 PM   in response to: Dan Ambrose in response to: Dan Ambrose
Dan wrote:

If I know the row number how to I calculate / get the RECT * to
pass to the win32 function?

Like this:

RECT r = ListView1->Items->Item[index]->DisplayRect(drBounds);


Or, since you are using a virtual ListView, it might make more sense to use
ListView_GetItemRect() and/or ListView_GetSubItemRect() directly, so you
don't have to trigger the TListView::OnData event.

Does TListItem * provide a means to get to the RECT * ?

Yes. I answered that in my previous reply (and again above).

If yes then I could add the necessary additional items like RECT * to
the TInverterRecord class. During ListOnData(TObject *Sender, TListItem
*Item) I could always save/copy the latest RECT * to the
TInverterRecord

I wouldn't do that. It is better to query the RECT at the time you need
to use it. Don't cache it, since it changes value as list items are scrolled,
moved around, sorted, etc.

Then when the zigbee refresh event hander gets called it could
limit the invalidate to just that one records and could efficiently
call the windows api

That is the idea.

--
Remy Lebeau (TeamB)
Dan Ambrose

Posts: 87
Registered: 12/11/03
Re: TListView.Repaint() horizontal scroll bar problem  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Nov 8, 2016 5:37 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy, your information was 100% accurate and very helpful. I am now using all of the win32 api functions you suggested. In the process I discovered the exact cause of my problem. Perhaps this could be a problem or place for improvement in the VCL ? Look at this line of code.

TListView->Items->Count = newSize; // This causes horizontal scroll bar to get reset to zero.

The above line of code has allot of overhead. Its not just an assignment. Its causing allot of stuff including the horizontal scroll bar being reset to zero.
Instead of the above line of code I replace it with a call to the following function.

void TIG2::ListSetSize(TListView *p, int size1)
{
int size2 = p->Items->Count;
if (size2 != size2) // Has the size changed ?
{
SCROLLINFO si;
ZeroMemory(&si, sizeof(si));
si.cbSize = sizeof(si);
si.fMask = SIF_TRACKPOS;
GetScrollInfo( ListView1->Handle , SB_HORZ, &si); // Save Hort scroll pos
p->Items->Count = size1; // Update count
SetScrollInfo( ListView1->Handle , SB_HORZ, &si, false); // Restore Hort scroll pos
}

}

I have also implemented this ....

void TIG2::ListRefresh(int item)
{
RECT *r = &ListView1->Items->Item[item]->DisplayRect(drBounds);
InvalidateRect(ListView1->Handle,(const RECT *) r, false);
}

The zigbee event handler calls ListRefresh(int item) with the linear record sort array position . This is using the win32 api to refresh just that records area on the screen.

Its dark outside so my 50 inverters are dead. I have two on a test bench with DC power supplies. With the above code the jumping problem seems to be gone. Once I have a sunny day I will know for sure but I would say this seems to me much better. This software is used by a few people all over the world to monitor enecsys micro inverters. Thank you very much for your help. Your a great asset to Embarcadero!

Dan Ambrose
Software and hardware and engineer
Saint Louis MO USA

Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: TListView.Repaint() horizontal scroll bar problem  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Nov 8, 2016 6:09 PM   in response to: Dan Ambrose in response to: Dan Ambrose
Dan wrote:

In the process I discovered the exact cause of my problem. Perhaps this
could be a problem or place for improvement in the VCL ?

Not likely to happen.

TListView->Items->Count = newSize; // This causes horizontal
scroll bar to get reset to zero.

In a virtual ListView, the TListItems::Count property setter uses ListView_SetItemCountEx(),
but it only specifies the LVSICF_NOINVALIDATEALL flag, omitting the LVSICF_NOSCROLL
flag. In my own virtual ListViews, I usually call ListView_SetItemCountEx()
directly, specifying both flags.

The above line of code has allot of overhead.

Not really.

int size2 = p->Items->Count;
if (size2 != size2) // Has the size changed ?

I think you meant to compare size1 to size2, not size2 to itself.

GetScrollInfo( ListView1->Handle , SB_HORZ, &si); // Save Hort scroll pos
p->Items->Count = size1; // Update count
SetScrollInfo( ListView1->Handle , SB_HORZ, &si, false); // Restore Hort
scroll pos

That is unnecessary if you call ListView_SetItemCountEx() with the LVSICF_NOSCROLL
flag enabled:

void TIG2::ListSetSize(TListView *p, int size)
{
    ListView_SetItemCountEx(p->Handle, size, (Value != 0) ? (LVSICF_NOINVALIDATEALL 
| LVSICF_NOSCROLL) : 0);
}


void TIG2::ListRefresh(int item)
{
RECT *r = &ListView1->Items->Item[item]->DisplayRect(drBounds);
InvalidateRect(ListView1->Handle,(const RECT *) r, false);
}

You are taking the address of a temporary variable that goes out of scope
immediately, thus passing an invalid pointer to the API. It needs to look
more like this instead:

void TIG2::ListRefresh(int item)
{
    RECT r = ListView1->Items->Item[item]->DisplayRect(drBounds);
    ::InvalidateRect(ListView1->Handle, &r, FALSE);
}


--
Remy Lebeau (TeamB)
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02