Tengo dos clases de modelo simples y un ViewModel ...
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 simple ventana:
<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 se establece 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é ItemsSource
el DataGrid en la GridItems
colección de ViewModel. Esta parte funciona, se muestra la línea de cuadrícula única con el nombre "Jim".
También quiero establecer el ItemsSource
ComboBox en cada fila en la CompanyItems
colección de ViewModel. Esta parte no funciona: el ComboBox permanece vacío y en la ventana de salida del depurador veo un mensaje de error:
Error de System.Windows.Data: 2: No se puede encontrar FrameworkElement o FrameworkContentElement para el elemento de destino. BindingExpression: Path = CompanyItems; DataItem = null; el elemento de destino es 'DataGridComboBoxColumn' (HashCode = 28633162); la propiedad de destino es 'ItemsSource' (tipo 'IEnumerable')
Creo que WPF espera CompanyItems
ser una propiedad de la GridItem
cual no es el caso, y esa es la razón por la que 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 de 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; DataItem = null; 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!
La documentación de MSDN sobre la
ItemsSource
deDataGridComboBoxColumn
dice que solo los recursos estáticos, el código estático o las colecciones en línea de elementos de cuadro combinado se pueden vincular aItemsSource
:La vinculación a la propiedad de un DataContext no es posible 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 cambie 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 una propiedad estática a veces puede estar bien, pero no siempre es lo que quiero.
fuente
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 se explica muy bien 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, tiene sentido crear
CollectionViewSource
e inicializar los datos una vez. También se deshace de los enlaces más largos para encontrar un ancestro y enlazar su contexto de datos (que siempre me pareció mal).Dejo esto aquí para cualquier otra persona que haya tenido problemas con este enlace y se preguntó si había una mejor manera (como esta página obviamente todavía aparece en los resultados de búsqueda, así es como llegué aquí).
fuente
MyItems
daría lugar a un error de compilación si se utiliza con el código de la OPMe doy cuenta de que esta pregunta tiene más de un año, pero me encontré con ella al tratar con un problema similar y pensé que compartiría otra posible solución en caso de que pudiera ayudar a un futuro viajero (oa mí mismo, cuando me olvide de esto más tarde y me encuentre dando vueltas en StackOverflow entre gritos y lanzamientos del objeto más cercano en mi escritorio).
En mi caso, pude obtener el efecto que quería usando un DataGridTemplateColumn en lugar de un DataGridComboBoxColumn, al estilo del siguiente fragmento. [advertencia: estoy usando .NET 4.0, y lo que he estado leyendo me lleva a creer que DataGrid ha evolucionado mucho, por lo que YMMV si usa una versión anterior]
<DataGridTemplateColumn Header="Identifier_TEMPLATED"> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <ComboBox IsEditable="False" Text="{Binding ComponentIdentifier,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding Path=ApplicableIdentifiers, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" /> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding ComponentIdentifier}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn>
fuente
RookieRick tiene razón, usar en
DataGridTemplateColumn
lugar deDataGridComboBoxColumn
ofrece un XAML mucho más simple.Además, poner la
CompanyItem
lista directamente accesible desde leGridItem
permite deshacerse delRelativeSource
.En mi humilde opinión, esto te da una solución muy limpia.
XAML:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding GridItems}" > <DataGrid.Resources> <DataTemplate x:Key="CompanyDisplayTemplate" DataType="vm:GridItem"> <TextBlock Text="{Binding Company}" /> </DataTemplate> <DataTemplate x:Key="CompanyEditingTemplate" DataType="vm:GridItem"> <ComboBox SelectedItem="{Binding Company}" ItemsSource="{Binding CompanyList}" /> </DataTemplate> </DataGrid.Resources> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding Name}" /> <DataGridTemplateColumn CellTemplate="{StaticResource CompanyDisplayTemplate}" CellEditingTemplate="{StaticResource CompanyEditingTemplate}" /> </DataGrid.Columns> </DataGrid>
Ver modelo:
public class GridItem { public string Name { get; set; } public CompanyItem Company { get; set; } public IEnumerable<CompanyItem> CompanyList { get; set; } } public class CompanyItem { public int ID { get; set; } public string Name { get; set; } public override string ToString() { return Name; } } public class ViewModel { readonly ObservableCollection<CompanyItem> companies; public ViewModel() { companies = new ObservableCollection<CompanyItem>{ new CompanyItem { ID = 1, Name = "Company 1" }, new CompanyItem { ID = 2, Name = "Company 2" } }; GridItems = new ObservableCollection<GridItem> { new GridItem { Name = "Jim", Company = companies[0], CompanyList = companies} }; } public ObservableCollection<GridItem> GridItems { get; set; } }
fuente
Su ComboBox está intentando enlazar para enlazar
GridItem[x].CompanyItems
, lo cual no existe.Su RelativeBinding está cerca, sin embargo, debe vincularse
DataContext.CompanyItems
porque Window.CompanyItems no existefuente
CompanyItems
porDataContext.CompanyItems
en el último fragmento de XAML en mi pregunta) pero me da el mismo error en la salida del depurador.{Binding ElementName=RootWindow, Path=DataContext.CompanyItems}
la forma en que utilizo el bastidor, enlazo el bloque de texto y el cuadro combinado a la misma propiedad y esta propiedad debería ser compatible con notifyPropertyChanged.
Utilicé un recurso relativo para unirme al contexto de datos de la vista principal, que es el control del usuario para subir el nivel de la cuadrícula de datos en el enlace porque en este caso la cuadrícula de datos buscará en el objeto que usó en la cuadrícula de datos.
<DataGridTemplateColumn Header="your_columnName"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SelectedUnit.Name, Mode=TwoWay}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <ComboBox DisplayMemberPath="Name" IsEditable="True" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.UnitLookupCollection}" SelectedItem="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.SelectedUnit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding UnitId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Id" /> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> </DataGridTemplateColumn>
fuente