This tutorial shows how to handle UI navigation on a screen in a simple and flexible way, using MVVM (the Model View ViewModel pattern) to create a Selection View Model and Common Activatable Widgets to manage focus. The process requires zero coding, and will be focused on UMG and blueprint-based MVVM.
We will implement a focus-based navigation in a simple screen where elements like lists, tiles and buttons communicate with each other using the Selection View Model and with the help of Common Activatable Widgets.
Hi,
I finished the tutorial and when I press the button “Equip” or “Mark as Junk” without triggering the modal, the selected Item loses focus. Or when I click somewhere on the screen the item loses focus too. Is there a way to keep the focus on the item after pressing on the screen/button?
Hello! Great catch.
The whole keyboard-mouse input management in UE isn’t handled the same way it’s handled with gamepad unfortunately, which means:
When you switch between mouse and gamepad, GetDesiredFocusTarget() is called and focus is re-evaluated. This doesn’t happen when you switch between mouse and keyboard. That’s because UE assumes you might still be using mouse-based navigation alongside keyboard shortcuts.
This is annoying when we want to implement a focus-based navigation, because we have no way of automatically refreshing the focus when switching from mouse to keyboard.
The other annoying thing is that when you click on a non-focusable widget (like a background image or border, or even a button that is hit-testable but not focusable, like our Equip and Junk buttons), Unreal sets focus to the viewport instead of maintaining focus within the UI.
Then, when you switch to keyboard, no widget is focused, so navigation doesn’t work, but if you switch to gamepad, Unreal detects an input method change, so it will trigger a focus re-evaluation calling the various GetDesiredFocusTarget() functions.
I can tell you in advance that there is no elegant way of solving this that I know of (and I suppose it’s not a surprise that in many games keyboard navigation is poorly supported), but there are still a couple of workarounds we can implement that use our SelectionVM and GetDesiredFocusTarget().
So there are 2 issues we need to fix:
not lose focus when clicking on a background element, like a background image or the text in the item details panel
not lose focus when clicking on a non-focusable button (the Equip and Junk buttons)
keeping in mind that:
we want to avoid using events and delegates, keeping loose dependencies
we want to avoid calling SetFocus() and instead prefer RequestRefreshFocus() and GetDesiredFocusTarget()
So here’s what we’re going to do:
Add a utility function in the SelectionVM to request a focus refresh (this is so that any widget might request a focus refresh, and not only Common Activatable Widgets)
Add a new FieldNotify property in the SelectionVM so that the widgets that can handle focus can also listen to external refresh focus requests from other widgets
Introduce a FocusCatcher button to catch click events to the inventory background. This way we can catch the focus and redirect it before losing it
Have our non-focusable buttons use the new utility function in the SelectionVM to re-evaluate the focus when they’re clicked, instead of just allowing Slate to panic
So let’s go:
In the SelectionVM view model class, add the new function RequestRefreshFocus() and a new property bool FocusRefreshRequested
We now create a new function in WBP_Inventory to listen to the View Binding of our new property. We call this ForceFocusEvaluation() and it will take the new property as input. If the bool is true, means we have a focus refresh request, so we can now use a widget that is Common Activatable Widget to actually call the proper RequestRefreshFocus() function of activatable widgets.
But there’s a catch! One thing I haven’t mentioned in the tutorial (didn’t want to overload with information) is that RequestRefreshFocus() will trigger a focus re-evaluation ONLY if requested by the leaf-mostactive activatable widget. What does this mean? It means that in our case our activatable widgets are WBP_Inventory and WBP_Category, and the leaf-most active is WBP_Category (because it’s deeper in the hierarchy). So if we call RequestRefreshFocus() from WBP_Inventory, nothing will happen, because WBP_Inventory isn’t our leaf-most active widget. So instead we need to call it from WBP_Category.
So we can either add our ForceFocusEvaluation() function to WBP_Category, or we can just implement the function in a way that it will call RequestRefreshFocus() on WBP_Category instead of self - which is what I chose for the screenshot.
We can now create the view binding that binds the SelectionVM property to the ForceFocusEvaluation() function
So this part takes case of the case when you click somewhere on a screen. You’ll now see that when you click around instead of losing focus, the focus will be refresh to the currently selected item tile.
Next is solving the bug when clicking on the Equip and Junk buttons.
We open WBP_ItemDetails and simply call RequestRefreshFocus() of the SelectionVM on the OnCliked events of both buttons.
And this should fix those issues! As I said, not the most elegant solution, but should do the trick for the moment. At Epic we’re discussing focus issues and how to improve this on a regular basis, so I hope we’ll get to better keyboard support as soon as possible.
Let me know how this goes - I might add this as an appendix to the tutorial.
Hello! I’m glad this was helpful.
I’m considering making a new tutorial using the same project, but instead of showing how to use MVVM to handle navigation, I would show how to use MVVM to populate our widgets. So it would basically be creating view bindings, functions, conversion functions and so on.
Would that be helpful?
Thank you very much for this tutorial! It’s a very good resource!
It would be nice to delve deeper into the topic of resolvers. Specifically, how to use them to retrieve viewmodels in a multiplayer context where I need to display information about other players.