Подтвердить что ты не робот

Связывающие элементыСвойства 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 установлен в 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. Эта часть работает, отображается единственная строка Grid с названием "Jim".

Я также хочу установить ItemsSource ComboBox в каждой строке в коллекцию CompanyItems ViewModel. Эта часть не работает: ComboBox остается пустым и в окне вывода отладчика появляется сообщение об ошибке:

Ошибка System.Windows.Data: 2: не удается найти Управление FrameworkElement или FrameworkContentElement для целевой элемент. BindingExpression: Path = CompanyItems; DataItem = NULL; целевой элемент 'DataGridComboBoxColumn' (HashCode = 28633162); целевое свойство это "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); целевое свойство "ItemsSource" (тип "IEnumerable" )

Вопрос: Как я могу привязать ItemSource DataGridComboBoxColumn к коллекции CompanyItems ViewModel? Возможно ли вообще?

Благодарим вас за помощь!

4b9b3361

Ответ 1

Pls, проверьте, будет ли 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

Ответ 2

Документация в MSDN о ItemsSource DataGridComboBoxColumn говорит, что к статическим ресурсам, статическому коду или встроенным коллекциям элементов combobox можно привязать ItemsSource:

Чтобы заполнить раскрывающийся список, сначала установите для свойства ItemsSource ComboBox, используя один из следующих Параметры:

  • Статический ресурс. Дополнительные сведения см. В разделе Разметка StaticResource Extension.
  • Объект x: объект статического кода. Для получения дополнительной информации см. X: Статическая разметка Extension.
  • Встроенный набор типов ComboBoxItem.

Привязка к свойству DataContext невозможна, если я правильно понимаю.

И действительно: когда я создаю CompanyItems свойство static в 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 как статического свойства иногда может быть ОК, но это не всегда то, что я хочу.

Ответ 3

Правильное решение похоже:

<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 и инициализация данных имеет смысл. Он также избавляется от более длинных привязок, чтобы найти предка и привязать к нему контекст данных (который всегда мне не нравился).

Я оставляю это здесь для всех, кто боролся с этой привязкой, и задавался вопросом, есть ли лучший способ (поскольку эта страница, очевидно, все еще подходит к результатам поиска, как я сюда попал).

Ответ 4

Я понимаю, что этот вопрос уже больше года, но я просто наткнулся на него, столкнувшись с подобной проблемой, и подумал, что поделюсь другим потенциальным решением, если это может помочь будущему путешественнику (или мне самому, когда я забуду это позже и найти себя flopping вокруг на StackOverflow между криками и броски ближайшего объекта на моем столе).

В моем случае я смог получить эффект, который я хотел, используя DataGridTemplateColumn вместо DataGridComboBoxColumn, a la следующий фрагмент. [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>

Ответ 5

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

Ответ 6

Ваш ComboBox пытается привязать к привязке к GridItem[x].CompanyItems, который не существует.

Ваше RelativeBinding близко, однако его необходимо привязать к DataContext.CompanyItems, потому что Window.CompanyItems не существует

Ответ 7

bast way я use Я привязываю textblock и combobox к тому же свойству, и это свойство должно поддерживать notifyPropertyChanged.

Я использовал relativeresource для привязки к родительскому представлению datacontext, который является usercontrol для повышения уровня 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>