Skip to content

Commit e3de756

Browse files
authored
Bindable dropdown (#31)
Add a bindable Dropdown element.
1 parent d50d1f2 commit e3de756

File tree

7 files changed

+367
-0
lines changed

7 files changed

+367
-0
lines changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,44 @@ The `BindableButton` can be bound to the following commands:
840840

841841
To pass a parameter to the viewmodel, see the [ParameterValueConverter](#parametervalueconverterttargettype) section.
842842

843+
#### BindableDropdownField
844+
845+
The `BindableDropdownField` allows you to work with dropdown. To set the binding of the selected value, you need to write `binding-selected-item-path`, and to set the binding of all dropdown elements, you need to use `binding-items-source-path`.
846+
Moreover, you can set them independently of each other.
847+
848+
The following example demonstrates how to bind to a collection of strings with `BindableDropdownField`.
849+
850+
In XML, you need to write the following:
851+
852+
```xml
853+
<ui:UXML xmlns:uitk="UnityMvvmToolkit.UITK.BindableUIElements" ...>
854+
<uitk:BindableDropdownField binding-selected-item-path="SelectedValue" binding-items-source-path="Choices" />
855+
</ui:UXML>
856+
```
857+
And in the C# class the following:
858+
859+
```csharp
860+
public class DropdownFieldViewModel : IBindingContext
861+
{
862+
public DropdownFieldViewModel()
863+
{
864+
TextValues = new ReadOnlyProperty<ObservableCollection<string>>(new ObservableCollection<string>()
865+
{
866+
"Value1",
867+
"Value2",
868+
"Value3"
869+
});
870+
871+
SelectedValue = new Property<string>();
872+
SelectedValue.Value = "Value1";
873+
}
874+
875+
public IProperty<string> SelectedValue { get; }
876+
877+
public IReadOnlyProperty<ObservableCollection<string>> Choices { get; }
878+
}
879+
```
880+
843881
#### BindableListView
844882

845883
The `BindableListView` control is the most efficient way to create lists. It uses virtualization and creates VisualElements only for visible items. Use the `binding-items-source-path` of the `BindableListView` to bind to an `ObservableCollection`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#if UNITYMVVMTOOLKIT_TEXTMESHPRO_SUPPORT
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Collections.ObjectModel;
6+
using System.Collections.Specialized;
7+
using System.Linq;
8+
using System.Runtime.CompilerServices;
9+
using TMPro;
10+
using UnityEngine;
11+
using UnityMvvmToolkit.Core;
12+
using UnityMvvmToolkit.Core.Extensions;
13+
using UnityMvvmToolkit.Core.Interfaces;
14+
15+
namespace UnityMvvmToolkit.UGUI.BindableUGUIElements
16+
{
17+
[RequireComponent(typeof(TMP_Dropdown))]
18+
public class BindableDropdown : MonoBehaviour, IBindableElement
19+
{
20+
[SerializeField] private TMP_Dropdown _dropdown;
21+
[SerializeField] private string _bindingSelectedItemPath;
22+
[SerializeField] private string _bindingItemsSourcePath;
23+
24+
private IProperty<string> _selectedItemProperty;
25+
private IReadOnlyProperty<ObservableCollection<string>> _itemsSource;
26+
27+
private PropertyBindingData _selectedItemBindingData;
28+
private PropertyBindingData _itemsSourceBindingData;
29+
30+
public void SetBindingContext(IBindingContext context, IObjectProvider objectProvider)
31+
{
32+
if (string.IsNullOrWhiteSpace(_bindingItemsSourcePath) == false)
33+
{
34+
_itemsSourceBindingData ??= _bindingItemsSourcePath.ToPropertyBindingData();
35+
_itemsSource = objectProvider
36+
.RentReadOnlyProperty<ObservableCollection<string>>(context, _itemsSourceBindingData);
37+
_itemsSource.Value.CollectionChanged += OnItemsCollectionChanged;
38+
_dropdown.options = new List<TMP_Dropdown.OptionData>(_itemsSource.Value.Select(value => new TMP_Dropdown.OptionData(value)));
39+
}
40+
41+
if (string.IsNullOrWhiteSpace(_bindingSelectedItemPath) == false)
42+
{
43+
_selectedItemBindingData ??= _bindingSelectedItemPath.ToPropertyBindingData();
44+
_selectedItemProperty = objectProvider.RentProperty<string>(context, _selectedItemBindingData);
45+
_selectedItemProperty.ValueChanged += OnPropertySelectedItemChanged;
46+
47+
var foundIndex = _dropdown.options.FindIndex(option => option.text == _selectedItemProperty.Value);
48+
if (foundIndex != -1)
49+
{
50+
UpdateControlValue(foundIndex);
51+
}
52+
53+
_dropdown.onValueChanged.AddListener(OnControlValueChanged);
54+
_selectedItemProperty.Value = _dropdown.options.Count > 0 ? _dropdown.options[0].text : default;
55+
}
56+
}
57+
58+
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
59+
{
60+
if (e.Action == NotifyCollectionChangedAction.Add)
61+
{
62+
foreach (string newItem in e.NewItems)
63+
{
64+
_dropdown.options.Add(new TMP_Dropdown.OptionData(newItem));
65+
}
66+
}
67+
68+
if (e.Action == NotifyCollectionChangedAction.Remove)
69+
{
70+
foreach (string oldItem in e.OldItems)
71+
{
72+
_dropdown.options.Remove(new TMP_Dropdown.OptionData(oldItem));
73+
}
74+
}
75+
76+
if (e.Action == NotifyCollectionChangedAction.Reset)
77+
{
78+
_dropdown.options.Clear();
79+
}
80+
}
81+
82+
public virtual void ResetBindingContext(IObjectProvider objectProvider)
83+
{
84+
if (_itemsSource != null)
85+
{
86+
_itemsSource.Value.CollectionChanged -= OnItemsCollectionChanged;
87+
objectProvider.ReturnReadOnlyProperty(_itemsSource);
88+
_itemsSource = null;
89+
_dropdown.options = new List<TMP_Dropdown.OptionData>();
90+
}
91+
92+
if (_selectedItemProperty != null)
93+
{
94+
_selectedItemProperty.ValueChanged -= OnPropertySelectedItemChanged;
95+
objectProvider.ReturnProperty(_selectedItemProperty);
96+
_selectedItemProperty = null;
97+
_dropdown.onValueChanged.RemoveListener(OnControlValueChanged);
98+
}
99+
100+
UpdateControlValue(default);
101+
}
102+
103+
protected virtual void OnControlValueChanged(int index)
104+
{
105+
_selectedItemProperty.Value = _dropdown.options[index].text;
106+
}
107+
108+
private void OnPropertySelectedItemChanged(object sender, string newValue)
109+
{
110+
var foundIndex = _dropdown.options.FindIndex(option => option.text == newValue);
111+
if (foundIndex == -1)
112+
{
113+
return;
114+
}
115+
116+
UpdateControlValue(foundIndex);
117+
}
118+
119+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
120+
protected virtual void UpdateControlValue(int newValue)
121+
{
122+
_dropdown.SetValueWithoutNotify(newValue);
123+
}
124+
}
125+
}
126+
127+
#endif

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UGUI/BindableUGUIElements/BindableDropdown.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Collections.Specialized;
5+
using System.Linq;
6+
using System.Runtime.CompilerServices;
7+
using UnityEngine.UIElements;
8+
using UnityMvvmToolkit.Common.Interfaces;
9+
using UnityMvvmToolkit.Core;
10+
using UnityMvvmToolkit.Core.Extensions;
11+
using UnityMvvmToolkit.Core.Interfaces;
12+
13+
namespace UnityMvvmToolkit.UITK.BindableUIElements
14+
{
15+
public partial class BindableDropdownField : DropdownField, IBindableCollection, IInitializable, IDisposable
16+
{
17+
private IProperty<string> _selectedItemProperty;
18+
private IReadOnlyProperty<ObservableCollection<string>> _itemsSource;
19+
20+
private PropertyBindingData _selectedItemBindingData;
21+
private PropertyBindingData _itemsSourceBindingData;
22+
23+
public void Initialize()
24+
{
25+
choices = new List<string>();
26+
}
27+
28+
public void Dispose()
29+
{
30+
choices.Clear();
31+
}
32+
33+
public void SetBindingContext(IBindingContext context, IObjectProvider objectProvider)
34+
{
35+
if (string.IsNullOrWhiteSpace(BindingItemsSourcePath) == false)
36+
{
37+
_itemsSourceBindingData ??= BindingItemsSourcePath.ToPropertyBindingData();
38+
_itemsSource = objectProvider
39+
.RentReadOnlyProperty<ObservableCollection<string>>(context, _itemsSourceBindingData);
40+
_itemsSource.Value.CollectionChanged += OnItemsCollectionChanged;
41+
choices = new List<string>(_itemsSource.Value);
42+
}
43+
44+
if (string.IsNullOrWhiteSpace(BindingSelectedItemPath) == false)
45+
{
46+
_selectedItemBindingData ??= BindingSelectedItemPath.ToPropertyBindingData();
47+
_selectedItemProperty = objectProvider.RentProperty<string>(context, _selectedItemBindingData);
48+
_selectedItemProperty.ValueChanged += OnSelectedItemValueChanged;
49+
50+
var isContains = choices.Contains(_selectedItemProperty.Value);
51+
if (isContains == true)
52+
{
53+
UpdateControlValue(_selectedItemProperty.Value);
54+
}
55+
56+
this.RegisterValueChangedCallback(OnControlValueChanged);
57+
_selectedItemProperty.Value = choices.Count > 0 ? choices[0] : default;
58+
}
59+
}
60+
61+
protected virtual void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
62+
{
63+
if (e.Action == NotifyCollectionChangedAction.Add)
64+
{
65+
foreach (string newItem in e.NewItems)
66+
{
67+
choices.Add(newItem);
68+
}
69+
}
70+
71+
if (e.Action == NotifyCollectionChangedAction.Remove)
72+
{
73+
foreach (string oldItem in e.OldItems)
74+
{
75+
choices.Remove(oldItem);
76+
}
77+
}
78+
79+
if (e.Action == NotifyCollectionChangedAction.Reset)
80+
{
81+
choices.Clear();
82+
}
83+
}
84+
85+
public virtual void ResetBindingContext(IObjectProvider objectProvider)
86+
{
87+
if (_selectedItemProperty != null)
88+
{
89+
_selectedItemProperty.ValueChanged -= OnSelectedItemValueChanged;
90+
objectProvider.ReturnProperty(_selectedItemProperty);
91+
_selectedItemProperty = null;
92+
this.UnregisterValueChangedCallback(OnControlValueChanged);
93+
}
94+
95+
if (_itemsSource != null)
96+
{
97+
_itemsSource.Value.CollectionChanged -= OnItemsCollectionChanged;
98+
choices = new List<string>();
99+
objectProvider.ReturnReadOnlyProperty(_itemsSource);
100+
_itemsSource = null;
101+
}
102+
103+
UpdateControlValue(default);
104+
}
105+
106+
protected virtual void OnControlValueChanged(ChangeEvent<string> e)
107+
{
108+
_selectedItemProperty.Value = e.newValue;
109+
}
110+
111+
private void OnSelectedItemValueChanged(object sender, string newValue)
112+
{
113+
var isContains = choices.Contains(newValue);
114+
if (isContains == false)
115+
{
116+
return;
117+
}
118+
119+
UpdateControlValue(newValue);
120+
}
121+
122+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
123+
protected virtual void UpdateControlValue(string newValue)
124+
{
125+
SetValueWithoutNotify(newValue);
126+
}
127+
}
128+
}

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/BindableDropdownField.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using UnityEngine.UIElements;
2+
using UnityMvvmToolkit.UITK.Extensions;
3+
4+
namespace UnityMvvmToolkit.UITK.BindableUIElements
5+
{
6+
partial class BindableDropdownField
7+
{
8+
public string BindingSelectedItemPath { get; private set; }
9+
10+
public string BindingItemsSourcePath { get; private set; }
11+
12+
public new class UxmlFactory : UxmlFactory<BindableDropdownField, UxmlTraits>
13+
{
14+
}
15+
16+
#if UNITY_2023_2_OR_NEWER
17+
[System.Serializable]
18+
public new class UxmlSerializedData : DropdownField.UxmlSerializedData
19+
{
20+
// ReSharper disable once InconsistentNaming
21+
#pragma warning disable 649
22+
[UnityEngine.SerializeField] private string BindingSelectedItemPath;
23+
[UnityEngine.SerializeField] private string BindingItemsSourcePath;
24+
#pragma warning restore 649
25+
26+
public override object CreateInstance() => new BindableDropdownField();
27+
public override void Deserialize(object visualElement)
28+
{
29+
base.Deserialize(visualElement);
30+
31+
var bindableDropdownField = visualElement.As<BindableDropdownField>();
32+
bindableDropdownField.BindingSelectedItemPath = BindingSelectedItemPath;
33+
bindableDropdownField.BindingItemsSourcePath = BindingItemsSourcePath;
34+
}
35+
}
36+
#else
37+
public new class UxmlTraits : DropdownField.UxmlTraits
38+
{
39+
private readonly UxmlStringAttributeDescription _bindingSelectedItemPath = new()
40+
{ name = "binding-selected-item-path", defaultValue = "" };
41+
42+
private readonly UxmlStringAttributeDescription _bindingItemsSourcePath = new()
43+
{ name = "binding-items-source-path", defaultValue = "" };
44+
45+
public override void Init(VisualElement visualElement, IUxmlAttributes bag, CreationContext context)
46+
{
47+
base.Init(visualElement, bag, context);
48+
49+
var bindableDropdownField = visualElement.As<BindableDropdownField>();
50+
51+
bindableDropdownField.BindingSelectedItemPath = _bindingSelectedItemPath.GetValueFromBag(bag, context);
52+
bindableDropdownField.BindingItemsSourcePath = _bindingItemsSourcePath.GetValueFromBag(bag, context);
53+
}
54+
}
55+
#endif
56+
}
57+
}

src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/UITK/BindableUIElements/Uxmls/BindableDropdownField.Uxml.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)