使用编译时未知列将对象绑定到WPF数据网格

时间:2018-11-08 19:55:45

标签: c# wpf datagrid

  

注意:问题与Bind Objects with generic list to wpf datagrid相同(未回答),该问题要求提供涉及CellTemplates的解决方案,并且不包含任何解决方法。我愿意接受任何解决方案,并且有一个可行的(但非理想的)解决方案。

设置是我有一个对象列表(人),每个对象列表都包含一个数据对象列表。

class Person : List<DataObject>
{
    public string id { get; set; }
}
class DataObject
{
    public string columnName { get; set;}
    public string value { get; set;}
}

columnNames基于用户输入,但是每个人在其DataObjects列表中都具有相同的columnNames(即,它们都具有firstName列,lastName列,dateOfBirth列,均具有不同的值)。

我想以DataGrid格式显示这些值,以便用户可以编辑这些值。

我目前正在使用的是网格(editGrid),并为每个列标题添加子TextBlocks,然后循环遍历各项以为每个单元格添加TextBoxes。这适用于少量数字,但是当我有成千上万的人时,由于创建的TextBox和TextBlocks数量庞大,程序会滞后。

List<People> items;
List<string> columnHeaders;
Grid editGrid;

// Generate column headers
editGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
var columns = items.SelectMany(o => o.Select(a => a.columnName)).Distinct(StringComparer.OrdinalIgnoreCase);
foreach (string text in columns)
{
    TextBlock headerText = new TextBlock();
    headerText.Text = text;
    Grid.SetColumn(headerText, editGrid.ColumnDefinitions.Count());
    editGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });

    columnHeaders.Add(text);
    editGrid.Children.Add(headerText);
 }

// Create rows
foreach (var item in items)
{
    foreach (var dataObject in item)
    {
        var columnNum = columnHeaders.IndexOf(dataObject.columnName);
        if (columnNum != -1)
        {
            TextBox valueBox = new TextBox();
            Binding bind = new Binding();
            bind.Source = dataObject;
            bind.Mode = BindingMode.TwoWay;
            bind.Path = new PropertyPath("value");
            BindingOperations.SetBinding(valueBox , TextBox.TextProperty, bind);

            Grid.SetColumn(valueBox, columnNum);
            Grid.SetRow(valueBox, editGrid.RowDefinitions.Count);
            editGrid.Children.Add(valueBox);
        }
    }
    editGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
}

1 个答案:

答案 0 :(得分:1)

您是否考虑过使用ItemsControl创建DataGrid效果?像这样:

<ItemsControl ItemsSource="{Binding}" Name="IC1"  VirtualizingStackPanel.IsVirtualizing="True" ScrollViewer.CanContentScroll="True">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.Template>
        <ControlTemplate>
            <Border
        BorderThickness="{TemplateBinding Border.BorderThickness}"
        Padding="{TemplateBinding Control.Padding}"
        BorderBrush="{TemplateBinding Border.BorderBrush}"
        Background="{TemplateBinding Panel.Background}"
        SnapsToDevicePixels="True">
                <ScrollViewer
                Padding="{TemplateBinding Control.Padding}"
                Focusable="False">
                    <ItemsPresenter
                    SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                </ScrollViewer>
            </Border>
        </ControlTemplate>
    </ItemsControl.Template>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="1*" />
                    <ColumnDefinition Width="9*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <TextBlock Text="ID" Grid.Column="0" Grid.Row="0"/>
                <TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding Path=id}"/>
                <ItemsControl Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" ItemsSource="{Binding}" >
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Horizontal"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <StackPanel>
                                <TextBlock Text="{Binding Path=columnName}" Width="100" />
                                <TextBox Text="{Binding Path=value}" Width="100" />
                            </StackPanel>                                    
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

我填充了一些测试数据(很抱歉,但在VB中):

Property PersonList As New ObservableCollection(Of Person)

Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
    For x As Integer = 1 To 500
        Dim P1 As New Person With {.id = "SE" & x}
        P1.Add(New DataObject With {.columnName = "First Name", .value = "Simon"})
        P1.Add(New DataObject With {.columnName = "Last Name", .value = "Evans"})
        P1.Add(New DataObject With {.columnName = "DOB", .value = "03/03/1980"})
        Dim P2 As New Person With {.id = "RE" & x}
        P2.Add(New DataObject With {.columnName = "First Name", .value = "Ruth"})
        P2.Add(New DataObject With {.columnName = "Last Name", .value = "Evans"})
        P2.Add(New DataObject With {.columnName = "DOB", .value = "11/02/1979"})
        PersonList.Add(P1)
        PersonList.Add(P2)
    Next
    IC1.DataContext = PersonList
End Sub

那是1,000行,但是因为控件使用虚拟化,所以没有延迟。

编辑

不知道这是否是最好的方法,但是我建议向您的DataObject类添加一个Int width属性,并将第二个ItemsControl中的TextBlocks和TextBoxes的宽度绑定到此。

>

然后使用下面的代码片段计算所需的最大宽度(对WPF equivalent to TextRenderer表示敬意):

public static Size MeasureTextSize(string text, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, double fontSize)
{
    FormattedText ft = new FormattedText(text,
                                         CultureInfo.CurrentCulture,
                                         FlowDirection.LeftToRight,
                                         new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
                                         fontSize,
                                         Brushes.Black);
    return new Size(ft.Width, ft.Height);
}

(我使用了Window的字体详细信息,但是,如果要设置文本框的样式,当然可以指定其他详细信息。)

然后我写了一点以浏览数据并设置宽度(对不起,回到VB):

    If PersonList.Count > 0 Then
        Dim MaxLengths(PersonList(0).Count - 1) As Integer
        For i As Integer = 0 To MaxLengths.Count - 1
            MaxLengths(i) = 70 'Set Minimum width to accomodate Headers
        Next
        For Each P As Person In PersonList
            For i As Integer = 0 To P.Count - 1
                Dim BoxSize As Size = MeasureTextSize(P(i).value, FontFamily, FontStyle, FontWeight, FontStretch, FontSize)
                If BoxSize.Width > MaxLengths(i) Then MaxLengths(i) = BoxSize.Width + 6 'to allow for padding
            Next
        Next
        For Each P As Person In PersonList
            For i As Integer = 0 To P.Count - 1
                P(i).width = MaxLengths(i)
            Next
        Next
    End If