зеркало из https://github.com/DeGsoft/maui-linux.git
[C] Use a Binding for ItemsSource object selection
This commit is contained in:
Родитель
693cc75068
Коммит
e5af21fdc3
|
@ -5,57 +5,57 @@ using System.Globalization;
|
|||
|
||||
namespace Xamarin.Forms.Core.UnitTests
|
||||
{
|
||||
internal class ContextFixture
|
||||
{
|
||||
public class NestedClass
|
||||
{
|
||||
public string Nested { get; set; }
|
||||
}
|
||||
|
||||
public NestedClass Nested { get; set; }
|
||||
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public string ComplexName { get; set; }
|
||||
|
||||
public ContextFixture(string displayName, string complexName)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
ComplexName = complexName;
|
||||
}
|
||||
|
||||
public ContextFixture()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal class BindingContext
|
||||
{
|
||||
public ObservableCollection<object> Items { get; set; }
|
||||
|
||||
public object SelectedItem { get; set; }
|
||||
}
|
||||
|
||||
internal class PickerTestValueConverter : IValueConverter
|
||||
{
|
||||
public bool ConvertCalled { get; private set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
ConvertCalled = true;
|
||||
var cf = (ContextFixture)value;
|
||||
return cf.DisplayName;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class PickerTests : BaseTestFixture
|
||||
{
|
||||
class PickerTestsContextFixture
|
||||
{
|
||||
public class PickerTestsNestedClass
|
||||
{
|
||||
public string Nested { get; set; }
|
||||
}
|
||||
|
||||
public PickerTestsNestedClass Nested { get; set; }
|
||||
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public string ComplexName { get; set; }
|
||||
|
||||
public PickerTestsContextFixture(string displayName, string complexName)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
ComplexName = complexName;
|
||||
}
|
||||
|
||||
public PickerTestsContextFixture()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class PickerTestsBindingContext
|
||||
{
|
||||
public ObservableCollection<object> Items { get; set; }
|
||||
|
||||
public object SelectedItem { get; set; }
|
||||
}
|
||||
|
||||
class PickerTestValueConverter : IValueConverter
|
||||
{
|
||||
public bool ConvertCalled { get; private set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
ConvertCalled = true;
|
||||
var cf = (PickerTestsContextFixture)value;
|
||||
return cf.DisplayName;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetSelectedIndexOnNullRows()
|
||||
{
|
||||
|
@ -185,28 +185,6 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
Assert.AreEqual(0, picker.Items.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplayFunc()
|
||||
{
|
||||
Func<object, string> customDisplayFunc = o =>
|
||||
{
|
||||
var f = (ContextFixture)o;
|
||||
return $"{f.DisplayName} ({f.ComplexName})";
|
||||
};
|
||||
var obj = new ContextFixture("Monkey", "Complex Monkey");
|
||||
var picker = new Picker
|
||||
{
|
||||
DisplayMemberPath = "Name",
|
||||
DisplayFunc = customDisplayFunc,
|
||||
ItemsSource = new ObservableCollection<object>
|
||||
{
|
||||
obj
|
||||
},
|
||||
SelectedIndex = 1
|
||||
};
|
||||
Assert.AreEqual("Monkey (Complex Monkey)", picker.Items[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetItemsSourceProperty()
|
||||
{
|
||||
|
@ -220,22 +198,22 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
};
|
||||
var picker = new Picker
|
||||
{
|
||||
DisplayMemberPath = "Name",
|
||||
ItemDisplayBinding = new Binding("Name"),
|
||||
ItemsSource = items
|
||||
};
|
||||
Assert.AreEqual(5, picker.Items.Count);
|
||||
Assert.AreEqual("John", picker.Items[0]);
|
||||
Assert.AreEqual("0", picker.Items[3]);
|
||||
Assert.AreEqual(null, picker.Items[3]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplayConverter()
|
||||
{
|
||||
var obj = new ContextFixture("John", "John Doe");
|
||||
var obj = new PickerTestsContextFixture("John", "John Doe");
|
||||
var converter = new PickerTestValueConverter();
|
||||
var picker = new Picker
|
||||
{
|
||||
DisplayConverter = converter,
|
||||
ItemDisplayBinding = new Binding (Binding.SelfPath, converter:converter),
|
||||
ItemsSource = new ObservableCollection<object>
|
||||
{
|
||||
obj
|
||||
|
@ -245,22 +223,6 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
Assert.AreEqual("John", picker.Items[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplayMemberPathShouldThrowArgumentExceptionInvalidPath()
|
||||
{
|
||||
var obj = new ContextFixture("Monkey", "Complex Monkey");
|
||||
Func<Picker> picker = () => new Picker
|
||||
{
|
||||
DisplayMemberPath = "Name",
|
||||
ItemsSource = new ObservableCollection<object>
|
||||
{
|
||||
obj
|
||||
},
|
||||
SelectedIndex = 1
|
||||
};
|
||||
Assert.Throws<ArgumentException>(() => picker());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemsSourceCollectionChangedAppend()
|
||||
{
|
||||
|
@ -272,7 +234,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
};
|
||||
var picker = new Picker
|
||||
{
|
||||
DisplayMemberPath = "Name",
|
||||
ItemDisplayBinding = new Binding("Name"),
|
||||
ItemsSource = items,
|
||||
SelectedIndex = 0
|
||||
};
|
||||
|
@ -294,7 +256,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
};
|
||||
var picker = new Picker
|
||||
{
|
||||
DisplayMemberPath = "Name",
|
||||
ItemDisplayBinding = new Binding("Name"),
|
||||
ItemsSource = items,
|
||||
SelectedIndex = 0
|
||||
};
|
||||
|
@ -314,7 +276,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
};
|
||||
var picker = new Picker
|
||||
{
|
||||
DisplayMemberPath = "Name",
|
||||
ItemDisplayBinding = new Binding("Name"),
|
||||
ItemsSource = items,
|
||||
SelectedIndex = 0
|
||||
};
|
||||
|
@ -337,7 +299,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
var bindingContext = new { Items = items };
|
||||
var picker = new Picker
|
||||
{
|
||||
DisplayMemberPath = "Name",
|
||||
ItemDisplayBinding = new Binding("Name"),
|
||||
BindingContext = bindingContext
|
||||
};
|
||||
picker.SetBinding(Picker.ItemsSourceProperty, "Items");
|
||||
|
@ -348,6 +310,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
"Orange"
|
||||
};
|
||||
picker.BindingContext = new { Items = items };
|
||||
picker.ItemDisplayBinding = null;
|
||||
Assert.AreEqual(2, picker.Items.Count);
|
||||
Assert.AreEqual("Peach", picker.Items[0]);
|
||||
}
|
||||
|
@ -358,12 +321,12 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
var items = new ObservableCollection<object>
|
||||
{
|
||||
new { Name = "John" },
|
||||
"Paul",
|
||||
"Ringo"
|
||||
new { Name = "Paul" },
|
||||
new { Name = "Ringo"},
|
||||
};
|
||||
var picker = new Picker
|
||||
{
|
||||
DisplayMemberPath = "Name",
|
||||
ItemDisplayBinding = new Binding("Name"),
|
||||
ItemsSource = items,
|
||||
SelectedIndex = 0
|
||||
};
|
||||
|
@ -395,11 +358,11 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
[Test]
|
||||
public void TestSelectedItemDefault()
|
||||
{
|
||||
var bindingContext = new BindingContext
|
||||
var bindingContext = new PickerTestsBindingContext
|
||||
{
|
||||
Items = new ObservableCollection<object>
|
||||
{
|
||||
new ContextFixture("John", "John")
|
||||
new PickerTestsContextFixture("John", "John")
|
||||
}
|
||||
};
|
||||
var picker = new Picker
|
||||
|
@ -413,19 +376,28 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
Assert.AreEqual(bindingContext.SelectedItem, picker.SelectedItem);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowsWhenModifyingItemsIfItemsSourceIsSet()
|
||||
{
|
||||
var picker = new Picker {
|
||||
ItemsSource = new System.Collections.Generic.List<object> ()
|
||||
};
|
||||
Assert.Throws<InvalidOperationException>(() => picker.Items.Add("foo"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNestedDisplayMemberPathExpression()
|
||||
{
|
||||
var obj = new ContextFixture
|
||||
var obj = new PickerTestsContextFixture
|
||||
{
|
||||
Nested = new ContextFixture.NestedClass
|
||||
Nested = new PickerTestsContextFixture.PickerTestsNestedClass
|
||||
{
|
||||
Nested = "NestedProperty"
|
||||
}
|
||||
};
|
||||
var picker = new Picker
|
||||
{
|
||||
DisplayMemberPath = "Nested.Nested",
|
||||
ItemDisplayBinding = new Binding("Nested.Nested"),
|
||||
ItemsSource = new ObservableCollection<object>
|
||||
{
|
||||
obj
|
||||
|
@ -454,8 +426,8 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
[Test]
|
||||
public void TestSelectedItemSet()
|
||||
{
|
||||
var obj = new ContextFixture("John", "John");
|
||||
var bindingContext = new BindingContext
|
||||
var obj = new PickerTestsContextFixture("John", "John");
|
||||
var bindingContext = new PickerTestsBindingContext
|
||||
{
|
||||
Items = new ObservableCollection<object>
|
||||
{
|
||||
|
@ -466,7 +438,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
var picker = new Picker
|
||||
{
|
||||
BindingContext = bindingContext,
|
||||
DisplayMemberPath = "DisplayName"
|
||||
ItemDisplayBinding = new Binding("DisplayName"),
|
||||
};
|
||||
picker.SetBinding(Picker.ItemsSourceProperty, "Items");
|
||||
picker.SetBinding(Picker.SelectedItemProperty, "SelectedItem");
|
||||
|
@ -478,8 +450,8 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
[Test]
|
||||
public void TestSelectedItemChangeSelectedIndex()
|
||||
{
|
||||
var obj = new ContextFixture("John", "John");
|
||||
var bindingContext = new BindingContext
|
||||
var obj = new PickerTestsContextFixture("John", "John");
|
||||
var bindingContext = new PickerTestsBindingContext
|
||||
{
|
||||
Items = new ObservableCollection<object>
|
||||
{
|
||||
|
@ -489,7 +461,7 @@ namespace Xamarin.Forms.Core.UnitTests
|
|||
var picker = new Picker
|
||||
{
|
||||
BindingContext = bindingContext,
|
||||
DisplayMemberPath = "DisplayName"
|
||||
ItemDisplayBinding = new Binding("DisplayName"),
|
||||
};
|
||||
picker.SetBinding(Picker.ItemsSourceProperty, "Items");
|
||||
picker.SetBinding(Picker.SelectedItemProperty, "SelectedItem");
|
||||
|
|
|
@ -2,8 +2,6 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using Xamarin.Forms.Platform;
|
||||
|
||||
namespace Xamarin.Forms
|
||||
|
@ -11,63 +9,33 @@ namespace Xamarin.Forms
|
|||
[RenderWith(typeof(_PickerRenderer))]
|
||||
public class Picker : View, IElementConfiguration<Picker>
|
||||
{
|
||||
public static readonly BindableProperty TextColorProperty = BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(Picker), Color.Default);
|
||||
public static readonly BindableProperty TextColorProperty =
|
||||
BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(Picker), Color.Default);
|
||||
|
||||
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(Picker), default(string));
|
||||
public static readonly BindableProperty TitleProperty =
|
||||
BindableProperty.Create(nameof(Title), typeof(string), typeof(Picker), default(string));
|
||||
|
||||
public static readonly BindableProperty SelectedIndexProperty = BindableProperty.Create(nameof(SelectedIndex), typeof(int), typeof(Picker), -1, BindingMode.TwoWay,
|
||||
propertyChanged: OnSelectedIndexChanged,
|
||||
coerceValue: CoerceSelectedIndex);
|
||||
public static readonly BindableProperty SelectedIndexProperty =
|
||||
BindableProperty.Create(nameof(SelectedIndex), typeof(int), typeof(Picker), -1, BindingMode.TwoWay,
|
||||
propertyChanged: OnSelectedIndexChanged, coerceValue: CoerceSelectedIndex);
|
||||
|
||||
public static readonly BindableProperty SelectedValueMemberPathProperty = BindableProperty.Create(nameof(SelectedValueMemberPath), typeof(string), typeof(Picker));
|
||||
public static readonly BindableProperty ItemsSourceProperty =
|
||||
BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(Picker), default(IList),
|
||||
propertyChanged: OnItemsSourceChanged);
|
||||
|
||||
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(Picker), default(IList), propertyChanged: OnItemsSourceChanged);
|
||||
|
||||
public static readonly BindableProperty DisplayMemberPathProperty = BindableProperty.Create(nameof(DisplayMemberPath), typeof(string), typeof(Picker), propertyChanged: OnDisplayMemberPathChanged);
|
||||
|
||||
public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(Picker), null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);
|
||||
public static readonly BindableProperty SelectedItemProperty =
|
||||
BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(Picker), null, BindingMode.TwoWay,
|
||||
propertyChanged: OnSelectedItemChanged);
|
||||
|
||||
readonly Lazy<PlatformConfigurationRegistry<Picker>> _platformConfigurationRegistry;
|
||||
|
||||
public static readonly BindableProperty DisplayFuncProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(DisplayFunc),
|
||||
typeof(Func<object, string>),
|
||||
typeof(Picker));
|
||||
|
||||
public static readonly BindableProperty DisplayConverterProperty =
|
||||
BindableProperty.Create(
|
||||
nameof(DisplayConverter),
|
||||
typeof(IValueConverter),
|
||||
typeof(Picker),
|
||||
default(IValueConverter));
|
||||
|
||||
|
||||
public Picker()
|
||||
{
|
||||
((ObservableList<string>)Items).CollectionChanged += OnItemsCollectionChanged;
|
||||
((INotifyCollectionChanged)Items).CollectionChanged += OnItemsCollectionChanged;
|
||||
_platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Picker>>(() => new PlatformConfigurationRegistry<Picker>(this));
|
||||
}
|
||||
|
||||
public string DisplayMemberPath
|
||||
{
|
||||
get { return (string)GetValue(DisplayMemberPathProperty); }
|
||||
set { SetValue(DisplayMemberPathProperty, value); }
|
||||
}
|
||||
|
||||
public Func<object, string> DisplayFunc
|
||||
{
|
||||
get { return (Func<object, string>)GetValue(DisplayFuncProperty); }
|
||||
set { SetValue(DisplayFuncProperty, value); }
|
||||
}
|
||||
|
||||
public IValueConverter DisplayConverter
|
||||
{
|
||||
get { return (IValueConverter)GetValue(DisplayConverterProperty); }
|
||||
set { SetValue(DisplayConverterProperty, value); }
|
||||
}
|
||||
|
||||
public IList<string> Items { get; } = new ObservableList<string>();
|
||||
public IList<string> Items { get; } = new LockableObservableListWrapper();
|
||||
|
||||
public IList ItemsSource
|
||||
{
|
||||
|
@ -87,73 +55,44 @@ namespace Xamarin.Forms
|
|||
set { SetValue(SelectedItemProperty, value); }
|
||||
}
|
||||
|
||||
public string SelectedValueMemberPath
|
||||
{
|
||||
get { return (string)GetValue(SelectedValueMemberPathProperty); }
|
||||
set { SetValue(SelectedValueMemberPathProperty, value); }
|
||||
}
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
public Color TextColor {
|
||||
get { return (Color)GetValue(TextColorProperty); }
|
||||
set { SetValue(TextColorProperty, value); }
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
public string Title {
|
||||
get { return (string)GetValue(TitleProperty); }
|
||||
set { SetValue(TitleProperty, value); }
|
||||
}
|
||||
|
||||
BindingBase _itemDisplayBinding;
|
||||
public BindingBase ItemDisplayBinding {
|
||||
get { return _itemDisplayBinding; }
|
||||
set {
|
||||
if (_itemDisplayBinding == value)
|
||||
return;
|
||||
|
||||
OnPropertyChanging();
|
||||
var oldValue = value;
|
||||
_itemDisplayBinding = value;
|
||||
OnItemDisplayBindingChanged(oldValue, _itemDisplayBinding);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler SelectedIndexChanged;
|
||||
|
||||
protected virtual string GetDisplayMember(object item)
|
||||
static readonly BindableProperty s_displayProperty =
|
||||
BindableProperty.Create("Display", typeof(string), typeof(Picker), default(string));
|
||||
|
||||
string GetDisplayMember(object item)
|
||||
{
|
||||
if (DisplayConverter != null)
|
||||
{
|
||||
var display = DisplayConverter.Convert(item, typeof(string), null, CultureInfo.CurrentUICulture) as string;
|
||||
if (display == null)
|
||||
{
|
||||
throw new ArgumentException("value must be converted to string");
|
||||
}
|
||||
return display;
|
||||
}
|
||||
if (DisplayFunc != null)
|
||||
{
|
||||
return DisplayFunc(item);
|
||||
}
|
||||
if (item == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
bool isValueType = item.GetType().GetTypeInfo().IsValueType;
|
||||
if (isValueType || string.IsNullOrEmpty(DisplayMemberPath) || item is string)
|
||||
{
|
||||
// For a mix of objects in ItemsSourc to be handled correctly in conjunction with DisplayMemberPath
|
||||
// we need to handle value types and string so that GetPropertyValue doesn't throw exception if the property
|
||||
// doesn't exist on the item object
|
||||
if (ItemDisplayBinding == null)
|
||||
return item.ToString();
|
||||
}
|
||||
return GetPropertyValue(item, DisplayMemberPath) as string;
|
||||
}
|
||||
|
||||
void AddItems(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
int index = e.NewStartingIndex < 0 ? Items.Count : e.NewStartingIndex;
|
||||
foreach (object newItem in e.NewItems)
|
||||
{
|
||||
Items.Insert(index++, GetDisplayMember(newItem));
|
||||
}
|
||||
}
|
||||
|
||||
void BindItems()
|
||||
{
|
||||
Items.Clear();
|
||||
foreach (object item in ItemsSource)
|
||||
{
|
||||
Items.Add(GetDisplayMember(item));
|
||||
}
|
||||
UpdateSelectedItem();
|
||||
ItemDisplayBinding.Apply(item, this, s_displayProperty);
|
||||
ItemDisplayBinding.Unapply();
|
||||
return (string)GetValue(s_displayProperty);
|
||||
}
|
||||
|
||||
static object CoerceSelectedIndex(BindableObject bindable, object value)
|
||||
|
@ -162,60 +101,9 @@ namespace Xamarin.Forms
|
|||
return picker.Items == null ? -1 : ((int)value).Clamp(-1, picker.Items.Count - 1);
|
||||
}
|
||||
|
||||
void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
void OnItemDisplayBindingChanged(BindingBase oldValue, BindingBase newValue)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
// Clear Items collection and re-populate it with values from ItemsSource
|
||||
BindItems();
|
||||
return;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
RemoveItems(e);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
AddItems(e);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
static object GetPropertyValue(object item, string memberPath)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
if (string.IsNullOrEmpty(memberPath))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(memberPath));
|
||||
}
|
||||
// Find the property by walking the display member path to find any nested properties
|
||||
string[] propertyPathParts = memberPath.Split('.');
|
||||
object propertyValue = item;
|
||||
foreach (string propertyPathPart in propertyPathParts)
|
||||
{
|
||||
PropertyInfo propInfo = propertyValue.GetType().GetTypeInfo().GetDeclaredProperty(propertyPathPart);
|
||||
if (propInfo == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"No property with name '{propertyPathPart}' was found on '{propertyValue.GetType().FullName}'");
|
||||
}
|
||||
propertyValue = propInfo.GetValue(propertyValue);
|
||||
}
|
||||
return propertyValue;
|
||||
}
|
||||
|
||||
static void OnDisplayMemberPathChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var picker = (Picker)bindable;
|
||||
if (picker.ItemsSource?.Count > 0)
|
||||
{
|
||||
picker.BindItems();
|
||||
}
|
||||
ResetItems();
|
||||
}
|
||||
|
||||
void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
|
@ -226,31 +114,71 @@ namespace Xamarin.Forms
|
|||
|
||||
static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
|
||||
{
|
||||
var picker = (Picker)bindable;
|
||||
// Check if the ItemsSource value has changed and if so, unsubscribe from collection changed
|
||||
var observable = oldValue as INotifyCollectionChanged;
|
||||
if (observable != null)
|
||||
{
|
||||
observable.CollectionChanged -= picker.CollectionChanged;
|
||||
((Picker)bindable).OnItemsSourceChanged((IList)oldValue, (IList)newValue);
|
||||
}
|
||||
|
||||
void OnItemsSourceChanged(IList oldValue, IList newValue)
|
||||
{
|
||||
var oldObservable = oldValue as INotifyCollectionChanged;
|
||||
if (oldObservable != null)
|
||||
oldObservable.CollectionChanged -= CollectionChanged;
|
||||
|
||||
var newObservable = newValue as INotifyCollectionChanged;
|
||||
if (newObservable != null) {
|
||||
newObservable.CollectionChanged += CollectionChanged;
|
||||
}
|
||||
observable = newValue as INotifyCollectionChanged;
|
||||
if (observable != null)
|
||||
{
|
||||
observable.CollectionChanged += picker.CollectionChanged;
|
||||
picker.BindItems();
|
||||
|
||||
if (newValue != null) {
|
||||
((LockableObservableListWrapper)Items).IsLocked = true;
|
||||
ResetItems();
|
||||
} else {
|
||||
((LockableObservableListWrapper)Items).InternalClear();
|
||||
((LockableObservableListWrapper)Items).IsLocked = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// newValue is null so clear the items collection
|
||||
picker.Items.Clear();
|
||||
}
|
||||
|
||||
void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
AddItems(e);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
RemoveItems(e);
|
||||
break;
|
||||
default: //Move, Replace, Reset
|
||||
ResetItems();
|
||||
break;
|
||||
}
|
||||
}
|
||||
void AddItems(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
int index = e.NewStartingIndex < 0 ? Items.Count : e.NewStartingIndex;
|
||||
foreach (object newItem in e.NewItems)
|
||||
((LockableObservableListWrapper)Items).InternalInsert(index++, GetDisplayMember(newItem));
|
||||
}
|
||||
|
||||
void RemoveItems(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
int index = e.OldStartingIndex < Items.Count ? e.OldStartingIndex : Items.Count;
|
||||
foreach (object _ in e.OldItems)
|
||||
((LockableObservableListWrapper)Items).InternalRemoveAt(index--);
|
||||
}
|
||||
|
||||
void ResetItems()
|
||||
{
|
||||
if (ItemsSource == null)
|
||||
return;
|
||||
((LockableObservableListWrapper)Items).InternalClear();
|
||||
foreach (object item in ItemsSource)
|
||||
((LockableObservableListWrapper)Items).InternalAdd(GetDisplayMember(item));
|
||||
UpdateSelectedItem();
|
||||
}
|
||||
|
||||
static void OnSelectedIndexChanged(object bindable, object oldValue, object newValue)
|
||||
{
|
||||
var picker = (Picker)bindable;
|
||||
EventHandler eh = picker.SelectedIndexChanged;
|
||||
eh?.Invoke(bindable, EventArgs.Empty);
|
||||
picker.SelectedIndexChanged?.Invoke(bindable, EventArgs.Empty);
|
||||
picker.UpdateSelectedItem();
|
||||
}
|
||||
|
||||
|
@ -260,34 +188,146 @@ namespace Xamarin.Forms
|
|||
picker.UpdateSelectedIndex(newValue);
|
||||
}
|
||||
|
||||
void RemoveItems(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
int index = e.OldStartingIndex < Items.Count ? e.OldStartingIndex : Items.Count;
|
||||
// TODO: How do we determine the order of which the items were removed
|
||||
foreach (object _ in e.OldItems)
|
||||
{
|
||||
Items.RemoveAt(index--);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSelectedIndex(object selectedItem)
|
||||
{
|
||||
string displayMember = GetDisplayMember(selectedItem);
|
||||
int index = Items.IndexOf(displayMember);
|
||||
// TODO Should we prevent call to FindObject since the object is already known
|
||||
// by setting a flag, or otherwise indicate, that we, internally, forced a SelectedIndex changed
|
||||
SelectedIndex = index;
|
||||
if (ItemsSource != null) {
|
||||
SelectedIndex = ItemsSource.IndexOf(selectedItem);
|
||||
return;
|
||||
}
|
||||
SelectedIndex = Items.IndexOf(selectedItem);
|
||||
}
|
||||
|
||||
void UpdateSelectedItem()
|
||||
{
|
||||
// coerceSelectedIndex ensures that SelectedIndex is in range [-1,ItemsSource.Count)
|
||||
SelectedItem = SelectedIndex == -1 ? null : ItemsSource?[SelectedIndex];
|
||||
if (SelectedIndex == -1) {
|
||||
SelectedItem = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ItemsSource != null) {
|
||||
SelectedItem = ItemsSource [SelectedIndex];
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedItem = Items [SelectedIndex];
|
||||
}
|
||||
|
||||
public IPlatformElementConfiguration<T, Picker> On<T>() where T : IConfigPlatform
|
||||
{
|
||||
return _platformConfigurationRegistry.Value.On<T>();
|
||||
}
|
||||
|
||||
class LockableObservableListWrapper : INotifyCollectionChanged, IList<string>
|
||||
{
|
||||
readonly ObservableList<string> _list = new ObservableList<string>();
|
||||
|
||||
public bool IsLocked { get; set; }
|
||||
|
||||
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged {
|
||||
add { _list.CollectionChanged += value; }
|
||||
remove { _list.CollectionChanged -= value; }
|
||||
}
|
||||
|
||||
void ThrowOnLocked()
|
||||
{
|
||||
if (IsLocked)
|
||||
throw new InvalidOperationException("The Items list can not be manipulated if the ItemsSource property is set");
|
||||
|
||||
}
|
||||
public string this [int index] {
|
||||
get { return _list [index]; }
|
||||
set {
|
||||
ThrowOnLocked();
|
||||
_list [index] = value; }
|
||||
}
|
||||
|
||||
public int Count {
|
||||
get { return _list.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly {
|
||||
get { return ((IList<string>)_list).IsReadOnly; }
|
||||
}
|
||||
|
||||
public void InternalAdd(string item)
|
||||
{
|
||||
_list.Add(item);
|
||||
}
|
||||
|
||||
public void Add(string item)
|
||||
{
|
||||
ThrowOnLocked();
|
||||
InternalAdd(item);
|
||||
}
|
||||
|
||||
public void InternalClear()
|
||||
{
|
||||
_list.Clear();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ThrowOnLocked();
|
||||
InternalClear();
|
||||
}
|
||||
|
||||
public bool Contains(string item)
|
||||
{
|
||||
return _list.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(string [] array, int arrayIndex)
|
||||
{
|
||||
_list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
{
|
||||
return _list.GetEnumerator();
|
||||
}
|
||||
|
||||
public int IndexOf(string item)
|
||||
{
|
||||
return _list.IndexOf(item);
|
||||
}
|
||||
|
||||
public void InternalInsert(int index, string item)
|
||||
{
|
||||
_list.Insert(index, item);
|
||||
}
|
||||
|
||||
public void Insert(int index, string item)
|
||||
{
|
||||
ThrowOnLocked();
|
||||
InternalInsert(index, item);
|
||||
}
|
||||
|
||||
public bool InternalRemove(string item)
|
||||
{
|
||||
return _list.Remove(item);
|
||||
}
|
||||
|
||||
public bool Remove(string item)
|
||||
{
|
||||
ThrowOnLocked();
|
||||
return InternalRemove(item);
|
||||
}
|
||||
|
||||
public void InternalRemoveAt(int index)
|
||||
{
|
||||
_list.RemoveAt(index);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
ThrowOnLocked();
|
||||
InternalRemoveAt(index);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_list).GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using Android.Content.Res;
|
|||
using Android.Text;
|
||||
using Android.Widget;
|
||||
using Object = Java.Lang.Object;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android.AppCompat
|
||||
{
|
||||
|
@ -31,7 +32,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
|
|||
{
|
||||
_disposed = true;
|
||||
|
||||
((ObservableList<string>)Element.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
((INotifyCollectionChanged)Element.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
@ -40,11 +41,11 @@ namespace Xamarin.Forms.Platform.Android.AppCompat
|
|||
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||
{
|
||||
if (e.OldElement != null)
|
||||
((ObservableList<string>)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
((INotifyCollectionChanged)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
((ObservableList<string>)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
|
||||
((INotifyCollectionChanged)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
|
||||
if (Control == null)
|
||||
{
|
||||
EditText textField = CreateNativeControl();
|
||||
|
|
|
@ -9,6 +9,7 @@ using ADatePicker = Android.Widget.DatePicker;
|
|||
using ATimePicker = Android.Widget.TimePicker;
|
||||
using Object = Java.Lang.Object;
|
||||
using Orientation = Android.Widget.Orientation;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Xamarin.Forms.Platform.Android
|
||||
{
|
||||
|
@ -30,7 +31,7 @@ namespace Xamarin.Forms.Platform.Android
|
|||
if (disposing && !_isDisposed)
|
||||
{
|
||||
_isDisposed = true;
|
||||
((ObservableList<string>)Element.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
((INotifyCollectionChanged)Element.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
|
@ -44,11 +45,11 @@ namespace Xamarin.Forms.Platform.Android
|
|||
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||
{
|
||||
if (e.OldElement != null)
|
||||
((ObservableList<string>)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
((INotifyCollectionChanged)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
((ObservableList<string>)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
|
||||
((INotifyCollectionChanged)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
|
||||
if (Control == null)
|
||||
{
|
||||
var textField = CreateNativeControl();
|
||||
|
|
|
@ -27,9 +27,9 @@ namespace Xamarin.Forms.Platform.WinPhone
|
|||
base.OnElementChanged(e);
|
||||
|
||||
if (e.OldElement != null)
|
||||
((ObservableList<string>)Element.Items).CollectionChanged -= ItemsCollectionChanged;
|
||||
((INotifyCollectionChanged)Element.Items).CollectionChanged -= ItemsCollectionChanged;
|
||||
|
||||
((ObservableList<string>)Element.Items).CollectionChanged += ItemsCollectionChanged;
|
||||
((INotifyCollectionChanged)Element.Items).CollectionChanged += ItemsCollectionChanged;
|
||||
|
||||
_listPicker.ItemTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["PickerItemTemplate"];
|
||||
_listPicker.FullModeItemTemplate = (System.Windows.DataTemplate)System.Windows.Application.Current.Resources["PickerFullItemTemplate"];
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using UIKit;
|
||||
using RectangleF = CoreGraphics.CGRect;
|
||||
|
@ -15,7 +16,7 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||
{
|
||||
if (e.OldElement != null)
|
||||
((ObservableList<string>)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
((INotifyCollectionChanged)e.OldElement.Items).CollectionChanged -= RowsCollectionChanged;
|
||||
|
||||
if (e.NewElement != null)
|
||||
{
|
||||
|
@ -55,7 +56,7 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
UpdatePicker();
|
||||
UpdateTextColor();
|
||||
|
||||
((ObservableList<string>)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
|
||||
((INotifyCollectionChanged)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
|
||||
}
|
||||
|
||||
base.OnElementChanged(e);
|
||||
|
|
|
@ -163,6 +163,22 @@ namespace FormsGallery
|
|||
</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="ItemDisplayBinding">
|
||||
<MemberSignature Language="C#" Value="public Xamarin.Forms.BindingBase ItemDisplayBinding { get; set; }" />
|
||||
<MemberSignature Language="ILAsm" Value=".property instance class Xamarin.Forms.BindingBase ItemDisplayBinding" />
|
||||
<MemberType>Property</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>Xamarin.Forms.BindingBase</ReturnType>
|
||||
</ReturnValue>
|
||||
<Docs>
|
||||
<summary>To be added.</summary>
|
||||
<value>To be added.</value>
|
||||
<remarks>To be added.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="Items">
|
||||
<MemberSignature Language="C#" Value="public System.Collections.Generic.IList<string> Items { get; }" />
|
||||
<MemberSignature Language="ILAsm" Value=".property instance class System.Collections.Generic.IList`1<string> Items" />
|
||||
|
@ -185,6 +201,37 @@ namespace FormsGallery
|
|||
<remarks>This property is read-only, but exposes the IList<> interface, so items can be added using Add().</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="ItemsSource">
|
||||
<MemberSignature Language="C#" Value="public System.Collections.IList ItemsSource { get; set; }" />
|
||||
<MemberSignature Language="ILAsm" Value=".property instance class System.Collections.IList ItemsSource" />
|
||||
<MemberType>Property</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.Collections.IList</ReturnType>
|
||||
</ReturnValue>
|
||||
<Docs>
|
||||
<summary>To be added.</summary>
|
||||
<value>To be added.</value>
|
||||
<remarks>To be added.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="ItemsSourceProperty">
|
||||
<MemberSignature Language="C#" Value="public static readonly Xamarin.Forms.BindableProperty ItemsSourceProperty;" />
|
||||
<MemberSignature Language="ILAsm" Value=".field public static initonly class Xamarin.Forms.BindableProperty ItemsSourceProperty" />
|
||||
<MemberType>Field</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>Xamarin.Forms.BindableProperty</ReturnType>
|
||||
</ReturnValue>
|
||||
<Docs>
|
||||
<summary>To be added.</summary>
|
||||
<remarks>To be added.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="On<T>">
|
||||
<MemberSignature Language="C#" Value="public Xamarin.Forms.IPlatformElementConfiguration<T,Xamarin.Forms.Picker> On<T> () where T : Xamarin.Forms.IConfigPlatform;" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public hidebysig newslot virtual instance class Xamarin.Forms.IPlatformElementConfiguration`2<!!T, class Xamarin.Forms.Picker> On<(class Xamarin.Forms.IConfigPlatform) T>() cil managed" />
|
||||
|
@ -275,6 +322,37 @@ namespace FormsGallery
|
|||
</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="SelectedItem">
|
||||
<MemberSignature Language="C#" Value="public object SelectedItem { get; set; }" />
|
||||
<MemberSignature Language="ILAsm" Value=".property instance object SelectedItem" />
|
||||
<MemberType>Property</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.Object</ReturnType>
|
||||
</ReturnValue>
|
||||
<Docs>
|
||||
<summary>To be added.</summary>
|
||||
<value>To be added.</value>
|
||||
<remarks>To be added.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="SelectedItemProperty">
|
||||
<MemberSignature Language="C#" Value="public static readonly Xamarin.Forms.BindableProperty SelectedItemProperty;" />
|
||||
<MemberSignature Language="ILAsm" Value=".field public static initonly class Xamarin.Forms.BindableProperty SelectedItemProperty" />
|
||||
<MemberType>Field</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>Xamarin.Forms.BindableProperty</ReturnType>
|
||||
</ReturnValue>
|
||||
<Docs>
|
||||
<summary>To be added.</summary>
|
||||
<remarks>To be added.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="TextColor">
|
||||
<MemberSignature Language="C#" Value="public Xamarin.Forms.Color TextColor { get; set; }" />
|
||||
<MemberSignature Language="ILAsm" Value=".property instance valuetype Xamarin.Forms.Color TextColor" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче