├── .editorconfig ├── .gitattributes ├── .gitignore ├── Directory.Build.props ├── IPConfig.sln ├── IPConfig ├── .dns │ ├── ipv4_public_dns.en.csv │ └── ipv4_public_dns.zh-CN.csv ├── .mask │ ├── ipv4_mask.en.csv │ └── ipv4_mask.zh-CN.csv ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── Behaviors │ ├── ContextMenuLeftClickBehavior.cs │ ├── CopyContentsSplitButtonBehavior.cs │ ├── IgnoreMouseWheelBehavior.cs │ ├── InputBindingBehavior.cs │ ├── SelectedItemsBehavior.cs │ ├── SplitButtonToggleDropDownBehavior.cs │ └── TripleClickToSelectAllBehavior.cs ├── Controls │ ├── AlignDashCornerRect.cs │ ├── BindingProxy.cs │ ├── DeferredContent.cs │ └── ReadOnlyComboBox.cs ├── Converters │ ├── BytesToFileSizeConverter.cs │ ├── CopyContentFormatConverter.cs │ ├── GetIPCIDRConverter.cs │ ├── MultiValueEqualsConverter.cs │ ├── NicIPConfigToolTipConverter.cs │ ├── OperationalStatusToolTipConverter.cs │ ├── PingDnsGroupIsEnabledConverter.cs │ ├── PingDnsLabelContentConverter.cs │ ├── PingDnsLabelStyleConverter.cs │ ├── PingReplyToolTipConverter.cs │ ├── SelectedNicIPConfigNameConverter.cs │ ├── SkinTypeToImageConverter.cs │ ├── SkinTypeToolTipConverter.cs │ ├── StringRemoveNewLineConverter.cs │ ├── StringToIntConverter.cs │ └── ValidationErrorsToolTipConverter.cs ├── Extensions │ ├── EnumerableExtensions.cs │ ├── IPConfigModelExtensions.cs │ ├── StringExtensions.cs │ └── WindowsThemeExtensions.cs ├── Helpers │ ├── BytesFormatter.cs │ ├── ClipboardHelper.cs │ ├── FileOrderHelper.cs │ ├── GroupItemHelper.cs │ ├── LiteDbHelper.cs │ ├── NameOfHelper.cs │ ├── NetworkManagement.cs │ ├── ResourceHelper.cs │ ├── ThemeManager.cs │ ├── ThemeWatcher.cs │ └── UriHelper.cs ├── IPConfig.csproj ├── Languages │ ├── Lang.Designer.cs │ ├── Lang.Designer.tt │ ├── Lang.en.resx │ ├── Lang.resx │ ├── LangExtension.cs │ └── LangSource.cs ├── Models │ ├── EditableIPConfigModel.cs │ ├── Error.cs │ ├── GitHub │ │ ├── GitHubApi.cs │ │ ├── GitHubApiException.cs │ │ └── GitHubReleaseInfo.cs │ ├── IDeepCloneTo.cs │ ├── IDeepCloneable.cs │ ├── IPAdvancedConfigBase.cs │ ├── IPConfigBase.cs │ ├── IPConfigModel.cs │ ├── IPv4AdvancedConfig.cs │ ├── IPv4Config.cs │ ├── IPv4Dns.cs │ ├── IPv4Mask.cs │ ├── IPv6AdvancedConfig.cs │ ├── LastUsedIPv4Config.cs │ ├── Messages │ │ ├── AddUntitledIPConfigMessage.cs │ │ ├── CancelEditMessage.cs │ │ ├── ChangeSelectionMessage.cs │ │ ├── CollectionChangeActionMessage.cs │ │ ├── EmptyMessage.cs │ │ ├── GoBackMessage.cs │ │ ├── ISender.cs │ │ ├── KeyPressMessage.cs │ │ ├── RefreshMessage.cs │ │ ├── SaveMessage.cs │ │ └── ToggleStateMessage.cs │ ├── Nic.cs │ ├── SimpleNicType.cs │ └── Validations │ │ ├── ForwardingErrorsAttribute.cs │ │ ├── IPValidationAttribute.cs │ │ ├── RequiredIfAttribute.cs │ │ └── ValidationLangAttributeBase.cs ├── Properties │ ├── Settings.Designer.cs │ ├── Settings.cs │ └── Settings.settings ├── Resources │ ├── Screenshots │ │ └── mainwindow.png │ ├── artist_palette_3d.png │ ├── crescent_moon_3d.png │ ├── inetcpl.cpl(4487).png │ ├── ipconfig.ico │ ├── network-tree.ai │ ├── network-tree.png │ ├── purple_circle_3d.png │ ├── shell32.dll(22).png │ └── sun_3d.png ├── Themes │ ├── FixedHandyControlSearchBarPlusTemplate.xaml │ ├── FixedHandyControlTextBoxPlusTemplate.xaml │ ├── MyDarkTheme.xaml │ ├── MyLightTheme.xaml │ ├── MyResources.xaml │ ├── MyStyles.xaml │ └── MyVioletTheme.xaml ├── ViewModels │ ├── IPConfigDetailViewModel.cs │ ├── IPConfigListViewModel.cs │ ├── MainViewModel.cs │ ├── NicConfigDetailViewModel.cs │ ├── NicViewModel.cs │ ├── StatusBarViewModel.cs │ ├── ThemeSwitchButtonViewModel.cs │ └── VersionInfoViewModel.cs ├── Views │ ├── IPConfigDetailView.xaml │ ├── IPConfigDetailView.xaml.cs │ ├── IPConfigListSelectionCounterView.xaml │ ├── IPConfigListSelectionCounterView.xaml.cs │ ├── IPConfigListView.xaml │ ├── IPConfigListView.xaml.cs │ ├── IPv4ConfigView.xaml │ ├── IPv4ConfigView.xaml.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── NicConfigDetailView.xaml │ ├── NicConfigDetailView.xaml.cs │ ├── NicInfoCardView.xaml │ ├── NicInfoCardView.xaml.cs │ ├── NicSelectorView.xaml │ ├── NicSelectorView.xaml.cs │ ├── NicSpeedMonitorView.xaml │ ├── NicSpeedMonitorView.xaml.cs │ ├── StatusBarView.xaml │ ├── StatusBarView.xaml.cs │ ├── ThemeSwitchButtonView.xaml │ ├── ThemeSwitchButtonView.xaml.cs │ ├── VersionInfoView.xaml │ └── VersionInfoView.xaml.cs └── app.manifest ├── LICENSE ├── Languages.Designer.t4 └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)=$(MSBuildProjectName) 5 | 6 | 7 | -------------------------------------------------------------------------------- /IPConfig.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33530.505 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IPConfig", "IPConfig\IPConfig.csproj", "{BB63F771-3406-4600-B774-AF6DE6499347}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D44A49D4-79B7-466B-A512-CA4C2125E89D}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | Directory.Build.props = Directory.Build.props 12 | Languages.Designer.t4 = Languages.Designer.t4 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {BB63F771-3406-4600-B774-AF6DE6499347}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {BB63F771-3406-4600-B774-AF6DE6499347}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {BB63F771-3406-4600-B774-AF6DE6499347}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {BB63F771-3406-4600-B774-AF6DE6499347}.Release|Any CPU.Build.0 = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(SolutionProperties) = preSolution 27 | HideSolutionNode = FALSE 28 | EndGlobalSection 29 | GlobalSection(ExtensibilityGlobals) = postSolution 30 | RESX_SortFileContentOnSave = True 31 | RESX_MoveToResources = {"Items":[{"Extensions":".cs,.vb","Patterns":"$File.$Key|{$File.$Key}|$Namespace.$File.$Key|LangKey.$Key|$Namespace.LangKey.$Key|nameof($File.$Key), ResourceType = typeof($File)|ErrorMessageResourceType = typeof($File), ErrorMessageResourceName = nameof($File.$Key)"},{"Extensions":".cshtml,.vbhtml","Patterns":"@$Namespace.$File.$Key|@$File.$Key|@StringResourceKey.$Key|@$Namespace.StringResourceKey.$Key"},{"Extensions":".cpp,.c,.hxx,.h","Patterns":"$File::$Key"},{"Extensions":".aspx,.ascx","Patterns":"<%$ Resources:$File,$Key %>|<%= $File.$Key %>|<%= $Namespace.$File.$Key %>"},{"Extensions":".xaml","Patterns":"\"{lang:Lang {x:Static lang:LangKey.$Key}}\""},{"Extensions":".ts","Patterns":"resources.$Key"},{"Extensions":".html","Patterns":"{{ resources.$Key }}"}]} 32 | SolutionGuid = {570D33A3-1D7B-44F2-9A79-313E68E608D8} 33 | RESX_NeutralResourcesLanguage = zh-CN 34 | RESX_SaveFilesImmediatelyUponChange = True 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /IPConfig/.dns/ipv4_public_dns.en.csv: -------------------------------------------------------------------------------- 1 | Provider,Filter,Dns1,Dns2,Group,Description 2 | 114DNS,NULL,114.114.114.114,114.114.115.115,Public DNS(China),"Pure and hijack-free, no need to endure the pain of being forced to watch adverts or vulgar websites" 3 | 114DNS,Enhanced Security,114.114.114.119,114.114.115.119,Public DNS(China),"Block phishing virus and Trojan horse websites to enhance the security of online banking, securities, shopping, gaming and privacy information" 4 | 114DNS,Minor,114.114.114.110,114.114.115.110,Public DNS(China),Schools or parents have the option of blocking pornographic websites to protect children and adolescents from pornographic content on the internet 5 | DNS Pai,Telecom,101.226.4.6,218.30.118.6,Public DNS(China),"Fast, smart, stable and pure" 6 | DNS Pai,Unicom,123.125.81.6,140.207.198.6,Public DNS(China),"Fast, smart, stable and pure" 7 | DNS Pai,Mobile,101.226.4.6,218.30.118.6,Public DNS(China),"Fast, smart, stable and pure" 8 | DNS Pai,Tietong,101.226.4.6,218.30.118.6,Public DNS(China),"Fast, smart, stable and pure" 9 | OneDNS,Pure,117.50.10.10,52.80.52.52,Public DNS(China),"Not to intercept any filtering of the visiting site, directly return its real response results" 10 | OneDNS,Blocking,117.50.11.11,52.80.66.66,Public DNS(China),"Protect against all kinds of malware, filter ads and nuisances" 11 | OneDNS,Family,117.50.60.30,52.80.60.30,Public DNS(China),"On the basis of the blocking version, pornographic and violent site filtering and gambling site filtering are added to better purify the home network environment" 12 | SDNS,NULL,1.2.4.8,202.98.0.68,Public DNS(China),China Internet Network Information Centre (CNNIC) 13 | AliDNS,NULL,223.5.5.5,223.6.6.6,Public DNS(China),"Fast, stable and secure" 14 | Baidu DNS,NULL,180.76.76.76,NULL,Public DNS(China),"Secure, hijack-free and more accurate" 15 | Tencent DNSPod,NULL,119.29.29.29,182.254.116.116,Public DNS(China),Free and reliable public resolution 16 | AdGuard,Default,94.140.14.14,94.140.15.15,Public DNS,AdGuard DNS will block ads and trackers 17 | AdGuard,Non-Filtering,94.140.14.140,94.140.14.141,Public DNS,"AdGuard DNS will not block ads, trackers, or any other DNS requests" 18 | AdGuard,Family Protection,94.140.14.15,94.140.15.16,Public DNS,"AdGuard DNS will block ads, trackers, adult content, and enable Safe Search and Safe Mode, where possible" 19 | CleanBrowsing,Family Filter,185.228.168.168,185.228.169.168,Public DNS,"Blocks access to all adult, pornographic and explicit sites. It also blocks proxy and VPN domains that are used to bypass the filters. Mixed content sites (like Reddit) are also blocked. Google, Bing and Youtube are set to the Safe Mode. Malicious and Phishing domains are blocked" 20 | CleanBrowsing,Adult Filter,185.228.168.10,185.228.169.11,Public DNS,"Blocks access to all adult, pornographic and explicit sites. It does not block proxy or VPNs, nor mixed-content sites. Sites like Reddit are allowed. Google and Bing are set to the Safe Mode. Malicious and Phishing domains are blocked" 21 | CleanBrowsing,Security Filter,185.228.168.9,185.228.169.9,Public DNS,"Blocks access to phishing, spam, malware and malicious domains. Our database of malicious domains is updated hourly and considered to be one of the best in the industry. Note that it does not block adult content" 22 | Cloudflare,NULL,1.1.1.1,1.0.0.1,Public DNS,Fast. Free. Private 23 | Cloudflare,Security Filter,1.1.1.2,1.0.0.2,Public DNS,Malware Blocking Only 24 | Cloudflare,Family Filter,1.1.1.3,1.0.0.3,Public DNS,Malware and Adult Content Blocking Together 25 | Google,NULL,8.8.8.8,8.8.4.4,Public DNS,"Speed up your browsing experience, Improve your security, Get the results you expect with absolutely no redirection" 26 | Level 3,NULL,209.244.0.3,209.244.0.4,Public DNS,"The free DNS servers listed above as Level 3 will automatically route to the nearest DNS server operated by Level 3 Communications, the company that provides most of the ISPs in the US their access to the internet backbone" 27 | Level 3,Verizon DNS,4.2.2.1,4.2.2.2,Public DNS,These servers are often given as Verizon DNS servers but that is not technically the case 28 | Level 3,Verizon DNS,4.2.2.3,4.2.2.4,Public DNS,These servers are often given as Verizon DNS servers but that is not technically the case 29 | Level 3,Verizon DNS,4.2.2.5,4.2.2.6,Public DNS,These servers are often given as Verizon DNS servers but that is not technically the case 30 | OpenDNS,Basic Security,208.67.222.222,208.67.220.220,Public DNS,"Our classic, free service with customizable filtering and basic protection" 31 | OpenDNS,Family Shield,208.67.222.123,208.67.220.123,Public DNS,Preconfigured to block adult content - set it & forget it 32 | OpenDNS,Sandbox,208.67.222.2,208.67.220.2,Public DNS,OpenDNS Sandbox is an RFC-compliant DNS service that does not provide any level of filtering 33 | OpenNIC,NULL,185.121.177.177,169.239.202.202,Public DNS,"DNS Neutrality, No Cost, Stop DNS Hijacking" 34 | Quad9,NULL,9.9.9.9,149.112.112.112,Public DNS,"Recommended: Malware Blocking, DNSSEC Validation (this is the most typical configuration)" 35 | Quad9,ECS,9.9.9.11,149.112.112.11,Public DNS,"Secured w/ECS: Malware blocking, DNSSEC Validation, ECS enabled" 36 | Quad9,Unsecured,9.9.9.10,149.112.112.10,Public DNS,"Unsecured: No Malware blocking, no DNSSEC validation (for experts only!)" 37 | V2EX,NULL,199.91.73.222,178.79.131.110,Public DNS,Powered by V2EX webmaster 38 | Yandex,Basic,77.88.8.1,77.88.8.8,Public DNS,No traffic filtering 39 | Yandex,Safe,77.88.8.2,77.88.8.88,Public DNS,Protection from infected and fraudulent sites is provided 40 | Yandex,Family,77.88.8.3,77.88.8.7,Public DNS,Protection from dangerous sites and blocks sites with adult content 41 | -------------------------------------------------------------------------------- /IPConfig/.dns/ipv4_public_dns.zh-CN.csv: -------------------------------------------------------------------------------- 1 | Provider,Filter,Dns1,Dns2,Group,Description 2 | 114DNS,NULL,114.114.114.114,114.114.115.115,公共 DNS(中国),纯净无劫持,无需再忍受被强扭去看广告或粗俗网站之痛苦 3 | 114DNS,增强安全,114.114.114.119,114.114.115.119,公共 DNS(中国),拦截钓鱼病毒木马网站,增强网银、证券、购物、游戏、隐私信息安全 4 | 114DNS,少年儿童,114.114.114.110,114.114.115.110,公共 DNS(中国),学校或家长可选拦截色情网站,保护少年儿童免受网络色情内容的毒害 5 | DNS 派,电信,101.226.4.6,218.30.118.6,公共 DNS(中国),快速、智能、稳定、纯净 6 | DNS 派,联通,123.125.81.6,140.207.198.6,公共 DNS(中国),快速、智能、稳定、纯净 7 | DNS 派,移动,101.226.4.6,218.30.118.6,公共 DNS(中国),快速、智能、稳定、纯净 8 | DNS 派,铁通,101.226.4.6,218.30.118.6,公共 DNS(中国),快速、智能、稳定、纯净 9 | OneDNS,纯净版,117.50.10.10,52.80.52.52,公共 DNS(中国),不对访问网站进行任何过滤拦截,直接返回其真实的响应结果 10 | OneDNS,拦截版,117.50.11.11,52.80.66.66,公共 DNS(中国),防护各类恶意软件,过滤广告骚扰 11 | OneDNS,家庭版,117.50.60.30,52.80.60.30,公共 DNS(中国),在拦截版的基础上,增加了色情暴力站点过滤、赌博站点过滤功能,更好的净化家庭网络环境 12 | SDNS,NULL,1.2.4.8,202.98.0.68,公共 DNS(中国),中国互联网络信息中心 13 | 阿里云,NULL,223.5.5.5,223.6.6.6,公共 DNS(中国),快速、稳定、安全 14 | 百度 DNS,NULL,180.76.76.76,NULL,公共 DNS(中国),安全、无劫持、更精准 15 | 腾讯 DNSPod,NULL,119.29.29.29,182.254.116.116,公共 DNS(中国),免费且可靠的公共解析 16 | AdGuard,默认,94.140.14.14,94.140.15.15,公共 DNS,AdGuard DNS 拦截广告和跟踪器 17 | AdGuard,无过滤,94.140.14.140,94.140.14.141,公共 DNS,AdGuard DNS 不拦截广告、跟踪器或其他任何 DNS 请求 18 | AdGuard,家庭保护,94.140.14.15,94.140.15.16,公共 DNS,AdGuard DNS 拦截广告、跟踪器、成人内容,并在可能的情况下启用安全搜索和安全模式 19 | CleanBrowsing,家庭过滤,185.228.168.168,185.228.169.168,公共 DNS,阻止访问所有成人、色情和露骨网站。它还阻止用于绕过过滤器的代理和 VPN 域。混合内容网站(如 Reddit)也被阻止。Google,Bing 和 Youtube 设置为安全模式。恶意域和网络钓鱼域被阻止 20 | CleanBrowsing,成人过滤,185.228.168.10,185.228.169.11,公共 DNS,阻止访问所有成人、色情和露骨网站。它不会阻止代理或 VPN,也不会阻止混合内容网站。像 Reddit 这样的网站是允许的。Google 和 Bing 设置为安全模式。恶意域和网络钓鱼域被阻止 21 | CleanBrowsing,安全过滤,185.228.168.9,185.228.169.9,公共 DNS,阻止对网络钓鱼、垃圾邮件、恶意软件和恶意域的访问。我们的恶意域名数据库每小时更新一次,被认为是业内最好的数据库之一。请注意,它不会阻止成人内容 22 | Cloudflare,NULL,1.1.1.1,1.0.0.1,公共 DNS,快速且私密地浏览互联网的方式 23 | Cloudflare,安全过滤,1.1.1.2,1.0.0.2,公共 DNS,仅恶意软件阻止 24 | Cloudflare,家庭过滤,1.1.1.3,1.0.0.3,公共 DNS,同时阻止恶意和成人内容 25 | Google,NULL,8.8.8.8,8.8.4.4,公共 DNS,提升您的浏览体验、提高安全性、获得您完全没有重定向的预期结果 26 | Level 3,NULL,209.244.0.3,209.244.0.4,公共 DNS,自动路由到最近的由 Level 3 通信公司运营的 DNS 服务器,该公司为美国大多数 ISP 提供互联网骨干网的接入 27 | Level 3,Verizon DNS,4.2.2.1,4.2.2.2,公共 DNS,通常作为 Verizon DNS 服务器,但技术上并非如此,也许可以让微软网络服务加速 28 | Level 3,Verizon DNS,4.2.2.3,4.2.2.4,公共 DNS,通常作为 Verizon DNS 服务器,但技术上并非如此,也许可以让微软网络服务加速 29 | Level 3,Verizon DNS,4.2.2.5,4.2.2.6,公共 DNS,通常作为 Verizon DNS 服务器,但技术上并非如此,也许可以让微软网络服务加速 30 | OpenDNS,家庭,208.67.222.222,208.67.220.220,公共 DNS,经典免费服务,具有可定制的过滤和基本保护 31 | OpenDNS,家庭盾牌,208.67.222.123,208.67.220.123,公共 DNS,预配置为阻止成人内容 32 | OpenDNS,沙盒,208.67.222.2,208.67.220.2,公共 DNS,OpenDNS Sandbox 是一种符合 RFC 标准的 DNS 服务,不提供任何级别的过滤 33 | OpenNIC,NULL,185.121.177.177,169.239.202.202,公共 DNS,DNS 中立性、无成本、停止 DNS 劫持 34 | Quad9,NULL,9.9.9.9,149.112.112.112,公共 DNS,建议:恶意软件阻止、DNSSEC 验证(这是最典型的配置) 35 | Quad9,ECS,9.9.9.11,149.112.112.11,公共 DNS,带 ECS 的安全:恶意软件阻止、DNSSEC 验证、启用 ECS 36 | Quad9,专家,9.9.9.10,149.112.112.10,公共 DNS,不安全:没有恶意软件阻止,没有DNSSEC验证(仅适用于专家!) 37 | V2EX,NULL,199.91.73.222,178.79.131.110,公共 DNS,由 V2EX 站长提供 38 | Yandex,基本,77.88.8.1,77.88.8.8,公共 DNS,请求处理速度快,连接速度快 39 | Yandex,安全,77.88.8.2,77.88.8.88,公共 DNS,防止受感染或欺诈网站 40 | Yandex,家庭,77.88.8.3,77.88.8.7,公共 DNS,防止受感染或欺诈性网站以及阻止成人网站 41 | -------------------------------------------------------------------------------- /IPConfig/.mask/ipv4_mask.en.csv: -------------------------------------------------------------------------------- 1 | Mask,CIDR,Group 2 | 255.0.0.0,8,Default 3 | 255.255.0.0,16,Default 4 | 255.255.255.0,24,Default 5 | 0.0.0.0,0,/0 ~ /8 6 | 128.0.0.0,1,/0 ~ /8 7 | 192.0.0.0,2,/0 ~ /8 8 | 224.0.0.0,3,/0 ~ /8 9 | 240.0.0.0,4,/0 ~ /8 10 | 248.0.0.0,5,/0 ~ /8 11 | 252.0.0.0,6,/0 ~ /8 12 | 254.0.0.0,7,/0 ~ /8 13 | 255.0.0.0,8,/0 ~ /8 14 | 255.128.0.0,9,/9 ~ /16 15 | 255.192.0.0,10,/9 ~ /16 16 | 255.224.0.0,11,/9 ~ /16 17 | 255.240.0.0,12,/9 ~ /16 18 | 255.248.0.0,13,/9 ~ /16 19 | 255.252.0.0,14,/9 ~ /16 20 | 255.254.0.0,15,/9 ~ /16 21 | 255.255.0.0,16,/9 ~ /16 22 | 255.255.128.0,17,/17 ~ /24 23 | 255.255.192.0,18,/17 ~ /24 24 | 255.255.224.0,19,/17 ~ /24 25 | 255.255.240.0,20,/17 ~ /24 26 | 255.255.248.0,21,/17 ~ /24 27 | 255.255.252.0,22,/17 ~ /24 28 | 255.255.254.0,23,/17 ~ /24 29 | 255.255.255.0,24,/17 ~ /24 30 | 255.255.255.128,25,/25 ~ /32 31 | 255.255.255.192,26,/25 ~ /32 32 | 255.255.255.224,27,/25 ~ /32 33 | 255.255.255.240,28,/25 ~ /32 34 | 255.255.255.248,29,/25 ~ /32 35 | 255.255.255.252,30,/25 ~ /32 36 | 255.255.255.254,31,/25 ~ /32 37 | 255.255.255.255,32,/25 ~ /32 38 | -------------------------------------------------------------------------------- /IPConfig/.mask/ipv4_mask.zh-CN.csv: -------------------------------------------------------------------------------- 1 | Mask,CIDR,Group 2 | 255.0.0.0,8,默认 3 | 255.255.0.0,16,默认 4 | 255.255.255.0,24,默认 5 | 0.0.0.0,0,/0 ~ /8 6 | 128.0.0.0,1,/0 ~ /8 7 | 192.0.0.0,2,/0 ~ /8 8 | 224.0.0.0,3,/0 ~ /8 9 | 240.0.0.0,4,/0 ~ /8 10 | 248.0.0.0,5,/0 ~ /8 11 | 252.0.0.0,6,/0 ~ /8 12 | 254.0.0.0,7,/0 ~ /8 13 | 255.0.0.0,8,/0 ~ /8 14 | 255.128.0.0,9,/9 ~ /16 15 | 255.192.0.0,10,/9 ~ /16 16 | 255.224.0.0,11,/9 ~ /16 17 | 255.240.0.0,12,/9 ~ /16 18 | 255.248.0.0,13,/9 ~ /16 19 | 255.252.0.0,14,/9 ~ /16 20 | 255.254.0.0,15,/9 ~ /16 21 | 255.255.0.0,16,/9 ~ /16 22 | 255.255.128.0,17,/17 ~ /24 23 | 255.255.192.0,18,/17 ~ /24 24 | 255.255.224.0,19,/17 ~ /24 25 | 255.255.240.0,20,/17 ~ /24 26 | 255.255.248.0,21,/17 ~ /24 27 | 255.255.252.0,22,/17 ~ /24 28 | 255.255.254.0,23,/17 ~ /24 29 | 255.255.255.0,24,/17 ~ /24 30 | 255.255.255.128,25,/25 ~ /32 31 | 255.255.255.192,26,/25 ~ /32 32 | 255.255.255.224,27,/25 ~ /32 33 | 255.255.255.240,28,/25 ~ /32 34 | 255.255.255.248,29,/25 ~ /32 35 | 255.255.255.252,30,/25 ~ /32 36 | 255.255.255.254,31,/25 ~ /32 37 | 255.255.255.255,32,/25 ~ /32 38 | -------------------------------------------------------------------------------- /IPConfig/App.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /IPConfig/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /IPConfig/Behaviors/ContextMenuLeftClickBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Controls.Primitives; 4 | using System.Windows.Data; 5 | 6 | namespace IPConfig.Behaviors; 7 | 8 | /// 9 | /// Show ContextMenu on Left Click using only XAML 10 | /// 11 | public static class ContextMenuLeftClickBehavior 12 | { 13 | public static readonly DependencyProperty IsLeftClickEnabledProperty = 14 | DependencyProperty.RegisterAttached( 15 | "IsLeftClickEnabled", 16 | typeof(bool), 17 | typeof(ContextMenuLeftClickBehavior), 18 | new UIPropertyMetadata(false, OnIsLeftClickEnabledChanged)); 19 | 20 | public static bool GetIsLeftClickEnabled(DependencyObject obj) 21 | { 22 | return (bool)obj.GetValue(IsLeftClickEnabledProperty); 23 | } 24 | 25 | public static void SetIsLeftClickEnabled(DependencyObject obj, bool value) 26 | { 27 | obj.SetValue(IsLeftClickEnabledProperty, value); 28 | } 29 | 30 | private static void OnIsLeftClickEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 31 | { 32 | if (d is UIElement uiElement) 33 | { 34 | bool isEnabled = e.NewValue is bool b && b; 35 | 36 | if (isEnabled) 37 | { 38 | if (uiElement is ButtonBase btn) 39 | { 40 | btn.Click += OnMouseLeftButtonUp; 41 | } 42 | else 43 | { 44 | uiElement.MouseLeftButtonUp += OnMouseLeftButtonUp; 45 | } 46 | } 47 | else 48 | { 49 | if (uiElement is ButtonBase btn) 50 | { 51 | btn.Click -= OnMouseLeftButtonUp; 52 | } 53 | else 54 | { 55 | uiElement.MouseLeftButtonUp -= OnMouseLeftButtonUp; 56 | } 57 | } 58 | } 59 | } 60 | 61 | private static void OnMouseLeftButtonUp(object sender, RoutedEventArgs e) 62 | { 63 | if (sender is FrameworkElement fe) 64 | { 65 | // 如果我们在上下文菜单中使用绑定, 则当我们在左键单击时显示菜单时, 它的 “数据上下文” 将不会被设置。 66 | // (当用户右键单击控件时, 似乎在 WPF 中设置了 “数据上下文”, 尽管我不确定) 67 | // 所以我们必须在这里手动设置 ContextMenu 的数据上下文。 68 | if (fe.ContextMenu.DataContext is null) 69 | { 70 | fe.ContextMenu.SetBinding(FrameworkElement.DataContextProperty, new Binding { Source = fe.DataContext }); 71 | } 72 | 73 | ContextMenuService.SetPlacementTarget(fe.ContextMenu, fe); 74 | 75 | ContextMenuService.SetPlacement(fe.ContextMenu, ContextMenuService.GetPlacement(fe)); 76 | ContextMenuService.SetPlacementRectangle(fe.ContextMenu, ContextMenuService.GetPlacementRectangle(fe)); 77 | ContextMenuService.SetHorizontalOffset(fe.ContextMenu, ContextMenuService.GetHorizontalOffset(fe)); 78 | ContextMenuService.SetVerticalOffset(fe.ContextMenu, ContextMenuService.GetVerticalOffset(fe)); 79 | 80 | fe.ContextMenu.IsOpen = true; 81 | 82 | // 设置切换形式打开上下文菜单。 83 | // 建议在 XAML 设置 ContextMenuService.IsEnabled="False" 以禁用右键打开功能,避免产生打开状态的切换冲突。 84 | fe.ContextMenu.Closed += (s, e) => fe.IsEnabled = true; 85 | fe.IsEnabled = false; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /IPConfig/Behaviors/CopyContentsSplitButtonBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Input; 5 | using System.Windows.Media; 6 | using System.Windows.Media.Animation; 7 | 8 | using HandyControl.Controls; 9 | 10 | using IPConfig.Languages; 11 | 12 | namespace IPConfig.Behaviors; 13 | 14 | public class CopyContentsSplitButtonBehavior : SplitButtonToggleDropDownBehavior 15 | { 16 | private bool _canPlayTextAnimation; 17 | 18 | protected override void OnAssociatedObjectClick(object sender, RoutedEventArgs e) 19 | { 20 | if (_canPlayTextAnimation) 21 | { 22 | AnimateText(); 23 | 24 | _canPlayTextAnimation = false; 25 | } 26 | } 27 | 28 | protected override void OnAssociatedObjectPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e, HitTestResult hitTestResult) 29 | { 30 | if (hitTestResult?.VisualHit is Border { Child: null } or TextBlock) 31 | { 32 | _canPlayTextAnimation = true; 33 | } 34 | } 35 | 36 | private void AnimateText() 37 | { 38 | NameScope.SetNameScope(AssociatedObject, new NameScope()); 39 | string name = $"SplitButton_{Guid.NewGuid().ToString().Replace('-', '_')}"; 40 | AssociatedObject.RegisterName(name, AssociatedObject); 41 | 42 | var keyFrames = new StringAnimationUsingKeyFrames { 43 | Duration = new(TimeSpan.FromSeconds(1)) 44 | }; 45 | 46 | var frame = new DiscreteStringKeyFrame(Lang.Copied, KeyTime.FromTimeSpan(TimeSpan.Zero)); 47 | keyFrames.KeyFrames.Add(frame); 48 | 49 | var story = new Storyboard { FillBehavior = FillBehavior.Stop }; 50 | Storyboard.SetTargetName(keyFrames, name); 51 | Storyboard.SetTargetProperty(keyFrames, new PropertyPath(SplitButton.ContentProperty)); 52 | story.Children.Add(keyFrames); 53 | story.Begin(AssociatedObject); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /IPConfig/Behaviors/IgnoreMouseWheelBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Input; 3 | 4 | using Microsoft.Xaml.Behaviors; 5 | 6 | namespace IPConfig.Behaviors; 7 | 8 | /// 9 | /// 忽略 的鼠标滚轮行为,兼容附加属性模式。 10 | /// 11 | /// Bubbling scroll events from a ListView to its parent 12 | /// 13 | /// 14 | public sealed class IgnoreMouseWheelBehavior : Behavior 15 | { 16 | // Using a DependencyProperty as the backing store for Enabled. This enables animation, styling, binding, etc... 17 | public static readonly DependencyProperty EnabledProperty = 18 | DependencyProperty.RegisterAttached( 19 | "Enabled", 20 | typeof(bool), 21 | typeof(IgnoreMouseWheelBehavior), 22 | new PropertyMetadata(false, OnEnabledChanged)); 23 | 24 | public static bool GetEnabled(DependencyObject obj) 25 | { 26 | return (bool)obj.GetValue(EnabledProperty); 27 | } 28 | 29 | public static void SetEnabled(DependencyObject obj, bool value) 30 | { 31 | obj.SetValue(EnabledProperty, value); 32 | } 33 | 34 | protected override void OnAttached() 35 | { 36 | base.OnAttached(); 37 | 38 | AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel; 39 | } 40 | 41 | protected override void OnDetaching() 42 | { 43 | AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel; 44 | 45 | base.OnDetaching(); 46 | } 47 | 48 | private static void OnEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 49 | { 50 | if (d is UIElement uie) 51 | { 52 | if ((bool)e.NewValue) 53 | { 54 | uie.PreviewMouseWheel += OnPreviewMouseWheel; 55 | } 56 | else 57 | { 58 | uie.PreviewMouseWheel -= OnPreviewMouseWheel; 59 | } 60 | } 61 | } 62 | 63 | private static void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) 64 | { 65 | e.Handled = true; 66 | 67 | var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) { 68 | RoutedEvent = UIElement.MouseWheelEvent 69 | }; 70 | 71 | ((UIElement)sender).RaiseEvent(e2); 72 | } 73 | 74 | private void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e) 75 | { 76 | e.Handled = true; 77 | 78 | var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta) { 79 | RoutedEvent = UIElement.MouseWheelEvent 80 | }; 81 | 82 | AssociatedObject.RaiseEvent(e2); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /IPConfig/Behaviors/InputBindingBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Windows; 6 | using System.Windows.Input; 7 | 8 | using IPConfig.Controls; 9 | 10 | namespace IPConfig.Behaviors; 11 | 12 | /// 13 | /// InputBindings work only when focused 14 | /// 15 | /// 此行为将在运行时引发 XAML 绑定错误,但不影响工作。使用 可以消除错误。 16 | /// 17 | /// System.Windows.Data Error: Cannot find governing FrameworkElement or FrameworkContentElement for target element. 18 | /// 19 | public class InputBindingBehavior 20 | { 21 | public static readonly DependencyProperty PropagateInputBindingsToWindowProperty = 22 | DependencyProperty.RegisterAttached( 23 | "PropagateInputBindingsToWindow", 24 | typeof(bool), 25 | typeof(InputBindingBehavior), 26 | new PropertyMetadata(false, OnPropagateInputBindingsToWindowChanged)); 27 | 28 | private static readonly Dictionary, List>> _trackedFrameWorkElementsToBindings = []; 29 | 30 | public static bool GetPropagateInputBindingsToWindow(FrameworkElement obj) 31 | { 32 | return (bool)obj.GetValue(PropagateInputBindingsToWindowProperty); 33 | } 34 | 35 | public static void SetPropagateInputBindingsToWindow(FrameworkElement obj, bool value) 36 | { 37 | obj.SetValue(PropagateInputBindingsToWindowProperty, value); 38 | } 39 | 40 | private static void CleanupBindingsDictionary(Window window, Dictionary, List>> bindingsDictionary) 41 | { 42 | foreach (int hashCode in bindingsDictionary.Keys.ToList()) 43 | { 44 | if (bindingsDictionary.TryGetValue(hashCode, out var trackedData) && 45 | !trackedData.Item1.TryGetTarget(out _)) 46 | { 47 | Debug.WriteLine($"InputBindingBehavior: FrameWorkElement {hashCode} did never unload but was GCed, cleaning up leftover KeyBindings."); 48 | 49 | foreach (var binding in trackedData.Item2) 50 | { 51 | window.InputBindings.Remove(binding); 52 | } 53 | 54 | trackedData.Item2.Clear(); 55 | bindingsDictionary.Remove(hashCode); 56 | } 57 | } 58 | } 59 | 60 | private static void OnFrameworkElementLoaded(object sender, RoutedEventArgs e) 61 | { 62 | var frameworkElement = (FrameworkElement)sender; 63 | 64 | var window = Window.GetWindow(frameworkElement); 65 | 66 | if (window is not null) 67 | { 68 | // Transfer InputBindings into our control. 69 | if (!_trackedFrameWorkElementsToBindings.TryGetValue(frameworkElement.GetHashCode(), out var trackingData)) 70 | { 71 | trackingData = Tuple.Create( 72 | new WeakReference(frameworkElement), 73 | frameworkElement.InputBindings.Cast().ToList()); 74 | 75 | _trackedFrameWorkElementsToBindings.Add( 76 | frameworkElement.GetHashCode(), trackingData); 77 | } 78 | 79 | // Apply Bindings to Window. 80 | foreach (var inputBinding in trackingData.Item2) 81 | { 82 | window.InputBindings.Add(inputBinding); 83 | } 84 | 85 | frameworkElement.InputBindings.Clear(); 86 | } 87 | } 88 | 89 | private static void OnFrameworkElementUnLoaded(object sender, RoutedEventArgs e) 90 | { 91 | var frameworkElement = (FrameworkElement)sender; 92 | var window = Window.GetWindow(frameworkElement); 93 | int hashCode = frameworkElement.GetHashCode(); 94 | 95 | // Remove Bindings from Window. 96 | if (window is not null) 97 | { 98 | if (_trackedFrameWorkElementsToBindings.TryGetValue(hashCode, out var trackedData)) 99 | { 100 | foreach (var binding in trackedData.Item2) 101 | { 102 | frameworkElement.InputBindings.Add(binding); 103 | window.InputBindings.Remove(binding); 104 | } 105 | 106 | trackedData.Item2.Clear(); 107 | _trackedFrameWorkElementsToBindings.Remove(hashCode); 108 | 109 | // Catch removed and orphaned entries. 110 | CleanupBindingsDictionary(window, _trackedFrameWorkElementsToBindings); 111 | } 112 | } 113 | } 114 | 115 | private static void OnPropagateInputBindingsToWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 116 | { 117 | ((FrameworkElement)d).Loaded += OnFrameworkElementLoaded; 118 | ((FrameworkElement)d).Unloaded += OnFrameworkElementUnLoaded; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /IPConfig/Behaviors/SelectedItemsBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Specialized; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Controls.Primitives; 6 | 7 | namespace IPConfig.Behaviors; 8 | 9 | /// 10 | /// 不要对绑定的 SelectedItems 属性重新赋值,此行为将对列表内部数据进行增删操作。 11 | /// 12 | /// SelectedItems-Behavior-for-ListBox-and-MultiSelect 13 | /// 14 | /// 15 | public sealed class SelectedItemsBehavior 16 | { 17 | public static readonly DependencyProperty SelectedItemsProperty = 18 | DependencyProperty.RegisterAttached( 19 | "SelectedItems", 20 | typeof(INotifyCollectionChanged), 21 | typeof(SelectedItemsBehavior), 22 | new PropertyMetadata(default(IList), OnSelectedItemsChanged)); 23 | 24 | private static readonly DependencyProperty _isBusyProperty = 25 | DependencyProperty.RegisterAttached( 26 | "IsBusy", 27 | typeof(bool), 28 | typeof(SelectedItemsBehavior), 29 | new PropertyMetadata(default(bool))); 30 | 31 | public static IList GetSelectedItems(DependencyObject element) 32 | { 33 | return (IList)element.GetValue(SelectedItemsProperty); 34 | } 35 | 36 | public static void SetSelectedItems(DependencyObject d, INotifyCollectionChanged value) 37 | { 38 | d.SetValue(SelectedItemsProperty, value); 39 | } 40 | 41 | private static bool GetIsBusy(DependencyObject element) 42 | { 43 | return (bool)element.GetValue(_isBusyProperty); 44 | } 45 | 46 | private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 47 | { 48 | IList? selectedItems = null; 49 | 50 | void CollectionChangedEventHandler(object? sender, NotifyCollectionChangedEventArgs args) 51 | { 52 | if (args.OldItems is not null) 53 | { 54 | foreach (object? item in args.OldItems) 55 | { 56 | if (selectedItems.Contains(item)) 57 | { 58 | selectedItems.Remove(item); 59 | } 60 | } 61 | } 62 | 63 | if (args.NewItems is not null) 64 | { 65 | foreach (object? item in args.NewItems) 66 | { 67 | if (!selectedItems.Contains(item)) 68 | { 69 | selectedItems.Add(item); 70 | } 71 | } 72 | } 73 | } 74 | 75 | if (d is ListView listView) 76 | { 77 | selectedItems = listView.SelectedItems; 78 | listView.SelectionChanged += OnSelectionChanged; 79 | } 80 | else if (d is ListBox listBox) 81 | { 82 | selectedItems = listBox.SelectedItems; 83 | listBox.SelectionChanged += OnSelectionChanged; 84 | } 85 | else if (d is MultiSelector multiSelector) 86 | { 87 | selectedItems = multiSelector.SelectedItems; 88 | multiSelector.SelectionChanged += OnSelectionChanged; 89 | } 90 | 91 | if (selectedItems is null) 92 | { 93 | return; 94 | } 95 | 96 | if (e.OldValue is INotifyCollectionChanged oldValue) 97 | { 98 | oldValue.CollectionChanged -= CollectionChangedEventHandler; 99 | } 100 | 101 | if (e.NewValue is INotifyCollectionChanged newValue) 102 | { 103 | newValue.CollectionChanged += CollectionChangedEventHandler; 104 | } 105 | } 106 | 107 | private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e) 108 | { 109 | if (sender is DependencyObject d) 110 | { 111 | if (!GetIsBusy(d)) 112 | { 113 | SetIsBusy(d, true); 114 | var list = GetSelectedItems(d); 115 | 116 | foreach (object? item in e.RemovedItems) 117 | { 118 | if (list.Contains(item)) 119 | { 120 | list.Remove(item); 121 | } 122 | } 123 | 124 | foreach (object? item in e.AddedItems) 125 | { 126 | if (!list.Contains(item)) 127 | { 128 | list.Add(item); 129 | } 130 | } 131 | 132 | SetIsBusy(d, false); 133 | } 134 | } 135 | } 136 | 137 | private static void SetIsBusy(DependencyObject element, bool value) 138 | { 139 | element.SetValue(_isBusyProperty, value); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /IPConfig/Behaviors/SplitButtonToggleDropDownBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Input; 4 | using System.Windows.Media; 5 | using System.Windows.Shapes; 6 | 7 | using HandyControl.Controls; 8 | 9 | using Microsoft.Xaml.Behaviors; 10 | 11 | namespace IPConfig.Behaviors; 12 | 13 | /// 14 | /// 使 SplitButton 的下拉显示表现为切换打开行为。 15 | /// 16 | public class SplitButtonToggleDropDownBehavior : Behavior 17 | { 18 | protected virtual void OnAssociatedObjectClick(object sender, RoutedEventArgs e) 19 | { } 20 | 21 | protected virtual void OnAssociatedObjectPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e, HitTestResult hitTestResult) 22 | { } 23 | 24 | protected override void OnAttached() 25 | { 26 | base.OnAttached(); 27 | 28 | AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObject_PreviewMouseLeftButtonDown; 29 | AssociatedObject.Click += AssociatedObject_Click; 30 | } 31 | 32 | protected override void OnDetaching() 33 | { 34 | AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObject_PreviewMouseLeftButtonDown; 35 | AssociatedObject.Click -= AssociatedObject_Click; 36 | 37 | base.OnDetaching(); 38 | } 39 | 40 | private void AssociatedObject_Click(object sender, RoutedEventArgs e) 41 | { 42 | OnAssociatedObjectClick(sender, e); 43 | } 44 | 45 | private void AssociatedObject_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) 46 | { 47 | var hitTestResult = VisualTreeHelper.HitTest(AssociatedObject, e.GetPosition(AssociatedObject)); 48 | OnAssociatedObjectPreviewMouseLeftButtonDown(sender, e, hitTestResult); 49 | 50 | // 如果 VisualHit 为 Path,则鼠标在点击 SplitButton 的 DropDown 位置, 51 | // 如果为 TextBlock,则鼠标在点击 SplitButton 的非 DropDown 位置。 52 | // 如果为 Border,并且其 Child 不为 null,则鼠标在点击 DropDown 位置,否则,鼠标在点击 非 DropDown 位置。 53 | // 如果为 null,则鼠标在点击其他位置。 54 | // 如果不判断 VisualHit,则在点击下拉项前,此事件将被调用,下拉项被关闭,导致下拉项无法被点击到。 55 | if (AssociatedObject.IsDropDownOpen && hitTestResult?.VisualHit is Border or Path or TextBlock) 56 | { 57 | AssociatedObject.IsDropDownOpen = false; 58 | e.Handled = true; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /IPConfig/Behaviors/TripleClickToSelectAllBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Input; 4 | 5 | namespace IPConfig.Behaviors; 6 | 7 | /// 8 | /// Why does WPF textbox not support triple-click to select all text 9 | /// 10 | public class TripleClickToSelectAllBehavior 11 | { 12 | public static readonly DependencyProperty EnabledProperty = 13 | DependencyProperty.RegisterAttached( 14 | "Enabled", 15 | typeof(bool), 16 | typeof(TripleClickToSelectAllBehavior), 17 | new PropertyMetadata(false, OnPropertyChanged)); 18 | 19 | public static bool GetEnabled(DependencyObject element) 20 | { 21 | return (bool)element.GetValue(EnabledProperty); 22 | } 23 | 24 | public static void SetEnabled(DependencyObject element, bool value) 25 | { 26 | element.SetValue(EnabledProperty, value); 27 | } 28 | 29 | private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 30 | { 31 | if (d is TextBox tb) 32 | { 33 | bool enabled = (bool)e.NewValue; 34 | 35 | if (enabled) 36 | { 37 | tb.PreviewMouseLeftButtonDown += OnTextBoxMouseDown; 38 | } 39 | else 40 | { 41 | tb.PreviewMouseLeftButtonDown -= OnTextBoxMouseDown; 42 | } 43 | } 44 | } 45 | 46 | private static void OnTextBoxMouseDown(object sender, MouseButtonEventArgs e) 47 | { 48 | if (sender is TextBox textBox && e.ClickCount == 3) 49 | { 50 | textBox.SelectAll(); 51 | textBox.ScrollToHome(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /IPConfig/Controls/AlignDashCornerRect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows; 4 | using System.Windows.Media; 5 | 6 | namespace IPConfig.Controls; 7 | 8 | /// 9 | /// WPF reactangle border with corner from connecting line of two dashes 10 | /// 11 | public class AlignDashCornerRect : FrameworkElement 12 | { 13 | public static readonly DependencyProperty FillProperty = 14 | DependencyProperty.Register( 15 | "Fill", 16 | typeof(Brush), 17 | typeof(AlignDashCornerRect), 18 | new PropertyMetadata(default(Brush), OnVisualPropertyChanged)); 19 | 20 | public static readonly DependencyProperty StrokeDashCapProperty = 21 | DependencyProperty.Register( 22 | "StrokeDashCap", 23 | typeof(PenLineCap), 24 | typeof(AlignDashCornerRect), 25 | new PropertyMetadata(PenLineCap.Flat, OnVisualPropertyChanged)); 26 | 27 | public static readonly DependencyProperty StrokeDashLineProperty = 28 | DependencyProperty.Register( 29 | "StrokeDashLine", 30 | typeof(double), 31 | typeof(AlignDashCornerRect), 32 | new PropertyMetadata(default(double), OnVisualPropertyChanged)); 33 | 34 | public static readonly DependencyProperty StrokeDashSpaceProperty = 35 | DependencyProperty.Register( 36 | "StrokeDashSpace", 37 | typeof(double), 38 | typeof(AlignDashCornerRect), 39 | new PropertyMetadata(default(double), OnVisualPropertyChanged)); 40 | 41 | public static readonly DependencyProperty StrokeEndLineCapProperty = 42 | DependencyProperty.Register( 43 | "StrokeEndLineCap", 44 | typeof(PenLineCap), 45 | typeof(AlignDashCornerRect), 46 | new PropertyMetadata(PenLineCap.Flat, OnVisualPropertyChanged)); 47 | 48 | public static readonly DependencyProperty StrokeLineJoinProperty = 49 | DependencyProperty.Register( 50 | "StrokeLineJoin", 51 | typeof(PenLineJoin), 52 | typeof(AlignDashCornerRect), 53 | new PropertyMetadata(PenLineJoin.Miter, OnVisualPropertyChanged)); 54 | 55 | public static readonly DependencyProperty StrokeMiterLimitProperty = 56 | DependencyProperty.Register( 57 | "StrokeMiterLimit", 58 | typeof(double), 59 | typeof(AlignDashCornerRect), 60 | new PropertyMetadata(10.0d, OnVisualPropertyChanged)); 61 | 62 | public static readonly DependencyProperty StrokeProperty = 63 | DependencyProperty.Register( 64 | "Stroke", 65 | typeof(Brush), 66 | typeof(AlignDashCornerRect), 67 | new PropertyMetadata(default(Brush), OnVisualPropertyChanged)); 68 | 69 | public static readonly DependencyProperty StrokeStartLineCapProperty = 70 | DependencyProperty.Register( 71 | "StrokeStartLineCap", 72 | typeof(PenLineCap), 73 | typeof(AlignDashCornerRect), 74 | new PropertyMetadata(PenLineCap.Flat, OnVisualPropertyChanged)); 75 | 76 | public static readonly DependencyProperty StrokeThicknessProperty = 77 | DependencyProperty.Register( 78 | "StrokeThickness", 79 | typeof(double), 80 | typeof(AlignDashCornerRect), 81 | new PropertyMetadata(default(double), OnVisualPropertyChanged)); 82 | 83 | public Brush Fill 84 | { 85 | get => (Brush)GetValue(FillProperty); 86 | set => SetValue(FillProperty, value); 87 | } 88 | 89 | public Brush Stroke 90 | { 91 | get => (Brush)GetValue(StrokeProperty); 92 | set => SetValue(StrokeProperty, value); 93 | } 94 | 95 | public PenLineCap StrokeDashCap 96 | { 97 | get => (PenLineCap)GetValue(StrokeDashCapProperty); 98 | set => SetValue(StrokeDashCapProperty, value); 99 | } 100 | 101 | public double StrokeDashLine 102 | { 103 | get => (double)GetValue(StrokeDashLineProperty); 104 | set => SetValue(StrokeDashLineProperty, value); 105 | } 106 | 107 | public double StrokeDashSpace 108 | { 109 | get => (double)GetValue(StrokeDashSpaceProperty); 110 | set => SetValue(StrokeDashSpaceProperty, value); 111 | } 112 | 113 | public PenLineCap StrokeEndLineCap 114 | { 115 | get => (PenLineCap)GetValue(StrokeEndLineCapProperty); 116 | set => SetValue(StrokeEndLineCapProperty, value); 117 | } 118 | 119 | public PenLineJoin StrokeLineJoin 120 | { 121 | get => (PenLineJoin)GetValue(StrokeLineJoinProperty); 122 | set => SetValue(StrokeLineJoinProperty, value); 123 | } 124 | 125 | public double StrokeMiterLimit 126 | { 127 | get => (double)GetValue(StrokeMiterLimitProperty); 128 | set => SetValue(StrokeMiterLimitProperty, value); 129 | } 130 | 131 | public PenLineCap StrokeStartLineCap 132 | { 133 | get => (PenLineCap)GetValue(StrokeStartLineCapProperty); 134 | set => SetValue(StrokeStartLineCapProperty, value); 135 | } 136 | 137 | public double StrokeThickness 138 | { 139 | get => (double)GetValue(StrokeThicknessProperty); 140 | set => SetValue(StrokeThicknessProperty, value); 141 | } 142 | 143 | public AlignDashCornerRect() 144 | { 145 | SnapsToDevicePixels = true; 146 | UseLayoutRounding = true; 147 | } 148 | 149 | protected override void OnRender(DrawingContext drawingContext) 150 | { 151 | double w = ActualWidth; 152 | double h = ActualHeight; 153 | double x = StrokeThickness / 2.0; 154 | 155 | var horizontalPen = GetPen(ActualWidth - (2.0 * x)); 156 | var verticalPen = GetPen(ActualHeight - (2.0 * x)); 157 | 158 | drawingContext.DrawRectangle(Fill, null, new Rect(new Point(0, 0), new Size(w, h))); 159 | 160 | drawingContext.DrawLine(horizontalPen, new Point(x, x), new Point(w - x, x)); 161 | drawingContext.DrawLine(horizontalPen, new Point(x, h - x), new Point(w - x, h - x)); 162 | 163 | drawingContext.DrawLine(verticalPen, new Point(x, x), new Point(x, h - x)); 164 | drawingContext.DrawLine(verticalPen, new Point(w - x, x), new Point(w - x, h - x)); 165 | } 166 | 167 | private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 168 | { 169 | ((AlignDashCornerRect)d).InvalidateVisual(); 170 | } 171 | 172 | private IEnumerable GetDashArray(double length) 173 | { 174 | double useableLength = length - StrokeDashLine; 175 | int lines = (int)Math.Round(useableLength / (StrokeDashLine + StrokeDashSpace)); 176 | useableLength -= lines * StrokeDashLine; 177 | double actualSpacing = useableLength / lines; 178 | 179 | yield return StrokeDashLine / StrokeThickness; 180 | yield return actualSpacing / StrokeThickness; 181 | } 182 | 183 | private Pen GetPen(double length) 184 | { 185 | var dashArray = GetDashArray(length); 186 | 187 | return new Pen(Stroke, StrokeThickness) { 188 | DashStyle = new DashStyle(dashArray, 0), 189 | DashCap = StrokeDashCap, 190 | StartLineCap = StrokeStartLineCap, 191 | EndLineCap = StrokeEndLineCap, 192 | LineJoin = StrokeLineJoin, 193 | MiterLimit = StrokeMiterLimit 194 | }; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /IPConfig/Controls/BindingProxy.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace IPConfig.Controls; 4 | 5 | /// 6 | /// https://thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ 7 | /// 8 | public class BindingProxy : Freezable 9 | { 10 | #region Overrides of Freezable 11 | 12 | protected override Freezable CreateInstanceCore() 13 | { 14 | return new BindingProxy(); 15 | } 16 | 17 | #endregion Overrides of Freezable 18 | 19 | // Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc... 20 | public static readonly DependencyProperty DataProperty = 21 | DependencyProperty.Register( 22 | "Data", 23 | typeof(object), 24 | typeof(BindingProxy), 25 | new UIPropertyMetadata(null)); 26 | 27 | public object Data 28 | { 29 | get => GetValue(DataProperty); 30 | set => SetValue(DataProperty, value); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /IPConfig/Controls/DeferredContent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | 5 | namespace IPConfig.Controls; 6 | 7 | /// 8 | /// Deferred loading of XAML 9 | /// 10 | public class DeferredContent : ContentPresenter 11 | { 12 | public static readonly DependencyProperty DeferredContentTemplateProperty = 13 | DependencyProperty.Register( 14 | "DeferredContentTemplate", 15 | typeof(DataTemplate), 16 | typeof(DeferredContent), 17 | null); 18 | 19 | public DataTemplate DeferredContentTemplate 20 | { 21 | get => (DataTemplate)GetValue(DeferredContentTemplateProperty); 22 | set => SetValue(DeferredContentTemplateProperty, value); 23 | } 24 | 25 | public event EventHandler? DeferredContentLoaded; 26 | 27 | public DeferredContent() 28 | { 29 | Loaded += HandleLoaded; 30 | } 31 | 32 | public void ShowDeferredContent() 33 | { 34 | if (DeferredContentTemplate is not null) 35 | { 36 | Content = DeferredContentTemplate.LoadContent(); 37 | RaiseDeferredContentLoaded(); 38 | } 39 | } 40 | 41 | private void HandleLoaded(object sender, RoutedEventArgs e) 42 | { 43 | Loaded -= HandleLoaded; 44 | Dispatcher.BeginInvoke(ShowDeferredContent); 45 | } 46 | 47 | private void RaiseDeferredContentLoaded() 48 | { 49 | DeferredContentLoaded?.Invoke(this, new RoutedEventArgs()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /IPConfig/Controls/ReadOnlyComboBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | using HcComboBox = HandyControl.Controls.ComboBox; 4 | 5 | namespace IPConfig.Controls; 6 | 7 | /// 8 | /// XAML ReadOnly ComboBox 9 | /// 10 | public class ReadOnlyComboBox : HcComboBox 11 | { 12 | private int _oldSelectedIndex = -1; 13 | 14 | //static ReadOnlyComboBox() 15 | //{ 16 | // IsDropDownOpenProperty.OverrideMetadata(typeof(ReadOnlyComboBox), new FrameworkPropertyMetadata( 17 | // propertyChangedCallback: delegate { }, 18 | // coerceValueCallback: (d, value) => { 19 | // if (((ReadOnlyComboBox)d).IsReadOnly) 20 | // { 21 | // // Prohibit opening the drop down when read only. 22 | // return false; 23 | // } 24 | 25 | // return value; 26 | // })); 27 | 28 | // IsReadOnlyProperty.OverrideMetadata(typeof(ReadOnlyComboBox), new FrameworkPropertyMetadata( 29 | // propertyChangedCallback: (d, e) => { 30 | // // When setting "read only" to false, close the drop down. 31 | // if (e.NewValue is true) 32 | // { 33 | // ((ReadOnlyComboBox)d).IsDropDownOpen = false; 34 | // } 35 | // })); 36 | //} 37 | 38 | public override bool VerifyData() 39 | { 40 | // 设置初始状态不提示错误。 41 | if (ErrorStr is null) 42 | { 43 | return false; 44 | } 45 | 46 | // 修复 HandyControl 无法去除验证错误信息的问题。 47 | // 另一种解决方法是在样式中重写 ErrorTemplate,并用一个与背景色相同的 Border 覆盖 ErrorStr。 48 | bool hasError = base.VerifyData(); 49 | 50 | if (!hasError) 51 | { 52 | // 此 ErrorStr 是 HandyControl 的属性,不影响 Validation.Errors 获取错误信息。 53 | ErrorStr = ""; 54 | } 55 | 56 | return hasError; 57 | } 58 | 59 | protected override void OnSelectionChanged(SelectionChangedEventArgs e) 60 | { 61 | if (IsReadOnly) 62 | { 63 | SelectedIndex = _oldSelectedIndex; 64 | 65 | // Disallow changing the selection when read only. 66 | e.Handled = true; 67 | 68 | return; 69 | } 70 | 71 | _oldSelectedIndex = SelectedIndex; 72 | 73 | base.OnSelectionChanged(e); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /IPConfig/Converters/BytesToFileSizeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | using IPConfig.Helpers; 6 | using IPConfig.Languages; 7 | 8 | namespace IPConfig.Converters; 9 | 10 | public class BytesToFileSizeConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | string size = BytesFormatter.ToFileSize(System.Convert.ToInt64(value)); 15 | var key = (LangKey)parameter; 16 | string transport = LangSource.Instance[key]; 17 | 18 | return $"{transport}: ~ {size}"; 19 | } 20 | 21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IPConfig/Converters/CopyContentFormatConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | using IPConfig.Languages; 6 | 7 | namespace IPConfig.Converters; 8 | 9 | public class CopyContentFormatConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | return String.Format(LangSource.Instance[LangKey.CopyContent_Format_], value); 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /IPConfig/Converters/GetIPCIDRConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Windows.Data; 6 | 7 | namespace IPConfig.Converters; 8 | 9 | public class GetIPCIDRConverter : IValueConverter 10 | { 11 | public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | string? str = value as string; 14 | 15 | if (IPAddress.TryParse(str, out var mask)) 16 | { 17 | return mask.GetAddressBytes().Sum(x => System.Convert.ToString(x, 2).Count(x => x == '1')); 18 | } 19 | 20 | return null; 21 | } 22 | 23 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /IPConfig/Converters/MultiValueEqualsConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Windows.Data; 6 | 7 | namespace IPConfig.Converters; 8 | 9 | public class MultiValueEqualsConverter : IMultiValueConverter 10 | { 11 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | return values.Skip(1).All(x => EqualityComparer.Default.Equals(x, values.ElementAtOrDefault(0))); 14 | } 15 | 16 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /IPConfig/Converters/NicIPConfigToolTipConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | using IPConfig.Languages; 6 | using IPConfig.Models; 7 | 8 | namespace IPConfig.Converters; 9 | 10 | public class NicIPConfigToolTipConverter : IMultiValueConverter 11 | { 12 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (values is [Nic nic, IPConfigModel config]) 15 | { 16 | return $"{nic}\n\n{config?.IPv4Config}\n\n{Lang.Shortcut_F11}".Trim(); 17 | } 18 | 19 | return Lang.NoData; 20 | } 21 | 22 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /IPConfig/Converters/OperationalStatusToolTipConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Net.NetworkInformation; 4 | using System.Windows.Data; 5 | 6 | using IPConfig.Languages; 7 | 8 | namespace IPConfig.Converters; 9 | 10 | public class OperationalStatusToolTipConverter : IValueConverter 11 | { 12 | public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is OperationalStatus v) 15 | { 16 | return v switch { 17 | OperationalStatus.Up => Lang.OpStatus_Up_ToolTip, 18 | OperationalStatus.Down => Lang.OpStatus_Down_ToolTip, 19 | OperationalStatus.Testing => Lang.OpStatus_Testing_ToolTip, 20 | OperationalStatus.Unknown => Lang.OpStatus_Unknown_ToolTip, 21 | OperationalStatus.Dormant => Lang.OpStatus_Dormant_ToolTip, 22 | OperationalStatus.NotPresent => Lang.OpStatus_NotPresent_ToolTip, 23 | OperationalStatus.LowerLayerDown => Lang.OpStatus_LowerLayerDown_ToolTip, 24 | _ => Binding.DoNothing 25 | }; 26 | } 27 | 28 | return value?.ToString(); 29 | } 30 | 31 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /IPConfig/Converters/PingDnsGroupIsEnabledConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using System.Windows.Data; 5 | 6 | using IPConfig.Helpers; 7 | using IPConfig.Models; 8 | 9 | namespace IPConfig.Converters; 10 | 11 | public class PingDnsGroupIsEnabledConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | if (value is CollectionViewGroup group) 16 | { 17 | var dnsItems = GroupItemHelper.GetGroupItems(group); 18 | 19 | return dnsItems.All(x => !x.IsRunning); 20 | } 21 | 22 | return false; 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /IPConfig/Converters/PingDnsLabelContentConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Net.NetworkInformation; 4 | using System.Windows.Data; 5 | 6 | using IPConfig.Languages; 7 | 8 | namespace IPConfig.Converters; 9 | 10 | public class PingDnsLabelContentConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is not PingReply reply) 15 | { 16 | return Lang.Test; 17 | } 18 | 19 | if (reply.Status == IPStatus.TimedOut) 20 | { 21 | return Lang.TimedOut; 22 | } 23 | 24 | if (reply.Status != IPStatus.Success) 25 | { 26 | return Lang.Error; 27 | } 28 | 29 | return $"{reply.RoundtripTime}ms"; 30 | } 31 | 32 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /IPConfig/Converters/PingDnsLabelStyleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Net.NetworkInformation; 4 | using System.Windows.Data; 5 | 6 | namespace IPConfig.Converters; 7 | 8 | public class PingDnsLabelStyleConverter : IValueConverter 9 | { 10 | public required object ErrorStyle { get; init; } 11 | 12 | public required object FastStyle { get; init; } 13 | 14 | public required object InitStyle { get; init; } 15 | 16 | public required object NormalStyle { get; init; } 17 | 18 | public required object SlowStyle { get; init; } 19 | 20 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | if (value is not PingReply reply) 23 | { 24 | return InitStyle; 25 | } 26 | 27 | if (reply.Status != IPStatus.Success) 28 | { 29 | return ErrorStyle; 30 | } 31 | 32 | return reply.RoundtripTime switch { 33 | < 40 => FastStyle, 34 | < 70 => NormalStyle, 35 | < 5000 => SlowStyle, 36 | _ => ErrorStyle 37 | }; 38 | } 39 | 40 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /IPConfig/Converters/PingReplyToolTipConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Net.NetworkInformation; 4 | using System.Windows.Data; 5 | 6 | using IPConfig.Extensions; 7 | using IPConfig.Languages; 8 | 9 | namespace IPConfig.Converters; 10 | 11 | public class PingReplyToolTipConverter : IMultiValueConverter 12 | { 13 | public object? Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | if (values is [null, string dns1]) 16 | { 17 | return $"ping {dns1}"; 18 | } 19 | else if (values is [PingReply reply, _]) 20 | { 21 | return $""" 22 | {Lang.ReplyFrom_Format.Format(reply.Address)}: 23 | {Lang.Bytes}={reply.Buffer.Length} 24 | {Lang.Time}={reply.RoundtripTime}ms 25 | TTL={reply.Options?.Ttl} 26 | {reply.Status} 27 | """; 28 | } 29 | 30 | return Binding.DoNothing; 31 | } 32 | 33 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /IPConfig/Converters/SelectedNicIPConfigNameConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | using IPConfig.Languages; 6 | using IPConfig.Models; 7 | 8 | namespace IPConfig.Converters; 9 | 10 | public class SelectedNicIPConfigNameConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is IPConfigModel iPConfig) 15 | { 16 | return iPConfig.Name; 17 | } 18 | 19 | return LangSource.Instance[LangKey.AdapterNotFound]; 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /IPConfig/Converters/SkinTypeToImageConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | using HandyControl.Data; 6 | 7 | namespace IPConfig.Converters; 8 | 9 | public class SkinTypeToImageConverter : IValueConverter 10 | { 11 | private static readonly Uri _dark = new("/Resources/crescent_moon_3d.png", UriKind.Relative); 12 | 13 | private static readonly Uri _light = new("/Resources/sun_3d.png", UriKind.Relative); 14 | 15 | private static readonly Uri _system = new("/Resources/artist_palette_3d.png", UriKind.Relative); 16 | 17 | private static readonly Uri _violet = new("/Resources/purple_circle_3d.png", UriKind.Relative); 18 | 19 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 20 | { 21 | var skinType = value as SkinType?; 22 | 23 | return skinType switch { 24 | SkinType.Default => _light, 25 | SkinType.Dark => _dark, 26 | SkinType.Violet => _violet, 27 | _ => _system 28 | }; 29 | } 30 | 31 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /IPConfig/Converters/SkinTypeToolTipConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | using HandyControl.Data; 6 | 7 | using IPConfig.Languages; 8 | 9 | namespace IPConfig.Converters; 10 | 11 | public class SkinTypeToolTipConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | var skinType = value as SkinType?; 16 | 17 | return skinType switch { 18 | SkinType.Default => $"{Lang.Theme}: {Lang.Light}", 19 | SkinType.Dark => $"{Lang.Theme}: {Lang.Dark}", 20 | SkinType.Violet => $"{Lang.Theme}: {Lang.Violet}", 21 | _ => $"{Lang.Theme}: {Lang.UseSystemSetting}" 22 | }; 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /IPConfig/Converters/StringRemoveNewLineConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text.RegularExpressions; 4 | using System.Windows.Data; 5 | 6 | namespace IPConfig.Converters; 7 | 8 | public partial class StringRemoveNewLineConverter : IValueConverter 9 | { 10 | public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | if (value is string str) 13 | { 14 | return RemoveNewLineRegex().Replace(str, " "); 15 | } 16 | 17 | return value?.ToString(); 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | [GeneratedRegex(@"\t|\n|\r")] 26 | private static partial Regex RemoveNewLineRegex(); 27 | } 28 | -------------------------------------------------------------------------------- /IPConfig/Converters/StringToIntConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace IPConfig.Converters; 6 | 7 | public class StringToIntConverter : IValueConverter 8 | { 9 | public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | return Int32.TryParse(value?.ToString(), out int v) ? v : value; 12 | } 13 | 14 | public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | return value?.ToString(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /IPConfig/Converters/ValidationErrorsToolTipConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Globalization; 4 | using System.Text; 5 | using System.Windows.Controls; 6 | using System.Windows.Data; 7 | 8 | namespace IPConfig.Converters; 9 | 10 | public class ValidationErrorsToolTipConverter : IValueConverter 11 | { 12 | public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | var sb = new StringBuilder(); 15 | 16 | if (value is ReadOnlyObservableCollection errors) 17 | { 18 | if (errors is [var err]) 19 | { 20 | return err.ErrorContent; 21 | } 22 | 23 | foreach (var error in errors) 24 | { 25 | sb.AppendLine($"• {error.ErrorContent}"); 26 | } 27 | 28 | string errStr = sb.ToString().Trim(); 29 | 30 | return String.IsNullOrEmpty(errStr) ? null : errStr; 31 | } 32 | 33 | return null; 34 | } 35 | 36 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 37 | { 38 | throw new NotImplementedException(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /IPConfig/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace IPConfig.Extensions; 7 | 8 | public static class EnumerableExtensions 9 | { 10 | public static int Offset { get; set; } = 2; 11 | 12 | public static string ToStringWithLeftAlignment(this IEnumerable collection, int leftAlign) 13 | { 14 | if (!collection.Any()) 15 | { 16 | return String.Empty; 17 | } 18 | 19 | var sb = new StringBuilder(Environment.NewLine); 20 | string left = new(' ', leftAlign + Offset); 21 | 22 | foreach (var item in collection) 23 | { 24 | sb.AppendLine($"{left}{item?.ToString()}"); 25 | } 26 | 27 | return sb.ToString().TrimEnd(); 28 | } 29 | 30 | public static string ToStringWithLeftAlignment(this IEnumerable collection, string leftAlign) 31 | { 32 | int len = leftAlign.Length; 33 | int byteCount = Encoding.UTF8.GetByteCount(leftAlign); 34 | int align = (len + byteCount) / 2; 35 | 36 | return ToStringWithLeftAlignment(collection, align); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /IPConfig/Extensions/IPConfigModelExtensions.cs: -------------------------------------------------------------------------------- 1 | using IPConfig.Models; 2 | 3 | namespace IPConfig.Extensions; 4 | 5 | public static class IPConfigModelExtensions 6 | { 7 | public static EditableIPConfigModel AsEditable(this IPConfigModel source, bool beginEdit = true) 8 | { 9 | var target = EditableIPConfigModel.Empty; 10 | source.DeepCloneTo(target); 11 | 12 | if (beginEdit) 13 | { 14 | target.BeginEdit(); 15 | } 16 | 17 | return target; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /IPConfig/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IPConfig.Extensions; 4 | 5 | public static class StringExtensions 6 | { 7 | public static string Format(this string format, params object[] args) 8 | { 9 | return String.Format(format, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /IPConfig/Extensions/WindowsThemeExtensions.cs: -------------------------------------------------------------------------------- 1 | using HandyControl.Data; 2 | 3 | using static IPConfig.Helpers.ThemeWatcher; 4 | 5 | namespace IPConfig.Extensions; 6 | 7 | public static class WindowsThemeExtensions 8 | { 9 | public static SkinType ToSkinType(this WindowsTheme windowsTheme) 10 | { 11 | return windowsTheme switch { 12 | WindowsTheme.Dark => SkinType.Dark, 13 | _ => SkinType.Default 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /IPConfig/Helpers/BytesFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IPConfig.Helpers; 4 | 5 | public static class BytesFormatter 6 | { 7 | private static readonly string[] _bandUnits = ["bps", "Kbps", "Mbps", "Gbps", "Tbps", "Pbps", "Ebps", "Zbps", "Ybps"]; 8 | 9 | private static readonly string[] _fileUnits = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "Zb", "Yb"]; 10 | 11 | private static readonly string[] _speedUnits = ["B/s", "KB/s", "MB/s", "GB/s", "TB/s", "PB/s", "EB/s", "Zb/s", "Yb/s"]; 12 | 13 | public static string ToFileSize(long size, int sys = 1024) 14 | { 15 | return Format(size, sys, _fileUnits); 16 | } 17 | 18 | public static string ToNetBand(long speed) 19 | { 20 | return Format(speed, 1000, _bandUnits); 21 | } 22 | 23 | public static string ToNetSpeed(long speed) 24 | { 25 | return Format(speed, 1024, _speedUnits); 26 | } 27 | 28 | private static string Format(long size, int sys, string[] units) 29 | { 30 | decimal rate = size; 31 | int idx = 0; 32 | 33 | while (rate >= sys) 34 | { 35 | rate /= sys; 36 | idx++; 37 | } 38 | 39 | return $"{Math.Round(rate, 1)} {units[idx]}"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /IPConfig/Helpers/ClipboardHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Threading.Tasks; 3 | using System.Windows; 4 | 5 | namespace IPConfig.Helpers; 6 | 7 | public static class ClipboardHelper 8 | { 9 | private const int CLIPBRD_E_CANT_OPEN = unchecked((int)0x800401D0); 10 | 11 | public static async Task SetTextAsync(string text) 12 | { 13 | for (int i = 0; i < 10; i++) 14 | { 15 | try 16 | { 17 | Clipboard.SetDataObject(text, true); 18 | 19 | return; 20 | } 21 | catch (COMException ex) when (ex.ErrorCode == CLIPBRD_E_CANT_OPEN) 22 | { } 23 | 24 | await Task.Delay(100); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /IPConfig/Helpers/FileOrderHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IPConfig.Helpers; 4 | 5 | public static class FileOrderHelper 6 | { 7 | public static int GetOrder(IEnumerable orders, int start = 1) 8 | { 9 | HashSet uniqueOrders = new(orders); 10 | 11 | for (int i = start; i < uniqueOrders.Count + 1; i++) 12 | { 13 | if (!uniqueOrders.Contains(i)) 14 | { 15 | return i; 16 | } 17 | } 18 | 19 | return uniqueOrders.Count + 1; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /IPConfig/Helpers/GroupItemHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Windows.Data; 3 | 4 | namespace IPConfig.Helpers; 5 | 6 | public class GroupItemHelper 7 | { 8 | public static ImmutableArray GetGroupItems(CollectionViewGroup group) 9 | { 10 | var builder = ImmutableArray.CreateBuilder(); 11 | 12 | void GetGroupItemsRec(CollectionViewGroup group) 13 | { 14 | foreach (object item in group.Items) 15 | { 16 | if (group.IsBottomLevel) 17 | { 18 | builder.Add((T)item); 19 | } 20 | else 21 | { 22 | GetGroupItemsRec((CollectionViewGroup)item); 23 | } 24 | } 25 | } 26 | 27 | GetGroupItemsRec(group); 28 | 29 | return builder.ToImmutableArray(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /IPConfig/Helpers/LiteDbHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | 6 | using IPConfig.Models; 7 | 8 | using LiteDB; 9 | 10 | namespace IPConfig.Helpers; 11 | 12 | public static class LiteDbHelper 13 | { 14 | private static bool _isDbBusy; 15 | 16 | public static bool IsDbBusy 17 | { 18 | get => _isDbBusy; 19 | private set 20 | { 21 | if (_isDbBusy != value) 22 | { 23 | _isDbBusy = value; 24 | OnStaticPropertyChanged(); 25 | } 26 | } 27 | } 28 | 29 | public static void Handle(Action> action) 30 | { 31 | Handle(col => { 32 | action(col); 33 | 34 | return true; 35 | }); 36 | } 37 | 38 | public static T Handle(Func, T> func) 39 | { 40 | IsDbBusy = true; 41 | 42 | using var db = new LiteDatabase("Filename=ipconfig.db; Connection=Shared;"); 43 | var col = db.GetCollection("ipconfigs"); 44 | 45 | var result = func(col); 46 | 47 | IsDbBusy = false; 48 | 49 | return result; 50 | } 51 | 52 | public static ILiteQueryable Query() 53 | { 54 | return Handle(col => { 55 | var result = col.Query(); 56 | 57 | return result; 58 | }); 59 | } 60 | 61 | public static void Query(Action> action) 62 | { 63 | Handle(col => { 64 | var result = col.Query().ToEnumerable(); 65 | action(result); 66 | }); 67 | } 68 | 69 | public static T Query(Func, T> func) 70 | { 71 | return Handle(col => { 72 | var enumerable = col.Query().ToEnumerable(); 73 | var result = func(enumerable); 74 | 75 | return result; 76 | }); 77 | } 78 | 79 | #region Static Properties Change Notification 80 | 81 | public static event EventHandler StaticPropertyChanged = delegate { }; 82 | 83 | private static void OnStaticPropertyChanged([CallerMemberName] string? staticPropertyName = null) 84 | { 85 | StaticPropertyChanged(null, new PropertyChangedEventArgs(staticPropertyName)); 86 | } 87 | 88 | #endregion Static Properties Change Notification 89 | } 90 | -------------------------------------------------------------------------------- /IPConfig/Helpers/NameOfHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace IPConfig.Helpers; 6 | 7 | /// 8 | /// How to use nameof for long property-paths to get the "fullnameof"? 9 | /// 10 | public static class NameOfHelper 11 | { 12 | public static string NameOf(Expression> property) 13 | { 14 | var members = new Stack(); 15 | 16 | for (var memberExpr = property.Body as MemberExpression; memberExpr is not null; memberExpr = memberExpr.Expression as MemberExpression) 17 | { 18 | members.Push(memberExpr.Member.Name); 19 | } 20 | 21 | return String.Join(".", members); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /IPConfig/Helpers/ResourceHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Windows; 4 | 5 | namespace IPConfig.Helpers; 6 | 7 | public static class ResourceHelper 8 | { 9 | public static Stream? GetResourceStream(string path) 10 | { 11 | try 12 | { 13 | var stream = Application.GetResourceStream(new(path, UriKind.RelativeOrAbsolute)).Stream; 14 | 15 | return stream; 16 | } 17 | catch (IOException) 18 | { 19 | return null; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /IPConfig/Helpers/ThemeManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.CompilerServices; 4 | using System.Windows; 5 | 6 | using HandyControl.Data; 7 | 8 | using IPConfig.Extensions; 9 | 10 | namespace IPConfig.Helpers; 11 | 12 | public static class ThemeManager 13 | { 14 | private static SkinType _currentSkinType; 15 | 16 | private static SkinType? _currentSkinTypeMode; 17 | 18 | public static SkinType CurrentSkinType 19 | { 20 | get => _currentSkinType; 21 | private set 22 | { 23 | _currentSkinType = value; 24 | OnStaticPropertyChanged(); 25 | } 26 | } 27 | 28 | /// 29 | /// : Light 30 | /// : Dark 31 | /// : Violet 32 | /// : Use System Setting 33 | /// 34 | public static SkinType? CurrentSkinTypeMode 35 | { 36 | get => _currentSkinTypeMode; 37 | private set 38 | { 39 | _currentSkinTypeMode = value; 40 | OnStaticPropertyChanged(); 41 | } 42 | } 43 | 44 | public static event EventHandler? ThemeChanged; 45 | 46 | public static event EventHandler? ThemeChanging; 47 | 48 | public static void UpdateSkin(SkinType? skin) 49 | { 50 | SkinType actualSkinType; 51 | 52 | if (skin is null) 53 | { 54 | actualSkinType = ThemeWatcher.GetCurrentWindowsTheme().ToSkinType(); 55 | } 56 | else 57 | { 58 | actualSkinType = skin.Value; 59 | } 60 | 61 | if (actualSkinType == CurrentSkinType) 62 | { 63 | CurrentSkinTypeMode = skin; 64 | 65 | return; 66 | } 67 | 68 | ThemeChanging?.Invoke(null, skin); 69 | 70 | App.Current.Resources.MergedDictionaries.Clear(); 71 | 72 | App.Current.Resources.MergedDictionaries.Add(new ResourceDictionary { 73 | Source = new($"pack://application:,,,/HandyControl;component/Themes/Skin{actualSkinType}.xaml") 74 | }); 75 | 76 | App.Current.Resources.MergedDictionaries.Add(new ResourceDictionary { 77 | Source = new("pack://application:,,,/HandyControl;component/Themes/Theme.xaml") 78 | }); 79 | 80 | App.Current.Resources.MergedDictionaries.Add(new ResourceDictionary { 81 | Source = new("/Themes/FixedHandyControlTextBoxPlusTemplate.xaml", UriKind.Relative) 82 | }); 83 | 84 | App.Current.Resources.MergedDictionaries.Add(new ResourceDictionary { 85 | Source = new("/Themes/FixedHandyControlSearchBarPlusTemplate.xaml", UriKind.Relative) 86 | }); 87 | 88 | App.Current.Resources.MergedDictionaries.Add(new ResourceDictionary { 89 | Source = new("/Themes/MyResources.xaml", UriKind.Relative) 90 | }); 91 | 92 | App.Current.Resources.MergedDictionaries.Add(new ResourceDictionary { 93 | Source = new("/Themes/MyStyles.xaml", UriKind.Relative) 94 | }); 95 | 96 | // 优化显示效果。 97 | var theme = actualSkinType switch { 98 | SkinType.Default => new ResourceDictionary { 99 | Source = new("/Themes/MyLightTheme.xaml", UriKind.Relative) 100 | }, 101 | SkinType.Dark => new ResourceDictionary { 102 | Source = new("/Themes/MyDarkTheme.xaml", UriKind.Relative) 103 | }, 104 | SkinType.Violet => new ResourceDictionary { 105 | Source = new("/Themes/MyVioletTheme.xaml", UriKind.Relative) 106 | }, 107 | _ => throw new NotImplementedException() 108 | }; 109 | 110 | App.Current.Resources.MergedDictionaries.Add(theme); 111 | CurrentSkinTypeMode = skin; 112 | CurrentSkinType = actualSkinType; 113 | 114 | ThemeChanged?.Invoke(null, skin); 115 | } 116 | 117 | #region Static Properties Change Notification 118 | 119 | public static event EventHandler StaticPropertyChanged = delegate { }; 120 | 121 | private static void OnStaticPropertyChanged([CallerMemberName] string? staticPropertyName = null) 122 | { 123 | StaticPropertyChanged(null, new PropertyChangedEventArgs(staticPropertyName)); 124 | } 125 | 126 | #endregion Static Properties Change Notification 127 | } 128 | -------------------------------------------------------------------------------- /IPConfig/Helpers/ThemeWatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Management; 4 | using System.Security.Principal; 5 | 6 | using Microsoft.Win32; 7 | 8 | namespace IPConfig.Helpers; 9 | 10 | /// 11 | /// Dark Theme in WPF 12 | /// 13 | public static class ThemeWatcher 14 | { 15 | public class ThemeChangedArgs(WindowsTheme windowsTheme) 16 | { 17 | public WindowsTheme WindowsTheme { set; get; } = windowsTheme; 18 | } 19 | 20 | public enum WindowsTheme 21 | { 22 | Default = 0, 23 | 24 | Light = 1, 25 | 26 | Dark = 2, 27 | } 28 | 29 | private const string RegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; 30 | 31 | private const string RegistryValueName = "AppsUseLightTheme"; 32 | 33 | private static WindowsTheme _currentWindowsTheme; 34 | 35 | public static event EventHandler? WindowsThemeChanged; 36 | 37 | public static WindowsTheme GetCurrentWindowsTheme() 38 | { 39 | var theme = WindowsTheme.Light; 40 | 41 | try 42 | { 43 | using var key = Registry.CurrentUser.OpenSubKey(RegistryKeyPath); 44 | object? registryValueObject = key?.GetValue(RegistryValueName); 45 | 46 | if (registryValueObject is null) 47 | { 48 | return WindowsTheme.Light; 49 | } 50 | 51 | int registryValue = (int)registryValueObject; 52 | theme = registryValue > 0 ? WindowsTheme.Light : WindowsTheme.Dark; 53 | 54 | return theme; 55 | } 56 | catch (Exception) 57 | { 58 | return theme; 59 | } 60 | } 61 | 62 | public static void StartThemeWatching() 63 | { 64 | var currentUser = WindowsIdentity.GetCurrent(); 65 | 66 | string query = String.Format( 67 | CultureInfo.InvariantCulture, 68 | @"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{0}\\{1}' AND ValueName = '{2}'", 69 | currentUser?.User?.Value, 70 | RegistryKeyPath.Replace(@"\", @"\\"), 71 | RegistryValueName); 72 | 73 | try 74 | { 75 | _currentWindowsTheme = GetCurrentWindowsTheme(); 76 | var watcher = new ManagementEventWatcher(query); 77 | watcher.EventArrived += Watcher_EventArrived; 78 | 79 | // Start listening for events. 80 | watcher.Start(); 81 | } 82 | catch (Exception) 83 | { 84 | // This can fail on Windows 7. 85 | _currentWindowsTheme = WindowsTheme.Default; 86 | } 87 | } 88 | 89 | private static void Watcher_EventArrived(object sender, EventArrivedEventArgs e) 90 | { 91 | var newWindowsTheme = GetCurrentWindowsTheme(); 92 | 93 | if (newWindowsTheme != _currentWindowsTheme) 94 | { 95 | _currentWindowsTheme = newWindowsTheme; 96 | WindowsThemeChanged?.Invoke(sender, new(_currentWindowsTheme)); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /IPConfig/Helpers/UriHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace IPConfig.Helpers; 5 | 6 | public static class UriHelper 7 | { 8 | public static string NormalizeUri(string uri) 9 | { 10 | // HACK: 如果不转换为 AbsoluteUri,那么链接中的 “%20” 将以空格形式传入参数。 11 | string absoluteUri = new Uri(uri, UriKind.RelativeOrAbsolute).AbsoluteUri; 12 | 13 | // HACK: 如果不替换 “&”,那么 “&” 后面的内容将被截断。 14 | absoluteUri = absoluteUri.Replace("&", "^&"); 15 | 16 | return absoluteUri; 17 | } 18 | 19 | public static void OpenUri(string uri) 20 | { 21 | uri = NormalizeUri(uri); 22 | 23 | var psi = new ProcessStartInfo { 24 | FileName = "cmd", 25 | WindowStyle = ProcessWindowStyle.Hidden, 26 | UseShellExecute = true, 27 | CreateNoWindow = true, 28 | Arguments = $"/c start {uri}" 29 | }; 30 | 31 | Process.Start(psi); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /IPConfig/IPConfig.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows7.0 6 | enable 7 | true 8 | app.manifest 9 | Resources\ipconfig.ico 10 | true 11 | IPConfig.App 12 | 1.2.4 13 | true 14 | True 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | PreserveNewest 46 | 47 | 48 | PreserveNewest 49 | 50 | 51 | PreserveNewest 52 | 53 | 54 | 55 | 56 | 57 | True 58 | True 59 | Lang.Designer.tt 60 | 61 | 62 | True 63 | True 64 | Settings.settings 65 | 66 | 67 | 68 | 69 | 70 | TextTemplatingFileGenerator 71 | Lang.resx 72 | Lang.Designer.cs 73 | 74 | 75 | SettingsSingleFileGenerator 76 | Settings.Designer.cs 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /IPConfig/Languages/Lang.Designer.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Languages/Lang.Designer.cs -------------------------------------------------------------------------------- /IPConfig/Languages/Lang.Designer.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="true" language="C#" #> 2 | <#@ include file="$(SolutionDir)Languages.Designer.t4" #> -------------------------------------------------------------------------------- /IPConfig/Languages/LangExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Data; 2 | 3 | namespace IPConfig.Languages; 4 | 5 | /// 6 | /// localization-of-a-wpf-app-the-simple-approach 7 | /// 8 | public class LangExtension : Binding 9 | { 10 | public LangExtension(LangKey langKey) : base("[" + langKey + "]") 11 | { 12 | Mode = BindingMode.OneWay; 13 | Source = LangSource.Instance; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IPConfig/Languages/LangSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Globalization; 5 | using System.Resources; 6 | 7 | using IPConfig.Properties; 8 | 9 | using HcLang = HandyControl.Properties.Langs.Lang; 10 | 11 | namespace IPConfig.Languages; 12 | 13 | /// 14 | /// localization-of-a-wpf-app-the-simple-approach 15 | /// 16 | public sealed class LangSource : INotifyPropertyChanged 17 | { 18 | #region Singleton 19 | 20 | public static LangSource Instance { get; } = new LangSource(); 21 | 22 | static LangSource() 23 | { 24 | Instance.SetLanguage(Settings.Default.Language); 25 | } 26 | 27 | private LangSource() 28 | { } 29 | 30 | #endregion Singleton 31 | 32 | private CultureInfo _currentCulture = CultureInfo.CurrentUICulture; 33 | 34 | public CultureInfo CurrentCulture 35 | { 36 | get => _currentCulture; 37 | set 38 | { 39 | if (!_currentCulture.Equals(value)) 40 | { 41 | _currentCulture = value; 42 | PropertyChanged?.Invoke(this, new(String.Empty)); 43 | LanguageChanged?.Invoke(this, new(nameof(CurrentCulture))); 44 | } 45 | } 46 | } 47 | 48 | public string? this[string key] => Lang.ResourceManager.GetString(key, CurrentCulture); 49 | 50 | public string this[LangKey key] => Lang.ResourceManager.GetString(key.ToString(), CurrentCulture)!; 51 | 52 | public event PropertyChangedEventHandler? LanguageChanged; 53 | 54 | public event PropertyChangedEventHandler? PropertyChanged; 55 | 56 | public static List GetAvailableCultures() 57 | { 58 | var result = new List() { CultureInfo.GetCultureInfo("zh-CN") }; 59 | 60 | var rm = new ResourceManager(typeof(Lang)); 61 | 62 | var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); 63 | 64 | foreach (var culture in cultures) 65 | { 66 | try 67 | { 68 | // Do not use "==", won't work. 69 | if (culture.Equals(CultureInfo.InvariantCulture)) 70 | { 71 | continue; 72 | } 73 | 74 | using var rs = rm.GetResourceSet(culture, true, false); 75 | 76 | if (rs is not null) 77 | { 78 | result.Add(culture); 79 | } 80 | } 81 | catch (CultureNotFoundException) 82 | { 83 | // NOP. 84 | } 85 | } 86 | 87 | return result; 88 | } 89 | 90 | public void SetLanguage(string locale) 91 | { 92 | if (String.IsNullOrEmpty(locale)) 93 | { 94 | return; 95 | } 96 | 97 | var newCulture = CultureInfo.GetCultureInfo(locale); 98 | Lang.Culture = newCulture; 99 | CurrentCulture = newCulture; 100 | HcLang.Culture = newCulture; 101 | 102 | Settings.Default.Language = locale; 103 | Settings.Default.Save(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /IPConfig/Models/EditableIPConfigModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Text.Json.Serialization; 9 | 10 | using IPConfig.Helpers; 11 | 12 | using LiteDB; 13 | 14 | namespace IPConfig.Models; 15 | 16 | [method: BsonCtor] 17 | public partial class EditableIPConfigModel(string name) : IPConfigModel(name), 18 | IDeepCloneable, IDeepCloneTo, 19 | IEditableObject, IRevertibleChangeTracking 20 | { 21 | private IPConfigModel _backup = IPConfigModel.Empty; 22 | 23 | private bool _inTxn = false; 24 | 25 | public static new EditableIPConfigModel Empty => new(""); 26 | 27 | /// 28 | /// 获取备份对象的副本。 29 | /// 30 | public IPConfigModel Backup => _backup.DeepClone(); 31 | 32 | [JsonIgnore] 33 | public int Order { get; set; } = -1; 34 | 35 | public event EventHandler? EditableChanged; 36 | 37 | public event PropertyChangedEventHandler? EditablePropertyChanged; 38 | 39 | public bool IsPropertyChanged(Func property) where T : notnull 40 | { 41 | return IsPropertyChanged(property, out _); 42 | } 43 | 44 | public bool IsPropertyChanged(Func property, [NotNullWhen(true)] out T? oldValue) where T : notnull 45 | { 46 | var prop = property(this); 47 | 48 | if (!_inTxn) 49 | { 50 | oldValue = prop; 51 | 52 | return false; 53 | } 54 | 55 | var backup = property(_backup); 56 | 57 | if (EqualityComparer.Default.Equals(prop, backup)) 58 | { 59 | oldValue = prop; 60 | 61 | return false; 62 | } 63 | 64 | oldValue = backup; 65 | 66 | return true; 67 | } 68 | 69 | public bool IsPropertyError(Expression> property, out IEnumerable validationResults) 70 | { 71 | string propertyName = NameOfHelper.NameOf(property); 72 | 73 | if (propertyName.StartsWith($"{nameof(IPv4Config)}.")) 74 | { 75 | validationResults = IPv4Config.GetErrors(propertyName[$"{nameof(IPv4Config)}.".Length..]); 76 | } 77 | else 78 | { 79 | validationResults = GetErrors(); 80 | } 81 | 82 | return validationResults.Any(); 83 | } 84 | 85 | public bool IsPropertyError(Expression> property) 86 | { 87 | return IsPropertyError(property, out _); 88 | } 89 | 90 | protected override void OnPropertyChanged(PropertyChangedEventArgs e) 91 | { 92 | base.OnPropertyChanged(e); 93 | 94 | if (e.PropertyName != nameof(IsChanged)) 95 | { 96 | OnPropertyChanged(nameof(IsChanged)); 97 | EditablePropertyChanged?.Invoke(this, e); 98 | } 99 | else 100 | { 101 | EditableChanged?.Invoke(this, IsChanged); 102 | } 103 | } 104 | 105 | #region IDeepCloneable 106 | 107 | public new EditableIPConfigModel DeepClone() 108 | { 109 | var clone = Empty; 110 | DeepCloneTo(clone); 111 | 112 | return clone; 113 | } 114 | 115 | #endregion IDeepCloneable 116 | 117 | #region IDeepCloneTo 118 | 119 | public void DeepCloneTo(EditableIPConfigModel other) 120 | { 121 | base.DeepCloneTo(other); 122 | _backup.DeepCloneTo(other._backup); 123 | other._inTxn = _inTxn; 124 | other.Order = Order; 125 | } 126 | 127 | #endregion IDeepCloneTo 128 | 129 | #region IEditableObject 130 | 131 | public void BeginEdit() 132 | { 133 | if (!_inTxn) 134 | { 135 | _backup = DeepClone(); 136 | OnPropertyChanged(nameof(Backup)); 137 | _inTxn = true; 138 | OnPropertyChanged(nameof(IsChanged)); 139 | } 140 | } 141 | 142 | public void CancelEdit() 143 | { 144 | if (_inTxn) 145 | { 146 | _backup.DeepCloneTo(this); 147 | _inTxn = false; 148 | OnPropertyChanged(nameof(IsChanged)); 149 | } 150 | } 151 | 152 | public void EndEdit() 153 | { 154 | if (_inTxn) 155 | { 156 | _backup = Empty; 157 | OnPropertyChanged(nameof(Backup)); 158 | _inTxn = false; 159 | OnPropertyChanged(nameof(IsChanged)); 160 | } 161 | } 162 | 163 | #endregion IEditableObject 164 | 165 | #region IRevertibleChangeTracking 166 | 167 | [BsonIgnore] 168 | [JsonIgnore] 169 | public bool IsChanged => _inTxn && !PropertyEquals(_backup); 170 | 171 | public void AcceptChanges() 172 | { 173 | EndEdit(); 174 | BeginEdit(); 175 | } 176 | 177 | public void RejectChanges() 178 | { 179 | CancelEdit(); 180 | BeginEdit(); 181 | } 182 | 183 | #endregion IRevertibleChangeTracking 184 | } 185 | -------------------------------------------------------------------------------- /IPConfig/Models/Error.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace IPConfig.Models; 7 | 8 | public class Error 9 | { 10 | public class SerializableException 11 | { 12 | public Dictionary Datas = []; 13 | 14 | public string? HelpLink { get; } 15 | 16 | public int HResult { get; } 17 | 18 | public SerializableException? InnerException { get; } 19 | 20 | public string Message { get; } 21 | 22 | public string? Source { get; } 23 | 24 | public string? StackTrace { get; } 25 | 26 | public string? TargetSite { get; } 27 | 28 | public string Type { get; } 29 | 30 | public SerializableException(Exception exception) 31 | { 32 | HelpLink = exception.HelpLink; 33 | HResult = exception.HResult; 34 | Message = exception.Message; 35 | Source = exception.Source; 36 | StackTrace = exception.StackTrace; 37 | TargetSite = exception.TargetSite?.ToString(); 38 | Type = exception.GetType().ToString(); 39 | 40 | foreach (DictionaryEntry data in exception.Data) 41 | { 42 | Datas.Add($"{data.Key}", data.Value?.ToString()); 43 | } 44 | 45 | if (exception?.InnerException is not null) 46 | { 47 | InnerException = new(exception.InnerException); 48 | } 49 | } 50 | 51 | public override string? ToString() 52 | { 53 | var sb = new StringBuilder(); 54 | sb.AppendLine($"{nameof(Type)}: {Type ?? "null"}"); 55 | sb.AppendLine($"{nameof(Source)}: {Source ?? "null"}"); 56 | sb.AppendLine($"{nameof(Message)}: {Message ?? "null"}"); 57 | sb.AppendLine($"{nameof(StackTrace)}:{Environment.NewLine}{StackTrace ?? "null"}"); 58 | sb.AppendLine($"{nameof(HResult)}: {HResult}"); 59 | sb.AppendLine($"{nameof(HelpLink)}: {HelpLink ?? "null"}"); 60 | sb.AppendLine($"{nameof(InnerException)}: {InnerException?.ToString() ?? "null"}"); 61 | 62 | return sb.ToString(); 63 | } 64 | } 65 | 66 | public SerializableException Exception { get; } 67 | 68 | public string Source { get; } 69 | 70 | public DateTime Timestamp { get; } 71 | 72 | public Error(Exception exception, string source) 73 | { 74 | Timestamp = DateTime.Now; 75 | Exception = new(exception); 76 | Source = source; 77 | } 78 | 79 | public Error(Exception exception, string source, DateTime timestamp) 80 | { 81 | Timestamp = timestamp; 82 | Exception = new(exception); 83 | Source = source; 84 | } 85 | 86 | public override string ToString() 87 | { 88 | var sb = new StringBuilder(); 89 | sb.AppendLine($"{nameof(Timestamp)}: {Timestamp.ToString()}"); 90 | sb.AppendLine($"{nameof(Source)}: {Source}"); 91 | sb.AppendLine($"{nameof(Exception)}: {Exception.ToString()}"); 92 | 93 | return sb.ToString(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /IPConfig/Models/GitHub/GitHubApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Net.Http.Headers; 4 | using System.Net.Http.Json; 5 | using System.Text.Json.Nodes; 6 | using System.Threading.Tasks; 7 | 8 | namespace IPConfig.Models.GitHub; 9 | 10 | public static class GitHubApi 11 | { 12 | public const string GetLatestReleaseApi = $"https://api.github.com/repos/{Owner}/{RepositoryName}/releases/latest"; 13 | 14 | public const string Owner = "CodingOctocat"; 15 | 16 | public const string ReleasesUrl = $"{RepositoryUrl}/releases"; 17 | 18 | public const string RepositoryName = "IPConfig"; 19 | 20 | public const string RepositoryUrl = $"https://github.com/{Owner}/{RepositoryName}"; 21 | 22 | private static readonly HttpClient _httpClient = new(); 23 | 24 | static GitHubApi() 25 | { 26 | _httpClient.DefaultRequestHeaders.UserAgent.Add(new(new ProductHeaderValue($"{Owner}-{RepositoryName}", App.VersionString))); 27 | } 28 | 29 | public static async Task GetLatestReleaseInfoAsync() 30 | { 31 | var jObj = await _httpClient.GetFromJsonAsync(GetLatestReleaseApi); 32 | 33 | ArgumentNullException.ThrowIfNull(jObj); 34 | 35 | string tagName = jObj.GetValueEx("tag_name", ""); 36 | string name = jObj.GetValueEx("name", ""); 37 | string releaseNote = jObj.GetValueEx("body", ""); 38 | var publishedAt = jObj.GetValueEx("published_at", DateTimeOffset.MinValue); 39 | string htmlUrl = jObj.GetValueEx("html_url", ""); 40 | var info = new GitHubReleaseInfo(tagName, name, releaseNote, publishedAt, htmlUrl); 41 | 42 | return info; 43 | } 44 | 45 | private static T GetValueEx(this JsonObject self, string propertyName, T defaultValue) 46 | { 47 | if (self.TryGetPropertyValue(propertyName, out var node)) 48 | { 49 | try 50 | { 51 | var value = node!.GetValue(); 52 | 53 | return value; 54 | } 55 | catch (Exception ex) 56 | { 57 | throw new GitHubApiException(ex.Message, ex); 58 | } 59 | } 60 | else 61 | { 62 | return defaultValue; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /IPConfig/Models/GitHub/GitHubApiException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IPConfig.Models.GitHub; 4 | 5 | public class GitHubApiException : Exception 6 | { 7 | public GitHubApiException() 8 | { } 9 | 10 | public GitHubApiException(string? message) : base(message) 11 | { } 12 | 13 | public GitHubApiException(string? message, Exception? innerException) : base(message, innerException) 14 | { } 15 | } 16 | -------------------------------------------------------------------------------- /IPConfig/Models/GitHub/GitHubReleaseInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IPConfig.Models.GitHub; 4 | 5 | public record GitHubReleaseInfo(string TagName, string Name, string ReleaseNote, DateTimeOffset PublishedAt, string HtmlUrl); 6 | -------------------------------------------------------------------------------- /IPConfig/Models/IDeepCloneTo.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models; 2 | 3 | /// 4 | /// 支持深层克隆。 5 | /// 6 | /// 7 | public interface IDeepCloneTo 8 | { 9 | void DeepCloneTo(T other); 10 | } 11 | -------------------------------------------------------------------------------- /IPConfig/Models/IDeepCloneable.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models; 2 | 3 | /// 4 | /// 支持深层克隆。 5 | /// 6 | /// 7 | public interface IDeepCloneable 8 | { 9 | T DeepClone(); 10 | } 11 | -------------------------------------------------------------------------------- /IPConfig/Models/IPAdvancedConfigBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Net.NetworkInformation; 3 | 4 | using IPConfig.Extensions; 5 | using IPConfig.Languages; 6 | 7 | namespace IPConfig.Models; 8 | 9 | /// 10 | /// 提供 TCP/IPv4 和 TCP/IPv6 高级设置的公共属性部分。 11 | /// 12 | public abstract record IPAdvancedConfigBase 13 | { 14 | #region TCP/IPv4 和 TCP/IPv6 常规属性 15 | 16 | public ImmutableList AlternateDnsCollection { get; init; } = []; 17 | 18 | public ImmutableList AlternateGatewayCollection { get; init; } = []; 19 | 20 | public ImmutableList AlternateIPCollection { get; init; } = []; 21 | 22 | public required string DhcpLeaseLifetime { get; init; } 23 | 24 | public ImmutableList DhcpLeaseLifetimeCollection { get; init; } = []; 25 | 26 | public required string PreferredDns { get; init; } 27 | 28 | public required string PreferredGateway { get; init; } 29 | 30 | public required string PreferredIP { get; init; } 31 | 32 | public required string PreferredLifetime { get; init; } 33 | 34 | public ImmutableList PreferredLifetimeCollection { get; init; } = []; 35 | 36 | public required string ValidLifetime { get; init; } 37 | 38 | public ImmutableList ValidLifetimeCollection { get; init; } = []; 39 | 40 | #endregion TCP/IPv4 和 TCP/IPv6 常规属性 41 | 42 | #region TCP/IPv4 和 TCP/IPv6 高级信息 43 | 44 | public required DuplicateAddressDetectionState DuplicateAddressDetectionState { get; init; } 45 | 46 | public ImmutableList DuplicateAddressDetectionStateCollcetion { get; init; } = []; 47 | 48 | public required bool IsDnsEligible { get; init; } 49 | 50 | public ImmutableList IsDnsEligibleCollcetion { get; init; } = []; 51 | 52 | public required bool IsTransient { get; init; } 53 | 54 | public ImmutableList IsTransientCollection { get; init; } = []; 55 | 56 | #endregion TCP/IPv4 和 TCP/IPv6 高级信息 57 | 58 | public abstract string FormatGeneralProperties(); 59 | 60 | public virtual string FormatLifetimes() 61 | { 62 | return $""" 63 | {Lang.ValidLifetime}: {ValidLifetime}{ValidLifetimeCollection.ToStringWithLeftAlignment(Lang.ValidLifetime)} 64 | {Lang.PreferredLifetime}: {PreferredLifetime}{PreferredLifetimeCollection.ToStringWithLeftAlignment(Lang.PreferredLifetime)} 65 | {Lang.DhcpLeaseLifetime}: {DhcpLeaseLifetime}{DhcpLeaseLifetimeCollection.ToStringWithLeftAlignment(Lang.DhcpLeaseLifetime)} 66 | """; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /IPConfig/Models/IPConfigBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Text.Json.Serialization; 3 | using System.Text.RegularExpressions; 4 | 5 | using CommunityToolkit.Mvvm.ComponentModel; 6 | 7 | namespace IPConfig.Models; 8 | 9 | public abstract partial class IPConfigBase : ObservableValidator, 10 | IDeepCloneable, IDeepCloneTo where T : IPConfigBase, new() 11 | { 12 | protected bool _allowAutoDisableAutoDns; 13 | 14 | protected bool _allowAutoDisableDhcp; 15 | 16 | protected string _dns1 = ""; 17 | 18 | protected string _dns2 = ""; 19 | 20 | protected string _gateway = ""; 21 | 22 | protected string _iP = ""; 23 | 24 | protected bool _isAutoDns; 25 | 26 | protected bool _isDhcpEnabled; 27 | 28 | protected string _mask = ""; 29 | 30 | [JsonPropertyOrder(5)] 31 | public virtual string Dns1 32 | { 33 | get => _dns1; 34 | set 35 | { 36 | if (TryNormalizeIPAddress(value, out string dns)) 37 | { 38 | SetProperty(ref _dns1, dns, true); 39 | } 40 | } 41 | } 42 | 43 | [JsonPropertyOrder(6)] 44 | public virtual string Dns2 45 | { 46 | get => _dns2; 47 | set 48 | { 49 | if (TryNormalizeIPAddress(value, out string dns)) 50 | { 51 | SetProperty(ref _dns2, dns, true); 52 | } 53 | } 54 | } 55 | 56 | [JsonPropertyOrder(3)] 57 | public virtual string Gateway 58 | { 59 | get => _gateway; 60 | set 61 | { 62 | if (TryNormalizeIPAddress(value, out string gateway)) 63 | { 64 | SetProperty(ref _gateway, gateway, true); 65 | } 66 | } 67 | } 68 | 69 | [JsonPropertyOrder(1)] 70 | public virtual string IP 71 | { 72 | get => _iP; 73 | set 74 | { 75 | if (TryNormalizeIPAddress(value, out string ip)) 76 | { 77 | SetProperty(ref _iP, ip, true); 78 | } 79 | } 80 | } 81 | 82 | [JsonPropertyOrder(4)] 83 | public virtual bool IsAutoDns 84 | { 85 | get => _isAutoDns; 86 | set 87 | { 88 | if (!IsDhcpEnabled) 89 | { 90 | value = false; 91 | } 92 | 93 | SetProperty(ref _isAutoDns, value); 94 | } 95 | } 96 | 97 | [JsonPropertyOrder(0)] 98 | public virtual bool IsDhcpEnabled 99 | { 100 | get => _isDhcpEnabled; 101 | set 102 | { 103 | SetProperty(ref _isDhcpEnabled, value); 104 | 105 | if (!_isDhcpEnabled) 106 | { 107 | IsAutoDns = false; 108 | } 109 | } 110 | } 111 | 112 | [JsonPropertyOrder(2)] 113 | public virtual string Mask 114 | { 115 | get => _mask; 116 | set 117 | { 118 | if (TryNormalizeIPAddress(value, out string mask)) 119 | { 120 | SetProperty(ref _mask, mask, true); 121 | } 122 | } 123 | } 124 | 125 | public void AllowAutoDisableAutoDns(bool allow = true) 126 | { 127 | _allowAutoDisableAutoDns = allow; 128 | } 129 | 130 | public void AllowAutoDisableDhcp(bool allow = true) 131 | { 132 | _allowAutoDisableDhcp = allow; 133 | } 134 | 135 | public virtual T DeepClone() 136 | { 137 | T clone = new(); 138 | DeepCloneTo(clone); 139 | 140 | return clone; 141 | } 142 | 143 | public virtual void DeepCloneTo(T other) 144 | { 145 | other.IP = IP; 146 | other.IsDhcpEnabled = IsDhcpEnabled; 147 | other.Mask = Mask; 148 | other.Gateway = Gateway; 149 | other.IsAutoDns = IsAutoDns; 150 | other.Dns1 = Dns1; 151 | other.Dns2 = Dns2; 152 | } 153 | 154 | public virtual void FormatProperties() 155 | { 156 | AllowAutoDisableDhcp(false); 157 | AllowAutoDisableAutoDns(false); 158 | 159 | InnerFormatProperties(); 160 | } 161 | 162 | protected abstract void InnerFormatProperties(); 163 | 164 | protected override void OnPropertyChanged(PropertyChangedEventArgs e) 165 | { 166 | base.OnPropertyChanged(e); 167 | 168 | if (_allowAutoDisableDhcp && e.PropertyName is nameof(IP) or nameof(Mask) or nameof(Gateway)) 169 | { 170 | IsDhcpEnabled = false; 171 | } 172 | else if (_allowAutoDisableAutoDns && e.PropertyName is nameof(Dns1) or nameof(Dns2)) 173 | { 174 | IsAutoDns = false; 175 | } 176 | } 177 | 178 | protected bool TryNormalizeIPAddress(string ip, out string normalizedIP) 179 | { 180 | normalizedIP = AutoPickupRegex().Replace(ip, "."); 181 | 182 | return PendingIPAddressRegex().IsMatch(normalizedIP); 183 | } 184 | 185 | [GeneratedRegex(@"[-`=\[\]\\;',/·【】、;‘’,。/]")] 186 | private static partial Regex AutoPickupRegex(); 187 | 188 | [GeneratedRegex(@"^\d{0,3}\.?\d{0,3}\.?\d{0,3}\.?\d{0,3}$")] 189 | private static partial Regex PendingIPAddressRegex(); 190 | } 191 | -------------------------------------------------------------------------------- /IPConfig/Models/IPConfigModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Text.Json.Serialization; 6 | 7 | using CommunityToolkit.Mvvm.ComponentModel; 8 | 9 | using IPConfig.Languages; 10 | using IPConfig.Models.Validations; 11 | using IPConfig.ViewModels; 12 | 13 | using LiteDB; 14 | 15 | namespace IPConfig.Models; 16 | 17 | /// 18 | /// 表示一个 IP 配置。 19 | /// 20 | public partial class IPConfigModel : ObservableValidator, IDeepCloneable, IDeepCloneTo 21 | { 22 | private IPv4Config _iPv4Config = new(); 23 | 24 | private string _name = ""; 25 | 26 | private string _remark = ""; 27 | 28 | public static IPConfigModel Empty => new(""); 29 | 30 | [BsonField("IPv4")] 31 | [JsonPropertyOrder(1)] 32 | [JsonPropertyName("IPv4")] 33 | [ForwardingErrors] 34 | public IPv4Config IPv4Config 35 | { 36 | get => _iPv4Config; 37 | set 38 | { 39 | if (EqualityComparer.Default.Equals(_iPv4Config, value)) 40 | { 41 | return; 42 | } 43 | 44 | OnPropertyChanging(); 45 | 46 | // 注销 oldValue 事件。 47 | _iPv4Config.ErrorsChanged -= IPv4Config_ErrorsChanged; 48 | _iPv4Config.PropertyChanged -= IPv4PropertyChanged; 49 | _iPv4Config.PropertyChanging -= IPv4PropertyChanging; 50 | 51 | // 确保 newValue 注册事件。 52 | value.PropertyChanging -= IPv4PropertyChanging; 53 | value.PropertyChanging += IPv4PropertyChanging; 54 | value.PropertyChanged -= IPv4PropertyChanged; 55 | value.PropertyChanged += IPv4PropertyChanged; 56 | value.ErrorsChanged -= IPv4Config_ErrorsChanged; 57 | value.ErrorsChanged += IPv4Config_ErrorsChanged; 58 | 59 | _iPv4Config = value; 60 | 61 | OnPropertyChanged(); 62 | 63 | ValidateProperty(_iPv4Config); 64 | } 65 | } 66 | 67 | [BsonId] 68 | [JsonPropertyOrder(0)] 69 | [Languages.Required(LangKey.Required)] 70 | [CustomValidation(typeof(IPConfigListViewModel), nameof(IPConfigListViewModel.ValidateName))] 71 | public string Name 72 | { 73 | get => _name; 74 | set 75 | { 76 | // 修复错误信息更新滞后的问题。 77 | ClearErrors(nameof(Name)); 78 | SetProperty(ref _name, value.Trim(), true); 79 | } 80 | } 81 | 82 | [JsonPropertyOrder(3)] 83 | public string Remark 84 | { 85 | get => _remark; 86 | set => SetProperty(ref _remark, value); 87 | } 88 | 89 | [BsonCtor] 90 | public IPConfigModel(string name) 91 | { 92 | Name = name; 93 | 94 | _iPv4Config = new(); 95 | _iPv4Config.PropertyChanging += IPv4PropertyChanging; 96 | _iPv4Config.PropertyChanged += IPv4PropertyChanged; 97 | _iPv4Config.ErrorsChanged += IPv4Config_ErrorsChanged; 98 | } 99 | 100 | public static IPConfigModel GetUntitledWith(int order) 101 | { 102 | return new($"{Lang.Untitled}{order}"); 103 | } 104 | 105 | public new void ClearErrors(string? propertyName = null) 106 | { 107 | base.ClearErrors(propertyName); 108 | IPv4Config.ClearErrors(propertyName); 109 | } 110 | 111 | #region IDeepCloneable 112 | 113 | public IPConfigModel DeepClone() 114 | { 115 | var clone = Empty; 116 | DeepCloneTo(clone); 117 | 118 | return clone; 119 | } 120 | 121 | #endregion IDeepCloneable 122 | 123 | #region IDeepCloneTo 124 | 125 | public void DeepCloneTo(IPConfigModel other) 126 | { 127 | other.Name = Name; 128 | IPv4Config.DeepCloneTo(other.IPv4Config); 129 | other.Remark = Remark; 130 | } 131 | 132 | #endregion IDeepCloneTo 133 | 134 | public bool PropertyEquals(IPConfigModel other) 135 | { 136 | bool iPv4ConfigEquals = IPv4Config.PropertyEquals(other.IPv4Config); 137 | 138 | if (iPv4ConfigEquals && Name == other.Name && Remark == other.Remark) 139 | { 140 | return true; 141 | } 142 | 143 | return false; 144 | } 145 | 146 | public override string ToString() 147 | { 148 | string? remark = Remark?.Length > 200 149 | ? String.Concat(Remark.AsSpan(0, 200), "…") 150 | : Remark; 151 | 152 | remark = remark?.Trim(); 153 | 154 | return $""" 155 | {Name} 156 | 157 | {IPv4Config} 158 | 159 | {Lang.Remark}: {(String.IsNullOrEmpty(remark) ? Lang.None : remark)} 160 | """; 161 | } 162 | 163 | public new void ValidateAllProperties() 164 | { 165 | base.ValidateAllProperties(); 166 | 167 | IPv4Config.ValidateAllProperties(); 168 | } 169 | 170 | private void IPv4Config_ErrorsChanged(object? sender, DataErrorsChangedEventArgs e) 171 | { 172 | ValidateProperty(IPv4Config, nameof(IPv4Config)); 173 | } 174 | 175 | private void IPv4PropertyChanged(object? sender, PropertyChangedEventArgs e) 176 | { 177 | OnPropertyChanged(e.PropertyName); 178 | } 179 | 180 | private void IPv4PropertyChanging(object? sender, PropertyChangingEventArgs e) 181 | { 182 | OnPropertyChanging(e.PropertyName); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /IPConfig/Models/IPv4AdvancedConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | 4 | using IPConfig.Extensions; 5 | using IPConfig.Languages; 6 | 7 | namespace IPConfig.Models; 8 | 9 | /// 10 | /// 高级 TCP/IPv4 设置属性。 11 | /// 12 | public record IPv4AdvancedConfig : IPAdvancedConfigBase 13 | { 14 | #region TCP/IPv4 常规属性 15 | 16 | public ImmutableList AlternateMaskCollection { get; init; } = []; 17 | 18 | public required bool IsAutoDns { get; init; } 19 | 20 | public required bool IsDhcpEnabled { get; init; } 21 | 22 | public required string PreferredMask { get; init; } 23 | 24 | public required string WinsServerAddress { get; init; } 25 | 26 | public ImmutableList WinsServerAddressCollection { get; init; } = []; 27 | 28 | #endregion TCP/IPv4 常规属性 29 | 30 | /// 31 | /// 转换为 。 32 | /// 33 | /// 34 | public IPv4Config ToIPv4Config() 35 | { 36 | return new IPv4Config() { 37 | IsDhcpEnabled = IsDhcpEnabled, 38 | IP = PreferredIP, 39 | Mask = PreferredMask, 40 | Gateway = PreferredGateway, 41 | IsAutoDns = IsAutoDns, 42 | Dns1 = PreferredDns, 43 | Dns2 = AlternateDnsCollection.ElementAtOrDefault(1) ?? "" 44 | }; 45 | } 46 | 47 | public override string FormatGeneralProperties() 48 | { 49 | return $""" 50 | {Lang.IPv4IsDhcpEnabled}: {IsDhcpEnabled} 51 | IP: {PreferredIP}{AlternateIPCollection.ToStringWithLeftAlignment("IP")} 52 | {Lang.IPv4SubnetMask}: {PreferredMask}{AlternateMaskCollection.ToStringWithLeftAlignment(Lang.IPv4SubnetMask)} 53 | {Lang.DefaultGateway}: {PreferredGateway}{AlternateGatewayCollection.ToStringWithLeftAlignment(Lang.DefaultGateway)} 54 | {Lang.IPv4IsAutoDns}: {IsAutoDns} 55 | DNS: {PreferredDns}{AlternateDnsCollection.ToStringWithLeftAlignment("DNS")} 56 | """; 57 | } 58 | 59 | public override string ToString() 60 | { 61 | return $""" 62 | [{Lang.IPv4GeneralProperties_Header}] 63 | {FormatGeneralProperties()} 64 | 65 | [{Lang.IPv4Lifetimes_Header}] 66 | {FormatLifetimes()} 67 | """; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /IPConfig/Models/IPv4Config.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Net; 3 | using System.Text.Json.Serialization; 4 | 5 | using IPConfig.Languages; 6 | using IPConfig.Models.Validations; 7 | 8 | namespace IPConfig.Models; 9 | 10 | /// 11 | /// 具有基本属性的 TCP/IPv4 配置。 12 | /// 13 | public class IPv4Config : IPConfigBase 14 | { 15 | [JsonPropertyOrder(5)] 16 | [RequiredIf(nameof(IsAutoDns), false)] 17 | [IPValidation(LangKey.InvalidIPv4Dns)] 18 | public override string Dns1 { get => base.Dns1; set => base.Dns1 = value; } 19 | 20 | [JsonPropertyOrder(6)] 21 | [IPValidation(LangKey.InvalidIPv4Dns)] 22 | public override string Dns2 { get => base.Dns2; set => base.Dns2 = value; } 23 | 24 | [JsonPropertyOrder(3)] 25 | [IPValidation(LangKey.InvalidIPv4DefaultGatewayAddress)] 26 | public override string Gateway { get => base.Gateway; set => base.Gateway = value; } 27 | 28 | [JsonPropertyOrder(1)] 29 | [RequiredIf(nameof(IsDhcpEnabled), false)] 30 | [IPValidation(LangKey.InvalidIPv4Address)] 31 | public override string IP { get => base.IP; set => base.IP = value; } 32 | 33 | [JsonPropertyOrder(2)] 34 | [RequiredIf(nameof(IsDhcpEnabled), false)] 35 | [IPValidation(LangKey.InvalidIPv4SubnetMask)] 36 | public override string Mask { get => _mask; set => SetProperty(ref _mask, value.Trim(), true); } 37 | 38 | public new void ClearErrors(string? propertyName = null) 39 | { 40 | base.ClearErrors(propertyName); 41 | } 42 | 43 | public bool PropertyEquals(IPv4Config other) 44 | { 45 | if (IP == other.IP 46 | && IsDhcpEnabled == other.IsDhcpEnabled 47 | && Mask == other.Mask 48 | && Gateway == other.Gateway 49 | && IsAutoDns == other.IsAutoDns 50 | && Dns1 == other.Dns1 51 | && Dns2 == other.Dns2) 52 | { 53 | return true; 54 | } 55 | 56 | return false; 57 | } 58 | 59 | public override string ToString() 60 | { 61 | return $""" 62 | IPv4: {IP} 63 | {Lang.IPv4SubnetMask}: {Mask} 64 | {Lang.DefaultGateway}: {Gateway} 65 | {Lang.PreferredDns}: {Dns1} 66 | {Lang.AlternateDns}: {Dns2} 67 | """; 68 | } 69 | 70 | public new void ValidateAllProperties() 71 | { 72 | base.ValidateAllProperties(); 73 | } 74 | 75 | protected override void InnerFormatProperties() 76 | { 77 | if (IPAddress.TryParse(IP, out var ip)) 78 | { 79 | IP = ip.ToString(); 80 | } 81 | 82 | if (IPAddress.TryParse(Mask, out var mask)) 83 | { 84 | Mask = mask.ToString(); 85 | } 86 | 87 | if (IPAddress.TryParse(Gateway, out var gateway)) 88 | { 89 | Gateway = gateway.ToString(); 90 | } 91 | 92 | if (IPAddress.TryParse(Dns1, out var dns1)) 93 | { 94 | Dns1 = dns1.ToString(); 95 | } 96 | 97 | if (IPAddress.TryParse(Dns2, out var dns2)) 98 | { 99 | Dns2 = dns2.ToString(); 100 | } 101 | } 102 | 103 | protected override void OnPropertyChanged(PropertyChangedEventArgs e) 104 | { 105 | base.OnPropertyChanged(e); 106 | 107 | // 修复数据验证不会及时更新的问题。 108 | if (e.PropertyName is nameof(IP) or nameof(IsDhcpEnabled)) 109 | { 110 | ClearErrors(nameof(IP)); 111 | ValidateProperty(IP, nameof(IP)); 112 | } 113 | else if (e.PropertyName is nameof(Mask) or nameof(IsDhcpEnabled)) 114 | { 115 | ClearErrors(nameof(Mask)); 116 | ValidateProperty(Mask, nameof(Mask)); 117 | } 118 | else if (e.PropertyName is nameof(Dns1) or nameof(IsAutoDns)) 119 | { 120 | ClearErrors(nameof(Dns1)); 121 | ValidateProperty(Dns1, nameof(Dns1)); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /IPConfig/Models/IPv4Dns.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.NetworkInformation; 6 | using System.Threading.Tasks; 7 | 8 | using CommunityToolkit.Mvvm.ComponentModel; 9 | 10 | using CsvHelper; 11 | using CsvHelper.Configuration; 12 | using CsvHelper.Configuration.Attributes; 13 | 14 | using IPConfig.Languages; 15 | 16 | namespace IPConfig.Models; 17 | 18 | public partial class IPv4Dns : ObservableObject 19 | { 20 | private static readonly string[] _emptyAndNull = ["", "NULL"]; 21 | 22 | private bool _isRunning; 23 | 24 | private PingReply? _pingReply; 25 | 26 | public string? Description { get; set; } 27 | 28 | public string Dns1 { get; set; } 29 | 30 | public string? Dns2 { get; set; } 31 | 32 | public string? Filter { get; set; } 33 | 34 | public string Group { get; set; } 35 | 36 | [Ignore] 37 | public bool IsRunning 38 | { 39 | get => _isRunning; 40 | private set => SetProperty(ref _isRunning, value); 41 | } 42 | 43 | [Ignore] 44 | public PingReply? PingReply 45 | { 46 | get => _pingReply; 47 | set => SetProperty(ref _pingReply, value); 48 | } 49 | 50 | public string Provider { get; set; } 51 | 52 | #pragma warning disable IDE0290 // 使用主构造函数 53 | 54 | public IPv4Dns( 55 | #pragma warning restore IDE0290 // 使用主构造函数 56 | [Name(nameof(Provider))] string provider, 57 | [Name(nameof(Filter))] string? filter, 58 | [Name(nameof(Dns1))] string dns1, 59 | [Name(nameof(Dns2))] string? dns2, 60 | [Name(nameof(Group))] string group, 61 | [Name(nameof(Description))] string? description) 62 | { 63 | Provider = provider; 64 | Filter = filter; 65 | Dns1 = dns1; 66 | Dns2 = dns2; 67 | Group = group; 68 | Description = description; 69 | } 70 | 71 | public static List ReadIPv4DnsList() 72 | { 73 | string[] files = Directory.GetFiles(@".\.dns", "*.csv"); 74 | 75 | var files1 = files.Where(x => x.EndsWith($".{LangSource.Instance.CurrentCulture.Name}.csv")); 76 | var files2 = files.Where(x => !Path.GetFileNameWithoutExtension(x).Contains('.')); 77 | 78 | var list = new List(); 79 | 80 | foreach (string file in files1.Concat(files2)) 81 | { 82 | using var reader = new StreamReader(file); 83 | 84 | var config = new CsvConfiguration(CultureInfo.InvariantCulture) { 85 | TrimOptions = TrimOptions.Trim 86 | }; 87 | 88 | using var csv = new CsvReader(reader, config); 89 | csv.Context.TypeConverterOptionsCache.GetOptions().NullValues.AddRange(_emptyAndNull); 90 | var records = csv.GetRecords(); 91 | list.AddRange(records); 92 | } 93 | 94 | return list; 95 | } 96 | 97 | public async Task PingAsync() 98 | { 99 | if (!IsRunning) 100 | { 101 | IsRunning = true; 102 | PingReply = null; 103 | 104 | using var ping = new Ping(); 105 | 106 | try 107 | { 108 | PingReply = await ping.SendPingAsync(Dns1); 109 | } 110 | catch 111 | { 112 | PingReply = null; 113 | } 114 | 115 | IsRunning = false; 116 | } 117 | } 118 | 119 | public override string ToString() 120 | { 121 | return Dns1; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /IPConfig/Models/IPv4Mask.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | using CsvHelper; 8 | using CsvHelper.Configuration; 9 | 10 | using IPConfig.Languages; 11 | 12 | namespace IPConfig.Models; 13 | 14 | public record IPv4Mask(string Mask, int CIDR, string Group) 15 | { 16 | private static readonly string[] _emptyAndNull = ["", "NULL"]; 17 | 18 | public static List ReadIPv4MaskList() 19 | { 20 | string[] files = Directory.GetFiles(@".\.mask", "*.csv"); 21 | 22 | var files1 = files.Where(x => x.EndsWith($".{LangSource.Instance.CurrentCulture.Name}.csv")); 23 | var files2 = files.Where(x => !Path.GetFileNameWithoutExtension(x).Contains('.')); 24 | 25 | var list = new List(); 26 | 27 | foreach (string file in files1.Concat(files2)) 28 | { 29 | using var reader = new StreamReader(file); 30 | 31 | var config = new CsvConfiguration(CultureInfo.InvariantCulture) { 32 | TrimOptions = TrimOptions.Trim 33 | }; 34 | 35 | using var csv = new CsvReader(reader, config); 36 | csv.Context.TypeConverterOptionsCache.GetOptions().NullValues.AddRange(_emptyAndNull); 37 | var records = csv.GetRecords(); 38 | list.AddRange(records); 39 | } 40 | 41 | return list; 42 | } 43 | 44 | public override string ToString() 45 | { 46 | return Mask; 47 | } 48 | } 49 | 50 | public enum IPv4MaskClass 51 | { 52 | [LocalizedDescription(LangKey.Default)] 53 | Default = 1, 54 | 55 | [Description("/0 ~ /8")] 56 | A = 2, 57 | 58 | [Description("/9 ~ /16")] 59 | B = 4, 60 | 61 | [Description("/17 ~ /24")] 62 | C = 8, 63 | 64 | [Description("/25 ~ /32")] 65 | D = 16 66 | } 67 | -------------------------------------------------------------------------------- /IPConfig/Models/IPv6AdvancedConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Net.NetworkInformation; 3 | 4 | using IPConfig.Extensions; 5 | using IPConfig.Languages; 6 | 7 | namespace IPConfig.Models; 8 | 9 | /// 10 | /// 高级 TCP/IPv6 设置属性。 11 | /// 12 | public record IPv6AdvancedConfig : IPAdvancedConfigBase 13 | { 14 | #region TCP/IPv6 常规属性 15 | 16 | public virtual ImmutableList AlternatePrefixLengthCollection { get; init; } = []; 17 | 18 | public virtual int PreferredPrefixLength { get; init; } 19 | 20 | public virtual PrefixOrigin PrefixOrigin { get; init; } 21 | 22 | public virtual ImmutableList PrefixOriginCollection { get; init; } = []; 23 | 24 | public virtual SuffixOrigin SuffixOrigin { get; init; } 25 | 26 | public virtual ImmutableList SuffixOriginCollection { get; init; } = []; 27 | 28 | #endregion TCP/IPv6 常规属性 29 | 30 | public override string FormatGeneralProperties() 31 | { 32 | return $""" 33 | IP: {PreferredIP}{AlternateIPCollection.ToStringWithLeftAlignment("IP")} 34 | {Lang.IPv6PrefixLength}: {PreferredPrefixLength}{AlternatePrefixLengthCollection.ToStringWithLeftAlignment(Lang.IPv6PrefixLength)} 35 | {Lang.IPv6PrefixOrigin}: {PrefixOrigin}{PrefixOriginCollection.ToStringWithLeftAlignment(Lang.IPv6PrefixOrigin)} 36 | {Lang.IPv6SuffixOrigin}: {SuffixOrigin}{SuffixOriginCollection.ToStringWithLeftAlignment(Lang.IPv6SuffixOrigin)} 37 | {Lang.DefaultGateway}: {PreferredGateway}{AlternateGatewayCollection.ToStringWithLeftAlignment(Lang.DefaultGateway)} 38 | DNS: {PreferredDns}{AlternateDnsCollection.ToStringWithLeftAlignment("DNS")} 39 | """; 40 | } 41 | 42 | public override string ToString() 43 | { 44 | return $""" 45 | [{Lang.IPv6GeneralProperties_Header}] 46 | {FormatGeneralProperties()} 47 | 48 | [{Lang.IPv6Lifetimes_Header}] 49 | {FormatLifetimes()} 50 | """; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /IPConfig/Models/LastUsedIPv4Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text.Json; 5 | using System.Threading.Tasks; 6 | 7 | using IPConfig.Languages; 8 | 9 | namespace IPConfig.Models; 10 | 11 | public partial class LastUsedIPv4Config(DateTime lastUsedTime) : IPv4Config 12 | { 13 | public static readonly Dictionary Backups = []; 14 | 15 | public string FormatedLastUsedTime => LastUsedTime.ToString(LangSource.Instance.CurrentCulture); 16 | 17 | public DateTime LastUsedTime { get; } = lastUsedTime; 18 | 19 | public static async Task BackupAsync(string nicId, IPv4Config value) 20 | { 21 | string path = $"backup/{nicId}.bak"; 22 | string json = JsonSerializer.Serialize(value, App.JsonOptions); 23 | Directory.CreateDirectory("backup"); 24 | await File.WriteAllTextAsync(path, json); 25 | var backup = new LastUsedIPv4Config(DateTime.Now); 26 | value.DeepCloneTo(backup); 27 | Backups[nicId] = backup; 28 | } 29 | 30 | public static async Task ReadAsync(string nicId) 31 | { 32 | if (Backups.TryGetValue(nicId, out var cache)) 33 | { 34 | return cache; 35 | } 36 | 37 | string path = $"backup/{nicId}.bak"; 38 | 39 | if (!File.Exists(path)) 40 | { 41 | return null; 42 | } 43 | 44 | try 45 | { 46 | string json = await File.ReadAllTextAsync(path); 47 | var backup = JsonSerializer.Deserialize(json); 48 | 49 | if (backup is null) 50 | { 51 | return null; 52 | } 53 | 54 | var lastUsedIPv4Config = new LastUsedIPv4Config(File.GetLastWriteTime(path)); 55 | backup.DeepCloneTo(lastUsedIPv4Config); 56 | Backups[nicId] = lastUsedIPv4Config; 57 | 58 | return lastUsedIPv4Config; 59 | } 60 | catch 61 | { 62 | return null; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/AddUntitledIPConfigMessage.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models.Messages; 2 | 3 | public class AddUntitledIPConfigMessage(object sender, string name = "") : ISender 4 | { 5 | public string Name { get; set; } = name; 6 | 7 | public object Sender { get; } = sender; 8 | } 9 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/CancelEditMessage.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models.Messages; 2 | 3 | public class CancelEditMessage(object sender, bool ask) : ISender 4 | { 5 | public bool Ask { get; } = ask; 6 | 7 | public object Sender { get; } = sender; 8 | } 9 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/ChangeSelectionMessage.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models.Messages; 2 | 3 | public class ChangeSelectionMessage(object sender, T selection) : ISender 4 | { 5 | public T Selection { get; } = selection; 6 | 7 | public object Sender { get; } = sender; 8 | } 9 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/CollectionChangeActionMessage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace IPConfig.Models.Messages; 4 | 5 | public class CollectionChangeActionMessage(object sender, CollectionChangeAction action, T item) : ISender 6 | { 7 | public CollectionChangeAction Action { get; } = action; 8 | 9 | public T Item { get; } = item; 10 | 11 | public object Sender { get; } = sender; 12 | } 13 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/EmptyMessage.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models.Messages; 2 | 3 | public class EmptyMessage(object sender) : ISender 4 | { 5 | public object Sender { get; } = sender; 6 | } 7 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/GoBackMessage.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models.Messages; 2 | 3 | public class GoBackMessage(object sender) : ISender 4 | { 5 | public object Sender { get; } = sender; 6 | } 7 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/ISender.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models.Messages; 2 | 3 | public interface ISender 4 | { 5 | object Sender { get; } 6 | } 7 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/KeyPressMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Globalization; 4 | using System.Windows.Input; 5 | 6 | namespace IPConfig.Models.Messages; 7 | 8 | public class KeyPressMessage : ISender 9 | { 10 | public KeyEventArgs Args { get; } 11 | 12 | public string Gesture { get; private set; } 13 | 14 | public object Sender { get; } 15 | 16 | public KeyPressMessage(object sender, KeyEventArgs args) 17 | { 18 | Sender = sender; 19 | Args = args; 20 | 21 | GetGesture(); 22 | } 23 | 24 | public bool GestureEquals(string gesture) 25 | { 26 | var gestureConverter = new KeyGestureConverter(); 27 | var keyGesture = gestureConverter.ConvertFromInvariantString(gesture) as KeyGesture; 28 | string? normalizedGesture = keyGesture?.GetDisplayStringForCulture(CultureInfo.InvariantCulture); 29 | 30 | if (normalizedGesture is not null) 31 | { 32 | return normalizedGesture.Equals(Gesture, StringComparison.OrdinalIgnoreCase); 33 | } 34 | 35 | return false; 36 | } 37 | 38 | [MemberNotNull(nameof(Gesture))] 39 | private void GetGesture() 40 | { 41 | try 42 | { 43 | var gesture = new KeyGesture(Args.Key, Args.KeyboardDevice.Modifiers); 44 | Gesture = gesture.GetDisplayStringForCulture(CultureInfo.InvariantCulture); 45 | } 46 | catch (NotSupportedException) 47 | { 48 | Gesture = Args.Key.ToString(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/RefreshMessage.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models.Messages; 2 | 3 | public class RefreshMessage(object sender) : ISender 4 | { 5 | public object Sender { get; } = sender; 6 | } 7 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/SaveMessage.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models.Messages; 2 | 3 | public class SaveMessage(object sender) : ISender 4 | { 5 | public object Sender { get; } = sender; 6 | } 7 | -------------------------------------------------------------------------------- /IPConfig/Models/Messages/ToggleStateMessage.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Models.Messages; 2 | 3 | public class ToggleStateMessage(object sender, T newValue) : ISender 4 | { 5 | public T NewValue { get; } = newValue; 6 | 7 | public object Sender { get; } = sender; 8 | } 9 | -------------------------------------------------------------------------------- /IPConfig/Models/Nic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.NetworkInformation; 3 | 4 | using CommunityToolkit.Mvvm.ComponentModel; 5 | 6 | using IPConfig.Helpers; 7 | using IPConfig.Languages; 8 | 9 | namespace IPConfig.Models; 10 | 11 | [INotifyPropertyChanged] 12 | public partial class Nic : NetworkInterface 13 | { 14 | public ConnectionType ConnectionType 15 | { 16 | get 17 | { 18 | var connType = NetworkInterfaceType switch { 19 | NetworkInterfaceType.Ethernet 20 | or NetworkInterfaceType.Ethernet3Megabit 21 | or NetworkInterfaceType.FastEthernetT 22 | or NetworkInterfaceType.FastEthernetFx 23 | or NetworkInterfaceType.GigabitEthernet => ConnectionType.Ethernet, 24 | NetworkInterfaceType.Wireless80211 25 | or NetworkInterfaceType.Wman 26 | or NetworkInterfaceType.Wwanpp 27 | or NetworkInterfaceType.Wwanpp2 => ConnectionType.Wlan, 28 | _ => ConnectionType.Other 29 | }; 30 | 31 | return connType; 32 | } 33 | } 34 | 35 | public override string Description => Instance.Description; 36 | 37 | public string FormatedMacAddress 38 | { 39 | get 40 | { 41 | if (String.IsNullOrEmpty(MacAddress)) 42 | { 43 | return "N/A"; 44 | } 45 | 46 | return BitConverter.ToString(Instance.GetPhysicalAddress().GetAddressBytes()); 47 | } 48 | } 49 | 50 | public string FormatedSpeed => BytesFormatter.ToNetBand(Speed); 51 | 52 | public override string Id => Instance.Id; 53 | 54 | public NetworkInterface Instance { get; } 55 | 56 | public IPv4InterfaceProperties? IPv4InterfaceProperties 57 | { 58 | get 59 | { 60 | if (SupportsIPv4) 61 | { 62 | return Instance.GetIPProperties().GetIPv4Properties(); 63 | } 64 | 65 | return null; 66 | } 67 | } 68 | 69 | public IPv6InterfaceProperties? IPv6InterfaceProperties 70 | { 71 | get 72 | { 73 | if (SupportsIPv6) 74 | { 75 | return Instance.GetIPProperties().GetIPv6Properties(); 76 | } 77 | 78 | return null; 79 | } 80 | } 81 | 82 | public override bool IsReceiveOnly => Instance.IsReceiveOnly; 83 | 84 | public string MacAddress => Instance.GetPhysicalAddress().ToString(); 85 | 86 | public override string Name => Instance.Name; 87 | 88 | public override NetworkInterfaceType NetworkInterfaceType 89 | { 90 | get 91 | { 92 | // HACK: 修复 Clash 虚拟网卡返回无效枚举值(53) 的问题。 93 | if (((int)Instance.NetworkInterfaceType) == 53) 94 | { 95 | return NetworkInterfaceType.Tunnel; 96 | } 97 | 98 | return Instance.NetworkInterfaceType; 99 | } 100 | } 101 | 102 | public override OperationalStatus OperationalStatus => Instance.OperationalStatus; 103 | 104 | public SimpleNicType SimpleNicType => GetSimpleNicType(); 105 | 106 | public override long Speed => Instance.Speed; 107 | 108 | public bool SupportsIPv4 => Supports(NetworkInterfaceComponent.IPv4); 109 | 110 | public bool SupportsIPv6 => Supports(NetworkInterfaceComponent.IPv6); 111 | 112 | public override bool SupportsMulticast => Instance.SupportsMulticast; 113 | 114 | public Nic(NetworkInterface nic) 115 | { 116 | Instance = nic; 117 | 118 | LangSource.Instance.LanguageChanged += (s, e) => OnPropertyChanged(nameof(OperationalStatus)); 119 | } 120 | 121 | public override IPInterfaceProperties GetIPProperties() 122 | { 123 | return Instance.GetIPProperties(); 124 | } 125 | 126 | public override IPInterfaceStatistics GetIPStatistics() 127 | { 128 | return Instance.GetIPStatistics(); 129 | } 130 | 131 | public override IPv4InterfaceStatistics GetIPv4Statistics() 132 | { 133 | return Instance.GetIPv4Statistics(); 134 | } 135 | 136 | public override PhysicalAddress GetPhysicalAddress() 137 | { 138 | return Instance.GetPhysicalAddress(); 139 | } 140 | 141 | public override bool Supports(NetworkInterfaceComponent networkInterfaceComponent) 142 | { 143 | return Instance.Supports(networkInterfaceComponent); 144 | } 145 | 146 | public override string ToString() 147 | { 148 | return $""" 149 | {Lang.AdapterName}: {Name} 150 | {Lang.AdapterDescription}: {Description} 151 | {Lang.AdapterMAC}: {FormatedMacAddress} 152 | {Lang.AdapterType}: {NetworkInterfaceType} 153 | {Lang.AdapterOperationalStatus}: {OperationalStatus} 154 | {Lang.AdapterLinkSpeed}: {FormatedSpeed} 155 | {Lang.AdapterSupportsMulticast}: {SupportsMulticast} 156 | {Lang.AdapterId}: {Id} 157 | {Lang.AdapterIsReceiveOnly}: {IsReceiveOnly} 158 | {Lang.AdapterSupportsIPv4}: {SupportsIPv4} 159 | {Lang.AdapterSupportsIPv6}: {SupportsIPv6} 160 | """; 161 | } 162 | 163 | private SimpleNicType GetSimpleNicType() 164 | { 165 | var simpleType = NetworkInterfaceType switch { 166 | NetworkInterfaceType.Ethernet 167 | or NetworkInterfaceType.Ethernet3Megabit 168 | or NetworkInterfaceType.FastEthernetT 169 | or NetworkInterfaceType.FastEthernetFx 170 | or NetworkInterfaceType.GigabitEthernet => SimpleNicType.Ethernet, 171 | NetworkInterfaceType.Wireless80211 172 | or NetworkInterfaceType.Wman 173 | or NetworkInterfaceType.Wwanpp 174 | or NetworkInterfaceType.Wwanpp2 => SimpleNicType.Wlan, 175 | NetworkInterfaceType.Unknown => SimpleNicType.Unknown, 176 | NetworkInterfaceType.Loopback => SimpleNicType.Loopback, 177 | _ => SimpleNicType.Other 178 | }; 179 | 180 | if (simpleType == SimpleNicType.Loopback) 181 | { 182 | return simpleType; 183 | } 184 | 185 | bool isPhysical = NetworkManagement.IsPhysicalAdapter(Id); 186 | 187 | if (!isPhysical) 188 | { 189 | return SimpleNicType.Other; 190 | } 191 | 192 | return simpleType; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /IPConfig/Models/SimpleNicType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace IPConfig.Models; 5 | 6 | public class ConnectionTypeComparer : IComparer 7 | { 8 | public static readonly ConnectionTypeComparer Instance = new(); 9 | 10 | public int Compare(ConnectionType x, ConnectionType y) 11 | { 12 | int xOrder = GetConnectionTypeOrder(x); 13 | int yOrder = GetConnectionTypeOrder(y); 14 | 15 | return xOrder.CompareTo(yOrder); 16 | } 17 | 18 | private static int GetConnectionTypeOrder(ConnectionType connectionType) 19 | { 20 | return connectionType switch { 21 | ConnectionType.Ethernet => 0, 22 | ConnectionType.Wlan => 1, 23 | _ => throw new ArgumentOutOfRangeException(nameof(connectionType), connectionType, null) 24 | }; 25 | } 26 | } 27 | 28 | public enum ConnectionType 29 | { 30 | Other, 31 | 32 | Ethernet, 33 | 34 | Wlan, 35 | } 36 | 37 | public enum SimpleNicType 38 | { 39 | Unknown, 40 | 41 | Ethernet, 42 | 43 | Wlan, 44 | 45 | Loopback, 46 | 47 | Other 48 | } 49 | 50 | public class SimpleNicTypeComparer : IComparer 51 | { 52 | public static readonly SimpleNicTypeComparer Instance = new(); 53 | 54 | public int Compare(SimpleNicType x, SimpleNicType y) 55 | { 56 | int xOrder = GetSimpleNicTypeOrder(x); 57 | int yOrder = GetSimpleNicTypeOrder(y); 58 | 59 | return xOrder.CompareTo(yOrder); 60 | } 61 | 62 | private static int GetSimpleNicTypeOrder(SimpleNicType simpleNicType) 63 | { 64 | return simpleNicType switch { 65 | SimpleNicType.Ethernet => 0, 66 | SimpleNicType.Wlan => 1, 67 | SimpleNicType.Other => 2, 68 | SimpleNicType.Loopback => 3, 69 | SimpleNicType.Unknown => 4, 70 | _ => throw new ArgumentOutOfRangeException(nameof(simpleNicType), simpleNicType, null) 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /IPConfig/Models/Validations/ForwardingErrorsAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace IPConfig.Models.Validations; 6 | 7 | /// 8 | /// 错误转发。用于解决嵌套(子)属性为复杂对象时,错误能够转发到调用类。 9 | /// 10 | /// 属性还需注册 事件以执行此验证。 11 | /// 12 | /// ObjcetProperty.ErrorsChanged += (s, e) => ValidateProperty(ObjcetProperty, nameof(ObjcetProperty)); 13 | /// 14 | /// 15 | /// 16 | public sealed class ForwardingErrorsAttribute : ValidationAttribute 17 | { 18 | public override bool IsValid(object? value) 19 | { 20 | ArgumentNullException.ThrowIfNull(value); 21 | 22 | return !((INotifyDataErrorInfo)value).HasErrors; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /IPConfig/Models/Validations/IPValidationAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | using IPConfig.Languages; 5 | 6 | namespace IPConfig.Models.Validations; 7 | 8 | public sealed class IPValidationAttribute : ValidationLangAttributeBase 9 | { 10 | public IPValidationAttribute(LangKey langKey) : base() 11 | { 12 | LangKey = langKey; 13 | } 14 | 15 | public override bool IsValid(object? value) 16 | { 17 | if (String.IsNullOrEmpty(value?.ToString())) 18 | { 19 | return true; 20 | } 21 | 22 | return IPAddress.TryParse(value?.ToString(), out _); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /IPConfig/Models/Validations/RequiredIfAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | using IPConfig.Languages; 6 | 7 | namespace IPConfig.Models.Validations; 8 | 9 | public sealed class RequiredIfAttribute(string propertyName, T? desiredValue) : ValidationAttribute 10 | { 11 | public T? DesiredValue { get; set; } = desiredValue; 12 | 13 | public string PropertyName { get; set; } = propertyName; 14 | 15 | protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) 16 | { 17 | object instance = validationContext.ObjectInstance; 18 | object? otherValue = instance.GetType().GetProperty(PropertyName)?.GetValue(instance); 19 | 20 | bool equals = EqualityComparer.Default.Equals(otherValue, DesiredValue); 21 | 22 | if (equals && String.IsNullOrEmpty(value?.ToString())) 23 | { 24 | return new(Lang.Required); 25 | } 26 | 27 | return ValidationResult.Success; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /IPConfig/Models/Validations/ValidationLangAttributeBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | using IPConfig.Languages; 5 | 6 | namespace IPConfig.Models.Validations; 7 | 8 | public abstract class ValidationLangAttributeBase : ValidationAttribute 9 | { 10 | protected LangKey _langKey; 11 | 12 | public LangKey LangKey 13 | { 14 | get => _langKey; 15 | set 16 | { 17 | _langKey = value; 18 | ErrorMessageResourceType = typeof(Lang); 19 | ErrorMessageResourceName = value.ToString(); 20 | } 21 | } 22 | 23 | protected ValidationLangAttributeBase(LangKey langKey) : base() 24 | { 25 | LangKey = langKey; 26 | } 27 | 28 | protected ValidationLangAttributeBase() : base() 29 | { } 30 | 31 | protected ValidationLangAttributeBase(Func errorMessageAccessor) : base(errorMessageAccessor) 32 | { } 33 | 34 | protected ValidationLangAttributeBase(string errorMessage) : base(errorMessage) 35 | { } 36 | } 37 | -------------------------------------------------------------------------------- /IPConfig/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // 此代码由工具生成。 4 | // 运行时版本:4.0.30319.42000 5 | // 6 | // 对此文件的更改可能会导致不正确的行为,并且如果 7 | // 重新生成代码,这些更改将会丢失。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace IPConfig.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 29 | public bool UpgradeRequired { 30 | get { 31 | return ((bool)(this["UpgradeRequired"])); 32 | } 33 | set { 34 | this["UpgradeRequired"] = value; 35 | } 36 | } 37 | 38 | [global::System.Configuration.UserScopedSettingAttribute()] 39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 40 | [global::System.Configuration.DefaultSettingValueAttribute("")] 41 | public string Language { 42 | get { 43 | return ((string)(this["Language"])); 44 | } 45 | set { 46 | this["Language"] = value; 47 | } 48 | } 49 | 50 | [global::System.Configuration.UserScopedSettingAttribute()] 51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 52 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 53 | public bool Topmost { 54 | get { 55 | return ((bool)(this["Topmost"])); 56 | } 57 | set { 58 | this["Topmost"] = value; 59 | } 60 | } 61 | 62 | [global::System.Configuration.UserScopedSettingAttribute()] 63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 64 | [global::System.Configuration.DefaultSettingValueAttribute("")] 65 | public string Theme { 66 | get { 67 | return ((string)(this["Theme"])); 68 | } 69 | set { 70 | this["Theme"] = value; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /IPConfig/Properties/Settings.cs: -------------------------------------------------------------------------------- 1 | namespace IPConfig.Properties; 2 | 3 | // 通过此类可以处理设置类的特定事件: 4 | // 在更改某个设置的值之前将引发 SettingChanging 事件。 5 | // 在更改某个设置的值之后将引发 PropertyChanged 事件。 6 | // 在加载设置值之后将引发 SettingsLoaded 事件。 7 | // 在保存设置值之前将引发 SettingsSaving 事件。 8 | internal sealed partial class Settings 9 | { 10 | public Settings() 11 | { 12 | // // 若要为保存和更改设置添加事件处理程序,请取消注释下列行: 13 | // 14 | // this.SettingChanging += this.SettingChangingEventHandler; 15 | // 16 | // this.SettingsSaving += this.SettingsSavingEventHandler; 17 | // 18 | } 19 | 20 | private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) 21 | { 22 | // 在此处添加用于处理 SettingChangingEvent 事件的代码。 23 | } 24 | 25 | private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) 26 | { 27 | // 在此处添加用于处理 SettingsSaving 事件的代码。 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /IPConfig/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | True 7 | 8 | 9 | 10 | 11 | 12 | False 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /IPConfig/Resources/Screenshots/mainwindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/Screenshots/mainwindow.png -------------------------------------------------------------------------------- /IPConfig/Resources/artist_palette_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/artist_palette_3d.png -------------------------------------------------------------------------------- /IPConfig/Resources/crescent_moon_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/crescent_moon_3d.png -------------------------------------------------------------------------------- /IPConfig/Resources/inetcpl.cpl(4487).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/inetcpl.cpl(4487).png -------------------------------------------------------------------------------- /IPConfig/Resources/ipconfig.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/ipconfig.ico -------------------------------------------------------------------------------- /IPConfig/Resources/network-tree.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/network-tree.ai -------------------------------------------------------------------------------- /IPConfig/Resources/network-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/network-tree.png -------------------------------------------------------------------------------- /IPConfig/Resources/purple_circle_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/purple_circle_3d.png -------------------------------------------------------------------------------- /IPConfig/Resources/shell32.dll(22).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/shell32.dll(22).png -------------------------------------------------------------------------------- /IPConfig/Resources/sun_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodingOctocat/IPConfig/4e005cac42d5a4d06f5164f6b8eae5bdcd918de3/IPConfig/Resources/sun_3d.png -------------------------------------------------------------------------------- /IPConfig/Themes/MyDarkTheme.xaml: -------------------------------------------------------------------------------- 1 |  3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /IPConfig/Themes/MyLightTheme.xaml: -------------------------------------------------------------------------------- 1 |  3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /IPConfig/Themes/MyResources.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | True 7 | False 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | m601 1152l295 886v10H765l-86-256H345l-86 256H128v-10l295-886h178zm36 512l-125-374l-125 374h250zM893 512q3 65 3 128v64q0 32-2 64H768v128H523l10 64q5 32 14 64H418q-8-32-12-64t-9-64H188q20 36 46 68t57 60h-35l-45 90q-101-91-156-214T0 640q0-88 23-170t64-153t100-129T317 88t153-65T640 0q88 0 170 23t153 64t129 100t100 130t65 153t23 170q0 32-3 64t-10 64h-124q17-65 17-128t-17-128H893zM640 120q-19 0-34 16t-28 41t-22 56t-15 60t-11 54t-6 37h231q-2-11-6-35t-10-54t-16-60t-22-56t-28-42t-33-17zM387 768q-3-64-3-128q0-63 3-128H137q-17 65-17 128t17 128h250zm11-384q6-58 18-116t34-112q-83 33-150 91T188 384h210zm369 384q3-64 3-128q0-63-3-128H513q-3 65-3 128q0 64 3 128h254zm58-613q23 54 36 112t20 117h211q-45-78-113-137t-154-92zm566 734q0-30-1-61t-8-60q69 0 137 6q6 1 12 3t7 10q0 5-3 12t-5 12q-2 6-3 16t-2 21t-1 22t0 17v13h296q42 0 83-1t84-2q7 0 13 3q3 5 3 10q-1 17-2 35t-1 36v58q0 36 1 72t2 73v7q0 4-3 6q-8 2-13 2h-25q-20 0-41 1q-19 0-34-1t-17-3q-2-8-2-24t-1-36q0-33 1-68t1-52h-802v39q0 15 1 31t0 34q0 29-1 52t-3 26q-8 2-13 2H949q-11 0-13-3t-2-13q1-40 1-79t0-79v-58q0-29-1-58q0-5 2-11q8-2 13-2q42 1 83 2t84 1h275v-11zm639 553q2 8 2 13v96q0 4-2 12q-8 2-13 2q-40-1-79-2t-79-1h-321v33q0 52 1 104t3 104v4q0 24-9 48t-29 38q-14 10-39 15t-55 8t-56 3t-44 1h-19q-13 0-18-6q-3-3-7-16t-6-18q-7-26-16-48t-24-46q34 4 68 5t68 2q24 0 36-7t13-34v-190h-319q-40 0-80 1t-80 2q-8 0-12-3q-2-6-2-11v-103q0-4 3-7q6-2 11-2l80 2q40 1 80 1h319q-2-26-2-52t-6-53q20 2 39 3t40 4h3q1 0 4 1q35-25 67-53t64-57h-308q-42 0-83 1t-84 2q-8 0-12-3q-2-6-2-11v-95q0-4 2-12q8-2 12-2q42 1 83 2t84 1h373q16 0 30-4t25-5q8 0 23 12t30 28t26 32t12 25q0 10-6 15t-15 10q-14 7-27 18t-25 21q-52 43-104 83t-110 78v11h321q40 0 79-1t79-2q7 0 13 3z 18 | M44.0001 11C44.0001 11 44 36.0623 44 38C44 41.3137 35.0457 44 24 44C12.9543 44 4.00003 41.3137 4.00003 38C4.00003 36.1423 4 11 4 11 M44 29C44 32.3137 35.0457 35 24 35C12.9543 35 4 32.3137 4 29 M44 20C44 23.3137 35.0457 26 24 26C12.9543 26 4 23.3137 4 20 M4 10a20 6 0 1 0 40 0a20 6 0 1 0 -40 0z 19 | M7 7h17v34h-17z M24 7H28 M33 7H35 M33 41H35 M41 7V9 M41 15V17 M41 23V25 M41 31V33 M41 39V41 M27 41H24 M24 4V44 20 | M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z 21 | 22 | -------------------------------------------------------------------------------- /IPConfig/Themes/MyVioletTheme.xaml: -------------------------------------------------------------------------------- 1 |  3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /IPConfig/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | 10 | using CommunityToolkit.Mvvm.ComponentModel; 11 | using CommunityToolkit.Mvvm.Input; 12 | using CommunityToolkit.Mvvm.Messaging; 13 | using CommunityToolkit.Mvvm.Messaging.Messages; 14 | 15 | using HandyControl.Controls; 16 | 17 | using IPConfig.Extensions; 18 | using IPConfig.Helpers; 19 | using IPConfig.Languages; 20 | using IPConfig.Models; 21 | using IPConfig.Models.Messages; 22 | using IPConfig.Properties; 23 | 24 | using HcMessageBox = HandyControl.Controls.MessageBox; 25 | 26 | namespace IPConfig.ViewModels; 27 | 28 | public partial class MainViewModel : ObservableRecipient 29 | { 30 | #region Observable Properties 31 | 32 | [ObservableProperty] 33 | private bool _isInNicConfigDetailView; 34 | 35 | [ObservableProperty] 36 | private ObservableCollection _languages = null!; 37 | 38 | [ObservableProperty] 39 | private bool _topmost = Settings.Default.Topmost; 40 | 41 | #endregion Observable Properties 42 | 43 | #region Constructors & Recipients 44 | 45 | public MainViewModel() 46 | { 47 | IsActive = true; 48 | } 49 | 50 | protected override void OnActivated() 51 | { 52 | base.OnActivated(); 53 | 54 | Messenger.Register, string>(this, "IsInNicConfigDetailView", 55 | (r, m) => IsInNicConfigDetailView = m.Value); 56 | } 57 | 58 | #endregion Constructors & Recipients 59 | 60 | #region Relay Commands 61 | 62 | [RelayCommand] 63 | private static void ChangeLanguage(string name) 64 | { 65 | LangSource.Instance.SetLanguage(name); 66 | } 67 | 68 | [RelayCommand] 69 | private async Task ClosingAsync(CancelEventArgs e) 70 | { 71 | if (LiteDbHelper.IsDbBusy) 72 | { 73 | HcMessageBox.Show( 74 | Lang.ClosingInfoDbBusy, 75 | App.AppName, 76 | MessageBoxButton.OK, 77 | MessageBoxImage.Information, 78 | MessageBoxResult.OK); 79 | 80 | e.Cancel = true; 81 | 82 | return; 83 | } 84 | 85 | var modifiedIPConfigs = Messenger.Send>, string>("ModifiedIPConfigs").Response.ToImmutableArray(); 86 | 87 | foreach (var item in modifiedIPConfigs) 88 | { 89 | Messenger.Send>(new(this, item)); 90 | 91 | var result = HcMessageBox.Show( 92 | Lang.ClosingSaveAsk_Format.Format(item.Name), 93 | App.AppName, 94 | MessageBoxButton.YesNoCancel, 95 | MessageBoxImage.Question, 96 | MessageBoxResult.Yes); 97 | 98 | if (result == MessageBoxResult.Yes) 99 | { 100 | item.ValidateAllProperties(); 101 | 102 | if (item.HasErrors) 103 | { 104 | Growl.Error(Lang.SaveFailedValidationError); 105 | e.Cancel = true; 106 | 107 | return; 108 | } 109 | 110 | Messenger.Send(new(this)); 111 | } 112 | else if (result == MessageBoxResult.No) 113 | { 114 | if (item.Order < 0) 115 | { 116 | Messenger.Send>(new(this, CollectionChangeAction.Remove, item)); 117 | } 118 | else 119 | { 120 | item.RejectChanges(); 121 | } 122 | 123 | continue; 124 | } 125 | else 126 | { 127 | e.Cancel = true; 128 | 129 | return; 130 | } 131 | } 132 | 133 | e.Cancel = true; 134 | 135 | var iPConfigList = Messenger.Send>, string>("IPConfigList").Response.ToImmutableArray(); 136 | 137 | await Task.Run(() => { 138 | LiteDbHelper.Handle(col => { 139 | for (int i = 0; i < iPConfigList.Length; i++) 140 | { 141 | var item = iPConfigList[i]; 142 | item.Order = i; 143 | col.Update(item); 144 | } 145 | }); 146 | }); 147 | 148 | App.CanForceClose = true; 149 | App.Current.Shutdown(); 150 | } 151 | 152 | [RelayCommand] 153 | private void Loaded() 154 | { 155 | var cultures = LangSource.GetAvailableCultures().OrderBy(x => x.Name); 156 | Languages = new(cultures); 157 | } 158 | 159 | [RelayCommand] 160 | private void Save() 161 | { 162 | Messenger.Send(new(this)); 163 | } 164 | 165 | #endregion Relay Commands 166 | 167 | #region Partial OnPropertyChanged Methods 168 | 169 | partial void OnTopmostChanged(bool value) 170 | { 171 | Settings.Default.Topmost = value; 172 | Settings.Default.Save(); 173 | } 174 | 175 | #endregion Partial OnPropertyChanged Methods 176 | } 177 | -------------------------------------------------------------------------------- /IPConfig/ViewModels/StatusBarViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace IPConfig.ViewModels; 4 | 5 | public partial class StatusBarViewModel : ObservableObject 6 | { } 7 | -------------------------------------------------------------------------------- /IPConfig/ViewModels/ThemeSwitchButtonViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using CommunityToolkit.Mvvm.ComponentModel; 4 | using CommunityToolkit.Mvvm.Input; 5 | 6 | using HandyControl.Data; 7 | 8 | using IPConfig.Helpers; 9 | using IPConfig.Properties; 10 | 11 | namespace IPConfig.ViewModels; 12 | 13 | public partial class ThemeSwitchButtonViewModel : ObservableObject 14 | { 15 | #region Relay Commands 16 | 17 | [RelayCommand] 18 | private static void ChangeTheme(SkinType? skin) 19 | { 20 | ThemeManager.UpdateSkin(skin); 21 | 22 | Settings.Default.Theme = ThemeManager.CurrentSkinTypeMode?.ToString(); 23 | Settings.Default.Save(); 24 | } 25 | 26 | [RelayCommand] 27 | private static void Loaded() 28 | { 29 | SkinType? skinType = null; 30 | 31 | if (Enum.TryParse(Settings.Default.Theme, out SkinType skin)) 32 | { 33 | skinType = skin; 34 | } 35 | 36 | ChangeTheme(skinType); 37 | } 38 | 39 | #endregion Relay Commands 40 | } 41 | -------------------------------------------------------------------------------- /IPConfig/ViewModels/VersionInfoViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | using System.Threading.Tasks; 4 | 5 | using CommunityToolkit.Mvvm.ComponentModel; 6 | using CommunityToolkit.Mvvm.Input; 7 | 8 | using HandyControl.Controls; 9 | 10 | using IPConfig.Extensions; 11 | using IPConfig.Helpers; 12 | using IPConfig.Languages; 13 | using IPConfig.Models.GitHub; 14 | 15 | namespace IPConfig.ViewModels; 16 | 17 | public partial class VersionInfoViewModel : ObservableObject 18 | { 19 | #region Fields 20 | 21 | private GitHubReleaseInfo? _githubReleaseInfo; 22 | 23 | #endregion Fields 24 | 25 | #region Observable Properties 26 | 27 | [ObservableProperty] 28 | [NotifyPropertyChangedFor(nameof(NewVersionAvailableToolTip))] 29 | private Exception? _checkUpdateError; 30 | 31 | [ObservableProperty] 32 | [NotifyPropertyChangedFor(nameof(NewVersionAvailableToolTip))] 33 | private bool _hasNewVersion; 34 | 35 | #endregion Observable Properties 36 | 37 | #region Properties 38 | 39 | public string NewVersionAvailableToolTip 40 | { 41 | get 42 | { 43 | if (CheckUpdateError is not null) 44 | { 45 | return $"{Lang.CheckUpdateFailed}\n\n{CheckUpdateError.Message}"; 46 | } 47 | 48 | if (HasNewVersion) 49 | { 50 | return Lang.NewVersionAvailable_Format_ToolTip.Format(_githubReleaseInfo?.Name ?? "", 51 | App.VersionString, _githubReleaseInfo?.TagName ?? ""); 52 | } 53 | else 54 | { 55 | return Lang.YouAreUpToDate; 56 | } 57 | } 58 | } 59 | 60 | #endregion Properties 61 | 62 | #region Constructors & Recipients 63 | 64 | public VersionInfoViewModel() 65 | { 66 | // 更新 ToolTip 信息。 67 | LangSource.Instance.LanguageChanged += (s, e) => OnPropertyChanged(nameof(NewVersionAvailableToolTip)); 68 | } 69 | 70 | #endregion Constructors & Recipients 71 | 72 | #region Relay Commands 73 | 74 | [RelayCommand] 75 | private async Task CheckUpdateAsync(bool showGrowl) 76 | { 77 | if (_githubReleaseInfo is null) 78 | { 79 | await GetLatestReleaseInfoAsync(); 80 | } 81 | 82 | if (!showGrowl) 83 | { 84 | return; 85 | } 86 | 87 | if (CheckUpdateError is not null) 88 | { 89 | string innerExMsg = CheckUpdateError.InnerException == null ? "" : $"\n\n{new string('-', 24)}\n{CheckUpdateError.InnerException.Message}"; 90 | 91 | Growl.Error($"{Lang.CheckUpdateFailed}\n\n{CheckUpdateError.Message}{innerExMsg}"); 92 | 93 | return; 94 | } 95 | 96 | if (!HasNewVersion) 97 | { 98 | Growl.Info(new() { 99 | Message = Lang.YouAreUpToDate, 100 | WaitTime = 2 101 | }); 102 | 103 | return; 104 | } 105 | 106 | string note = ReleaseNoteFormatRegex().Replace(_githubReleaseInfo!.ReleaseNote, "• "); 107 | 108 | if (String.IsNullOrWhiteSpace(note)) 109 | { 110 | note = ""; 111 | } 112 | 113 | Growl.Ask(new() { 114 | ConfirmStr = Lang.JumpToGitHub, 115 | CancelStr = Lang.Cancel, 116 | Message = $""" 117 | {Lang.LatestVersion}{_githubReleaseInfo.Name} 118 | {App.VersionString} -> {_githubReleaseInfo.TagName} 119 | {_githubReleaseInfo.PublishedAt.ToLocalTime():yyyy/MM/dd HH:mm:ss 'GMT'z} 120 | 121 | What's Changed 122 | {note} 123 | """, 124 | ActionBeforeClose = (isConfirm) => { 125 | if (isConfirm) 126 | { 127 | UriHelper.OpenUri(GitHubApi.ReleasesUrl); 128 | } 129 | 130 | return true; 131 | } 132 | }); 133 | } 134 | 135 | [RelayCommand] 136 | private async Task LoadedAsync() 137 | { 138 | await CheckUpdateCommand.ExecuteAsync(false); 139 | } 140 | 141 | #endregion Relay Commands 142 | 143 | #region Private Methods 144 | 145 | [GeneratedRegex("^\\-\\s", RegexOptions.Multiline)] 146 | private static partial Regex ReleaseNoteFormatRegex(); 147 | 148 | private async Task GetLatestReleaseInfoAsync() 149 | { 150 | try 151 | { 152 | _githubReleaseInfo = await GitHubApi.GetLatestReleaseInfoAsync(); 153 | 154 | if (Version.Parse(_githubReleaseInfo.TagName.TrimStart('v')) > App.Version) 155 | { 156 | HasNewVersion = true; 157 | } 158 | 159 | CheckUpdateError = null; 160 | } 161 | catch (Exception ex) 162 | { 163 | _githubReleaseInfo = null; 164 | HasNewVersion = false; 165 | CheckUpdateError = ex; 166 | } 167 | } 168 | 169 | #endregion Private Methods 170 | } 171 | -------------------------------------------------------------------------------- /IPConfig/Views/IPConfigDetailView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | using CommunityToolkit.Mvvm.Messaging; 4 | 5 | using IPConfig.Models.Messages; 6 | using IPConfig.ViewModels; 7 | 8 | using Microsoft.Extensions.DependencyInjection; 9 | 10 | namespace IPConfig.Views; 11 | 12 | /// 13 | /// IPConfigDetailView.xaml 的交互逻辑 14 | /// 15 | public partial class IPConfigDetailView : UserControl 16 | { 17 | public IPConfigDetailViewModel ViewModel => (IPConfigDetailViewModel)DataContext; 18 | 19 | public IPConfigDetailView() 20 | { 21 | InitializeComponent(); 22 | 23 | if (App.IsInDesignMode) 24 | { 25 | return; 26 | } 27 | 28 | DataContext = App.Current.Services.GetRequiredService(); 29 | 30 | WeakReferenceMessenger.Default.Register(this, 31 | (r, m) => { 32 | if (m.GestureEquals("F2")) 33 | { 34 | tbIPConfigName.Focus(); 35 | tbIPConfigName.SelectAll(); 36 | tbIPConfigName.ScrollToHome(); 37 | } 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /IPConfig/Views/IPConfigListSelectionCounterView.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 16 | 17 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /IPConfig/Views/IPConfigListSelectionCounterView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | using IPConfig.ViewModels; 4 | 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace IPConfig.Views; 8 | 9 | /// 10 | /// IPConfigListSelectionCounterView.xaml 的交互逻辑 11 | /// 12 | public partial class IPConfigListSelectionCounterView : UserControl 13 | { 14 | public IPConfigListSelectionCounterView() 15 | { 16 | InitializeComponent(); 17 | 18 | if (App.IsInDesignMode) 19 | { 20 | return; 21 | } 22 | 23 | DataContext = App.Current.Services.GetRequiredService(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IPConfig/Views/IPConfigListView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Controls; 3 | using System.Windows.Input; 4 | using System.Windows.Media; 5 | 6 | using CommunityToolkit.Mvvm.Messaging; 7 | 8 | using IPConfig.Helpers; 9 | using IPConfig.Models.Messages; 10 | using IPConfig.ViewModels; 11 | 12 | using Microsoft.Extensions.DependencyInjection; 13 | 14 | namespace IPConfig.Views; 15 | 16 | /// 17 | /// IPConfigListView.xaml 的交互逻辑 18 | /// 19 | public partial class IPConfigListView : UserControl 20 | { 21 | private ScrollViewer? _scrollViewer; 22 | 23 | private double _scrollViewerVOffset; 24 | 25 | public IPConfigListViewModel ViewModel => (IPConfigListViewModel)DataContext; 26 | 27 | public IPConfigListView() 28 | { 29 | InitializeComponent(); 30 | 31 | if (App.IsInDesignMode) 32 | { 33 | return; 34 | } 35 | 36 | DataContext = App.Current.Services.GetRequiredService(); 37 | 38 | WeakReferenceMessenger.Default.Register(this, 39 | (r, m) => { 40 | if (m.GestureEquals("Ctrl+F")) 41 | { 42 | tbSearchBar.Focus(); 43 | } 44 | else if (m.GestureEquals("Esc") && Keyboard.FocusedElement == tbSearchBar) 45 | { 46 | string text = tbSearchBar.Text; 47 | 48 | tbSearchBar.Clear(); 49 | 50 | if (String.IsNullOrEmpty(text)) 51 | { 52 | lbIPConfigs.Focus(); 53 | } 54 | } 55 | else if (m.GestureEquals("F9")) 56 | { 57 | lbIPConfigs.UpdateLayout(); 58 | 59 | if (lbIPConfigs.SelectedItem is null && lbIPConfigs.HasItems) 60 | { 61 | lbIPConfigs.SelectedItem = lbIPConfigs.Items[0]; 62 | } 63 | 64 | var listBoxItem = lbIPConfigs.ItemContainerGenerator.ContainerFromItem(lbIPConfigs.SelectedItem) as ListBoxItem; 65 | listBoxItem?.Focus(); 66 | } 67 | }); 68 | 69 | ThemeManager.ThemeChanging += (s, e) => _scrollViewerVOffset = _scrollViewer?.VerticalOffset ?? 0; 70 | ThemeManager.ThemeChanged += (s, e) => Dispatcher.Invoke(() => _scrollViewer?.ScrollToVerticalOffset(_scrollViewerVOffset)); 71 | } 72 | 73 | private void LbIPConfigs_Loaded(object sender, System.Windows.RoutedEventArgs e) 74 | { 75 | var border = (Border)VisualTreeHelper.GetChild(lbIPConfigs, 0); 76 | _scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0); 77 | } 78 | 79 | private void LbIPConfigs_SelectionChanged(object sender, SelectionChangedEventArgs e) 80 | { 81 | // 修复多选时总是滚动到 SelectedItem 的问题。 82 | if (lbIPConfigs.SelectedItems.Count == 1) 83 | { 84 | lbIPConfigs.ScrollIntoView(lbIPConfigs.SelectedItem); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /IPConfig/Views/IPv4ConfigView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Controls; 3 | using System.Windows.Threading; 4 | 5 | using IPConfig.ViewModels; 6 | 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace IPConfig.Views; 10 | 11 | /// 12 | /// IPv4ConfigView.xaml 的交互逻辑 13 | /// 14 | public partial class IPv4ConfigView : UserControl 15 | { 16 | private readonly DispatcherTimer _updateTargetTimer = new(); 17 | 18 | private MenuItem? _pingDnsGroupMenuItem; 19 | 20 | public IPConfigDetailViewModel ViewModel => (IPConfigDetailViewModel)DataContext; 21 | 22 | public IPv4ConfigView() 23 | { 24 | InitializeComponent(); 25 | 26 | if (App.IsInDesignMode) 27 | { 28 | return; 29 | } 30 | 31 | DataContext = App.Current.Services.GetRequiredService(); 32 | 33 | _updateTargetTimer.Interval = TimeSpan.FromMicroseconds(100); 34 | _updateTargetTimer.Tick += DispatcherTimer_Tick; 35 | } 36 | 37 | private void DispatcherTimer_Tick(object? sender, EventArgs e) 38 | { 39 | _pingDnsGroupMenuItem?.GetBindingExpression(MenuItem.IsEnabledProperty).UpdateTarget(); 40 | } 41 | 42 | private void TxtPindDnsGroup_ContextMenuClosing(object sender, ContextMenuEventArgs e) 43 | { 44 | _updateTargetTimer.Stop(); 45 | } 46 | 47 | private void TxtPingDnsGroup_ContextMenuOpening(object sender, ContextMenuEventArgs e) 48 | { 49 | // 如果 DNS 分组中存在尚未完成的 ping 动作,应该禁用上下文菜单项。 50 | // 51 | // 因为 CollectionViewGroup 不是 BindingList,所以每次打开菜单时都需要强制更新数据源以便重新计算菜单项的可用性。 52 | var txt = (TextBlock)sender; 53 | _pingDnsGroupMenuItem = txt.ContextMenu.Items[0] as MenuItem; 54 | 55 | _updateTargetTimer.Start(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /IPConfig/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Windows; 4 | using System.Windows.Input; 5 | 6 | using CommunityToolkit.Mvvm.ComponentModel; 7 | using CommunityToolkit.Mvvm.Messaging; 8 | 9 | using IPConfig.Languages; 10 | using IPConfig.Models.Messages; 11 | using IPConfig.ViewModels; 12 | 13 | using Microsoft.Extensions.DependencyInjection; 14 | 15 | using HcWindow = HandyControl.Controls.Window; 16 | 17 | namespace IPConfig.Views; 18 | 19 | /// 20 | /// Interaction logic for MainWindow.xaml 21 | /// 22 | [INotifyPropertyChanged] 23 | public partial class MainWindow : HcWindow 24 | { 25 | public static Version? Version => App.Version; 26 | 27 | public static string WindowTitle1 => $": TCP/IPv4 {Lang.ConfigTool}"; 28 | 29 | public static string WindowTitle2 => $"{WindowTitle1} by CodingNinja"; 30 | 31 | public string DisplayTitle { get; private set; } = WindowTitle1; 32 | 33 | public string ReleasedLongTime => File.GetLastWriteTime(GetType().Assembly.Location).ToString(LangSource.Instance.CurrentCulture); 34 | 35 | public MainViewModel ViewModel => (MainViewModel)DataContext; 36 | 37 | public string WindowTitle3 => $"{WindowTitle2} / Released on {ReleasedLongTime}"; 38 | 39 | public MainWindow() 40 | { 41 | InitializeComponent(); 42 | 43 | if (App.IsInDesignMode) 44 | { 45 | return; 46 | } 47 | 48 | DataContext = App.Current.Services.GetRequiredService(); 49 | 50 | LangSource.Instance.LanguageChanged += (s, e) => UpdateTitle(); 51 | } 52 | 53 | private void UpdateTitle() 54 | { 55 | DisplayTitle = ActualWidth switch { 56 | < 640 => WindowTitle1, 57 | < 960 => WindowTitle2, 58 | _ => WindowTitle3 59 | }; 60 | 61 | txtTitle.Text = DisplayTitle; 62 | } 63 | 64 | private void Window_PreviewKeyDown(object sender, KeyEventArgs e) 65 | { 66 | WeakReferenceMessenger.Default.Send(new(this, e)); 67 | } 68 | 69 | private void Window_SizeChanged(object sender, SizeChangedEventArgs e) 70 | { 71 | UpdateTitle(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /IPConfig/Views/NicConfigDetailView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | using CommunityToolkit.Mvvm.ComponentModel; 5 | 6 | using IPConfig.Helpers; 7 | using IPConfig.ViewModels; 8 | 9 | using Microsoft.Extensions.DependencyInjection; 10 | 11 | namespace IPConfig.Views; 12 | 13 | /// 14 | /// NicConfigDetailView.xaml 的交互逻辑 15 | /// 16 | [INotifyPropertyChanged] 17 | public partial class NicConfigDetailView : UserControl 18 | { 19 | private double _scrollViewerVOffset; 20 | 21 | [ObservableProperty] 22 | [NotifyPropertyChangedFor(nameof(TitleWidthMargin))] 23 | private double _titleWidth = 120; 24 | 25 | public Thickness TitleWidthMargin => new(TitleWidth, 0, 0, 0); 26 | 27 | public NicConfigDetailViewModel ViewModel => (NicConfigDetailViewModel)DataContext; 28 | 29 | public NicConfigDetailView() 30 | { 31 | InitializeComponent(); 32 | 33 | if (App.IsInDesignMode) 34 | { 35 | return; 36 | } 37 | 38 | DataContext = App.Current.Services.GetRequiredService(); 39 | 40 | ThemeManager.ThemeChanging += (s, e) => _scrollViewerVOffset = scrollViewer.VerticalOffset; 41 | ThemeManager.ThemeChanged += (s, e) => scrollViewer.ScrollToVerticalOffset(_scrollViewerVOffset); 42 | } 43 | 44 | private void UserControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) 45 | { 46 | ViewModel.CanUpdate = (bool)e.NewValue; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /IPConfig/Views/NicInfoCardView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | using IPConfig.ViewModels; 4 | 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace IPConfig.Views; 8 | 9 | /// 10 | /// NicInfoCardView.xaml 的交互逻辑 11 | /// 12 | public partial class NicInfoCardView : UserControl 13 | { 14 | public NicInfoCardView() 15 | { 16 | InitializeComponent(); 17 | 18 | if (App.IsInDesignMode) 19 | { 20 | return; 21 | } 22 | 23 | DataContext = App.Current.Services.GetRequiredService(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IPConfig/Views/NicSelectorView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | using IPConfig.ViewModels; 4 | 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace IPConfig.Views; 8 | 9 | /// 10 | /// NicSelectorView.xaml 的交互逻辑 11 | /// 12 | public partial class NicSelectorView : UserControl 13 | { 14 | public NicSelectorView() 15 | { 16 | InitializeComponent(); 17 | 18 | if (App.IsInDesignMode) 19 | { 20 | return; 21 | } 22 | 23 | DataContext = App.Current.Services.GetRequiredService(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IPConfig/Views/NicSpeedMonitorView.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 16 | 17 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /IPConfig/Views/NicSpeedMonitorView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | using IPConfig.ViewModels; 4 | 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace IPConfig.Views; 8 | 9 | /// 10 | /// NicSpeedMonitorView.xaml 的交互逻辑 11 | /// 12 | public partial class NicSpeedMonitorView : UserControl 13 | { 14 | public NicSpeedMonitorView() 15 | { 16 | InitializeComponent(); 17 | 18 | if (App.IsInDesignMode) 19 | { 20 | return; 21 | } 22 | 23 | DataContext = App.Current.Services.GetRequiredService(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IPConfig/Views/StatusBarView.xaml: -------------------------------------------------------------------------------- 1 |  16 | 21 | 22 | 26 | 27 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 67 | 68 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /IPConfig/Views/ThemeSwitchButtonView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | using IPConfig.Languages; 4 | using IPConfig.ViewModels; 5 | 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace IPConfig.Views; 9 | 10 | /// 11 | /// ThemeSwitchButtonView.xaml 的交互逻辑 12 | /// 13 | public partial class ThemeSwitchButtonView : UserControl 14 | { 15 | public ThemeSwitchButtonView() 16 | { 17 | InitializeComponent(); 18 | 19 | if (App.IsInDesignMode) 20 | { 21 | return; 22 | } 23 | 24 | DataContext = App.Current.Services.GetRequiredService(); 25 | 26 | LangSource.Instance.LanguageChanged += (s, e) => btnThemeSwitcher.GetBindingExpression(ToolTipProperty).UpdateTarget(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /IPConfig/Views/VersionInfoView.xaml: -------------------------------------------------------------------------------- 1 |  15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 36 | 37 | 38 | 66 | 67 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /IPConfig/Views/VersionInfoView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | using IPConfig.ViewModels; 4 | 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace IPConfig.Views; 8 | 9 | /// 10 | /// VersionInfoView.xaml 的交互逻辑 11 | /// 12 | public partial class VersionInfoView : UserControl 13 | { 14 | public VersionInfoView() 15 | { 16 | InitializeComponent(); 17 | 18 | if (App.IsInDesignMode) 19 | { 20 | return; 21 | } 22 | 23 | DataContext = App.Current.Services.GetRequiredService(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IPConfig/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 53 | 61 | 62 | 63 | 64 | 65 | 66 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 CodingNinja 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IPConfig 2 | 3 | [![Target framework](https://img.shields.io/badge/support-.NET_8.0--Windows7.0-blue)](https://github.com/CodingOctocat/IPConfig) 4 | [![GitHub issues](https://img.shields.io/github/issues/CodingOctocat/IPConfig)](https://github.com/CodingOctocat/IPConfig/issues) 5 | [![GitHub stars](https://img.shields.io/github/stars/CodingOctocat/IPConfig)](https://github.com/CodingOctocat/IPConfig/stargazers) 6 | [![GitHub license](https://img.shields.io/github/license/CodingOctocat/IPConfig)](https://github.com/CodingOctocat/IPConfig/blob/master/LICENSE) 7 | [![CodeFactor](https://www.codefactor.io/repository/github/codingoctocat/ipconfig/badge)](https://www.codefactor.io/repository/github/codingoctocat/ipconfig) 8 | 9 | A simple and easy IP configuration tool. 10 | 11 | 12 | Logo 13 | 14 | 15 | --- 16 | 17 | ## Features 18 | 19 | - Simple and easy - no need to navigate through the Control Panel for network settings Control Panel 20 | - Quick switch between your preset IP configurations 21 | - View detailed information about network adapters 22 | - Customizable subnet mask and DNS preset list 23 | - Batch ping DNS list 24 | - Form auto-completion 25 | - Light, Dark, Violet themes 26 | - ... 27 | 28 | ## Screenshots 29 | 30 | mainwindow 31 | 32 | ## Languages 33 | 34 | - English - en 35 | - 中文 (中国) - zh-CN 36 | 37 | ## Shortcuts 38 | 39 | - `F2`: Rename configuration 40 | - `F5`: Refresh network adapter list 41 | - `F9`: Focus on configuration list 42 | - `F11`: View to network adapter IP configuration 43 | - `F12`: Navigate to the network adapter details view 44 | - `Ctrl+F`: Search configuration 45 | - `Ctrl+S`: Save configuration 46 | 47 | ## FAQ 48 | 49 | 1. How to customise subnet mask and DNS preset list? 50 | > You can create a copy of the template and edit it, while following to file naming convention: `filename[.lang].csv` (Don't use English periods(`.`) in filename). 51 | For example, `ipv4_public_dns.fr.csv` is good, `ipv4.public.dns.fr.csv` is bad. 52 | 2. Where is the settings file? 53 | > `%LOCALAPPDATA%\IPConfig` 54 | --------------------------------------------------------------------------------