Welcome back to this magic show called
Programming with Qt and QML. I'm Jesper Pedersen from KDAB. We saw grids or we saw a row
or column or whatever it was we saw in the previous video together with a
repeater. The repeater spit out a number of elements, they went into this column
which meant that we had a number of elements on top of each other in a
column. Here's what you should try now. Pause the playback, create a repeater
that spits out 200 million items and, when you've seen that come back...yes, your
computer will likely have crashed or your qml engine will have complained too
many elements in the scene or whatever. That's just not doable. For that, we have
ListView. Let's not go down the road of discussing usability of your application
if you have two hundred million elements. But nevertheless, with a ListView you'll
only get as many elements out of the or into the view as is needed on the screen,
give and take. Now, let's see what a ListView looks like.
We have it up here. My ListView - I anchor it as usual, I
give it a model, I give it a delegate and I say "clip : true." The "clip : true" is
actually needed with a ListView, otherwise it will actually draw the
items outside of the ListView when you're scrolling up and down and you likely do not want that. Let's see the complete code here. It
is really just what's on the screen plus what we've seen previously.
Here is my model. I got a number of elements generated in my model. Here is my
component. It creates this text element. And here's my ListView. The
ListView just has the code that we saw there. I run it and this time we can see that I
can actually scroll the ListView. All of the elements are visible on the
screen at the same time, so that's less fun. But let's just add a few more to see
how fast I can type. And now when I scroll it, you can see that I can
actually scroll the elements that we have here. I told you that the ListView
actually allows us to not create all the elements up front, and for that we can - I
can - prove it to you. I know how you like me to prove things, so how would I prove it? Press the pause button now, and ask yourself how could I
actually see that? Yeah, I'm just shutting up for a while because I know you're not
going to press the pause button. Yes, Component.onCompleted and Component.onDestruction - these are the two signal handlers that will be executed whenever
either the element is created or deleted. So, Component.onCompleted console.log Welcome welcome and it's a um model.index and
model.name. Similar component - I'm too lazy to type
all that. Component.onDestruction, or Destroyed, same thing. Cannot assign to non...it's not called onDestroyed... onDestruction - I was right! There we are and now we can see it says down here, Welcome 9 10 11 12 13 14. And I scroll a bit more 15
and 16 and if I scroll a lot more, well I guess I should have not covered the
bases that much because I don't want the other one to be saying die so now we'll handle it soon get to kill somebody we are welcoming people, there we are!
Now I I killed Bob and Jane and Victor and Wendy because I ran out of memory
for how much memory I had. That's one of those things that you control with Qt
ListView. You can control down to the pixel level
how many pixel lines that you want in your cache. The cache is called cacheBuffer. So my ListView here...cacheBuffer... Let's just give it, um, 40. That's 40 pixel
lines. It doesn't mean that I'll have 40 elements - It's just 40 lines of pixels
and when I scroll down it will reload in at the bottom extra elements up to 40
pixels lines of elements. And you can see I welcomed Bob now...that's Alice and now
I welcome Jane here's Bob I welcomed just before. I welcome Victor and so on. You
can see I welcome them a bit and I also start killing them off at the other
end, a ways sooner simply because I only had 50...40 lines...
I had 40 lines above and 40 lines below for caching so when I started scrolling
they would fall off the lists. Okay - How many do I have by default?
Well again Component.onComplete console.log cacheBuffer and you can see no...where did the print out...there we are!
I got 320 pixels on each side of my ListView for the cacheBuffer. You're
gonna observe something really unique now! You're gonna observe me being stupid. Okay, I am gonna act stupid because it's really difficult being stupid by
command here. I have even written down my list of how stupid I can be. I'm just
gonna simulate you when you start playing with ListView the very first
time or I'm gonna simulate me the first 250 times I was working with ListView
because there's a number of things that can go wrong and every time you do one
of those things and you have seen before you'll go like mmm what was it again? So,
bookmark this part in this video production so that you can go back and
see this because well, as I said, there are quite a few things that can go wrong.
Let's go back to this example. Did I just close it down? See that was step number
one of being stupid - I shouldn't close it down. There we are. I would like now to
have the possibility of highlight. If I run my application again you can see
there is no highlight of the current item here. I would like to add that
myself. In the next video, we're gonna see how to do that,
but up until that next video we could do it ourselves. QML, on this
view of course, does support this building anything else would be pretty stupid of
the ListView and it's only me that's stupid now so it is building but I'll
show you how you could do it yourself. It goes like this: So, first of all
I would like to update a current index and if we just go and look at my ListView, we'll actually notice that the ListView it has a current index. So I'll
update that. It goes on my component here I'll add it on my text I'll add a MouseArea and I will say onClicked...current index...let's just give it id for one root
here...root.currentIndex equal to index yada yada model.index and yes index is one of those that are so special that it's okay
to say just index rather than model.index and then I want to go down on my
ListView here. It's of course...see I'm stupid...that's okay that was not stupid by mistake that was a true stupid. I need a
ListView here. It's a ListView's current index that I want to update and then
I'll go down here and say component - let's not do that anymore - We'll say onCurr...CurrentIndexChanged console.log currentIndex. And we run! And
now when I click on the item I have proven to you that I can be stupid on
command because nothing works. Poor sit and think over what did go
wrong and welcome back yes I did forget to anchor my my MouseArea so it filled
exactly zero pixels that is likely not what I want. Now it works not anchors.fill onClicked this view of currentIndex equal to index on the currentIndexChanged console.log...oh sorry I of course need to see my application
output. Now in my application output we can see that
when I click on the items here that the currentIndex updates. That was the first
one. Now what I would like to do is that I would like to highlight that currentIndex
so I would like to add a rectangle around the element here. My
rectangle will have a color and the color should be if currentIndex well this
view of currentIndex is equal to index, then it should be great, and otherwise it
should be transparent. Well, that's making yellow just so that we are not blaming
transparent for the mistake of things. We'll make this text element inside of
my rectangle let me just select it all and re-indent so that it we can see it
looks right. My rectangle here has the text element inside it and it looks
pretty good, right? Let's run it. This is a celebration! It was the 150,000th time I had this exact problem. It's so annoying! Every time I forget what is it really?
All my elements are standing on top of each other. Why is that? hmm...all right, yes...My rectangle here doesn't have height, so therefore its height will be 0 pixels.
Well, wait a minute I saw the text. Well that, my dear friend, is because I had
"clip: false," meaning that the children are still able to draw outside of the parent.
Now "clip: true" and you see absolutely nothing. Hmm...let's just try and run this
in GammaRay and see why, because GammaRay is actually kind enough to tell us -
for each of those it tells us. My dear friend, item is visible but out of view.
Okay. Item has size of 0. Oh yeah, that is this height. So let's go and kill it
again. It is the height that I need to specify here. You know better by now it's of course the implicit height that I want to specify - and the
implicit height should be...and let's not call it root, let's call it txt and txt.implicitHeight. Run it, and still nothing's going on. Let's see...
yes...implicit.Width obviously and txt.implicitWidth. Hallelujah! We see
something now if...we even see that I can select an item and it becomes the
current item. However, if you look at this it looks somewhat weird, doesn't it?
The yellow extends only to the end of the name so obviously we wanted to take
the full width here. And how do we do that? Well of course I need to specify
not just the width here, I need to anchor it to the left and to the right. So
instead of the implicitWidth, let's just anchor it. So, anchors left: parent.left, right: parent.right. And now it takes the full width
and I can click on my items here. Everything is beautiful.
Oops...yeah, I can only click on the text, not outside of the text...hmm...
So what's the problem here? Well, the problem of course is the MouseArea. The
MouseArea only fills the text. So, maybe it would make sense to
take them out there right here. Move it one level and now it fills the whole
item and I can click out here. Nice! There is one problem. If you're in for
creating reusable components, then you might want to have this component - the
name delegate here - actually work also with other ListViews. So how do we do
that? See it only works with this ListView because it says lv.currentIndex and for that we need to go to our ListView. We need to look at
the documentation for our ListView. My documentation down here has this
section on Attached Properties and one of the attached properties, View. So
whenever I got a delegate, then that delegate has an attached property
ListView.view, which is actually a pointer to the actual ListView using
that delegate. So I can go down here and I can say, instead of lv here, I can say
ListView.view.currentIndex and you see everything works fine still.
I do the same thing come down to the other place I was here
ListView.view.currentIndex and now it doesn't work anymore. The key
thing to understand is that the attached properties are only attached to the root
element of the delegate - not throughout the whole hierarchy. That's an
optimization that, hmm...yes, okay, it's an optimization. Sometimes optimizations hurt. This one hurts every time you forget about it. But
I'll show you a simple trick to avoid this. The first way I could could solve
this problem with would be to simply say parent.ListView.view.currentIndex and here
we are again back in action. But if I had an element that was several layers deep
then I I couldn't do parent.parent. parent.ListView.view.currentIndex. I would need to have an ID on the root element id: root and then
root or root delegate or whatever. Root delegate.ListView.view. and
so on. There is another way to do this. Up here on my ListView, I will simply
create a new property. Let's call that underscore underscore LV and the value
is ListView.view. Let's make that a readonly property even. readonly property
underscore underscore lv: ListView.view. Why are you giving me arrows? invalid
name readonly property...hmm...I think it's just confused. That's okay - everybody can
get confused at some point...and then down here underscore underscore LV and down
here, instead of the parent.ListView.view underscore underscore LV, let's
just see it running...hmm so am I the one really confused? readonly property
underscore underscore LV:...Hey, that's when you just go and restart. readonly
property type - oh of course, gee - and the type is hideous or we
could even make it ListView and then underscore underscore LV and it's ListView.view. Unfortunately none of our us are
confused anymore. You can see I can click on each of the items. So let that be a
pattern that you will apply to your delegates going forward. You'll
have this readonly property ListView: underscore underscore ListView:
ListView.view with this whole thing you can just, throughout your
delegate, refer to the actual list view as underscore underscore ListView or underscore underscore lv. That's quite a few challenges that I put
myself to and quite a few problems that I had there with the items and trust me
especially the one where you see all the items up at the top standing on top of
each other I doom you you're gonna and jinx you
that you're gonna see that in a number of different setups going forward. Just
remember you need to specify the implicitWidth and implicitHeight or at
least some other way to avoid that problem. In the next section, we'll go
even deeper into the ListView. So thank you for listening in and if you haven't
done so already, you know what am I gonna say now. Please do subscribe to our
channels. you