Xamarin Slider: ‘Значение является недопустимым значением для максимума’ (только при переходе от страницы)

#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 
  

Пара вещей:

  1. OnDetachingFrom в моем пользовательском Behavior<Slider> классе никогда не вызывается, даже после того, как вызвана OnDisappearing функция страницы-владельца
  2. Я знаю о требовании установить максимальное значение ползунков перед минимальным значением. Очень возможно, что странности Slider класса кусают меня здесь.
  3. Я считаю, что 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:

  1. Определите нашу модель представления, которую мы используем

  2. Переопределите OnBindingContextChanged, чтобы фактически создать его экземпляр

  3. Установите правильные минимальные / максимальные значения.

    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>