WPF DataGridTemplateColumn с привязкой ComboBox (шаблон MVVM)

Я схожу с ума со следующим сценарием WPF DataGrid+ComboBox.

у меня есть набор классов, которые выглядят как;

class Owner
{
    int ID { get; }
    string Name { get; }

    public override ToString()
    { 
        return this.Name;
    }
}

class House
{
    int ID { get; }
    Owner HouseOwner { get; set; }
}

class ViewModel
{
    ObservableCollection<Owner> Owners;
    ObservableCollection<House> Houses
}

теперь мой желаемый результат-DataGrid, который показывает список строк типа дома, и в одном из столбцов ComboBox, который позволяет пользователю изменять значение дом.Домовладелец!--8-->.

в этом случае DataContext для сетки ViewModel.Дома и для ComboBox, я хочу, чтобы ItemsSource был привязан к ViewModel.Владельцы.

это вообще возможно? Я схожу с ума от этого... лучшее, что я смог сделать, это правильно получить привязку ItemsSource, однако ComboBox (внутри DataGridTemplateColumn) не показывает правильные значения для House.Домовладелец в каждом ряду.

Примечание: Если я возьму ComboBox из изображения и помещу TextBlock в DataTemplate вместо этого, я могу правильно видеть значения для каждой строки, но получение как ItemsSource, так и показать правильное значение в выборе не работает для меня...

внутри моего кода позади, я установил DataContext в окне в ViewModel и в сетке DataContext установлен в ViewModel.Дома. Для всего, кроме этого combobox, он работает...

мой XAML для оскорбительного столбца выглядит как;

<DataGridTemplateColumn Header="HouseOwner">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                        DisplayMemberPath="Name"
                        SelectedItem="{Binding HouseOwner, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
                        SelectedValue="{Binding HouseOwner.ID, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Mode=OneWay}"
                        SelectedValuePath="ID" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

хотел бы немного помочь в этом... кажется, немного Вуду хотя требуется...

4 ответов


as по умолчанию.Крамер!--22--> сказал, вам нужно удалить RelativeSource из ваших Привязок на SelectedItem и SelectedValue вроде этого (обратите внимание, что вы должны добавить Mode=TwoWay к вашей привязке, чтобы изменение в combobox отражалось в вашей модели).

<DataGridTemplateColumn Header="House Owner">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox
                ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                DisplayMemberPath="Name"
                SelectedItem="{Binding HouseOwner, Mode=TwoWay}"
                SelectedValue="{Binding HouseOwner.ID}"
                SelectedValuePath="ID"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

однако, в отличие от того, что он сказал, вам не нужно удалять привязку для SelectedValue. В самом деле, если вы удалите его, он не будет работать (как SelectedValue и SelectedValuePath должно быть установлено здесь, как вы сделали), потому что это то, что позволяя механизму привязки идентифицировать выбор из combobox в Datagrid's HouseOwner собственность.

SelectedValue/SelectedValuePath комбинация очень интересная. SelectedValuePath сообщает привязке данных, что ID свойства Owner выбранный объект представляет его стоимостью, SelectedValue говорит, что значение должно быть привязано к HouseOwner.ID что является выбранным объектом в DataGrid.

поэтому, если вы удалите эти привязки, единственное, что механизм привязки данных будет знать, это "какой объект выбран" и сделать соответствие между выбранным элементом в ComboBox и HouseOwner свойство для выбранного элемента в DataGrid они должны быть "той же ссылкой на объект". Это означает, что, например, следующее не будет работать:

Owners = new ObservableCollection<Owner>
                {
                    new Owner {ID = 1, Name = "Abdou"},
                    new Owner {ID = 2, Name = "Moumen"}
                };
Houses = new ObservableCollection<House>
                {
                    new House {ID = 1, HouseOwner = new Owner {ID = 1, Name = "Abdou" }},
                    new House {ID = 2, HouseOwner = new Owner {ID = 2, Name = "Moumen"}}
                };

(обратите внимание, что" домовладельцы " коллекции домов отличаются (новые) от тех, в коллекции владельцев). Однако, следующее б работы:

Owners = new ObservableCollection<Owner>
                {
                    new Owner {ID = 1, Name = "Abdou"},
                    new Owner {ID = 2, Name = "Moumen"}
                };
Houses = new ObservableCollection<House>
                {
                    new House {ID = 1, HouseOwner = Owners[0]},
                    new House {ID = 2, HouseOwner = Owners[1]}
                };

надеюсь, что это помогает :)

обновление: во втором случае вы можете получить тот же результат, не имея одинаковых ссылок, переопределив равна на Owner класс (естественно, поскольку он используется для сравнения объектов в первую очередь). (спасибо @РЖ Лохан для того, чтобы отметить это в комментариях ниже.)


Спасибо за помощь все-я, наконец, понял, почему я не мог выбрать элементы ComboBox-из-за обработчика событий предварительного просмотра мыши, который я прикрепил к стилю ячейки, когда я использовал DataGridComboBoxColumn.

ударила себя за это, спасибо за помощь.

также, как Примечание; единственный способ, которым это будет работать для меня, - это с дополнительным;

IsSynchronizedWithCurrentItem="False"

добавлено в ComboBox, иначе все они показывают одинаковое значение для некоторая причина.

кроме того, мне не требуется SelectedValue/SelectedValuePath свойства в моей привязке, я считаю, потому что я переопределил равна в моем связанном типе владельца.

и наконец, я должен явно установить;

Mode=TwoWay, UpdateSourceTrigger=PropertyChanged

в привязке для того, чтобы значения были записаны обратно в связанные элементы, когда ComboBox изменился.

Итак, окончательный (рабочий) XAML для привязки выглядит так:

    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox 
                ItemsSource="{Binding Path=DataContext.Owners,  
                RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                IsSynchronizedWithCurrentItem="False"
                SelectedItem="{Binding HouseOwner, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"  />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>

Ура!

rJ


это определенно возможно, и вы на правильном пути, используя AncestorType привязка к ItemsSource. Но мне кажется, я вижу пару ошибок.

во-первых, ваш ItemsSource должен быть привязан к DataContext.Owners, а не DataContext.Houses, верно? Вы хотите, чтобы коллекция владельцев viewmodels отображалась в раскрывающемся списке. Итак, во-первых, изменить ItemsSource и выньте материал, связанный с выбором, например:

<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
          DisplayMemberPath="Name" />

теперь проверьте его и убедитесь, что ItemsSource работает правильно. Не пытайтесь возиться с выбором, пока эта часть не сработает.

что касается выбора, я думаю, вы должны быть обязательными SelectedItem только не SelectedValue. Для этой привязки ты не хочу RelativeSource привязка-DataContext будет одним House таким образом, вы можете напрямую связать его HouseOwner. Мое предположение таково:

<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
          DisplayMemberPath="Name"
          SelectedItem="{Binding HouseOwner}" />

наконец, для отладки Привязок вы можете см. окно вывода Visual Studio или шаг к инструменту, как Snoop или инспектор WPF. Если вы планируете делать много WPF, я бы рекомендовал начать работу со Snoop раньше, чем позже.


полный пример, основанный на предложении AbdouMoumen. Также удалены SelectedValue и SelectedValuePath.

enter image description here

//---------
//CLASS STRUCTURES.    
//---------
//One grid row per house.    
public class House
{
    public string name { get; set; }
    public Owner ownerObj { get; set; }
}

//Owner is a combobox choice.  Each house is assigned an owner.    
public class Owner
{
    public int id { get; set; }
    public string name { get; set; }
}

//---------
//FOR XAML BINDING.    
//---------
//Records for datagrid.  
public ObservableCollection<House> houses { get; set; }

//List of owners.  Each house record gets an owner object assigned.    
public ObservableCollection<Owner> owners { get; set; }

//---------
//INSIDE “AFTER CONTROL LOADED” METHOD.  
//---------
//Populate list of owners.  For combobox choices.  
owners = new ObservableCollection<Owner>
{
    new Owner {id = 1, name = "owner 1"},
    new Owner {id = 2, name = "owner 2"}
};

//Populate list of houses.  Again, each house is a datagrid record.  
houses = new ObservableCollection<House>
{
    new House {name = "house 1", ownerObj = owners[0]},
    new House {name = "house 2", ownerObj = owners[1]}
};


<DataGrid ItemsSource="{Binding Path=houses, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" >
    <DataGrid.Columns>
        <DataGridTextColumn Header="name" Binding="{Binding name}" />
        <DataGridTextColumn Header="owner (as value)" Binding="{Binding ownerObj.name}"/>

        <DataGridTemplateColumn Header="owner (as combobox)" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox
                            ItemsSource="{Binding Path=owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                            DisplayMemberPath="name"
                            SelectedItem="{Binding ownerObj, Mode=TwoWay}"
                            />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>

</DataGrid>