.net wpf binding wpfdatagrid datagridcomboboxcolumn

.net - Artículos vinculantes Origen de un ComboBoxColumn en WPF DataGrid



binding wpfdatagrid (7)

La documentación en MSDN sobre ItemsSource de DataGridComboBoxColumn dice que solo recursos estáticos, código estático o colecciones en línea de elementos del cuadro combinado pueden vincularse a ItemsSource :

Para rellenar la lista desplegable, primero establezca la propiedad ItemsSource para ComboBox utilizando una de las siguientes opciones:

  • Un recurso estático. Para obtener más información, vea 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 de ComboBoxItem.

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 está ubicado 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 una propiedad estática a veces puede ser correcto, pero no siempre es lo que quiero.

Tengo dos clases de modelos 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 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 DataContext de MainWindow 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, configuro el ItemsSource del DataGrid en la colección GridItems del ViewModel. Esta parte funciona, se muestra la línea de cuadrícula única con el nombre "Jim".

También quiero establecer el ItemsSource del ComboBox en cada fila a la colección CompanyItems del 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 FrameworkElemento 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 que CompanyItems sea ​​propiedad de GridItem que no es el caso, y esa es la razón por la que falla el enlace.

Ya he intentado trabajar con 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:

System.Windows.Data Error: 4: No se puede encontrar el origen del 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 el ItemsSource del DataGridComboBoxColumn a la colección CompanyItems del ViewModel? ¿Es posible?

¡Gracias por la ayuda de antemano!


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 generalmente no cambian durante el tiempo de ejecución. Entonces, crear un CollectionViewSource e inicializar los datos una vez tiene sentido. También se deshace de los enlaces más largos para encontrar un ancestro y vincularse en su contexto de datos (lo que siempre me pareció mal).

Lo dejo aquí para cualquier otra persona que haya tenido problemas con esta encuadernación, y me pregunté si habría una mejor manera (dado que esta página obviamente sigue apareciendo en los resultados de búsqueda, así es como llegué aquí).


Me doy cuenta de que esta pregunta tiene más de un año, pero me topé 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 viajero futuro (o a mí mismo, cuando lo olvide más tarde y me encuentre dando vueltas en 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 igual que el siguiente fragmento de código. [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>


Por favor, compruebe si DataGridComboBoxColumn xaml 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


RookieRick tiene razón, usar DataGridTemplateColumn lugar de DataGridComboBoxColumn le da un XAML mucho más simple.

Además, poner la lista CompanyItem directamente accesible desde GridItem permite deshacerse de RelativeSource .

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; } }


Su ComboBox está intentando enlazarse para unirse a GridItem[x].CompanyItems , que no existe.

Su RelativeBinding está cerca, sin embargo, necesita vincularse a DataContext.CompanyItems porque Window.CompanyItems no existe.


la forma de bast que utilizo ata el bloque de texto y el cuadro combinado a la misma propiedad y esta propiedad debería ser compatible con notifyPropertyChanged.

utilicé relativeresource para enlazar a parent view datacontext que es usercontrol para subir el nivel de la grilla de datos en el enlace porque en este caso la grilla de datos buscará en el objeto que usaste en datagrid.itemsource

<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>