#c# #xamarin #xamarin.forms #xamarin.android
#c# #xamarin #xamarin.forms #xamarin.android
Вопрос:
Мне нужно отображать разные элементы пользовательского интерфейса для каждого элемента в CollectionView
. Базовый тип каждого элемента в CollectionView
является Setting
. Существуют различные типы настроек: AnalogSetting
, BooleanSetting
, RangeSetting
и т.д. Каждый производный класс имеет различный набор элементов пользовательского интерфейса, позволяющий пользователю взаимодействовать с ним. BooleanSetting
может иметь простой переключатель, в то время как у AnalogSetting
есть ползунок. Количество и типы настроек предоставляются во время выполнения и могут меняться на лету.
Самый простой способ, которым я мог бы справиться с этим, — это использовать DataTemplateSelector
, а затем определить разные DataTemplates
для каждого производного Setting
класса. Есть одна особенность, DataTemplate
которая приводит к сбою моего приложения всякий раз, когда я удаляюсь от страницы, на которой отображается CollectionView
.
У меня есть следующее CollectionView
:
...
<CollectionView
x:Name="SettingListView"
ItemsSource="{Binding Settings}"
ItemTemplate="{StaticResource settingSelector}"
SelectionMode="None">
</CollectionView>
То, DataTemplate
что вызывает у меня огорчение:
<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>
SliderBehavior
Класс, используемый в 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;
}
}
}
Ошибка, выдаваемая мне всякий раз, когда я удаляюсь от страницы, на которой отображается это DataTemplate
:
System.ArgumentException: 'Value is an invalid value for Maximum
Parameter name: value'
Соответствующая часть стека вызовов, предоставляемая мне при возникновении этого исключения:
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:a1sXamarin.Forms.CoreBindableObject.cs:368,5
0x121 in Xamarin.Forms.Internals.TypedBinding<Common.Models.AnalogSetting,double>.ApplyCore at D:a1sXamarin.Forms.CoreTypedBinding.cs:218,5
0x5D in Xamarin.Forms.Internals.TypedBinding<Common.Models.AnalogSetting,double>.Apply at D:a1sXamarin.Forms.CoreTypedBinding.cs:135,4
0x51 in Xamarin.Forms.BindableObject.ApplyBindings at D:a1sXamarin.Forms.CoreBindableObject.cs:480,5
0x5D in Xamarin.Forms.BindableObject.SetInheritedBindingContext at D:a1sXamarin.Forms.CoreBindableObject.cs:209,4
0x2 in Xamarin.Forms.Element.SetChildInheritedBindingContext at D:a1sXamarin.Forms.CoreElement.cs:470,4
0x8 in Xamarin.Forms.Element.<OnBindingContextChanged>b__82_0 at D:a1sXamarin.Forms.CoreElement.cs:308,5
0x2F in Xamarin.Forms.BindableObjectExtensions.PropagateBindingContext<Xamarin.Forms.Element> at D:a1sXamarin.Forms.CoreBindableObjectExtensions.cs:28,5
0x13 in Xamarin.Forms.Element.OnBindingContextChanged at D:a1sXamarin.Forms.CoreElement.cs:306,4
0x7 in Xamarin.Forms.VisualElement.OnBindingContextChanged at D:a1sXamarin.Forms.CoreVisualElement.cs:812,4
0xD in Xamarin.Forms.View.OnBindingContextChanged at D:a1sXamarin.Forms.CoreView.cs:158,4
0x10 in Xamarin.Forms.BindableObject.BindingContextPropertyChanged at D:a1sXamarin.Forms.CoreBindableObject.cs:500,4 C#
0x12E in Xamarin.Forms.BindableObject.SetValueActual at D:a1sXamarin.Forms.CoreBindableObject.cs:463,5
0x17C in Xamarin.Forms.BindableObject.SetValueCore at D:a1sXamarin.Forms.CoreBindableObject.cs:397,5
0x56 in Xamarin.Forms.BindableObject.SetValue at D:a1sXamarin.Forms.CoreBindableObject.cs:334,4
0x5 in Xamarin.Forms.BindableObject.SetValue at D:a1sXamarin.Forms.CoreBindableObject.cs:311,68
0x7 in Xamarin.Forms.BindableObject.set_BindingContext at D:a1sXamarin.Forms.CoreBindableObject.cs:41,11
0x13 in Xamarin.Forms.Platform.Android.TemplatedItemViewHolder.Recycle at D:a1sXamarin.Forms.Platform.AndroidCollectionViewTemplatedItemViewHolder.cs:38,4
0x16 in Xamarin.Forms.Platform.Android.ItemsViewAdapter<Xamarin.Forms.GroupableItemsView,Xamarin.Forms.Platform.Android.IGroupableItemsViewSource>.OnViewRecycled at D:a1sXamarin.Forms.Platform.AndroidCollectionViewItemsViewAdapter.cs:64,5
0x32 in Xamarin.Forms.Platform.Android.SelectableItemsViewAdapter<Xamarin.Forms.GroupableItemsView,Xamarin.Forms.Platform.Android.IGroupableItemsViewSource>.OnViewRecycled at D:a1sXamarin.Forms.Platform.AndroidCollectionViewSelectableItemsViewAdapter.cs:53,4
0x11 in
Пара вещей:
OnDetachingFrom
в моем пользовательскомBehavior<Slider>
классе никогда не вызывается, даже после того, как вызванаOnDisappearing
функция страницы-владельца- Я знаю о требовании установить максимальное значение ползунков перед минимальным значением. Очень возможно, что странности
Slider
класса кусают меня здесь. - Я считаю, что
AnalogSetting
модель уничтожается при переходе от страницы, ноBehavior<Slider>
класс все еще пытается использовать значения, к которым он привязан изначально. Я не знаю этого на 100%, но это мое текущее предположение.
Отладка этого была болезненной, поскольку ни один из моих кодов не находится в стеке вызовов. Любые советы о том, как получить дополнительную информацию в стеке вызовов, будут оценены.
Я открыт для предложений относительно использования чего-то другого, кроме DataTemplateSelector
и Behaviors
, чтобы получить то, что мне нужно.
Комментарии:
1. Я бы начал с добавления try / catch ко всем методам в поведении и посмотрел, может ли это помочь вам сузить его
2. @Jason Я включил весь код в поведение и ничего не выбросил. Я также перенес
OnDisappearing
на страницу владельца (поскольку это последняя строка моего кода, которая, как я знаю, выполняется).3. если вы можете создать тестовый пример, я бы предложил отправить его на XF github, чтобы посмотреть, что они говорят
Ответ №1:
У нас тоже была проблема с нашими ползунками, когда мы использовали DataTemplate, мы сделали следующее: поскольку каждый DataTemplate на самом деле является просто прославленным элементом XAML, мы можем безопасно установить x:Name
свойство на нашем слайдере, и оно будет уникальным и не вызовет никаких конфликтов во время выполнения / компиляции из-за неуникальных имен.
Итак, в нашем SliderTemplate.xaml мы устанавливаем Max и Min
<Slider
x:Name="NoteSlider"
Maximum="5"
Minimum="0"
...
/>
Затем в нашем SliderTemplate.xaml.cs:
-
Определите нашу модель представления, которую мы используем
-
Переопределите OnBindingContextChanged, чтобы фактически создать его экземпляр
-
Установите правильные минимальные / максимальные значения.
private MyViewModel controlModel;
public SliderInputTemplate() { InitializeComponent(); } protected override void OnBindingContextChanged() { base.OnBindingContextChanged(); controlModel = this.BindingContext as MyViewModel ; BindingContext = controlModel; } protected override void OnAppearing() { base.OnAppearing(); if(controlModel != null) { //controlModel.UpdateSlider(); if(controlModel.Max > controlModel.Min) { NoteSlider.Maximum = controlModel.Max; NoteSlider.Minimum = controlModel.Min; NoteSlider.Value = controlModel.SliderValue; } ... } }
Я хочу сказать, что причина, по которой это вызывает это исключение, заключается в том, что, скорее всего, виртуальная машина удаляется при переходе, но представление все еще вроде как работает и, следовательно, оно находится в плохом состоянии. Вот как мы решили эту проблему
Комментарии:
1. Я понял это. Это была проблема max / min. Минимальное значение для моего слайдера было 1, а не 0. Я думаю, когда ползунок уничтожается, maximum стремится к нулю, что приводит к max < min. Я изменил min на 0 и больше никаких сбоев. Мне нужно придумать способ поймать этот случай и убедиться, что min всегда < max.
2. Я думаю, что принудительное использование max и min в xaml и установка для него правильного значения в коде, как я сделал, должно решить вашу проблему
3. Вы правы. Я надеялся, что смогу сохранить привязку, а затем вручную сбросить значения в
OnDisappering
, но это все равно приводит к сбою. Установка значений по разумным значениям по умолчанию и последующее их применениеOnAppearing
работает лучше всего.
Ответ №2:
Я получал ту же ошибку при выходе со страницы. Чтобы исправить это, я добавил FallbackValue
параметр к Maximum
атрибуту в XAML и установил для него значение, большее, чем любое из моих минимальных значений, которое могло бы быть.
<Slider Maximum="{Binding Max, FallbackValue=1000000}" Minimum="{Binding Min}" Value="{Binding SettingValue}"></Slider>