У меня есть два простых класса модели и 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 установлен на MainWindow 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: Путь = CompanyItems; DataItem = null; целевой элемент - DataGridComboBoxColumn (HashCode = 28633162); целевым свойством является ItemsSource (тип IEnumerable)
Я считаю, что WPF ожидает CompanyItems
быть свойством, GridItem
которого нет, и поэтому привязка не выполняется.
Я уже пробовал работать с a 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: Путь = CompanyItems; DataItem = null; целевой элемент - DataGridComboBoxColumn (HashCode = 1150788); целевым свойством является ItemsSource (тип IEnumerable)
Вопрос. Как я могу привязать ItemsSource DataGridComboBoxColumn к коллекции CompanyItems ViewModel? Это вообще возможно?
Заранее благодарю за помощь!
Документация на MSDN о
ItemsSource
изDataGridComboBoxColumn
говорит , что только статические ресурсы, статический код или встроенные наборы элементов COMBOBOX могут быть связаны сItemsSource
:Если я правильно понимаю, привязка к свойству 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}" />
... тогда это работает. Но наличие ItemsSource в качестве статического свойства иногда может быть приемлемым, но не всегда то, что я хочу.
источник
Кажется, что правильное решение:
<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
и инициализация данных имеет смысл один раз. Он также избавляется от более длинных привязок, чтобы найти предка и привязать его к контексту данных (что всегда казалось мне неправильным).Я оставляю это здесь для всех, кто боролся с этой привязкой и задавался вопросом, есть ли лучший способ (поскольку эта страница, очевидно, все еще появляется в результатах поиска, вот как я попал сюда).
источник
MyItems
использовании с кодом OP это приведет к ошибке компиляцииЯ понимаю, что этому вопросу больше года, но я просто наткнулся на него при решении аналогичной проблемы и подумал, что поделюсь другим потенциальным решением, если оно может помочь будущему путешественнику (или мне самому, когда я забуду об этом позже и обнаружу себя шлепаюсь на StackOverflow между криками и бросаниями ближайшего предмета на моем столе).
В моем случае я смог получить желаемый эффект, используя DataGridTemplateColumn вместо DataGridComboBoxColumn, как в следующем фрагменте. [предостережение: я использую .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
.IMHO, это дает вам очень чистое решение.
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 не существуетисточник
CompanyItems
наDataContext.CompanyItems
в последнем фрагменте XAML в моем вопросе), но это дает мне ту же ошибку в выводе отладчика.{Binding ElementName=RootWindow, Path=DataContext.CompanyItems}
Ловкий способ, которым я использую, я привязываю текстовый блок и поле со списком к одному и тому же свойству, и это свойство должно поддерживать notifyPropertyChanged.
Я использовал relativeresource для привязки к родительскому представлению datacontext, который является пользовательским контролем для повышения уровня datagrid в привязке, потому что в этом случае datagrid будет искать в объекте, который вы использовали в 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>
источник