Елементи прив'язки Джерело ComboBoxColumn у WPF DataGrid


82

У мене є два простих класи Моделі та 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 встановлено на DataContextMainWindow 's в 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();
    }
}

Як бачите, я встановив ItemsSourceDataGrid до GridItemsколекції ViewModel. Ця частина працює, відображається одна лінія сітки з ім'ям "Джим".

Я також хочу встановити значення ItemsSourceComboBox у кожному рядку до CompanyItemsколекції ViewModel. Ця частина не працює: ComboBox залишається порожнім, і у вікні виводу налагоджувача я бачу повідомлення про помилку:

Помилка System.Windows.Data: 2: Не вдається знайти керування FrameworkElement або FrameworkContentElement для цільового елемента. BindingExpression: Path = 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: Path = CompanyItems; DataItem = null; цільовим елементом є 'DataGridComboBoxColumn' (HashCode = 1150788); цільовою властивістю є 'ItemsSource' (тип 'IEnumerable')

Питання: Як я можу прив'язати ItemsSource DataGridComboBoxColumn до колекції CompanyItems ViewModel? Чи можливо це взагалі?

Дякуємо за допомогу заздалегідь!

Відповіді:


123

Будь ласка, перевірте, чи працює для вас нижче 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


4
Чорт, це працює !!! Якби я тільки міг зрозуміти, чому? А чому б не оригінальний код із змінами, рекомендованими Рейчел? У будь-якому разі, велике спасибі!
Слаума

1
Я вважаю, ви можете знайти пояснення тут: wpf.codeplex.com/workitem/8153?ProjectName=wpf (див. Коментарі)
serge_gubenko

1
Здається, вони вирішили перетворити цю помилку ("Ми внесли помилку у нашу внутрішню базу даних, щоб її виправити в майбутньому випуску."), У функцію. Погляньте на мою власну відповідь у цій темі: проблема вирішена документацією, що є вагомим свідченням того, що це ніколи не зміниться.
Slauma

1
+1 для посилання joemorrison.org/blog/2009/02/17/… . це вирішило моє питання. Відстій, ~ 5 годин, і я зрозумів, що вже мав цей тип у своєму проекті для чогось іншого, що ми робили :( Це завжди процес навчання.
TravisWhidden

Не працює у мене. Здається, EditingElementStyle працює, але чомусь після того, як я додаю ElementStyle, я отримую ComboBoxes, які нічого не заповнюють (замість значення з DisplayMemberPath), і він не повернеться до EditingElementStyle, коли я клацну.
Вільям

46

Документація на MSDN про ItemsSourceзDataGridComboBoxColumn говорить , що тільки статичні ресурси, статичний код або вбудовані набори елементів COMBOBOX можуть бути пов'язані з 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 як статичну властивість іноді може бути нормально, але це не завжди те, що я хочу.


1
я все ще сподіваюся, що Microsoft виправить цю помилку
juFo 02

38

Здається, правильним рішенням є:

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

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


1
Хоча, безперечно, точна відповідь, вона, можливо, абстрагована від питання ОП. Це MyItemsпризведе до помилки компіляції, якщо використовуватиметься з кодом операційної системи
MickyD

22

Я усвідомлюю, що цьому питанню вже більше року, але я просто натрапив на нього, вирішуючи подібну проблему, і думав, що поділюся ще одним потенційним рішенням, якщо це може допомогти майбутньому мандрівнику (або мені самому, коли я забуду це пізніше і опинюсь плюхаючись на 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>

Після боротьби з першими двома відповідями, я спробував це, і це теж спрацювало для мене. Дякую.
coson

7

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

4

Ваш ComboBox намагається прив’язати, до GridItem[x].CompanyItemsякого не існує.

Ваше RelativeBinding близьке, однак його потрібно прив’язати, DataContext.CompanyItemsоскільки Window.CompanyItems не існує


Дякую за відповідь! Я спробував це (замінено CompanyItemsна DataContext.CompanyItemsв останньому фрагменті XAML у моєму запитанні), але це призводить до тієї ж помилки у вихідних даних налагоджувача.
Slauma

1
@Slauma Я тоді не впевнений, це має спрацювати. Єдине, що я бачу у XAML у вас, це Mode = FindAncestor, і я зазвичай це пропускаю. Ви намагалися надати корінному вікну ім'я та вказати його за назвою у вашому прив'язку замість використання RelativeSource? {Binding ElementName=RootWindow, Path=DataContext.CompanyItems}
Рейчел

Спробував обидві речі (опущений режим = FindAncestor і змінив прив’язку до іменованого елемента), але це не працює. Дивно, що такий спосіб працює для вас. Я створив цей простий тестовий додаток, щоб перетягнути проблему з мого додатка в дуже простий контекст. Я не знаю, що я можу зробити неправильно, код, який ви бачите у питанні, - це повна програма (створена із шаблону проекту WPF у VS2010), навколо цього коду більше нічого немає.
Слаума

1

найпопулярніший спосіб, яким я користуюся, прив'язує текстовий блок та комбобокс до тієї ж властивості, і ця властивість повинна підтримувати 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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.