From e8469c1851a75a03e8ae2d7958017146bc13e1b8 Mon Sep 17 00:00:00 2001 From: Violet Hansen Date: Fri, 28 Feb 2025 15:08:08 +0200 Subject: [PATCH] Added new Setting behavior to center list views automatically (#628) This is a new option in the app settings that is off by default. It is an accessibility setting and also makes it easier to interact with the List Views across the app. Works with touch, mouse and keyboard. It is added under a new settings category called "Behavior". More customizations will be added under this category in the future. This option, just like others, is remembered between app sessons so you only need to configure it once. --- .../AppSettings/AppSettingsCls.cs | 3 +- .../Others/ListViewUIHelpers.cs | 220 +++++++++++++++++- .../AllowNewAppsEventLogsDataGrid.xaml | 1 + .../AllowNewAppsEventLogsDataGrid.xaml.cs | 20 ++ .../AllowNewAppsLocalFilesDataGrid.xaml | 1 + .../AllowNewAppsLocalFilesDataGrid.xaml.cs | 18 ++ ...eDenyPolicyFilesAndFoldersScanResults.xaml | 1 + ...nyPolicyFilesAndFoldersScanResults.xaml.cs | 18 ++ ...entalPolicyFilesAndFoldersScanResults.xaml | 1 + ...alPolicyFilesAndFoldersScanResults.xaml.cs | 20 +- .../Pages/EventLogsPolicyCreation.xaml | 1 + .../Pages/EventLogsPolicyCreation.xaml.cs | 17 ++ .../Pages/MDEAHPolicyCreation.xaml | 1 + .../Pages/MDEAHPolicyCreation.xaml.cs | 18 ++ AppControl Manager/Pages/Settings.xaml | 10 + AppControl Manager/Pages/Settings.xaml.cs | 25 +- AppControl Manager/Pages/Simulation.xaml | 1 + AppControl Manager/Pages/Simulation.xaml.cs | 18 ++ .../Pages/StrictKernelPolicyScanResults.xaml | 1 + .../StrictKernelPolicyScanResults.xaml.cs | 17 ++ .../ViewCurrentPolicies.xaml.cs | 17 +- .../Pages/ViewFileCertificates.xaml | 1 + .../Pages/ViewFileCertificates.xaml.cs | 18 ++ .../Strings/en-US/Resources.resw | 18 ++ 24 files changed, 461 insertions(+), 5 deletions(-) diff --git a/AppControl Manager/AppSettings/AppSettingsCls.cs b/AppControl Manager/AppSettings/AppSettingsCls.cs index d2e5d7d4d..b4ca49e01 100644 --- a/AppControl Manager/AppSettings/AppSettingsCls.cs +++ b/AppControl Manager/AppSettings/AppSettingsCls.cs @@ -81,6 +81,7 @@ internal enum SettingKeys MainWindowHeight, MainWindowIsMaximized, AutomaticAssignmentSidebar, - AutoCheckForUpdateAtStartup + AutoCheckForUpdateAtStartup, + ListViewsVerticalCentering } } diff --git a/AppControl Manager/Others/ListViewUIHelpers.cs b/AppControl Manager/Others/ListViewUIHelpers.cs index 25df407f9..c3039c667 100644 --- a/AppControl Manager/Others/ListViewUIHelpers.cs +++ b/AppControl Manager/Others/ListViewUIHelpers.cs @@ -1,12 +1,22 @@ -using System.Text; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; using AppControlManager.IntelGathering; +using CommunityToolkit.WinUI; using Microsoft.UI.Text; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Media; using Windows.Foundation; +using static AppControlManager.AppSettings.AppSettingsCls; namespace AppControlManager.Others; +/// +/// This class includes methods that are helpers for the custom ListView implementations in this application. +/// internal static class ListViewUIHelpers { // An offscreen TextBlock for measurement @@ -64,4 +74,212 @@ internal static string ConvertRowToText(FileIdentity row) .AppendLine($"Opus Data: {row.Opus}") .ToString(); } + + + /* + + Windows Community Toolkit + + Copyright © .NET Foundation and Contributors + + All rights reserved. + + MIT License (MIT) + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + + // This is a modification of the methods in Windows Community Toolkit, ListViewExtensions, Smooth Scroll Into View feature that only has the center vertically code plus some additional logic + // https://github.com/CommunityToolkit/Windows/pull/648 + + + private static readonly Dictionary ObjRemovalTracking = []; + + /// + /// Smooth scrolling the list to bring the specified index into view, centering vertically + /// + /// List to scroll + /// The index to bring into view. Index can be negative. + /// Set true to disable animation + /// Set false to disable scrolling when the corresponding item is in view + /// Adds additional horizontal offset + /// Adds additional vertical offset + /// Returns that completes after scrolling + internal static async Task SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(this ListViewBase listViewBase, ListView listView, int index, bool disableAnimation = false, bool scrollIfVisible = true, int additionalHorizontalOffset = 0, int additionalVerticalOffset = 0) + { + + // Only perform the scroll if the setting is enabled + if (!GetSetting(SettingKeys.ListViewsVerticalCentering)) + { + return; + } + + // Don't center if an item was deleted + // Without this step, after row deletion in ListView, the data jumps up/down in a weird way + if (!ObjRemovalTracking.TryGetValue(listView, out int value)) + { + ObjRemovalTracking.Add(listView, ((IList)listView.ItemsSource).Count); + } + + if (value != ((IList)listView.ItemsSource).Count) + { + ObjRemovalTracking[listView] = ((IList)listView.ItemsSource).Count; + + return; + } + + if (index > (listViewBase.Items.Count - 1)) + { + index = listViewBase.Items.Count - 1; + } + + if (index < -listViewBase.Items.Count) + { + index = -listViewBase.Items.Count; + } + + index = (index < 0) ? (index + listViewBase.Items.Count) : index; + + bool isVirtualizing = default; + double previousXOffset = default, previousYOffset = default; + + ScrollViewer? scrollViewer = listViewBase.FindDescendant(); + SelectorItem? selectorItem = listViewBase.ContainerFromIndex(index) as SelectorItem; + + if (scrollViewer is null) + { + return; + } + + // If selectorItem is null then the panel is virtualized. + // So in order to get the container of the item we need to scroll to that item first and then use ContainerFromIndex + if (selectorItem is null) + { + isVirtualizing = true; + + previousXOffset = scrollViewer.HorizontalOffset; + previousYOffset = scrollViewer.VerticalOffset; + + TaskCompletionSource tcs = new(); + + void ViewChanged(object? _, ScrollViewerViewChangedEventArgs __) => tcs.TrySetResult(result: default); + + try + { + scrollViewer.ViewChanged += ViewChanged; + listViewBase.ScrollIntoView(listViewBase.Items[index], ScrollIntoViewAlignment.Leading); + _ = await tcs.Task; + } + finally + { + scrollViewer.ViewChanged -= ViewChanged; + } + + selectorItem = (SelectorItem)listViewBase.ContainerFromIndex(index); + } + + GeneralTransform transform = selectorItem.TransformToVisual((UIElement)scrollViewer.Content); + Point position = transform.TransformPoint(new Point(0, 0)); + + // Scrolling back to previous position + if (isVirtualizing) + { + await scrollViewer.ChangeViewAsync(previousXOffset, previousYOffset, zoomFactor: null, disableAnimation: true); + } + + double listViewBaseWidth = listViewBase.ActualWidth; + double selectorItemWidth = selectorItem.ActualWidth; + double listViewBaseHeight = listViewBase.ActualHeight; + double selectorItemHeight = selectorItem.ActualHeight; + + previousXOffset = scrollViewer.HorizontalOffset; + previousYOffset = scrollViewer.VerticalOffset; + + double minXPosition = position.X - listViewBaseWidth + selectorItemWidth; + double minYPosition = position.Y - listViewBaseHeight + selectorItemHeight; + + double maxXPosition = position.X; + double maxYPosition = position.Y; + + double finalXPosition, finalYPosition; + + // If the Item is in view and scrollIfVisible is false then we don't need to scroll + if (!scrollIfVisible && (previousXOffset <= maxXPosition && previousXOffset >= minXPosition) && (previousYOffset <= maxYPosition && previousYOffset >= minYPosition)) + { + finalXPosition = previousXOffset; + finalYPosition = previousYOffset; + } + // Center it vertically + else + { + finalXPosition = previousXOffset + additionalHorizontalOffset; + finalYPosition = maxYPosition - ((listViewBaseHeight - selectorItemHeight) / 2.0) + additionalVerticalOffset; + } + + await scrollViewer.ChangeViewAsync(finalXPosition, finalYPosition, zoomFactor: null, disableAnimation); + } + + /// + /// Changes the view of asynchronous. + /// + /// The scroll viewer. + /// The horizontal offset. + /// The vertical offset. + /// The zoom factor. + /// if set to true disable animation. + private static async Task ChangeViewAsync(this ScrollViewer scrollViewer, double? horizontalOffset, double? verticalOffset, float? zoomFactor, bool disableAnimation) + { + if (horizontalOffset > scrollViewer.ScrollableWidth) + { + horizontalOffset = scrollViewer.ScrollableWidth; + } + else if (horizontalOffset < 0) + { + horizontalOffset = 0; + } + + if (verticalOffset > scrollViewer.ScrollableHeight) + { + verticalOffset = scrollViewer.ScrollableHeight; + } + else if (verticalOffset < 0) + { + verticalOffset = 0; + } + + // MUST check this and return immediately, otherwise this async task will never complete because ViewChanged event won't get triggered + if (horizontalOffset == scrollViewer.HorizontalOffset && verticalOffset == scrollViewer.VerticalOffset) + { + return; + } + + TaskCompletionSource tcs = new(); + + void ViewChanged(object? _, ScrollViewerViewChangedEventArgs e) + { + if (e.IsIntermediate) + { + return; + } + + _ = tcs.TrySetResult(result: default); + } + + try + { + scrollViewer.ViewChanged += ViewChanged; + _ = scrollViewer.ChangeView(horizontalOffset, verticalOffset, zoomFactor, disableAnimation); + _ = await tcs.Task; + } + finally + { + scrollViewer.ViewChanged -= ViewChanged; + } + } + } diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml index c1eeed605..a09dd07bc 100644 --- a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml @@ -112,6 +112,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Visible" ShowsScrollingPlaceholders="True" ScrollViewer.VerticalScrollBarVisibility="Visible" + SelectionChanged="FileIdentitiesListView_SelectionChanged" ContainerContentChanging="ListView_ContainerContentChanging"> diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml.cs b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml.cs index 381a2acde..8fc340cf1 100644 --- a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml.cs +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsEventLogsDataGrid.xaml.cs @@ -731,6 +731,10 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs // If the item is not already selected, clear previous selections and select this one. if (!item.IsSelected) { + + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + //clear for exclusive selection FileIdentitiesListView.SelectedItems.Clear(); item.IsSelected = true; @@ -751,4 +755,20 @@ private void CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvoke ListViewFlyoutMenuCopy_Click(sender, new RoutedEventArgs()); args.Handled = true; } + + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + + private async void FileIdentitiesListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + } + } diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml index 0c2bff211..84fb0c5ab 100644 --- a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml @@ -110,6 +110,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Visible" ShowsScrollingPlaceholders="True" ScrollViewer.VerticalScrollBarVisibility="Visible" + SelectionChanged="FileIdentitiesListView_SelectionChanged" ContainerContentChanging="ListView_ContainerContentChanging"> diff --git a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml.cs b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml.cs index ae2db4fed..2b0afd7ad 100644 --- a/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml.cs +++ b/AppControl Manager/Pages/AllowNewApps/AllowNewAppsLocalFilesDataGrid.xaml.cs @@ -673,6 +673,9 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs // If the item is not already selected, clear previous selections and select this one. if (!item.IsSelected) { + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + //clear for exclusive selection FileIdentitiesListView.SelectedItems.Clear(); item.IsSelected = true; @@ -693,4 +696,19 @@ private void CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvoke ListViewFlyoutMenuCopy_Click(sender, new RoutedEventArgs()); args.Handled = true; } + + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + + private async void FileIdentitiesListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + } } diff --git a/AppControl Manager/Pages/CreateDenyPolicyFilesAndFoldersScanResults.xaml b/AppControl Manager/Pages/CreateDenyPolicyFilesAndFoldersScanResults.xaml index 44eb01920..c9bf82e0f 100644 --- a/AppControl Manager/Pages/CreateDenyPolicyFilesAndFoldersScanResults.xaml +++ b/AppControl Manager/Pages/CreateDenyPolicyFilesAndFoldersScanResults.xaml @@ -121,6 +121,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Visible" ShowsScrollingPlaceholders="True" ScrollViewer.VerticalScrollBarVisibility="Visible" + SelectionChanged="FileIdentitiesListView_SelectionChanged" ContainerContentChanging="ListView_ContainerContentChanging"> diff --git a/AppControl Manager/Pages/CreateDenyPolicyFilesAndFoldersScanResults.xaml.cs b/AppControl Manager/Pages/CreateDenyPolicyFilesAndFoldersScanResults.xaml.cs index 471511c05..e637e3185 100644 --- a/AppControl Manager/Pages/CreateDenyPolicyFilesAndFoldersScanResults.xaml.cs +++ b/AppControl Manager/Pages/CreateDenyPolicyFilesAndFoldersScanResults.xaml.cs @@ -624,6 +624,9 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs // If the item is not already selected, clear previous selections and select this one. if (!item.IsSelected) { + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + //clear for exclusive selection FileIdentitiesListView.SelectedItems.Clear(); item.IsSelected = true; @@ -643,4 +646,19 @@ private void CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvoke ListViewFlyoutMenuCopy_Click(sender, new RoutedEventArgs()); args.Handled = true; } + + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + + private async void FileIdentitiesListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + } } diff --git a/AppControl Manager/Pages/CreateSupplementalPolicyFilesAndFoldersScanResults.xaml b/AppControl Manager/Pages/CreateSupplementalPolicyFilesAndFoldersScanResults.xaml index 4d7c20b31..fea803e3e 100644 --- a/AppControl Manager/Pages/CreateSupplementalPolicyFilesAndFoldersScanResults.xaml +++ b/AppControl Manager/Pages/CreateSupplementalPolicyFilesAndFoldersScanResults.xaml @@ -122,6 +122,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Visible" ShowsScrollingPlaceholders="True" ScrollViewer.VerticalScrollBarVisibility="Visible" + SelectionChanged="FileIdentitiesListView_SelectionChanged" ContainerContentChanging="ListView_ContainerContentChanging"> diff --git a/AppControl Manager/Pages/CreateSupplementalPolicyFilesAndFoldersScanResults.xaml.cs b/AppControl Manager/Pages/CreateSupplementalPolicyFilesAndFoldersScanResults.xaml.cs index b9661320c..369960126 100644 --- a/AppControl Manager/Pages/CreateSupplementalPolicyFilesAndFoldersScanResults.xaml.cs +++ b/AppControl Manager/Pages/CreateSupplementalPolicyFilesAndFoldersScanResults.xaml.cs @@ -618,9 +618,12 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs { if (sender is ListViewItem item) { - // If the item is not already selected, clear previous selections and select this one. + // If the item is not already selected if (!item.IsSelected) { + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + //clear for exclusive selection FileIdentitiesListView.SelectedItems.Clear(); item.IsSelected = true; @@ -641,4 +644,19 @@ private void CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvoke ListViewFlyoutMenuCopy_Click(sender, new RoutedEventArgs()); args.Handled = true; } + + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + + private async void FileIdentitiesListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + } } diff --git a/AppControl Manager/Pages/EventLogsPolicyCreation.xaml b/AppControl Manager/Pages/EventLogsPolicyCreation.xaml index 57160a666..16baa54a6 100644 --- a/AppControl Manager/Pages/EventLogsPolicyCreation.xaml +++ b/AppControl Manager/Pages/EventLogsPolicyCreation.xaml @@ -333,6 +333,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Visible" ShowsScrollingPlaceholders="True" ScrollViewer.VerticalScrollBarVisibility="Visible" + SelectionChanged="FileIdentitiesListView_SelectionChanged" ContainerContentChanging="ListView_ContainerContentChanging"> diff --git a/AppControl Manager/Pages/EventLogsPolicyCreation.xaml.cs b/AppControl Manager/Pages/EventLogsPolicyCreation.xaml.cs index eec1162f1..6fddb8368 100644 --- a/AppControl Manager/Pages/EventLogsPolicyCreation.xaml.cs +++ b/AppControl Manager/Pages/EventLogsPolicyCreation.xaml.cs @@ -1222,6 +1222,9 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs // If the item is not already selected, clear previous selections and select this one. if (!item.IsSelected) { + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + //clear for exclusive selection FileIdentitiesListView.SelectedItems.Clear(); item.IsSelected = true; @@ -1243,4 +1246,18 @@ private void CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvoke args.Handled = true; } + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + + private async void FileIdentitiesListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + } } diff --git a/AppControl Manager/Pages/MDEAHPolicyCreation.xaml b/AppControl Manager/Pages/MDEAHPolicyCreation.xaml index f3b699deb..1a5245ed4 100644 --- a/AppControl Manager/Pages/MDEAHPolicyCreation.xaml +++ b/AppControl Manager/Pages/MDEAHPolicyCreation.xaml @@ -408,6 +408,7 @@ ScrollViewer.HorizontalScrollMode="Enabled" ScrollViewer.IsHorizontalRailEnabled="True" ScrollViewer.HorizontalScrollBarVisibility="Visible" + SelectionChanged="FileIdentitiesListView_SelectionChanged" ShowsScrollingPlaceholders="True" ScrollViewer.VerticalScrollBarVisibility="Visible" ContainerContentChanging="ListView_ContainerContentChanging"> diff --git a/AppControl Manager/Pages/MDEAHPolicyCreation.xaml.cs b/AppControl Manager/Pages/MDEAHPolicyCreation.xaml.cs index a524fe54c..9158562e5 100644 --- a/AppControl Manager/Pages/MDEAHPolicyCreation.xaml.cs +++ b/AppControl Manager/Pages/MDEAHPolicyCreation.xaml.cs @@ -1468,6 +1468,9 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs // If the item is not already selected, clear previous selections and select this one. if (!item.IsSelected) { + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + //clear for exclusive selection FileIdentitiesListView.SelectedItems.Clear(); item.IsSelected = true; @@ -1488,6 +1491,21 @@ private void CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvoke ListViewFlyoutMenuCopy_Click(sender, new RoutedEventArgs()); args.Handled = true; } + + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + + private async void FileIdentitiesListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + } } diff --git a/AppControl Manager/Pages/Settings.xaml b/AppControl Manager/Pages/Settings.xaml index 38e160ea8..a1acb0bdc 100644 --- a/AppControl Manager/Pages/Settings.xaml +++ b/AppControl Manager/Pages/Settings.xaml @@ -152,6 +152,16 @@ + + + + + + + + + + diff --git a/AppControl Manager/Pages/Settings.xaml.cs b/AppControl Manager/Pages/Settings.xaml.cs index d3f81645a..1b5df0e1c 100644 --- a/AppControl Manager/Pages/Settings.xaml.cs +++ b/AppControl Manager/Pages/Settings.xaml.cs @@ -39,6 +39,8 @@ public Settings() SoundToggleSwitch.IsOn = GetSetting(SettingKeys.SoundSetting); + ListViewsCenterVerticallyUponSelectionToggleSwitch.IsOn = GetSetting(SettingKeys.ListViewsVerticalCentering); + BackgroundComboBox.SelectedIndex = (GetSetting(SettingKeys.BackDropBackground)) switch { "MicaAlt" => 0, @@ -78,6 +80,7 @@ public Settings() NavigationMenuLocation.SelectionChanged += NavigationViewLocationComboBox_SelectionChanged; SoundToggleSwitch.Toggled += SoundToggleSwitch_Toggled; IconsStyleComboBox.SelectionChanged += IconsStyleComboBox_SelectionChanged; + ListViewsCenterVerticallyUponSelectionToggleSwitch.Toggled += ListViewsCenterVerticallyUponSelectionToggleSwitch_Toggled; } @@ -198,7 +201,7 @@ private void SoundToggleSwitch_Toggled(object sender, RoutedEventArgs e) // Raise the event to notify the app of the sound setting change SoundManager.OnSoundSettingChanged(isSoundOn); - // Save the sound setting to the local app settings + // Save the setting to the local app settings SaveSetting(SettingKeys.SoundSetting, isSoundOn); } @@ -405,4 +408,24 @@ private void NavigationViewBackgroundToggleSettingsCard_Click(object sender, Rou NavigationViewBackgroundToggle.IsOn = !NavigationViewBackgroundToggle.IsOn; NavigationViewBackground_Toggled(NavigationViewBackgroundToggle, new RoutedEventArgs()); } + + + private void ListViewsCenterVerticallyUponSelectionSettingsCard_Click(object sender, RoutedEventArgs e) + { + ListViewsCenterVerticallyUponSelectionToggleSwitch.IsOn = !ListViewsCenterVerticallyUponSelectionToggleSwitch.IsOn; + } + + + private void ListViewsCenterVerticallyUponSelectionToggleSwitch_Toggled(object sender, RoutedEventArgs e) + { + // Get the ToggleSwitch that triggered the event + ToggleSwitch toggleSwitch = (ToggleSwitch)sender; + + // Get the state of the toggle switch (on or off) + bool IsOn = toggleSwitch.IsOn; + + // Save the setting to the local app settings + SaveSetting(SettingKeys.ListViewsVerticalCentering, IsOn); + } + } diff --git a/AppControl Manager/Pages/Simulation.xaml b/AppControl Manager/Pages/Simulation.xaml index c9a9639d8..af6ea5e51 100644 --- a/AppControl Manager/Pages/Simulation.xaml +++ b/AppControl Manager/Pages/Simulation.xaml @@ -270,6 +270,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Visible" ShowsScrollingPlaceholders="True" ScrollViewer.VerticalScrollBarVisibility="Visible" + SelectionChanged="SimOutputListView_SelectionChanged" ContainerContentChanging="ListView_ContainerContentChanging"> diff --git a/AppControl Manager/Pages/Simulation.xaml.cs b/AppControl Manager/Pages/Simulation.xaml.cs index ac03aa444..427a6bcba 100644 --- a/AppControl Manager/Pages/Simulation.xaml.cs +++ b/AppControl Manager/Pages/Simulation.xaml.cs @@ -689,6 +689,9 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs // If the item is not already selected, clear previous selections and select this one. if (!item.IsSelected) { + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + //clear for exclusive selection SimOutputListView.SelectedItems.Clear(); item.IsSelected = true; @@ -709,4 +712,19 @@ private void CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvoke ListViewFlyoutMenuCopy_Click(sender, new RoutedEventArgs()); args.Handled = true; } + + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + + private async void SimOutputListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + } } diff --git a/AppControl Manager/Pages/StrictKernelPolicyScanResults.xaml b/AppControl Manager/Pages/StrictKernelPolicyScanResults.xaml index ff7cc7b4d..cf63afe1a 100644 --- a/AppControl Manager/Pages/StrictKernelPolicyScanResults.xaml +++ b/AppControl Manager/Pages/StrictKernelPolicyScanResults.xaml @@ -121,6 +121,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Visible" ShowsScrollingPlaceholders="True" ScrollViewer.VerticalScrollBarVisibility="Visible" + SelectionChanged="FileIdentitiesListView_SelectionChanged" ContainerContentChanging="ListView_ContainerContentChanging"> diff --git a/AppControl Manager/Pages/StrictKernelPolicyScanResults.xaml.cs b/AppControl Manager/Pages/StrictKernelPolicyScanResults.xaml.cs index 02c7eddbd..928aa8e47 100644 --- a/AppControl Manager/Pages/StrictKernelPolicyScanResults.xaml.cs +++ b/AppControl Manager/Pages/StrictKernelPolicyScanResults.xaml.cs @@ -643,6 +643,9 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs // If the item is not already selected, clear previous selections and select this one. if (!item.IsSelected) { + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + //clear for exclusive selection FileIdentitiesListView.SelectedItems.Clear(); item.IsSelected = true; @@ -664,4 +667,18 @@ private void CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvoke args.Handled = true; } + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + + private async void FileIdentitiesListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + } } diff --git a/AppControl Manager/Pages/SystemInformation/ViewCurrentPolicies.xaml.cs b/AppControl Manager/Pages/SystemInformation/ViewCurrentPolicies.xaml.cs index c304d0d3c..b9e43f403 100644 --- a/AppControl Manager/Pages/SystemInformation/ViewCurrentPolicies.xaml.cs +++ b/AppControl Manager/Pages/SystemInformation/ViewCurrentPolicies.xaml.cs @@ -472,6 +472,9 @@ private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) } + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + /// /// Event handler for when a policy is selected from the ListView. It will contain the selected policy. /// When the Refresh button is pressed, this event is fired again, but due to clearing the existing data in the refresh event handler, ListView's SelectedItem property will be null, @@ -479,8 +482,17 @@ private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) /// /// /// - private void DeployedPolicies_SelectionChanged(object sender, SelectionChangedEventArgs e) + private async void DeployedPolicies_SelectionChanged(object sender, SelectionChangedEventArgs e) { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + // Get the selected policy from the ListView CiPolicyInfo? temp = (CiPolicyInfo)DeployedPolicies.SelectedItem; @@ -1021,6 +1033,9 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs // and mark this item as selected. if (!item.IsSelected) { + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + item.IsSelected = true; } } diff --git a/AppControl Manager/Pages/ViewFileCertificates.xaml b/AppControl Manager/Pages/ViewFileCertificates.xaml index 2667c4554..4a5c9ab1b 100644 --- a/AppControl Manager/Pages/ViewFileCertificates.xaml +++ b/AppControl Manager/Pages/ViewFileCertificates.xaml @@ -102,6 +102,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Visible" ShowsScrollingPlaceholders="True" ScrollViewer.VerticalScrollBarVisibility="Visible" + SelectionChanged="FileCertificatesListView_SelectionChanged" ContainerContentChanging="ListView_ContainerContentChanging"> diff --git a/AppControl Manager/Pages/ViewFileCertificates.xaml.cs b/AppControl Manager/Pages/ViewFileCertificates.xaml.cs index 632c35e15..2fa5d2c4b 100644 --- a/AppControl Manager/Pages/ViewFileCertificates.xaml.cs +++ b/AppControl Manager/Pages/ViewFileCertificates.xaml.cs @@ -805,6 +805,9 @@ private void ListViewItem_RightTapped(object sender, RightTappedRoutedEventArgs // If the item is not already selected, clear previous selections and select this one. if (!item.IsSelected) { + // Set the counter so that the SelectionChanged event handler will ignore the next 2 events. + _skipSelectionChangedCount = 2; + item.IsSelected = true; } } @@ -822,4 +825,19 @@ private void CtrlC_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvoke ListViewFlyoutMenuCopy_Click(sender, new RoutedEventArgs()); args.Handled = true; } + + // A counter to prevent SelectionChanged event from firing twice when right-clicking on an unselected row + private int _skipSelectionChangedCount; + + private async void FileCertificatesListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Check if we need to skip this event. + if (_skipSelectionChangedCount > 0) + { + _skipSelectionChangedCount--; + return; + } + + await ListViewUIHelpers.SmoothScrollIntoViewWithIndexCenterVerticallyOnlyAsync(listViewBase: (ListView)sender, listView: (ListView)sender, index: ((ListView)sender).SelectedIndex, disableAnimation: false, scrollIfVisible: true, additionalHorizontalOffset: 0, additionalVerticalOffset: 0); + } } diff --git a/AppControl Manager/Strings/en-US/Resources.resw b/AppControl Manager/Strings/en-US/Resources.resw index 656206fc6..7303bc329 100644 --- a/AppControl Manager/Strings/en-US/Resources.resw +++ b/AppControl Manager/Strings/en-US/Resources.resw @@ -1432,4 +1432,22 @@ NOTE: This option is always enforced if any App Control UMCI policy enables it. Using this option means the Microsoft Recommended (User-Mode) Block Rules will not be created/deployed along with the base policy. (Not Recommended) + + Change the behavior of the AppControl Manager and various elements inside of it + + + Behavior + + + Change the behavior of the AppControl Manager and various elements inside of it + + + Whenever you select an item in a List View, center that item vertically on the screen. + + + List Views Center Vertically Upon Selection + + + Whenever you select an item in a List View, center that item vertically on the screen. + \ No newline at end of file