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

Commit e3de756

Browse files
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)

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