Avalonia.UI ComboBox 没有 SelectedValuePath 和 DisplayMemberPath,我没有足够的技术来实现它们。 我已经尝试使用 TemplatedControl 作为解决方法,因为无论如何我都在使用 TemplatedControl,并且我不想在 ViewModel 上有额外的属性来处理它。 我想要存档的是通过 DisplayMember 显示 ComboBox 中的项目,但通过 Id 加载/保存。在此测试用例中通过 Project.ManagerId。它正在工作,但最好不必在后面的 TemplatedControl 代码中硬编码类型,就像我现在使用“LookupItem”所做的那样。应该可以通过设置 x:DataType="viewModels:LookupItem" 或其他内容来解决这个问题。 有什么想法吗?
我删除了大部分不相关的代码。
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:ChangeTrackingHeaderControls.Controls">
<Design.PreviewWith>
<controls:ChangeTrackingHeaderComboBox />
</Design.PreviewWith>
<Style Selector="controls|ChangeTrackingHeaderComboBox">
<!-- Set Defaults -->
<Setter Property="Template">
<ControlTemplate>
<ComboBox Items="{TemplateBinding Items, Mode=OneWay}"
SelectedItem="{TemplateBinding SelectedItem, Mode=TwoWay}"
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"
ItemTemplate="{TemplateBinding ItemTemplate}"
HorizontalAlignment="Stretch" />
</ControlTemplate>
</Setter>
</Style>
</Styles>
public class ChangeTrackingHeaderComboBox : TemplatedControl
{
private object? _selectedItem;
private int? _selectedValue;
public static readonly DirectProperty<ChangeTrackingHeaderComboBox, object?> SelectedItemProperty =
AvaloniaProperty.RegisterDirect<ChangeTrackingHeaderComboBox, object?>(nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
public static readonly DirectProperty<ChangeTrackingHeaderComboBox, int?> SelectedValueProperty =
AvaloniaProperty.RegisterDirect<ChangeTrackingHeaderComboBox, int?>(nameof(SelectedValue),
o => o.SelectedValue,
(o, v) => o.SelectedValue = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
public static readonly StyledProperty<IEnumerable> ItemsProperty = AvaloniaProperty
.Register<ChangeTrackingHeaderComboBox, IEnumerable>(nameof(Items));
public static readonly StyledProperty<int> SelectedIndexProperty = AvaloniaProperty
.Register<ChangeTrackingHeaderComboBox, int>(nameof(SelectedIndex));
public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty = AvaloniaProperty
.Register<ChangeTrackingHeaderComboBox, IDataTemplate>(nameof(ItemTemplate));
public object? SelectedItem
{
get => _selectedItem;
set
{
SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
OnSelectedItemChanged();
}
}
public int? SelectedValue
{
get => _selectedValue;
set
{
SetAndRaise(SelectedValueProperty, ref _selectedValue, value);
OnSelectedValueChanged();
}
}
public IEnumerable Items
{
get => GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}
public int SelectedIndex
{
get => GetValue(SelectedIndexProperty);
set => SetValue(SelectedIndexProperty, value);
}
public IDataTemplate ItemTemplate
{
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
{
base.UpdateDataValidation(property, state, error);
if (property == SelectedItemProperty)
{
DataValidationErrors.SetError(this, error);
}
if (property == SelectedValueProperty)
{
DataValidationErrors.SetError(this, error);
}
}
private void OnSelectedItemChanged()
{
// LookupItem should not be hard coded
if (SelectedItem is LookupItem selectedItem)
{
if (SelectedValue == selectedItem.Id) return;
if (Items is IEnumerable<LookupItem> items)
{
var selectedItemDisplayMember = selectedItem.DisplayMember;
SelectedValue = items.SingleOrDefault(x => x.DisplayMember == selectedItemDisplayMember).Id;
}
}
}
private void OnSelectedValueChanged()
{
// LookupItem should not be hard coded
if (Items is IEnumerable<LookupItem> items)
{
if (SelectedItem == null)
{
SelectedItem = items.SingleOrDefault(x => x.Id == SelectedValue);
return;
}
if (SelectedItem is LookupItem selectedItem)
{
if (selectedItem.Id == SelectedValue) return;
SelectedItem = items.SingleOrDefault(x => x.Id == SelectedValue);
}
}
}
}
// App.axaml
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:ChangeTrackingHeaderControls.Controls"
xmlns:viewModels="clr-namespace:ChangeTrackingHeaderControls.ViewModels"
x:Class="ChangeTrackingHeaderControls.App">
<Application.Resources>
<DataTemplate x:Key="LookupItemItemTemplate" x:DataType="viewModels:LookupItem">
<ComboBoxItem Content="{CompiledBinding DisplayMember}"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center" />
</DataTemplate>
</Application.Resources>
<Application.Styles>
<FluentTheme Mode="Light" />
<StyleInclude Source="/Controls/ChangeTrackingHeaderComboBox.axaml" />
</Application.Styles>
</Application>`
// MainWindow
<controls:ChangeTrackingHeaderComboBox Items="{CompiledBinding Managers}"
SelectedValue="{CompiledBinding Project.ManagerId}"
ItemTemplate="{StaticResource LookupItemItemTemplate}" />
// ViewModel
public sealed class MainViewModel : ViewModelBase
{
private Project _project;
public MainViewModel()
{
Managers = new ObservableCollection<LookupItem>();
// Simulating loading Managers from database
for (var i = 1; i < 12; i++)
{
Managers.Add(new LookupItem
{
Id = i,
DisplayMember = $"Manager {i}"
});
}
// One single Project loaded from database
Project = new Project
{
Id = 1,
Name = "Project 1",
ManagerId = Managers.LastOrDefault()?.Id
};
}
public ObservableCollection<LookupItem> Managers { get; }
public Project Project
{
get => _project;
private set
{
if (value == _project) return;
_project = value;
OnPropertyChanged();
}
}
}
// Models
public sealed class Project
{
public int Id { get; set; }
public string Name { get; set; }
public int? ManagerId { get; set; }
}
public sealed class LookupItem
{
public int Id { get; init; }
public string DisplayMember { get; init; }
}```
I have tried to use x:DataType="viewModels:LookupItem" and also implemented StyledProperty<Type> but no succcess.
在绑定到
ViewModel
的 ComboBox
属性的 Items
实例上,覆盖 ToString()
并返回您想要在打开的下拉列表中显示的值。
请改用 DisplayMemberBinding 和 SelectedValueBinding 属性。由于这些是绑定,您需要在 xaml 中使用 {Binding somepath}