Reputation: 3039
When I scroll vertical scrollbar, DataGrid
automatically expands column width if content in new visible rows is bigger and exceeds previous column width. It's OK.
But if all bigger rows are scrolled over and new visible ones have small content width, DataGrid
does not decrease column width. Is there a way to archieve this?
Attached behaviour implementation will be great.
Code behing:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var persons = new List<Person>();
for (var i = 0; i < 20; i++)
persons.Add(new Person() {Name = "Coooooooooooooool", Surname = "Super"});
for (var i = 0; i < 20; i++)
persons.Add(new Person() {Name = "Cool", Surname = "Suuuuuuuuuuuuuuper"});
for (var i = 0; i < 20; i++)
persons.Add(new Person() {Name = "Coooooooooooooool", Surname = "Super"});
DG.ItemsSource = persons;
}
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
}
XAML:
<Window
x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="400"
Height="200"
mc:Ignorable="d">
<Grid>
<DataGrid
x:Name="DG"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False" />
</Grid>
</Window>
Upvotes: 6
Views: 1546
Reputation: 240
Add the LoadingRow property to you Datagrid:
<DataGrid x:Name="DG"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False" LoadingRow="DG_LoadingRow">
</DataGrid>
And then add this code in the code behind:
private void DG_LoadingRow(object sender, DataGridRowEventArgs e)
{
foreach (DataGridColumn c in DG.Columns)
c.Width = 0;
DG.UpdateLayout();
foreach (DataGridColumn c in DG.Columns)
c.Width = DataGridLength.Auto;
}
It's definitely not the cleanest solution but it will resize the columns that are in view while scrolling.
Hope this helps.
Can you please wrap this into attached behaviour?
1) First option is to use an Attached Property.
public class DataGridHelper : DependencyObject
{
public static readonly DependencyProperty SyncedColumnWidthsProperty =
DependencyProperty.RegisterAttached(
"SyncedColumnWidths",
typeof(Boolean),
typeof(DataGridHelper),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnSyncColumnsChanged)
));
private static void OnSyncColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DataGrid dataGrid)
{
dataGrid.LoadingRow += SyncColumnWidths;
}
}
private static void SyncColumnWidths(object sender, DataGridRowEventArgs e)
{
var dataGrid = (DataGrid)sender;
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = 0;
e.Row.UpdateLayout();
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = DataGridLength.Auto;
}
public static void SetSyncedColumnWidths(UIElement element, Boolean value)
{
element.SetValue(SyncedColumnWidthsProperty, value);
}
}
Usage
<DataGrid
ext:DataGridHelper.SyncedColumnWidths="True"
... />
2) Alternatively, Behaviors provide a more encapsulated way to extend functionality (requires System.Windows.Interactivity).
using System.Windows.Interactivity;
...
public class SyncedColumnWidthsBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
this.AssociatedObject.LoadingRow += this.SyncColumnWidths;
}
protected override void OnDetaching()
{
this.AssociatedObject.LoadingRow -= this.SyncColumnWidths;
}
private void SyncColumnWidths(object sender, DataGridRowEventArgs e)
{
var dataGrid = this.AssociatedObject;
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = 0;
e.Row.UpdateLayout();
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = DataGridLength.Auto;
}
}
Usage
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
<DataGrid
... >
<i:Interaction.Behaviors>
<ext:SyncedColumnWidthsBehavior />
</i:Interaction.Behaviors>
</DataGrid>
Behaviors offer a clean way to release event handlers. Although, in this case, even when we don't unsubscribe with the attached property, we wouldn't create memory leaks (ref Is it bad to not unregister event handlers?).
Upvotes: 5
Reputation: 1973
Apologies for delay to answer your question.
My method here is to capture the visible row in the screen and get the average width and assign to the columns width.
First Subscribed to ScrollViewer's ScrollChanged event.
<DataGrid
ScrollViewer.ScrollChanged="DG_ScrollChanged"
x:Name="DG"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False" />
</Grid>
By using ScrollChanged
and FindVisualChildren
I am able to get the Vertical Offset.
we can get the row index from vertical offset and index of the last row is calculated using (int)scroll.VerticalOffset + (int)scroll.ViewportHeight - 1
ScrollViewer scroll = null;
private void DG_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// get the control once and then use its offset to get the row index
if (scroll == null)
scroll = MethodRepo.FindVisualChildren<ScrollViewer>((DependencyObject)sender).First();
int firstRow = (int)scroll.VerticalOffset;
int lastRow = (int)scroll.VerticalOffset + (int)scroll.ViewportHeight + 1;
List<int> FirstColumnLength = new List<int>();
List<int> SecondColumnLength = new List<int>();
for (int i = firstRow; i < lastRow-1; i++)
{
FirstColumnLength.Add(Convert.ToString(persons[i].Name).Length);
SecondColumnLength.Add(Convert.ToString(persons[i].Surname).Length);
}
DG.Columns[0].Width = FirstColumnLength.Max()*10;
DG.Columns[1].Width = SecondColumnLength.Max() * 10;
}
I also created a static class to get the Visual children.
public static class MethodRepo
{
public static IEnumerable<T> FindVisualChildren<T>([NotNull] this DependencyObject parent) where T : DependencyObject
{
if (parent == null)
throw new ArgumentNullException(nameof(parent));
var queue = new Queue<DependencyObject>(new[] { parent });
while (queue.Any())
{
var reference = queue.Dequeue();
var count = VisualTreeHelper.GetChildrenCount(reference);
for (var i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(reference, i);
if (child is T children)
yield return children;
queue.Enqueue(child);
}
}
}
}
Upvotes: 2