我正在使用绑定到集合的 WPF DataGrid。其中一列将是一个组合框。我希望 ComboBox 是可编辑的,并且对文本的任何更改都将传播到集合中之前使用相同值的所有其他项目。
这是我的 XAML:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Students}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" />
<DataGridTemplateColumn Header="Team Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox IsEditable="True" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=TeamNames}" Text="{Binding TeamName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Window>
这是我的代码:
public class Student : INotifyPropertyChanged
{
private string teamName;
public string Name { get; set; }
public string TeamName
{
get { return teamName; }
set
{
teamName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TeamName"));
}
}
public Student(string name, string teamName)
{
Name = name;
this.teamName = teamName;
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class MainWindow : Window
{
public ObservableCollection<Student> Students { get; set; } = new ObservableCollection<Student>(new Student[] { new Student("Alice", "Red"), new Student("Bob", "Blue"), new Student("Craig", "Blue") });
public List<string> TeamNames { get; set; } = new List<string>();
public MainWindow()
{
InitializeComponent();
Colors.AddRange(Students.Select(x => x.TeamName).Distinct());
}
}
例如,如果 Craig 的团队名称更改为“BlueGreen”,Bob 的团队名称应该同时更新:
我通过处理 ComboBox.TextBoxBase.TextChanged 事件已经非常接近了,但它变得足够复杂,让我认为必须有一种更简单的方法。
这不是最优雅的解决方案,但它会起作用并通常向您展示如何解决该问题。
首先,修改您的
Student
类以使用 id 作为 Team
而不是名称,并添加 Team
类来处理团队的 Id
和 Name
public class Student
{
public string Name { get; set; }
public int TeamId { get; set; }
public Student(string name, int teamId)
{
Name = name;
TeamId = teamId;
}
}
public class Team
{
public Team(int id, string name)
{
Id = id;
Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
}
然后您可以在
ComboBox
中关闭这些值。当 ComboBox
失去焦点时,您可以通过 Teams
事件更新您的 LostFocus
列表。
<DataGrid x:Name="DataTable" ItemsSource="{Binding Students}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name"/>
<DataGridTemplateColumn Header="Team Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.Teams,
RelativeSource={RelativeSource AncestorType=Window}}"
SelectedValue="{Binding TeamId}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
IsEditable="True"
BorderThickness="0"
LostFocus="ComboBox_LostFocus"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
public partial class EditableComboBox : Window
{
public ViewModel DataModel { get; set; }
public EditableComboBox()
{
InitializeComponent();
DataModel = new ViewModel();
DataContext = DataModel;
}
private void ComboBox_LostFocus(object sender, RoutedEventArgs e)
{
var record = (e.Source as TextBox)?.DataContext as Student;
if (record == null) return;
DataModel.UpdateTeam(record.TeamId, (e.Source as TextBox)?.Text);
DataTable.Items.Refresh();
}
}
public class ViewModel
{
public IReadOnlyList<Team> Teams { get; } = new List<Team>
{
new(1, "Blue"),
new(2, "Red" ),
new(3, "Green" ),
};
public List<Student> Students { get; set; } = new List<Student>
{
new("Name 1", 1),
new("Name 2", 2),
new("Name 3", 3),
new("Name 4", 1),
new("Name 5", 2),
new("Name 6", 3),
new("Name 7", 1),
new("Name 8", 2),
new("Name 9", 3),
};
public void UpdateTeam(int id, string newName)
{
var team = Teams.FirstOrDefault(x => x.Id == id);
if (team == null) return;
team.Name = newName;
}
}