0

I wonder if with LINQ it is possible to write a short algorithm to achieve that.

Here is what I am talking about.

Let's say you have a List of 500 items. Now, for performance reasons, you do not want to bind this list to your ListView. Rather, you'd want to only display a portion of it and as the user scroll, you load the next or previous items.

Consider the following method : public List<MyItemType> GetPartOfList(bool next, int quantity);

How would you complete so that it returns you a List containing a certain quantity of item. The bool parameter tells whether you want the next or previous set of items.

Of course, to avoid OutOfBounds Exception, you will have to return a list with less items that asked but that's OK.

Is there already such an alogorithm available as I would not want to rebuild the wheel ? Thanks !

For now, here is what I did :

    private int _currentIndexBeg = 0; // required to know where the current portion of the list starts
    private int _currentIndexEnd = 0; // required to know where the current portion of the list stops
    public ObservableCollection<T> GetPartOfConversation( bool less, int quantity )
    {
         if( quantity < 0 )
             throw new ArgumentException( "quantity", "quantity MUST NOT be NEGATIVE!" );

         if (quantity > _originalList.Count)
         {
             _currentIndexBeg = 0;
             _currentIndexEnd = _originalList.Count;
             return _originalList; // list is small enough
         }

         if (_currentIndexBeg == 0 && _currentIndexEnd == 0) // first time call
         {
             _currentIndexBeg = 0;
             _currentIndexEnd = quantity;
             return new ObserableCollection<T>(_originalList.GetRange(0, quantity); // safe, no risk of exception, already handled before
         }
         else
         {
             // now onto the fun part
             if (less) //meaning we want the PREVIOUS "quantity" items
             {
                if( _currentIndexBeg == 0 ) // which means top in the list, no more items
                    return null; //if null, it means there is no other items to be displayed into the ListView. Requires a null check, that's still OK.
                if (_currentIndexBeg - quantity < 0) // alright, we want the n previous items. But if there are previous items but not as much as we would want we could either return what remains, 
                              // or what remains plus what already exists to make sure we return a List of quantity elements
                {
                      // TODO
                    return null; // TODO
                }
                 // here means that there is at least (equal or more) quantity elements that remains and can be displayed
                 // TODO
             }
             else //meaning we want the NEXT "quantity" items
             {
                 if( _currentIndexEnd == _originalList.Count ) // which means bottom of the list, no more items
                    return null; //if null, it means there is no other items to be displayed into the ListView. Requires a null check, that's still OK.
                 if (_currentIndexEnd + quantity > originalList.Count) // aright, we want the n next items. But if there are indeed next available items, but not as much as we would want
                                       // we could return what remains (less than quantity) or what remains + what already exists
                 {
                     int remain = originalList.Count - _currentIndexEnd - 1; // minus 1 because zero based indexation
                     return new ObserableCollection<T>(_originalList.GetRange(_currentIndexEnd , remain);
                     // OR
                     return new ObserableCollection<T>(_originalList.GetRange(_currentIndexEnd - (quantity - remain) , quantity); // to match the requested quantity
                 }
                 // here means that there is at least (equal or more) quantity elements that remains and can be displayed
                 // TODO
             }
     }
 }

I will update the code as I progress but I'd like to have help if I am doing something utterly wrong. Thanks !

[UPDATE]

For more clarification :

  1. My ListView displays a conversation between two users ;
  2. My internal List contains custom-maded item representating a "chat bubble" ;
  3. The conversation can grow as messages are added ;
  4. Each time a message is added, I want to display it, so the ListView scrolls automatically to the last entry (last message) using ScrollIntoView() ; I cannot use Virtualisation as described here : https://stackoverflow.com/a/30765631/3535408
Community
  • 1
  • 1
Mackovich
  • 3,319
  • 6
  • 35
  • 73

2 Answers2

1

Have you explored Virtualization? This is built-in functionality that achieves what you are describing by only rendering what is visible. You would still bind your entire collection, however the framework will handle rendering logic. You will just need to set the VirtualizingStackPanel.IsVirtualizing property to true. Judging by your code you are already keeping your entire list in memory, so batching via LINQ will only add overhead. See the links below for additional detail.

https://msdn.microsoft.com/en-us/library/cc716879(v=vs.110).aspx

How to enable UI virtualization in Standard WPF ListView

UPDATE:

It is not possible to have Virtualization and also CanContentScroll='False' (Is it possible to implement smooth scroll in a WPF listview?). Here are a few workarounds that come to mind:

  • Fetch the most recent N items, but allow the user to lazy load older items; this is a technique employed by many chat applications. As the user loads more items, however, you will experience the performance hit you are trying to avoid. With this approach you are hedging a bet that the user will only occasionally care about older conversations.

  • Limit the items in the ListView to a manageable number and allow the user to visit a different page to view an extended chat history.

  • If you have the real estate you can also increase the height of the ListView and/or decrease the height of the items to display more items at a time, this should effectively slow your scroll speed. (untested)

Community
  • 1
  • 1
alan
  • 6,705
  • 9
  • 40
  • 70
  • Yes but I had to disable Virtualization because of http://stackoverflow.com/q/30738073/3535408 :( So according to http://stackoverflow.com/a/30765631/3535408 I am supposed to cached my ListView :( – Mackovich Jun 16 '15 at 12:56
  • Now you had me realized a major flaw: when starting the app will load only (let's say 50) items (the last 50). If a message is added and the user get scrolled all back to the bottom, this will certainly fail, big time ... Virtualization works like a charm, but scroll speed is way too FAST and I have rendering issues described in http://stackoverflow.com/q/30738073/3535408 – Mackovich Jun 16 '15 at 13:00
  • 1
    @Mackovich, Do I understand correctly you do not want scrolling to be by item but instead smooth (aka by Pixel)? Also, if there are preconditions to your situation you should always include them in the question. Please update your question with the details from your previous post, this will allow new contributors to give you your answer sooner. – alan Jun 16 '15 at 13:02
  • you are right. I have just updated my question. I thought I could talk only about algorithmia in this question. Yes I'd like smooth scrolling but not too slow and not too fast as well. With `Virtualization` the scroll is way too fast whereas without it's too slow... I am lost :/ – Mackovich Jun 16 '15 at 13:08
  • @Mackovich, unfortunately you'll find this is not a possibility to have smooth scrolling and also Virtualization. However, if you disable smooth scrolling and limit the number of items in your list you may get your desired result. See my updated answer. – alan Jun 16 '15 at 13:14
  • Your solution sounds great and I will definitely think about opening a new window to show the complete message history. But what do you mean by "real estate" ? :) – Mackovich Jun 16 '15 at 14:28
  • @Mackovich By real estate I mean how much extra screen/view space available to expand the chat window into (i.e. if you can make it taller). – alan Jun 16 '15 at 15:12
  • Ah no I can't make it larger. It takes all the place it is supposed to and the app runs in maximized state. For now I display only the last 50 messages. Upon reaching the top of the conversation, a menu appears inviting the user to click to display complete message history. You have my thanks for your help. I also manage to almost complete my alogorith and will soon post it here. I am still working on it, should I use later ;-) – Mackovich Jun 16 '15 at 15:30
0

In linq the best way to do that is using Skip and Take methods.

collection.Skip((pageNumber - 1) * itensPerPage).Take(itensPerPage)

In your code you just need to know the 'page' number as your user scrolls.

bateloche
  • 699
  • 4
  • 19
  • Alright, but how can I ascertain the page number ? There is no page in my code. It's a ListView representing a conversation between two users. The list can grow any time AND there is only one page displaying the conversation. So the paging system will not work unfortunately :( – Mackovich Jun 16 '15 at 13:02