| 分享时间 | 2020-09-07 15:04 | 
| 最后更新 | 22天12小时前 | 
| 修订版本 | 10 | 
| 用户许可 | -未设置- | 
| Quicker版本 | 1.44.22 | 
| 动作大小 | 9.3 KB | 
更新C#代码实现,调用Windows API刷新任务栏(资源管理器)避免终端弹窗和任务栏不刷新或者资源管理器重启问题
这段 C# 代码定义了一个 `ToggleThemeAction` 类,用于在 Windows 10 或 11 系统上切换浅色和深色主题。它通过操作 Windows 注册表和调用 Windows API 来实现主题的切换和界面刷新。代码可能是一个自动化工具(Quicker,基于 `Quicker.Public` 命名空间和 `IStepContext` 接口)的一部分。以下是对代码的详细解释,分为几个关键部分:
---
### **1. 导入和依赖**
```csharp
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Quicker.Public;
```
- **`Microsoft.Win32`**:提供对 Windows 注册表的访问,用于读取和修改主题设置。
- **`System.Diagnostics`**:用于启动和管理进程(如 `explorer.exe` 和 `rundll32.exe`)。
- **`System.Runtime.InteropServices`**:支持与 Windows API 函数交互(如 `SendMessageTimeout` 和 `SHChangeNotify`)。
- **`System.Threading`**:用于通过 `Thread.Sleep` 暂停执行。
- **`Quicker.Public`**:一个自定义命名空间,可能是 Quicker 自动化工具的一部分,提供 `IStepContext` 接口以集成到工作流中。
---
### **2. Windows API 常量和导入**
#### **常量**
```csharp
private const int HWND_BROADCAST = 0xFFFF;
private const int WM_SETTINGCHANGE = 0x001A;
private const int SMTO_ABORTIFHUNG = 0x0002;
```
- **`HWND_BROADCAST`**:一个特殊的窗口句柄(`0xFFFF`),用于向所有顶级窗口广播消息。
- **`WM_SETTINGCHANGE`**:一个 Windows 消息(`0x001A`),用于通知应用程序系统设置已更改。
- **`SMTO_ABORTIFHUNG`**:用于 `SendMessageTimeout` 的标志,如果接收窗口无响应,则中止消息。
#### **导入的函数**
```csharp
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessageTimeout(
IntPtr hWnd, uint Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult);
[DllImport("shell32.dll")]
private static extern void SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
```
- **`SendMessageTimeout`**:向窗口发送消息(如 `WM_SETTINGCHANGE`),并在指定超时时间内等待响应,用于通知系统主题更改。
- **`SHChangeNotify`**:通知系统 shell 设置已更改,触发用户界面刷新。
---
### **3. 辅助方法**
#### **IsWindows10Or11**
```csharp
private static bool IsWindows10Or11()
{
return Environment.OSVersion.Version.Major >= 10;
}
```
- **功能**:检查当前操作系统是否为 Windows 10 或 11。
- **实现**:通过 `Environment.OSVersion.Version.Major` 获取操作系统的主版本号。如果主版本号大于或等于 10,则返回 `true`。
---
#### **GetCurrentTheme**
```csharp
private static string GetCurrentTheme()
{
try
{
using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize"))
{
if (key != null)
{
var value = key.GetValue("AppsUseLightTheme");
return value != null && (int)value == 1 ? "light" : "dark";
}
return null;
}
}
catch
{
return null;
}
}
```
- **功能**:获取当前系统主题(浅色或深色)。
- **实现**:
- 打开注册表路径 `HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`。
- 读取 `AppsUseLightTheme` 键的值:
- 如果值为 `1`,返回 `"light"`(浅色主题)。
- 如果值为 `0`,返回 `"dark"`(深色主题)。
- 如果键不存在或发生错误,返回 `null`。
---
#### **SetTheme**
```csharp
private static bool SetTheme(string theme)
{
try
{
int value = theme == "dark" ? 0 : 1;
using (var key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize"))
{
if (key != null)
{
key.SetValue("AppsUseLightTheme", value, RegistryValueKind.DWord);
key.SetValue("SystemUsesLightTheme", value, RegistryValueKind.DWord);
key.SetValue("EnableTransparency", value, RegistryValueKind.DWord);
}
}
return true;
}
catch
{
return false;
}
}
```
- **功能**:设置系统主题为指定值(`"light"` 或 `"dark"`)。
- **实现**:
- 根据输入的 `theme` 参数,将 `value` 设置为:
- `"dark"`:`value = 0`。
- `"light"`:`value = 1`。
- 在注册表路径 `HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize` 中创建或打开子键。
- 设置以下三个注册表键的值:
- `AppsUseLightTheme`:控制应用程序的主题。
- `SystemUsesLightTheme`:控制系统界面的主题。
- `EnableTransparency`:控制窗口透明效果。
- 如果操作成功,返回 `true`;否则返回 `false`。
---
#### **RefreshExplorerMethod1**
```csharp
private static bool RefreshExplorerMethod1()
{
try
{
IntPtr result;
bool success = SendMessageTimeout(
(IntPtr)HWND_BROADCAST,
WM_SETTINGCHANGE,
IntPtr.Zero,
"ImmersiveColorSet",
SMTO_ABORTIFHUNG,
5000,
out result) != IntPtr.Zero;
return success;
}
catch
{
return false;
}
}
```
- **功能**:通过 `SendMessageTimeout` 广播主题更改通知,刷新系统界面。
- **实现**:
- 使用 `SendMessageTimeout` 向所有窗口(`HWND_BROADCAST`)发送 `WM_SETTINGCHANGE` 消息,参数为 `"ImmersiveColorSet"`。
- 设置 5 秒超时,并使用 `SMTO_ABORTIFHUNG` 标志以避免挂起。
- 如果消息发送成功,返回 `true`;否则返回 `false`。
---
#### **RefreshExplorerMethod2**
```csharp
private static bool RefreshExplorerMethod2()
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "rundll32.exe",
Arguments = "user32.dll,UpdatePerUserSystemParameters",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
process.WaitForExit();
return true;
}
catch
{
return false;
}
}
```
- **功能**:通过运行 `rundll32.exe` 刷新用户界面。
- **实现**:
- 执行命令 `rundll32.exe user32.dll,UpdatePerUserSystemParameters`,通知系统更新用户参数。
- 使用 `Process` 类启动进程,隐藏窗口并重定向输出。
- 等待进程完成,如果成功,返回 `true`;否则返回 `false`。
---
#### **RefreshExplorerMethod3**
```csharp
private static bool RefreshExplorerMethod3()
{
try
{
SHChangeNotify(0x8000000, 0, IntPtr.Zero, IntPtr.Zero);
return true;
}
catch
{
return false;
}
}
```
- **功能**:通过 `SHChangeNotify` 通知系统刷新界面。
- **实现**:
- 调用 `SHChangeNotify` 函数,参数 `0x8000000` 表示通知 shell 更新。
- 如果调用成功,返回 `true`;否则返回 `false`。
---
#### **RefreshExplorerMethod4**
```csharp
private static bool RefreshExplorerMethod4()
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "taskkill",
Arguments = "/F /IM explorer.exe",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
process.WaitForExit();
Thread.Sleep(1000);
Process.Start("explorer.exe");
Thread.Sleep(2000);
return true;
}
catch
{
try
{
Process.Start("explorer.exe");
}
catch { }
return false;
}
}
```
- **功能**:通过终止并重启 `explorer.exe` 进程强制刷新界面。
- **实现**:
- 使用 `taskkill /F /IM explorer.exe` 命令强制终止 Windows 资源管理器进程。
- 等待 1 秒后重新启动 `explorer.exe`。
- 再等待 2 秒以确保进程启动完成。
- 如果发生错误,尝试重新启动 `explorer.exe`,并返回 `false`。
---
#### **RefreshExplorer**
```csharp
private static bool RefreshExplorer()
{
var methods = new (string Name, Func<bool> Method)[]
{
("Windows API", RefreshExplorerMethod1),
("rundll32", RefreshExplorerMethod2),
("SHChangeNotify", RefreshExplorerMethod3),
("Restart Explorer", RefreshExplorerMethod4)
};
foreach (var (name, method) in methods)
{
if (method())
{
return true;
}
}
return false;
}
```
- **功能**:尝试所有刷新方法,直到一个方法成功或全部失败。
- **实现**:
- 定义一个包含四种刷新方法的数组,每项包括方法名称和对应的函数。
- 依次调用每个方法,如果某个方法返回 `true`,则停止并返回 `true`。
- 如果所有方法都失败,返回 `false`。
---
### **4. 主执行方法 (Exec)**
```csharp
public static object Exec(IStepContext context)
{
try
{
if (!IsWindows10Or11())
{
context.SetVarValue("Output", "此动作仅支持Windows 10/11系统");
return false;
}
string currentTheme = GetCurrentTheme();
if (currentTheme == null)
{
context.SetVarValue("Output", "无法获取当前主题设置");
return false;
}
string newTheme = currentTheme == "light" ? "dark" : "light";
if (SetTheme(newTheme))
{
bool refreshSuccess = RefreshExplorer();
string message = refreshSuccess
? $"成功切换到{(newTheme == "dark" ? "深色" : "浅色")}模式并刷新界面"
: "主题切换成功,但界面刷新失败。可能需要手动刷新或重启资源管理器";
context.SetVarValue("Output", message);
return true;
}
else
{
context.SetVarValue("Output", "切换主题失败");
return false;
}
}
catch (Exception ex)
{
context.SetVarValue("Output", $"发生错误: {ex.Message}");
throw; // 抛出异常以触发“失败后停止”
}
}
```
- **功能**:Quicker 工作流的主入口方法,用于切换主题并刷新界面。
- **实现**:
1. **检查操作系统**:调用 `IsWindows10Or11`,如果不是 Windows 10/11,设置错误消息并返回 `false`。
2. **获取当前主题**:调用 `GetCurrentTheme`,如果失败,设置错误消息并返回 `false`。
3. **确定新主题**:如果当前是浅色主题(`"light"`),则切换到深色(`"dark"`),反之亦然。
4. **设置主题**:调用 `SetTheme(newTheme)` 更改注册表中的主题设置。
5. **刷新界面**:调用 `RefreshExplorer` 尝试刷新系统界面。
6. **返回结果**:
- 如果主题切换和刷新都成功,设置成功消息并返回 `true`。
- 如果主题切换成功但刷新失败,设置部分成功消息并返回 `true`。
- 如果主题切换失败,设置失败消息并返回 `false`。
7. **异常处理**:捕获任何异常,设置错误消息并抛出异常以触发 Quicker 的“失败后停止”机制。
---
### **5. 总体功能总结**
- **主要目标**:在 Windows 10/11 系统上实现浅色和深色主题的切换,并确保界面更新以反映新主题。
- **工作流程**:
1. 检查系统版本是否支持。
2. 获取当前主题(浅色或深色)。
3. 切换到相反的主题(通过修改注册表)。
4. 使用多种方法尝试刷新系统界面(包括 API 调用、命令行和重启资源管理器)。
5. 通过 `IStepContext` 返回操作结果(成功/失败消息)。
- **健壮性设计**:
- 使用 try-catch 块处理潜在错误。
- 提供多种刷新方法,确保至少有一种方法能成功更新界面。
- 返回详细的错误或成功消息,方便用户调试或确认操作结果。
---
### **6. 使用场景**
- 这段代码是为 Quicker 自动化工具设计的,允许用户通过一个动作快速切换 Windows 的主题。
- 适用于需要频繁切换浅色/深色主题的用户(如开发人员、设计师或夜间工作者)。
- 代码通过多种刷新方法确保主题切换后界面立即更新,避免用户手动重启资源管理器。
---
### **7. 注意事项**
- **权限要求**:修改注册表和终止/启动 `explorer.exe` 需要管理员权限,否则可能失败。
- **刷新方法可靠性**:重启资源管理器(`RefreshExplorerMethod4`)可能导致用户界面短暂中断,应作为最后手段。
- **Quicker 依赖**:代码依赖 `IStepContext` 接口,需在 Quicker 环境中运行。
- **错误处理**:代码捕获异常并返回用户友好的消息,但仍抛出异常以支持 Quicker 的错误处理机制。
如果您有进一步的问题或需要对某部分代码进行更深入的分析,请告诉我!
| 修订版本 | 更新时间 | 更新说明 | 
|---|---|---|
| 10 | 22天12小时前 | 问题分析 原来的C#实现使用的是 SendMessageTimeout 方法,这是一种同步调用,会等待所有窗口处理完消息才会返回。当系统负载较重时,这个过程会变得很慢,导致用户感觉界面刷新有延迟。 解决方案 我添加了一个新的刷新方法 RefreshExplorerMethod1Fast(),它使用 PostMessage API 替代 SendMessageTimeout。这种方法的特点是: 非阻塞调用:PostMessage 将消息放入目标窗口的消息队列后立即返回,不会等待消息被处理 立即执行:无论系统负载如何,调用都会立即完成 系统范围通知:仍然能够有效地通知所有顶层窗口主题已更改 具体修改内容 添加了 PostMessage API 的导入声明: csharp [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool PostMessage( IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 实现了新的快速刷新方法 RefreshExplorerMethod1Fast(): 使用 PostMessage 发送 WM_SETTINGCHANGE 消息 正确处理了字符串参数的内存分配和释放 这是一种非阻塞调用,不会等待其他程序处理完毕 更新了 RefreshExplorer() 方法: 在刷新方法列表中增加了新的快速方法 保持了原有的容错机制,如果快速方法失败还会尝试其他方法 现在,当系统繁忙时,程序会首先尝试原有的可靠但可能较慢的方法,如果耗时过长,则会使用新的快速刷新方式,从而确保在各种情况下都能快速响应用户的操作。 这个修改无需您进行任何额外操作,已经直接应用到了您的代码中。 | 
| 9 | 2025-09-30 12:18 | 更新使用C#代码,避免PS1弹窗问题,问题,速度不如设置切换快,暂时没有找到设置内的切换方法 | 
| 8 | 2025-09-30 11:05 | 最近接触了Windows API,修改脚本使用Windows API刷新资源管理器 |