Привязка ComboBox вызывается не для каждой строки в DataGrid

#wpf #binding #datagrid #combobox #datagridtemplatecolumn

#wpf #привязка #datagrid #combobox #datagridtemplatecolumn

Вопрос:

Я пытаюсь создать combobox внутри DataGridTemplateColumn, но он должен содержать разные значения в зависимости от строки. Вот мой код:

 <dg:DataGridTemplateColumn x:Name ="NameColumn" Header="Player Name">
    <dg:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox
                SelectedValue="0"
                DisplayMemberPath="FullName"
                SelectedValuePath="Id"
                ItemsSource="{Binding AllPlayers, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/>
        </DataTemplate>
    </dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
  

Все проигрыватели будут возвращать разные списки после каждого вызова.

 public List<Player> AllPlayers
{
    get 
    {
        counter = counter   1;
        Debug.Print("getting all players "   counter);

        List<Player> lst = new List<Player>();

        for (int i=0; i < 5; i  ) 
        {
            Player p = new Player();
            p.Id = counter   i;
            p.FullName = "Name "   counter   i;
            lst.Add(p);
        }

        return lst;
    }
}
  

По какой-то причине функция AllPlayers вызывается для первых 39 строк, а затем данные берутся из ранее созданных списков. Я вижу это из отладочной информации (она перестает печатать после 39 вызовов). А также списки в выпадающих списках не уникальны. Я не понимаю логики такого поведения. Мне нужно, чтобы AllPlayers вызывались для каждой строки.

Ответ №1:

Покажите вашу привязку к сетке. Я бы сделал Players общедоступным свойством коллекции, которую вы привязываете к сетке. В ctor для списка из 39 создайте там AllPlayers. Предположим, что ваш список из 39 — это команды и имеет свойства Name, Manager, City, Players. Даже если вы создадите игроков в шаблоне, они не будут напрямую связаны с командой (без обхода визуального дерева).

Комментарии:

1. Я не могу изменить коллекцию, которую я привязываю к datagrid, потому что она автоматически генерируется из dataLayer.

2. Я знаю, что вам не понравится этот ответ, но вы могли бы создать пользовательский объект из DataTable и привязать к нему. Для эффективности используйте DataReader . Если вы используете расширенную функцию DataTable, такую как edit, это еще менее привлекательно. Возможно, вы сможете создать набор данных со связью и вручную добавить своих игроков в набор данных. Я сделал эту привязку обратно в Windows Forms, но с WPF и List и ObservableCollection я ушел от наборов данных.

Ответ №2:

Ваш подход неверен .. во-первых, вы не должны доверять порядку, в котором происходит виртуализация datagrid. Следовательно, подход на основе счетчика для загрузки разных списков происходит хаотично.

Когда строка datagrid де-виртуализируется, ваш combobox становится видимым и запрашивает источник элементов и получает его из Window.AllPlayers свойства. Но порядок counter будет изменен на основе прокрутки. Если вы внезапно прокручиваете, пропуская несколько строк, или если вы используете отложенную прокрутку counter , это всегда будет неправильно. Если вы будете прокручивать вперед и назад counter , это приведет к завинчиванию (поскольку я не вижу никакого кода для уменьшения счетчика)…

Итак, суть в том, пожалуйста, не используйте этот подход.

Теперь вы сказали, что не хотите загружать список из отдельного элемента. counter Переменная, вероятно, относится к Index текущей строке в datagrid ItemsSource . Если это так, вы могли бы по крайней мере использовать мультиконвертер для того же.

Combobox XAML:

     <ComboBox
        SelectedValue="0"
        DisplayMemberPath="FullName"
        SelectedValuePath="Id" >
        <ComboBox.ItemsSource>
            <MultiBinding Converter="{StaticResource RowWiseListConverter}">
                <!--The current row item-->
                <Binding BindsDirectlyToSource="True" /> 

                <!---The items source of the data grid.-->
                <Binding Path="ItemsSource"
                         RelativeSource="{RelativeSource
                                 AncestorType={x:Type DataGrid}}"/>
            </MultiBinding>
        </ComboBox.ItemsSource>
    </ComboBox>
  

Код с несколькими конвертерами:

 public class RowWiseListConverter : IMultiValueConverter
{
    public object Convert(
            object[] values,
            Type targetType,
            object parameter,
            CultureInfo culture)
    {
        var item = values[0];
        var list = values[1] as System.Collections.IEnumerable;

        if (item != null amp;amp; list != null)
        {
            var counter = list.Cast<object>().ToList().IndexOf(item);

            List<Player> lst = new List<Player>();
            for (int i = 0; i < 5; i  )
            {
                Player p = new Player();
                p.Id = counter   i;
                p.FullName = "Name "   counter   i;
                lst.Add(p);
            }

            return lst; 
        }

        return null;
    }
    .....
}
  

Код предназначен только для иллюстрации и может не компилироваться.

Надеюсь, это поможет.

Ответ №3:

Я не использовал счетчик для подсчета индексов, а просто для целей отладки, чтобы подсчитать количество раз, когда вызывалась функция, и использовать ее для создания уникального списка для каждого combobox. В моем исходном коде используется тот же подход, который вы предложили. Вот конвертер:

 Public Function Convert(ByVal value() As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert
    Dim playerReportRow As MainAdminDS.PlayerReportRow = value(0).Row
    'Dim sourceList As MainAdminDS.PRSourceDataTable = SmallReportForm.GetSmallReportForm().PRSource
    Dim sourceList As MainAdminDS.PRSourceDataTable = value(1)

    Dim sourceListView As New List(Of MainAdminDS.PRSourceRow)

    Dim rand As New Random
    For i As Integer = 0 To sourceList.Count - 1
        If (sourceList(i).PRSource_Id = playerReportRow.PlayerReport_Source Or rand.Next(0, 2) = 0) Then
            sourceListView.Add(sourceList(i))
        End If
    Next

    Return sourceListView
End Function
  

Снова я создаю уникальный список для целей отладки. Это тоже не работает!!!

Я нашел решение, добавив новые поля в dataLayer типа Object, и они не назначаются никаким полям. Эти поля содержат список для выпадающих списков, и я инициализирую этот список индивидуально для каждого объекта.. Это отлично работает. Но меня все еще озадачивает, почему предыдущий подход не сработал. У меня такое чувство, что это просто ошибка в WPF.