Я изучаю 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: индекс вышел за пределы допустимого диапазона. Должно быть неотрицательным и меньше размера коллекции. (Параметр «индекс»)
Я чувствую, что это связано с тем, что коллекция пуста во время выполнения, но я не получаю эту ошибку, если использую какие-либо свойства «Параметр» в другом месте, и поскольку я не могу заполнить коллекцию до времени выполнения, я не совсем уверен как подойти к решению этой проблемы.
Это может быть полезно: stackoverflow.com/q/63207058/1136211 . См. также: docs.avaloniaui.net/docs/get-started/wpf/datatemplates
@Clemens Спасибо, кажется, я совершенно неправильно истолковал, как работает x:Key... Я просмотрел страницы, на которые вы ссылались, и думаю, что лучше это понимаю. Я отредактировал свой код, чтобы отразить это, но по-прежнему получаю исключение OutOfRangeException. Этот код работал, когда я тестировал его с базовым List<string>, заполненным при создании (конвертер шаблонов на тот момент также использовал строку), но, похоже, он не работает, даже если я предоставляю «Параметры» с образцами значений при оно создано.
В вашем коде в основном есть две ошибки.
Во-первых, <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>
Спасибо! Не знаю, почему я собирался использовать подпривязку вместо простого приведения к параметру.
Как это вообще должно работать? Свойство ItemTemplate не имеет привязки к каждому элементу. Текущий DataContext не является объектом параметра, а является DataContext элемента управления ItemsControl, поэтому свойство DataType отсутствует. Кроме того, метод Convert должен возвращать значение, которое можно присвоить свойству ItemTemplate, то есть DataTemplate, а не строку.