【问题标题】:How to fill up datagrid with empty rows如何用空行填充数据网格
【发布时间】:2012-10-05 03:32:27
【问题描述】:

我有一个绑定到可观察集合的数据网格。

我想用我的 Datagrid 实现this post 中所示的类似事情,但还有其他注意事项:

  1. Datagrid 可以由用户调整大小。用一些固定数量的行填充数据表不适用于我的目的。
  2. 滚动行为应该可以正常工作。

基本上我正在尝试制作一个类似于 Visual Studio 中的错误列表窗口。

如果有任何指导方针,我将不胜感激。

【问题讨论】:

    标签: c# .net wpf datagrid


    【解决方案1】:

    这是一个棘手的问题。我的想法是创建一个装饰器,负责绘制您需要的不同线条。我不喜欢创建不必要的行对象。

    这是一个开始的例子(还有一些小故障,需要调整,但我认为这是一个好的开始。)

    XAML

    <Window x:Class="WpfApplication11.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:WpfApplication11"
        Title="MainWindow" Height="350" Width="525">
    
    <local:MyDataGrid HeadersVisibility="Column">
        <local:MyDataGrid.Columns>
            <DataGridTextColumn Header="Column 123" Binding="{Binding}" />
            <DataGridTextColumn Header="Column 2" Binding="{Binding}" />
            <DataGridTextColumn Header="Column 33333333333333333333333" Binding="{Binding}" />
        </local:MyDataGrid.Columns>
        <sys:String>Row</sys:String>
        <sys:String>Row</sys:String>
    </local:MyDataGrid>
    
    </Window>
    

    控制代码

    public static class Visual_ExtensionMethods
    {
        public static T FindDescendant<T>(this Visual @this, Predicate<T> predicate = null) where T : Visual
        {
            return @this.FindDescendant(v => v is T && (predicate == null || predicate((T)v))) as T;
        }
    
        public static Visual FindDescendant(this Visual @this, Predicate<Visual> predicate)
        {
            if (@this == null)
                return null;
    
            var frameworkElement = @this as FrameworkElement;
            if (frameworkElement != null)
            {
                frameworkElement.ApplyTemplate();
            }
    
            Visual child = null;
            for (int i = 0, count = VisualTreeHelper.GetChildrenCount(@this); i < count; i++)
            {
                child = VisualTreeHelper.GetChild(@this, i) as Visual;
                if (predicate(child))
                    return child;
    
                child = child.FindDescendant(predicate);
                if (child != null)
                    return child;
    
            }
            return child;
        }
    }
    
    public class GridAdorner : Adorner
    {
        public GridAdorner(MyDataGrid dataGrid)
            : base(dataGrid)
        {
            dataGrid.LayoutUpdated += new EventHandler(dataGrid_LayoutUpdated);
        }
    
        void dataGrid_LayoutUpdated(object sender, EventArgs e)
        {
            InvalidateVisual();
        }
    
        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
    
            var myDataGrid = AdornedElement as MyDataGrid;
            if (myDataGrid == null)
                throw new InvalidOperationException();
    
            // Draw Horizontal lines
            var lastRowBottomOffset = myDataGrid.LastRowBottomOffset;
            var remainingSpace = myDataGrid.RenderSize.Height - lastRowBottomOffset;
            var placeHolderRowHeight = myDataGrid.PlaceHolderRowHeight;
            var lineNumber = (int)(Math.Floor(remainingSpace / placeHolderRowHeight));
    
            for (int i = 1; i <= lineNumber; i++)
            {
                Rect rectangle = new Rect(new Size(base.RenderSize.Width, 1)) { Y = lastRowBottomOffset + (i * placeHolderRowHeight) };
                drawingContext.DrawRectangle(Brushes.Black, null, rectangle);
            }
    
            // Draw vertical lines
            var reorderedColumns = myDataGrid.Columns.OrderBy(c => c.DisplayIndex);
            double verticalLineOffset = - myDataGrid.ScrollViewer.HorizontalOffset;
            foreach (var column in reorderedColumns)
            {
                verticalLineOffset += column.ActualWidth;
    
                Rect rectangle = new Rect(new Size(1, Math.Max(0, remainingSpace))) { X = verticalLineOffset, Y = lastRowBottomOffset };
                drawingContext.DrawRectangle(Brushes.Black, null, rectangle);
            }
        }
    }
    
    public class MyDataGrid : DataGrid
    {
        public MyDataGrid()
        {
            Background = Brushes.White;
            Loaded += new RoutedEventHandler(MyDataGrid_Loaded);
            PlaceHolderRowHeight = 20.0D; // random value, can be changed
        }
    
        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
        }
    
        private static void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
        {
            var dataGrid = sender as MyDataGrid;
            if (dataGrid == null)
                throw new InvalidOperationException();
    
            // Add the adorner that will be responsible for drawing grid lines
            var adornerLayer = AdornerLayer.GetAdornerLayer(dataGrid);
            if (adornerLayer != null)
            {
                adornerLayer.Add(new GridAdorner(dataGrid));
            }
    
            // Find DataGridRowsPresenter and set alignment to top to easily retrieve last row vertical offset
            dataGrid.DataGridRowsPresenter.VerticalAlignment = System.Windows.VerticalAlignment.Top;
        }
    
        public double PlaceHolderRowHeight
        {
            get;
            set;
        }
    
        public double LastRowBottomOffset
        {
            get
            {
                return DataGridColumnHeadersPresenter.RenderSize.Height + DataGridRowsPresenter.RenderSize.Height;
            }
        }
    
        public DataGridColumnHeadersPresenter DataGridColumnHeadersPresenter
        {
            get
            {
                if (dataGridColumnHeadersPresenter == null)
                {
                    dataGridColumnHeadersPresenter = this.FindDescendant<DataGridColumnHeadersPresenter>();
                    if (dataGridColumnHeadersPresenter == null)
                        throw new InvalidOperationException();
                }
                return dataGridColumnHeadersPresenter;
            }
        }
    
        public DataGridRowsPresenter DataGridRowsPresenter
        {
            get
            {
                if (dataGridRowsPresenter == null)
                {
                    dataGridRowsPresenter = this.FindDescendant<DataGridRowsPresenter>();
                    if (dataGridRowsPresenter == null)
                        throw new InvalidOperationException();
                }
                return dataGridRowsPresenter;
            }
        }
    
        public ScrollViewer ScrollViewer
        {
            get
            {
                if (scrollViewer == null)
                {
                    scrollViewer = this.FindDescendant<ScrollViewer>();
                    if (scrollViewer == null)
                        throw new InvalidOperationException();
                }
                return scrollViewer;
            }
        }
    
        private DataGridRowsPresenter dataGridRowsPresenter;
        private DataGridColumnHeadersPresenter dataGridColumnHeadersPresenter;
        private ScrollViewer scrollViewer;
    }
    

    这段特定的代码

    void dataGrid_LayoutUpdated(object sender, EventArgs e)
    {
        InvalidateVisual();
    }
    

    你真的不想要。这是在必要时调用 OnRender 的最简单但最丑陋的方法。您应该只强制在列重新排序和列大小更改时调用 OnRender。祝你好运!

    【讨论】:

    • @l46kok : 你有机会尝试一下吗?
    • 绝对出色的解决方案。不过有一点问题: AdornerLayer.GetAdornerLayer(dataGrid);每次都返回null。通过将 DataGrid 包装在 AdornerDecorator 中来修复它。
    【解决方案2】:

    用集合中的空对象填充它,然后在添加到网格时覆盖它们。进行某种循环检查以确定最新的“空”行在哪里。你甚至可以抓取每个非空对象,对它们进行排序,然后重新插入它们。

    不需要使用任何自定义模块,只需使用一些好的老式逻辑和 for/foreach 循环。

    【讨论】:

    • 当我看到第一个答案时,我很惊讶没有人给他这个非常基本的建议。最好的解决方案,干净,不涉及编码也不重新定义 DataGrid 的默认行为。
    • 大马士革这不是干净的溶液。可能会出现滚动条,并且行是可选的。
    【解决方案3】:

    我将创建一个带有 DockPanel 的 UserControl,其中包含两个 GridView,其中第一个停靠在“顶部”,第二个(带有空白行)将使用剩余空间(如果有剩余空间,取决于第一个 GridView)。 还需要一个 ScrollViewer 来实现两个 GridView 之间的滚动。

    您也可以使用 DataGrid,但是您需要将 Column Widths 绑定到一些常见的数据源,因为 DataGrid 没有在 Columns 上实现 INotifyPropertyChanged(但 GridView 可以)。

    下面的代码是一个关于如何实现的示例(注意需要额外的样式来获得更好的 GridLines ) 测试将新对象添加到 ObjectList 时会发生什么。 在您的 UserControl 上使用不同的固定大小进行测试。 空白将通过“魔法”在没有滚动条的情况下被清除,但在剩余足够空间时可见。


    <Window.Resources>
    <x:Array x:Key="ObjectList" Type="{x:Type local:MyDataStructure}">
        <local:MyDataStructure Description="John" Value="13" />
        <local:MyDataStructure Description="Tom" Value="12" />
        <local:MyDataStructure Description="John" Value="13" />
        <local:MyDataStructure Description="Tom" Value="12" />
        <local:MyDataStructure Description="John" Value="13" />
        <local:MyDataStructure Description="Tom" Value="12" />
    </x:Array>
    
    <x:Array x:Key="Blanks" Type="{x:Type local:MyDataStructure}">
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
        <local:MyDataStructure Description="" Value="{x:Null}" />
    </x:Array>
    
    <GridViewColumnCollection x:Key="columns">
        <GridViewColumn Header="Description" DisplayMemberBinding="{Binding Description}" Width="100" />
        <GridViewColumn Header="Value" DisplayMemberBinding="{Binding Value}" Width="50" />
    </GridViewColumnCollection>
    
    <DataTemplate x:Key="RowTemplate">
        <Border BorderBrush="Gray" BorderThickness="1">
        <GridViewRowPresenter Content="{Binding}" Columns="{StaticResource columns}" />
        </Border>
    </DataTemplate>
    </Window.Resources>
    
    <DockPanel>
    <ScrollViewer VerticalScrollBarVisibility="Auto" DockPanel.Dock="Top">
        <ItemsControl DockPanel.Dock="Top">
        <GridViewHeaderRowPresenter Columns="{StaticResource columns}" DockPanel.Dock="Top" />
        <ItemsControl ItemsSource="{StaticResource ObjectList}" ItemTemplate="{StaticResource RowTemplate}" DockPanel.Dock="Top"></ItemsControl>
        </ItemsControl>
    </ScrollViewer>
    <ItemsControl ItemsSource="{StaticResource Blanks}" ItemTemplate="{StaticResource RowTemplate}"></ItemsControl>
    </DockPanel>
    

      public class MyDataStructure
      {
        public string Description { get; set; }
    
        public int? Value { get; set; }
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多