Reputation: 3039
Here is the solution to automatically reduce DataGrid columns width during scrolling. I need a slightly modified version where last column fills all row width left after other columns.
The old solution:
private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
if (sender is not DataGrid dg) return;
foreach (var c in dg.Columns) c.Width = 0;
e.Row.UpdateLayout();
foreach (var c in dg.Columns) c.Width = DataGridLength.Auto;
}
If dg.Columns.Last().Width = new DataGridLength(1, DataGridLengthUnitType.Star);
added at the end of the method last column not respects other columns Auto
size and forces its size to 20px (see the pic).
UPDATE:
The reason I've required described behaviour is for selecting row when mouse clicked on any place of the row. The row did not select when I clicked right side of the row - the was no cell. I've achieved this by adding MouseLeftButtonDown
event handler described here.
Upvotes: 3
Views: 772
Reputation: 546
It seems the DataGrid
can't handle all the changes in one go, but you can "stagger" the update and let an "intermediate" layout be performed by putting the last part on the event loop, e.g., like this:
private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
if (sender is not DataGrid dg) return;
foreach (var c in dg.Columns) c.Width = 0;
foreach (var c in dg.Columns.SkipLast(1)) c.Width = DataGridLength.Auto;
Dispatcher.BeginInvoke((DataGridColumn lc) => lc.Width = new DataGridLength(1, DataGridLengthUnitType.Star), dg.Columns.Last());
}
This is very much not a pretty solution, and the grid flickers a bit due to the visible changes in column width. Maybe it can be improved, e.g., by detecting when it is actually necessary to "reset" the widths. You decide if it is good enough.
EDIT - Here is a slightly less flickering version by setting the last column to the same width during the intermediate layout:
private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
if (sender is not DataGrid dg) return;
foreach (var c in dg.Columns.SkipLast(1)) c.Width = 0;
var lastColumn = dg.Columns.Last();
lastColumn.Width = lastColumn.ActualWidth;
foreach (var c in dg.Columns.SkipLast(1)) c.Width = DataGridLength.Auto;
Dispatcher.BeginInvoke((DataGridColumn lc) =>
lc.Width = new DataGridLength(1, DataGridLengthUnitType.Star), lastColumn);
}
EDIT 2 - A refactor to make it a bit clearer:
private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
if (sender is not DataGrid dg) return;
foreach (var c in dg.Columns.SkipLast(1))
{
c.Width = 0;
c.Width = DataGridLength.Auto;
}
var lastColumn = dg.Columns.Last();
lastColumn.Width = lastColumn.ActualWidth;
Dispatcher.BeginInvoke(static (DataGridColumn lc) =>
lc.Width = new DataGridLength(1, DataGridLengthUnitType.Star), lastColumn);
}
Upvotes: 1
Reputation: 28968
The UpdateLayout
call is totally redundant. Modifying the Width
will automatically trigger a layout pass. Additionally, both iterations should be merged into a single iteration in order to improve the performance.
The complete implementation is very questionable in terms of user experience: in case the user adjusts the column's width, you would override and reset his customizations, which provides a poor experience. Also the grid would potentially resize itself constantly, which looks and feels odd too. You should at least set DataGrid.CanUserResizeColumns
to false
.
I believe that adding a column filter, that allows the user to hide particular columns in order to compact the view, has a higher value.
From the code you have posted, it's not possible to tell exactly what is going on or what could cause the issue. The actual layout configuration of your DataGrid
control and its context is apparently undisclosed.
However, the issue could be related to how the DataGrid
is calculating the width for each column, in case you give the DataGrid
a fixed Width
and this value would cause the content to exceed the available viewport width (the ScrollViewer
would appear in this case):
a) In case of each column width is set to Auto
, the DataGrid
would simply give each column the required minimum width.
b) In case a column's width is set to star *
size, the assigned width is calculated relative to the available remaining space.
In order to calculate a reasonable value, the algorithm would use the effective size of the DataGrid
.
The point is, that when using a ScrollViewer
, the virtual width would be infinite. Now, if the algorithm would set the last column's width to infinite, you would get a very tiny scroll bar. Therefore, infinite is not a reasonable value. In effect, the final width can not be calculated. That's why the algorithm falls back to use the effective width of the DataGrid
, which is the ScrollViewer.ViewportWidth
. The result is that all columns must be sqeezed into the available viewport space, causing columns to clip their headers and cell content.
I can provide two solutions.
The first solution is to use a fixed Width
for the last column like 1000
DIPs.
This solution does scale very bad. The last column would have either a too small width or a too extreme width that does not make much sense (that's why a reasonable width for the last column can't be calculated - what is reasonable in this context?).
The second and better solution would try to detect if the ScrollViewer
can scroll: in this case the last column would be at least partially out of view. This results in a reasonable Width
for the last column of Auto
: the minimal required space.
The other case, when the ScrollViewer
can't scroll, is when all columns are visible in the viewport. This results in a reasonable Width
of *
in order to allow the last column to fill the remaining space:
private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
var dataGrid = sender as DataGrid;
foreach (var dataGridColumn in dataGrid.Columns)
{
dataGridColumn.Width = 0;
dataGridColumn.Width = DataGridLength.Auto;
}
if (TryFindVisualChildElementByName(dataGrid, string.Empty, out ScrollViewer scrollViewer)
&& scrollViewer.ScrollableWidth == 0)
{
dataGrid.Columns.Last().Width = new DataGridLength(1, DataGridLengthUnitType.Star);
}
}
public static bool TryFindVisualChildElementByName<TChild>(
DependencyObject parent,
string childElementName,
out TChild resultElement) where TChild : FrameworkElement
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild frameworkElement)
{
if (string.IsNullOrWhiteSpace(childElementName)
|| frameworkElement.Name.Equals(childElementName, StringComparison.OrdinalIgnoreCase))
{
resultElement = frameworkElement;
return true;
}
}
if (TryFindVisualChildElementByName(childElement, childElementName, out resultElement))
{
return true;
}
}
return false;
}
Upvotes: 1