[EnvVar][Hosts][RegPrev]Decouple and refactor to make it "packable" as nuget package (#32604)

* WIP Hosts - remove deps

* Add consumer app

* Move App and MainWindow to Consumer app. Make Hosts dll

* Try consume it

* Fix errors

* Make it work with custom build targets

* Dependency injection
Refactor
Explicit page creation
Wire missing dependencies

* Fix installer

* Remove unneeded stuff

* Fix build again

* Extract UI and logic from MainWindow to RegistryPreviewMainPage

* Convert to lib
Change namespace to RegistryPreviewUILib
Remove PT deps

* Add exe app and move App.xaml and MainWindow.xaml

* Consume the lib

* Update Hosts package creation

* Fix RegistryPreview package creation

* Rename RegistryPreviewUI back to RegistryPreview

* Back to consuming lib

* Ship icons and assets in nuget packages

* Rename to EnvironmentVariablesUILib and convert to lib

* Add app and consume

* Telemetry

* GPO

* nuget

* Rename HostsPackageConsumer to Hosts and Hosts lib to HostsUILib

* Assets cleanup

* nuget struct

* v0

* assets

* [Hosts] Re-add AppList to Lib Assets, [RegPrev] Copy lib assets to out dir

* Sign UI dlls

* Revert WinUIEx bump

* Cleanup

* Align deps

* version exception dll

* Fix RegistryPreview crashes

* XAML format

* XAML format 2

* Pack .pri files in lib/ dir

---------

Co-authored-by: Darshak Bhatti <dabhatti@microsoft.com>
This commit is contained in:
Stefan Markovic
2024-04-26 19:41:44 +02:00
committed by GitHub
parent 28ba2bd301
commit 41a0114efe
125 changed files with 2097 additions and 1212 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -0,0 +1,39 @@
// 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.Runtime.InteropServices;
namespace RegistryPreviewUILib
{
// Workaround for File Pickers that don't work while running as admin, per:
// https://github.com/microsoft/WindowsAppSDK/issues/2504
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct FileName
{
public int StructSize;
public IntPtr HwndOwner;
public IntPtr Instance;
public string Filter;
public string CustomFilter;
public int MaxCustFilter;
public int FilterIndex;
public string File;
public int MaxFile;
public string FileTitle;
public int MaxFileTitle;
public string InitialDir;
public string Title;
public int Flags;
public short FileOffset;
public short FileExtension;
public string DefExt;
public IntPtr CustData;
public IntPtr Hook;
public string TemplateName;
public IntPtr PtrReserved;
public int Reserved;
public int FlagsEx;
}
}

View File

@@ -0,0 +1,38 @@
// 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.Runtime.InteropServices;
namespace RegistryPreviewUILib
{
// Workaround for File Pickers that don't work while running as admin, per:
// https://github.com/microsoft/WindowsAppSDK/issues/2504
public static partial class OpenFilePicker
{
[DllImport("comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool GetOpenFileName(ref FileName openFileName);
public static string ShowDialog(IntPtr windowHandle, string filter, string dialogTitle)
{
FileName openFileName = default(FileName);
openFileName.StructSize = Marshal.SizeOf(openFileName);
openFileName.HwndOwner = windowHandle;
openFileName.Filter = filter;
openFileName.File = new string(new char[256]);
openFileName.MaxFile = openFileName.File.Length;
openFileName.FileTitle = new string(new char[64]);
openFileName.MaxFileTitle = openFileName.FileTitle.Length;
openFileName.Title = dialogTitle;
if (GetOpenFileName(ref openFileName))
{
return openFileName.File;
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,32 @@
// 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.
namespace RegistryPreviewUILib
{
/// <summary>
/// Class representing an each item in the tree view, each one a Registry Key;
/// FullPath is so we can re-select the node after a live update
/// Tag is an Array of ListViewItems that stores all the children for the current object
/// </summary>
public class RegistryKey
{
public string Name { get; set; }
public string FullPath { get; set; }
public string Image { get; set; }
public string ToolTipText { get; set; }
public object Tag { get; set; }
public RegistryKey(string name, string fullPath, string image, string toolTipText)
{
this.Name = name;
this.FullPath = fullPath;
this.Image = image;
this.ToolTipText = toolTipText;
}
}
}

View File

@@ -0,0 +1,374 @@
// 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
{
/// <summary>
/// Event that is will prevent the app from closing if the "save file" flag is active
/// </summary>
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;
}
}
/// <summary>
/// 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
/// </summary>
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);
}
/// <summary>
/// Uses a picker to select a new file to open
/// </summary>
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;
}
}
/// <summary>
/// Saves the currently opened file in place
/// </summary>
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
SaveFile();
}
/// <summary>
/// Uses a picker to save out a copy of the current reg file
/// </summary>
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));
}
/// <summary>
/// Reloads the current REG file from storage
/// </summary>
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;
}
/// <summary>
/// Opens the Registry Editor; UAC is handled by the request to open
/// </summary>
private void RegistryButton_Click(object sender, RoutedEventArgs e)
{
// pass in an empty string as we have no file to open
OpenRegistryEditor(string.Empty);
}
/// <summary>
/// Opens the Registry Editor and tries to set "last used"; UAC is handled by the request to open
/// </summary>
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);
}
/// <summary>
/// Merges the currently saved file into the Registry Editor; UAC is handled by the request to open
/// </summary>
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);
}
/// <summary>
/// Opens the currently saved file in the PC's default REG file editor (often Notepad)
/// </summary>
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"));
}
}
/// <summary>
/// Trigger that fires when a node in treeView is clicked and which populates dataGrid
/// Can also be fired from elsewhere in the code
/// </summary>
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<RegistryValue>();
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);
}
/// <summary>
/// When the text in textBox changes, reload treeView and possibly dataGrid and reset the save button
/// </summary>
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
RefreshRegistryFile();
saveButton.IsEnabled = true;
}
}
}

View File

@@ -0,0 +1,270 @@
<Page
x:Class="RegistryPreviewUILib.RegistryPreviewMainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tk7controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid
x:Name="gridPreview"
Grid.Row="1"
Width="Auto"
Height="Auto"
Margin="12"
x:FieldModifier="public"
Loaded="GridPreview_Loaded"
TabFocusNavigation="Cycle">
<Grid.Resources>
<Style x:Key="GridCardStyle" TargetType="Border">
<Style.Setters>
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
<Setter Property="CornerRadius" Value="{StaticResource OverlayCornerRadius}" />
</Style.Setters>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<!-- Left, Splitter, Right -->
<ColumnDefinition Width="*" />
<ColumnDefinition Width="8" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<!-- CommandBar, Tree, Splitter, List -->
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="8" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="0,0,0,12"
Style="{StaticResource GridCardStyle}">
<CommandBar
Name="commandBar"
HorizontalAlignment="Left"
DefaultLabelPosition="Right">
<AppBarButton
x:Name="openButton"
x:Uid="OpenButton"
Click="OpenButton_Click"
Icon="{ui:FontIcon Glyph=&#xe8e5;}">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Key="O" Modifiers="Control" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton
x:Name="refreshButton"
x:Uid="RefreshButton"
Click="RefreshButton_Click"
Icon="{ui:FontIcon Glyph=&#xe72c;}">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Key="F5" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarSeparator />
<AppBarButton
x:Name="saveButton"
x:Uid="SaveButton"
Click="SaveButton_Click"
Icon="{ui:FontIcon Glyph=&#xe74e;}"
IsEnabled="False">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Key="S" Modifiers="Control" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton
x:Name="saveAsButton"
x:Uid="SaveAsButton"
Click="SaveAsButton_Click"
Icon="{ui:FontIcon Glyph=&#xe792;}"
IsEnabled="True">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Key="S" Modifiers="Control,Shift" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarSeparator />
<AppBarButton
x:Name="editButton"
x:Uid="EditButton"
Click="EditButton_Click"
Icon="{ui:FontIcon Glyph=&#xe8a7;}">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Key="E" Modifiers="Control" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton
x:Name="writeButton"
x:Uid="WriteButton"
Click="WriteButton_Click"
Icon="{ui:FontIcon Glyph=&#xe72d;}">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Key="W" Modifiers="Control" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton
x:Name="registryButton"
x:Uid="RegistryButton"
Click="RegistryButton_Click"
Icon="{ui:FontIcon Glyph=&#xe8ad;}">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Key="R" Modifiers="Control" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
<AppBarButton
x:Name="registryJumpToKeyButton"
x:Uid="RegistryJumpToKeyButton"
Click="RegistryJumpToKeyButton_Click"
IsEnabled="True">
<AppBarButton.KeyboardAccelerators>
<KeyboardAccelerator Key="R" Modifiers="Control,Shift" />
</AppBarButton.KeyboardAccelerators>
</AppBarButton>
</CommandBar>
</Border>
<TextBox
x:Name="textBox"
x:Uid="textBox"
Grid.Row="1"
Grid.RowSpan="3"
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AcceptsReturn="True"
CanBeScrollAnchor="False"
CornerRadius="{StaticResource OverlayCornerRadius}"
FontFamily="Cascadia Mono, Consolas, Courier New"
IsSpellCheckEnabled="False"
IsTabStop="True"
IsTextPredictionEnabled="False"
PlaceholderText="{Binding PlaceholderText}"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.IsHorizontalRailEnabled="True"
ScrollViewer.IsVerticalRailEnabled="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
TabIndex="0"
TextWrapping="NoWrap" />
<Border
Grid.Row="1"
Grid.Column="2"
Style="{StaticResource GridCardStyle}">
<TreeView
x:Name="treeView"
AllowDrop="False"
AllowFocusOnInteraction="True"
CanDragItems="False"
CanReorderItems="False"
IsEnabled="True"
IsTabStop="False"
ItemInvoked="TreeView_ItemInvoked"
ScrollViewer.BringIntoViewOnFocusChange="True"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Auto"
TabIndex="1">
<TreeView.ItemTemplate>
<DataTemplate>
<StackPanel
VerticalAlignment="Center"
IsTabStop="False"
Orientation="Horizontal"
Spacing="8">
<Image
MaxWidth="16"
MaxHeight="16"
Source="{Binding Path=Content.Image}"
ToolTipService.ToolTip="{Binding Path=Content.ToolTipText}" />
<TextBlock Text="{Binding Path=Content.Name}" />
</StackPanel>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Border>
<Border
Grid.Row="3"
Grid.Column="2"
Style="{StaticResource GridCardStyle}">
<tk7controls:DataGrid
x:Name="dataGrid"
AllowDrop="False"
AreRowDetailsFrozen="True"
AutoGenerateColumns="False"
CanDrag="False"
HeadersVisibility="Column"
IsReadOnly="True"
IsTabStop="true"
ItemsSource="{x:Bind listRegistryValues}"
RowDetailsVisibilityMode="Collapsed"
SelectionMode="Single"
TabIndex="2">
<tk7controls:DataGrid.Columns>
<tk7controls:DataGridTemplateColumn
x:Uid="NameColumn"
Width="Auto"
IsReadOnly="True">
<tk7controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel
Margin="4"
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="8">
<Image
MaxWidth="16"
MaxHeight="16"
IsTabStop="False"
Source="{Binding ImageUri}"
ToolTipService.ToolTip="{Binding ToolTipText}" />
<TextBlock
IsTabStop="False"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</tk7controls:DataGridTemplateColumn.CellTemplate>
</tk7controls:DataGridTemplateColumn>
<tk7controls:DataGridTextColumn
x:Uid="TypeColumn"
Width="Auto"
Binding="{Binding Type}"
FontSize="{StaticResource CaptionTextBlockFontSize}" />
<tk7controls:DataGridTextColumn
x:Uid="ValueColumn"
Width="Auto"
Binding="{Binding Value}"
FontSize="{StaticResource CaptionTextBlockFontSize}" />
</tk7controls:DataGrid.Columns>
</tk7controls:DataGrid>
</Border>
<tkcontrols:GridSplitter
x:Name="verticalSplitter"
Grid.Row="1"
Grid.RowSpan="3"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
IsTabStop="False" />
<tkcontrols:GridSplitter
x:Name="horizontalSplitter"
Grid.Row="2"
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsTabStop="False" />
</Grid>
</Page>

View File

@@ -0,0 +1,55 @@
// 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.Collections.Generic;
using System.IO;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.ApplicationModel.Resources;
namespace RegistryPreviewUILib
{
public sealed partial class RegistryPreviewMainPage : Page
{
// Const values
private const string REGISTRYHEADER4 = "regedit4";
private const string REGISTRYHEADER5 = "windows registry editor version 5.00";
private const string APPNAME = "RegistryPreview";
private const string KEYIMAGE = "ms-appx:///Assets/RegistryPreview/folder32.png";
private const string DELETEDKEYIMAGE = "ms-appx:///Assets/RegistryPreview/deleted-folder32.png";
private const string ERRORIMAGE = "ms-appx:///Assets/RegistryPreview/error32.png";
// private members
private ResourceLoader resourceLoader;
private bool visualTreeReady;
private Dictionary<string, TreeViewNode> mapRegistryKeys;
private List<RegistryValue> listRegistryValues;
private UpdateWindowTitleFunction _updateWindowTitleFunction;
private string _appFileName;
private Window _mainWindow;
public RegistryPreviewMainPage(Window mainWindow, UpdateWindowTitleFunction updateWindowTitleFunction, string appFilename)
{
// TODO (stefan): check ctor
this.InitializeComponent();
_mainWindow = mainWindow;
_updateWindowTitleFunction = updateWindowTitleFunction;
_appFileName = appFilename;
_mainWindow.Closed += MainWindow_Closed;
// Initialize the string table
resourceLoader = ResourceLoaderInstance.ResourceLoader;
// Update Toolbar
if ((_appFileName == null) || (File.Exists(_appFileName) != true))
{
UpdateToolBarAndUI(false);
_updateWindowTitleFunction(resourceLoader.GetString("FileNotFound"));
}
}
}
}

View File

@@ -0,0 +1,69 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Condition="exists('..\..\..\Version.props')" Project="..\..\..\Version.props" />
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0-windows10.0.20348</TargetFramework>
<RootNamespace>RegistryPreviewUILib</RootNamespace>
<UseWinUI>true</UseWinUI>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<AssemblyName>PowerToys.RegistryPreviewUILib</AssemblyName>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>PowerToys.RegistryPreviewUILib.pri</ProjectPriFileName>
<GenerateLibraryLayout>true</GenerateLibraryLayout>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<None Include="$(OutDir)\PowerToys.RegistryPreviewUILib.pri" Pack="True" PackageCopyToOutput="true" PackagePath="lib/$(TargetFramework)" />
<None Include="$(OutDir)\PowerToys.RegistryPreviewUILib.pri" Pack="True" PackageCopyToOutput="True" PackagePath="contentFiles\any\$(TargetFramework)" />
<XBFFile Include="$(OutDir)**\*.xbf" />
<None Include="@(XBFFile)" Pack="True" PackageCopyToOutput="True" PackagePath="contentFiles\any\$(TargetFramework)" />
<None Include="$(OutDir)\PowerToys.RegistryPreviewUILib.pdb" Pack="True" PackageCopyToOutput="true" PackagePath="lib/$(TargetFramework)" />
<None Include="Assets\**\*.png" Pack="true" PackageCopyToOutput="true" PackagePath="contentFiles\any\$(TargetFramework)\Assets" />
<None Include="Assets\**\*.ico" Pack="true" PackageCopyToOutput="true" PackagePath="contentFiles\any\$(TargetFramework)\Assets" />
</ItemGroup>
<ItemGroup>
<Content Remove="Assets\**\*.png" Pack="false" />
<Content Remove="Assets\**\*.ico" Pack="false" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DebugSymbols>true</DebugSymbols>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.DataGrid" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="WinUIEx" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<None Update="Assets\RegistryPreview\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,57 @@
// 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;
namespace RegistryPreviewUILib
{
/// <summary>
/// Class representing an each item in the list view, each one a Registry Value.
/// </summary>
public class RegistryValue
{
// Static members
private static Uri uriStringValue = new Uri("ms-appx:///Assets/RegistryPreview/string32.png");
private static Uri uriBinaryValue = new Uri("ms-appx:///Assets/RegistryPreview/data32.png");
private static Uri uriDeleteValue = new Uri("ms-appx:///Assets/RegistryPreview/deleted-value32.png");
private static Uri uriErrorValue = new Uri("ms-appx:///Assets/RegistryPreview/error32.png");
public string Name { get; set; }
public string Type { get; set; }
public string Value { get; set; }
public string ToolTipText { get; set; }
public Uri ImageUri
{
// Based off the Type of the item, pass back the correct image Uri used by the Binding of the DataGrid
get
{
switch (Type)
{
case "REG_SZ":
case "REG_EXPAND_SZ":
case "REG_MULTI_SZ":
return uriStringValue;
case "ERROR":
return uriErrorValue;
case "":
return uriDeleteValue;
}
return uriBinaryValue;
}
}
public RegistryValue(string name, string type, string value)
{
this.Name = name;
this.Type = type;
this.Value = value;
this.ToolTipText = string.Empty;
}
}
}

View File

@@ -0,0 +1,17 @@
// 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 Microsoft.Windows.ApplicationModel.Resources;
namespace RegistryPreviewUILib
{
internal static class ResourceLoaderInstance
{
internal static ResourceLoader ResourceLoader { get; private set; }
static ResourceLoaderInstance()
{
ResourceLoader = new Microsoft.Windows.ApplicationModel.Resources.ResourceLoader("PowerToys.RegistryPreviewUILib.pri", "PowerToys.RegistryPreviewUILib/Resources");
}
}
}

View File

@@ -0,0 +1,40 @@
// 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.Runtime.InteropServices;
namespace RegistryPreviewUILib
{
// Workaround for File Pickers that don't work while running as admin, per:
// https://github.com/microsoft/WindowsAppSDK/issues/2504
public static partial class SaveFilePicker
{
[DllImport("comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool GetSaveFileName(ref FileName saveFileName);
public static string ShowDialog(IntPtr windowHandle, string suggestedFilename, string filter, string dialogTitle)
{
FileName saveFileName = default(FileName);
saveFileName.StructSize = Marshal.SizeOf(saveFileName);
saveFileName.HwndOwner = windowHandle;
saveFileName.Filter = filter;
saveFileName.File = new string(new char[256]);
saveFileName.MaxFile = saveFileName.File.Length;
saveFileName.File = string.Concat(suggestedFilename, saveFileName.File);
saveFileName.FileTitle = new string(new char[64]);
saveFileName.MaxFileTitle = saveFileName.FileTitle.Length;
saveFileName.Title = dialogTitle;
saveFileName.DefExt = "reg";
if (GetSaveFileName(ref saveFileName))
{
return saveFileName.File;
}
return string.Empty;
}
}
}

View File

@@ -0,0 +1,263 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="EditButton.Label" xml:space="preserve">
<value>Edit</value>
</data>
<data name="ErrorDialogTitle" xml:space="preserve">
<value>Error</value>
</data>
<data name="FileEditorError" xml:space="preserve">
<value>The REG file editor could not be opened.</value>
</data>
<data name="FileNotFound" xml:space="preserve">
<value>File was not found</value>
</data>
<data name="FileSaveError" xml:space="preserve">
<value>The REG file cannot be written to.</value>
</data>
<data name="FilterAllFiles" xml:space="preserve">
<value>All files (*.*)</value>
</data>
<data name="FilterRegistryName" xml:space="preserve">
<value>Registry files (*.reg)</value>
</data>
<data name="InvalidBinary" xml:space="preserve">
<value>(Invalid binary value)</value>
</data>
<data name="InvalidDword" xml:space="preserve">
<value>(Invalid DWORD (32-bit) value)</value>
</data>
<data name="InvalidQword" xml:space="preserve">
<value>(Invalid QWORD (64-bit) value)</value>
</data>
<data name="InvalidRegistryFile" xml:space="preserve">
<value> appears to be an invalid registry file.</value>
</data>
<data name="InvalidRegistryFileTitle" xml:space="preserve">
<value>File was not a Registry file</value>
</data>
<data name="InvalidString" xml:space="preserve">
<value>(Invalid string value)</value>
</data>
<data name="LargeRegistryFile" xml:space="preserve">
<value> is larger than 10MB which is too large for this application.</value>
</data>
<data name="LargeRegistryFileTitle" xml:space="preserve">
<value>File is too large</value>
</data>
<data name="NameColumn.Header" xml:space="preserve">
<value>Name</value>
</data>
<data name="NoNodesFoundInFile" xml:space="preserve">
<value>No valid Keys were found</value>
</data>
<data name="OkButtonText" xml:space="preserve">
<value>OK</value>
<comment>as on the OK button</comment>
</data>
<data name="OpenButton.Label" xml:space="preserve">
<value>Open…</value>
</data>
<data name="OpenDialogTitle" xml:space="preserve">
<value>Open Registry file</value>
</data>
<data name="RefreshButton.Label" xml:space="preserve">
<value>Reload</value>
</data>
<data name="RegistryButton.Label" xml:space="preserve">
<value>Open Registry Editor</value>
</data>
<data name="RegistryJumpToKeyButton.Label" xml:space="preserve">
<value>Open Key</value>
</data>
<data name="SaveDialogTitle" xml:space="preserve">
<value>Save As</value>
</data>
<data name="SaveAsButton.Label" xml:space="preserve">
<value>Save as…</value>
</data>
<data name="SaveButton.Label" xml:space="preserve">
<value>Save</value>
</data>
<data name="SuggestFileName" xml:space="preserve">
<value>New Registry file</value>
</data>
<data name="textBox.PlaceholderText" xml:space="preserve">
<value>Registry file text will appear here</value>
</data>
<data name="titleBarText.ApplicationTitle" xml:space="preserve">
<value>Registry Preview</value>
</data>
<data name="ToolTipAddedKey" xml:space="preserve">
<value>Key will be added, if needed</value>
</data>
<data name="ToolTipBinaryValue" xml:space="preserve">
<value>Binary value will be updated</value>
</data>
<data name="ToolTipDeletedKey" xml:space="preserve">
<value>Key will be deleted</value>
</data>
<data name="ToolTipDeletedValue" xml:space="preserve">
<value>Value will be deleted</value>
</data>
<data name="ToolTipErrorKey" xml:space="preserve">
<value>Key couldn't be parsed</value>
</data>
<data name="ToolTipErrorValue" xml:space="preserve">
<value>Value has a syntax error</value>
</data>
<data name="ToolTipStringValue" xml:space="preserve">
<value>String value will be updated</value>
</data>
<data name="TypeColumn.Header" xml:space="preserve">
<value>Type</value>
<comment>noun, as in "a type of..."</comment>
</data>
<data name="UACDialogError" xml:space="preserve">
<value>Click Yes on the User Account Control dialog to run the application.</value>
</data>
<data name="UACDialogTitle" xml:space="preserve">
<value>User Account Control</value>
</data>
<data name="ValueColumn.Header" xml:space="preserve">
<value>Data</value>
</data>
<data name="WriteButton.Label" xml:space="preserve">
<value>Write to Registry</value>
</data>
<data name="YesNoCancelDialogCloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="YesNoCancelDialogContent" xml:space="preserve">
<value>Save changes?</value>
</data>
<data name="YesNoCancelDialogPrimaryButtonText" xml:space="preserve">
<value>Save</value>
</data>
<data name="YesNoCancelDialogSecondaryButtonText" xml:space="preserve">
<value>Don't save</value>
</data>
<data name="YesNoCancelDialogTitle" xml:space="preserve">
<value>Registry Preview</value>
</data>
<data name="ZeroLength" xml:space="preserve">
<value>(zero-length binary value)</value>
</data>
</root>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="RegistryPreview.app"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>