How to populate a datagrid with blank lines

I have a datagrid associated with an observable collection.

I would like to get a similar thing shown in this post with my Datagrid, but there are additional considerations:

  • Datagrid user can be changed by user. Filling the data table with some fixed number of rows does not work for my purposes.
  • Scrolling behavior should work as expected.

Basically, I am trying to create an error list window similar to what is inside Visual Studio.

I would be grateful for any recommendations.

+4
source share
3 answers

It was complicated. My idea would be to create an adorner that will be responsible for drawing the various lines you need. I do not like to create unnecessary string objects.

Here is an initial example (there are still some glitches and it needs to be set up, but I think this is a good start.)

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> 

Control code

 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; } 

This particular piece of code

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

you really don't want to. This is the easiest but ugliest way to get OnRender to be called when necessary. You must force OnRender to reorder the columns and resize the column. Good luck

+11
source

I would create a UserControl with a DockPanel that contains two GridViews, where the first is docked with the "top" and the second (with empty rows) will use the remaining space (if any left depending on the number of rows in the first GridView). ScrollViewer is also needed to implement scrolling across both GridViews.

You can also use a DataGrid, but then you will need to bind the column widths to some common data source, since the DataGrid does not implement INotifyPropertyChanged in the columns (but the GridView does).

A copy of how this will be implemented follows in the code below (NOTE, an additional style will be required to improve GridLines) Check what happens when you add new objects to the ObjectList. Test the userControl using different fixed sizes. The blanks will be β€œmagic” cleared without a scroll bar, but visible when there is enough space left.


 <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; } } 
+1
source

Fill it with empty objects in the collection, and then overwrite them by adding to the grid. Do some loop checking that determines where the last "empty" line is. you can even grab every non-empty object and start sorting on them, and then reinsert them after.

There is no need to use any custom modules, just use some good logic for / foreach loops too.

-2
source

Source: https://habr.com/ru/post/902291/


All Articles