-
-
Notifications
You must be signed in to change notification settings - Fork 30
How do I get the selected item in the BindableListView? #43
-
A common scenario in my application is to have a list of items, where one could be selected by the user (e.g. clicked) and then the information about that item (e.g. description) would be displayed in a neaby BindableLabel
. I would not like to use a dropdown for this.
The question is: how do I get the selected item in the BindableListView
?
I don't mind using a BindableScrollView
if that matters.
Beta Was this translation helpful? Give feedback.
All reactions
There are two approaches, with and without the UnityMvvmToolkit.Generator.
Let's say we have the following ViewModels.
MyItemViewModel with UnityMvvmToolkit.Generator
public partial class MyItemViewModel : ICollectionItem { public MyItemViewModel() { Id = Guid.NewGuid().GetHashCode(); } public int Id { get; } [WithObservableBackingField] public string Name { get => _name.Value; set => _name.Value = value; } }
MyItemViewModel without UnityMvvmToolkit.Generator
public partial class MyItemViewModel : ICollectionItem { private readonly IProperty<string> _name = new Property<string>(); public MyItemViewModel() { Id
Replies: 2 comments 4 replies
-
There are two approaches, with and without the UnityMvvmToolkit.Generator.
Let's say we have the following ViewModels.
MyItemViewModel with UnityMvvmToolkit.Generator
public partial class MyItemViewModel : ICollectionItem { public MyItemViewModel() { Id = Guid.NewGuid().GetHashCode(); } public int Id { get; } [WithObservableBackingField] public string Name { get => _name.Value; set => _name.Value = value; } }
MyItemViewModel without UnityMvvmToolkit.Generator
public partial class MyItemViewModel : ICollectionItem { private readonly IProperty<string> _name = new Property<string>(); public MyItemViewModel() { Id = Guid.NewGuid().GetHashCode(); } public int Id { get; } public string Name { get => _name.Value; set => _name.Value = value; } }
public class MyViewModel : IBindingContext { [Observable] private readonly IProperty<MyItemViewModel> _selectedItem; [Observable] private readonly IReadOnlyProperty<ObservableCollection<MyItemViewModel>> _items; public MyViewModel() { var items = new ObservableCollection<MyItemViewModel> { new() { Name = "My Item 1" }, new() { Name = "My Item 2" }, new() { Name = "My Item 3" }, }; _items = new ReadOnlyProperty<ObservableCollection<MyItemViewModel>>(items); _selectedItem = new Property<MyItemViewModel>(); } }
Then we have to create a ListView
with bindable selected item property support.
MyListView with UnityMvvmToolkit.Generator
[BindableElement] public partial class MyListView : BindableListView<MyItemViewModel> { [BindableProperty("SelectedItem")] private IProperty<MyItemViewModel> _selectedItem; partial void AfterSetBindingContext(IBindingContext context, IObjectProvider objectProvider) { onSelectionChange += OnSelectionChange; } partial void AfterResetBindingContext(IObjectProvider objectProvider) { onSelectionChange -= OnSelectionChange; } private void OnSelectionChange(IEnumerable<object> obj) { _selectedItem.Value = (MyItemViewModel) selectedItem; } }
MyListView without UnityMvvmToolkit.Generator
public class MyListView : BindableListView<MyItemViewModel> { private PropertyBindingData _selectedItemBindingData; private IProperty<MyItemViewModel> _selectedItem; public override void SetBindingContext(IBindingContext context, IObjectProvider objectProvider) { base.SetBindingContext(context, objectProvider); if (string.IsNullOrWhiteSpace(BindingSelectedItemPath) == false) { _selectedItemBindingData ??= BindingSelectedItemPath.ToPropertyBindingData(); _selectedItem = objectProvider.RentProperty<MyItemViewModel>(context, _selectedItemBindingData!); } onSelectionChange += OnSelectionChange; } public override void ResetBindingContext(IObjectProvider objectProvider) { if (_selectedItem is not null) { objectProvider.ReturnProperty(_selectedItem); _selectedItem = null; } base.ResetBindingContext(objectProvider); onSelectionChange -= OnSelectionChange; } private void OnSelectionChange(IEnumerable<object> obj) { _selectedItem.Value = (MyItemViewModel) selectedItem; } private string BindingSelectedItemPath { get; set; } public new class UxmlFactory : UxmlFactory<MyListView, UxmlTraits> { } public new class UxmlTraits : BindableListView<MyItemViewModel>.UxmlTraits { private readonly UxmlStringAttributeDescription _bindingSelectedItemPath = new() { name = "binding-selected-item-path", defaultValue = "SelectedItem" }; public override void Init(VisualElement visualElement, IUxmlAttributes bag, CreationContext context) { base.Init(visualElement, bag, context); var control = (MyListView) visualElement; control.BindingSelectedItemPath = _bindingSelectedItemPath.GetValueFromBag(bag, context); } } }
Finally, you can create a BindingContextProvider to provide the SelectedItem
as a BindingContext
for all child elements.
[UxmlElement] public partial class MyItemViewModelProvider : BindingContextProvider<MyItemViewModel> { }
And use everything as follows.
<ui:UXML xmlns:uitk="UnityMvvmToolkit.UITK.BindableUIElements" ...> <MyItemViewModelProvider binding-context-path="SelectedItem"> <uitk:BindableLabel binding-text-path="Name" /> </MyItemViewModelProvider> <MyListView binding-items-source-path="Items" binding-selected-item-path="SelectedItem" /> </ui:UXML>
Note: You have to use the
UnityMvvmToolkit.Generator v0.0.1-pre.4
or newer.
Beta Was this translation helpful? Give feedback.
All reactions
-
Tried the generator-less approach. It worked but I had to modify the MyItemViewModel
to look like this:
public class MyItemViewModel : ICollectionItem
{
public int Id { get; }
public IProperty<string> Name { get; set; }
public MyItemViewModel(MyItem item)
{
Id = Guid.NewGuid().GetHashCode();
Name = new Property<string>(item.Name);
}
}
Otherwise it complained that the property with name Name
could not be found.
Also, the MyItemViewModelProvider
in the example relies on the generator. The generator-less class would look like this:
public class MyItemViewModelProvider: BindingContextProvider<MyItemViewModel>
{
public new class UxmlFactory : UxmlFactory<MyItemViewModelProvider, UxmlTraits>
{
}
}
Beta Was this translation helpful? Give feedback.
All reactions
-
- Speaking of the
MyItemViewModel
issue. Maybe you forgot to make the classpartial
? - The
MyItemViewModelProvider
relies on the UnityUxmlGenerator. This one is available for free. That's why I gave an example with its usage.
Beta Was this translation helpful? Give feedback.
All reactions
-
Yeah, you are right. Thanks!
Beta Was this translation helpful? Give feedback.
All reactions
-
Do I get it right that I'd have to write a separate view for each viewmodel type that I'd like to have this working with? Is there a way to reduce the amount of boilerplate and make it somewhat more generic? Ideally, one SelectableListView
supporting any ObservableCollection
model put inside of it (and not having to derive this type for each viewmodel type)?
Beta Was this translation helpful? Give feedback.
All reactions
-
No, you don't have to write a separate view for each ViewModel type.
You can write a CommonBindingContextProvider
.
[UxmlElement] public partial class CommonBindingContextProvider : BindingContextProvider<IBindingContext> { }
But this approach entails implementing PropertyValueConverter from the MyItemViewModel
to the IBindingContext
.
public class MyItemViewModelConverter : PropertyValueConverter<MyItemViewModel, IBindingContext> { public override IBindingContext Convert(MyItemViewModel value) { return value; } public override MyItemViewModel ConvertBack(IBindingContext value) { throw new System.NotImplementedException(); } }
Note: Don't forget to register the
MyItemViewModelConverter
in the view.
In the following UnityMvvmToolkit
release, this type of conversion will be done automatically.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1