diff --git a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Events.cs b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Events.cs
index 0df3a5c0f5ed..ab9fd900b21b 100644
--- a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Events.cs
+++ b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Events.cs
@@ -23,6 +23,9 @@ public sealed partial class RegistryPreviewMainPage : Page
{
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
+ // Indicator if we loaded/reloaded/saved a file and need to skip TextChanged event one time.
+ private static bool editorContentChangedScripted;
+
///
/// Event that is will prevent the app from closing if the "save file" flag is active
///
@@ -107,11 +110,17 @@ private async void OpenButton_Click(object sender, RoutedEventArgs e)
{
case ContentDialogResult.Primary:
// Save, then continue the file open
- SaveFile();
+ if (!AskFileName(string.Empty) ||
+ !SaveFile())
+ {
+ return;
+ }
+
break;
case ContentDialogResult.Secondary:
// Don't save and continue the file open!
saveButton.IsEnabled = false;
+ UpdateUnsavedFileIndicator(false);
break;
default:
// Don't open the new file!
@@ -138,11 +147,15 @@ private async void OpenButton_Click(object sender, RoutedEventArgs e)
{
// mute the TextChanged handler to make for clean UI
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;
+ editorContentChangedScripted = true;
+
+ // update file name
_appFileName = storageFile.Path;
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName));
// disable the Save button as it's a new file
saveButton.IsEnabled = false;
+ UpdateUnsavedFileIndicator(false);
// Restore the event handler as we're loaded
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
@@ -154,7 +167,13 @@ private async void OpenButton_Click(object sender, RoutedEventArgs e)
///
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
- SaveFile();
+ if (!AskFileName(string.Empty))
+ {
+ return;
+ }
+
+ // save and update window title
+ _ = SaveFile();
}
///
@@ -162,23 +181,19 @@ private void SaveButton_Click(object sender, RoutedEventArgs e)
///
private async 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"));
+ // mute the TextChanged handler to make for clean UI
+ editorContentChangedScripted = true;
+ MonacoEditor.TextChanged -= MonacoEditor_TextChanged;
- if (filename == string.Empty)
+ if (!AskFileName(_appFileName) || !SaveFile())
{
return;
}
- _appFileName = filename;
- SaveFile();
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName));
+
+ // restore the TextChanged handler
+ MonacoEditor.TextChanged += MonacoEditor_TextChanged;
}
///
@@ -187,12 +202,14 @@ private async void SaveAsButton_Click(object sender, RoutedEventArgs e)
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
{
// mute the TextChanged handler to make for clean UI
+ editorContentChangedScripted = true;
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;
// reload the current Registry file and update the toolbar accordingly.
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName), true, true);
saveButton.IsEnabled = false;
+ UpdateUnsavedFileIndicator(false);
// restore the TextChanged handler
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
@@ -257,7 +274,12 @@ private async void WriteButton_Click(object sender, RoutedEventArgs e)
{
case ContentDialogResult.Primary:
// Save, then continue the file open
- SaveFile();
+ if (!AskFileName(string.Empty) ||
+ !SaveFile())
+ {
+ return;
+ }
+
break;
case ContentDialogResult.Secondary:
// Don't save and continue the file open!
@@ -354,7 +376,13 @@ private void MonacoEditor_TextChanged(object sender, EventArgs e)
_dispatcherQueue.TryEnqueue(() =>
{
RefreshRegistryFile();
- saveButton.IsEnabled = true;
+ if (!editorContentChangedScripted)
+ {
+ saveButton.IsEnabled = true;
+ UpdateUnsavedFileIndicator(true);
+ }
+
+ editorContentChangedScripted = false;
});
}
}
diff --git a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs
index b8fc6e4e1d9a..dfce7f88398c 100644
--- a/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs
+++ b/src/modules/registrypreview/RegistryPreviewUILib/RegistryPreviewMainPage.Utilities.cs
@@ -11,6 +11,7 @@
using System.Linq;
using System.Reflection;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.UI.Input;
@@ -22,6 +23,9 @@ namespace RegistryPreviewUILib
{
public sealed partial class RegistryPreviewMainPage : Page
{
+ private static readonly string _unsavedFileIndicator = "* ";
+ private static readonly char[] _unsavedFileIndicatorChars = [' ', '*'];
+
private static SemaphoreSlim _dialogSemaphore = new(1);
private string lastKeyPath;
@@ -830,10 +834,16 @@ private async void HandleDirtyClosing(string title, string content, string prima
{
case ContentDialogResult.Primary:
// Save, then close
- SaveFile();
+ if (!AskFileName(string.Empty) ||
+ !SaveFile())
+ {
+ return;
+ }
+
break;
case ContentDialogResult.Secondary:
// Don't save, and then close!
+ UpdateUnsavedFileIndicator(false);
saveButton.IsEnabled = false;
break;
default:
@@ -902,11 +912,63 @@ public void ChangeCursor(UIElement uiElement, bool wait)
type.InvokeMember("ProtectedCursor", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, uiElement, new object[] { cursor }, CultureInfo.InvariantCulture);
}
+ public void UpdateUnsavedFileIndicator(bool show)
+ {
+ // get and cut current title
+ string currentTitle = Regex.Replace(_mainWindow.Title, APPNAME + @"$|\s-\s" + APPNAME + @"$", string.Empty);
+
+ // verify
+ bool titleContainsIndicator = currentTitle.StartsWith(_unsavedFileIndicator, StringComparison.CurrentCultureIgnoreCase);
+
+ // update
+ if (!titleContainsIndicator && show)
+ {
+ _updateWindowTitleFunction(_unsavedFileIndicator + currentTitle);
+ }
+ else if (titleContainsIndicator && !show)
+ {
+ _updateWindowTitleFunction(currentTitle.TrimStart(_unsavedFileIndicatorChars));
+ }
+ }
+
+ ///
+ /// Ask the user for the file path if it is unknown because of an unsaved file
+ ///
+ /// If not empty always ask for a file path and use the value as name.
+ /// Returns true if user selected a path, otherwise false
+ public bool AskFileName(string fileName)
+ {
+ if (string.IsNullOrEmpty(_appFileName) || !string.IsNullOrEmpty(fileName) )
+ {
+ string fName = string.IsNullOrEmpty(fileName) ? resourceLoader.GetString("SuggestFileName") : fileName;
+
+ // 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,
+ fName,
+ resourceLoader.GetString("FilterRegistryName") + '\0' + "*.reg" + '\0' + resourceLoader.GetString("FilterAllFiles") + '\0' + "*.*" + '\0' + '\0',
+ resourceLoader.GetString("SaveDialogTitle"));
+
+ if (filename == string.Empty)
+ {
+ return false;
+ }
+
+ _appFileName = filename;
+ }
+
+ return true;
+ }
+
///
/// Wrapper method that saves the current file in place, using the current text in editor.
///
- private void SaveFile()
+ private bool SaveFile()
{
+ bool saveSuccess = true;
+
ChangeCursor(gridPreview, true);
// set up the FileStream for all writing
@@ -930,10 +992,13 @@ private void SaveFile()
streamWriter.Close();
// only change when the save is successful
+ _updateWindowTitleFunction(_appFileName);
saveButton.IsEnabled = false;
}
catch (UnauthorizedAccessException ex)
{
+ saveSuccess = false;
+
// this exception is thrown if the file is there but marked as read only
ShowMessageBox(
resourceLoader.GetString("ErrorDialogTitle"),
@@ -942,6 +1007,8 @@ private void SaveFile()
}
catch
{
+ saveSuccess = false;
+
// this catch handles all other exceptions thrown when trying to write the file out
ShowMessageBox(
resourceLoader.GetString("ErrorDialogTitle"),
@@ -959,6 +1026,8 @@ private void SaveFile()
// restore the cursor
ChangeCursor(gridPreview, false);
+
+ return saveSuccess;
}
///