623 lines
22 KiB
C#
623 lines
22 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
|
|
namespace DJ.Helper.ListViewLayoutManager
|
|
{
|
|
public class ListViewLayoutManager
|
|
{
|
|
// ##############################################################################################################################
|
|
// Properties
|
|
// ##############################################################################################################################
|
|
|
|
#region Properties
|
|
|
|
// ##########################################################################################
|
|
// Public Properties
|
|
// ##########################################################################################
|
|
|
|
public ListView ListView => _ListView;
|
|
|
|
public ScrollBarVisibility VerticalScrollBarVisibility
|
|
{
|
|
get => _VerticalScrollBarVisibility;
|
|
set => _VerticalScrollBarVisibility = value;
|
|
}
|
|
|
|
// ##########################################################################################
|
|
// Private Properties
|
|
// ##########################################################################################
|
|
|
|
private readonly ListView _ListView;
|
|
private ScrollViewer _ScrollViewer;
|
|
private bool _Loaded;
|
|
private bool _Resizing;
|
|
private Cursor _ResizeCursor;
|
|
private ScrollBarVisibility _VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
|
|
private GridViewColumn _AutoSizedColumn;
|
|
|
|
private const double _ZERO_WIDTH_RANGE = 0.1;
|
|
|
|
#endregion
|
|
|
|
// ##############################################################################################################################
|
|
// AttachedProperties
|
|
// ##############################################################################################################################
|
|
|
|
#region AttachedProperties
|
|
|
|
public static void SetEnabled(DependencyObject dependencyObject, bool enabled)
|
|
{
|
|
dependencyObject.SetValue(EnabledProperty, enabled);
|
|
}
|
|
|
|
public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached("Enabled",
|
|
typeof(bool), typeof(ListViewLayoutManager), new FrameworkPropertyMetadata(_OnLayoutManagerEnabledChanged));
|
|
|
|
#endregion
|
|
|
|
// ##############################################################################################################################
|
|
// Constructor
|
|
// ##############################################################################################################################
|
|
|
|
#region Constructor
|
|
|
|
public ListViewLayoutManager(ListView listView)
|
|
{
|
|
_ListView = listView ?? throw new ArgumentNullException(nameof(listView));
|
|
_ListView.Loaded += ListView_Loaded;
|
|
_ListView.Unloaded += ListView_Unloaded;
|
|
}
|
|
|
|
private void ListView_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
_RegisterEvents(_ListView);
|
|
_InitColumns();
|
|
_DoResizeColumns();
|
|
_Loaded = true;
|
|
}
|
|
|
|
|
|
private void ListView_Unloaded(object sender, RoutedEventArgs e)
|
|
{
|
|
if (!_Loaded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_UnRegisterEvents(_ListView);
|
|
_Loaded = false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
// ##############################################################################################################################
|
|
// public methods
|
|
// ##############################################################################################################################
|
|
|
|
#region public methods
|
|
|
|
public void Refresh()
|
|
{
|
|
_InitColumns();
|
|
_DoResizeColumns();
|
|
}
|
|
|
|
protected virtual void ResizeColumns()
|
|
{
|
|
GridView view = _ListView.View as GridView;
|
|
if (view == null || view.Columns.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// listview width
|
|
double actualWidth = double.PositiveInfinity;
|
|
if (_ScrollViewer != null)
|
|
{
|
|
actualWidth = _ScrollViewer.ViewportWidth;
|
|
}
|
|
|
|
if (double.IsInfinity(actualWidth))
|
|
{
|
|
actualWidth = _ListView.ActualWidth;
|
|
}
|
|
|
|
if (double.IsInfinity(actualWidth) || actualWidth <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
double resizeableRegionCount = 0;
|
|
double otherColumnsWidth = 0;
|
|
// determine column sizes
|
|
foreach (GridViewColumn gridViewColumn in view.Columns)
|
|
{
|
|
if (ProportionalColumn.IsProportionalColumn(gridViewColumn))
|
|
{
|
|
double? proportionalWidth = ProportionalColumn.GetProportionalWidth(gridViewColumn);
|
|
if (proportionalWidth != null)
|
|
{
|
|
resizeableRegionCount += proportionalWidth.Value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
otherColumnsWidth += gridViewColumn.ActualWidth;
|
|
}
|
|
}
|
|
|
|
if (resizeableRegionCount <= 0)
|
|
{
|
|
// no proportional columns present: commit the regulation to the scroll viewer
|
|
if (_ScrollViewer != null)
|
|
{
|
|
_ScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
|
|
}
|
|
|
|
// search the first fill column
|
|
GridViewColumn fillColumn = null;
|
|
for (int i = 0; i < view.Columns.Count; i++)
|
|
{
|
|
GridViewColumn gridViewColumn = view.Columns[i];
|
|
if (_IsFillColumn(gridViewColumn))
|
|
{
|
|
fillColumn = gridViewColumn;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fillColumn != null)
|
|
{
|
|
double otherColumnsWithoutFillWidth = otherColumnsWidth - fillColumn.ActualWidth;
|
|
double fillWidth = actualWidth - otherColumnsWithoutFillWidth;
|
|
if (fillWidth > 0)
|
|
{
|
|
double? minWidth = RangeColumn.GetRangeMinWidth(fillColumn);
|
|
double? maxWidth = RangeColumn.GetRangeMaxWidth(fillColumn);
|
|
|
|
bool setWidth = !(minWidth.HasValue && fillWidth < minWidth.Value);
|
|
if (maxWidth.HasValue && fillWidth > maxWidth.Value)
|
|
{
|
|
setWidth = false;
|
|
}
|
|
|
|
if (setWidth)
|
|
{
|
|
if (_ScrollViewer != null)
|
|
{
|
|
_ScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
|
|
}
|
|
|
|
fillColumn.Width = fillWidth;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
double resizeableColumnsWidth = actualWidth - otherColumnsWidth;
|
|
if (resizeableColumnsWidth <= 0)
|
|
{
|
|
return; // missing space
|
|
}
|
|
|
|
// resize columns
|
|
double resizeableRegionWidth = resizeableColumnsWidth / resizeableRegionCount;
|
|
foreach (GridViewColumn gridViewColumn in view.Columns)
|
|
{
|
|
if (ProportionalColumn.IsProportionalColumn(gridViewColumn))
|
|
{
|
|
double? proportionalWidth = ProportionalColumn.GetProportionalWidth(gridViewColumn);
|
|
if (proportionalWidth != null)
|
|
{
|
|
gridViewColumn.Width = proportionalWidth.Value * resizeableRegionWidth;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
// ##############################################################################################################################
|
|
// private methods
|
|
// ##############################################################################################################################
|
|
|
|
#region private methods
|
|
|
|
private void _DoResizeColumns()
|
|
{
|
|
if (_Resizing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_Resizing = true;
|
|
try
|
|
{
|
|
ResizeColumns();
|
|
}
|
|
finally
|
|
{
|
|
_Resizing = false;
|
|
}
|
|
}
|
|
|
|
private void _RegisterEvents(DependencyObject start)
|
|
{
|
|
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(start); i++)
|
|
{
|
|
Visual childVisual = VisualTreeHelper.GetChild(start, i) as Visual;
|
|
if (childVisual is Thumb)
|
|
{
|
|
GridViewColumn gridViewColumn = _FindParentColumn(childVisual);
|
|
if (gridViewColumn != null)
|
|
{
|
|
Thumb thumb = childVisual as Thumb;
|
|
if (ProportionalColumn.IsProportionalColumn(gridViewColumn) ||
|
|
FixedColumn.IsFixedColumn(gridViewColumn) || _IsFillColumn(gridViewColumn))
|
|
{
|
|
thumb.IsHitTestVisible = false;
|
|
}
|
|
else
|
|
{
|
|
thumb.PreviewMouseMove += _ThumbPreviewMouseMove;
|
|
thumb.PreviewMouseLeftButtonDown +=
|
|
_ThumbPreviewMouseLeftButtonDown;
|
|
DependencyPropertyDescriptor.FromProperty(
|
|
GridViewColumn.WidthProperty,
|
|
typeof(GridViewColumn)).AddValueChanged(gridViewColumn, _GridColumnWidthChanged);
|
|
}
|
|
}
|
|
}
|
|
else if (childVisual is GridViewColumnHeader)
|
|
{
|
|
GridViewColumnHeader columnHeader = childVisual as GridViewColumnHeader;
|
|
columnHeader.SizeChanged += _GridColumnHeaderSizeChanged;
|
|
}
|
|
else if (_ScrollViewer == null && childVisual is ScrollViewer)
|
|
{
|
|
_ScrollViewer = childVisual as ScrollViewer;
|
|
_ScrollViewer.ScrollChanged += _ScrollViewerScrollChanged;
|
|
// assume we do the regulation of the horizontal scrollbar
|
|
_ScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
|
|
_ScrollViewer.VerticalScrollBarVisibility = _VerticalScrollBarVisibility;
|
|
}
|
|
|
|
_RegisterEvents(childVisual);
|
|
}
|
|
}
|
|
|
|
private void _UnRegisterEvents(DependencyObject start)
|
|
{
|
|
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(start); i++)
|
|
{
|
|
Visual childVisual = VisualTreeHelper.GetChild(start, i) as Visual;
|
|
if (childVisual is Thumb)
|
|
{
|
|
GridViewColumn gridViewColumn = _FindParentColumn(childVisual);
|
|
if (gridViewColumn != null)
|
|
{
|
|
Thumb thumb = childVisual as Thumb;
|
|
if (ProportionalColumn.IsProportionalColumn(gridViewColumn) ||
|
|
FixedColumn.IsFixedColumn(gridViewColumn) || _IsFillColumn(gridViewColumn))
|
|
{
|
|
thumb.IsHitTestVisible = true;
|
|
}
|
|
else
|
|
{
|
|
thumb.PreviewMouseMove -= _ThumbPreviewMouseMove;
|
|
thumb.PreviewMouseLeftButtonDown -=
|
|
_ThumbPreviewMouseLeftButtonDown;
|
|
DependencyPropertyDescriptor.FromProperty(
|
|
GridViewColumn.WidthProperty,
|
|
typeof(GridViewColumn)).RemoveValueChanged(gridViewColumn, _GridColumnWidthChanged);
|
|
}
|
|
}
|
|
}
|
|
else if (childVisual is GridViewColumnHeader)
|
|
{
|
|
GridViewColumnHeader columnHeader = childVisual as GridViewColumnHeader;
|
|
columnHeader.SizeChanged -= _GridColumnHeaderSizeChanged;
|
|
}
|
|
else if (_ScrollViewer == null && childVisual is ScrollViewer)
|
|
{
|
|
_ScrollViewer = childVisual as ScrollViewer;
|
|
_ScrollViewer.ScrollChanged -= _ScrollViewerScrollChanged;
|
|
}
|
|
|
|
_UnRegisterEvents(childVisual);
|
|
}
|
|
}
|
|
|
|
private GridViewColumn _FindParentColumn(DependencyObject element)
|
|
{
|
|
if (element == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
while (element != null)
|
|
{
|
|
GridViewColumnHeader gridViewColumnHeader = element as GridViewColumnHeader;
|
|
if (gridViewColumnHeader != null)
|
|
{
|
|
return (gridViewColumnHeader).Column;
|
|
}
|
|
|
|
element = VisualTreeHelper.GetParent(element);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private GridViewColumnHeader _FindColumnHeader(DependencyObject start, GridViewColumn gridViewColumn)
|
|
{
|
|
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(start); i++)
|
|
{
|
|
Visual childVisual = VisualTreeHelper.GetChild(start, i) as Visual;
|
|
if (childVisual is GridViewColumnHeader)
|
|
{
|
|
GridViewColumnHeader gridViewHeader = childVisual as GridViewColumnHeader;
|
|
if (gridViewHeader.Column == gridViewColumn)
|
|
{
|
|
return gridViewHeader;
|
|
}
|
|
}
|
|
|
|
GridViewColumnHeader childGridViewHeader = _FindColumnHeader(childVisual, gridViewColumn);
|
|
if (childGridViewHeader != null)
|
|
{
|
|
return childGridViewHeader;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void _InitColumns()
|
|
{
|
|
GridView view = _ListView.View as GridView;
|
|
if (view == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (GridViewColumn gridViewColumn in view.Columns)
|
|
{
|
|
if (!RangeColumn.IsRangeColumn(gridViewColumn))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
double? minWidth = RangeColumn.GetRangeMinWidth(gridViewColumn);
|
|
double? maxWidth = RangeColumn.GetRangeMaxWidth(gridViewColumn);
|
|
if (!minWidth.HasValue && !maxWidth.HasValue)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
GridViewColumnHeader columnHeader = _FindColumnHeader(_ListView, gridViewColumn);
|
|
if (columnHeader == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
double actualWidth = columnHeader.ActualWidth;
|
|
if (minWidth.HasValue)
|
|
{
|
|
columnHeader.MinWidth = minWidth.Value;
|
|
if (!double.IsInfinity(actualWidth) && actualWidth < columnHeader.MinWidth)
|
|
{
|
|
gridViewColumn.Width = columnHeader.MinWidth;
|
|
}
|
|
}
|
|
|
|
if (maxWidth.HasValue)
|
|
{
|
|
columnHeader.MaxWidth = maxWidth.Value;
|
|
if (!double.IsInfinity(actualWidth) && actualWidth > columnHeader.MaxWidth)
|
|
{
|
|
gridViewColumn.Width = columnHeader.MaxWidth;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns the delta
|
|
private double _SetRangeColumnToBounds(GridViewColumn gridViewColumn)
|
|
{
|
|
double startWidth = gridViewColumn.Width;
|
|
|
|
double? minWidth = RangeColumn.GetRangeMinWidth(gridViewColumn);
|
|
double? maxWidth = RangeColumn.GetRangeMaxWidth(gridViewColumn);
|
|
|
|
if ((minWidth.HasValue && maxWidth.HasValue) && (minWidth > maxWidth))
|
|
{
|
|
return 0; // invalid case
|
|
}
|
|
|
|
if (minWidth.HasValue && gridViewColumn.Width < minWidth.Value)
|
|
{
|
|
gridViewColumn.Width = minWidth.Value;
|
|
}
|
|
else if (maxWidth.HasValue && gridViewColumn.Width > maxWidth.Value)
|
|
{
|
|
gridViewColumn.Width = maxWidth.Value;
|
|
}
|
|
|
|
return gridViewColumn.Width - startWidth;
|
|
}
|
|
|
|
private bool _IsFillColumn(GridViewColumn gridViewColumn)
|
|
{
|
|
if (gridViewColumn == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
GridView view = _ListView.View as GridView;
|
|
if (view == null || view.Columns.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool? isFillColumn = RangeColumn.GetRangeIsFillColumn(gridViewColumn);
|
|
return isFillColumn.HasValue && isFillColumn.Value;
|
|
}
|
|
|
|
private void _ThumbPreviewMouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
Thumb thumb = sender as Thumb;
|
|
if (thumb == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GridViewColumn gridViewColumn = _FindParentColumn(thumb);
|
|
if (gridViewColumn == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// suppress column resizing for proportional, fixed and range fill columns
|
|
if (ProportionalColumn.IsProportionalColumn(gridViewColumn) ||
|
|
FixedColumn.IsFixedColumn(gridViewColumn) ||
|
|
_IsFillColumn(gridViewColumn))
|
|
{
|
|
thumb.Cursor = null;
|
|
return;
|
|
}
|
|
|
|
// check range column bounds
|
|
if (thumb.IsMouseCaptured && RangeColumn.IsRangeColumn(gridViewColumn))
|
|
{
|
|
double? minWidth = RangeColumn.GetRangeMinWidth(gridViewColumn);
|
|
double? maxWidth = RangeColumn.GetRangeMaxWidth(gridViewColumn);
|
|
|
|
if ((minWidth.HasValue && maxWidth.HasValue) && (minWidth > maxWidth))
|
|
{
|
|
return; // invalid case
|
|
}
|
|
|
|
if (_ResizeCursor == null)
|
|
{
|
|
_ResizeCursor = thumb.Cursor; // save the resize cursor
|
|
}
|
|
|
|
if (minWidth.HasValue && gridViewColumn.Width <= minWidth.Value)
|
|
{
|
|
thumb.Cursor = Cursors.No;
|
|
}
|
|
else if (maxWidth.HasValue && gridViewColumn.Width >= maxWidth.Value)
|
|
{
|
|
thumb.Cursor = Cursors.No;
|
|
}
|
|
else
|
|
{
|
|
thumb.Cursor = _ResizeCursor; // between valid min/max
|
|
}
|
|
}
|
|
}
|
|
|
|
private void _ThumbPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
Thumb thumb = sender as Thumb;
|
|
GridViewColumn gridViewColumn = _FindParentColumn(thumb);
|
|
|
|
// suppress column resizing for proportional, fixed and range fill columns
|
|
if (ProportionalColumn.IsProportionalColumn(gridViewColumn) ||
|
|
FixedColumn.IsFixedColumn(gridViewColumn) ||
|
|
_IsFillColumn(gridViewColumn))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
private void _GridColumnWidthChanged(object sender, EventArgs e)
|
|
{
|
|
if (!_Loaded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GridViewColumn gridViewColumn = sender as GridViewColumn;
|
|
|
|
// suppress column resizing for proportional and fixed columns
|
|
if (ProportionalColumn.IsProportionalColumn(gridViewColumn) || FixedColumn.IsFixedColumn(gridViewColumn))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// ensure range column within the bounds
|
|
if (RangeColumn.IsRangeColumn(gridViewColumn))
|
|
{
|
|
// special case: auto column width - maybe conflicts with min/max range
|
|
if (gridViewColumn != null && gridViewColumn.Width.Equals(double.NaN))
|
|
{
|
|
_AutoSizedColumn = gridViewColumn;
|
|
return; // handled by the change header size event
|
|
}
|
|
|
|
// ensure column bounds
|
|
if (Math.Abs(_SetRangeColumnToBounds(gridViewColumn) - 0) > _ZERO_WIDTH_RANGE)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
_DoResizeColumns();
|
|
}
|
|
|
|
// handle autosized column
|
|
private void _GridColumnHeaderSizeChanged(object sender, SizeChangedEventArgs e)
|
|
{
|
|
if (_AutoSizedColumn == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GridViewColumnHeader gridViewColumnHeader = sender as GridViewColumnHeader;
|
|
if (gridViewColumnHeader != null && gridViewColumnHeader.Column == _AutoSizedColumn)
|
|
{
|
|
if (gridViewColumnHeader.Width.Equals(double.NaN))
|
|
{
|
|
// sync column with
|
|
gridViewColumnHeader.Column.Width = gridViewColumnHeader.ActualWidth;
|
|
_DoResizeColumns();
|
|
}
|
|
|
|
_AutoSizedColumn = null;
|
|
}
|
|
}
|
|
|
|
private void _ScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
|
|
{
|
|
if (_Loaded && Math.Abs(e.ViewportWidthChange - 0) > _ZERO_WIDTH_RANGE)
|
|
{
|
|
_DoResizeColumns();
|
|
}
|
|
}
|
|
|
|
private static void _OnLayoutManagerEnabledChanged(DependencyObject dependencyObject,
|
|
DependencyPropertyChangedEventArgs e)
|
|
{
|
|
ListView listView = dependencyObject as ListView;
|
|
if (listView != null)
|
|
{
|
|
bool enabled = (bool) e.NewValue;
|
|
if (enabled)
|
|
{
|
|
new ListViewLayoutManager(listView);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |