// Copyright (c) Microsoft Corporation // The Microsoft Corporation 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.Diagnostics; using System.Globalization; using System.IO; using System.Threading.Tasks; using CommunityToolkit.WinUI.UI.Controls; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Data; using Windows.Foundation.Metadata; using Windows.Storage; namespace RegistryPreviewUILib { public sealed partial class RegistryPreviewMainPage : Page { /// /// Event that is will prevent the app from closing if the "save file" flag is active /// public void MainWindow_Closed(object sender, WindowEventArgs args) { // Only block closing if the REG file has been edited but not yet saved if (saveButton.IsEnabled) { // if true, the app will not close args.Handled = true; // ask the user if they want to save, discard or cancel the close; strings must be loaded here and passed to avoid timing issues HandleDirtyClosing( resourceLoader.GetString("YesNoCancelDialogTitle"), resourceLoader.GetString("YesNoCancelDialogContent"), resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"), resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"), resourceLoader.GetString("YesNoCancelDialogCloseButtonText")); } // Check to see if the textbox's context menu is open if (textBox.ContextFlyout != null && textBox.ContextFlyout.IsOpen) { textBox.ContextFlyout.Hide(); // if true, the app will not close yet args.Handled = true; // HACK: To fix https://github.com/microsoft/PowerToys/issues/28820, wait a bit for the close animation of the flyout to run before closing the application. // This might be called many times if the flyout still hasn't been closed, as Window_Closed will be called again by App.Current.Exit DispatcherQueue.TryEnqueue(async () => { await Task.Delay(100); Application.Current.Exit(); }); return; } } /// /// Event that gets fired after the visual tree has been fully loaded; the app opens the reg file from here so it can show a message box successfully /// private void GridPreview_Loaded(object sender, RoutedEventArgs e) { // static flag to track whether the Visual Tree is ready - if the main Grid has been loaded, the tree is ready. visualTreeReady = true; // Check to see if the REG file was opened and parsed successfully if (OpenRegistryFile(_appFileName) == false) { if (File.Exists(_appFileName)) { // Allow Refresh and Edit to be enabled because a broken Reg file might be fixable UpdateToolBarAndUI(false, true, true); _updateWindowTitleFunction(resourceLoader.GetString("InvalidRegistryFileTitle")); textBox.TextChanged += TextBox_TextChanged; return; } else { UpdateToolBarAndUI(false, false, false); _updateWindowTitleFunction(string.Empty); } } else { textBox.TextChanged += TextBox_TextChanged; } textBox.Focus(FocusState.Programmatic); } /// /// Uses a picker to select a new file to open /// private async void OpenButton_Click(object sender, RoutedEventArgs e) { // Check to see if the current file has been saved if (saveButton.IsEnabled) { ContentDialog contentDialog = new ContentDialog() { Title = resourceLoader.GetString("YesNoCancelDialogTitle"), Content = resourceLoader.GetString("YesNoCancelDialogContent"), PrimaryButtonText = resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"), SecondaryButtonText = resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"), CloseButtonText = resourceLoader.GetString("YesNoCancelDialogCloseButtonText"), DefaultButton = ContentDialogButton.Primary, }; // Use this code to associate the dialog to the appropriate AppWindow by setting // the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow. if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) { contentDialog.XamlRoot = this.Content.XamlRoot; } ContentDialogResult contentDialogResult = await contentDialog.ShowAsync(); switch (contentDialogResult) { case ContentDialogResult.Primary: // Save, then continue the file open SaveFile(); break; case ContentDialogResult.Secondary: // Don't save and continue the file open! saveButton.IsEnabled = false; break; default: // Don't open the new file! return; } } // Pull in a new REG file - we have to use the direct Win32 method because FileOpenPicker crashes when it's // called while running as admin IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(_mainWindow); string filename = OpenFilePicker.ShowDialog( windowHandle, resourceLoader.GetString("FilterRegistryName") + '\0' + "*.reg" + '\0' + resourceLoader.GetString("FilterAllFiles") + '\0' + "*.*" + '\0' + '\0', resourceLoader.GetString("OpenDialogTitle")); if (filename == string.Empty || File.Exists(filename) == false) { return; } StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filename); if (storageFile != null) { // mute the TextChanged handler to make for clean UI textBox.TextChanged -= TextBox_TextChanged; _appFileName = storageFile.Path; UpdateToolBarAndUI(OpenRegistryFile(_appFileName)); // disable the Save button as it's a new file saveButton.IsEnabled = false; // Restore the event handler as we're loaded textBox.TextChanged += TextBox_TextChanged; } } /// /// Saves the currently opened file in place /// private void SaveButton_Click(object sender, RoutedEventArgs e) { SaveFile(); } /// /// Uses a picker to save out a copy of the current reg file /// private void SaveAsButton_Click(object sender, RoutedEventArgs e) { // Save out a new REG file and then open it - we have to use the direct Win32 method because FileOpenPicker crashes when it's // called while running as admin IntPtr windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(_mainWindow); string filename = SaveFilePicker.ShowDialog( windowHandle, resourceLoader.GetString("SuggestFileName"), resourceLoader.GetString("FilterRegistryName") + '\0' + "*.reg" + '\0' + resourceLoader.GetString("FilterAllFiles") + '\0' + "*.*" + '\0' + '\0', resourceLoader.GetString("SaveDialogTitle")); if (filename == string.Empty) { return; } _appFileName = filename; SaveFile(); UpdateToolBarAndUI(OpenRegistryFile(_appFileName)); } /// /// Reloads the current REG file from storage /// private void RefreshButton_Click(object sender, RoutedEventArgs e) { // mute the TextChanged handler to make for clean UI textBox.TextChanged -= TextBox_TextChanged; // reload the current Registry file and update the toolbar accordingly. UpdateToolBarAndUI(OpenRegistryFile(_appFileName), true, true); saveButton.IsEnabled = false; // restore the TextChanged handler textBox.TextChanged += TextBox_TextChanged; } /// /// Opens the Registry Editor; UAC is handled by the request to open /// private void RegistryButton_Click(object sender, RoutedEventArgs e) { // pass in an empty string as we have no file to open OpenRegistryEditor(string.Empty); } /// /// Opens the Registry Editor and tries to set "last used"; UAC is handled by the request to open /// private void RegistryJumpToKeyButton_Click(object sender, RoutedEventArgs e) { // Get the selected Key, if there is one TreeViewNode currentNode = treeView.SelectedNode; if (currentNode != null) { // since there is a valid node, get the FullPath of the key that was selected string key = ((RegistryKey)currentNode.Content).FullPath; // it's impossible to directly open a key via command-line option, so we must override the last remember key Microsoft.Win32.Registry.SetValue(@"HKEY_Current_User\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit", "LastKey", key); } // pass in an empty string as we have no file to open OpenRegistryEditor(string.Empty); } /// /// Merges the currently saved file into the Registry Editor; UAC is handled by the request to open /// private async void WriteButton_Click(object sender, RoutedEventArgs e) { // Check to see if the current file has been saved if (saveButton.IsEnabled) { ContentDialog contentDialog = new ContentDialog() { Title = resourceLoader.GetString("YesNoCancelDialogTitle"), Content = resourceLoader.GetString("YesNoCancelDialogContent"), PrimaryButtonText = resourceLoader.GetString("YesNoCancelDialogPrimaryButtonText"), SecondaryButtonText = resourceLoader.GetString("YesNoCancelDialogSecondaryButtonText"), CloseButtonText = resourceLoader.GetString("YesNoCancelDialogCloseButtonText"), DefaultButton = ContentDialogButton.Primary, }; // Use this code to associate the dialog to the appropriate AppWindow by setting // the dialog's XamlRoot to the same XamlRoot as an element that is already present in the AppWindow. if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8)) { contentDialog.XamlRoot = this.Content.XamlRoot; } ContentDialogResult contentDialogResult = await contentDialog.ShowAsync(); switch (contentDialogResult) { case ContentDialogResult.Primary: // Save, then continue the file open SaveFile(); break; case ContentDialogResult.Secondary: // Don't save and continue the file open! saveButton.IsEnabled = false; break; default: // Don't open the new file! return; } } // pass in the filename so we can edit the current file OpenRegistryEditor(_appFileName); } /// /// Opens the currently saved file in the PC's default REG file editor (often Notepad) /// private void EditButton_Click(object sender, RoutedEventArgs e) { // use the REG file's filename and verb so we can respect the selected editor Process process = new Process(); process.StartInfo.FileName = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", _appFileName); process.StartInfo.Verb = "Edit"; process.StartInfo.UseShellExecute = true; try { process.Start(); } catch { ShowMessageBox( resourceLoader.GetString("ErrorDialogTitle"), resourceLoader.GetString("FileEditorError"), resourceLoader.GetString("OkButtonText")); } } /// /// Trigger that fires when a node in treeView is clicked and which populates dataGrid /// Can also be fired from elsewhere in the code /// private void TreeView_ItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args) { TreeViewItemInvokedEventArgs localArgs = args as TreeViewItemInvokedEventArgs; TreeViewNode treeViewNode = null; // if there are no args, the mouse didn't get clicked but we want to believe it did if (args != null) { treeViewNode = args.InvokedItem as TreeViewNode; } else { treeViewNode = treeView.SelectedNode; } // Update the toolbar button for the tree registryJumpToKeyButton.IsEnabled = CheckTreeForValidKey(); // Grab the object that has Registry data in it from the currently selected treeView node RegistryKey registryKey = (RegistryKey)treeViewNode.Content; // no matter what happens, clear the ListView of items on each click ClearTable(); // if there's no ListView items stored for the selected node, dataGrid is clear so get out now if (registryKey.Tag == null) { return; } // if there WAS something in the Tag property, cast it to a list and Populate the ListView ArrayList arrayList = (ArrayList)registryKey.Tag; listRegistryValues = new List(); for (int i = 0; i < arrayList.Count; i++) { RegistryValue listViewItem = (RegistryValue)arrayList[i]; listRegistryValues.Add(listViewItem); } // create a new binding for dataGrid and reattach it, updating the rows Binding listRegistryValuesBinding = new Binding { Source = listRegistryValues }; dataGrid.SetBinding(DataGrid.ItemsSourceProperty, listRegistryValuesBinding); } /// /// When the text in textBox changes, reload treeView and possibly dataGrid and reset the save button /// private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { RefreshRegistryFile(); saveButton.IsEnabled = true; } } }