Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

How do I get the selected item in the BindableListView? #43

Answered by ChebanovDD
KTolmachevSP asked this question in Q&A
Discussion options

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.

You must be logged in to vote

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

Comment options

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.

You must be logged in to vote
3 replies
Comment options

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>
 {
 }
 }
Comment options

  • Speaking of the MyItemViewModel issue. Maybe you forgot to make the class partial?
  • The MyItemViewModelProvider relies on the UnityUxmlGenerator. This one is available for free. That's why I gave an example with its usage.
Comment options

Yeah, you are right. Thanks!

Answer selected by ChebanovDD
Comment options

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)?

You must be logged in to vote
1 reply
Comment options

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /