diff --git a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml index 97e649dd4..f36260f91 100644 --- a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml +++ b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml @@ -42,6 +42,7 @@ FooterMenuItemsSource="{Binding ViewModel.FooterMenuItems, Mode=OneWay}" FrameMargin="0" IsBackButtonVisible="Visible" + IsGridSplitterEnabled="True" IsPaneToggleVisible="True" MenuItemsSource="{Binding ViewModel.MenuItems, Mode=OneWay}" OpenPaneLength="310" diff --git a/src/Wpf.Ui/Controls/NavigationView/INavigationView.cs b/src/Wpf.Ui/Controls/NavigationView/INavigationView.cs index df33384c5..681d3c92e 100644 --- a/src/Wpf.Ui/Controls/NavigationView/INavigationView.cs +++ b/src/Wpf.Ui/Controls/NavigationView/INavigationView.cs @@ -166,6 +166,11 @@ public interface INavigationView /// Thickness FrameMargin { get; set; } + /// + /// Gets or sets a value indicating whether the GridSplitter is enabled in the LeftNavigationViewTemplate. + /// + bool IsGridSplitterEnabled { get; set; } + /// /// Occurs when the NavigationView pane is opened. /// diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Events.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Events.cs index ef2af06f2..226148db6 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Events.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Events.cs @@ -125,6 +125,7 @@ public event TypedEventHandler Navigated protected virtual void OnPaneOpened() { RaiseEvent(new RoutedEventArgs(PaneOpenedEvent, this)); + UpdatePaneColumnWidthForToggle(); } /// @@ -133,6 +134,7 @@ protected virtual void OnPaneOpened() protected virtual void OnPaneClosed() { RaiseEvent(new RoutedEventArgs(PaneClosedEvent, this)); + UpdatePaneColumnWidthForToggle(); } /// diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs index 43086aa16..30290cdc9 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs @@ -259,6 +259,14 @@ public partial class NavigationView new FrameworkPropertyMetadata(default(Thickness)) ); + /// Identifies the dependency property. + public static readonly DependencyProperty IsGridSplitterEnabledProperty = DependencyProperty.Register( + nameof(IsGridSplitterEnabled), + typeof(bool), + typeof(NavigationView), + new FrameworkPropertyMetadata(false) + ); + /// /// Gets or sets a value indicating whether debugging messages for this control are enabled /// @@ -480,6 +488,13 @@ public Thickness FrameMargin set => SetValue(FrameMarginProperty, value); } + /// + public bool IsGridSplitterEnabled + { + get => (bool)GetValue(IsGridSplitterEnabledProperty); + set => SetValue(IsGridSplitterEnabledProperty, value); + } + private void OnMenuItemsSource_CollectionChanged( object? sender, IList collection, diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationView.TemplateParts.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationView.TemplateParts.cs index b0aa2436f..e22a5f934 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationView.TemplateParts.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationView.TemplateParts.cs @@ -6,6 +6,10 @@ // Based on Windows UI Library // Copyright(c) Microsoft Corporation.All rights reserved. +using System; +using System.ComponentModel; +using System.Windows; + // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; @@ -30,6 +34,7 @@ namespace Wpf.Ui.Controls; Name = TemplateElementAutoSuggestBoxSymbolButton, Type = typeof(System.Windows.Controls.Button) )] +[TemplatePart(Name = TemplateElementPaneColumn, Type = typeof(System.Windows.Controls.ColumnDefinition))] public partial class NavigationView { /// @@ -63,6 +68,11 @@ public partial class NavigationView /// private const string TemplateElementAutoSuggestBoxSymbolButton = "PART_AutoSuggestBoxSymbolButton"; + /// + /// Template element represented by the PART_PaneColumn name. + /// + private const string TemplateElementPaneColumn = "PART_PaneColumn"; + /// /// Gets or sets the control responsible for rendering the content. /// @@ -93,6 +103,15 @@ public partial class NavigationView /// protected System.Windows.Controls.Button? AutoSuggestBoxSymbolButton { get; set; } + /// + /// Gets or sets the pane column definition for GridSplitter support. + /// + protected System.Windows.Controls.ColumnDefinition? PaneColumn { get; set; } + + private DependencyPropertyDescriptor? _openPaneLengthDescriptor; + private DependencyPropertyDescriptor? _isPaneOpenDescriptor; + private double _lastOpenPaneWidth = double.NaN; + /// public override void OnApplyTemplate() { @@ -149,6 +168,170 @@ is System.Windows.Controls.Button autoSuggestBoxSymbolButton ToggleButton.Click -= OnToggleButtonClick; ToggleButton.Click += OnToggleButtonClick; } + + // Clean up previous event handlers + if (_openPaneLengthDescriptor != null) + { + _openPaneLengthDescriptor.RemoveValueChanged(this, OnPanePropertyChanged); + _openPaneLengthDescriptor = null; + } + + if (_isPaneOpenDescriptor != null) + { + _isPaneOpenDescriptor.RemoveValueChanged(this, OnPanePropertyChanged); + _isPaneOpenDescriptor = null; + } + + // Initialize PaneColumn width for GridSplitter support + var paneColumn = GetTemplateChild(TemplateElementPaneColumn) as System.Windows.Controls.ColumnDefinition; + if (paneColumn != null) + { + PaneColumn = paneColumn; + + // Set initial width using OpenPaneLength or saved width + if (IsPaneOpen && OpenPaneLength > 0) + { + var initialWidth = !double.IsNaN(_lastOpenPaneWidth) && _lastOpenPaneWidth > 0 + ? _lastOpenPaneWidth + : OpenPaneLength; + PaneColumn.Width = new GridLength(initialWidth); + } + else if (!IsPaneOpen) + { + PaneColumn.Width = new GridLength(40.0); + } + + // Update on next render pass to avoid issues + Dispatcher.BeginInvoke(new Action(UpdatePaneColumnWidth), System.Windows.Threading.DispatcherPriority.Loaded); + + // Listen to OpenPaneLength and IsPaneOpen changes + _openPaneLengthDescriptor = DependencyPropertyDescriptor.FromProperty(OpenPaneLengthProperty, typeof(NavigationView)); + if (_openPaneLengthDescriptor != null) + { + _openPaneLengthDescriptor.AddValueChanged(this, OnPanePropertyChanged); + } + + _isPaneOpenDescriptor = DependencyPropertyDescriptor.FromProperty(IsPaneOpenProperty, typeof(NavigationView)); + if (_isPaneOpenDescriptor != null) + { + _isPaneOpenDescriptor.AddValueChanged(this, OnPanePropertyChanged); + } + } + else + { + PaneColumn = null; + } + } + + private void OnPanePropertyChanged(object? sender, EventArgs e) + { + if (Dispatcher.CheckAccess()) + { + UpdatePaneColumnWidth(); + } + else + { + Dispatcher.BeginInvoke(new Action(UpdatePaneColumnWidth), System.Windows.Threading.DispatcherPriority.Normal); + } + } + + private void UpdatePaneColumnWidth() + { + if (PaneColumn is null) + { + return; + } + + try + { + // If pane is closed, don't update the width - maintain closed state + // This prevents the pane from automatically opening when window size changes + if (!IsPaneOpen) + { + // Only ensure it's set to 40.0 if it's not already + if (!PaneColumn.Width.IsAbsolute || Math.Abs(PaneColumn.Width.Value - 40.0) > 0.1) + { + PaneColumn.SetCurrentValue(System.Windows.Controls.ColumnDefinition.WidthProperty, new GridLength(40.0)); + } + return; + } + + // Only update width when pane is open + var currentWidth = PaneColumn.Width.IsAbsolute ? PaneColumn.Width.Value : OpenPaneLength; + var targetWidth = !double.IsNaN(_lastOpenPaneWidth) && _lastOpenPaneWidth > 0 + ? _lastOpenPaneWidth + : OpenPaneLength; + + // Don't update if GridSplitter is enabled and user has manually resized + // This allows GridSplitter to control the width + if (IsGridSplitterEnabled && Math.Abs(currentWidth - targetWidth) > 1.0) + { + // User has manually resized, save the current width + if (PaneColumn.Width.IsAbsolute) + { + _lastOpenPaneWidth = PaneColumn.Width.Value; + } + return; + } + + // Update to target width + if (Math.Abs(currentWidth - targetWidth) > 0.1) + { + PaneColumn.SetCurrentValue(System.Windows.Controls.ColumnDefinition.WidthProperty, new GridLength(targetWidth)); + } + } + catch + { + // Ignore errors during initialization + } + } + + /// + /// Updates PaneColumn width when pane is toggled open/closed. + /// This is called from OnPaneOpened/OnPaneClosed to ensure width updates even if user has resized. + /// + private void UpdatePaneColumnWidthForToggle() + { + if (PaneColumn is null) + { + return; + } + + try + { + if (IsPaneOpen) + { + // When opening, use the last saved width if available, otherwise use OpenPaneLength + var targetWidth = !double.IsNaN(_lastOpenPaneWidth) && _lastOpenPaneWidth > 0 + ? _lastOpenPaneWidth + : OpenPaneLength; + + if (targetWidth > 0) + { + PaneColumn.SetCurrentValue(System.Windows.Controls.ColumnDefinition.WidthProperty, new GridLength(targetWidth)); + } + } + else + { + // When closing, save the current width if GridSplitter is enabled and pane was resized + if (IsGridSplitterEnabled && PaneColumn.Width.IsAbsolute) + { + var currentWidth = PaneColumn.Width.Value; + // Only save if it's different from the default OpenPaneLength (user has resized) + if (Math.Abs(currentWidth - OpenPaneLength) > 1.0) + { + _lastOpenPaneWidth = currentWidth; + } + } + + // Always set to 40.0 when closing + PaneColumn.SetCurrentValue(System.Windows.Controls.ColumnDefinition.WidthProperty, new GridLength(40.0)); + } + } + catch + { + // Ignore errors during initialization + } } protected T GetTemplateChild(string name) diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml b/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml index 6ea15a786..408ca4177 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml @@ -282,14 +282,18 @@ - + + @@ -436,8 +440,33 @@ - + + + + + + - - + + + + + + + +