diff --git a/Directory.Packages.props b/Directory.Packages.props
index 9f1533ae6..bd7c0dc3a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -22,6 +22,7 @@
+
diff --git a/src/Wpf.Ui.Gallery/Models/AppConfig.cs b/src/Wpf.Ui.Gallery/Models/AppConfig.cs
new file mode 100644
index 000000000..e95403291
--- /dev/null
+++ b/src/Wpf.Ui.Gallery/Models/AppConfig.cs
@@ -0,0 +1,28 @@
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
+// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
+// All Rights Reserved.
+
+namespace Wpf.Ui.Gallery.Models;
+
+///
+/// Application configuration model.
+///
+public class AppConfig
+{
+ ///
+ /// Gets or sets the configurations folder.
+ ///
+ public string? ConfigurationsFolder { get; set; }
+
+ ///
+ /// Gets or sets the app properties file name.
+ ///
+ public string? AppPropertiesFileName { get; set; }
+
+ ///
+ /// Gets or sets the navigation pane width.
+ ///
+ public double NavigationPaneWidth { get; set; } = 310.0;
+}
+
diff --git a/src/Wpf.Ui.Gallery/Services/AppConfigService.cs b/src/Wpf.Ui.Gallery/Services/AppConfigService.cs
new file mode 100644
index 000000000..189507426
--- /dev/null
+++ b/src/Wpf.Ui.Gallery/Services/AppConfigService.cs
@@ -0,0 +1,75 @@
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
+// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
+// All Rights Reserved.
+
+using System.Text.Json;
+using Wpf.Ui.Gallery.Models;
+
+namespace Wpf.Ui.Gallery.Services;
+
+///
+/// Application configuration service.
+///
+public class AppConfigService
+{
+ private readonly string _configFilePath;
+ private readonly AppConfig _config;
+
+ public AppConfigService()
+ {
+ string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ string appFolder = Path.Combine(appDataPath, "Wpf.Ui.Gallery");
+
+ if (!Directory.Exists(appFolder))
+ {
+ _ = Directory.CreateDirectory(appFolder);
+ }
+
+ _configFilePath = Path.Combine(appFolder, "config.json");
+ _config = LoadConfig();
+ }
+
+ public AppConfig Config => _config;
+
+ private AppConfig LoadConfig()
+ {
+ if (File.Exists(_configFilePath))
+ {
+ try
+ {
+ string json = File.ReadAllText(_configFilePath);
+ return JsonSerializer.Deserialize(json) ?? new AppConfig();
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error loading config: {ex.Message}");
+ return new AppConfig();
+ }
+ }
+ return new AppConfig();
+ }
+
+ private void SaveConfig(AppConfig config)
+ {
+ try
+ {
+ string json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
+ File.WriteAllText(_configFilePath, json);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error saving config: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Updates the navigation pane width and saves the configuration
+ ///
+ /// The new width value
+ public void UpdateNavigationPaneWidth(double newWidth)
+ {
+ _config.NavigationPaneWidth = newWidth;
+ SaveConfig(_config);
+ }
+}
diff --git a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml
index a6a9607a2..08e5c0d5f 100644
--- a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml
+++ b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml
@@ -40,7 +40,7 @@
BreadcrumbBar="{Binding ElementName=BreadcrumbBar}"
EnableDebugMessages="True"
FooterMenuItemsSource="{Binding ViewModel.FooterMenuItems, Mode=OneWay}"
- FrameMargin="0"
+ FrameMargin="0,100"
IsBackButtonVisible="Visible"
IsPaneToggleVisible="True"
MenuItemsSource="{Binding ViewModel.MenuItems, Mode=OneWay}"
@@ -50,11 +50,37 @@
PaneOpened="NavigationView_OnPaneOpened"
SelectionChanged="OnNavigationSelectionChanged"
TitleBar="{Binding ElementName=TitleBar, Mode=OneWay}"
- Transition="FadeInWithSlide">
+ Transition="FadeInWithSlide"
+ IsPaneResizable="True"
+ PaneResizeMinWidth="200"
+ PaneResizeMaxWidth="500">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml.cs b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml.cs
index cf2543f77..21ad5fa45 100644
--- a/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml.cs
+++ b/src/Wpf.Ui.Gallery/Views/Windows/MainWindow.xaml.cs
@@ -4,6 +4,7 @@
// All Rights Reserved.
using Wpf.Ui.Controls;
+using Wpf.Ui.Gallery.Services;
using Wpf.Ui.Gallery.Services.Contracts;
using Wpf.Ui.Gallery.ViewModels.Windows;
using Wpf.Ui.Gallery.Views.Pages;
@@ -12,6 +13,8 @@ namespace Wpf.Ui.Gallery.Views.Windows;
public partial class MainWindow : IWindow
{
+ private readonly AppConfigService _configService;
+
public MainWindow(
MainWindowViewModel viewModel,
INavigationService navigationService,
@@ -25,11 +28,21 @@ IContentDialogService contentDialogService
ViewModel = viewModel;
DataContext = this;
+ // Initialize configuration service
+ _configService = new AppConfigService();
+
InitializeComponent();
+ // Restore saved navigation pane width
+ RestoreNavigationPaneWidth();
+
+ SetPageService(serviceProvider.GetService()!);
snackbarService.SetSnackbarPresenter(SnackbarPresenter);
- navigationService.SetNavigationControl(NavigationView);
- contentDialogService.SetDialogHost(RootContentDialog);
+ contentDialogService.Set = RootContentDialog;
+
+ NavigationView.Set
+ NavigationView.SetCurrentValue(NavigationView.MenuItemsSourceProperty, ViewModel.MenuItems);
+ NavigationView.SetCurrentValue(NavigationView.FooterMenuItemsSourceProperty, ViewModel.FooterMenuItems);
}
public MainWindowViewModel ViewModel { get; }
@@ -38,6 +51,31 @@ IContentDialogService contentDialogService
private bool _isPaneOpenedOrClosedFromCode;
+ ///
+ /// Handles the value change event of the navigation pane width slider
+ ///
+ private void PaneWidthSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ if (NavigationView != null && e.NewValue > 0)
+ {
+ NavigationView.SetCurrentValue(NavigationView.OpenPaneLengthProperty, e.NewValue);
+ _configService.UpdateNavigationPaneWidth(e.NewValue);
+ }
+ }
+
+ ///
+ /// Restores the saved navigation pane width
+ ///
+ private void RestoreNavigationPaneWidth()
+ {
+ var savedWidth = _configService.Config.NavigationPaneWidth;
+ if (savedWidth >= 200 && savedWidth <= 500)
+ {
+ NavigationView.SetCurrentValue(NavigationView.OpenPaneLengthProperty, savedWidth);
+ PaneWidthSlider.SetCurrentValue(System.Windows.Controls.Primitives.RangeBase.ValueProperty, savedWidth);
+ }
+ }
+
private void OnNavigationSelectionChanged(object sender, RoutedEventArgs e)
{
if (sender is not Wpf.Ui.Controls.NavigationView navigationView)
diff --git a/src/Wpf.Ui.Gallery/Wpf.Ui.Gallery.csproj b/src/Wpf.Ui.Gallery/Wpf.Ui.Gallery.csproj
index 6ecc4de7a..9e4233965 100644
--- a/src/Wpf.Ui.Gallery/Wpf.Ui.Gallery.csproj
+++ b/src/Wpf.Ui.Gallery/Wpf.Ui.Gallery.csproj
@@ -27,6 +27,7 @@
+
diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs
index 47aa0758f..34baf6e13 100644
--- a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs
+++ b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Properties.cs
@@ -243,6 +243,30 @@ public partial class NavigationView
new FrameworkPropertyMetadata(default(Thickness))
);
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty IsPaneResizableProperty = DependencyProperty.Register(
+ nameof(IsPaneResizable),
+ typeof(bool),
+ typeof(NavigationView),
+ new FrameworkPropertyMetadata(true)
+ );
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty PaneResizeMinWidthProperty = DependencyProperty.Register(
+ nameof(PaneResizeMinWidth),
+ typeof(double),
+ typeof(NavigationView),
+ new FrameworkPropertyMetadata(200.0)
+ );
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty PaneResizeMaxWidthProperty = DependencyProperty.Register(
+ nameof(PaneResizeMaxWidth),
+ typeof(double),
+ typeof(NavigationView),
+ new FrameworkPropertyMetadata(500.0)
+ );
+
///
/// Gets or sets a value indicating whether debugging messages for this control are enabled
///
@@ -450,6 +474,33 @@ public Thickness FrameMargin
set => SetValue(FrameMarginProperty, value);
}
+ ///
+ /// Gets or sets a value indicating whether the navigation pane is resizable.
+ ///
+ public bool IsPaneResizable
+ {
+ get => (bool)GetValue(IsPaneResizableProperty);
+ set => SetValue(IsPaneResizableProperty, value);
+ }
+
+ ///
+ /// Gets or sets the minimum width for resizing the navigation pane.
+ ///
+ public double PaneResizeMinWidth
+ {
+ get => (double)GetValue(PaneResizeMinWidthProperty);
+ set => SetValue(PaneResizeMinWidthProperty, value);
+ }
+
+ ///
+ /// Gets or sets the maximum width for resizing the navigation pane.
+ ///
+ public double PaneResizeMaxWidth
+ {
+ get => (double)GetValue(PaneResizeMaxWidthProperty);
+ set => SetValue(PaneResizeMaxWidthProperty, value);
+ }
+
private void OnMenuItemsSource_CollectionChanged(
object? sender,
IList collection,
diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml b/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml
index 03a7524a3..9fd8af31e 100644
--- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml
+++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewCompact.xaml
@@ -6,6 +6,7 @@
+
@@ -421,6 +422,16 @@
+
+
+
+/// NavigationView resize handle control
+///
+public class NavigationViewResizeHandle : Control
+{
+ static NavigationViewResizeHandle()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(
+ typeof(NavigationViewResizeHandle),
+ new FrameworkPropertyMetadata(typeof(NavigationViewResizeHandle))
+ );
+ }
+
+ ///
+ /// Minimum resizable width
+ ///
+ public static readonly DependencyProperty MinWidthProperty = DependencyProperty.Register(
+ nameof(MinWidth),
+ typeof(double),
+ typeof(NavigationViewResizeHandle),
+ new FrameworkPropertyMetadata(200.0)
+ );
+
+ ///
+ /// Maximum resizable width
+ ///
+ public static readonly DependencyProperty MaxWidthProperty = DependencyProperty.Register(
+ nameof(MaxWidth),
+ typeof(double),
+ typeof(NavigationViewResizeHandle),
+ new FrameworkPropertyMetadata(500.0)
+ );
+
+ ///
+ /// Current width
+ ///
+ public static readonly DependencyProperty CurrentWidthProperty = DependencyProperty.Register(
+ nameof(CurrentWidth),
+ typeof(double),
+ typeof(NavigationViewResizeHandle),
+ new FrameworkPropertyMetadata(320.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
+ );
+
+ ///
+ /// Gets or sets the minimum resizable width
+ ///
+ public double MinWidth
+ {
+ get => (double)GetValue(MinWidthProperty);
+ set => SetValue(MinWidthProperty, value);
+ }
+
+ ///
+ /// Gets or sets the maximum resizable width
+ ///
+ public double MaxWidth
+ {
+ get => (double)GetValue(MaxWidthProperty);
+ set => SetValue(MaxWidthProperty, value);
+ }
+
+ ///
+ /// Gets or sets the current width
+ ///
+ public double CurrentWidth
+ {
+ get => (double)GetValue(CurrentWidthProperty);
+ set => SetValue(CurrentWidthProperty, value);
+ }
+
+ ///
+ /// Gets a value indicating whether the mouse button is pressed
+ ///
+ public bool IsPressed
+ {
+ get => (bool)GetValue(IsPressedProperty);
+ private set => SetValue(IsPressedProperty, value);
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty IsPressedProperty = DependencyProperty.Register(
+ nameof(IsPressed),
+ typeof(bool),
+ typeof(NavigationViewResizeHandle),
+ new FrameworkPropertyMetadata(false)
+ );
+
+ private bool _isResizing;
+ private Point _startPoint;
+ private double _startWidth;
+ private double _lastWidth;
+
+ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
+ {
+ if (e.ButtonState == MouseButtonState.Pressed)
+ {
+ _isResizing = true;
+ IsPressed = true;
+ // Get position relative to parent element (NavigationView)
+ _startPoint = e.GetPosition(this.Parent as FrameworkElement ?? this);
+ _startWidth = CurrentWidth;
+ _lastWidth = _startWidth;
+ CaptureMouse();
+ e.Handled = true;
+ }
+
+ base.OnMouseLeftButtonDown(e);
+ }
+
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ if (_isResizing)
+ {
+ // Get position relative to parent element (NavigationView)
+ Point currentPoint = e.GetPosition(this.Parent as FrameworkElement ?? this);
+ double deltaX = currentPoint.X - _startPoint.X;
+ double newWidth = _startWidth + deltaX;
+
+ // Apply minimum and maximum width constraints
+ newWidth = Math.Max(MinWidth, Math.Min(MaxWidth, newWidth));
+
+ // Only update if width change is at least 1px to prevent flickering
+ if (Math.Abs(newWidth - _lastWidth) >= 1.0)
+ {
+ SetCurrentValue(CurrentWidthProperty, newWidth);
+ _lastWidth = newWidth;
+ }
+
+ e.Handled = true;
+ }
+ else
+ {
+ // Change mouse cursor to resize cursor
+ SetCurrentValue(CursorProperty, Cursors.SizeWE);
+ }
+
+ base.OnMouseMove(e);
+ }
+
+ protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
+ {
+ if (_isResizing)
+ {
+ _isResizing = false;
+ IsPressed = false;
+ ReleaseMouseCapture();
+ e.Handled = true;
+ }
+
+ base.OnMouseLeftButtonUp(e);
+ }
+
+ protected override void OnMouseLeave(MouseEventArgs e)
+ {
+ if (!_isResizing)
+ {
+ SetCurrentValue(CursorProperty, Cursors.Arrow);
+ }
+
+ base.OnMouseLeave(e);
+ }
+
+ protected override void OnLostMouseCapture(MouseEventArgs e)
+ {
+ _isResizing = false;
+ IsPressed = false;
+ base.OnLostMouseCapture(e);
+ }
+}
+
diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewResizeHandle.xaml b/src/Wpf.Ui/Controls/NavigationView/NavigationViewResizeHandle.xaml
new file mode 100644
index 000000000..8254055d9
--- /dev/null
+++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewResizeHandle.xaml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+