我有一个绑定到可观察集合的数据网格。
我想用我的 Datagrid 实现这篇文章中显示的类似事情,但还有其他注意事项:
- 用户可以调整 Datagrid 的大小。用一些固定数量的行填充数据表不适用于我的目的。
- 滚动行为应该可以正常工作。
基本上,我正在尝试制作一个类似于 Visual Studio 中的错误列表窗口。
我会很感激任何指导方针。
这是一个棘手的问题。我的想法是创建一个装饰器,负责绘制您需要的不同线条。我不喜欢创建不必要的行对象。
这是一个开始的例子(仍然有一些小故障,需要调整,但我认为这是一个好的开始。)
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。祝你好运 !
我将创建一个带有 DockPanel 的 UserControl,其中包含两个 GridView,其中第一个停靠在“顶部”,第二个(带有空白行)将使用剩余空间(如果有剩余空间,取决于第一个 GridView 中的行数)。还需要一个 ScrollViewer 来实现两个 GridView 之间的滚动。
您也可以使用 DataGrid,但是您需要将列宽绑定到一些公共数据源,因为 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; }
}
用集合中的空对象填充它,然后在添加到网格时覆盖它们。进行某种循环检查以确定最新的“空”行在哪里。您甚至可以抓取每个非空对象,并对它们进行排序,然后重新插入它们。
不需要使用任何自定义模块,只需使用一些好的老式逻辑和 for/foreach 循环。