Авалония – изменение DataTemplate на основе значений коллекции вызывает исключение ArgumentOutOfRangeException

Я изучаю Avalonia и пытаюсь переключить ItemsControl между двумя разными DataTemplates на основе свойства в ObservableCollection пользовательских объектов.

У меня есть ObservableCollection<Parameter> под названием «Параметры», где «Параметр» определен следующим образом:

public class Parameter
{
  public Parameter(string value = "") {
    if (value.ToLower() == "switch") {
      IsBool = true;
    }
  }
  
  private string _name;
  public string Name {
    get { return _name; }
    set {
      _name = value;
    }
  }

  public bool IsBool { get; set; } = false;
}

Это мой код XAML, в котором определен ItemsControl:

<ItemsControl Grid.IsSharedSizeScope = "True" ItemsSource = "{Binding Parameters}">
  <ItemsControl.ItemTemplate>
    <Binding Path = "IsBool">
      <resources:ParameterTemplateSelector>

        <DataTemplate x:Key = "true">
          <Grid Margin = "20 10 ">
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width = "Auto" SharedSizeGroup = "Label" />
              <ColumnDefinition Width = "10" />
              <ColumnDefinition Width = "*" MaxWidth = "600" />
            </Grid.ColumnDefinitions>
            <Label Content = "{ReflectionBinding Path=Name}" VerticalContentAlignment = "Center"                   
                   HorizontalContentAlignment = "Right" />
            <ToggleSwitch Grid.Column = "2"/>
          </Grid>
        </DataTemplate>

        <DataTemplate x:Key = "false">
          <Grid Margin = "20 10 ">
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width = "Auto" SharedSizeGroup = "Label" />
              <ColumnDefinition Width = "10" />
              <ColumnDefinition Width = "*" MaxWidth = "600" />
            </Grid.ColumnDefinitions>
            <Label Content = "{ReflectionBinding Path=Name}" VerticalContentAlignment = "Center"
                   HorizontalContentAlignment = "Right" />
            <AutoCompleteBox Name = "AutoCompleteBox" Grid.Column = "2"
                             HorizontalAlignment = "Stretch"/>
          </Grid>
        </DataTemplate>

      </resources:ParameterTemplateSelector>
    </Binding>
  </ItemsControl.ItemTemplate>
</ItemsControl>

А вот «ParameterTemplateConverter» (хотя и без него код выдает ту же ошибку):

public class ParameterTemplateSelector : IDataTemplate
{
  [Content]
  public Dictionary<bool, IDataTemplate> AvailableTemplates { get; } = new Dictionary<bool, IDataTemplate>();

  public Control Build(object? param)
  {
    var key = param?.Equals(true);

    if (key == null) {
      throw new ArgumentNullException(nameof(param));
    }
    
    return AvailableTemplates[key.Value].Build(param);
  }

  public bool Match(object? data)
  {
    var key = data?.Equals(true);

    return data is bool
           && key != null
           && AvailableTemplates.ContainsKey(key.Value);
  }
}

В идеале это должно создать сетку с ToggleSwitch для каждого «IsBool» со ​​значением true и одну с AutoCompleteBox для остальных, но попытка запустить код дает мне эту ошибку, предположительно в связанном свойстве «IsBool»:

Ошибка AVLN: 0004 Avalonia: внутренняя ошибка компилятора при преобразовании узла XamlX.Ast.XamlAstObjectNode: System.ArgumentOutOfRangeException: индекс вышел за пределы допустимого диапазона. Должно быть неотрицательным и меньше размера коллекции. (Параметр «индекс»)

Я чувствую, что это связано с тем, что коллекция пуста во время выполнения, но я не получаю эту ошибку, если использую какие-либо свойства «Параметр» в другом месте, и поскольку я не могу заполнить коллекцию до времени выполнения, я не совсем уверен как подойти к решению этой проблемы.

Как это вообще должно работать? Свойство ItemTemplate не имеет привязки к каждому элементу. Текущий DataContext не является объектом параметра, а является DataContext элемента управления ItemsControl, поэтому свойство DataType отсутствует. Кроме того, метод Convert должен возвращать значение, которое можно присвоить свойству ItemTemplate, то есть DataTemplate, а не строку.

Clemens 22.08.2024 12:39

@Clemens Спасибо, кажется, я совершенно неправильно истолковал, как работает x:Key... Я просмотрел страницы, на которые вы ссылались, и думаю, что лучше это понимаю. Я отредактировал свой код, чтобы отразить это, но по-прежнему получаю исключение OutOfRangeException. Этот код работал, когда я тестировал его с базовым List<string>, заполненным при создании (конвертер шаблонов на тот момент также использовал строку), но, похоже, он не работает, даже если я предоставляю «Параметры» с образцами значений при оно создано.

electroshock777 23.08.2024 10:57
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В вашем коде в основном есть две ошибки.

Во-первых, <resources:ParameterTemplateSelector> не следует объявлять внутри привязки. XAML должен выглядеть следующим образом:

<ItemsControl ItemsSource = "{Binding Parameters}" ...>
    <ItemsControl.ItemTemplate>
        <resources:ParameterTemplateSelector>
            <DataTemplate x:Key = "true">
                ...
            </DataTemplate>
            <DataTemplate x:Key = "false">
                ...
            </DataTemplate>
        </resources:ParameterTemplateSelector>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Во-вторых, код ParameterTemplateSelector должен выглядеть так, как показано ниже. Я использовал #nullable disable для краткости.

Обратите внимание, что объект, передаваемый методу Build, тот же, что и объект, передаваемый методу Match. Следовательно, параметр метода также должен быть data, потому что param здесь сбивает с толку. Название было именно таким, потому что метод Build объявлен в базовом интерфейсе с дополнительными целями.

#nullable disable

public class ParameterTemplateSelector : IDataTemplate
{
    [Content]
    public Dictionary<string, IDataTemplate> AvailableTemplates { get; }
        = new Dictionary<string, IDataTemplate>();

    public Control Build(object data)
    {
        // cast data to Parameter and access the property value
        var parameter = (Parameter)data;
        var key = parameter.IsBool.ToString().ToLower();

        return AvailableTemplates[key].Build(parameter);
    }

    public bool Match(object data)
    {
        // test if data is an instance of Parameter
        return data is Parameter;
    }
}

В общем, кажется бессмысленным вводить свойство IsBool. Лучше сохраните свойство string, которое можно будет напрямую использовать в качестве ключа словаря.

Возможно, объявите класс параметров следующим образом:

public class Parameter
{
    public string Name { get; set; }
    public string Kind { get; set; }
}

и используйте свойство Kind в качестве ключа:

public Control Build(object data)
{
    var parameter = (Parameter)data;

    return AvailableTemplates[parameter.Kind].Build(parameter);
}

с

<resources:ParameterTemplateSelector>
    <DataTemplate x:Key = "switch">
        ...
    </DataTemplate>
    <DataTemplate x:Key = "acbox">
        ...
    </DataTemplate>
</resources:ParameterTemplateSelector>

Спасибо! Не знаю, почему я собирался использовать подпривязку вместо простого приведения к параметру.

electroshock777 23.08.2024 12:53

Другие вопросы по теме