Привязка ItemsSource для ComboBoxColumn в WPF DataGrid
У меня есть два простых класса Model и 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; }
}
... и простое окно:
<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 установлен в главное окно DataContext
в 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();
}
}
Как вы можете видеть, я установил ItemsSource
DataGrid к GridItems
Коллекция ViewModel. Эта часть работает, отображается одна строка сетки с именем "Джим".
Я также хочу установить ItemsSource
ComboBox в каждом ряду до CompanyItems
Коллекция ViewModel. Эта часть не работает: ComboBox остается пустым, и в окне вывода отладчика я вижу сообщение об ошибке:
Ошибка System.Windows.Data: 2: не удается найти управляющий FrameworkElement или FrameworkContentElement для целевого элемента. BindingExpression:Path=CompanyItems; DataItem= NULL; целевым элементом является DataGridComboBoxColumn (HashCode=28633162); Свойство target - "ItemsSource" (тип "IEnumerable")
Я полагаю, что WPF ожидает CompanyItems
быть собственностью GridItem
что не так, и это причина, по которой связывание не выполняется.
Я уже пытался работать с RelativeSource
а также AncestorType
вот так:
<DataGridComboBoxColumn ItemsSource="{Binding CompanyItems,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValueBinding="{Binding CompanyID}" />
Но это дает мне еще одну ошибку в выводе отладчика:
Ошибка System.Windows.Data: 4: Не удается найти источник для привязки со ссылкой 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=CompanyItems; DataItem= NULL; целевым элементом является DataGridComboBoxColumn (HashCode=1150788); Свойство target - "ItemsSource" (тип "IEnumerable")
Вопрос: Как я могу связать ItemsSource DataGridComboBoxColumn с коллекцией CompanyItems ViewModel? Это вообще возможно?
Заранее спасибо за помощь!
8 ответов
Пожалуйста, проверьте, будет ли работать DataGridComboBoxColumn xaml ниже для вас:
<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>
Здесь вы можете найти другое решение проблемы, с которой вы столкнулись: Использование комбинированных полей с WPF DataGrid
Документация на MSDN о ItemsSource
из DataGridComboBoxColumn
говорит, что только статические ресурсы, статический код или встроенные коллекции элементов комбинированного списка могут быть связаны с ItemsSource
:
Чтобы заполнить раскрывающийся список, сначала установите свойство ItemsSource для ComboBox, используя один из следующих параметров:
- Статический ресурс. Для получения дополнительной информации см. Расширение разметки StaticResource.
- X: Статический кодовый объект. Для получения дополнительной информации см. X: Расширение статической разметки.
- Встроенная коллекция типов ComboBoxItem.
Привязка к свойству DataContext невозможна, если я правильно понимаю.
И действительно: когда я делаю CompanyItems
статическое свойство во ViewModel...
public static ObservableCollection<CompanyItem> CompanyItems { get; set; }
... добавить пространство имен, где ViewModel находится в окне...
xmlns:vm="clr-namespace:DataGridComboBoxColumnApp"
... и измените привязку на...
<DataGridComboBoxColumn
ItemsSource="{Binding Source={x:Static vm:ViewModel.CompanyItems}}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValueBinding="{Binding CompanyID}" />
... тогда это работает. Но иногда можно использовать ItemSource в качестве статического свойства, но это не всегда то, что я хочу.
Правильное решение кажется:
<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>
Схема выше работает отлично для меня, и должен работать для других. Этот выбор дизайна также имеет смысл, хотя он нигде не очень хорошо объяснен. Но если у вас есть столбец данных с предопределенными значениями, эти значения обычно не изменяются во время выполнения. Так что создание CollectionViewSource
и инициализация данных однажды имеет смысл. Он также избавляет от более длинных привязок, чтобы найти предка и привязать его к контексту данных (что всегда казалось мне неправильным).
Я оставляю это здесь для всех, кто боролся с этим связыванием и задавался вопросом, был ли лучший способ (поскольку эта страница, очевидно, все еще появляется в результатах поиска, вот как я сюда попал).
Я понимаю, что этому вопросу больше года, но я наткнулся на него, имея дело с подобной проблемой, и подумал, что поделюсь другим потенциальным решением в случае, если оно поможет будущему путешественнику (или мне самому, когда я забуду об этом позже и найду себя шлепается по Stackru между криками и бросками ближайшего объекта на моем столе).
В моем случае я смог получить желаемый эффект, используя DataGridTemplateColumn вместо DataGridComboBoxColumn, как в следующем фрагменте. [caveat: я использую.NET 4.0, и то, что я читал, наводит меня на мысль, что DataGrid сильно развился, поэтому YMMV, если используется более ранняя версия]
<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>
RookieRick прав, используя DataGridTemplateColumn
вместо DataGridComboBoxColumn
дает гораздо более простой XAML.
Кроме того, положить CompanyItem
список напрямую доступен из GridItem
позволяет избавиться от RelativeSource
,
ИМХО, это даст вам очень чистое решение.
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>
Посмотреть модель:
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; }
}
Ваш ComboBox пытается привязать к GridItem[x].CompanyItems
, которого не существует
Ваше RelativeBinding близко, однако оно должно быть привязано к DataContext.CompanyItems
потому что Window.CompanyItems не существует
Это работает для меня
<DataGridTemplateColumn Width="*" Header="Block Names">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
VerticalContentAlignment="Center"
ItemsSource="{Binding DataContext.LayerNames, RelativeSource={RelativeSource Findancestor, AncestorType={x:Type Window}}}"
SelectedItem="{Binding LayerName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
В большинстве случаев я использую текстовый блок и комбинированный список с одним и тем же свойством, и это свойство должно поддерживать notifyPropertyChanged.
я использовал относительный ресурс для привязки к родительскому представлению datacontext, который является usercontrol для повышения уровня сетки данных в привязке, потому что в этом случае сетка данных будет искать в объекте, который вы использовали в 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>