Announcement

Collapse
No announcement yet.

Listview - getting started in BP

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    [TUTORIAL] Listview - getting started in BP

    I am creating this guide since I had trouble grasping the listview at first, seeing many have the same struggle and there not being any good information about them in a blueprint context.
    The listview should be pretty straight forward to use once you got the basic grasp of it.

    Note!:If a widget is generated for one instance more than once, the engine will crash. So any instance should only be in the listview once.
    Note! If the node "Is List Item Selected", part of the entrywidget class, is used, the engine will crash.
    Note! Multi Select Mode seems to be bugged, clicking an item clears the selection so it is not possible to multi-select by click. (implementing custom logic would work).

    Following bug reports has been made:
    - Is list Item Selected / Expanded crash: https://issues.unrealengine.com/issue/UE-73025 (removed in 4.23)
    - Multi select, click clears selection: https://issues.unrealengine.com/issue/UE-72610 (backlogged)
    - EditorWidget listview with actor items, if an actor is deleted, trying to update the listview crash: https://issues.unrealengine.com/issue/UE-73014 (fixed)
    - When a list view item in UMG is selected using "Set Item Selection" it will get the item of the first entry in the list or the last entry manually selected by the user. https://issues.unrealengine.com/issue/UE-86431


    For versions: 4.21, 4.22
    For changes to 4.23 and 4.24, see Reply #13

    For using the Listview or Tileview for "inventory", see Reply #14, after learning the basics.

    Contents: (Reply#, Subject index. subject - description)
    Listview
    1,1. Basic Setup - Setting up the EntryWidget Interface and adding it to the ListView.
    1,2. Listview overview - Examples of some useful functions.
    1,3. The Pitfall - Common mistake adding members to the Listview.
    1,4. Listview generation - Explaining how EntryWidget works and events firing.
    1,5. Creating a proper list - Proper method for setting up a very simple list.
    1,6. Going advanced - Actor representation - Displaying Actors in the list.
    1,7. Going advanced - Selection - How selection is displayed in the list.
    1,8. On Item Selection Changed - ListView Or EntryWidget? - Differences between the selection changed event between the Listview and Entrywidget.
    1,9. Going advanced - World selection - World selection and Listview syncing.
    1,10. Going Advanced - Drag & Drop - Basic Drag and Drop example.
    1,11. Multiselect - Making a custom multiselect solution.
    1,12. Node Information - Additional information about specific nodes.

    Treeview
    2,1. Treeview - Getting Started - Basic Treeview setup, how to set hierarchy/get children
    2,2. Treeview - Spacing [Backtracking through parent] - Adding some spacing to children depending on [Actor parenting] hierarchy depth.
    2,3. Treeview - Expandable symbol - Changing or hiding a symbol, based on the expanded state and if Item has children.
    2,4. Treeview - Spacing [When getting the items children] - More universal way of tracking depth, does not rely on Actor parenting.
    2,5. Treeview - Expand only if Selected [revert expansion method] - Workaround to default expansion behaviour (not recommended)
    2,6. Treeview - Don't change expansion [Override click method] - Workaround to default expansion behaviour (recommended)

    12,1. Changes in 4.23

    13.1 Listview & Inventory Items - Solution specifically for "inventory Items" often found in rpg games. Display.
    13.2 Drag & Drop solution with mediators - Solution to have Drag & Drop behaviour with inventory using mediator objects.





    1. Basic Setup
    Create a widget to hold the listview. For this guide, this is the widget that is added to viewport. Add a listview. You can't compile now because it wants an entry widget.
    Create a widget to be the EntryWidget. Delete the canvas panel and add a Border and a Text block. Then, follow the image steps.

    Click image for larger version  Name:	ListView1.png Views:	1 Size:	213.9 KB ID:	1595438
    1. Go to the Entrywidgets class settings.
    2. Add the interface "UserobjectListEntry".
    3. Note how the Entrywidget now has an interface function. This may be very useful but we need to return a value.
    4. Add the event "Event On List Item Object Set" which is also part of the interface we added. Right click on the List Item Object and promote it to variable. I renamed it to Item.
    6. Go back to the function in 3. and return the Item.
    7. Go to the listview widget, select the listview and the entrywidget you created can now be selected as the entry widget class.

    That's the basic setup done and you are now ready to use the listview.

    This is what the EntryWidget looks like:​​​​​Click image for larger version  Name:	1AEntryWidgetDetail.png Views:	5 Size:	31.5 KB ID:	1702600
    But from now on, basic umg knowledge is presumed and we will just focus on the listview.

    A disclaimer for new users to the UE engine is that this tutorial is that it does not follow best practices and can be inconsistent. My goal is to have as few nodes on as little screen space as possible for screenshot purposes. Some parts of this tutorial uses variable bindings on Text Block text or the Border brush, which is generally not good. Other parts Sets the text for the Text Block or the colour on the Border, which is generally better. Bindings is like a Tick, always checking its value. If you make a big game using this a lot in your UI, you'll run into performance issues. Setting the value only happens during that event.
    There are some other things like how the Item is set during the On List Item Object Set. Maybe, cast it here so you don't have to cast at multiple other places? Either way, the result is the same, just slightly different execution. The goal here is to teach the overall methodology which once learned, you can use your own solution for.

    2. Listview overview
    First of all, the listview works with instances. You create any object (Uobject, Actor etc) to add as an item to the listview and the listview will, if needed, create an entrywidget to represent that item.
    Here's a selection of functions that the listview has. Only one for the generated widgets. It is designed to work with the items, the widgets are just there to represent the item.

    Click image for larger version  Name:	ListView2.png Views:	1 Size:	265.7 KB ID:	1595439
    3. The Pitfall
    Now, you might be tempted to use the entrywidget you created to use as an item, changing some properties and expecting the changes to be displayed.
    Something like this:

    Click image for larger version  Name:	Listview31.png Views:	1 Size:	57.8 KB ID:	1595440
    (How to expose variables):
     
    Spoiler

    You might expect that this will create a listing of numbers from 0 to 10 as the displayed text. But instead the following is the actual result:

    Click image for larger version  Name:	Listview32.png Views:	1 Size:	3.0 KB ID:	1595441

    In this example a widget was created and added as an Item. The listview then generates widgets to represent those items.The Item has some properties changed (the text), the entrywidget - the widget that is displayed - remains default.
    Click image for larger version  Name:	Listview-ItemWidget.png Views:	1 Size:	78.3 KB ID:	1606577
    While widgets can be used as items, I think it's mostly confusing for learning. Lets start with something else. (the entrywidget could be used as a slot to which the widget item is added. But that's a little more advanced as of now).

    4. Listview generation
    The listview generates a widget for each item that needs to be displayed. When an entrywidget is generated, two events are fired. One in the listview called On Entry Generated. The other in the entrywidget which we saw earlier - "Event On List Item Object Set".
    When scrolling, entrywidgets are dynamically On Entry Released, On Entry Generated, triggering On List Item Object Set. Also On Item Selection Changed - IF that item is selected.

    5. Creating a proper list
    Here the item will be an UObject with a single text property which is set during a loop.
    To update the entrywidget, go to the entrywidget and on the event "Event On List Item Object Set" which is currently storing the item, cast the item to the Objecttype added as item and get its text.

    Click image for larger version  Name:	ListView4.png Views:	1 Size:	180.7 KB ID:	1595442
    Here, an UObject is used to just hold some data. But the item doesn't have to "just hold data". The ltem can be any instance and even different classes. So I could have an item list of UObjects, Actors, characters and vehicles. How you format the entrywidget visually is really up to you, with what kind of elements to display.
    Now that the basics are out of the way, lets do something more interesting. An Item can be any object, so what if we added some actors and these actors to be listed in the view?

    6. Going advanced - Actor representation
    For me the actor is just named "Ball" and has a static mesh sphere. These are dragged into the scene, about 10, and in the listviews construct event "get all actors(ball)". I wouldn't recommend this for practical use but it is good enough for an example.
    Now, we could loop through the array, adding each instance with the "add item". Or, we can just use the listviews "Set list items".
    The entrywidget will need to cast the item to ball class. For now, the entrywidget only has a textblock and I'm going to set its text to the objects "get display name".

    Click image for larger version  Name:	ListView5.png Views:	1 Size:	131.5 KB ID:	1595443

    We could make it more interesting, like randomizing the ball size and adding another textblock to the entrywidget, displaying the instance size. But that's just more data pulling which we have already learned.
    What if, instead, we were able to click on the listview and the corresponding ball is selected? Or Vice verse?

    7. Going advanced - Selection
    The listview has some inbuilt selection functionality. You can even choose between single and multiple selection in its properties. Single selection for now.
    The following image shows a simple setup. When "Event On Item Selection Changed" is called because you clicked on one of the items in the list, one entrywidget will return false and another will return true. One was deselected, the other selected. When debugging, if we only printed the boolean, how would we know which was selected and which was deselected? The impulse might be to include the widget reference as debug data, but that really provides poor information. Instead, try to work with the Items, which is done in the image.

    Click image for larger version  Name:	ListView6.png Views:	1 Size:	59.5 KB ID:	1595445

    Running the above will show that it works but we really need better representation.
    The Entrywidget gets a new variable - "Border Color", a linear Color which the border brush color is bound to.
    The ball has also gotten an event that can be called.
    The idea is that when the Item is clicked, change the scale of the Ball to show that it is selected in the world.

    Click image for larger version  Name:	ListView7.png Views:	1 Size:	221.6 KB ID:	1595444
    Running the above will change the size of the ball actor when a list item is clicked.

    -Problem 1-
    But there is a problem. If you scroll down you will eventually see a widget appear, marked as if it is selected.

    That is because the widgets are not destroyed when they are scrolled out of view, they are On Entry Released and then On Entry Generated when needed again. So they still have their old properties. The display name text changed, because we do that "Event On List Item Object Set", but the border color remains untouched.
    So how do we fix that? Well, there are a many ways. There could be a "Reset Properties"-function that is run when Event On List Item Object Set? If we think about this execution logic, it would look like this:
    1. widget(s) generated, setting Item, reseting border color.
    2. Item selected, changing border color to green. and changing ball size.
    3. widget scrolled out of view, released, possibly generated again for some other item but that's fine.
    4. Selected Item is scrolled back into view - widget generated, setting Item, reseting border color!
    5. But there's another event that happens after that. The listview knows that the item is selected so it calls Event On Item Selection Changed on the widget. (only for those items that are selected, not for the ones which are not).

    -Solution 1-
    A reset function could work in this case, but would still be an issue for other types of executions. Imagine if, say a tree, would grow each time it was selected. But then it would also grow each time i was scrolled back into view...

    The reason for pointing all of this out, is that it is important to test just what all the functions do and that different solutions is needed depending on the situation.

    Update: Use the "Event On Item Released" to reset the entrywidget to its default visual.


    -Problem 2-
    If an item is selected - changing the ball size - and you scroll down and select another item, the ball size of the previous selected should reset, but it doesn't!

    Because we have implemented behaviour into the EntryWidget, and the entrywidget points at different items - items which might not be represented at the moment - the widgets may not be able to communicate with the relevant item.
    This case example should show us that the EntryWidget may not be a proper place to implement item changes to.
    So the border color stays, the ball size change goes.
    To where? To the listview to handle.

    -Solution 2-
    The listview has the event "On Item Selection Changed", which provides the item and if it is selected or not. But we also want to reset the previously selected ball size. Well, the listview only provides so many functionalities. This is a behaviour we need to implement ourselves. Current setup:

    Click image for larger version  Name:	ListView9.png Views:	1 Size:	180.2 KB ID:	1595446
    Remember that this is just for single selection. If we wanted multiple selected items the node setup would have to be modified.


    8. On Item Selection Changed - ListView Or EntryWidget?
    At first glance the Selection Changed events of the treeview and the entry widget may appear very similar, but they are in fact very different.

    Click image for larger version  Name:	ListView-OnItemSelection.png Views:	1 Size:	63.1 KB ID:	1604412
    The EntryWidgets event should be used for setting up visual representation of the entrywidget.
    The ListViews event should be used for logic such as informing the Item about its selection state. If the deselected item is relevant, you need to track it manually using a variable, which we have done in a previous example.


    9. Going advanced - World selection
    So we can select an item in the list to show that it is selected, and store the current selection in a variable for any class to use. But what about clicking on the actor itself?
    Well, how do you want to handle that? should it be with the spheres on clicked event? Or a linetrace from the camera?
    If we do it with the spheres On Clicked event, then the sphere needs to either send information to -some class- that it was clicked, or have -some class- bind to the ball being clicked.
    I'm going to go with a linetrace from the third person character, in the third person template, because that's just the easy way to do it. The third person character in my case is also the class that creates the listview widget.

    Now there is a slight issue.
    We know that by clicking on an entrywidget, that item will be selected, telling that actor to change size. To demonstrate the issue, the ball will now print the value of an int, which is incremented if selected and decremented when deselected. So deselected =0, selected =1.
    Say I do the following:

    Click image for larger version  Name:	ListView10.png Views:	1 Size:	211.1 KB ID:	1595447

    1. Detect ball through a linetrace.
    2. Tell the ball that it has been selected.
    3. Inform the listview to mark the ball as selected.
    What now happens is that the listview tells the ball to be selected, again. As you can see a 2 int.
    When it comes to selection - if the item needs to know that it is selected - in my experience it is better to go through the listview to have IT perform the selection.

    Let's follow some execution flow with some requirements.
    - The character needs to be updated about the selection, say saving the reference in a variable.
    - The Item needs to be informed that it has been selected, say becoming highlighted.
    - The List needs to be updated about the selection.

    First is a selection in the listview.
    Click image for larger version  Name:	ListView-SelectListView.png Views:	1 Size:	9.2 KB ID:	1604603

    Second, the item selected though the world. For example, a linetrace from the character. If the Item and character are informed of the selection after the trace, and then the Listview is told to select the item it creates unnecessary logic, which might be problematic.
    Click image for larger version  Name:	ListView-SelectCircular.png Views:	1 Size:	15.5 KB ID:	1604604

    Third, the item selected though the world. For example, a linetrace from the character. The character informs the Listview to select the Item which triggers the execution of the flow in the first example.
    Click image for larger version  Name:	ListView-SelectusingList.png Views:	1 Size:	11.3 KB ID:	1604605
    But in the end the setup depends on what you require the selection to do.


    10. Going Advanced - Drag & Drop
    Drag & drop is as easy to implement for a listview as anything else. If we want to be able to reorder the items then, instead of having the listview handle the items, we need to take more control in the form of an array that we can remove and insert items into.
    The entrywidget now has a "Item dropped on" dispatcher which the listview binds to when the entry is generated

    Click image for larger version  Name:	ListView12.png Views:	1 Size:	366.2 KB ID:	1595449

    Bindings in the Entry Widget
    If your Entry Widget has a event dispatcher that the widget holding the listview binds to during the "on entry generated" event, you do not have to unbind them when the widgets are released. Seems like the entrywidget internally unbinds any bindings during the released event.
    However, if the Entry Widget binds to event dispatchers - say the item they represent - those have to be unbound manually.


    11. Multiselect

    So what about multi select? Well, it's not looking good: https://issues.unrealengine.com/issue/UE-72610
    I'd expect that holding shift while clicking items would be the default behavior but such a feature is backlogged.
    So I have to do it myself. This is going to be somewhat of a brute force and will not be pretty.
    There are several methods I have considered but many have met one issue or another. So first of all I'll list the issues.
    1. If the function "Set Item Selection" is used, the "On item selection changed" of the ListView will usually not provide the item that was changed, but the first one in the list or the item most recently clicked on.
    2. When the EntryWidget is pressed ( Mouse button down), it will clear the selection of other items - silently. When mouse button is released, all the items will be marked correctly. That means every time I click, the list flickers/blinks.
    there's also some other annoyances that would take too much space to write about. But to circumvent these problems, first the entrywidgets default clicks can not be used. We need a Button to consume the click. We also have to provide some additional logic to the listview so that we don't get the wrong Item when "On Item Selection Changed" event fires.



    Entrywidget is standard setup.



    The following THREE pictures for the setup for the listview.
    - A List of Objects to easily track selected Items.
    - You have a choice between two methods.
    Either you use a boolean to know if the "On Item Selection Changed" might provide the wrong item, which it may when "Set Item Selection" is used.
    Or you make your own function to know when Item selection is changed. Both examples are provided but you only need one.
    Know that when "On Item Selection Changed" provides the wrong Item, it provides the wrong Items selected state, so another boolean for if the clicked item is selected is needed.
    Explanation after the images.






    On Entry generated -> Bind to the buttons click event when the entry is generated.
    On Clicked ->The Clicked dispatcher provides the Item clicked and we save it as a variable, so that we know what item to use, when we use "Set Item Selection".
    Then Query if shift [or whatever method to enable multi select] is used. If it is, change the selected state. If it isn't, set the clicked item to be the only selected item.
    Change Item Selection -> Selected becomes deselected, deselected becomes selected.

    Deselect Item -> Remove the Item from the Selected Items list. Check that the item "On Item Selection Changed" provides may be wrong. Set the clicked items selection boolean to false. Then call "Set Item Selection", followed by the custom "On Item Selection Changed".
    Select Item -> pretty much same as above.
    Select Item (Single) -> an Item was clicked on without holding shift. That should select only that item. So first clear the selection. Know here that using "Clear Selection" for the ListView will make the "On Item Selection Changed" event fire with a null item, therefore the isvalid check later. Then, add the clicked item to the selection and once again prepare for the "On Item Selection Changed" event to fire with a wrong item.

    My Custom On Item Selection Changed -> If used, the event that is used to perform whatever logic is needed with the correct Item.
    On Item Selection Changed -> If used, Query if the Item is valid. It will not be when calling "Clear Selection" on the Listview. Then query of this event was called internally, or manually using "Set Item Selection". If it is a valid item, proceed. If not, the Item was set Manually and it was the last clicked item.



    Well, that was messy. Hope it makes sense. I've played around with it a bit and can't see any obvious issues. But if you really want MultiSelect, this is probably the easiest solution. By the way, the Listviews selection mode between Single and Multi doesn't seem to matter. Same behaviour in both cases but change it to Multi anyway.



    12. Node information

    Click image for larger version  Name:	ListView-ScrollIntoView.png Views:	1 Size:	58.8 KB ID:	1604413


    **Ignore attachments**
    Attached Files
    Last edited by ste1nar; 02-16-2020, 08:05 AM.

    #2
    [Reserved for treeview and Tileview]

    The treeview and gridview are very similar to listview.

    Note! The function "is List item Expanded" crashes the editor.

    1. Treeview - Getting Started
    You setup a treeview the same way as a listview.
    The point of a treeview is to have Items be "children" of other Items, like a tree!
    You add children to an item in the function "On BP on Get Item Children 0". This is a binding you find on the treeview events in the designer graph.
    Once that binding is created, you will get a function with an in Item, and an array of items as children.
    Again, if a entrywidget is created more than once for an instance, the engine will crash. This is very easily done when learning the treeview.
    Using the ball actor in previous examples, I have added a bunch of mesh components which will be the children.
    This is the basic setup I have created:

    Click image for larger version  Name:	Treeview1.png Views:	1 Size:	265.5 KB ID:	1595475
    **Addendum: On BP Get Item Children needs to return an empty array if the cast fails, or this setup will not work.
    Here, Ball 12 has been selected and the treeview expands it by getting all the children - children components in my case - and generating entrywidgets for them. Do note that the "On BP on Get Item Children 0" is also executed for each item - each child - that appears.
    While there's some basic formatting happening in the entrywidget - a textblock either has a > or V symbol, it could be done better.
    - Some spacing for each child to better indicate what item they are children of.
    - If an item does not have children, it really should not have either > or V symbol.

    2. Treeview - Spacing [Backtracking through parent]
    The spacing is set by adjusting the border left padding. The value depends on how deep in the hierarchy the item is. In this case we can find out the depth by calling a recursive function which gets the parent of each item.

    Click image for larger version  Name:	Treeview2.png Views:	1 Size:	337.4 KB ID:	1595476


    3. Treeview - Expandable symbol
    There are three possibilities with the expand symbol, in my case a simple > or V.
    > means it can be expanded
    V means it is expanded.
    But if it can not be expanded - if it has no children - no symbol should be displayed.
    So how do we determine that? Sure, you can cast the item to actor or scene component and get its children. But there's already a similar example of that above, and also maybe you use some other method to determine if an item has children for the treeview. It might be better to let the treeview determine this.
    Besides, while "On Item Selection Changed" updates for the entrywidget when scrolled into view, the event "On Item Expansion Changed" does not so we can not rely on that though it is needed.
    The "On BP on Get Item Children 0" can be called manually to determine if the item is expandable or not.
    The function "is List item Expanded" would have been useful but any use of it crashes the engine.
    So we need to implement a custom way of remember the expanded items. I use a TSet of UObjects.


    Click image for larger version  Name:	Treeview3.png Views:	1 Size:	336.9 KB ID:	1595477


    4. Treeview - Spacing [When getting the items children]

    But not all treeviews will have items that actually inherit from one another. In that case you need to track the depth of the item. The tracking can be done in the "On BP on Get Item Children" function. By using a Tmap to store the item and its depth, we can query the depth of the parent and set the depth of its children as we enter those into the map. Then, as items are generated we can get the Item depth and set it for the widget.

    Click image for larger version  Name:	Treeview-Spacing-forward.png Views:	1 Size:	355.0 KB ID:	1603229


    Treeview - I don't want the expansion changed!
    Currenty, if an item has children and is clicked on, its expansion state will change. Following are a couple of solutions so that does not happen

    5. Treeview - Expand only if Selected [revert expansion method]

    Currently when an item is clicked, it will expand or collapse. What if we don't want it to change its expanded state when we select it, but if we click it when it is selected, then it will change expansion state? Again, this is possible to achieve but requires our own implementation logic.
    First, we must track which item is selected but can not use the treeview's get selected item(s) function.
    For the events that we will be using, the first thing that happens is that On Item Expansion Changed is called and after that On Item Selection Changed is called. However, printing the item that the expansion event provides, shows that the clicked item has already changed.

    Click image for larger version  Name:	Treeview-ExpansionDebug.png Views:	1 Size:	119.4 KB ID:	1603247

    This means we can not use the treeview's get selected item(s) function but can track the selected item and query the previous selected item during the expansion event.
    The clicked item will change its expansion state, but we can revert the change by using the treeview function "Set Item Expansion". However, that is going to trigger "On Item Expansion Changed" event again, so we need to track the revert logic so that the second event does not call the "Set Item Expansion" again.

    Click image for larger version  Name:	Treeview-Revert-expansion.png Views:	1 Size:	296.6 KB ID:	1603248
    ** After some more experience, you may want to use an Item variable Exclusively to track the expanded state, instead of the "selected Item" reference which would be used for other things.


    6. Treeview - Don't change expansion [Override click method]

    The entrywidget will internally tell the treeview that it was clicked on. Whether it was a border, an image or another user widget, it will detect the click.
    Setting the entrywidget to hit test invisible is no good - we need to be able to click on the widget.
    There is one widget that does not let the userwidget know it was clicked on: the button.
    So below is an image of the entrywidget hierarchy. Logic will be that when the
    -"Expand Button" is clicked, the expansion of the item is changed- collapses if expanded, expands if collapsed.
    -"Select Button" is clicked, the item is selected.
    -The border inside the "Select Button" is there for double clicks. That is done by selecting the border and under its events, create binding.
    These click events calls event dispatchers.

    Click image for larger version  Name:	Treeview-CustomExpansion.png Views:	1 Size:	178.9 KB ID:	1625304

    Inside the widget that holds the treeview, we simply bind to the dispatchers during the "on entry generated" event.

    Click image for larger version  Name:	Treeview-CustomExpansion2.png Views:	1 Size:	214.7 KB ID:	1625305
    The result is that the entry widget do not receive the clicked events, thus does not change the expanded state.
    We manually change the expanded state depending on our needs.
    In this case, the border with the text (which is held by a button) can be clicked on to only select the item, no change in expanded state.
    The visible button can be clicked to change the expanded state only, without selecting the item.
    Last edited by ste1nar; 01-05-2020, 09:41 PM.

    Comment


      #3
      Wow, great job writing this up!

      I struggled when working with the TreeView about a month ago. I eventually figured it out, but it became clear to me that there was insufficient documentation out there to make things easy for first-time users of these widgets.

      If I had only waited a month, I would have had a much easier time, I think, thanks to your excellent tutorial here.

      So, thanks for this, even if I missed the chance to maximally benefit from it!

      Comment


        #4
        Hey, thank you.

        While this is about getting introduced to working with the listview, if anyone wonders how to do anything specific, feel free to ask. For example, if the listview listed actors based on distance. Sure that can be done, but it is not a listview thing to handle, rather you would implement your own algorithm to do the sorting of items and then update the listview.

        Feedback is also welcome, if some parts were unclear or if something specific should be mentioned. A large part of this guide tries to explain why some things -don't- work rather than just showing a setup of a listview that works. I feel that that wouldn't actually teach anything.
        I'd be especially interested to hear from someone who had never used a listview prior.
        Last edited by ste1nar; 03-22-2019, 08:03 PM.

        Comment


          #5
          ste1nar

          This tutorial was really great. It helped me figure out the last bit of opacity on the side of how-do-list-views-work.

          I have a specific problem in a EditorUtilityWidget I am making.

          I create a list view of certain actors in the level. Everything is great about it unless I delete one of the list-represented actors from the level the manual way-- selecting it and hitting the DEL key.

          This causes the whole editor to barf with an error about :

          LogSlate: Warning: WidgetMapToItem length (6) does not match ItemsWithGeneratedWidgets length (5). This is often because the same item is in the list more than once in ListViewT<ItemType>[ListViewBase.h(219)]. Diagnostics follow.

          The diagnostics show what would be expected from the error description... all the Items and Widget Map entries match up, but there is that one less object in the scene.

          I can't figure out what Unreal wants me to do to intercept this event.

          I tried clearing, setting and regenerating the list bound to the various item release events on the listview and listitem widgets, and tried to bind to OnDestroy and OnEndPlay on the object. But I can't figure out what event, at design time, will prevent slate from discovering my listview is out of sync.

          This particular functionality is a sanity check deliberately meant to detect when the user tries deleting an object that shouldn't be manually deleted from a set of objects.

          Any idea on that?

          Thanks!

          Comment


            #6
            @JDStrawesome

            I haven't used EditorWidgets yet, I created one just now to see if I could get it working. unfortunately, I could not. No matter what, when the listview needs updating (whether that is by: clear list items, Set List Items (with an updated array), regenerating, scrolling with the deleted item represented), it crashes. The only thing I noticed was that if the Item was not a displayed item, it could be deleted and when scrolling to it, there would be an "empty" entrywidget (without telling the listview to update).
            I now also noticed that if the Item is NOT currently displayed, it can be deleted and the listview updated. But the real issue is of course the displayed items.


            I did have better luck with the treeview though, if that could be a temporary solution while the listview is not working.
            The idea behind the treeview is that the widget itself is the only manually added Item. When the Treeview runs the "On Get Item Children", you just provide the array of the items you are manually tracking.
            I haven't noticed any glaring problems using the treeview, but there certainly could be.

            Click image for larger version  Name:	EditorTreeview.png Views:	1 Size:	344.4 KB ID:	1608926
            Last edited by ste1nar; 04-17-2019, 10:49 AM.

            Comment


              #7
              ste1nar

              I solved this.

              I combed through source code, and it just seems like when you delete an actor from the level, it's presence is still referenced... probably for the sake of the undo stack.

              Because of that, actors-as-items in a utility widget running at design time will crash the editor every time because you end up with a duplicate item entry in ItemToWidgetMap but not in the WidgetToItemMap and GeneratedWidgetsblahblah list.

              To solve this, I made a data object that only holds a reference of the actor it represents... I instantiate and pass these objects to the listview as items. During EventOnListItemObjectSet, the object passes its reference of the actor to the list item widget so that it can populate all the informational widgets.

              The actor handles all its own original data.

              The proxy object simply acts as a stand-in or handshake.

              Works like a charm... and exposes that the listview widget seems to work in utility widgets as intended if it weren't for this special behavior in level actors.
              Last edited by JDStrawesome; 04-18-2019, 02:54 PM.

              Comment


                #8
                Interesting, well done. I did submit a bug report about the issue, https://issues.unrealengine.com/issue/UE-73014 so perhaps next update it will just work.

                Comment


                  #9

                  Great tutorial, it would be better if there was a small example!

                  Comment


                    #10
                    Hi Ste1ner
                    I've been trying to follow your tutorial but am stuck at the "creating a proper list" section. You use Construct Object from Class (Item Object) but I can't work out how to define the "Item Object" class. I've tried "Add New … C++ class" based on UObject but this never appears in the content browser or in any lists.
                    Are you able to include the definition of Item Object or some instructions.

                    Regards

                    Comment


                      #11
                      Hi ,I try drag&drog in entrywidget,but OnMouseButtonDown event is never called,why?

                      Comment


                        #12
                        Originally posted by Grrrover View Post
                        Hi Ste1ner
                        I've been trying to follow your tutorial but am stuck at the "creating a proper list" section. You use Construct Object from Class (Item Object) but I can't work out how to define the "Item Object" class. I've tried "Add New … C++ class" based on UObject but this never appears in the content browser or in any lists.
                        Are you able to include the definition of Item Object or some instructions.

                        Regards
                        Hello. The "Item Object" (which is really poorly named), is just an object class, which you can create by right-clicking the content browser to bring up the menu, click "blueprint class" to create a new blueprint. Then instead of clicking one of the common classes, expand "all classes" and select "Object".
                        But by this time you have probably moved on.
                        Originally posted by Lee393939 View Post
                        Hi ,I try drag&drog in entrywidget,but OnMouseButtonDown event is never called,why?
                        Hello.If your Entry Widget has elements with the Visibility option set to "Not Hit-testable", then those elements can not interact with the cursor.
                        Or, if you have a Button widget, then that will override the OnMouseButtonDown, so the Entry Widget is not automatically informed that there was a click, or button down.

                        Comment


                          #13
                          Changes in 4.23
                          The nodes "Is List Item Selected", "is List Item Expanded" and "Get List Item Object" has been removed. A new event has been added to the List View: "On Entry Initialized". This event provides both the Item and the Widget references and practically renders "Get List Item Object" redundant, since it would be used every time with "On Entry Generated" event.
                          Now, instead of using "On Entry Generated", use "On Entry Initialized".

                          Click image for larger version

Name:	1AListRemovedNodes.png
Views:	1694
Size:	48.8 KB
ID:	1704210
                          Click image for larger version

Name:	1AListAddedNodes.png
Views:	1672
Size:	22.9 KB
ID:	1704211

                          "On Entry Initialized" is called every time an Entry Widget is generated, not only the first time. ie scrolled into view, appearing in the list for a new item.
                          Initialized event fires first, then generated. In fact, in the following screenshot we can see that all the initialized event fires for all items before the generated event

                          Click image for larger version

Name:	1AListInitializedGeneratedOrder.png
Views:	1681
Size:	71.3 KB
ID:	1704212



                          In 4.23 The Entry Widget seems bugged. Adding the "User Object List Entry" interface does not create any functions that the Entry Widget needs. This is fixed in 4.24.
                          Last edited by ste1nar; 01-04-2020, 08:20 AM.

                          Comment


                            #14
                            1. Listview & Inventory Items

                            This serves exclusively for using the listview to display "inventory items".

                            The first question is, what are those items? Here's some common item types:
                            -Structure holding the data
                            -Name, pointing at a data table that holds the data
                            -Object or Actor.

                            The Listview needs an Object to handle it as one of its Items, so if your inventory is data only, then you may have to create objects just to pass them to the Listview just to act as mediators.
                            In the case of using a data table, the EntryWidget could pull the data from the Data Table.
                            If your items are Objects, they can just be passed to the listview. But still there may be another more practical option.

                            And that option is to have Mediator objects for each item slot. This should work for constant-sized inventories and would account for empty slots. Think Minecraft or Diablo (though this will not be about having one Item occupy several slots).


                            So here's an example of mediator objects acting as pointers to the inventory slot they represent.

                            Click image for larger version  Name:	1AInvMediator2.png Views:	134 Size:	498.9 KB ID:	1702644

                            2. Drag & Drop solution with mediators
                            Here in the entry widget I am creating a drag drop operation.
                            Click image for larger version  Name:	1ATileview-DragOP.png Views:	0 Size:	88.4 KB ID:	1704266
                            The result is this:
                            Click image for larger version  Name:	Tileview-DD-sizediff.png Views:	0 Size:	5.9 KB ID:	1704265
                            The dragged element is smaller. That is because the TileView has a size setting for its tiles, default to 128x128. Then the behavior for widgets with a border and text is to be as small as possible. To have the dragged element be the same size, unfortunately we have to use a SizeBox wrapping the border.
                            The SizeBox Height and width should be = Entry height and width - Entry Spacing.

                            For the On Drop function, the idea is to get the payload index and the index it is dropped on. At first I was setting up this logic in the On Drop function of the EntryWidget. But really, isn't it better to have the character [or whatever class holds your inventory] always be informed about a drop and let it decide what should happen?

                            The entry widget does not know about the character, but the mediator does. So you have two options here. You can either tell the mediator to tell the character to inform about the drop, so that the character can swap the items. If you do this, don't forget to then tell the Listview to regenerate entries, ie to update that the widgets display the correct item. The upside to this is that you can make a function with a return value, so that you can do fancy things with the Entry Widget depending on the outcome of the drop.
                            OR
                            Have a dispatcher in the Entrywidget that the Inventory Widget is bound to, which is what is shown here.
                            The Inventory Widget knows about the character and can tell it that a drop happened, or it can have a dispatcher of its own that the character listens to.

                            Click image for larger version  Name:	1ATileview-OnDrop.png Views:	0 Size:	210.4 KB ID:	1704268
                            Above is really just a long winded way of getting the payload index and the index of the dropped on mediator.
                            With a dispatcher, you can still inform the Entry Widget about the outcome of the drop, but then the dispatcher has to provide the entry widget reference and you need to implement some functions to call.

                            If the Entry Widget is going to have a binding, we have to bind to it when it is generated.

                            Click image for larger version  Name:	1ATileview-DragDropBinding.png Views:	0 Size:	107.2 KB ID:	1704269

                            Now for the actual item swap query and logic.
                            - If the item is dropped on itself, nothing should happen.
                            - If the item is dropped on an empty slot, set that slot to the item and empty the payload (where it came from)
                            - if the item is dropped on an occupied slot, swap them by having a helper variable hold item A, as item B is set to occupy A, then set the origin of Item B to equal Item A.

                            Click image for larger version  Name:	1ATileview-OnDropQuery.png Views:	0 Size:	243.6 KB ID:	1704270
                            The reason for the return boolean, is that if there's a change, the listview need to regenerate its entries. Try not regenerating and you'll see that visually nothing happens.

                            One note is that while we can drag items around, we can also drag empty slots around. To fix that, you can't add an additional query in the character function - Inventory Drag Drop, to see if Inventory[PayloadIndex] == None. Because then you'd still be able to drag around an empty slot. Haven't seen any games do that. So that has to be queried before we create the Drag Drop Operation. The Mediator Object gets a new function returning if its index has an item or not.

                            Click image for larger version  Name:	1ATileview-queried-DragDropOP.png Views:	0 Size:	129.4 KB ID:	1704271

                            Do note that with this type of inventory, you never delete members of the inventory array. You empty the array members that should be empty. If you delete members, you'll start getting errors since the mediators can't access index members that does not exist.

                            -- End of drag and drop with mediators--


                            An alternative option to having mediators for your inventory items is to have the inventory slots be Objects. So instead of an array of Items or structs, you'd have an array of Inventory Slot Objects. These Objects holds the items or structs, and are the ones passed to the listview.

                            Here's the concept:

                            Click image for larger version  Name:	1AInventoryConcept.png Views:	123 Size:	25.9 KB ID:	1702645
                            In a game like Diablo, you can have a single item on a slot, or a stack of pots on a slot.
                            Or in a game like Kenshi, food can be stacked despite them having various amount of consumed. By having a single slot being able to hold several items, you can have item stacks where each item of the stack is an instance.
                            You just have to query when using drag and drop that the items are of the same type.
                            Last edited by ste1nar; 01-05-2020, 10:04 PM.

                            Comment


                              #15
                              Thanks for the reply Steiner. Yes, I did figure it out in the end but took me a couple of days to get it sorted. Now I'll try implementing the tree view, fingers crossed.
                              Very good tutorial sir, you certainly know your stuff.

                              Comment

                              Working...
                              X