Skip to content

Commit b3fab9e

Browse files
Feature: Redesign password dialogs (#3231)
* Feature: Migrate password dialogs to childwindow * Feature: Migrate password dialogs to childwindow / code cleanup * Chore: Refactor dialogs * Feature: Migrate dialog * Chore: Migrate to new dialog helper * Feature: Redesign dialogs * Update next-release.md * Update Source/NETworkManager/ViewModels/SettingsProfilesViewModel.cs Co-authored-by: Copilot <[email protected]> * Remove duplicate call to OnProfileManagerDialogClose * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 16e3415 commit b3fab9e

31 files changed

+463
-511
lines changed

Source/NETworkManager.Utilities/SecureStringHelper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public static class SecureStringHelper
99
public static string ConvertToString(SecureString secureString)
1010
{
1111
var valuePtr = IntPtr.Zero;
12+
1213
try
1314
{
1415
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(secureString);
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using MahApps.Metro.SimpleChildWindow;
2+
using NETworkManager.Localization.Resources;
3+
using NETworkManager.Settings;
4+
using NETworkManager.Utilities;
5+
using NETworkManager.ViewModels;
6+
using NETworkManager.Views;
7+
using System.Threading.Tasks;
8+
using System.Windows;
9+
10+
namespace NETworkManager
11+
{
12+
/// <summary>
13+
/// Helper class for showing dialog messages.
14+
/// </summary>
15+
public static class DialogHelper
16+
{
17+
/// <summary>
18+
/// Displays a modal message dialog with an OK button, allowing the user to acknowledge the message before
19+
/// continuing.
20+
/// </summary>
21+
/// <remarks>The dialog is shown as a child window of the specified parent. The method is
22+
/// asynchronous and returns when the dialog is dismissed by the user.</remarks>
23+
/// <param name="parentWindow">The parent window that will host the message dialog. Cannot be null.</param>
24+
/// <param name="title">The title text displayed in the message dialog window. Cannot be null.</param>
25+
/// <param name="message">The main message content shown in the dialog. Cannot be null.</param>
26+
/// <param name="icon">The icon to display in the dialog, indicating the message type. Defaults to Info if not specified.</param>
27+
/// <param name="buttonOKText">The text to display on the OK button. If null, a default value is used.</param>
28+
/// <returns>A task that completes when the user closes the message dialog.</returns>
29+
public static Task ShowOKMessageAsync(Window parentWindow, string title, string message, ChildWindowIcon icon = ChildWindowIcon.Info, string buttonOKText = null)
30+
{
31+
buttonOKText ??= Strings.OK;
32+
33+
var childWindow = new OKMessageChildWindow();
34+
35+
var childWindowViewModel = new OKMessageViewModel(_ =>
36+
{
37+
childWindow.IsOpen = false;
38+
ConfigurationManager.Current.IsChildWindowOpen = false;
39+
}, message, icon, buttonOKText);
40+
41+
childWindow.Title = title;
42+
43+
childWindow.DataContext = childWindowViewModel;
44+
45+
ConfigurationManager.Current.IsChildWindowOpen = true;
46+
47+
return parentWindow.ShowChildWindowAsync(childWindow);
48+
}
49+
50+
/// <summary>
51+
/// Displays an asynchronous modal dialog with OK and Cancel buttons, allowing the user to confirm or cancel an
52+
/// action.
53+
/// </summary>
54+
/// <remarks>The dialog is shown as a child window of the specified parent. The method does not
55+
/// return until the user closes the dialog. Custom button text and icon can be provided to tailor the dialog to
56+
/// specific scenarios.</remarks>
57+
/// <param name="parentWindow">The parent window that hosts the child dialog. Cannot be null.</param>
58+
/// <param name="title">The title text displayed in the dialog window.</param>
59+
/// <param name="message">The message content shown to the user in the dialog.</param>
60+
/// <param name="icon">The icon displayed in the dialog to indicate the message type. Defaults to Info.</param>
61+
/// <param name="buttonOKText">The text label for the OK button. If null, a default value is used.</param>
62+
/// <param name="buttonCancelText">The text label for the Cancel button. If null, a default value is used.</param>
63+
/// <returns>A task that represents the asynchronous operation. The task result is <see langword="true"/> if the user
64+
/// clicks OK; otherwise, <see langword="false"/>.</returns>
65+
public static async Task<bool> ShowOKCancelMessageAsync(Window parentWindow, string title, string message, ChildWindowIcon icon = ChildWindowIcon.Info, string buttonOKText = null, string buttonCancelText = null)
66+
{
67+
buttonOKText ??= Strings.OK;
68+
buttonCancelText ??= Strings.Cancel;
69+
70+
var result = false;
71+
72+
var childWindow = new OKCancelMessageChildWindow();
73+
74+
var childWindowViewModel = new OKCancelMessageViewModel(_ =>
75+
{
76+
childWindow.IsOpen = false;
77+
ConfigurationManager.Current.IsChildWindowOpen = false;
78+
79+
result = true;
80+
},
81+
_ =>
82+
{
83+
childWindow.IsOpen = false;
84+
ConfigurationManager.Current.IsChildWindowOpen = false;
85+
},
86+
message, icon, buttonOKText, buttonCancelText);
87+
88+
childWindow.Title = title;
89+
childWindow.DataContext = childWindowViewModel;
90+
91+
ConfigurationManager.Current.IsChildWindowOpen = true;
92+
93+
await parentWindow.ShowChildWindowAsync(childWindow);
94+
95+
return result;
96+
}
97+
}
98+
}

Source/NETworkManager/NETworkManager.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@
141141
<XamlRuntime>Wpf</XamlRuntime>
142142
<SubType>Designer</SubType>
143143
</Page>
144-
<Page Update="Views\OKCancelInfoMessageChildWindow.xaml">
144+
<Page Update="Views\OKCancelMessageChildWindow.xaml">
145145
<Generator>MSBuild:Compile</Generator>
146146
<XamlRuntime>Wpf</XamlRuntime>
147147
<SubType>Designer</SubType>

Source/NETworkManager/ProfileDialogManager.cs

Lines changed: 23 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using NETworkManager.Models.PuTTY;
77
using NETworkManager.Models.RemoteDesktop;
88
using NETworkManager.Profiles;
9+
using NETworkManager.Utilities;
910
using NETworkManager.ViewModels;
1011
using NETworkManager.Views;
1112
using System;
@@ -560,39 +561,24 @@ public static Task ShowCopyAsProfileDialog(Window parentWindow, IProfileManagerM
560561
return parentWindow.ShowChildWindowAsync(childWindow);
561562
}
562563

563-
public static Task ShowDeleteProfileDialog(Window parentWindow, IProfileManagerMinimal viewModel,
564+
public static async Task ShowDeleteProfileDialog(Window parentWindow, IProfileManagerMinimal viewModel,
564565
IList<ProfileInfo> profiles)
565566
{
566-
var childWindow = new OKCancelInfoMessageChildWindow();
567567

568-
OKCancelInfoMessageViewModel childWindowViewModel = new(_ =>
569-
{
570-
childWindow.IsOpen = false;
571-
Settings.ConfigurationManager.Current.IsChildWindowOpen = false;
572-
573-
viewModel.OnProfileManagerDialogClose();
574-
575-
ProfileManager.RemoveProfiles(profiles);
576-
}, _ =>
577-
{
578-
childWindow.IsOpen = false;
579-
Settings.ConfigurationManager.Current.IsChildWindowOpen = false;
580-
581-
viewModel.OnProfileManagerDialogClose();
582-
},
583-
profiles.Count == 1 ? Strings.DeleteProfileMessage : Strings.DeleteProfilesMessage,
584-
Strings.Delete
585-
);
586-
587-
childWindow.Title = profiles.Count == 1 ? Strings.DeleteProfile : Strings.DeleteProfiles;
568+
viewModel.OnProfileManagerDialogOpen();
588569

589-
childWindow.DataContext = childWindowViewModel;
570+
var result = await DialogHelper.ShowOKCancelMessageAsync(parentWindow,
571+
profiles.Count == 1 ? Strings.DeleteProfile : Strings.DeleteProfiles,
572+
profiles.Count == 1 ? Strings.DeleteProfileMessage : Strings.DeleteProfilesMessage,
573+
ChildWindowIcon.Info,
574+
Strings.Delete);
590575

591-
viewModel.OnProfileManagerDialogOpen();
576+
viewModel.OnProfileManagerDialogClose();
592577

593-
Settings.ConfigurationManager.Current.IsChildWindowOpen = true;
578+
if (!result)
579+
return;
594580

595-
return parentWindow.ShowChildWindowAsync(childWindow);
581+
ProfileManager.RemoveProfiles(profiles);
596582
}
597583

598584
#endregion
@@ -662,40 +648,24 @@ public static Task ShowEditGroupDialog(Window parentWindow, IProfileManagerMinim
662648
return parentWindow.ShowChildWindowAsync(childWindow);
663649
}
664650

665-
public static Task ShowDeleteGroupDialog(Window parentWindow, IProfileManagerMinimal viewModel,
651+
public static async Task ShowDeleteGroupDialog(Window parentWindow, IProfileManagerMinimal viewModel,
666652
GroupInfo group)
667653
{
668-
var childWindow = new OKCancelInfoMessageChildWindow();
669-
670-
OKCancelInfoMessageViewModel childWindowViewModel = new(_ =>
671-
{
672-
childWindow.IsOpen = false;
673-
Settings.ConfigurationManager.Current.IsChildWindowOpen = false;
674-
675-
viewModel.OnProfileManagerDialogClose();
676-
677-
ProfileManager.RemoveGroup(group);
678-
}, _ =>
679-
{
680-
childWindow.IsOpen = false;
681-
Settings.ConfigurationManager.Current.IsChildWindowOpen = false;
654+
viewModel.OnProfileManagerDialogOpen();
682655

683-
viewModel.OnProfileManagerDialogClose();
684-
},
656+
var result = await DialogHelper.ShowOKCancelMessageAsync(parentWindow,
657+
Strings.DeleteGroup,
685658
Strings.DeleteGroupMessage,
686-
Strings.Delete
687-
);
688-
689-
childWindow.Title = Strings.DeleteGroup;
659+
ChildWindowIcon.Info,
660+
Strings.Delete);
690661

691-
childWindow.DataContext = childWindowViewModel;
662+
viewModel.OnProfileManagerDialogClose();
692663

693-
viewModel.OnProfileManagerDialogOpen();
664+
if (!result)
665+
return;
694666

695-
Settings.ConfigurationManager.Current.IsChildWindowOpen = true;
696-
697-
return parentWindow.ShowChildWindowAsync(childWindow);
667+
ProfileManager.RemoveGroup(group);
698668
}
699669

700670
#endregion
701-
}
671+
}

Source/NETworkManager/ViewModels/AWSSessionManagerSettingsViewModel.cs

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -275,32 +275,19 @@ public async Task EditAWSProfile()
275275
await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog);
276276
}
277277

278-
private Task DeleteAWSProfile()
278+
private async Task DeleteAWSProfile()
279279
{
280-
var childWindow = new OKCancelInfoMessageChildWindow();
280+
var result = await DialogHelper.ShowOKCancelMessageAsync(Application.Current.MainWindow,
281+
Strings.DeleteAWSProfile,
282+
Strings.DeleteAWSProfileMessage,
283+
ChildWindowIcon.Info,
284+
Strings.Delete);
281285

282-
var childWindowViewModel = new OKCancelInfoMessageViewModel(_ =>
283-
{
284-
childWindow.IsOpen = false;
285-
ConfigurationManager.Current.IsChildWindowOpen = false;
286286

287-
SettingsManager.Current.AWSSessionManager_AWSProfiles.Remove(SelectedAWSProfile);
288-
}, _ =>
289-
{
290-
childWindow.IsOpen = false;
291-
ConfigurationManager.Current.IsChildWindowOpen = false;
292-
},
293-
Strings.DeleteAWSProfileMessage,
294-
Strings.Delete
295-
);
287+
if (!result)
288+
return;
296289

297-
childWindow.Title = Strings.DeleteAWSProfile;
298-
299-
childWindow.DataContext = childWindowViewModel;
300-
301-
ConfigurationManager.Current.IsChildWindowOpen = true;
302-
303-
return (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow);
290+
SettingsManager.Current.AWSSessionManager_AWSProfiles.Remove(SelectedAWSProfile);
304291
}
305292

306293
private async Task Configure()

Source/NETworkManager/ViewModels/DNSLookupSettingsViewModel.cs

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -357,32 +357,18 @@ public async Task EditDNSServer()
357357
await _dialogCoordinator.ShowMetroDialogAsync(this, customDialog);
358358
}
359359

360-
private Task DeleteDNSServer()
360+
private async Task DeleteDNSServer()
361361
{
362-
var childWindow = new OKCancelInfoMessageChildWindow();
362+
var result = await DialogHelper.ShowOKCancelMessageAsync(Application.Current.MainWindow,
363+
Strings.DeleteDNSServer,
364+
Strings.DeleteDNSServerMessage,
365+
ChildWindowIcon.Info,
366+
Strings.Delete);
363367

364-
var childWindowViewModel = new OKCancelInfoMessageViewModel(_ =>
365-
{
366-
childWindow.IsOpen = false;
367-
ConfigurationManager.Current.IsChildWindowOpen = false;
368-
369-
SettingsManager.Current.DNSLookup_DNSServers.Remove(SelectedDNSServer);
370-
}, _ =>
371-
{
372-
childWindow.IsOpen = false;
373-
ConfigurationManager.Current.IsChildWindowOpen = false;
374-
},
375-
Strings.DeleteDNSServerMessage,
376-
Strings.Delete
377-
);
378-
379-
childWindow.Title = Strings.DeleteDNSServer;
380-
381-
childWindow.DataContext = childWindowViewModel;
382-
383-
ConfigurationManager.Current.IsChildWindowOpen = true;
368+
if (!result)
369+
return;
384370

385-
return (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow);
371+
SettingsManager.Current.DNSLookup_DNSServers.Remove(SelectedDNSServer);
386372
}
387373

388374
#endregion

Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -391,38 +391,28 @@ private async Task DeleteEntryAction()
391391
{
392392
IsModifying = true;
393393

394-
var childWindow = new OKCancelInfoMessageChildWindow();
395-
396-
var childWindowViewModel = new OKCancelInfoMessageViewModel(async _ =>
397-
{
398-
childWindow.IsOpen = false;
399-
ConfigurationManager.Current.IsChildWindowOpen = false;
400-
401-
var result = await HostsFileEditor.DeleteEntryAsync(SelectedResult);
402-
403-
if (result != HostsFileEntryModifyResult.Success)
404-
await ShowErrorMessageAsync(result);
405-
406-
IsModifying = false;
407-
}, _ =>
408-
{
409-
childWindow.IsOpen = false;
410-
ConfigurationManager.Current.IsChildWindowOpen = false;
411-
412-
IsModifying = false;
413-
},
394+
var result = await DialogHelper.ShowOKCancelMessageAsync(Application.Current.MainWindow,
395+
Strings.DeleteEntry,
414396
string.Format(Strings.DeleteHostsFileEntryMessage, SelectedResult.IPAddress, SelectedResult.Hostname,
415397
string.IsNullOrEmpty(SelectedResult.Comment) ? "" : $"# {SelectedResult.Comment}"),
398+
ChildWindowIcon.Info,
416399
Strings.Delete
417-
);
400+
);
418401

419-
childWindow.Title = Strings.DeleteEntry;
420402

421-
childWindow.DataContext = childWindowViewModel;
403+
if (!result)
404+
{
405+
IsModifying = false;
406+
return;
407+
}
422408

423-
ConfigurationManager.Current.IsChildWindowOpen = true;
424409

425-
await (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow);
410+
var modifyResult = await HostsFileEditor.DeleteEntryAsync(SelectedResult);
411+
412+
if (modifyResult != HostsFileEntryModifyResult.Success)
413+
await ShowErrorMessageAsync(modifyResult);
414+
415+
IsModifying = false;
426416
}
427417

428418
private bool ModifyEntry_CanExecute(object obj)
@@ -452,7 +442,7 @@ private async Task ShowErrorMessageAsync(HostsFileEntryModifyResult result)
452442
{
453443
childWindow.IsOpen = false;
454444
ConfigurationManager.Current.IsChildWindowOpen = false;
455-
}, message, Strings.OK, ChildWindowIcon.Error);
445+
}, message, ChildWindowIcon.Error, Strings.OK);
456446

457447
childWindow.Title = Strings.Error;
458448

@@ -479,7 +469,7 @@ private async Task RestartAsAdminAction()
479469
{
480470
childWindow.IsOpen = false;
481471
ConfigurationManager.Current.IsChildWindowOpen = false;
482-
}, ex.Message, Strings.OK, ChildWindowIcon.Error);
472+
}, ex.Message, ChildWindowIcon.Error, Strings.OK);
483473

484474
childWindow.Title = Strings.Error;
485475

0 commit comments

Comments
 (0)