#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.