From 2f3ed272a2ccbfb6227c2dd2288091c3a97d7f6c Mon Sep 17 00:00:00 2001 From: Dominic Jonas Date: Tue, 4 Feb 2020 13:29:25 +0100 Subject: [PATCH 1/8] implemented ListViewLayoutManager --- src/NLogViewer.TestApp/MainWindow.xaml.cs | 4 +- .../ConverterGridViewColumn.cs | 59 ++ .../ListViewLayoutManager/FixedColumn.cs | 61 ++ .../ImageGridViewColumn.cs | 58 ++ .../ListViewLayoutManager/LayoutColumn.cs | 41 ++ .../ListViewLayoutManager.cs | 623 ++++++++++++++++++ .../ProportionalColumn.cs | 61 ++ .../ListViewLayoutManager/RangeColumn.cs | 136 ++++ src/NLogViewer/NLogViewer.xaml | 7 +- 9 files changed, 1047 insertions(+), 3 deletions(-) create mode 100644 src/NLogViewer/Helper/ListViewLayoutManager/ConverterGridViewColumn.cs create mode 100644 src/NLogViewer/Helper/ListViewLayoutManager/FixedColumn.cs create mode 100644 src/NLogViewer/Helper/ListViewLayoutManager/ImageGridViewColumn.cs create mode 100644 src/NLogViewer/Helper/ListViewLayoutManager/LayoutColumn.cs create mode 100644 src/NLogViewer/Helper/ListViewLayoutManager/ListViewLayoutManager.cs create mode 100644 src/NLogViewer/Helper/ListViewLayoutManager/ProportionalColumn.cs create mode 100644 src/NLogViewer/Helper/ListViewLayoutManager/RangeColumn.cs diff --git a/src/NLogViewer.TestApp/MainWindow.xaml.cs b/src/NLogViewer.TestApp/MainWindow.xaml.cs index a3eb4e0..eaf0e5f 100644 --- a/src/NLogViewer.TestApp/MainWindow.xaml.cs +++ b/src/NLogViewer.TestApp/MainWindow.xaml.cs @@ -10,6 +10,8 @@ namespace TestApplication /// public partial class MainWindow : Window { + public const string LOREM_IPSUM = @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; + private readonly Logger _Logger = LogManager.GetCurrentClassLogger(); public MainWindow() { @@ -33,7 +35,7 @@ public MainWindow() _Logger.Warn("Hello everyone"); break; case 5: - _Logger.Error("Hello everyone"); + _Logger.Error(LOREM_IPSUM); break; case 6: _Logger.Fatal("Hello everyone"); diff --git a/src/NLogViewer/Helper/ListViewLayoutManager/ConverterGridViewColumn.cs b/src/NLogViewer/Helper/ListViewLayoutManager/ConverterGridViewColumn.cs new file mode 100644 index 0000000..54febf0 --- /dev/null +++ b/src/NLogViewer/Helper/ListViewLayoutManager/ConverterGridViewColumn.cs @@ -0,0 +1,59 @@ +using System; +using System.Globalization; +using System.Windows.Controls; +using System.Windows.Data; + +namespace DJ.Helper.ListViewLayoutManager +{ + public abstract class ConverterGridViewColumn : GridViewColumn, IValueConverter + { + public Type BindingType => _BindingType; + private readonly Type _BindingType; + + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + protected ConverterGridViewColumn(Type bindingType) + { + if (bindingType == null) + { + throw new ArgumentNullException(nameof(bindingType)); + } + + this._BindingType = bindingType; + + Binding binding = new Binding {Mode = BindingMode.OneWay, Converter = this}; + DisplayMemberBinding = binding; + } + + #endregion + + // ############################################################################################################################## + // IValueConverter + // ############################################################################################################################## + + #region IValueConverter + + object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (!_BindingType.IsInstanceOfType(value)) + { + throw new InvalidOperationException(); + } + + return ConvertValue(value); + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + protected abstract object ConvertValue(object value); + + #endregion + } +} \ No newline at end of file diff --git a/src/NLogViewer/Helper/ListViewLayoutManager/FixedColumn.cs b/src/NLogViewer/Helper/ListViewLayoutManager/FixedColumn.cs new file mode 100644 index 0000000..5a64ab5 --- /dev/null +++ b/src/NLogViewer/Helper/ListViewLayoutManager/FixedColumn.cs @@ -0,0 +1,61 @@ +using System.Windows; +using System.Windows.Controls; + +namespace DJ.Helper.ListViewLayoutManager +{ + public sealed class FixedColumn : LayoutColumn + { + public static bool IsFixedColumn(GridViewColumn column) + { + if (column == null) + { + return false; + } + + return HasPropertyValue(column, WidthProperty); + } + + public static double? GetFixedWidth(GridViewColumn column) + { + return GetColumnWidth(column, WidthProperty); + } + + public static GridViewColumn ApplyWidth(GridViewColumn gridViewColumn, double width) + { + SetWidth(gridViewColumn, width); + return gridViewColumn; + } + + // ############################################################################################################################## + // AttachedProperties + // ############################################################################################################################## + + #region AttachedProperties + + public static double GetWidth(DependencyObject obj) + { + return (double) obj.GetValue(WidthProperty); + } + + public static void SetWidth(DependencyObject obj, double width) + { + obj.SetValue(WidthProperty, width); + } + + public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached("Width", typeof(double), typeof(FixedColumn)); + + #endregion + + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + private FixedColumn() + { + } + + #endregion + } +} \ No newline at end of file diff --git a/src/NLogViewer/Helper/ListViewLayoutManager/ImageGridViewColumn.cs b/src/NLogViewer/Helper/ListViewLayoutManager/ImageGridViewColumn.cs new file mode 100644 index 0000000..cf81e4a --- /dev/null +++ b/src/NLogViewer/Helper/ListViewLayoutManager/ImageGridViewColumn.cs @@ -0,0 +1,58 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; + +namespace DJ.Helper.ListViewLayoutManager +{ + public abstract class ImageGridViewColumn : GridViewColumn, IValueConverter + { + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + protected ImageGridViewColumn(Stretch imageStretch) + { + FrameworkElementFactory imageElement = new FrameworkElementFactory(typeof(Image)); + + Binding imageSourceBinding = new Binding {Converter = this, Mode = BindingMode.OneWay}; + imageElement.SetBinding(Image.SourceProperty, imageSourceBinding); + + Binding imageStretchBinding = new Binding {Source = imageStretch}; + imageElement.SetBinding(Image.StretchProperty, imageStretchBinding); + + DataTemplate template = new DataTemplate {VisualTree = imageElement}; + CellTemplate = template; + } + + protected ImageGridViewColumn() : this(Stretch.None) + { + } + + #endregion + + // ############################################################################################################################## + // IValueConverter + // ############################################################################################################################## + + #region IValueConverter + + object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return GetImageSource(value); + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + protected abstract ImageSource GetImageSource(object value); + + #endregion + } +} \ No newline at end of file diff --git a/src/NLogViewer/Helper/ListViewLayoutManager/LayoutColumn.cs b/src/NLogViewer/Helper/ListViewLayoutManager/LayoutColumn.cs new file mode 100644 index 0000000..d94d28d --- /dev/null +++ b/src/NLogViewer/Helper/ListViewLayoutManager/LayoutColumn.cs @@ -0,0 +1,41 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace DJ.Helper.ListViewLayoutManager +{ + public abstract class LayoutColumn + { + protected static bool HasPropertyValue(GridViewColumn column, DependencyProperty dp) + { + if (column == null) + { + throw new ArgumentNullException(nameof(column)); + } + + object value = column.ReadLocalValue(dp); + if (value?.GetType() == dp.PropertyType) + { + return true; + } + + return false; + } + + protected static double? GetColumnWidth(GridViewColumn column, DependencyProperty dp) + { + if (column == null) + { + throw new ArgumentNullException(nameof(column)); + } + + object value = column.ReadLocalValue(dp); + if (value?.GetType() == dp.PropertyType) + { + return (double) value; + } + + return null; + } + } +} diff --git a/src/NLogViewer/Helper/ListViewLayoutManager/ListViewLayoutManager.cs b/src/NLogViewer/Helper/ListViewLayoutManager/ListViewLayoutManager.cs new file mode 100644 index 0000000..a850ed9 --- /dev/null +++ b/src/NLogViewer/Helper/ListViewLayoutManager/ListViewLayoutManager.cs @@ -0,0 +1,623 @@ +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 + } +} \ No newline at end of file diff --git a/src/NLogViewer/Helper/ListViewLayoutManager/ProportionalColumn.cs b/src/NLogViewer/Helper/ListViewLayoutManager/ProportionalColumn.cs new file mode 100644 index 0000000..f43fea7 --- /dev/null +++ b/src/NLogViewer/Helper/ListViewLayoutManager/ProportionalColumn.cs @@ -0,0 +1,61 @@ +using System.Windows; +using System.Windows.Controls; + +namespace DJ.Helper.ListViewLayoutManager +{ + public sealed class ProportionalColumn : LayoutColumn + { + public static bool IsProportionalColumn(GridViewColumn column) + { + if (column == null) + { + return false; + } + + return HasPropertyValue(column, WidthProperty); + } + + public static double? GetProportionalWidth(GridViewColumn column) + { + return GetColumnWidth(column, WidthProperty); + } + + public static GridViewColumn ApplyWidth(GridViewColumn gridViewColumn, double width) + { + SetWidth(gridViewColumn, width); + return gridViewColumn; + } + + // ############################################################################################################################## + // AttachedProperties + // ############################################################################################################################## + + #region AttachedProperties + + public static double GetWidth(DependencyObject obj) + { + return (double) obj.GetValue(WidthProperty); + } + + public static void SetWidth(DependencyObject obj, double width) + { + obj.SetValue(WidthProperty, width); + } + + public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached("Width", typeof(double), typeof(ProportionalColumn)); + + #endregion + + // ############################################################################################################################## + // Constructor + // ############################################################################################################################## + + #region Constructor + + private ProportionalColumn() + { + } + + #endregion + } +} \ No newline at end of file diff --git a/src/NLogViewer/Helper/ListViewLayoutManager/RangeColumn.cs b/src/NLogViewer/Helper/ListViewLayoutManager/RangeColumn.cs new file mode 100644 index 0000000..66df102 --- /dev/null +++ b/src/NLogViewer/Helper/ListViewLayoutManager/RangeColumn.cs @@ -0,0 +1,136 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace DJ.Helper.ListViewLayoutManager +{ + + + public sealed class RangeColumn : LayoutColumn + { + + + public static readonly DependencyProperty MinWidthProperty = + DependencyProperty.RegisterAttached( + "MinWidth", + typeof( double ), + typeof( RangeColumn ) ); + + + public static readonly DependencyProperty MaxWidthProperty = + DependencyProperty.RegisterAttached( + "MaxWidth", + typeof( double ), + typeof( RangeColumn ) ); + + + public static readonly DependencyProperty IsFillColumnProperty = + DependencyProperty.RegisterAttached( + "IsFillColumn", + typeof( bool ), + typeof( RangeColumn ) ); + + + private RangeColumn() + { + } // RangeColumn + + + public static double GetMinWidth( DependencyObject obj ) + { + return (double)obj.GetValue( MinWidthProperty ); + } // GetMinWidth + + + public static void SetMinWidth( DependencyObject obj, double minWidth ) + { + obj.SetValue( MinWidthProperty, minWidth ); + } // SetMinWidth + + + public static double GetMaxWidth( DependencyObject obj ) + { + return (double)obj.GetValue( MaxWidthProperty ); + } // GetMaxWidth + + + public static void SetMaxWidth( DependencyObject obj, double maxWidth ) + { + obj.SetValue( MaxWidthProperty, maxWidth ); + } // SetMaxWidth + + + public static bool GetIsFillColumn( DependencyObject obj ) + { + return (bool)obj.GetValue( IsFillColumnProperty ); + } // GetIsFillColumn + + + public static void SetIsFillColumn( DependencyObject obj, bool isFillColumn ) + { + obj.SetValue( IsFillColumnProperty, isFillColumn ); + } // SetIsFillColumn + + + public static bool IsRangeColumn( GridViewColumn column ) + { + if ( column == null ) + { + return false; + } + return + HasPropertyValue( column, MinWidthProperty ) || + HasPropertyValue( column, MaxWidthProperty ) || + HasPropertyValue( column, IsFillColumnProperty ); + } // IsRangeColumn + + + public static double? GetRangeMinWidth( GridViewColumn column ) + { + return GetColumnWidth( column, MinWidthProperty ); + } // GetRangeMinWidth + + + public static double? GetRangeMaxWidth( GridViewColumn column ) + { + return GetColumnWidth( column, MaxWidthProperty ); + } // GetRangeMaxWidth + + + public static bool? GetRangeIsFillColumn( GridViewColumn column ) + { + if ( column == null ) + { + throw new ArgumentNullException( nameof(column) ); + } + object value = column.ReadLocalValue( IsFillColumnProperty ); + if ( value != null && value.GetType() == IsFillColumnProperty.PropertyType ) + { + return (bool)value; + } + + return null; + } // GetRangeIsFillColumn + + + public static GridViewColumn ApplyWidth( GridViewColumn gridViewColumn, double minWidth, + double width, double maxWidth ) + { + return ApplyWidth( gridViewColumn, minWidth, width, maxWidth, false ); + } // ApplyWidth + + + public static GridViewColumn ApplyWidth( GridViewColumn gridViewColumn, double minWidth, + double width, double maxWidth, bool isFillColumn ) + { + SetMinWidth( gridViewColumn, minWidth ); + gridViewColumn.Width = width; + SetMaxWidth( gridViewColumn, maxWidth ); + SetIsFillColumn( gridViewColumn, isFillColumn ); + return gridViewColumn; + } // ApplyWidth + + } // class RangeColumn + +} // namespace Itenso.Windows.Controls.ListViewLayout +// -- EOF ------------------------------------------------------------------- diff --git a/src/NLogViewer/NLogViewer.xaml b/src/NLogViewer/NLogViewer.xaml index 29de806..031e470 100644 --- a/src/NLogViewer/NLogViewer.xaml +++ b/src/NLogViewer/NLogViewer.xaml @@ -5,6 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:dj="clr-namespace:DJ" xmlns:nLog="clr-namespace:NLog;assembly=NLog" + xmlns:listViewLayoutManager="clr-namespace:DJ.Helper.ListViewLayoutManager" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -63,7 +64,8 @@ Grid.Row="2" ItemsSource="{Binding Path=LogEvents.View, IsAsync=True}" BorderThickness="0" - ScrollViewer.CanContentScroll="True"> + ScrollViewer.CanContentScroll="True" + listViewLayoutManager:ListViewLayoutManager.Enabled="true"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/NLogViewer/NLogViewer.xaml.cs b/src/NLogViewer/NLogViewer.xaml.cs index 81bcca1..7c66f4f 100644 --- a/src/NLogViewer/NLogViewer.xaml.cs +++ b/src/NLogViewer/NLogViewer.xaml.cs @@ -12,6 +12,7 @@ using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; +using DJ.Resolver; using DJ.Targets; using NLog; @@ -28,36 +29,11 @@ public partial class NLogViewer : UserControl #region Dependency Properties - /// - /// Is looking if any target with this name is configured and tries to link it - /// - [Category("NLogViewer")] - public string TargetName - { - get => (string)GetValue(TargetNameProperty); - set => SetValue(TargetNameProperty, value); - } + // ########################################################################################## + // Colors + // ########################################################################################## - /// - /// The DependencyProperty. - /// - public static readonly DependencyProperty TargetNameProperty = DependencyProperty.Register("TargetName", typeof(string), typeof(NLogViewer), new PropertyMetadata(null)); - - /// - /// Private DP to bind to the gui - /// - [Category("NLogViewer")] - public CollectionViewSource LogEvents - { - get => (CollectionViewSource) GetValue(LogEventsProperty); - private set => SetValue(LogEventsProperty, value); - } - - /// - /// The DependencyProperty. - /// - public static readonly DependencyProperty LogEventsProperty = DependencyProperty.Register("LogEvents", - typeof(CollectionViewSource), typeof(NLogViewer), new PropertyMetadata(null)); + #region Colors /// /// The background for the trace output @@ -261,6 +237,45 @@ public Brush FatalForeground public static readonly DependencyProperty FatalForegroundProperty = DependencyProperty.Register("FatalForeground", typeof(Brush), typeof(NLogViewer), new PropertyMetadata(Brushes.Yellow)); + + #endregion + + // ########################################################################################## + // NLogViewer + // ########################################################################################## + + #region NLogViewer + + /// + /// Is looking if any target with this name is configured and tries to link it + /// + [Category("NLogViewer")] + public string TargetName + { + get => (string)GetValue(TargetNameProperty); + set => SetValue(TargetNameProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty TargetNameProperty = DependencyProperty.Register("TargetName", typeof(string), typeof(NLogViewer), new PropertyMetadata(null)); + + /// + /// Private DP to bind to the gui + /// + [Category("NLogViewer")] + public CollectionViewSource LogEvents + { + get => (CollectionViewSource) GetValue(LogEventsProperty); + private set => SetValue(LogEventsProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty LogEventsProperty = DependencyProperty.Register("LogEvents", + typeof(CollectionViewSource), typeof(NLogViewer), new PropertyMetadata(null)); /// /// Automatically scroll to the newest entry @@ -338,7 +353,79 @@ public int MaxCount /// The DependencyProperty. /// public static readonly DependencyProperty MaxCountProperty = DependencyProperty.Register("MaxCount", typeof(int), typeof(NLogViewer), new PropertyMetadata(5000)); + + + #endregion + + // ########################################################################################## + // Resolver + // ########################################################################################## + + #region Resolver + + /// + /// The to format the id + /// + [Category("NLogViewerResolver")] + public ILogEventInfoResolver IdResolver + { + get => (ILogEventInfoResolver)GetValue(IdResolverProperty); + set => SetValue(IdResolverProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty IdResolverProperty = DependencyProperty.Register("IdResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new IdResolver())); + /// + /// The to format the timestamp output + /// + [Category("NLogViewerResolver")] + public ILogEventInfoResolver TimeStampResolver + { + get => (ILogEventInfoResolver)GetValue(TimeStampResolverProperty); + set => SetValue(TimeStampResolverProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty TimeStampResolverProperty = DependencyProperty.Register("TimeStampResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new TimeStampResolver())); + + /// + /// The to format the loggername + /// + [Category("NLogViewerResolver")] + public ILogEventInfoResolver LoggerNameResolver + { + get => (ILogEventInfoResolver)GetValue(LoggerNameResolverProperty); + set => SetValue(LoggerNameResolverProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty LoggerNameResolverProperty = DependencyProperty.Register("LoggerNameResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new LoggerNameResolver())); + + /// + /// The to format the message + /// + [Category("NLogViewerResolver")] + public ILogEventInfoResolver MessageResolver + { + get => (ILogEventInfoResolver)GetValue(MessageResolverProperty); + set => SetValue(MessageResolverProperty, value); + } + + /// + /// The DependencyProperty. + /// + public static readonly DependencyProperty MessageResolverProperty = DependencyProperty.Register("MessageResolver", typeof(ILogEventInfoResolver), typeof(NLogViewer), new PropertyMetadata(new MessageResolver())); + + #endregion + + #endregion // ############################################################################################################################## diff --git a/src/NLogViewer/Resolver/ILogEventInfoResolver.cs b/src/NLogViewer/Resolver/ILogEventInfoResolver.cs new file mode 100644 index 0000000..ea0e2f2 --- /dev/null +++ b/src/NLogViewer/Resolver/ILogEventInfoResolver.cs @@ -0,0 +1,12 @@ +using NLog; + +namespace DJ.Resolver +{ + /// + /// Default interface to resolve a for your personal needs + /// + public interface ILogEventInfoResolver + { + string Resolve(LogEventInfo logEventInfo); + } +} diff --git a/src/NLogViewer/Resolver/IdResolver.cs b/src/NLogViewer/Resolver/IdResolver.cs new file mode 100644 index 0000000..e427d29 --- /dev/null +++ b/src/NLogViewer/Resolver/IdResolver.cs @@ -0,0 +1,12 @@ +using NLog; + +namespace DJ.Resolver +{ + public class IdResolver : ILogEventInfoResolver + { + public string Resolve(LogEventInfo logEventInfo) + { + return logEventInfo.SequenceID.ToString(); + } + } +} \ No newline at end of file diff --git a/src/NLogViewer/Resolver/LoggerNameResolver.cs b/src/NLogViewer/Resolver/LoggerNameResolver.cs new file mode 100644 index 0000000..c8773a8 --- /dev/null +++ b/src/NLogViewer/Resolver/LoggerNameResolver.cs @@ -0,0 +1,12 @@ +using NLog; + +namespace DJ.Resolver +{ + public class LoggerNameResolver : ILogEventInfoResolver + { + public string Resolve(LogEventInfo logEventInfo) + { + return logEventInfo.LoggerName; + } + } +} \ No newline at end of file diff --git a/src/NLogViewer/Resolver/MessageResolver.cs b/src/NLogViewer/Resolver/MessageResolver.cs new file mode 100644 index 0000000..e6907e7 --- /dev/null +++ b/src/NLogViewer/Resolver/MessageResolver.cs @@ -0,0 +1,21 @@ +using System.Text; +using NLog; + +namespace DJ.Resolver +{ + public class MessageResolver : ILogEventInfoResolver + { + public string Resolve(LogEventInfo logEventInfo) + { + StringBuilder builder = new StringBuilder(); + builder.Append(logEventInfo.FormattedMessage); + + if (logEventInfo.Exception != null) + { + builder.AppendLine().Append(logEventInfo.Exception); + } + + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/src/NLogViewer/Resolver/TimeStampResolver.cs b/src/NLogViewer/Resolver/TimeStampResolver.cs new file mode 100644 index 0000000..45ac642 --- /dev/null +++ b/src/NLogViewer/Resolver/TimeStampResolver.cs @@ -0,0 +1,12 @@ +using NLog; + +namespace DJ.Resolver +{ + public class TimeStampResolver : ILogEventInfoResolver + { + public string Resolve(LogEventInfo logEventInfo) + { + return logEventInfo.TimeStamp.ToString("dd-MM-yyyy hh:mm:ss.fff"); + } + } +} \ No newline at end of file diff --git a/src/NLogViewer/XamlMultiValueConverter/ILogEventResolverToStringConverter.cs b/src/NLogViewer/XamlMultiValueConverter/ILogEventResolverToStringConverter.cs new file mode 100644 index 0000000..e1bf42f --- /dev/null +++ b/src/NLogViewer/XamlMultiValueConverter/ILogEventResolverToStringConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using DJ.Resolver; +using NLog; + +namespace DJ.XamlMultiValueConverter +{ + public class ILogEventResolverToStringConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values[0] is LogEventInfo logEventInfo && values[1] is ILogEventInfoResolver resolver) + return resolver.Resolve(logEventInfo); + return "####"; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} From f0af91a47f8f0688929e3ff79ce8311379b94780 Mon Sep 17 00:00:00 2001 From: Dominic Jonas Date: Mon, 10 Feb 2020 12:19:41 +0100 Subject: [PATCH 8/8] AutoSizedGridView is only adjusting the column with if the logger name is exceeding the last known length --- src/NLogViewer.TestApp/MainWindow.xaml.cs | 15 ++++++++-- src/NLogViewer/Helper/AutoSizedGridView.cs | 35 +++++++++++++++++----- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/NLogViewer.TestApp/MainWindow.xaml.cs b/src/NLogViewer.TestApp/MainWindow.xaml.cs index 5001177..c9dfed3 100644 --- a/src/NLogViewer.TestApp/MainWindow.xaml.cs +++ b/src/NLogViewer.TestApp/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Reactive.Linq; using System.Windows; using DJ.Resolver; @@ -14,6 +15,7 @@ public partial class MainWindow : Window public const string LOREM_IPSUM = @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; private readonly Logger _Logger = LogManager.GetCurrentClassLogger(); + private readonly Logger _Logger2 = LogManager.GetLogger("Lorem.Ipsum.Foo.Hello.World.Lorem.Ipsum"); public MainWindow() { Title = $"Testing v{AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}"; @@ -21,9 +23,9 @@ public MainWindow() DataContext = this; NLogViewer1.TimeStampResolver = new FooTimeStampResolver(); - + Stopwatch stopwatch = Stopwatch.StartNew(); Random random = new Random(); - Observable.Interval(TimeSpan.FromMilliseconds(250)).ObserveOnDispatcher().Subscribe(l => + Observable.Interval(TimeSpan.FromMilliseconds(200)).ObserveOnDispatcher().Subscribe(l => { switch (random.Next(1,6)) { @@ -31,7 +33,14 @@ public MainWindow() _Logger.Trace("Hello everyone"); break; case 2: - _Logger.Debug("Hello everyone"); + if (stopwatch.Elapsed.Seconds > 5) + { + _Logger2.Debug("Hello everyone"); + } + else + { + _Logger.Debug("Hello everyone"); + } break; case 3: _Logger.Info("Hello everyone"); diff --git a/src/NLogViewer/Helper/AutoSizedGridView.cs b/src/NLogViewer/Helper/AutoSizedGridView.cs index 313829c..1b8158a 100644 --- a/src/NLogViewer/Helper/AutoSizedGridView.cs +++ b/src/NLogViewer/Helper/AutoSizedGridView.cs @@ -1,4 +1,7 @@ -using System.Windows.Controls; +using System; +using System.Reactive.Linq; +using System.Windows.Controls; +using NLog; namespace DJ.Helper { @@ -7,16 +10,32 @@ namespace DJ.Helper /// Used to fix the column width: https://stackoverflow.com/questions/60147905/column-width-adjustment-is-broken-if-using-multibinding-on-displaymemberbinding /// public class AutoSizedGridView : GridView - { + { + private int _MaxLoggerNameLength; + protected override void PrepareItem(ListViewItem item) { - foreach (GridViewColumn column in Columns) + if (item.DataContext is LogEventInfo info) { - //setting NaN for the column width automatically determines the required width enough to hold the content completely. - //if column width was set to NaN already, set it ActualWidth temporarily and set to NaN. This raises the property change event and re computes the width. - if (double.IsNaN(column.Width)) column.Width = column.ActualWidth; - column.Width = double.NaN; - } + if (info.LoggerName.Length > _MaxLoggerNameLength) + { + _MaxLoggerNameLength = info.LoggerName.Length; + Observable.Timer(TimeSpan.FromMilliseconds(1)).ObserveOnDispatcher().Subscribe(l => + { + foreach (GridViewColumn column in Columns) + { + //setting NaN for the column width automatically determines the required width enough to hold the content completely. + //if column width was set to NaN already, set it ActualWidth temporarily and set to NaN. This raises the property change event and re computes the width. + if (double.IsNaN(column.Width)) + { + column.Width = column.ActualWidth; + column.Width = double.NaN; + } + } + }); + } + } + base.PrepareItem(item); } }