diff --git a/src/SampleApp/App.xaml b/src/SampleApp/App.xaml
index 010590c..88e6948 100644
--- a/src/SampleApp/App.xaml
+++ b/src/SampleApp/App.xaml
@@ -1,16 +1,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ #320C171E
+ #321D3849
+ #3224465B
+ #3233637F
+
+
+
+
diff --git a/src/SampleApp/App.xaml.cs b/src/SampleApp/App.xaml.cs
index 9c40589..b9c6f6d 100644
--- a/src/SampleApp/App.xaml.cs
+++ b/src/SampleApp/App.xaml.cs
@@ -1,50 +1,540 @@
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Controls.Primitives;
-using Microsoft.UI.Xaml.Data;
-using Microsoft.UI.Xaml.Input;
-using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Navigation;
-using Microsoft.UI.Xaml.Shapes;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices.WindowsRuntime;
-using Windows.ApplicationModel;
-using Windows.ApplicationModel.Activation;
-using Windows.Foundation;
-using Windows.Foundation.Collections;
-
-// To learn more about WinUI, the WinUI project structure,
-// and more about our project templates, see: http://aka.ms/winui-project-info.
-
-namespace SampleApp
-{
- ///
- /// Provides application-specific behavior to supplement the default Application class.
- ///
- public partial class App : Application
- {
- ///
- /// Initializes the singleton application object. This is the first line of authored code
- /// executed, and as such is the logical equivalent of main() or WinMain().
- ///
- public App()
- {
- this.InitializeComponent();
- }
-
- ///
- /// Invoked when the application is launched.
- ///
- /// Details about the launch request and process.
- protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
- {
- m_window = new MainWindow();
- m_window.Activate();
- }
-
- private Window m_window;
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Media.Imaging;
+
+using Windows.UI.Popups;
+using Windows.UI.ViewManagement;
+
+namespace SampleApp;
+
+///
+/// I've modified the UX/UI and added the "SingleClickEdit" feature.
+/// Forked from https://github.com/w-ahmad/WinUI.TableView
+///
+public partial class App : Application
+{
+ int m_width = 1300;
+ int m_height = 700;
+ Window m_window = null;
+ static UISettings m_UISettings = new UISettings();
+ public static bool IsClosing { get; set; } = false;
+ public static IntPtr WindowHandle { get; set; }
+ public static FrameworkElement? MainRoot { get; set; }
+
+ // We won't configure backing fields for these as the user could adjust them during app lifetime.
+ public static bool TransparencyEffectsEnabled
+ {
+ get => m_UISettings.AdvancedEffectsEnabled;
+ }
+ public static bool AnimationsEffectsEnabled
+ {
+ get => m_UISettings.AnimationsEnabled;
+ }
+ public static double TextScaleFactor
+ {
+ get => m_UISettings.TextScaleFactor;
+ }
+ public static bool AutoHideScrollbars
+ {
+ get => m_UISettings.AutoHideScrollBars;
+ }
+
+ public static string TimeStamp
+ {
+ get => DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fff tt");
+ }
+
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ Debug.WriteLine($"[INFO] {System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType?.Name}__{System.Reflection.MethodBase.GetCurrentMethod()?.Name} [{TimeStamp}]");
+
+ App.Current.DebugSettings.FailFastOnErrors = false;
+
+ #region [Exception handlers]
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomainUnhandledException;
+ AppDomain.CurrentDomain.FirstChanceException += CurrentDomainFirstChanceException;
+ AppDomain.CurrentDomain.ProcessExit += CurrentDomainOnProcessExit;
+ TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
+ UnhandledException += ApplicationUnhandledException;
+ #endregion
+
+ this.InitializeComponent();
+
+ foreach (var ra in GetReferencedAssemblies())
+ {
+ Debug.WriteLine($"[INFO] {ra.Key} {ra.Value}");
+ }
+ }
+
+ ///
+ /// Invoked when the application is launched.
+ ///
+ /// Details about the launch request and process.
+ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
+ {
+ m_window = new MainWindow();
+
+ var appWin = GetAppWindow(m_window);
+ if (appWin != null)
+ {
+ // We don't have the Closing event exposed by default, so we'll use the AppWindow to compensate.
+ appWin.Closing += (s, e) =>
+ {
+ App.IsClosing = true;
+ Debug.WriteLine($"[INFO] Application closing detected at {TimeStamp}");
+ };
+
+ // Destroying is always called, but Closing is only called when the application is shutdown normally.
+ appWin.Destroying += (s, e) =>
+ {
+ Debug.WriteLine($"[INFO] Application destroying detected at {TimeStamp}");
+ };
+
+ // The changed event holds a bunch of juicy info that we can extrapolate.
+ appWin.Changed += (s, args) =>
+ {
+ if (args.DidSizeChange)
+ {
+ Debug.WriteLine($"[INFO] Window size is now {s.Size.Width},{s.Size.Height}");
+ }
+ if (args.DidPositionChange)
+ {
+ if (s.Presenter is Microsoft.UI.Windowing.OverlappedPresenter op && op.State != Microsoft.UI.Windowing.OverlappedPresenterState.Maximized)
+ Debug.WriteLine($"[INFO] Window position is now {s.Position.X},{s.Position.Y}");
+ }
+ };
+ appWin.SetIcon(System.IO.Path.Combine(AppContext.BaseDirectory, $"Assets/StoreLogoAlt.ico"));
+ appWin.TitleBar.IconShowOptions = Microsoft.UI.Windowing.IconShowOptions.ShowIconAndSystemMenu;
+ appWin.Resize(new Windows.Graphics.SizeInt32(m_width, m_height));
+ }
+
+ m_window.Activate();
+ MainRoot = m_window.Content as FrameworkElement; // Save the FrameworkElement for any future content dialogs.
+ CenterWindow(m_window);
+ }
+
+ #region [Window Helpers]
+ ///
+ /// This code example demonstrates how to retrieve an AppWindow from a WinUI3 window.
+ /// The AppWindow class is available for any top-level HWND in your app.
+ /// AppWindow is available only to desktop apps (both packaged and unpackaged), it's not available to UWP apps.
+ /// https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/windowing/windowing-overview
+ /// https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.windowing.appwindow.create?view=windows-app-sdk-1.3
+ ///
+ public Microsoft.UI.Windowing.AppWindow? GetAppWindow(object window)
+ {
+ // Retrieve the window handle (HWND) of the current (XAML) WinUI3 window.
+ var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
+
+ // For other classes to use (mostly P/Invoke).
+ App.WindowHandle = hWnd;
+
+ // Retrieve the WindowId that corresponds to hWnd.
+ Microsoft.UI.WindowId windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
+
+ // Lastly, retrieve the AppWindow for the current (XAML) WinUI3 window.
+ Microsoft.UI.Windowing.AppWindow appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);
+
+ return appWindow;
+ }
+
+ ///
+ /// Centers a based on the .
+ ///
+ /// This must be run on the UI thread.
+ public static void CenterWindow(Window window)
+ {
+ if (window == null) { return; }
+
+ try
+ {
+ System.IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
+ Microsoft.UI.WindowId windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
+ if (Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId) is Microsoft.UI.Windowing.AppWindow appWindow &&
+ Microsoft.UI.Windowing.DisplayArea.GetFromWindowId(windowId, Microsoft.UI.Windowing.DisplayAreaFallback.Nearest) is Microsoft.UI.Windowing.DisplayArea displayArea)
+ {
+ Windows.Graphics.PointInt32 CenteredPosition = appWindow.Position;
+ CenteredPosition.X = (displayArea.WorkArea.Width - appWindow.Size.Width) / 2;
+ CenteredPosition.Y = (displayArea.WorkArea.Height - appWindow.Size.Height) / 2;
+ appWindow.Move(CenteredPosition);
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ERROR] {MethodBase.GetCurrentMethod()?.Name}: {ex.Message}");
+ }
+ }
+
+ ///
+ /// The exposes properties such as:
+ /// OuterBounds (Rect32)
+ /// WorkArea.Width (int)
+ /// WorkArea.Height (int)
+ /// IsPrimary (bool)
+ /// DisplayId.Value (ulong)
+ ///
+ ///
+ ///
+ public Microsoft.UI.Windowing.DisplayArea? GetDisplayArea(Window window)
+ {
+ try
+ {
+ System.IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
+ Microsoft.UI.WindowId windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
+ var da = Microsoft.UI.Windowing.DisplayArea.GetFromWindowId(windowId, Microsoft.UI.Windowing.DisplayAreaFallback.Nearest);
+ return da;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ERROR] {MethodBase.GetCurrentMethod()?.Name}: {ex.Message}");
+ return null;
+ }
+ }
+ #endregion
+
+ #region [Dialog Helpers]
+ static SemaphoreSlim semaSlim = new SemaphoreSlim(1, 1);
+ ///
+ /// The does not look as nice as the
+ /// and is not part of the native Microsoft.UI.Xaml.Controls.
+ /// The offers the
+ /// callback, but this could be replaced with actions. Both can be shown asynchronously.
+ ///
+ ///
+ /// You'll need to call when using the ,
+ /// because the does not exist and an owner must be defined.
+ ///
+ public static async Task ShowMessageBox(string title, string message, string yesText, string noText, Action? yesAction, Action? noAction)
+ {
+ if (App.WindowHandle == IntPtr.Zero) { return; }
+
+ // Create the dialog.
+ var messageDialog = new MessageDialog($"{message}");
+ messageDialog.Title = title;
+
+ if (!string.IsNullOrEmpty(yesText))
+ {
+ messageDialog.Commands.Add(new UICommand($"{yesText}", (opt) => { yesAction?.Invoke(); }));
+ messageDialog.DefaultCommandIndex = 0;
+ }
+
+ if (!string.IsNullOrEmpty(noText))
+ {
+ messageDialog.Commands.Add(new UICommand($"{noText}", (opt) => { noAction?.Invoke(); }));
+ messageDialog.DefaultCommandIndex = 1;
+ }
+
+ // We must initialize the dialog with an owner.
+ WinRT.Interop.InitializeWithWindow.Initialize(messageDialog, App.WindowHandle);
+ // Show the message dialog. Our DialogDismissedHandler will deal with what selection the user wants.
+ await messageDialog.ShowAsync();
+ // We could force the result in a separate timer...
+ //DialogDismissedHandler(new UICommand("time-out"));
+ }
+
+ ///
+ /// The does not look as nice as the
+ /// and is not part of the native Microsoft.UI.Xaml.Controls.
+ /// The offers the
+ /// callback, but this could be replaced with actions. Both can be shown asynchronously.
+ ///
+ ///
+ /// You'll need to call when using the ,
+ /// because the does not exist and an owner must be defined.
+ ///
+ public static async Task ShowMessageBox(string title, string message, string primaryText, string cancelText)
+ {
+ // Create the dialog.
+ var messageDialog = new MessageDialog($"{message}");
+ messageDialog.Title = title;
+
+ if (!string.IsNullOrEmpty(primaryText))
+ {
+ messageDialog.Commands.Add(new UICommand($"{primaryText}", new UICommandInvokedHandler(DialogDismissedHandler)));
+ messageDialog.DefaultCommandIndex = 0;
+ }
+
+ if (!string.IsNullOrEmpty(cancelText))
+ {
+ messageDialog.Commands.Add(new UICommand($"{cancelText}", new UICommandInvokedHandler(DialogDismissedHandler)));
+ messageDialog.DefaultCommandIndex = 1;
+ }
+ // We must initialize the dialog with an owner.
+ WinRT.Interop.InitializeWithWindow.Initialize(messageDialog, App.WindowHandle);
+ // Show the message dialog. Our DialogDismissedHandler will deal with what selection the user wants.
+ await messageDialog.ShowAsync();
+
+ // We could force the result in a separate timer...
+ //DialogDismissedHandler(new UICommand("time-out"));
+ }
+
+ ///
+ /// Callback for the selected option from the user.
+ ///
+ static void DialogDismissedHandler(IUICommand command)
+ {
+ Debug.WriteLine($"[INFO] UICommand.Label ⇨ {command.Label}");
+ }
+
+ ///
+ /// The looks much better than the
+ /// and is part of the native Microsoft.UI.Xaml.Controls.
+ /// The does not offer a
+ /// callback, but in this example was replaced with actions. Both can be shown asynchronously.
+ ///
+ ///
+ /// There is no need to call when using the ,
+ /// but a must be defined since it inherits from .
+ /// The was added to prevent "COMException: Only one ContentDialog can be opened at a time."
+ ///
+ public static async Task ShowDialogBox(string title, string message, string primaryText, string cancelText, Action? onPrimary, Action? onCancel, Uri? imageUri)
+ {
+ if (App.MainRoot?.XamlRoot == null) { return; }
+
+ await semaSlim.WaitAsync();
+
+ #region [Initialize Assets]
+ double fontSize = 16;
+ Microsoft.UI.Xaml.Media.FontFamily fontFamily = new Microsoft.UI.Xaml.Media.FontFamily("Consolas");
+
+ if (App.Current.Resources.TryGetValue("FontSizeMedium", out object _))
+ fontSize = (double)App.Current.Resources["FontSizeMedium"];
+
+ if (App.Current.Resources.TryGetValue("PrimaryFont", out object _))
+ fontFamily = (Microsoft.UI.Xaml.Media.FontFamily)App.Current.Resources["PrimaryFont"];
+
+ StackPanel panel = new StackPanel()
+ {
+ Orientation = Microsoft.UI.Xaml.Controls.Orientation.Vertical,
+ Spacing = 10d
+ };
+
+ if (imageUri is not null)
+ {
+ panel.Children.Add(new Image
+ {
+ Margin = new Thickness(1, -50, 1, 1), // Move the image into the title area.
+ HorizontalAlignment = Microsoft.UI.Xaml.HorizontalAlignment.Right,
+ Stretch = Microsoft.UI.Xaml.Media.Stretch.UniformToFill,
+ Width = 40,
+ Height = 40,
+ Source = new BitmapImage(imageUri)
+ });
+ }
+
+ panel.Children.Add(new TextBlock()
+ {
+ Text = message,
+ FontSize = fontSize,
+ FontFamily = fontFamily,
+ HorizontalAlignment = Microsoft.UI.Xaml.HorizontalAlignment.Left,
+ TextWrapping = Microsoft.UI.Xaml.TextWrapping.Wrap
+ });
+
+ var tb = new TextBox()
+ {
+ Text = message,
+ FontSize = fontSize,
+ FontFamily = fontFamily,
+ TextWrapping = TextWrapping.Wrap
+ };
+ tb.Loaded += (s, e) => { tb.SelectAll(); };
+ #endregion
+
+ // NOTE: Content dialogs will automatically darken the background.
+ ContentDialog contentDialog = new ContentDialog()
+ {
+ Title = title,
+ PrimaryButtonText = primaryText,
+ CloseButtonText = cancelText,
+ Content = panel,
+ XamlRoot = App.MainRoot?.XamlRoot,
+ RequestedTheme = App.MainRoot?.ActualTheme ?? ElementTheme.Default
+ };
+
+ try
+ {
+ ContentDialogResult result = await contentDialog.ShowAsync();
+
+ switch (result)
+ {
+ case ContentDialogResult.Primary:
+ onPrimary?.Invoke();
+ break;
+ //case ContentDialogResult.Secondary:
+ // onSecondary?.Invoke();
+ // break;
+ case ContentDialogResult.None: // Cancel
+ onCancel?.Invoke();
+ break;
+ default:
+ Debug.WriteLine($"Dialog result not defined.");
+ break;
+ }
+ }
+ catch (System.Runtime.InteropServices.COMException ex)
+ {
+ Debug.WriteLine($"[ERROR] ShowDialogBox: {ex.Message}");
+ }
+ finally
+ {
+ semaSlim.Release();
+ }
+ }
+ #endregion
+
+ #region [Domain Events]
+ void ApplicationUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
+ {
+ // https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
+ Exception? ex = e.Exception;
+ Debug.WriteLine($"[UnhandledException]: {ex?.Message}");
+ Debug.WriteLine($"⇨ Unhandled exception of type {ex?.GetType()}: {ex}");
+ DebugLog($"Unhandled Exception StackTrace: {Environment.StackTrace}");
+ e.Handled = true;
+ }
+
+ void CurrentDomainOnProcessExit(object? sender, EventArgs e)
+ {
+ if (!IsClosing)
+ IsClosing = true;
+
+ if (sender is null)
+ return;
+
+ if (sender is AppDomain ad)
+ {
+ Debug.WriteLine($"[OnProcessExit]", $"{nameof(App)}");
+ Debug.WriteLine($"⇨ DomainID: {ad.Id}", $"{nameof(App)}");
+ Debug.WriteLine($"⇨ FriendlyName: {ad.FriendlyName}", $"{nameof(App)}");
+ Debug.WriteLine($"⇨ BaseDirectory: {ad.BaseDirectory}", $"{nameof(App)}");
+ }
+ }
+
+ void CurrentDomainFirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
+ {
+ Debug.WriteLine($"[ERROR] First chance exception from {sender?.GetType()}: {e.Exception.Message}");
+ DebugLog($"First chance exception from {sender?.GetType()}: {e.Exception.Message}");
+ if (e.Exception.InnerException != null)
+ DebugLog($" - InnerException: {e.Exception.InnerException.Message}");
+ DebugLog($"First chance exception StackTrace: {Environment.StackTrace}");
+ }
+
+ void CurrentDomainUnhandledException(object? sender, System.UnhandledExceptionEventArgs e)
+ {
+ Exception? ex = e.ExceptionObject as Exception;
+ Debug.WriteLine($"[ERROR] Thread exception of type {ex?.GetType()}: {ex}");
+ DebugLog($"Thread exception of type {ex?.GetType()}: {ex}");
+ }
+
+ void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
+ {
+ if (e.Exception is AggregateException aex)
+ {
+ aex?.Flatten().Handle(ex =>
+ {
+ Debug.WriteLine($"[ERROR] Unobserved task exception: {ex?.Message}");
+ DebugLog($"Unobserved task exception: {ex?.Message}");
+ return true;
+ });
+ }
+ e.SetObserved(); // suppress and handle manually
+ }
+ #endregion
+
+ #region [Miscellaneous]
+ public static void CloseAllDialogs()
+ {
+ if (App.MainRoot?.XamlRoot == null) { return; }
+ var openedDialogs = VisualTreeHelper.GetOpenPopupsForXamlRoot(App.MainRoot?.XamlRoot);
+ foreach (var item in openedDialogs)
+ {
+ if (item.Child is ContentDialog dialog)
+ dialog.Hide();
+ }
+ }
+
+ ///
+ /// Simplified debug logger for app-wide use.
+ ///
+ /// the text to append to the file
+ public static void DebugLog(string message)
+ {
+ try
+ {
+ System.IO.File.AppendAllText(System.IO.Path.Combine(System.AppContext.BaseDirectory, "Debug.log"), $"[{TimeStamp}] {message}{Environment.NewLine}");
+ }
+ catch (Exception)
+ {
+ Debug.WriteLine($"[{TimeStamp}] {message}");
+ }
+ }
+
+ ///
+ /// Returns the basic assemblies needed by the application.
+ ///
+ public static Dictionary GetReferencedAssemblies(bool addSelf = false)
+ {
+ Dictionary values = new Dictionary();
+ try
+ {
+ var assem = Assembly.GetExecutingAssembly();
+ int idx = 0; // to prevent key collisions only
+ if (addSelf)
+ values.Add($"{++idx}: {assem.GetName().Name}", assem.GetName().Version ?? new Version()); // add self
+ IOrderedEnumerable names = assem.GetReferencedAssemblies().OrderBy(o => o.Name);
+ foreach (var sas in names)
+ {
+ values.Add($"{++idx}: {sas.Name}", sas.Version ?? new Version());
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ERROR] GetReferencedAssemblies: {ex.Message}");
+ }
+ return values;
+ }
+
+ ///
+ /// Returns an exhaustive list of all modules involved in the current process.
+ ///
+ public static List GetProcessDependencies()
+ {
+ List result = new List();
+ try
+ {
+ string self = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + ".exe";
+ System.Diagnostics.ProcessModuleCollection pmc = System.Diagnostics.Process.GetCurrentProcess().Modules;
+ IOrderedEnumerable pmQuery = pmc
+ .OfType()
+ .Where(pt => pt.ModuleMemorySize > 0)
+ .OrderBy(o => o.ModuleName);
+ foreach (var item in pmQuery)
+ {
+ //if (!item.ModuleName.Contains($"{self}"))
+ result.Add($"Module name: {item.ModuleName}, {(string.IsNullOrEmpty(item.FileVersionInfo.FileVersion) ? "version unknown" : $"v{item.FileVersionInfo.FileVersion}")}");
+ try { item.Dispose(); }
+ catch { }
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ERROR] ProcessModuleCollection: {ex.Message}");
+ }
+ return result;
+ }
+ #endregion
+}
diff --git a/src/SampleApp/Assets/Info.png b/src/SampleApp/Assets/Info.png
new file mode 100644
index 0000000..8957f23
Binary files /dev/null and b/src/SampleApp/Assets/Info.png differ
diff --git a/src/SampleApp/Assets/SpinnerRing.png b/src/SampleApp/Assets/SpinnerRing.png
new file mode 100644
index 0000000..869cdba
Binary files /dev/null and b/src/SampleApp/Assets/SpinnerRing.png differ
diff --git a/src/SampleApp/Assets/StoreLogo.ico b/src/SampleApp/Assets/StoreLogo.ico
new file mode 100644
index 0000000..66e9c10
Binary files /dev/null and b/src/SampleApp/Assets/StoreLogo.ico differ
diff --git a/src/SampleApp/Assets/StoreLogo.png b/src/SampleApp/Assets/StoreLogo.png
new file mode 100644
index 0000000..023510d
Binary files /dev/null and b/src/SampleApp/Assets/StoreLogo.png differ
diff --git a/src/SampleApp/Assets/StoreLogoAlt.ico b/src/SampleApp/Assets/StoreLogoAlt.ico
new file mode 100644
index 0000000..ef304fa
Binary files /dev/null and b/src/SampleApp/Assets/StoreLogoAlt.ico differ
diff --git a/src/SampleApp/Assets/StoreLogoAlt.png b/src/SampleApp/Assets/StoreLogoAlt.png
new file mode 100644
index 0000000..056c702
Binary files /dev/null and b/src/SampleApp/Assets/StoreLogoAlt.png differ
diff --git a/src/SampleApp/Assets/Warning.png b/src/SampleApp/Assets/Warning.png
new file mode 100644
index 0000000..21b3373
Binary files /dev/null and b/src/SampleApp/Assets/Warning.png differ
diff --git a/src/SampleApp/Assets/mtns.csv b/src/SampleApp/Assets/mtns.csv
index 8028212..3722c9a 100644
--- a/src/SampleApp/Assets/mtns.csv
+++ b/src/SampleApp/Assets/mtns.csv
@@ -5,7 +5,7 @@
5,Makalu,8485,Mahalangur Himalaya,27d53m23sN 87d5m20sE,2386,Mount Everest,05/15/1955,45 (52)
6,Cho Oyu,8188,Mahalangur Himalaya,28d05m39sN 86d39m39sE,2340,Mount Everest,10/19/1954,79 (28)
7,Dhaulagiri I,8167,Dhaulagiri Himalaya,28d41m48sN 83d29m35sE,3357,K2,05/13/1960,51 (39)
-8,Manaslu,8163,Manaslu Himalaya,28d33m00sN 84d33m35sE,3092,Cho Oyu,05/9/1956,49 (45)
+8,Manaslu,8163,Manaslu Himalaya,28d33m00sN 84d33m35sE,3092,Cho Oyu,05/09/1956,49 (45)
9,Nanga Parbat,8126,Nanga Parbat Himalaya,35d14m14sN 74d35m21sE,4608,Dhaulagiri,07/03/1953,52 (67)
10,Annapurna I,8091,Annapurna Himalaya,28d35m44sN 83d49m13sE,2984,Cho Oyu,06/03/1950,36 (47)
11,Gasherbrum I,8080,Baltoro Karakoram,35d43m28sN 76d41m47sE,2155,K2,01/01/1958,31 (16)
diff --git a/src/SampleApp/DataGridDataItem.cs b/src/SampleApp/DataGridDataItem.cs
index 028ac3e..d511d23 100644
--- a/src/SampleApp/DataGridDataItem.cs
+++ b/src/SampleApp/DataGridDataItem.cs
@@ -1,255 +1,223 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Runtime.CompilerServices;
-
-namespace SampleApp;
-
-#nullable disable
-
-public class DataGridDataItem : INotifyDataErrorInfo, IComparable, INotifyPropertyChanged
-{
- private Dictionary> _errors = new Dictionary>();
- private uint _rank;
- private string _mountain;
- private uint _height;
- private string _range;
- private string _parentMountain;
- private string coordinates;
- private uint prominence;
- private DateTimeOffset first_ascent;
- private string ascents;
-
- public event EventHandler ErrorsChanged;
- public event PropertyChangedEventHandler PropertyChanged;
-
- public uint Rank
- {
- get
- {
- return _rank;
- }
-
- set
- {
- if (_rank != value)
- {
- _rank = value;
- OnPropertyChanged();
- }
- }
- }
-
- public string Mountain
- {
- get
- {
- return _mountain;
- }
-
- set
- {
- if (_mountain != value)
- {
- _mountain = value;
-
- bool isMountainValid = !_errors.ContainsKey("Mountain");
- if (_mountain == string.Empty && isMountainValid)
- {
- List errors = new List();
- errors.Add("Mountain name cannot be empty");
- _errors.Add("Mountain", errors);
- ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Mountain"));
- }
- else if (_mountain != string.Empty && !isMountainValid)
- {
- _errors.Remove("Mountain");
- ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Mountain"));
- }
-
- OnPropertyChanged();
- }
- }
- }
-
- public uint Height_m
- {
- get
- {
- return _height;
- }
-
- set
- {
- if (_height != value)
- {
- _height = value;
- OnPropertyChanged();
- }
- }
- }
-
- public string Range
- {
- get
- {
- return _range;
- }
-
- set
- {
- if (_range != value)
- {
- _range = value;
-
- bool isRangeValid = !_errors.ContainsKey("Range");
- if (_range == string.Empty && isRangeValid)
- {
- List errors = new List();
- errors.Add("Range name cannot be empty");
- _errors.Add("Range", errors);
- ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Range"));
- }
- else if (_range != string.Empty && !isRangeValid)
- {
- _errors.Remove("Range");
- ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Range"));
- }
-
- OnPropertyChanged();
- }
- }
- }
-
- public string Parent_mountain
- {
- get
- {
- return _parentMountain;
- }
-
- set
- {
- if (_parentMountain != value)
- {
- _parentMountain = value;
-
- bool isParentValid = !_errors.ContainsKey("Parent_mountain");
- if (_parentMountain == string.Empty && isParentValid)
- {
- List errors = new List();
- errors.Add("Parent_mountain name cannot be empty");
- _errors.Add("Parent_mountain", errors);
- ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Parent_mountain"));
- }
- else if (_parentMountain != string.Empty && !isParentValid)
- {
- _errors.Remove("Parent_mountain");
- ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Parent_mountain"));
- }
-
- OnPropertyChanged();
- }
- }
- }
-
- public string Coordinates
- {
- get => coordinates;
- set
- {
- if (coordinates != value)
- {
- coordinates = value;
- OnPropertyChanged();
- }
- }
- }
-
- public uint Prominence
- {
- get => prominence;
- set
- {
- if (prominence != value)
- {
- prominence = value;
- OnPropertyChanged();
- }
- }
- }
-
- // You need to use DateTimeOffset to get proper binding to the CalendarDatePicker control, DateTime won't work.
- public DateTimeOffset First_ascent
- {
- get => first_ascent; set
- {
- if (first_ascent != value)
- {
- first_ascent = value;
- OnPropertyChanged();
- }
- }
- }
-
- public string Ascents
- {
- get => ascents; set
- {
- if (ascents != value)
- {
- ascents = value;
- OnPropertyChanged();
- }
- }
- }
-
- bool INotifyDataErrorInfo.HasErrors
- {
- get
- {
- return _errors.Keys.Count > 0;
- }
- }
-
- IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
- {
- if (propertyName == null)
- {
- propertyName = string.Empty;
- }
-
- if (_errors.ContainsKey(propertyName))
- {
- return _errors[propertyName];
- }
- else
- {
- return null;
- }
- }
-
- int IComparable.CompareTo(object obj)
- {
- int lnCompare = Range.CompareTo((obj as DataGridDataItem).Range);
-
- if (lnCompare == 0)
- {
- return Parent_mountain.CompareTo((obj as DataGridDataItem).Parent_mountain);
- }
- else
- {
- return lnCompare;
- }
- }
-
- private void OnPropertyChanged([CallerMemberName] string propertyName = null)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace SampleApp;
+
+#nullable disable
+
+public class DataGridDataItem : INotifyDataErrorInfo, IComparable, INotifyPropertyChanged
+{
+ string _mountain;
+ string _range;
+ string _parentMountain;
+ string _coordinates;
+ string _ascents;
+ uint _rank;
+ uint _height;
+ uint _prominence;
+ DateTimeOffset _firstAscent;
+ Dictionary> _errors = new Dictionary>();
+
+ public event EventHandler ErrorsChanged;
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ if (!string.IsNullOrEmpty(propertyName))
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ public uint Rank
+ {
+ get => _rank;
+ set
+ {
+ if (_rank != value)
+ {
+ _rank = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public string Mountain
+ {
+ get => _mountain;
+ set
+ {
+ if (_mountain != value)
+ {
+ _mountain = value;
+
+ bool isMountainValid = !_errors.ContainsKey("Mountain");
+ if (_mountain == string.Empty && isMountainValid)
+ {
+ List errors = new List();
+ errors.Add("Mountain name cannot be empty");
+ _errors.Add("Mountain", errors);
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Mountain"));
+ }
+ else if (_mountain != string.Empty && !isMountainValid)
+ {
+ _errors.Remove("Mountain");
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Mountain"));
+ }
+
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public uint Height_m
+ {
+ get => _height;
+ set
+ {
+ if (_height != value)
+ {
+ _height = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public string Range
+ {
+ get => _range;
+ set
+ {
+ if (_range != value)
+ {
+ _range = value;
+
+ bool isRangeValid = !_errors.ContainsKey("Range");
+ if (_range == string.Empty && isRangeValid)
+ {
+ List errors = new List();
+ errors.Add("Range name cannot be empty");
+ _errors.Add("Range", errors);
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Range"));
+ }
+ else if (_range != string.Empty && !isRangeValid)
+ {
+ _errors.Remove("Range");
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Range"));
+ }
+
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public string Parent_mountain
+ {
+ get => _parentMountain;
+ set
+ {
+ if (_parentMountain != value)
+ {
+ _parentMountain = value;
+
+ bool isParentValid = !_errors.ContainsKey("Parent_mountain");
+ if (_parentMountain == string.Empty && isParentValid)
+ {
+ List errors = new List();
+ errors.Add("Parent_mountain name cannot be empty");
+ _errors.Add("Parent_mountain", errors);
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Parent_mountain"));
+ }
+ else if (_parentMountain != string.Empty && !isParentValid)
+ {
+ _errors.Remove("Parent_mountain");
+ ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs("Parent_mountain"));
+ }
+
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public string Coordinates
+ {
+ get => _coordinates;
+ set
+ {
+ if (_coordinates != value)
+ {
+ _coordinates = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public uint Prominence
+ {
+ get => _prominence;
+ set
+ {
+ if (_prominence != value)
+ {
+ _prominence = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ ///
+ /// You need to use DateTimeOffset to get proper binding to the CalendarDatePicker control, DateTime won't work.
+ ///
+ public DateTimeOffset First_ascent
+ {
+ get => _firstAscent;
+ set
+ {
+ if (_firstAscent != value)
+ {
+ _firstAscent = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public string Ascents
+ {
+ get => _ascents;
+ set
+ {
+ if (_ascents != value)
+ {
+ _ascents = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ bool INotifyDataErrorInfo.HasErrors
+ {
+ get => _errors.Keys.Count > 0;
+ }
+
+ IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
+ {
+ if (propertyName == null)
+ propertyName = string.Empty;
+
+ if (_errors.ContainsKey(propertyName))
+ return _errors[propertyName];
+ else
+ return null;
+ }
+
+ int IComparable.CompareTo(object obj)
+ {
+ int lnCompare = Range.CompareTo((obj as DataGridDataItem).Range);
+
+ if (lnCompare == 0)
+ return Parent_mountain.CompareTo((obj as DataGridDataItem).Parent_mountain);
+ else
+ return lnCompare;
+ }
}
\ No newline at end of file
diff --git a/src/SampleApp/Helpers/ObservableCollectionEx.cs b/src/SampleApp/Helpers/ObservableCollectionEx.cs
new file mode 100644
index 0000000..a5d97bd
--- /dev/null
+++ b/src/SampleApp/Helpers/ObservableCollectionEx.cs
@@ -0,0 +1,57 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace SampleApp.Helpers;
+
+///
+/// Extends the functionality of the type.
+/// Provides the ability to add a range of items and only fire the collection changed event one time.
+/// https://github.com/AndrewKeepCoding/ObservableCollectionExSampleApp/blob/main/ObservableCollectionExSampleApp/ObservableCollectionEx.cs
+///
+/// data type for the collection
+public class ObservableCollectionEx : ObservableCollection
+{
+ public void AddRange(IEnumerable collection)
+ {
+ CheckReentrancy(); // from the System.Collections.ObjectModel.ObservableCollection class
+
+ // Is there anything to add?
+ if (collection.Any() is false)
+ return;
+
+ List itemsList = (List)Items;
+ itemsList.AddRange(collection);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(action: NotifyCollectionChangedAction.Add, changedItems: itemsList, startingIndex: itemsList.Count - 1));
+ }
+
+ public void AddRange(IList collection)
+ {
+ CheckReentrancy(); // from the System.Collections.ObjectModel.ObservableCollection class
+
+ // Is there anything to add?
+ if (collection.Any() is false)
+ return;
+
+ List itemsList = (List)Items;
+ itemsList.AddRange(collection);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(action: NotifyCollectionChangedAction.Add, changedItems: itemsList, startingIndex: itemsList.Count - 1));
+ }
+
+ public void AddRange(ICollection collection)
+ {
+ CheckReentrancy(); // from the System.Collections.ObjectModel.ObservableCollection class
+
+ // Is there anything to add?
+ if (collection.Any() is false)
+ return;
+
+ List itemsList = (List)Items;
+ itemsList.AddRange(collection);
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(action: NotifyCollectionChangedAction.Add, changedItems: itemsList, startingIndex: itemsList.Count - 1));
+ }
+}
\ No newline at end of file
diff --git a/src/SampleApp/Helpers/VisualTreeHelperExtensions.cs b/src/SampleApp/Helpers/VisualTreeHelperExtensions.cs
new file mode 100644
index 0000000..58205a5
--- /dev/null
+++ b/src/SampleApp/Helpers/VisualTreeHelperExtensions.cs
@@ -0,0 +1,217 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Media;
+
+using Windows.Foundation;
+
+namespace SampleApp.Helpers;
+
+public static class VisualTreeHelperExtensions
+{
+ public static T? GetFirstDescendantOfType(this DependencyObject start) where T : DependencyObject
+ {
+ return start.GetDescendantsOfType().FirstOrDefault();
+ }
+
+ public static IEnumerable GetDescendantsOfType(this DependencyObject start) where T : DependencyObject
+ {
+ return start.GetDescendants().OfType();
+ }
+
+ public static IEnumerable GetDescendants(this DependencyObject start)
+ {
+ var queue = new Queue();
+ var count = VisualTreeHelper.GetChildrenCount(start);
+
+ for (int i = 0; i < count; i++)
+ {
+ var child = VisualTreeHelper.GetChild(start, i);
+ yield return child;
+ queue.Enqueue(child);
+ }
+
+ while (queue.Count > 0)
+ {
+ var parent = queue.Dequeue();
+ var count2 = VisualTreeHelper.GetChildrenCount(parent);
+
+ for (int i = 0; i < count2; i++)
+ {
+ var child = VisualTreeHelper.GetChild(parent, i);
+ yield return child;
+ queue.Enqueue(child);
+ }
+ }
+ }
+
+ public static T? GetFirstAncestorOfType(this DependencyObject start) where T : DependencyObject
+ {
+ return start.GetAncestorsOfType().FirstOrDefault();
+ }
+
+ public static IEnumerable GetAncestorsOfType(this DependencyObject start) where T : DependencyObject
+ {
+ return start.GetAncestors().OfType();
+ }
+
+ public static IEnumerable GetAncestors(this DependencyObject start)
+ {
+ var parent = VisualTreeHelper.GetParent(start);
+
+ while (parent != null)
+ {
+ yield return parent;
+ parent = VisualTreeHelper.GetParent(parent);
+ }
+ }
+
+ public static bool IsInVisualTree(this DependencyObject dob)
+ {
+ return Window.Current.Content != null && dob.GetAncestors().Contains(Window.Current.Content);
+ }
+
+ public static Rect GetBoundingRect(this FrameworkElement dob, FrameworkElement? relativeTo = null)
+ {
+ if (relativeTo == null)
+ {
+ relativeTo = Window.Current.Content as FrameworkElement;
+ }
+
+ if (relativeTo == null)
+ {
+ throw new InvalidOperationException("Element not in visual tree.");
+ }
+
+ if (dob == relativeTo)
+ return new Rect(0, 0, relativeTo.ActualWidth, relativeTo.ActualHeight);
+
+ var ancestors = dob.GetAncestors().ToArray();
+
+ if (!ancestors.Contains(relativeTo))
+ {
+ throw new InvalidOperationException("Element not in visual tree.");
+ }
+
+ var pos =
+ dob
+ .TransformToVisual(relativeTo)
+ .TransformPoint(new Point());
+ var pos2 =
+ dob
+ .TransformToVisual(relativeTo)
+ .TransformPoint(
+ new Point(
+ dob.ActualWidth,
+ dob.ActualHeight));
+
+ return new Rect(pos, pos2);
+ }
+
+ public static IEnumerable GetHierarchyFromUIElement(this Type element)
+ {
+ if (element.GetTypeInfo().IsSubclassOf(typeof(UIElement)) != true)
+ {
+ yield break;
+ }
+
+ Type? current = element;
+
+ while (current != null && current != typeof(UIElement))
+ {
+ yield return current;
+ current = current.GetTypeInfo().BaseType;
+ }
+ }
+
+ public static TypeInfo GetTypeInfo(this Type type)
+ {
+ if (type == null)
+ throw new ArgumentNullException(nameof(type));
+
+ if (type is IReflectableType reflectableType)
+ return reflectableType.GetTypeInfo();
+
+ return new TypeDelegator(type);
+ }
+
+ public static T? FindVisualChildByType(this DependencyObject element) where T : DependencyObject
+ {
+ if (element == null)
+ return null;
+
+ if (element is T elementAsT)
+ return elementAsT;
+
+ int childrenCount = VisualTreeHelper.GetChildrenCount(element);
+ for (int i = 0; i < childrenCount; i++)
+ {
+ var result = VisualTreeHelper.GetChild(element, i).FindVisualChildByType();
+ if (result != null)
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ public static FrameworkElement? FindVisualChildByName(this DependencyObject element, string name)
+ {
+ if (element == null || string.IsNullOrWhiteSpace(name))
+ return null;
+
+ if (element is FrameworkElement elementAsFE && elementAsFE.Name == name)
+ return elementAsFE;
+
+ int childrenCount = VisualTreeHelper.GetChildrenCount(element);
+ for (int i = 0; i < childrenCount; i++)
+ {
+ var result = VisualTreeHelper.GetChild(element, i).FindVisualChildByName(name);
+ if (result != null)
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ public static T? FindVisualParentByType(this DependencyObject element) where T : DependencyObject
+ {
+ if (element is null)
+ return null;
+
+ return element is T elementAsT
+ ? elementAsT
+ : VisualTreeHelper.GetParent(element).FindVisualParentByType();
+ }
+
+ public static FrameworkElement? FindVisualParentByName(this DependencyObject element, string name)
+ {
+ if (element is null || string.IsNullOrWhiteSpace(name))
+ return null;
+
+ if (element is FrameworkElement elementAsFE && elementAsFE.Name == name)
+ return elementAsFE;
+
+ return VisualTreeHelper.GetParent(element).FindVisualParentByName(name);
+ }
+
+ ///
+ /// foreach (UIElement element in navigationViewItem.GetChildren()) { _ = results.Add(element); }
+ ///
+ public static IEnumerable GetChildren(this UIElement parent)
+ {
+ for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
+ {
+ if (VisualTreeHelper.GetChild(parent, i) is UIElement child)
+ {
+ yield return child;
+ }
+ }
+ }
+}
diff --git a/src/SampleApp/MainWindow.xaml b/src/SampleApp/MainWindow.xaml
index b69129e..87654bb 100644
--- a/src/SampleApp/MainWindow.xaml
+++ b/src/SampleApp/MainWindow.xaml
@@ -1,63 +1,147 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SampleApp/MainWindow.xaml.cs b/src/SampleApp/MainWindow.xaml.cs
index e058255..fc74c9c 100644
--- a/src/SampleApp/MainWindow.xaml.cs
+++ b/src/SampleApp/MainWindow.xaml.cs
@@ -1,58 +1,163 @@
-using Microsoft.UI.Xaml;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-
-namespace SampleApp;
-
-public sealed partial class MainWindow : Window
-{
- private readonly List _items = new();
- private readonly ObservableCollection _mountains = new();
-
- public MainWindow()
- {
- InitializeComponent();
- }
-
- private async void OnRootGridLoaded(object sender, RoutedEventArgs e)
- {
- var lines = await File.ReadAllLinesAsync("Assets\\mtns.csv");
-
- foreach (var line in lines)
- {
- var values = line.Split(',');
-
- _items.Add(
- new DataGridDataItem()
- {
- Rank = uint.Parse(values[0]),
- Mountain = values[1],
- Height_m = uint.Parse(values[2]),
- Range = values[3],
- Coordinates = values[4],
- Prominence = uint.Parse(values[5]),
- Parent_mountain = values[6],
- First_ascent = DateTimeOffset.Parse(values[7], CultureInfo.InvariantCulture.DateTimeFormat),
- Ascents = values[8],
- });
- _mountains.Add(values[1]);
- }
-
- tableView.ItemsSource = new ObservableCollection(_items);
- }
-
- private void OnLoadMoreButtonClick(object sender, RoutedEventArgs e)
- {
- var collection = (ObservableCollection)tableView.ItemsSource;
- _items.ForEach(collection.Add);
- }
-
- private void OnClearAndLoadButtonClick(object sender, RoutedEventArgs e)
- {
- tableView.ItemsSource = new ObservableCollection(_items);
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Hosting;
+
+namespace SampleApp;
+
+public sealed partial class MainWindow : Window
+{
+ static bool _loaded = false;
+ readonly List _items = new();
+ readonly ObservableCollection _mountains = new();
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ this.ExtendsContentIntoTitleBar = true;
+ SetTitleBar(CustomTitleBar);
+ CreateGradientBackdrop(root, new System.Numerics.Vector2(0.9f, 1));
+ }
+
+ async void OnRootGridLoaded(object sender, RoutedEventArgs e)
+ {
+ if (!File.Exists("Assets\\mtns.csv"))
+ {
+ await App.ShowDialogBox("Warning", $"The CSV data is missing from this location:{Environment.NewLine}{AppContext.BaseDirectory}", "OK", "", null, null, new Uri($"ms-appx:///Assets/Warning.png"));
+ return;
+ }
+
+ if (App.AnimationsEffectsEnabled)
+ StoryboardSpinner.Begin();
+
+ #region [Load file data]
+ var lines = await File.ReadAllLinesAsync("Assets\\mtns.csv");
+ foreach (var line in lines)
+ {
+ try
+ {
+ var values = line.Split(',');
+ _items.Add(new DataGridDataItem()
+ {
+ Rank = uint.Parse(values[0]),
+ Mountain = values[1],
+ Height_m = uint.Parse(values[2]),
+ Range = values[3],
+ Coordinates = values[4],
+ Prominence = uint.Parse(values[5]),
+ Parent_mountain = values[6],
+ First_ascent = DateTimeOffset.Parse(values[7], CultureInfo.InvariantCulture.DateTimeFormat),
+ Ascents = values[8],
+ });
+ _mountains.Add(values[1]);
+ }
+ catch (Exception) { }
+ }
+ #endregion
+
+ ReloadItems();
+ }
+
+ void OnLoadMoreButtonClick(object sender, RoutedEventArgs e)
+ {
+ var collection = (ObservableCollection)tableView.ItemsSource;
+ _items.ForEach(collection.Add);
+ }
+
+ void OnClearAndLoadButtonClick(object sender, RoutedEventArgs e)
+ {
+ ReloadItems();
+ }
+
+ ///
+ /// We'll need to reload the s to observe the toggle change.
+ ///
+ void SingleClickEditOnToggled(object sender, RoutedEventArgs e)
+ {
+ if (!_loaded)
+ return;
+
+ ReloadItems();
+ }
+
+ void ReloadItems()
+ {
+ // Trying to prevent the stall by spinning this off on another thread.
+ Task.Run(async () =>
+ {
+ await Task.Delay(20); // Allow button/toggle animation to finish.
+ DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
+ {
+ imgSpinner.Visibility = Visibility.Visible;
+ });
+ })
+ .ContinueWith((t) =>
+ {
+ DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, async () =>
+ {
+ tableView.ItemsSource = new ObservableCollection(_items);
+ await Task.Delay(1500);
+ imgSpinner.Visibility = Visibility.Collapsed;
+ });
+ _loaded = true;
+ });
+ }
+
+ void CreateGradientBackdrop(FrameworkElement fe, System.Numerics.Vector2 endPoint)
+ {
+ // Get the FrameworkElement's compositor.
+ var compositor = ElementCompositionPreview.GetElementVisual(fe).Compositor;
+ if (compositor == null) { return; }
+ var gb = compositor.CreateLinearGradientBrush();
+
+ // Define gradient stops.
+ var gradientStops = gb.ColorStops;
+
+ // If we found our App.xaml brushes then use them.
+ if (App.Current.Resources.TryGetValue("GC1", out object clr1) &&
+ App.Current.Resources.TryGetValue("GC2", out object clr2) &&
+ App.Current.Resources.TryGetValue("GC3", out object clr3) &&
+ App.Current.Resources.TryGetValue("GC4", out object clr4))
+ {
+ gradientStops.Insert(0, compositor.CreateColorGradientStop(0.0f, (Windows.UI.Color)clr1));
+ gradientStops.Insert(1, compositor.CreateColorGradientStop(0.5f, (Windows.UI.Color)clr2));
+ gradientStops.Insert(2, compositor.CreateColorGradientStop(0.75f, (Windows.UI.Color)clr3));
+ gradientStops.Insert(3, compositor.CreateColorGradientStop(1.0f, (Windows.UI.Color)clr4));
+ }
+ else
+ {
+ gradientStops.Insert(0, compositor.CreateColorGradientStop(0.0f, Windows.UI.Color.FromArgb(55, 255, 0, 0))); // Red
+ gradientStops.Insert(1, compositor.CreateColorGradientStop(0.3f, Windows.UI.Color.FromArgb(55, 255, 216, 0))); // Yellow
+ gradientStops.Insert(2, compositor.CreateColorGradientStop(0.6f, Windows.UI.Color.FromArgb(55, 0, 255, 0))); // Green
+ gradientStops.Insert(3, compositor.CreateColorGradientStop(1.0f, Windows.UI.Color.FromArgb(55, 0, 0, 255))); // Blue
+ }
+
+ // Set the direction of the gradient.
+ gb.StartPoint = new System.Numerics.Vector2(0, 0);
+ //gb.EndPoint = new System.Numerics.Vector2(1, 1);
+ gb.EndPoint = endPoint;
+
+ // Create a sprite visual and assign the gradient brush.
+ var spriteVisual = Compositor.CreateSpriteVisual();
+ spriteVisual.Brush = gb;
+
+ // Set the size of the sprite visual to cover the entire window.
+ spriteVisual.Size = new System.Numerics.Vector2((float)fe.ActualSize.X, (float)fe.ActualSize.Y);
+
+ // Handle the SizeChanged event to adjust the size of the sprite visual when the window is resized.
+ fe.SizeChanged += (s, e) =>
+ {
+ spriteVisual.Size = new System.Numerics.Vector2((float)fe.ActualWidth, (float)fe.ActualHeight);
+ };
+
+ // Set the sprite visual as the background of the FrameworkElement.
+ ElementCompositionPreview.SetElementChildVisual(fe, spriteVisual);
+ }
+}
diff --git a/src/SampleApp/SampleApp.csproj b/src/SampleApp/SampleApp.csproj
index a882a8d..228c67f 100644
--- a/src/SampleApp/SampleApp.csproj
+++ b/src/SampleApp/SampleApp.csproj
@@ -1,24 +1,80 @@
-
-
- WinExe
- SampleApp
- app.manifest
- true
- true
- None
-
-
-
-
-
-
-
-
-
-
-
-
- Always
-
-
-
+
+
+ WinExe
+ SampleApp
+ app.manifest
+ true
+ true
+ None
+ latest
+ Assets\StoreLogoAlt.ico
+
+ false
+ false
+
+ true
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/WinUI.TableView/CommunityToolkit.WinUI.Collections/VectorChangedEventArgs.cs b/src/WinUI.TableView/CommunityToolkit.WinUI.Collections/VectorChangedEventArgs.cs
index 5283014..f5a51c8 100644
--- a/src/WinUI.TableView/CommunityToolkit.WinUI.Collections/VectorChangedEventArgs.cs
+++ b/src/WinUI.TableView/CommunityToolkit.WinUI.Collections/VectorChangedEventArgs.cs
@@ -1,8 +1,4 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Windows.Foundation.Collections;
+using Windows.Foundation.Collections;
namespace CommunityToolkit.WinUI.Collections;
@@ -10,7 +6,6 @@ namespace CommunityToolkit.WinUI.Collections;
/// Vector changed EventArgs
///
///
-
internal class VectorChangedEventArgs : IVectorChangedEventArgs
{
/// Initializes a new instance of the class.
diff --git a/src/WinUI.TableView/KeyBoardHelper.cs b/src/WinUI.TableView/KeyBoardHelper.cs
index e2fa839..e8d7dd3 100644
--- a/src/WinUI.TableView/KeyBoardHelper.cs
+++ b/src/WinUI.TableView/KeyBoardHelper.cs
@@ -1,4 +1,5 @@
using Microsoft.UI.Input;
+
using Windows.System;
using Windows.UI.Core;
diff --git a/src/WinUI.TableView/TableView.Properties.cs b/src/WinUI.TableView/TableView.Properties.cs
index f7c1147..b41b8bd 100644
--- a/src/WinUI.TableView/TableView.Properties.cs
+++ b/src/WinUI.TableView/TableView.Properties.cs
@@ -24,7 +24,8 @@ public partial class TableView
public static readonly DependencyProperty CanFilterColumnsProperty = DependencyProperty.Register(nameof(CanFilterColumns), typeof(bool), typeof(TableView), new PropertyMetadata(true, OnCanFilterColumnsChanged));
public static readonly DependencyProperty MinColumnWidthProperty = DependencyProperty.Register(nameof(MinColumnWidth), typeof(double), typeof(TableView), new PropertyMetadata(50d, OnMinColumnWidthChanged));
public static readonly DependencyProperty MaxColumnWidthProperty = DependencyProperty.Register(nameof(MaxColumnWidth), typeof(double), typeof(TableView), new PropertyMetadata(double.PositiveInfinity, OnMaxColumnWidthChanged));
- public static readonly DependencyProperty SelectionUnitProperty = DependencyProperty.Register(nameof(SelectionUnit), typeof(TableViewSelectionUnit), typeof(TableView), new PropertyMetadata(TableViewSelectionUnit.CellOrRow, OnSelectionUnitChanged));
+ public static readonly DependencyProperty SelectionUnitProperty = DependencyProperty.Register(nameof(SelectionUnit), typeof(TableViewSelectionUnit), typeof(TableView), new PropertyMetadata(TableViewSelectionUnit.CellOrRow, OnSelectionUnitChanged));
+ public static readonly DependencyProperty SingleClickEditingProperty = DependencyProperty.Register(nameof(SingleClickEditing), typeof(bool), typeof(TableView), new PropertyMetadata(false));
public IAdvancedCollectionView CollectionView { get; private set; } = new AdvancedCollectionView();
internal IDictionary> ActiveFilters { get; } = new Dictionary>();
@@ -69,7 +70,7 @@ public bool ShowExportOptions
{
get => (bool)GetValue(ShowExportOptionsProperty);
set => SetValue(ShowExportOptionsProperty, value);
- }
+ }
public bool AutoGenerateColumns
{
@@ -105,6 +106,12 @@ public bool CanFilterColumns
{
get => (bool)GetValue(CanFilterColumnsProperty);
set => SetValue(CanFilterColumnsProperty, value);
+ }
+
+ public bool SingleClickEditing
+ {
+ get => (bool)GetValue(SingleClickEditingProperty);
+ set => SetValue(SingleClickEditingProperty, value);
}
public double MinColumnWidth
diff --git a/src/WinUI.TableView/TableView.cs b/src/WinUI.TableView/TableView.cs
index 2a9ff6f..51f48f6 100644
--- a/src/WinUI.TableView/TableView.cs
+++ b/src/WinUI.TableView/TableView.cs
@@ -26,12 +26,20 @@
using WinUI.TableView.Extensions;
namespace WinUI.TableView;
+
public partial class TableView : ListView
{
private TableViewHeaderRow? _headerRow;
- private ScrollViewer _scrollViewer = null!;
+ private ScrollViewer? _scrollViewer;
private bool _shouldThrowSelectionModeChangedException;
+ internal event EventHandler? SelectedCellsChanged;
+ internal event EventHandler? CurrentCellChanged;
+ public event EventHandler? AutoGeneratingColumn;
+ public event EventHandler? ExportAllContent;
+ public event EventHandler? ExportSelectedContent;
+ public event EventHandler? CopyToClipboard;
+
public TableView()
{
DefaultStyleKey = typeof(TableView);
@@ -45,7 +53,7 @@ public TableView()
SelectionChanged += TableView_SelectionChanged;
}
- private void TableView_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ void TableView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!KeyBoardHelper.IsCtrlKeyDown())
{
@@ -158,7 +166,43 @@ protected override void OnPreviewKeyDown(KeyRoutedEventArgs e)
}
}
- private bool HandleShortKeys(bool shiftKey, bool ctrlKey, VirtualKey key)
+ protected virtual void OnCopyToClipboard(TableViewCopyToClipboardEventArgs args)
+ {
+ CopyToClipboard?.Invoke(this, args);
+ }
+
+ protected virtual void OnAutoGeneratingColumn(TableViewAutoGeneratingColumnEventArgs e)
+ {
+ AutoGeneratingColumn?.Invoke(this, e);
+ }
+
+ protected virtual void OnExportSelectedContent(TableViewExportContentEventArgs args)
+ {
+ ExportSelectedContent?.Invoke(this, args);
+ }
+
+ protected async override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ _headerRow = GetTemplateChild("HeaderRow") as TableViewHeaderRow;
+ _scrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer;
+
+ if (IsLoaded)
+ {
+ while (ItemsPanelRoot is null) await Task.Delay(1);
+
+ ApplyItemsClip();
+ UpdateVerticalScrollBarMargin();
+ }
+ }
+
+ protected virtual void OnExportAllContent(TableViewExportContentEventArgs args)
+ {
+ ExportAllContent?.Invoke(this, args);
+ }
+
+ bool HandleShortKeys(bool shiftKey, bool ctrlKey, VirtualKey key)
{
if (key == VirtualKey.A && ctrlKey && !shiftKey)
{
@@ -179,38 +223,13 @@ private bool HandleShortKeys(bool shiftKey, bool ctrlKey, VirtualKey key)
return false;
}
- protected override void OnApplyTemplate()
+ void OnLoaded(object sender, RoutedEventArgs e)
{
- base.OnApplyTemplate();
-
- _headerRow = GetTemplateChild("HeaderRow") as TableViewHeaderRow;
- }
-
- private void OnLoaded(object sender, RoutedEventArgs e)
- {
- if (GetTemplateChild("ScrollViewer") is not ScrollViewer scrollViewer)
- {
- return;
- }
-
- _scrollViewer = scrollViewer;
- Canvas.SetZIndex(ItemsPanelRoot, -1);
-
- var scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scrollViewer);
- var compositor = scrollProperties.Compositor;
- var scrollPropSet = scrollProperties.GetSpecializedReference();
- var itemsPanelVisual = ElementCompositionPreview.GetElementVisual(ItemsPanelRoot);
- var contentClip = compositor.CreateInsetClip();
- var expressionClipAnimation = ExpressionFunctions.Max(-scrollPropSet.Translation.Y, 0);
-
- itemsPanelVisual.Clip = contentClip;
- contentClip.TopInset = (float)Math.Max(-scrollViewer.VerticalOffset, 0);
- contentClip.StartAnimation("TopInset", expressionClipAnimation);
-
+ ApplyItemsClip();
UpdateVerticalScrollBarMargin();
}
- private TableViewCellSlot GetNextSlot(TableViewCellSlot? currentSlot, bool isShiftKeyDown, bool isEnterKey)
+ TableViewCellSlot GetNextSlot(TableViewCellSlot? currentSlot, bool isShiftKeyDown, bool isEnterKey)
{
var rows = Items.Count;
var columns = Columns.VisibleColumns.Count;
@@ -251,7 +270,7 @@ private TableViewCellSlot GetNextSlot(TableViewCellSlot? currentSlot, bool isShi
return new TableViewCellSlot(nextRow, nextColumn);
}
- private bool Filter(object obj)
+ bool Filter(object obj)
{
return ActiveFilters.All(item => item.Value(obj));
}
@@ -271,11 +290,6 @@ internal void CopyToClipboardInternal(bool includeHeaders)
Clipboard.SetContent(package);
}
- protected virtual void OnCopyToClipboard(TableViewCopyToClipboardEventArgs args)
- {
- CopyToClipboard?.Invoke(this, args);
- }
-
public string GetSelectedContent(bool includeHeaders, char separator = '\t')
{
var slots = Enumerable.Empty();
@@ -308,15 +322,13 @@ public string GetAllContent(bool includeHeaders, char separator = '\t')
return GetCellsContent(slots, includeHeaders, separator);
}
- private string GetCellsContent(IEnumerable slots, bool includeHeaders, char separator)
+ string GetCellsContent(IEnumerable slots, bool includeHeaders, char separator)
{
if (!slots.Any())
{
return string.Empty;
}
- var minRow = slots.Select(x => x.Row).Min();
- var maxRow = slots.Select(x => x.Row).Max();
var minColumn = slots.Select(x => x.Column).Min();
var maxColumn = slots.Select(x => x.Column).Max();
@@ -328,7 +340,7 @@ private string GetCellsContent(IEnumerable slots, bool includ
stringBuilder.AppendLine(GetHeadersContent(separator, minColumn, maxColumn));
}
- for (var row = minRow; row <= maxRow; row++)
+ foreach (var row in slots.Select(x => x.Row).Distinct())
{
var item = Items[row];
var type = ItemsSource?.GetType() is { } listType && listType.IsGenericType ? listType.GetGenericArguments()[0] : item?.GetType();
@@ -338,7 +350,7 @@ private string GetCellsContent(IEnumerable slots, bool includ
if (Columns.VisibleColumns[col] is not TableViewBoundColumn column ||
!slots.Contains(new TableViewCellSlot(row, col)))
{
- stringBuilder.Append('\t');
+ stringBuilder.Append(separator);
continue;
}
@@ -361,7 +373,7 @@ private string GetCellsContent(IEnumerable slots, bool includ
return stringBuilder.ToString();
}
- private string GetHeadersContent(char separator, int minColumn, int maxColumn)
+ string GetHeadersContent(char separator, int minColumn, int maxColumn)
{
var stringBuilder = new StringBuilder();
for (var col = minColumn; col <= maxColumn; col++)
@@ -373,7 +385,7 @@ private string GetHeadersContent(char separator, int minColumn, int maxColumn)
return stringBuilder.ToString();
}
- private void GenerateColumns()
+ void GenerateColumns()
{
var itemsSourceType = ItemsSource?.GetType();
if (itemsSourceType?.IsGenericType == true)
@@ -383,10 +395,7 @@ private void GenerateColumns()
{
var displayAttribute = propertyInfo.GetCustomAttributes().OfType().FirstOrDefault();
var autoGenerateField = displayAttribute?.GetAutoGenerateField();
- if (autoGenerateField == false)
- {
- continue;
- }
+ if (autoGenerateField == false) { continue; }
var header = displayAttribute?.GetShortName() ?? propertyInfo.Name;
var canFilter = displayAttribute?.GetAutoGenerateFilter() ?? true;
@@ -401,7 +410,7 @@ private void GenerateColumns()
}
}
- private static TableViewAutoGeneratingColumnEventArgs GenerateColumn(Type propertyType, string propertyName, string header, bool canFilter)
+ static TableViewAutoGeneratingColumnEventArgs GenerateColumn(Type propertyType, string propertyName, string header, bool canFilter)
{
var newColumn = GetTableViewColumnFromType(propertyType);
newColumn.Header = header;
@@ -411,29 +420,30 @@ private static TableViewAutoGeneratingColumnEventArgs GenerateColumn(Type proper
return new TableViewAutoGeneratingColumnEventArgs(propertyName, propertyType, newColumn);
}
- protected virtual void OnAutoGeneratingColumn(TableViewAutoGeneratingColumnEventArgs e)
+ static TableViewBoundColumn GetTableViewColumnFromType(Type type)
{
- AutoGeneratingColumn?.Invoke(this, e);
- }
-
- private static TableViewBoundColumn GetTableViewColumnFromType(Type type)
- {
- if (type == typeof(bool) || type == typeof(bool?))
+ switch (Type.GetTypeCode(type))
{
- return new TableViewCheckBoxColumn();
+ case TypeCode.Byte:
+ case TypeCode.SByte:
+ case TypeCode.UInt16:
+ case TypeCode.UInt32:
+ case TypeCode.UInt64:
+ case TypeCode.Int16:
+ case TypeCode.Int32:
+ case TypeCode.Int64:
+ case TypeCode.Single:
+ case TypeCode.Double:
+ case TypeCode.Decimal:
+ return new TableViewNumberColumn();
+ case TypeCode.Boolean:
+ return new TableViewCheckBoxColumn();
+ default:
+ return new TableViewTextColumn();
}
- else if (type == typeof(int) || type == typeof(int?) ||
- type == typeof(double) || type == typeof(double?) ||
- type == typeof(float) || type == typeof(float?) ||
- type == typeof(decimal) || type == typeof(decimal?))
- {
- return new TableViewNumberColumn();
- }
-
- return new TableViewTextColumn();
}
- private void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
+ void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
{
((AdvancedCollectionView)CollectionView).Source = null!;
@@ -449,7 +459,7 @@ private void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
}
}
- private void RemoveAutoGeneratedColumns()
+ void RemoveAutoGeneratedColumns()
{
while (Columns.Any(x => x.IsAutoGenerated))
{
@@ -486,11 +496,6 @@ internal async void ExportSelectedToCSV()
catch { }
}
- protected virtual void OnExportSelectedContent(TableViewExportContentEventArgs args)
- {
- ExportSelectedContent?.Invoke(this, args);
- }
-
internal async void ExportAllToCSV()
{
var args = new TableViewExportContentEventArgs();
@@ -519,12 +524,7 @@ internal async void ExportAllToCSV()
catch { }
}
- protected virtual void OnExportAllContent(TableViewExportContentEventArgs args)
- {
- ExportAllContent?.Invoke(this, args);
- }
-
- private static async Task GetStorageFile(IntPtr hWnd)
+ static async Task GetStorageFile(IntPtr hWnd)
{
var savePicker = new FileSavePicker();
InitializeWithWindow.Initialize(savePicker, hWnd);
@@ -533,7 +533,25 @@ private static async Task GetStorageFile(IntPtr hWnd)
return await savePicker.PickSaveFileAsync();
}
- private void UpdateVerticalScrollBarMargin()
+ void ApplyItemsClip()
+ {
+ if (_scrollViewer is null || ItemsPanelRoot is null) return;
+
+ Canvas.SetZIndex(ItemsPanelRoot, -1);
+
+ var scrollProperties = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollViewer);
+ var compositor = scrollProperties.Compositor;
+ var scrollPropSet = scrollProperties.GetSpecializedReference();
+ var itemsPanelVisual = ElementCompositionPreview.GetElementVisual(ItemsPanelRoot);
+ var contentClip = compositor.CreateInsetClip();
+ var expressionClipAnimation = ExpressionFunctions.Max(-scrollPropSet.Translation.Y, 0);
+
+ itemsPanelVisual.Clip = contentClip;
+ contentClip.TopInset = (float)Math.Max(-_scrollViewer.VerticalOffset, 0);
+ contentClip.StartAnimation("TopInset", expressionClipAnimation);
+ }
+
+ void UpdateVerticalScrollBarMargin()
{
if (GetTemplateChild("ScrollViewer") is ScrollViewer scrollViewer)
{
@@ -574,7 +592,7 @@ internal void ClearFilters()
}
}
- private bool IsValidSlot(TableViewCellSlot slot)
+ bool IsValidSlot(TableViewCellSlot slot)
{
return slot.Row >= 0 && slot.Column >= 0 && slot.Row < Items.Count && slot.Column < Columns.VisibleColumns.Count;
}
@@ -606,7 +624,7 @@ private bool IsValidSlot(TableViewCellSlot slot)
}
}
- private void SelectAllCells()
+ void SelectAllCells()
{
switch (SelectionMode)
{
@@ -642,7 +660,7 @@ internal void DeselectAll()
DeselectAllCells();
}
- private void DeselectAllItems()
+ void DeselectAllItems()
{
switch (SelectionMode)
{
@@ -656,7 +674,7 @@ private void DeselectAllItems()
}
}
- private void DeselectAllCells()
+ void DeselectAllCells()
{
SelectedCellRanges.Clear();
OnCellSelectionChanged();
@@ -760,7 +778,7 @@ internal void SetCurrentCell(TableViewCellSlot? slot)
CurrentCellChanged?.Invoke(this, new TableViewCurrentCellChangedEventArgs(oldSlot, slot));
}
- private void OnCellSelectionChanged()
+ void OnCellSelectionChanged()
{
var oldSelection = SelectedCells;
SelectedCells = new HashSet(SelectedCellRanges.SelectMany(x => x));
@@ -769,6 +787,8 @@ private void OnCellSelectionChanged()
internal async Task ScrollCellIntoView(TableViewCellSlot slot)
{
+ if (_scrollViewer is null) return default!;
+
var row = await ScrollRowIntoView(slot.Row);
var (start, end) = GetColumnsInDisplay();
var xOffset = 0d;
@@ -823,8 +843,10 @@ void ViewChanged(object? _, ScrollViewerViewChangedEventArgs e)
return row?.Cells.ElementAt(slot.Column)!;
}
- private async Task ScrollRowIntoView(int index)
+ async Task ScrollRowIntoView(int index)
{
+ if (_scrollViewer is null) return default!;
+
var item = Items[index];
index = Items.IndexOf(item); // if the ItemsSource has duplicate items in it. ScrollIntoView will only bring first index of item.
ScrollIntoView(item);
@@ -836,7 +858,7 @@ void ViewChanged(object? _, ScrollViewerViewChangedEventArgs e)
{
var transform = row.TransformToVisual(_scrollViewer);
var positionInScrollViewer = transform.TransformPoint(new Point(0, 0));
- if ((index == 0 && _scrollViewer.VerticalOffset > 0) || (index > 0 && positionInScrollViewer.Y <= row.ActualHeight))
+ if ((index == 0 && _scrollViewer.VerticalOffset > 0) || (index > 0 && positionInScrollViewer.Y < HeaderRowHeight))
{
var xOffset = _scrollViewer.HorizontalOffset;
var yOffset = index == 0 ? 0d : _scrollViewer.VerticalOffset - row.ActualHeight + positionInScrollViewer.Y + 8;
@@ -880,8 +902,10 @@ internal TableViewCell GetCellFromSlot(TableViewCellSlot slot)
return ContainerFromIndex(slot.Row) is TableViewRow row ? row.Cells[slot.Column] : default!;
}
- private (int start, int end) GetColumnsInDisplay()
+ (int start, int end) GetColumnsInDisplay()
{
+ if (_scrollViewer is null) return default!;
+
var start = -1;
var end = -1;
var width = 0d;
@@ -905,17 +929,10 @@ internal TableViewCell GetCellFromSlot(TableViewCellSlot slot)
return (start, end);
}
- private void UpdateBaseSelectionMode()
+ void UpdateBaseSelectionMode()
{
_shouldThrowSelectionModeChangedException = true;
base.SelectionMode = SelectionUnit is TableViewSelectionUnit.Cell ? ListViewSelectionMode.None : SelectionMode;
_shouldThrowSelectionModeChangedException = false;
}
-
- public event EventHandler? AutoGeneratingColumn;
- public event EventHandler? ExportAllContent;
- public event EventHandler? ExportSelectedContent;
- public event EventHandler? CopyToClipboard;
- internal event EventHandler? SelectedCellsChanged;
- internal event EventHandler? CurrentCellChanged;
}
diff --git a/src/WinUI.TableView/TableViewBoundColumn.cs b/src/WinUI.TableView/TableViewBoundColumn.cs
index ded9894..010b2c0 100644
--- a/src/WinUI.TableView/TableViewBoundColumn.cs
+++ b/src/WinUI.TableView/TableViewBoundColumn.cs
@@ -30,8 +30,8 @@ public bool CanFilter
{
get => (bool)GetValue(CanFilterProperty);
set => SetValue(CanFilterProperty, value);
- }
-
+ }
+
public static readonly DependencyProperty CanSortProperty = DependencyProperty.Register(nameof(CanSort), typeof(bool), typeof(TableViewBoundColumn), new PropertyMetadata(true));
- public static readonly DependencyProperty CanFilterProperty = DependencyProperty.Register(nameof(CanFilter), typeof(bool), typeof(TableViewBoundColumn), new PropertyMetadata(true));
+ public static readonly DependencyProperty CanFilterProperty = DependencyProperty.Register(nameof(CanFilter), typeof(bool), typeof(TableViewBoundColumn), new PropertyMetadata(true));
}
diff --git a/src/WinUI.TableView/TableViewCell.cs b/src/WinUI.TableView/TableViewCell.cs
index c8d3724..ac7c282 100644
--- a/src/WinUI.TableView/TableViewCell.cs
+++ b/src/WinUI.TableView/TableViewCell.cs
@@ -1,228 +1,239 @@
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Input;
-using System;
-using System.Threading.Tasks;
-using Windows.Foundation;
-
-namespace WinUI.TableView;
-
-[TemplateVisualState(Name = VisualStates.StateNormal, GroupName = VisualStates.GroupCommon)]
-[TemplateVisualState(Name = VisualStates.StatePointerOver, GroupName = VisualStates.GroupCommon)]
-[TemplateVisualState(Name = VisualStates.StateRegular, GroupName = VisualStates.GroupCurrent)]
-[TemplateVisualState(Name = VisualStates.StateCurrent, GroupName = VisualStates.GroupCurrent)]
-[TemplateVisualState(Name = VisualStates.StateSelected, GroupName = VisualStates.GroupSelection)]
-[TemplateVisualState(Name = VisualStates.StateUnselected, GroupName = VisualStates.GroupSelection)]
-public class TableViewCell : ContentControl
-{
- public TableViewCell()
- {
- DefaultStyleKey = typeof(TableViewCell);
- Loaded += OnLoaded;
- }
-
- private void OnLoaded(object sender, RoutedEventArgs e)
- {
- ApplySelectionState();
- }
-
- protected override Size MeasureOverride(Size availableSize)
- {
- var size = base.MeasureOverride(availableSize);
-
- if ((Content ?? ContentTemplateRoot) is FrameworkElement element)
- {
- var contentWidth = Column.ActualWidth;
- contentWidth -= element.Margin.Left;
- contentWidth -= element.Margin.Right;
- contentWidth -= Padding.Left;
- contentWidth -= Padding.Right;
- contentWidth -= BorderThickness.Left;
- contentWidth -= BorderThickness.Right;
-
- element.MaxWidth = contentWidth;
-
- if (Column is not null)
- {
- var desiredWidth = element.DesiredSize.Width;
- desiredWidth += Padding.Left;
- desiredWidth += Padding.Right;
- desiredWidth += BorderThickness.Left;
- desiredWidth += BorderThickness.Right;
-
- Column.DesiredWidth = Math.Max(Column.DesiredWidth, desiredWidth);
- }
- }
-
- return size;
- }
-
- protected override void OnPointerEntered(PointerRoutedEventArgs e)
- {
- base.OnPointerEntered(e);
-
- VisualStates.GoToState(this, false, VisualStates.StatePointerOver);
- }
-
- protected override void OnPointerExited(PointerRoutedEventArgs e)
- {
- base.OnPointerEntered(e);
-
- VisualStates.GoToState(this, false, VisualStates.StateNormal);
- }
-
- protected override void OnTapped(TappedRoutedEventArgs e)
- {
- base.OnTapped(e);
-
- if (TableView.IsEditing && TableView.CurrentCellSlot == Slot)
- {
- return;
- }
-
- var shiftKey = KeyBoardHelper.IsShiftKeyDown();
- var ctrlKey = KeyBoardHelper.IsCtrlKeyDown();
-
- if (IsSelected && (ctrlKey || TableView.SelectionMode is ListViewSelectionMode.Multiple) && !shiftKey)
- {
- TableView.DeselectCell(Slot);
- }
- else
- {
- TableView.IsEditing = false;
- TableView.SelectCells(Slot, shiftKey, ctrlKey);
- }
-
- Focus(FocusState.Programmatic);
- }
-
- protected override void OnPointerPressed(PointerRoutedEventArgs e)
- {
- base.OnPointerPressed(e);
-
- if (!KeyBoardHelper.IsShiftKeyDown())
- {
- TableView.SelectionStartCellSlot = Slot;
- }
- }
-
- protected override void OnPointerReleased(PointerRoutedEventArgs e)
- {
- base.OnPointerReleased(e);
-
- if (!KeyBoardHelper.IsShiftKeyDown())
- {
- TableView.SelectionStartCellSlot = null;
- }
-
- e.Handled = TableView.SelectionUnit != TableViewSelectionUnit.Row;
- }
-
- protected override void OnPointerMoved(PointerRoutedEventArgs e)
- {
- base.OnPointerMoved(e);
-
- var point = e.GetCurrentPoint(this);
-
- if (point.Properties.IsLeftButtonPressed && !TableView.IsEditing)
- {
- var ctrlKey = KeyBoardHelper.IsCtrlKeyDown();
-
- TableView.SelectCells(Slot, true, ctrlKey);
- }
- }
-
- protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs e)
- {
- if (!IsReadOnly)
- {
- PrepareForEdit();
-
- TableView.IsEditing = true;
- }
- }
-
- internal async void PrepareForEdit()
- {
- SetEditingElement();
-
- await Task.Delay(20);
-
- if ((Content ?? ContentTemplateRoot) is UIElement editingElement)
- {
- editingElement.Focus(FocusState.Programmatic);
- }
- }
-
- internal void SetElement()
- {
- Content = Column.GenerateElement();
- }
-
- private void SetEditingElement()
- {
- Content = Column.GenerateEditingElement();
- if (TableView is not null)
- {
- TableView.IsEditing = true;
- }
- }
-
- internal void ApplySelectionState()
- {
- var stateName = IsSelected ? VisualStates.StateSelected : VisualStates.StateUnselected;
- VisualStates.GoToState(this, false, stateName);
- }
-
- internal void ApplyCurrentCellState()
- {
- var stateName = IsCurrent ? VisualStates.StateCurrent : VisualStates.StateRegular;
- VisualStates.GoToState(this, false, stateName);
- }
-
- private static void OnColumnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- if (d is TableViewCell cell)
- {
- if (cell.TableView?.IsEditing == true)
- {
- cell.SetEditingElement();
- }
- else
- {
- cell.SetElement();
- }
- }
- }
-
- public bool IsReadOnly => TableView.IsReadOnly || Column is TableViewTemplateColumn { EditingTemplate: null } or { IsReadOnly: true };
-
- internal TableViewCellSlot Slot => new(Row.Index, Index);
-
- internal int Index { get; set; }
-
- public bool IsSelected => TableView.SelectedCells.Contains(Slot);
- public bool IsCurrent => TableView.CurrentCellSlot == Slot;
-
- public TableViewColumn Column
- {
- get => (TableViewColumn)GetValue(ColumnProperty);
- set => SetValue(ColumnProperty, value);
- }
-
- public TableViewRow Row
- {
- get => (TableViewRow)GetValue(TableViewRowProperty);
- set => SetValue(TableViewRowProperty, value);
- }
-
- public TableView TableView
- {
- get => (TableView)GetValue(TableViewProperty);
- set => SetValue(TableViewProperty, value);
- }
-
- public static readonly DependencyProperty ColumnProperty = DependencyProperty.Register(nameof(Column), typeof(TableViewColumn), typeof(TableViewCell), new PropertyMetadata(default, OnColumnChanged));
- public static readonly DependencyProperty TableViewRowProperty = DependencyProperty.Register(nameof(Row), typeof(TableViewRow), typeof(TableViewCell), new PropertyMetadata(default));
- public static readonly DependencyProperty TableViewProperty = DependencyProperty.Register(nameof(TableView), typeof(TableView), typeof(TableViewCell), new PropertyMetadata(default));
-}
+using System;
+using System.Threading.Tasks;
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Input;
+
+using Windows.Foundation;
+
+namespace WinUI.TableView;
+
+[TemplateVisualState(Name = VisualStates.StateNormal, GroupName = VisualStates.GroupCommon)]
+[TemplateVisualState(Name = VisualStates.StatePointerOver, GroupName = VisualStates.GroupCommon)]
+[TemplateVisualState(Name = VisualStates.StateRegular, GroupName = VisualStates.GroupCurrent)]
+[TemplateVisualState(Name = VisualStates.StateCurrent, GroupName = VisualStates.GroupCurrent)]
+[TemplateVisualState(Name = VisualStates.StateSelected, GroupName = VisualStates.GroupSelection)]
+[TemplateVisualState(Name = VisualStates.StateUnselected, GroupName = VisualStates.GroupSelection)]
+public class TableViewCell : ContentControl
+{
+ #region [My changes - Aug 3, 2024]
+ static bool SingleClickEditing { get; set; } = false;
+ #endregion
+
+ public TableViewCell()
+ {
+ DefaultStyleKey = typeof(TableViewCell);
+ Loaded += OnLoaded;
+ }
+
+ #region [Private]
+ internal TableViewCellSlot Slot => new(Row.Index, Index);
+ internal int Index { get; set; }
+
+ void OnLoaded(object sender, RoutedEventArgs e)
+ {
+ ApplySelectionState();
+ #region [My changes - Aug 3, 2024]
+ if (TableView is not null)
+ { // Detect editing mode set from TableView XAML.
+ SingleClickEditing = TableView.SingleClickEditing;
+ }
+ #endregion
+ }
+
+ void SetEditingElement()
+ {
+ Content = Column.GenerateEditingElement();
+ if (TableView is not null)
+ {
+ TableView.IsEditing = true;
+ }
+ }
+
+ internal void ApplySelectionState()
+ {
+ var stateName = IsSelected ? VisualStates.StateSelected : VisualStates.StateUnselected;
+ VisualStates.GoToState(this, false, stateName);
+ }
+
+ internal void ApplyCurrentCellState()
+ {
+ var stateName = IsCurrent ? VisualStates.StateCurrent : VisualStates.StateRegular;
+ VisualStates.GoToState(this, false, stateName);
+ }
+
+ static void OnColumnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is TableViewCell cell)
+ {
+ if (cell.TableView?.IsEditing == true)
+ cell.SetEditingElement();
+ else
+ cell.SetElement();
+ }
+ }
+
+ internal async void PrepareForEdit()
+ {
+ SetEditingElement();
+ await Task.Delay(20);
+ if ((Content ?? ContentTemplateRoot) is UIElement editingElement)
+ {
+ editingElement.Focus(FocusState.Programmatic);
+ if (editingElement is TextBox tb && !string.IsNullOrEmpty(tb.Text))
+ tb.SelectAll();
+ }
+ }
+
+ internal void SetElement()
+ {
+ Content = Column.GenerateElement();
+ }
+ #endregion
+
+ #region [Protected]
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ var size = base.MeasureOverride(availableSize);
+
+ if ((Content ?? ContentTemplateRoot) is FrameworkElement element)
+ {
+ var contentWidth = Column.ActualWidth;
+ contentWidth -= element.Margin.Left;
+ contentWidth -= element.Margin.Right;
+ contentWidth -= Padding.Left;
+ contentWidth -= Padding.Right;
+ contentWidth -= BorderThickness.Left;
+ contentWidth -= BorderThickness.Right;
+
+ element.MaxWidth = contentWidth;
+
+ if (Column is not null)
+ {
+ var desiredWidth = element.DesiredSize.Width;
+ desiredWidth += Padding.Left;
+ desiredWidth += Padding.Right;
+ desiredWidth += BorderThickness.Left;
+ desiredWidth += BorderThickness.Right;
+
+ Column.DesiredWidth = Math.Max(Column.DesiredWidth, desiredWidth);
+ }
+ }
+
+ return size;
+ }
+
+ protected override void OnPointerEntered(PointerRoutedEventArgs e)
+ {
+ base.OnPointerEntered(e);
+
+ VisualStates.GoToState(this, false, VisualStates.StatePointerOver);
+ }
+
+ protected override void OnPointerExited(PointerRoutedEventArgs e)
+ {
+ base.OnPointerEntered(e);
+ VisualStates.GoToState(this, false, VisualStates.StateNormal);
+ }
+
+ protected override void OnTapped(TappedRoutedEventArgs e)
+ {
+ base.OnTapped(e);
+
+ if (TableView.IsEditing && TableView.CurrentCellSlot == Slot)
+ {
+ return;
+ }
+
+ var shiftKey = KeyBoardHelper.IsShiftKeyDown();
+ var ctrlKey = KeyBoardHelper.IsCtrlKeyDown();
+
+ if (IsSelected && (ctrlKey || TableView.SelectionMode is ListViewSelectionMode.Multiple) && !shiftKey)
+ {
+ TableView.DeselectCell(Slot);
+ }
+ else
+ {
+ TableView.IsEditing = false;
+ TableView.SelectCells(Slot, shiftKey, ctrlKey);
+ }
+
+ Focus(FocusState.Programmatic);
+
+ #region [My changes - Aug 3, 2024]
+ if (!TableView.IsEditing && SingleClickEditing)
+ {
+ OnDoubleTapped(new DoubleTappedRoutedEventArgs());
+ }
+ #endregion
+ }
+
+ protected override void OnPointerPressed(PointerRoutedEventArgs e)
+ {
+ base.OnPointerPressed(e);
+ if (!KeyBoardHelper.IsShiftKeyDown())
+ {
+ TableView.SelectionStartCellSlot = Slot;
+ }
+ }
+
+ protected override void OnPointerReleased(PointerRoutedEventArgs e)
+ {
+ base.OnPointerReleased(e);
+ if (!KeyBoardHelper.IsShiftKeyDown())
+ {
+ TableView.SelectionStartCellSlot = null;
+ }
+ e.Handled = TableView.SelectionUnit != TableViewSelectionUnit.Row;
+ }
+
+ protected override void OnPointerMoved(PointerRoutedEventArgs e)
+ {
+ base.OnPointerMoved(e);
+ var point = e.GetCurrentPoint(this);
+ if (point.Properties.IsLeftButtonPressed && !TableView.IsEditing)
+ {
+ var ctrlKey = KeyBoardHelper.IsCtrlKeyDown();
+ TableView.SelectCells(Slot, true, ctrlKey);
+ }
+ }
+
+ protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs e)
+ {
+ if (!IsReadOnly)
+ {
+ PrepareForEdit();
+ TableView.IsEditing = true;
+ }
+ }
+ #endregion
+
+ #region [Public]
+ public bool IsReadOnly => TableView.IsReadOnly || Column is TableViewTemplateColumn { EditingTemplate: null } or { IsReadOnly: true };
+ public bool IsSelected => TableView.SelectedCells.Contains(Slot);
+ public bool IsCurrent => TableView.CurrentCellSlot == Slot;
+
+ public TableViewColumn Column
+ {
+ get => (TableViewColumn)GetValue(ColumnProperty);
+ set => SetValue(ColumnProperty, value);
+ }
+
+ public TableViewRow Row
+ {
+ get => (TableViewRow)GetValue(TableViewRowProperty);
+ set => SetValue(TableViewRowProperty, value);
+ }
+
+ public TableView TableView
+ {
+ get => (TableView)GetValue(TableViewProperty);
+ set => SetValue(TableViewProperty, value);
+ }
+
+ public static readonly DependencyProperty ColumnProperty = DependencyProperty.Register(nameof(Column), typeof(TableViewColumn), typeof(TableViewCell), new PropertyMetadata(default, OnColumnChanged));
+ public static readonly DependencyProperty TableViewRowProperty = DependencyProperty.Register(nameof(Row), typeof(TableViewRow), typeof(TableViewCell), new PropertyMetadata(default));
+ public static readonly DependencyProperty TableViewProperty = DependencyProperty.Register(nameof(TableView), typeof(TableView), typeof(TableViewCell), new PropertyMetadata(default));
+ #endregion
+}
diff --git a/src/WinUI.TableView/TableViewCellSlot.cs b/src/WinUI.TableView/TableViewCellSlot.cs
index fc09909..c26125f 100644
--- a/src/WinUI.TableView/TableViewCellSlot.cs
+++ b/src/WinUI.TableView/TableViewCellSlot.cs
@@ -1,2 +1,3 @@
namespace WinUI.TableView;
+
internal readonly record struct TableViewCellSlot(int Row, int Column);
diff --git a/src/WinUI.TableView/Themes/Generic.xaml b/src/WinUI.TableView/Themes/Generic.xaml
index 2cc2cd5..e55530b 100644
--- a/src/WinUI.TableView/Themes/Generic.xaml
+++ b/src/WinUI.TableView/Themes/Generic.xaml
@@ -1,31 +1,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/WinUI.TableView/Themes/TableView.xaml b/src/WinUI.TableView/Themes/TableView.xaml
index f16c95c..6759b8d 100644
--- a/src/WinUI.TableView/Themes/TableView.xaml
+++ b/src/WinUI.TableView/Themes/TableView.xaml
@@ -1,110 +1,95 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/WinUI.TableView/Themes/TableViewCell.xaml b/src/WinUI.TableView/Themes/TableViewCell.xaml
index 7a67abf..ca9e695 100644
--- a/src/WinUI.TableView/Themes/TableViewCell.xaml
+++ b/src/WinUI.TableView/Themes/TableViewCell.xaml
@@ -1,81 +1,72 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/src/WinUI.TableView/Themes/TableViewHeaderRow.xaml b/src/WinUI.TableView/Themes/TableViewHeaderRow.xaml
index 1c898bd..34a7c9c 100644
--- a/src/WinUI.TableView/Themes/TableViewHeaderRow.xaml
+++ b/src/WinUI.TableView/Themes/TableViewHeaderRow.xaml
@@ -1,169 +1,162 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/WinUI.TableView/Themes/TableViewRow.xaml b/src/WinUI.TableView/Themes/TableViewRow.xaml
index 08f6680..67bb963 100644
--- a/src/WinUI.TableView/Themes/TableViewRow.xaml
+++ b/src/WinUI.TableView/Themes/TableViewRow.xaml
@@ -1,75 +1,75 @@
-
-
-
-
-
+
+
+
+
+