Skip to content

[RegPreview] Various improvements on how files are saved #37628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ 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.
// (Solves the problem that enabling the event handler fires it one time.)
private static bool editorContentChangedScripted;

/// <summary>
/// Event that is will prevent the app from closing if the "save file" flag is active
/// </summary>
Expand Down Expand Up @@ -76,6 +80,67 @@ private async void GridPreview_Loaded(object sender, RoutedEventArgs e)
MonacoEditor.Focus(FocusState.Programmatic);
}

/// <summary>
/// New button action: Ask to save last changes and reset editor content to reg header only
/// </summary>
private async void NewButton_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 new action
if (!AskFileName(string.Empty) ||
!SaveFile())
{
return;
}

break;
case ContentDialogResult.Secondary:
// Don't save and continue the new action!
break;
default:
// Don't open the new action!
return;
}
}

// mute the TextChanged handler to make for clean UI
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;

// reset editor, file info and ui.
_appFileName = string.Empty;
ResetEditorAndFile();

// disable buttons that do not make sense
UpdateUnsavedFileState(false);
refreshButton.IsEnabled = false;

// restore the TextChanged handler
ButtonAction_RestoreTextChangedEvent();
}

/// <summary>
/// Uses a picker to select a new file to open
/// </summary>
Expand Down Expand Up @@ -106,11 +171,15 @@ 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;
break;
default:
// Don't open the new file!
Expand All @@ -137,14 +206,16 @@ private async void OpenButton_Click(object sender, RoutedEventArgs e)
{
// mute the TextChanged handler to make for clean UI
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;

// update file name
_appFileName = storageFile.Path;
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName));

// disable the Save button as it's a new file
saveButton.IsEnabled = false;
UpdateUnsavedFileState(false);

// Restore the event handler as we're loaded
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
ButtonAction_RestoreTextChangedEvent();
}
}

Expand All @@ -153,66 +224,49 @@ private async void OpenButton_Click(object sender, RoutedEventArgs e)
/// </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 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"));

if (filename == string.Empty)
if (!AskFileName(string.Empty))
{
return;
}

_appFileName = filename;
SaveFile();
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName));
// save and update window title
// error handling and ui update happens in SaveFile() method
_ = SaveFile();
}

/// <summary>
/// Reloads the current REG file from storage
/// Uses a picker to save out a copy of the current reg file
/// </summary>
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
private async void SaveAsButton_Click(object sender, RoutedEventArgs e)
{
// mute the TextChanged handler to make for clean UI
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;

// reload the current Registry file and update the toolbar accordingly.
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName), true, true);
if (!AskFileName(_appFileName) || !SaveFile())
{
return;
}

// disable the Save button as it's a new file
saveButton.IsEnabled = false;
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName));

// restore the TextChanged handler
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
ButtonAction_RestoreTextChangedEvent();
}

/// <summary>
/// Resets the editor content
/// Reloads the current REG file from storage
/// </summary>
private async void NewButton_Click(object sender, RoutedEventArgs e)
private async void RefreshButton_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"),
Content = resourceLoader.GetString("ReloadDialogContent"),
PrimaryButtonText = resourceLoader.GetString("ReloadDialogPrimaryButtonText"),
CloseButtonText = resourceLoader.GetString("ReloadDialogCloseButtonText"),
DefaultButton = ContentDialogButton.Primary,
};

Expand All @@ -227,32 +281,25 @@ private async void NewButton_Click(object sender, RoutedEventArgs e)
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;
// Don't save and continue the reload action!
break;
default:
// Don't open the new file!
// Don't continue the reload action!
return;
}
}

// mute the TextChanged handler to make for clean UI
MonacoEditor.TextChanged -= MonacoEditor_TextChanged;

// reset editor, file info and ui.
_appFileName = string.Empty;
ResetEditorAndFile();
// reload the current Registry file and update the toolbar accordingly.
UpdateToolBarAndUI(await OpenRegistryFile(_appFileName), true, true);

// restore the TextChanged handler
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
// disable the Save button as it's a new file
UpdateUnsavedFileState(false);

// disable buttons that do not make sense
saveButton.IsEnabled = false;
refreshButton.IsEnabled = false;
// restore the TextChanged handler
ButtonAction_RestoreTextChangedEvent();
}

/// <summary>
Expand Down Expand Up @@ -313,15 +360,20 @@ private async void WriteButton_Click(object sender, RoutedEventArgs e)
switch (contentDialogResult)
{
case ContentDialogResult.Primary:
// Save, then continue the file open
SaveFile();
// Save, then continue the merge action
if (!AskFileName(string.Empty) ||
!SaveFile())
{
return;
}

break;
case ContentDialogResult.Secondary:
// Don't save and continue the file open!
saveButton.IsEnabled = false;
// Don't save and continue the merge action!
UpdateUnsavedFileState(false);
break;
default:
// Don't open the new file!
// Don't merge the file!
return;
}
}
Expand Down Expand Up @@ -411,10 +463,29 @@ private void MonacoEditor_TextChanged(object sender, EventArgs e)
_dispatcherQueue.TryEnqueue(() =>
{
RefreshRegistryFile();
saveButton.IsEnabled = true;
if (!editorContentChangedScripted)
{
UpdateUnsavedFileState(true);
}

editorContentChangedScripted = false;
});
}

/// <summary>
/// Sets indicator for programatic text change and adds text changed handler
/// </summary>
/// <remarks>
/// Use this always, if button actions temporary disable the text changed event
/// </remarks>
private void ButtonAction_RestoreTextChangedEvent()
{
// Solves the problem that enabling the event handler fires it one time.
// These one time fired event would causes wrong unsaved changes state.
editorContentChangedScripted = true;
MonacoEditor.TextChanged += MonacoEditor_TextChanged;
}

// Commands to show data preview
public void ButtonExtendedPreview_Click(object sender, RoutedEventArgs e)
{
Expand Down
Loading
Loading