Vinculación de ItemsSource de un ComboBoxColumn en WPF DataGrid
Tengo dos clases de modelo simples y un modelo de vista...
public class GridItem
{
public string Name { get; set; }
public int CompanyID { get; set; }
}
public class CompanyItem
{
public int ID { get; set; }
public string Name { get; set; }
}
public class ViewModel
{
public ViewModel()
{
GridItems = new ObservableCollection<GridItem>() {
new GridItem() { Name = "Jim", CompanyID = 1 } };
CompanyItems = new ObservableCollection<CompanyItem>() {
new CompanyItem() { ID = 1, Name = "Company 1" },
new CompanyItem() { ID = 2, Name = "Company 2" } };
}
public ObservableCollection<GridItem> GridItems { get; set; }
public ObservableCollection<CompanyItem> CompanyItems { get; set; }
}
...y una ventana simple:
<Window x:Class="DataGridComboBoxColumnApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" />
<DataGridComboBoxColumn ItemsSource="{Binding CompanyItems}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValueBinding="{Binding CompanyID}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
ViewModel está configurado en MainWindow DataContext
en App.xaml.cs:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
ViewModel viewModel = new ViewModel();
window.DataContext = viewModel;
window.Show();
}
}
Como puede ver, configuré el ItemsSource
DataGrid en la GridItems
colección de ViewModel. Esta parte funciona, se muestra una única línea de cuadrícula con el nombre "Jim".
También quiero configurar ItemsSource
el ComboBox en cada fila para la CompanyItems
colección de ViewModel. Esta parte no funciona: el cuadro combinado permanece vacío y en la ventana de salida del depurador veo un mensaje de error:
Error System.Windows.Data: 2: No se puede encontrar el FrameworkElement o FrameworkContentElement que rige para el elemento de destino. BindingExpression:Path=CompanyItems; Elemento de datos=nulo; el elemento de destino es 'DataGridComboBoxColumn' (HashCode=28633162); la propiedad de destino es 'ItemsSource' (tipo 'IEnumerable')
Creo que WPF espera CompanyItems
ser una propiedad, GridItem
lo cual no es el caso, y esa es la razón por la cual falla el enlace.
Ya intenté trabajar con a RelativeSource
y AncestorType
así:
<DataGridComboBoxColumn ItemsSource="{Binding CompanyItems,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValueBinding="{Binding CompanyID}" />
Pero eso me da otro error en la salida del depurador:
Error System.Windows.Data: 4: No se puede encontrar la fuente para el enlace con la referencia 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=CompanyItems; Elemento de datos=nulo; el elemento de destino es 'DataGridComboBoxColumn' (HashCode=1150788); la propiedad de destino es 'ItemsSource' (tipo 'IEnumerable')
Pregunta: ¿Cómo puedo vincular ItemsSource de DataGridComboBoxColumn a la colección CompanyItems de ViewModel? ¿Es posible en absoluto?
¡Gracias por la ayuda de antemano!
Por favor, verifique si el xaml DataGridComboBoxColumn a continuación funcionaría para usted:
<DataGridComboBoxColumn
SelectedValueBinding="{Binding CompanyID}"
DisplayMemberPath="Name"
SelectedValuePath="ID">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.CompanyItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
Aquí puede encontrar otra solución para el problema al que se enfrenta: Usar cuadros combinados con WPF DataGrid
La documentación en MSDN sobre ItemsSource
diceDataGridComboBoxColumn
que solo los recursos estáticos, el código estático o las colecciones en línea de elementos del cuadro combinado se pueden vincular a ItemsSource
:
Para completar la lista desplegable, primero establezca la propiedad ItemsSource para ComboBox usando una de las siguientes opciones:
- Un recurso estático. Para obtener más información, consulte Extensión de marcado StaticResource.
- Una x: entidad de código estático. Para obtener más información, consulte x:Extensión de marcado estático.
- Una colección en línea de tipos ComboBoxItem.
No es posible vincularse a la propiedad de un DataContext si lo entiendo correctamente.
Y de hecho: cuando hago CompanyItems
una propiedad estática en ViewModel...
public static ObservableCollection<CompanyItem> CompanyItems { get; set; }
... agregue el espacio de nombres donde se encuentra ViewModel a la ventana ...
xmlns:vm="clr-namespace:DataGridComboBoxColumnApp"
... y cambiar el enlace a ...
<DataGridComboBoxColumn
ItemsSource="{Binding Source={x:Static vm:ViewModel.CompanyItems}}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValueBinding="{Binding CompanyID}" />
... entonces funciona. Pero tener ItemsSource como propiedad estática a veces puede estar bien, pero no siempre es lo que quiero.
La solución correcta parece ser:
<Window.Resources>
<CollectionViewSource x:Key="ItemsCVS" Source="{Binding MyItems}" />
</Window.Resources>
<!-- ... -->
<DataGrid ItemsSource="{Binding MyRecords}">
<DataGridComboBoxColumn Header="Column With Predefined Values"
ItemsSource="{Binding Source={StaticResource ItemsCVS}}"
SelectedValueBinding="{Binding MyItemId}"
SelectedValuePath="Id"
DisplayMemberPath="StatusCode" />
</DataGrid>
El diseño anterior funciona perfectamente bien para mí y debería funcionar para otros. Esta elección de diseño también tiene sentido, aunque no está muy bien explicada en ninguna parte. Pero si tiene una columna de datos con valores predefinidos, esos valores normalmente no cambian durante el tiempo de ejecución. Por lo tanto, crear CollectionViewSource
e inicializar los datos una vez tiene sentido. También elimina los enlaces más largos para encontrar un antepasado y vincularlo en su contexto de datos (lo que siempre me pareció incorrecto).
Dejo esto aquí para cualquiera que haya tenido problemas con este enlace y se haya preguntado si hay una manera mejor (como obviamente esta página todavía aparece en los resultados de búsqueda, así es como llegué aquí).