From 1cdd57dc77ba5cbafa827d5c82d88943a97289c5 Mon Sep 17 00:00:00 2001 From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com> Date: Mon, 7 Jul 2025 01:37:53 +0200 Subject: [PATCH 1/6] Feature: HostsFileEditor add/edit --- .../Resources/Strings.Designer.cs | 42 ++++-- .../Resources/Strings.resx | 30 ++-- .../NETworkManager.Utilities/RegexHelper.cs | 5 +- .../HostsFileEntryHostnameValidator.cs | 34 +++++ .../IPAddressValidator.cs | 19 +++ .../IPv6AddressValidator.cs | 17 +-- .../HostsFileEditorEntryViewModel.cs | 139 ++++++++++++++++++ .../ViewModels/HostsFileEditorViewModel.cs | 64 +++++++- ...entialsPasswordProfileFileChildWindow.xaml | 2 +- ...ialsPasswordProfileFileChildWindow.xaml.cs | 7 +- .../HostsFileEditorEntryChildWindow.xaml | 122 +++++++++++++++ .../HostsFileEditorEntryChildWindow.xaml.cs | 22 +++ 12 files changed, 454 insertions(+), 49 deletions(-) create mode 100644 Source/NETworkManager.Validators/HostsFileEntryHostnameValidator.cs create mode 100644 Source/NETworkManager.Validators/IPAddressValidator.cs create mode 100644 Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs create mode 100644 Source/NETworkManager/Views/HostsFileEditorEntryChildWindow.xaml create mode 100644 Source/NETworkManager/Views/HostsFileEditorEntryChildWindow.xaml.cs diff --git a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs index 1b033dfbba..38cc9e886d 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.Designer.cs +++ b/Source/NETworkManager.Localization/Resources/Strings.Designer.cs @@ -1015,7 +1015,7 @@ public static string ApplyWindowsKeyCombinations { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Apply Windows key combinations (e.g. ALT+TAB): ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Apply Windows key combinations (e.g., ALT+TAB): ähnelt. /// public static string ApplyWindowsKeyCombinationsLikeAltTab { get { @@ -3495,7 +3495,7 @@ public static string EnterValidBaud { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid domain (like "example.com")! ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid domain (e.g., "example.com")! ähnelt. /// public static string EnterValidDomain { get { @@ -3540,7 +3540,7 @@ public static string EnterValidHostnameAndPort { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid hostname (like "server-01" or "example.com") or a valid IP address (like 192.168.178.1)! ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid hostname (e.g., "server-01" or "example.com") or a valid IP address (e.g., 192.168.178.1)! ähnelt. /// public static string EnterValidHostnameOrIPAddress { get { @@ -3557,6 +3557,15 @@ public static string EnterValidHosts { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid hostname (e.g., "server-01" or "example.com")! Multiple hostnames can be separated with a space. ähnelt. + /// + public static string EnterValidHostsFileEntryHostname { + get { + return ResourceManager.GetString("EnterValidHostsFileEntryHostname", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid IP address! ähnelt. /// @@ -3594,7 +3603,7 @@ public static string EnterValidIPv6Address { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid MAC address (like 00:F1:23:AB:F2:35)! ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid MAC address (e.g., 00:F1:23:AB:F2:35)! ähnelt. /// public static string EnterValidMACAddress { get { @@ -3639,7 +3648,7 @@ public static string EnterValidPortOrPortRange { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid subnet (like 192.168.178.133/26)! ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid subnet (e.g., 192.168.178.133/26)! ähnelt. /// public static string EnterValidSubnet { get { @@ -3648,7 +3657,7 @@ public static string EnterValidSubnet { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid subnetmask (like 255.255.255.0)! ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid subnetmask (e.g., 255.255.255.0)! ähnelt. /// public static string EnterValidSubnetmask { get { @@ -3657,7 +3666,7 @@ public static string EnterValidSubnetmask { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid subnetmask or CIDR (like 255.255.255.0 or /24)! ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid subnetmask or CIDR (e.g., 255.255.255.0 or /24)! ähnelt. /// public static string EnterValidSubnetmaskOrCIDR { get { @@ -3666,7 +3675,7 @@ public static string EnterValidSubnetmaskOrCIDR { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid website (like https://example.com/index.html) ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Enter a valid website (e.g., https://example.com/index.html) ähnelt. /// public static string EnterValidWebsiteUri { get { @@ -3764,6 +3773,15 @@ public static string ExampleGroupDescription { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Primary DNS server ähnelt. + /// + public static string ExampleHostsFileEntryComment { + get { + return ResourceManager.GetString("ExampleHostsFileEntryComment", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Ubuntu Server running Docker with Nextcloud and Traefik... ähnelt. /// @@ -4373,7 +4391,7 @@ public static string HelpMessage_CustomCommandVariables { } /// - /// Sucht eine lokalisierte Zeichenfolge, die URL to a web service that can be reached via http or https and returns an IPv4 address like "xx.xx.xx.xx" as response. ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die URL to a web service that can be reached via http or https and returns an IPv4 address e.g., "xx.xx.xx.xx" as response. ähnelt. /// public static string HelpMessage_CustomPublicIPv4AddressAPI { get { @@ -4382,7 +4400,7 @@ public static string HelpMessage_CustomPublicIPv4AddressAPI { } /// - /// Sucht eine lokalisierte Zeichenfolge, die URL to a web service that can be reached via http or https and returns an IPv6 address like "xxxx:xx:xxx::xx" as response. ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die URL to a web service that can be reached via http or https and returns an IPv6 address e.g., "xxxx:xx:xxx::xx" as response. ähnelt. /// public static string HelpMessage_CustomPublicIPv6AddressAPI { get { @@ -4454,7 +4472,7 @@ public static string HelpMessage_PublicIPv6Address { } /// - /// Sucht eine lokalisierte Zeichenfolge, die SSH hostkey to use for the connection (like "71:b8:f2:6e..."). Only available if the mode is "SSH". ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die SSH hostkey to use for the connection (e.g., "71:b8:f2:6e..."). Only available if the mode is "SSH". ähnelt. /// public static string HelpMessage_PuTTYHostkey { get { @@ -4463,7 +4481,7 @@ public static string HelpMessage_PuTTYHostkey { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Full path to the private key file (like "C:\Users\BornToBeRoot\SSH\private_key.ppk"). Only available if the mode is "SSH". ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Full path to the private key file (e.g., "C:\Users\BornToBeRoot\SSH\private_key.ppk"). Only available if the mode is "SSH". ähnelt. /// public static string HelpMessage_PuTTYPrivateKeyFile { get { diff --git a/Source/NETworkManager.Localization/Resources/Strings.resx b/Source/NETworkManager.Localization/Resources/Strings.resx index 11315b1d1e..dbabea4d53 100644 --- a/Source/NETworkManager.Localization/Resources/Strings.resx +++ b/Source/NETworkManager.Localization/Resources/Strings.resx @@ -679,7 +679,7 @@ Apply - Apply Windows key combinations (e.g. ALT+TAB): + Apply Windows key combinations (e.g., ALT+TAB): Auth @@ -1367,16 +1367,16 @@ Profile files are not affected! Enter a valid IPv4 address! - Enter a valid MAC address (like 00:F1:23:AB:F2:35)! + Enter a valid MAC address (e.g., 00:F1:23:AB:F2:35)! Enter a valid port (1 - 65535)! - Enter a valid subnetmask (like 255.255.255.0)! + Enter a valid subnetmask (e.g., 255.255.255.0)! - Enter a valid subnetmask or CIDR (like 255.255.255.0 or /24)! + Enter a valid subnetmask or CIDR (e.g., 255.255.255.0 or /24)! Field cannot be empty! @@ -1448,10 +1448,10 @@ Profile files are not affected! Enter a valid port and/or port range (1 - 65535)! - Enter a valid subnet (like 192.168.178.133/26)! + Enter a valid subnet (e.g., 192.168.178.133/26)! - Enter a valid website (like https://example.com/index.html) + Enter a valid website (e.g., https://example.com/index.html) Green @@ -1742,7 +1742,7 @@ Profile files are not affected! Add a tab to query whois... - Enter a valid domain (like "example.com")! + Enter a valid domain (e.g., "example.com")! Whois server not found for the domain: "{0}" @@ -2032,7 +2032,7 @@ is disabled! Use custom IPv4 address API - URL to a web service that can be reached via http or https and returns an IPv4 address like "xx.xx.xx.xx" as response. + URL to a web service that can be reached via http or https and returns an IPv4 address e.g., "xx.xx.xx.xx" as response. Could not parse public ip address from "{0}"! Try another service or use the default... @@ -3192,7 +3192,7 @@ If the option is disabled again, the values are no longer modified. However, the SNTP server(s) - URL to a web service that can be reached via http or https and returns an IPv6 address like "xxxx:xx:xxx::xx" as response. + URL to a web service that can be reached via http or https and returns an IPv6 address e.g., "xxxx:xx:xxx::xx" as response. IP endpoint @@ -3234,7 +3234,7 @@ If the option is disabled again, the values are no longer modified. However, the An SNTP server with this name already exists! - Enter a valid hostname (like "server-01" or "example.com") or a valid IP address (like 192.168.178.1)! + Enter a valid hostname (e.g., "server-01" or "example.com") or a valid IP address (e.g., 192.168.178.1)! Server(s) @@ -3528,10 +3528,10 @@ Changes to this value will take effect after the application is restarted. Wheth The settings on this page contain errors. Correct them to be able to save. - SSH hostkey to use for the connection (like "71:b8:f2:6e..."). Only available if the mode is "SSH". + SSH hostkey to use for the connection (e.g., "71:b8:f2:6e..."). Only available if the mode is "SSH". - Full path to the private key file (like "C:\Users\BornToBeRoot\SSH\private_key.ppk"). Only available if the mode is "SSH". + Full path to the private key file (e.g., "C:\Users\BornToBeRoot\SSH\private_key.ppk"). Only available if the mode is "SSH". Username that will be passed into the PuTTY session. Only available if the mode is "SSH", "Telnet" or "Rlogin". @@ -3938,4 +3938,10 @@ Right-click for more options. {0} {1} {2} + + Primary DNS server + + + Enter a valid hostname (e.g., "server-01" or "example.com")! Multiple hostnames can be separated with a space. + \ No newline at end of file diff --git a/Source/NETworkManager.Utilities/RegexHelper.cs b/Source/NETworkManager.Utilities/RegexHelper.cs index 127d2f98f1..c1445c70a7 100644 --- a/Source/NETworkManager.Utilities/RegexHelper.cs +++ b/Source/NETworkManager.Utilities/RegexHelper.cs @@ -26,17 +26,14 @@ public static class RegexHelper public const string IPv4AddressRangeRegex = $"^{IPv4AddressValues}-{IPv4AddressValues}$"; // Match a MAC-Address 000000000000 00:00:00:00:00:00, 00-00-00-00-00-00-00 or 0000.0000.0000 - public const string MACAddressRegex = @"^^[A-Fa-f0-9]{12}$|^[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}$|^[A-Fa-f0-9]{4}.[A-Fa-f0-9]{4}.[A-Fa-f0-9]{4}$$"; // Match the first 3 bytes of a MAC-Address 000000, 00:00:00, 00-00-00 - public const string MACAddressFirst3BytesRegex = @"^[A-Fa-f0-9]{6}$|^[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}(:|-){1}[A-Fa-f0-9]{2}$|^[A-Fa-f0-9]{4}.[A-Fa-f0-9]{2}$"; // Private subnetmask / cidr values - private const string SubnetmaskValues = @"(((255\.){3}(255|254|252|248|240|224|192|128|0+))|((255\.){2}(255|254|252|248|240|224|192|128|0+)\.0)|((255\.)(255|254|252|248|240|224|192|128|0+)(\.0+){2})|((255|254|252|248|240|224|192|128|0+)(\.0+){3}))"; @@ -111,7 +108,7 @@ public static class RegexHelper // Match an SNMP OID (like 1.3.6.1 or .1.3.6.2) public const string SnmpOidRegex = @"^\.?[012]\.(?:[0-9]|[1-3][0-9])(\.\d+)*$"; - + // Match a hosts file entry with optional comments, supporting IPv4, IPv6, and hostnames // ^* : Matches the beginning of the line // (#)? : Optionally matches a comment (#) at the start of the line diff --git a/Source/NETworkManager.Validators/HostsFileEntryHostnameValidator.cs b/Source/NETworkManager.Validators/HostsFileEntryHostnameValidator.cs new file mode 100644 index 0000000000..f8e1ab397f --- /dev/null +++ b/Source/NETworkManager.Validators/HostsFileEntryHostnameValidator.cs @@ -0,0 +1,34 @@ +using NETworkManager.Localization.Resources; +using NETworkManager.Utilities; +using System.Diagnostics; +using System.Globalization; +using System.Text.RegularExpressions; +using System.Windows.Controls; + +namespace NETworkManager.Validators; + +public class HostsFileEntryHostnameValidator : ValidationRule +{ + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + var input = (value as string); + + var isValid = true; + + Debug.WriteLine(input); + + foreach (var hostname in input.Split(' ')) + { + if (Regex.IsMatch(hostname, RegexHelper.HostnameOrDomainRegex) == false) + { + isValid = false; + break; + } + } + + if (isValid) + return ValidationResult.ValidResult; + + return new ValidationResult(false, Strings.EnterValidHostsFileEntryHostname); + } +} \ No newline at end of file diff --git a/Source/NETworkManager.Validators/IPAddressValidator.cs b/Source/NETworkManager.Validators/IPAddressValidator.cs new file mode 100644 index 0000000000..93e47bacd3 --- /dev/null +++ b/Source/NETworkManager.Validators/IPAddressValidator.cs @@ -0,0 +1,19 @@ +using NETworkManager.Localization.Resources; +using System.Globalization; +using System.Net.Sockets; +using System.Windows.Controls; + +namespace NETworkManager.Validators; + +public class IPAddressValidator : ValidationRule +{ + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + var input = (value as string); + + if (System.Net.IPAddress.TryParse(input, out var address) && (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)) + return ValidationResult.ValidResult; + + return new ValidationResult(false, Strings.EnterValidIPv6Address); + } +} diff --git a/Source/NETworkManager.Validators/IPv6AddressValidator.cs b/Source/NETworkManager.Validators/IPv6AddressValidator.cs index 3c8ca5f258..1087fd7eff 100644 --- a/Source/NETworkManager.Validators/IPv6AddressValidator.cs +++ b/Source/NETworkManager.Validators/IPv6AddressValidator.cs @@ -1,8 +1,7 @@ -using System.Globalization; -using System.Text.RegularExpressions; +using NETworkManager.Localization.Resources; +using System.Globalization; +using System.Net.Sockets; using System.Windows.Controls; -using NETworkManager.Localization.Resources; -using NETworkManager.Utilities; namespace NETworkManager.Validators; @@ -10,13 +9,11 @@ public class IPv6AddressValidator : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - var ipAddress = (value as string)?.Trim(); + var input = (value as string); - if (string.IsNullOrEmpty(ipAddress)) - return new ValidationResult(false, Strings.EnterValidIPv6Address); + if (System.Net.IPAddress.TryParse(input, out var address) && address.AddressFamily == AddressFamily.InterNetworkV6) + return ValidationResult.ValidResult; - return Regex.IsMatch(ipAddress, RegexHelper.IPv6AddressRegex) - ? ValidationResult.ValidResult - : new ValidationResult(false, Strings.EnterValidIPv6Address); + return new ValidationResult(false, Strings.EnterValidIPv6Address); } } \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs new file mode 100644 index 0000000000..52ac61b05f --- /dev/null +++ b/Source/NETworkManager/ViewModels/HostsFileEditorEntryViewModel.cs @@ -0,0 +1,139 @@ +using NETworkManager.Models.HostsFileEditor; +using NETworkManager.Utilities; +using System; +using System.Windows.Input; + +namespace NETworkManager.ViewModels; + +/// +/// ViewModel for adding or editing a hosts file entry in the HostsFileEditor dialog. +/// +public class HostsFileEditorEntryViewModel : ViewModelBase +{ + /// + /// Creates a new instance of for adding or + /// editing a hosts file entry. + /// + /// OK command to save the entry. + /// Cancel command to discard the entry. + /// Entry to edit, if null a new entry will be created. + public HostsFileEditorEntryViewModel(Action okCommand, + Action cancelHandler, HostsFileEntry entry = null) + { + OKCommand = new RelayCommand(_ => okCommand(this)); + CancelCommand = new RelayCommand(_ => cancelHandler(this)); + + Entry = entry; + + // Add entry + if (Entry == null) + { + IsEnabled = true; + } + // Edit entry + else + { + IsEnabled = entry.IsEnabled; + IPAddress = entry.IPAddress; + Hostname = entry.Hostname; + Comment = entry.Comment; + } + } + + /// + /// OK command to save the entry. + /// + public ICommand OKCommand { get; } + + /// + /// Cancel command to discard the entry. + /// + public ICommand CancelCommand { get; } + + public HostsFileEntry Entry { get; } = null; + + /// + /// Private field of property. + /// + private bool _isEnabled; + + /// + /// Indicates whether the entry is enabled or not. + /// + public bool IsEnabled + { + get => _isEnabled; + set + { + if (value == _isEnabled) + return; + + _isEnabled = value; + OnPropertyChanged(); + } + } + + /// + /// Private field of property. + /// + private string _ipAddress; + + /// + /// IP address of the host. + /// + public string IPAddress + { + get => _ipAddress; + set + { + if (value == _ipAddress) + return; + + _ipAddress = value; + OnPropertyChanged(); + } + } + + /// + /// Private field of property. + /// + private string _hostname; + + /// + /// Host name(s) of the host. Multiple host names are separated by a + /// space (equal to the hosts file format). + /// + public string Hostname + { + get => _hostname; + set + { + if (value == _hostname) + return; + + _hostname = value; + OnPropertyChanged(); + } + } + + /// + /// Private field of property. + /// + private string _comment; + + /// + /// Comment of the host. + /// + public string Comment + { + get => _comment; + set + { + if (value == _comment) + return; + + _comment = value; + OnPropertyChanged(); + } + } +} \ No newline at end of file diff --git a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs index 30cdb00ca6..83ac7a28f4 100644 --- a/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs +++ b/Source/NETworkManager/ViewModels/HostsFileEditorViewModel.cs @@ -291,7 +291,62 @@ private async Task DisableEntryAction() private async Task AddEntryAction() { - MessageBox.Show("Add entry action is not implemented yet.", "Add Entry", MessageBoxButton.OK, MessageBoxImage.Information); + IsModifying = true; + + var childWindow = new HostsFileEditorEntryChildWindow(); + + var childWindowViewModel = new HostsFileEditorEntryViewModel(async _ => + { + childWindow.IsOpen = false; + ConfigurationManager.Current.IsChildWindowOpen = false; + + IsModifying = false; + }, _ => + { + childWindow.IsOpen = false; + ConfigurationManager.Current.IsChildWindowOpen = false; + + IsModifying = false; + }); + + childWindow.Title = Strings.AddEntry; + + childWindow.DataContext = childWindowViewModel; + + ConfigurationManager.Current.IsChildWindowOpen = true; + + await (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow); + } + + public ICommand EditEntryCommand => new RelayCommand(_ => EditEntryAction().ConfigureAwait(false), ModifyEntry_CanExecute); + + private async Task EditEntryAction() + { + IsModifying = true; + + var childWindow = new HostsFileEditorEntryChildWindow(); + + var childWindowViewModel = new HostsFileEditorEntryViewModel(async _ => + { + childWindow.IsOpen = false; + ConfigurationManager.Current.IsChildWindowOpen = false; + + IsModifying = false; + }, _ => + { + childWindow.IsOpen = false; + ConfigurationManager.Current.IsChildWindowOpen = false; + + IsModifying = false; + }, SelectedResult); + + childWindow.Title = Strings.EditEntry; + + childWindow.DataContext = childWindowViewModel; + + ConfigurationManager.Current.IsChildWindowOpen = true; + + await (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow); } public ICommand DeleteEntryCommand => new RelayCommand(_ => DeleteEntryAction().ConfigureAwait(false), ModifyEntry_CanExecute); @@ -327,13 +382,6 @@ private async Task DeleteEntryAction() await (Application.Current.MainWindow as MainWindow).ShowChildWindowAsync(childWindow); } - public ICommand EditEntryCommand => new RelayCommand(_ => EditEntryAction().ConfigureAwait(false), ModifyEntry_CanExecute); - - private async Task EditEntryAction() - { - MessageBox.Show("Edit entry action is not implemented yet.", "Edit Entry", MessageBoxButton.OK, MessageBoxImage.Information); - } - private bool ModifyEntry_CanExecute(object obj) { return ConfigurationManager.Current.IsAdmin && diff --git a/Source/NETworkManager/Views/CredentialsPasswordProfileFileChildWindow.xaml b/Source/NETworkManager/Views/CredentialsPasswordProfileFileChildWindow.xaml index 912bc8442c..2b736cc6d5 100644 --- a/Source/NETworkManager/Views/CredentialsPasswordProfileFileChildWindow.xaml +++ b/Source/NETworkManager/Views/CredentialsPasswordProfileFileChildWindow.xaml @@ -21,7 +21,7 @@ TitleForeground="{DynamicResource ResourceKey=MahApps.Brushes.Gray3}" CloseByEscape="False" OverlayBrush="{DynamicResource ResourceKey=MahApps.Brushes.Gray.SemiTransparent}" - Loaded="CredentialsPasswordProfileFileChildWindow_OnLoaded" + Loaded="ChildWindow_OnLoaded" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:CredentialsPasswordProfileFileViewModel}"> diff --git a/Source/NETworkManager/Views/CredentialsPasswordProfileFileChildWindow.xaml.cs b/Source/NETworkManager/Views/CredentialsPasswordProfileFileChildWindow.xaml.cs index b99ad03a0f..3e20cd5bd4 100644 --- a/Source/NETworkManager/Views/CredentialsPasswordProfileFileChildWindow.xaml.cs +++ b/Source/NETworkManager/Views/CredentialsPasswordProfileFileChildWindow.xaml.cs @@ -11,9 +11,12 @@ public CredentialsPasswordProfileFileChildWindow() InitializeComponent(); } - private void CredentialsPasswordProfileFileChildWindow_OnLoaded(object sender, RoutedEventArgs e) + private void ChildWindow_OnLoaded(object sender, RoutedEventArgs e) { // Focus the PasswordBox when the child window is loaded - Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(delegate { PasswordBoxPassword.Focus(); })); + Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(delegate + { + PasswordBoxPassword.Focus(); + })); } } \ No newline at end of file diff --git a/Source/NETworkManager/Views/HostsFileEditorEntryChildWindow.xaml b/Source/NETworkManager/Views/HostsFileEditorEntryChildWindow.xaml new file mode 100644 index 0000000000..06366e7420 --- /dev/null +++ b/Source/NETworkManager/Views/HostsFileEditorEntryChildWindow.xaml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + +