├── .gitignore
├── LICENSE
├── README.md
├── TimedPower.sln
├── TimedPower
├── About.cs
├── AfterTimeValue.cs
├── AutoTaskForm.Designer.cs
├── AutoTaskForm.cs
├── AutoTaskForm.en-us.resx
├── AutoTaskForm.resx
├── AutoTaskForm.zh-cn.resx
├── BatFile.cs
├── BatFiles
│ └── RemoveOldVersionRegData.bat
├── DataCore.cs
├── DataFiles.cs
├── HtmlMessageBox.Designer.cs
├── HtmlMessageBox.cs
├── HtmlMessageBox.resx
├── Main.Designer.cs
├── Main.cs
├── Main.en-us.resx
├── Main.resx
├── Main.zh-cn.resx
├── PowerInvoke.cs
├── Program.cs
├── Resources
│ ├── img
│ │ ├── Images.resx
│ │ ├── TimedPower_logo_32x.png
│ │ ├── TimedPower_logo_window-icon.ico
│ │ ├── TimedPower_taskFile_logo.ico
│ │ └── logo
│ │ │ └── TimedPower_logo_256x.png
│ └── langs
│ │ ├── language.en-us.resx
│ │ ├── language.resx
│ │ └── language.zh-cn.resx
├── SettingForm.Designer.cs
├── SettingForm.cs
├── SettingForm.en-us.resx
├── SettingForm.resx
├── SettingForm.zh-cn.resx
├── TaskEditor.Designer.cs
├── TaskEditor.cs
├── TaskEditor.en-us.resx
├── TaskEditor.resx
├── TaskEditor.zh-cn.resx
├── TaskbarProgressBar.cs
├── Theme.cs
├── TimedPower.csproj
└── TimedPower_logo.ico
└── nsis
└── setupScript.nsi
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | .vscode
3 | */bin
4 | */obj
5 | */Properties
6 | */appsettings*.json
7 | *.csproj.user
8 | */.config
9 | */dll
10 |
11 | TimedPower/Resources/**/*.Designer.cs
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 定时电源(TimedPower)
2 |
3 | [English](https://github.com/Hgnim/TimedPower/blob/doc/README.en.md) | [简体中文](https://github.com/Hgnim/TimedPower/blob/doc/README.md) | [繁體中文](https://github.com/Hgnim/TimedPower/blob/doc/README.zh-Hant.md) | [русский](https://github.com/Hgnim/TimedPower/blob/doc/README.ru.md) | [Deutsch](https://github.com/Hgnim/TimedPower/blob/doc/README.de.md) | [Español](https://github.com/Hgnim/TimedPower/blob/doc/README.es.md) | [Français](https://github.com/Hgnim/TimedPower/blob/doc/README.fr.md) | [Português](https://github.com/Hgnim/TimedPower/blob/doc/README.pt.md)
4 |
5 |
6 | 基于Windows平台下的多功能定时电源操作软件。\
7 | 软件目前只支持中英两种语言。
8 |
9 | 
10 | 
11 |
12 | ## 介绍
13 |
14 | - 该软件可以定时关机、重启、睡眠、休眠、锁定、注销。\
15 | 
16 | - 操作简单,外观简约,支持明暗双主题。\
17 | 
18 | - 支持任意时间后进行电源操作和设置某个时间点定时操作。\
19 | 
20 | - 在倒计时最后几秒时会发送桌面通知进行提醒,误将倒计时设置太短后执行操作会进行弹窗确认。\
21 | 
22 | - 倒计时进行时会在任务栏的程序图标中显示进度条,进度条会随着时间变短而变色,增加观赏感:) \
23 | 
24 | - 可以在设置中开启添加Windows右键菜单快捷按钮(可选功能,需要在软件设置中手动启用)。也可以通过右键主窗口中的时间输入框进行快捷输入。\
25 | 
26 | 
27 | - 支持自动定时任务执行,可快捷添加静默进行的计时任务。并且包含误操作保护机制。\
28 | 
29 | 
30 | - 可用任务文件定义倒计时任务操作,执行文件以一键开始任务。\
31 | 
32 |
33 | ## 文档
34 |
35 | 前往[**帮助文档**](https://github.com/Hgnim/TimedPower/wiki)
36 |
37 | ## 用户须知
38 |
39 | - 使用该软件前必需[安装.net 8.0 runtime框架](https://dotnet.microsoft.com/zh-cn/download/dotnet/thank-you/runtime-8.0.10-windows-x64-installer)。
40 | - Windows版本要求10.0.17763.0及以上。现在目前只支持windows x64平台。
41 | - 用户在使用定时休眠功能时,需要确保系统已启用且支持休眠功能,否则将会没有任何效果。
42 |
43 | ## 获取软件
44 |
45 | [前往最新版本下载页面](https://github.com/Hgnim/TimedPower/releases/latest)
46 |
47 | ## 贡献
48 |
49 | - 如果在使用本软件的时候发现了问题与错误或有功能增强与改进的想法,欢迎[创建Issues](https://github.com/Hgnim/TimedPower/issues/new)提出你的看法。
50 | - 此项目接受大多类型的贡献,在进行较大的更改时请先联系存储库管理者。
51 |
52 | ## 声明
53 |
54 | - 此软件为免费开源软件,禁止将其应用于商业用途。
55 | - 该软件在每个版本发布前都已经进行了多次调试,以将异常率降到最低,并对一些用户容易疏忽和误操作的地方做出了保护\(防呆\)机制。但也不敢保证其绝对的万无一失,可能会因各种潜在因素导致异常。
56 | - 在使用软件前,请务必提前了解软件的运作方式,任何因用户使用不当而导致出现致命问题开发者概不负责。
57 | - 本软件的开发者对使用者因任何原因在使用本软件时对自己或他人造成的任何形式的损失和伤害不承担责任。
58 |
59 | ## 相关视频
60 |
61 | 相关介绍视频参考(点击将跳转至bilibili观看):\
62 | [](https://www.bilibili.com/video/BV1yxNAenEBb)
63 | > 视频中使用的软件版本为v2.8.7
64 |
--------------------------------------------------------------------------------
/TimedPower.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.10.35004.147
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimedPower", "TimedPower\TimedPower.csproj", "{79A6CEBB-730B-442E-AF51-6327F89DF960}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {79A6CEBB-730B-442E-AF51-6327F89DF960}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {79A6CEBB-730B-442E-AF51-6327F89DF960}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {79A6CEBB-730B-442E-AF51-6327F89DF960}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {79A6CEBB-730B-442E-AF51-6327F89DF960}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {BBECCD74-943F-4EB3-9DF7-34F2A8D688D1}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/TimedPower/About.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using static TimedPower.DataCore.LanguageData.Language;
8 | using System.Resources;
9 | using static TimedPower.DataCore;
10 | using static TimedPower.DataCore.DataFiles;
11 | using Markdig;
12 | using static TimedPower.DataFile;
13 |
14 | namespace TimedPower {
15 | internal class About {
16 | internal About() =>
17 | UpdateLanguageResource();
18 |
19 | ResourceManager langRes = null!;
20 | string GetLangStr(string key, string head = "about") => langRes.GetString($"{head}.{key}", CultureInfo.CurrentUICulture)!;
21 | void UpdateLanguageResource() => LanguageData.UpdateLanguageResource(out langRes, FilePath.MainLanguageFile);
22 |
23 | static string VersionColorAdd() {
24 | if (!PInfo.version.Contains('-', StringComparison.CurrentCulture))
25 | return "unset";
26 |
27 | if (PInfo.version.Contains("pre", StringComparison.CurrentCulture))
28 | return "#ccba2a";
29 | else if (PInfo.version.Contains("debug", StringComparison.CurrentCulture))
30 | return "red";
31 | else
32 | return "unset";
33 | }
34 | internal void Show() =>
35 | new HtmlMessageBox(
36 | Markdown.ToHtml(
37 | string.Format(GetLangStr("main"),
38 | PInfo.copyright,
39 | PInfo.Alias,
40 | $"V{PInfo.version}",
41 | PInfo.githubUrl,
42 | string.Format(GetLangStr("stats"),
43 | statsData.StartNum,
44 | statsData.DoActionNum)
45 | )
46 | )+
47 | @$"
48 |
53 | ",
54 | [
55 | new(){Text=GetLangStr("button.ok")}
56 | ],
57 | GetLangStr("title"),
58 | formSize:new(470,411),
59 | userCanChangeFormSize: true
60 | )
61 | .ShowDialog();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/TimedPower/AutoTaskForm.en-us.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | New task
122 |
123 |
124 | Save
125 |
126 |
127 | Cancel
128 |
129 |
130 | Delete task
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | Top, Left
141 |
142 |
143 |
144 | False
145 |
146 |
147 | GrowOnly
148 |
149 |
150 |
151 |
152 |
153 | Tile
154 |
155 |
156 | None
157 |
158 |
159 | Standard
160 |
161 |
162 |
163 |
164 |
165 |
166 | MiddleCenter
167 |
168 |
169 | -1
170 |
171 |
172 |
173 |
174 |
175 | 126, 1
176 |
177 |
178 | 0, 0
179 |
180 |
181 | Inherit
182 |
183 |
184 | MiddleCenter
185 |
186 |
187 | Overlay
188 |
189 |
190 | 154, 29
191 |
192 |
193 | everyday
194 |
195 |
196 | on time
197 |
198 |
199 | after app start
200 |
201 |
202 | 243, 21
203 |
204 |
205 | 130, 29
206 |
207 |
208 | 171, 24
209 |
210 |
211 | 67, 19
212 |
213 |
214 | Time type
215 |
216 |
217 | 154, 29
218 |
219 |
220 | Shutdown
221 |
222 |
223 | Restart
224 |
225 |
226 | Sleep
227 |
228 |
229 | Hibernate
230 |
231 |
232 | Lock
233 |
234 |
235 | LogOff
236 |
237 |
238 | 266, 56
239 |
240 |
241 | 107, 29
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 | Top, Left
251 |
252 |
253 | False
254 |
255 |
256 | GrowOnly
257 |
258 |
259 |
260 |
261 |
262 | Tile
263 |
264 |
265 | None
266 |
267 |
268 | Standard
269 |
270 |
271 |
272 |
273 |
274 | MiddleCenter
275 |
276 |
277 | -1
278 |
279 |
280 |
281 |
282 |
283 | 95, 1
284 |
285 |
286 | 0, 0
287 |
288 |
289 | Inherit
290 |
291 |
292 | MiddleCenter
293 |
294 |
295 | Overlay
296 |
297 |
298 | 123, 29
299 |
300 |
301 | 45, 19
302 |
303 |
304 | Name
305 |
306 |
307 | 58, 15
308 |
309 |
310 | Enable
311 |
312 |
313 | 216, 59
314 |
315 |
316 | 46, 19
317 |
318 |
319 | Action
320 |
321 |
322 | 58, 19
323 |
324 |
325 | Time set
326 |
327 |
328 | 100, 19
329 |
330 |
331 | Scheduled tasks
332 |
333 |
334 | Scheduled tasks
335 |
336 |
--------------------------------------------------------------------------------
/TimedPower/AutoTaskForm.zh-cn.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | True
123 |
124 |
--------------------------------------------------------------------------------
/TimedPower/BatFile.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Diagnostics;
3 | using System.Reflection;
4 | using static TimedPower.DataCore;
5 |
6 | namespace TimedPower
7 | {
8 | public static class BatFile
9 | {
10 | }
11 | internal static class BatFileControl {
12 | ///
13 | /// 执行
14 | ///
15 | /// 文件
16 | /// 是否使用管理员权限
17 | public static void RunBat(string[] batFiles ,bool admin=false) {
18 | foreach (string file in batFiles) {
19 | Assembly assembly = Assembly.GetExecutingAssembly();
20 | string resPath = $"TimedPower.BatFiles.{file}";
21 | Stream stream = assembly.GetManifestResourceStream(resPath)!;
22 | Stream outFile = File.Create(FilePath.TempDir + file);
23 | stream.CopyTo(outFile);
24 | outFile.Close();
25 | stream.Close();
26 | }
27 |
28 | string allFileString = "\"";
29 | for (int i = 0; i < batFiles.Length; i++) {
30 | allFileString += FilePath.TempDir + batFiles[i];
31 | if (i + 1 < batFiles.Length)
32 | allFileString += " & ";
33 | }
34 | allFileString += "\"";
35 |
36 | using Process process = new() {
37 | StartInfo = new ProcessStartInfo {
38 | UseShellExecute = true,
39 | CreateNoWindow = true,
40 | FileName = "cmd.exe",
41 | Arguments = " /c " + allFileString
42 | }
43 | };
44 | if (admin) {
45 | process.StartInfo.Verb = "RunAs"; // 请求管理员权限
46 | }
47 | try {
48 | process.Start();
49 | process.WaitForExit();
50 | } catch (Win32Exception) { MessageBox.Show("用户取消了授权", PInfo.Alias, MessageBoxButtons.OK, MessageBoxIcon.Error); } catch { MessageBox.Show("发生未知错误!", PInfo.Alias, MessageBoxButtons.OK, MessageBoxIcon.Error); }
51 | //process.Close();
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/TimedPower/BatFiles/RemoveOldVersionRegData.bat:
--------------------------------------------------------------------------------
1 | reg delete HKEY_CLASSES_ROOT\Directory\Background\shell\TimedPower /f
2 | reg delete HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run /f /v "TimedPower"
3 | timeout /t 3
--------------------------------------------------------------------------------
/TimedPower/DataCore.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using System.ComponentModel;
3 | using System.Globalization;
4 | using System.Reflection;
5 | using System.Resources;
6 | using System.Xml.Linq;
7 | using YamlDotNet.Core.Tokens;
8 | using static TimedPower.DataCore.LanguageData.Language;
9 | using static TimedPower.Main;
10 |
11 | namespace TimedPower {
12 | public struct DataCore {
13 | internal readonly struct PInfo {
14 | static void UpdateAlias() =>
15 | alias =
16 | LanguageData.GetLanguageResource(FilePath.MainLanguageFile).GetString("global.alias", CultureInfo.CurrentUICulture)!;
17 | private static string? alias=null;
18 | internal static string Alias {
19 | get {
20 | if(alias == null) {
21 | ProgramLanguage.UpdateLanguage += UpdateAlias;
22 | UpdateAlias();
23 | }
24 | return alias ?? throw new NullReferenceException();
25 | }
26 | }
27 |
28 | internal const string name = "TimedPower";
29 |
30 | [System.AttributeUsage(System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
31 | sealed internal class VersionAttribute : System.Attribute {
32 | public string Version => VersionPrefix+ ((VersionSuffix is not null and not "") ? $"-{VersionSuffix}" : "");
33 | public string VersionPrefix { get; }
34 | public string VersionSuffix { get; }
35 | public VersionAttribute(string versionPrefix,string versionSuffix) {
36 | this.VersionPrefix = versionPrefix[1..^1];//去掉前后引号
37 | this.VersionSuffix = versionSuffix[1..^1];
38 | }
39 | }
40 | public readonly static string version = Assembly.GetExecutingAssembly()
41 | .GetCustomAttribute()?.Version ?? "Error";
42 |
43 | public static string ShortVersion {
44 | get {
45 | string[] v = version.Split('.');
46 | return $"{v[0]}.{v[1]}.{v[2]}";
47 | }
48 | }
49 | public static uint ShortVersionNum {
50 | get {
51 | string[] v = version.Split('-')[0].Split('.');
52 | return uint.Parse($"{v[0]}{v[1]}{v[2]}");
53 | }
54 | }
55 | internal const string githubUrl = "https://github.com/Hgnim/TimedPower";
56 | internal const string githubWiki = "https://github.com/Hgnim/TimedPower/wiki";
57 | internal const string copyright = "Copyright (C) 2024-2025 Hgnim, All rights reserved.";
58 | }
59 |
60 | internal struct DataFiles {
61 | public static DataFile.MainData mainData;
62 | public static DataFile.StatsData statsData;
63 | }
64 |
65 |
66 | internal readonly struct FilePath {
67 | internal static readonly string thisExeFilePath = System.Windows.Forms.Application.ExecutablePath;
68 |
69 | internal static readonly string ConfigDir = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\{PInfo.name}\\";//踩坑提醒,之前该字符串使用@$前缀,而其中的\会将{转义,报出非常莫名其妙毫不相干的错误,在此作为提醒。
70 | internal static readonly string MainDataFile = ConfigDir + "data.yml";
71 | internal static readonly string MainDataFile_Obsolete = ConfigDir + "data.xml";
72 | internal static readonly string AutoTaskFile = ConfigDir + "autoTask.xml";
73 | internal static readonly string StatsDataFile = ConfigDir + "stats.yml";
74 | internal readonly struct Icon {
75 | internal static string IconDir => ConfigDir + @"icons\";
76 | //使用属性比readonly字段更好,能减少运行内存的占用。后续得将其它的readonly字段也改为属性。如果能用常量则优先使用常量。
77 | internal static string TptFileIcon => IconDir + "tpt.ico";
78 | }
79 |
80 | internal static readonly string TempDir = System.IO.Path.GetTempPath() + @$"{PInfo.name}\";
81 | internal static readonly string CommandDir = @$"{TempDir}Command\";
82 | internal static readonly string commandFile = CommandDir + "Command.dat";
83 | internal static readonly string htmlMessageBoxDir = $@"{TempDir}hmb\";
84 | internal static readonly string webViewCacheDir = $@"{htmlMessageBoxDir}WebView2\";
85 |
86 | internal const string ResourceDir = "TimedPower.Resources";
87 | internal const string MainLanguageFile = ResourceDir + ".langs.language";
88 | internal const string MainImageFile = ResourceDir + ".img.Images";
89 | }
90 | internal readonly struct RegPath {
91 | internal readonly struct CU {
92 | internal static RegistryKey root = Registry.CurrentUser;
93 | internal const string ContextMenuPath = @"Software\Classes\Directory\Background\shell\" + PInfo.name;
94 | internal const string SelfStartingKey = @"Software\Microsoft\Windows\CurrentVersion\Run\" + PInfo.name;
95 | internal readonly struct Classes {
96 | internal const string rootKeyPath = @"Software\Classes\";
97 | internal readonly struct TPT {
98 | internal const string fileExt = ".tpt";
99 | internal const string progId = $"{PInfo.name}.tpt";
100 | }
101 | }
102 | }
103 | }
104 |
105 | public struct LanguageData {
106 | public readonly struct Language {
107 | public enum Langs {
108 | zh_cn,
109 | en_us
110 | }
111 | private static readonly Dictionary dict = new() {
112 | { Langs.zh_cn, "zh-cn" },
113 | { Langs.en_us, "en-us" },
114 | };
115 | internal static string GetString(Langs value) =>
116 | dict.TryGetValue(value, out string? stringValue)
117 | ? stringValue
118 | : throw new ArgumentException("Invalid enum value");
119 | internal static Langs GetValue(string str) {
120 | Langs? lang = null;
121 | lang= dict.FirstOrDefault(x => x.Value == str).Key;
122 | return lang != null
123 | ? (Langs)lang
124 | : throw new ArgumentException("Invalid string value");
125 | }
126 | }
127 |
128 | ///
129 | /// 更改语言
130 | /// 在调用此方法更改语言后再进行资源更新
131 | ///
132 | /// 更改的语言
133 | internal static void ChangeLanguage(Language.Langs lang) =>
134 | Thread.CurrentThread.CurrentUICulture = new CultureInfo(Language.GetString(lang));
135 | internal static Language.Langs GetLanguage() =>
136 | Language.GetValue(CultureInfo.CurrentUICulture.ToString().ToLower());
137 | ///
138 | /// 更新资源
139 | ///
140 | /// 语言资源。输出更改后的语言资源
141 | /// 资源路径
142 | internal static void UpdateLanguageResource(out ResourceManager langResource, string resourcePath) =>
143 | langResource = new(resourcePath, Assembly.GetExecutingAssembly());
144 | ///
145 | /// 获取资源,与更新资源同理
146 | ///
147 | /// 资源路径
148 | ///
149 | internal static ResourceManager GetLanguageResource(string resourcePath)=> new(resourcePath, Assembly.GetExecutingAssembly());
150 | ///
151 | /// 从语言资源文件中获取字符串
152 | ///
153 | /// 语言资源实例
154 | /// 语言数据的唯一标识
155 | ///
156 | internal static string GetLangStr(ResourceManager langRes, string key) => langRes?.GetString(key, CultureInfo.CurrentUICulture)!;
157 | ///
158 | /// 更新语言资源
159 | ///
160 | /// 窗体实例
161 | /// 更多对象,一般填入不是Form的子对象的目标
162 | ///
163 | internal static void UpdateFormLanguage(Form form, object[]? moreObj = null) {
164 | static void ApplyResourcesToControls(object obj, ComponentResourceManager resources) {
165 | ///执行操作
166 | static void DoAction(dynamic obj, ComponentResourceManager resources) {
167 | //if (obj is not Form) {
168 | if (obj is not CustomObjName) {
169 | resources.ApplyResources(obj, obj.Name);
170 | }
171 | else {
172 | //自定义属性名的对象类型操作
173 | resources.ApplyResources(obj.Obj, obj.Name);
174 | obj = obj.Obj;
175 | }
176 | //}
177 | if (obj is not NotifyIcon) {
178 | if (obj.HasChildren) {
179 | ApplyResourcesToControls(obj.Controls, resources);
180 | }
181 | }
182 | if(obj is ComboBox) {
183 | int itemNum=obj.Items.Count;
184 | int itemSel = obj.SelectedIndex;//保存先前已经选择的选项
185 | obj.Items.Clear();
186 | for(int i = 0; i < itemNum; i++) {
187 | string key = $"{obj.Name}.Items";
188 | if (i != 0) key += i.ToString();
189 | obj.Items.Add(resources.GetString(key, CultureInfo.CurrentUICulture)!);
190 | }
191 | obj.SelectedIndex = itemSel;
192 | }
193 | //检查是否包含右键菜单
194 | if (obj.ContextMenuStrip != null) {
195 | static void ApplyResourcesToContextMenuItems(ToolStripItemCollection items, ComponentResourceManager resources) {
196 | foreach (ToolStripItem item in items) {
197 | resources.ApplyResources(item, item.Name!);
198 | if (item is ToolStripMenuItem menuItem && menuItem.DropDownItems.Count > 0) {
199 | ApplyResourcesToContextMenuItems(menuItem.DropDownItems, resources);
200 | }
201 | }
202 | }
203 | //有则将其对象的子项目进行资源应用
204 | ApplyResourcesToContextMenuItems(obj.ContextMenuStrip.Items, resources);
205 | }
206 | }
207 | if (obj is Control.ControlCollection controls) {
208 | foreach (Control con in controls) {
209 | DoAction(con, resources);
210 | }
211 | }else if(obj is Form) {
212 | resources.ApplyResources(obj, "$this");
213 | DoAction(obj, resources);
214 | }
215 | else if(obj is CustomObjName) {
216 | DoAction(obj, resources);
217 | }
218 | else
219 | throw new ArgumentException("未找到处理此类型所需的合适方法", nameof(obj));
220 | }
221 |
222 | ArgumentNullException.ThrowIfNull(form);
223 | ComponentResourceManager resources = new(form.GetType());
224 | ApplyResourcesToControls(form, resources);
225 | if (moreObj != null) {
226 | foreach (object obj in moreObj) {
227 | ApplyResourcesToControls(obj, resources);
228 | }
229 | }
230 | }
231 | ///
232 | /// 为那些实例化中没有名字(Name属性)的对象准备的结构,手动赋予其名字
233 | ///
234 | internal struct CustomObjName {
235 | public required string Name { get; set; }
236 | public required dynamic Obj { get; set; }
237 | }
238 | }
239 |
240 | internal readonly static Theme themeManager =new();
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/TimedPower/DataFiles.cs:
--------------------------------------------------------------------------------
1 | using YamlDotNet.Serialization;
2 | using YamlDotNet.Serialization.NamingConventions;
3 | using static TimedPower.DataCore;
4 | using static TimedPower.DataCore.DataFiles;
5 | using static TimedPower.DataCore.FilePath;
6 |
7 | namespace TimedPower {
8 | public struct DataFile {
9 | ///
10 | /// 将配置数据保存至配置文件中
11 | ///
12 | internal static void SaveData() {
13 | ISerializer yamlS = new SerializerBuilder()
14 | .WithNamingConvention(CamelCaseNamingConvention.Instance)
15 | .Build();
16 | using (StreamWriter sw = new(MainDataFile, false)) {
17 | sw.WriteLine("#注意,私自修改数据文件导致的程序错误开发者概不负责!");
18 | sw.Write(yamlS.Serialize(mainData));
19 | }
20 | using (StreamWriter sw = new(StatsDataFile, false)) {
21 | sw.WriteLine("#注意,私自修改数据文件导致的程序错误开发者概不负责!");
22 | sw.Write(yamlS.Serialize(statsData));
23 | }
24 | }
25 | ///
26 | /// 读取数据文件并将数据写入实例中
27 | ///
28 | internal static void ReadData() {
29 | IDeserializer yamlD = new DeserializerBuilder()
30 | .IgnoreUnmatchedProperties()//忽略不匹配错误
31 | .WithNamingConvention(CamelCaseNamingConvention.Instance)
32 | .Build();
33 |
34 | if (File.Exists(MainDataFile))
35 | mainData = yamlD.Deserialize(File.ReadAllText(MainDataFile));
36 | if(File.Exists(StatsDataFile))
37 | statsData= yamlD.Deserialize(File.ReadAllText(StatsDataFile));
38 | }
39 | public struct MainData {
40 | public required bool First { get; set; }
41 | public struct SmallPoint {
42 | public required int X { get; set; }
43 | public required int Y { get; set; }
44 | }
45 | public required SmallPoint Window { get; set; }
46 | public required int Action { get; set; }
47 | public required int TimeType { get; set; }
48 | public required string TimeInput { get; set; }
49 | public struct SettingS {
50 | public required bool CloseToTaskBar { get; set; }
51 | public required bool AutoCheckUpdate { get; set; }
52 | public required DataCore.LanguageData.Language.Langs Language { get; set; }
53 | public required TimedPower.Theme.Themes Theme { get; set; }
54 | }
55 | public required SettingS Setting { get; set; }
56 | public required uint Version { get; set; }
57 |
58 | public MainData() {
59 | First = true;
60 | Window = new() { X = 0, Y = 0 };
61 | Action = 0;
62 | TimeType = 0;
63 | TimeInput = "";
64 | Setting = new() {
65 | CloseToTaskBar = true,
66 | AutoCheckUpdate = true,
67 | Language=DataCore.LanguageData.Language.Langs.zh_cn,
68 | Theme = Theme.Themes.sysDef
69 | };
70 | Version = 0;
71 | }
72 | }
73 | public struct StatsData {
74 | public required int StartNum { get; set; }
75 | public required int DoActionNum { get; set; }
76 | public StatsData() {
77 | StartNum = 0;
78 | DoActionNum = 0;
79 | }
80 | }
81 | }
82 | public struct TimedPowerTask {
83 | public enum TaskTimeType {
84 | after//在此之后
85 | , ontime//在此时
86 | }
87 | public enum TaskAction {
88 | shutdown, reboot, useroff, userlock, sleep, hibernate
89 | }
90 | public class TPT {
91 | public required TaskAction Action { get; set; }
92 | public required string Time { get; set; }
93 | public required TaskTimeType TimeType { get; set; }
94 | public uint FileVersion { get; set; } = PInfo.ShortVersionNum;
95 | private bool littleTimeWarning = true;
96 | public bool LittleTimeWarning {
97 | get => littleTimeWarning;
98 | set => littleTimeWarning = value;
99 | }
100 | }
101 | internal static TPT? TPTRead(string filePath) {
102 | IDeserializer yamlD = new DeserializerBuilder()
103 | .WithNamingConvention(UnderscoredNamingConvention.Instance)
104 | .Build();
105 |
106 | return
107 | File.Exists(filePath)
108 | ? yamlD.Deserialize(File.ReadAllText(filePath))
109 | : null;
110 | }
111 | internal static void TPTSave(string filePath, TPT data) {
112 | ISerializer yamlS = new SerializerBuilder()
113 | .WithNamingConvention(UnderscoredNamingConvention.Instance)
114 | .Build();
115 |
116 | File.WriteAllText(filePath, yamlS.Serialize(data));
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/TimedPower/HtmlMessageBox.Designer.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Web.WebView2.WinForms;
2 |
3 | namespace TimedPower {
4 | partial class HtmlMessageBox {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing) {
15 | if (disposing && (components != null)) {
16 | components.Dispose();
17 | }
18 | base.Dispose(disposing);
19 | }
20 |
21 | #region Windows Form Designer generated code
22 |
23 | ///
24 | /// Required method for Designer support - do not modify
25 | /// the contents of this method with the code editor.
26 | ///
27 | private void InitializeComponent() {
28 | HtmlPanel = new Panel();
29 | htmlView = new WebView2();
30 | HtmlPanel.SuspendLayout();
31 | ((System.ComponentModel.ISupportInitialize)htmlView).BeginInit();
32 | SuspendLayout();
33 | //
34 | // HtmlPanel
35 | //
36 | HtmlPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
37 | HtmlPanel.Controls.Add(htmlView);
38 | HtmlPanel.Location = new Point(0, 0);
39 | HtmlPanel.Name = "HtmlPanel";
40 | HtmlPanel.Size = new Size(301, 174);
41 | HtmlPanel.TabIndex = 0;
42 | //
43 | // htmlView
44 | //
45 | htmlView.AllowExternalDrop = false;
46 | htmlView.CreationProperties = null;
47 | htmlView.DefaultBackgroundColor = Color.White;
48 | htmlView.Dock = DockStyle.Fill;
49 | htmlView.Location = new Point(0, 0);
50 | htmlView.Name = "htmlView";
51 | htmlView.Size = new Size(301, 174);
52 | htmlView.TabIndex = 0;
53 | htmlView.TabStop = false;
54 | htmlView.ZoomFactor = 1D;
55 | //
56 | // HtmlMessageBox
57 | //
58 | AutoScaleMode = AutoScaleMode.None;
59 | ClientSize = new Size(301, 174);
60 | Controls.Add(HtmlPanel);
61 | FormBorderStyle = FormBorderStyle.FixedDialog;
62 | MaximizeBox = false;
63 | MinimizeBox = false;
64 | Name = "HtmlMessageBox";
65 | ShowIcon = false;
66 | StartPosition = FormStartPosition.CenterParent;
67 | FormClosed += HtmlMessageBox_FormClosed;
68 | Load += HtmlMessageBox_Load;
69 | Shown += HtmlMessageBox_Shown;
70 | HtmlPanel.ResumeLayout(false);
71 | ((System.ComponentModel.ISupportInitialize)htmlView).EndInit();
72 | ResumeLayout(false);
73 | }
74 |
75 | #endregion
76 |
77 | private Panel HtmlPanel;
78 | private WebView2 htmlView;
79 | }
80 | }
--------------------------------------------------------------------------------
/TimedPower/HtmlMessageBox.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using System.Media;
3 | using System.Diagnostics;
4 | using System;
5 | using Microsoft.Web.WebView2.Core;
6 |
7 | namespace TimedPower {
8 | public partial class HtmlMessageBox : Form {
9 | ///
10 | /// 结果代码
11 | ///
12 | public int ResultCode { get; set; } = -1;
13 |
14 | public Sounds PlaySound { get; set; } = Sounds.none;
15 |
16 | string tmpFilePath;
17 | ///
18 | /// 显示Html的消息框
19 | ///
20 | /// html文本
21 | /// 窗口下的多个按钮
22 | /// 窗口标题
23 | /// 弹出窗口时播放的系统提示音
24 | /// 默认选择的按钮的序号
25 | /// 窗口打开时的大小
26 | /// 窗口大小是否能被用户更改
27 | /// WebView2引擎使用时生成的缓存文件与用户文件的存储位置,如果为null则存在与程序同级的目录中
28 | public HtmlMessageBox(
29 | string htmlMessage,
30 | HMsgBoxButton[] buttons, string title = "",
31 | Sounds playSound = Sounds.none,
32 | int defaultButtonIndex = 0,
33 | Size? formSize = null,
34 | bool userCanChangeFormSize = false,
35 | string? webCacheAndUserDataFolder = "normal"
36 | ) {
37 | InitializeComponent();
38 | if(webCacheAndUserDataFolder=="normal") webCacheAndUserDataFolder = DataCore.FilePath.webViewCacheDir;
39 | htmlView.EnsureCoreWebView2Async(CoreWebView2Environment.CreateAsync(null, webCacheAndUserDataFolder).Result);//更改WebView2的数据存储目录
40 |
41 | this.Text = title;
42 | this.FormBorderStyle = userCanChangeFormSize ? FormBorderStyle.Sizable : FormBorderStyle.FixedDialog;
43 | PlaySound = playSound;
44 | for (int i = 0; true; i++) {
45 | tmpFilePath = $"{DataCore.FilePath.htmlMessageBoxDir}hmb-tmp_{i}.html";
46 | if (!File.Exists(tmpFilePath)) {
47 | if (!Directory.Exists(DataCore.FilePath.htmlMessageBoxDir)) Directory.CreateDirectory(DataCore.FilePath.htmlMessageBoxDir);
48 | using (StreamWriter sw = new(tmpFilePath, false, Encoding.UTF8)) {
49 | sw.Write(htmlMessage);
50 | }
51 | break;
52 | }
53 | }
54 | htmlView.Source = new Uri($"file:///{tmpFilePath}", UriKind.Absolute);
55 | for (int i = 0; i < buttons.Length; i++) {
56 | Button btn = new() {
57 | Text = buttons[i].Text,
58 | Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
59 | AutoSize = true,
60 | Location = new Point(0/*245*/, 143),
61 | Name = $"actionButton-{i}",
62 | Size = new Size(0/*56*/, 27),
63 | TabIndex = i,
64 | UseVisualStyleBackColor = true,
65 | };
66 | btn.Click += (sender, e) => {
67 | int index = int.Parse((sender as Button)!.Name.Split('-')[1]);
68 | {
69 | DialogResult? dr = buttons[index].Result;
70 | if (dr != null) this.DialogResult = (DialogResult)dr;
71 | }
72 | ResultCode = buttons[index].ResultCode;
73 |
74 | this.Close();
75 | };
76 |
77 | this.Controls.Add(btn);
78 |
79 | if (i != 0)
80 | btn.Location = new Point(hmsgBoxButtons[i - 1].Location.X - hmsgBoxButtons[i - 1].Size.Width - 5, btn.Location.Y);
81 | else
82 | btn.Location = new Point(this.Size.Width - 25 - btn.Size.Width, btn.Location.Y);
83 |
84 | HtmlPanel.Size = new(HtmlPanel.Size.Width, HtmlPanel.Size.Height - btn.Size.Height - 10);//根据按钮调整HtmlPanel的高度,以适应不同系统缩放
85 |
86 | if (i == defaultButtonIndex)
87 | btn.Select();
88 |
89 | hmsgBoxButtons.Add(btn);
90 | }
91 | if (formSize != null) this.Size = (Size)formSize;
92 | }
93 | readonly List