I have a need to display different UI elements per item in a CollectionView. The base type of each item in the CollectionView is a Setting. There are different types of settings: AnalogSetting, BooleanSetting, RangeSetting, etc. Each derived class has a different set of UI elements to allow the user to interact with it. BooleanSetting may have a simple toggle switch while the AnalogSetting has a slider. The amount and types of settings are delivered at runtime and can change on the fly.
The simplest way I could manage this was to use a DataTemplateSelector and then define different DataTemplates for each derived Setting class. There is a particular DataTemplate that causes my app to crash whenever I navigate away from the page that is displaying the CollectionView.
I have the following CollectionView:
...
<CollectionView
x:Name="SettingListView"
ItemsSource="{Binding Settings}"
ItemTemplate="{StaticResource settingSelector}"
SelectionMode="None">
</CollectionView>
The particular DataTemplate that is causing me grief:
<DataTemplate x:Key="AnalogSetting">
<StackLayout x:DataType="model:AnalogSetting">
<Label Text="{Binding Name}"/>
<Slider Maximum="{Binding MaximumValue}" Minimum="{Binding MinimumValue}" Value="{Binding AnalogValue}">
<Slider.Behaviors>
<behaviors:SliderBehavior Command="{Binding SetValue}" />
</Slider.Behaviors>
</Slider>
</StackLayout>
</DataTemplate>
The SliderBehavior class being used in the DataTemplate:
public class SliderBehavior : Behavior<Slider>
{
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(SliderBehavior), null);
public Slider Bindable { get; private set; }
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
protected override void OnAttachedTo(Slider bindable)
{
base.OnAttachedTo(bindable);
Bindable = bindable;
Bindable.BindingContextChanged += OnBindingContextChanged;
Bindable.ValueChanged += OnValueChanged;
}
protected override void OnDetachingFrom(Slider bindable)
{
base.OnDetachingFrom(bindable);
Bindable.BindingContextChanged -= OnBindingContextChanged;
Bindable.ValueChanged -= OnValueChanged;
Bindable = null;
}
private void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
BindingContext = Bindable.BindingContext;
}
private void OnValueChanged(object sender, ValueChangedEventArgs e)
{
double value = Math.Round(e.NewValue);
if (value != Math.Round(e.OldValue))
{
Command?.Execute(value.ToString());
}
Bindable.Value = value;
}
}
}
The error given to me whenever I navigate away from the page that is displaying this DataTemplate:
System.ArgumentException: 'Value is an invalid value for Maximum
Parameter name: value'
The relevant portion of the callstack given to me when this exception is thrown:
0xFFFFFFFFFFFFFFFF in System.Diagnostics.Debugger.Mono_UnhandledException_internal
0x1 in System.Diagnostics.Debugger.Mono_UnhandledException at /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/corlib/System.Diagnostics/Debugger.cs:125,4
0x26 in Android.Runtime.DynamicMethodNameCounter.148
0xBA in Xamarin.Forms.BindableObject.SetValueCore at D:\a\1\s\Xamarin.Forms.Core\BindableObject.cs:368,5
0x121 in Xamarin.Forms.Internals.TypedBinding<Common.Models.AnalogSetting,double>.ApplyCore at D:\a\1\s\Xamarin.Forms.Core\TypedBinding.cs:218,5
0x5D in Xamarin.Forms.Internals.TypedBinding<Common.Models.AnalogSetting,double>.Apply at D:\a\1\s\Xamarin.Forms.Core\TypedBinding.cs:135,4
0x51 in Xamarin.Forms.BindableObject.ApplyBindings at D:\a\1\s\Xamarin.Forms.Core\BindableObject.cs:480,5
0x5D in Xamarin.Forms.BindableObject.SetInheritedBindingContext at D:\a\1\s\Xamarin.Forms.Core\BindableObject.cs:209,4
0x2 in Xamarin.Forms.Element.SetChildInheritedBindingContext at D:\a\1\s\Xamarin.Forms.Core\Element.cs:470,4
0x8 in Xamarin.Forms.Element.<OnBindingContextChanged>b__82_0 at D:\a\1\s\Xamarin.Forms.Core\Element.cs:308,5
0x2F in Xamarin.Forms.BindableObjectExtensions.PropagateBindingContext<Xamarin.Forms.Element> at D:\a\1\s\Xamarin.Forms.Core\BindableObjectExtensions.cs:28,5
0x13 in Xamarin.Forms.Element.OnBindingContextChanged at D:\a\1\s\Xamarin.Forms.Core\Element.cs:306,4
0x7 in Xamarin.Forms.VisualElement.OnBindingContextChanged at D:\a\1\s\Xamarin.Forms.Core\VisualElement.cs:812,4
0xD in Xamarin.Forms.View.OnBindingContextChanged at D:\a\1\s\Xamarin.Forms.Core\View.cs:158,4
0x10 in Xamarin.Forms.BindableObject.BindingContextPropertyChanged at D:\a\1\s\Xamarin.Forms.Core\BindableObject.cs:500,4 C#
0x12E in Xamarin.Forms.BindableObject.SetValueActual at D:\a\1\s\Xamarin.Forms.Core\BindableObject.cs:463,5
0x17C in Xamarin.Forms.BindableObject.SetValueCore at D:\a\1\s\Xamarin.Forms.Core\BindableObject.cs:397,5
0x56 in Xamarin.Forms.BindableObject.SetValue at D:\a\1\s\Xamarin.Forms.Core\BindableObject.cs:334,4
0x5 in Xamarin.Forms.BindableObject.SetValue at D:\a\1\s\Xamarin.Forms.Core\BindableObject.cs:311,68
0x7 in Xamarin.Forms.BindableObject.set_BindingContext at D:\a\1\s\Xamarin.Forms.Core\BindableObject.cs:41,11
0x13 in Xamarin.Forms.Platform.Android.TemplatedItemViewHolder.Recycle at D:\a\1\s\Xamarin.Forms.Platform.Android\CollectionView\TemplatedItemViewHolder.cs:38,4
0x16 in Xamarin.Forms.Platform.Android.ItemsViewAdapter<Xamarin.Forms.GroupableItemsView,Xamarin.Forms.Platform.Android.IGroupableItemsViewSource>.OnViewRecycled at D:\a\1\s\Xamarin.Forms.Platform.Android\CollectionView\ItemsViewAdapter.cs:64,5
0x32 in Xamarin.Forms.Platform.Android.SelectableItemsViewAdapter<Xamarin.Forms.GroupableItemsView,Xamarin.Forms.Platform.Android.IGroupableItemsViewSource>.OnViewRecycled at D:\a\1\s\Xamarin.Forms.Platform.Android\CollectionView\SelectableItemsViewAdapter.cs:53,4
0x11 in
A couple things:
OnDetachingFromin my customBehavior<Slider>class never gets called, even after theOnDisappearingfunction of the owning page is invoked- I'm aware of the requirement to set the sliders Maximum value before the Minimum value. It's very possible the oddities of the
Sliderclass is biting me here. - I believe the
AnalogSettingmodel is being destroyed when the page is navigated away from, but theBehavior<Slider>class is still attempting to use the values that it bound to initially. I don't know this 100%, but it's my current guess.
Debugging this has been a pain, since none of my code is in the callstack. Any tips on how to go about figuring out more information within the callstack would be appreciated.
I'm open to suggestions in regards to using something other than DataTemplateSelector and Behaviors to get what I'm after.