├── .gitattributes
├── .gitignore
├── ChangeFolderIcon
├── MainWindow.xaml.cs
├── Assets
│ ├── icon
│ │ ├── default.ico
│ │ ├── app_icon.ico
│ │ └── microsoft_windows_11.png
│ ├── Images
│ │ ├── 1_en-us.png
│ │ └── 1_zh-cn.png
│ └── VisualAssets
│ │ ├── BadgeLogo.scale-100.png
│ │ ├── BadgeLogo.scale-125.png
│ │ ├── BadgeLogo.scale-150.png
│ │ ├── BadgeLogo.scale-200.png
│ │ ├── BadgeLogo.scale-400.png
│ │ ├── LargeTile.scale-100.png
│ │ ├── LargeTile.scale-125.png
│ │ ├── LargeTile.scale-150.png
│ │ ├── LargeTile.scale-200.png
│ │ ├── LargeTile.scale-400.png
│ │ ├── SmallTile.scale-100.png
│ │ ├── SmallTile.scale-125.png
│ │ ├── SmallTile.scale-150.png
│ │ ├── SmallTile.scale-200.png
│ │ ├── SmallTile.scale-400.png
│ │ ├── StoreLogo.scale-100.png
│ │ ├── StoreLogo.scale-125.png
│ │ ├── StoreLogo.scale-150.png
│ │ ├── StoreLogo.scale-200.png
│ │ ├── StoreLogo.scale-400.png
│ │ ├── SplashScreen.scale-100.png
│ │ ├── SplashScreen.scale-125.png
│ │ ├── SplashScreen.scale-150.png
│ │ ├── SplashScreen.scale-200.png
│ │ ├── SplashScreen.scale-400.png
│ │ ├── Square44x44Logo.scale-100.png
│ │ ├── Square44x44Logo.scale-125.png
│ │ ├── Square44x44Logo.scale-150.png
│ │ ├── Square44x44Logo.scale-200.png
│ │ ├── Square44x44Logo.scale-400.png
│ │ ├── Wide310x150Logo.scale-100.png
│ │ ├── Wide310x150Logo.scale-125.png
│ │ ├── Wide310x150Logo.scale-150.png
│ │ ├── Wide310x150Logo.scale-200.png
│ │ ├── Wide310x150Logo.scale-400.png
│ │ ├── Square150x150Logo.scale-100.png
│ │ ├── Square150x150Logo.scale-125.png
│ │ ├── Square150x150Logo.scale-150.png
│ │ ├── Square150x150Logo.scale-200.png
│ │ ├── Square150x150Logo.scale-400.png
│ │ ├── Square44x44Logo.targetsize-16.png
│ │ ├── Square44x44Logo.targetsize-24.png
│ │ ├── Square44x44Logo.targetsize-32.png
│ │ ├── Square44x44Logo.targetsize-48.png
│ │ ├── Square44x44Logo.targetsize-256.png
│ │ ├── Square44x44Logo.altform-unplated_targetsize-16.png
│ │ ├── Square44x44Logo.altform-unplated_targetsize-24.png
│ │ ├── Square44x44Logo.altform-unplated_targetsize-32.png
│ │ ├── Square44x44Logo.altform-unplated_targetsize-48.png
│ │ ├── Square44x44Logo.altform-unplated_targetsize-256.png
│ │ ├── Square44x44Logo.altform-lightunplated_targetsize-16.png
│ │ ├── Square44x44Logo.altform-lightunplated_targetsize-24.png
│ │ ├── Square44x44Logo.altform-lightunplated_targetsize-32.png
│ │ ├── Square44x44Logo.altform-lightunplated_targetsize-48.png
│ │ └── Square44x44Logo.altform-lightunplated_targetsize-256.png
├── UserControls
│ ├── IconControl.xaml.cs
│ ├── WindowControl
│ │ ├── CustomTitleBar.xaml.cs
│ │ └── CustomTitleBar.xaml
│ └── IconControl.xaml
├── Properties
│ ├── launchSettings.json
│ └── PublishProfiles
│ │ ├── win-arm64.pubxml.user
│ │ ├── win-x86.pubxml.user
│ │ ├── win-x64.pubxml.user
│ │ ├── win-x86.pubxml
│ │ ├── win-x64.pubxml
│ │ └── win-arm64.pubxml
├── Utils
│ ├── Services
│ │ ├── LanguageService.cs
│ │ ├── SettingsService.cs
│ │ ├── IconPackService.cs
│ │ ├── FolderNavigationService.cs
│ │ └── ElevationService.cs
│ ├── Events
│ │ └── IconChangedEventArgs.cs
│ └── WindowsAPI
│ │ ├── DpiHelper.cs
│ │ └── IconManager.cs
├── App.xaml
├── Models
│ └── IconInfo.cs
├── app.manifest
├── Program.cs
├── App.xaml.cs
├── Package.appxmanifest
├── ChangeFolderIcon.csproj.user
├── ChangeFolderIcon.csproj
├── MainWindow.xaml
├── Pages
│ ├── SettingsPage.xaml.cs
│ ├── IconsPage.xaml
│ ├── IconsPage.xaml.cs
│ └── SettingsPage.xaml
└── Strings
│ ├── en-US
│ └── Resources.resw
│ └── zh-CN
│ └── Resources.resw
├── ElevatedWorker
├── ElevatedWorker.csproj
├── Properties
│ └── PublishProfiles
│ │ ├── FolderProfile.pubxml.user
│ │ └── FolderProfile.pubxml
├── ElevatedWorker.csproj.user
├── Program.cs
└── WindowsAPI
│ └── IconManager.cs
├── README.md
├── README-EN-US.md
├── ChangeFolderIcon.iss
└── ChangeFolderIcon.sln
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vs
2 | /Output
3 | /ChangeFolderIcon/bin
4 | /ChangeFolderIcon/obj
5 | /ElevatedWorker/bin
6 | /ElevatedWorker/obj
7 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/MainWindow.xaml.cs
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/icon/default.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/icon/default.ico
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/Images/1_en-us.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/Images/1_en-us.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/Images/1_zh-cn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/Images/1_zh-cn.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/icon/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/icon/app_icon.ico
--------------------------------------------------------------------------------
/ChangeFolderIcon/UserControls/IconControl.xaml.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/UserControls/IconControl.xaml.cs
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/icon/microsoft_windows_11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/icon/microsoft_windows_11.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-100.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-125.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-150.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-200.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/BadgeLogo.scale-400.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-100.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-125.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-150.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-200.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/LargeTile.scale-400.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-100.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-125.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-150.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-200.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SmallTile.scale-400.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-100.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-125.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-150.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-200.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/StoreLogo.scale-400.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-100.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-125.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-150.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/SplashScreen.scale-400.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-100.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-125.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-150.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.scale-400.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-100.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-125.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-150.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Wide310x150Logo.scale-400.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/UserControls/WindowControl/CustomTitleBar.xaml.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/UserControls/WindowControl/CustomTitleBar.xaml.cs
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-100.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-125.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-150.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square150x150Logo.scale-400.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-16.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-24.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-32.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-48.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.targetsize-256.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-16.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-24.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-32.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-48.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-unplated_targetsize-256.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-16.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-24.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-32.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-48.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YILING0013/ChangeFolderIcon/HEAD/ChangeFolderIcon/Assets/VisualAssets/Square44x44Logo.altform-lightunplated_targetsize-256.png
--------------------------------------------------------------------------------
/ChangeFolderIcon/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ChangeFolderIcon (Package)": {
4 | "commandName": "MsixPackage"
5 | },
6 | "ChangeFolderIcon (Unpackaged)": {
7 | "commandName": "Project"
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/ChangeFolderIcon/Properties/PublishProfiles/win-arm64.pubxml.user:
--------------------------------------------------------------------------------
1 |
2 |
3 | True|2025-08-08T16:31:07.2891900Z||;
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Properties/PublishProfiles/win-x86.pubxml.user:
--------------------------------------------------------------------------------
1 |
2 |
3 | True|2025-08-08T16:29:37.2645296Z||;
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ElevatedWorker/ElevatedWorker.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ElevatedWorker/Properties/PublishProfiles/FolderProfile.pubxml.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | True|2025-08-11T16:14:40.8837494Z||;True|2025-08-11T23:53:22.2925264+08:00||;
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ElevatedWorker/ElevatedWorker.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <_LastSelectedProfileId>E:\Visual_Studio_project\ChangeFolderIcon\ElevatedWorker\Properties\PublishProfiles\FolderProfile.pubxml
5 |
6 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Properties/PublishProfiles/win-x64.pubxml.user:
--------------------------------------------------------------------------------
1 |
2 |
3 | True|2025-08-08T16:11:05.3022052Z||;True|2025-08-09T00:03:30.3577429+08:00||;True|2025-08-09T00:01:19.9752697+08:00||;True|2025-08-08T22:01:42.3759655+08:00||;False|2025-08-08T22:00:35.2751804+08:00||;
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Utils/Services/LanguageService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Microsoft.Windows.Globalization;
7 |
8 | namespace ChangeFolderIcon.Utils.Services
9 | {
10 | public static class LanguageService
11 | {
12 | public static void SetLanguage(string language)
13 | {
14 | ApplicationLanguages.PrimaryLanguageOverride = language;
15 | // 重置资源加载器,以便更改生效。要重启应用
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Utils/Events/IconChangedEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ChangeFolderIcon.Utils.Events
4 | {
5 | ///
6 | /// IconsPage 在图标被应用 / 清除后抛出的事件参数。
7 | /// IconPath == null 表示已清除图标。
8 | ///
9 | public sealed class IconChangedEventArgs : EventArgs
10 | {
11 | public string FolderPath { get; }
12 | public string? IconPath { get; }
13 |
14 | public IconChangedEventArgs(string folderPath, string? iconPath)
15 | {
16 | FolderPath = folderPath;
17 | IconPath = iconPath;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ElevatedWorker/Properties/PublishProfiles/FolderProfile.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Release
6 | x86
7 | bin\Release\Publish
8 | FileSystem
9 | <_TargetId>Folder
10 | net8.0
11 | win-x86
12 | true
13 | true
14 | false
15 | false
16 |
17 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Properties/PublishProfiles/win-x86.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | x86
9 | win-x86
10 | bin\\\unpackage\publish\x86
11 | true
12 | false
13 | Release
14 | net8.0-windows10.0.22621.0
15 | true
16 | true
17 |
18 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Properties/PublishProfiles/win-x64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | x64
9 | win-x64
10 | bin\\\unpackage\publish\win-x64
11 | true
12 | false
13 | Release
14 | net8.0-windows10.0.22621.0
15 | false
16 | false
17 |
18 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Properties/PublishProfiles/win-arm64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | ARM64
9 | win-arm64
10 | bin\\\unpackage\publish\arm64
11 | true
12 | false
13 | Release
14 | net8.0-windows10.0.22621.0
15 | true
16 | true
17 |
18 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Models/IconInfo.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI.Xaml.Media.Imaging;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ChangeFolderIcon.Models
9 | {
10 | public class IconInfo
11 | {
12 | public string Name { get; set; } = "";
13 | public string FullPath { get; set; } = "";
14 | public BitmapImage IconSource { get; set; } = new BitmapImage();
15 |
16 | public static IconInfo FromPath(string path)
17 | {
18 | return new IconInfo
19 | {
20 | Name = System.IO.Path.GetFileNameWithoutExtension(path),
21 | FullPath = path,
22 | IconSource = new BitmapImage(new Uri(path))
23 | };
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | PerMonitorV2
17 |
18 |
19 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Utils/WindowsAPI/DpiHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI.Xaml;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Runtime.InteropServices;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ChangeFolderIcon.Utils.WindowsAPI
10 | {
11 | public class DpiHelper
12 | {
13 | // Win32 API for DPI
14 | [DllImport("user32.dll", SetLastError = true)]
15 | static extern uint GetDpiForWindow(nint hWnd);
16 |
17 | [DllImport("user32.dll", SetLastError = true)]
18 | static extern int GetSystemMetricsForDpi(int nIndex, uint dpi);
19 |
20 | // Constants for system metrics
21 | private const int SM_CXCAPTION = 4;
22 | private const int SM_CYCAPTION = 4;
23 |
24 | ///
25 | /// 获取当前窗口的DPI缩放比例
26 | ///
27 | public static double GetScaleAdjustment(Window window)
28 | {
29 | var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
30 | var dpi = GetDpiForWindow(hWnd);
31 | return (double)dpi / 96;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Program.cs:
--------------------------------------------------------------------------------
1 | using ChangeFolderIcon.Utils.Services;
2 | using Microsoft.UI.Dispatching;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Text;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Microsoft.Windows.Globalization;
11 |
12 | namespace ChangeFolderIcon
13 | {
14 | public static class Program
15 | {
16 | [STAThread]
17 | static void Main(string[] args)
18 | {
19 | WinRT.ComWrappersSupport.InitializeComWrappers();
20 |
21 | var settingsService = new SettingsService();
22 | var lang = settingsService.Settings.Language;
23 |
24 | if (!string.IsNullOrWhiteSpace(lang))
25 | {
26 | ApplicationLanguages.PrimaryLanguageOverride = lang; // ← Microsoft.Windows.*
27 | }
28 |
29 | Microsoft.UI.Xaml.Application.Start(p =>
30 | {
31 | var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
32 | SynchronizationContext.SetSynchronizationContext(context);
33 | new App();
34 | });
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ChangeFolderIcon 文件夹图标修改程序
2 |
3 | [ChangeFolderIcon](https://github.com/YILING0013/ChangeFolderIcon)是基于WinUI3以及Windows API开发的一款对Windows文件夹图标快速修改程序,支持拖入文件夹快速应用图标样式,以及创建文件夹层级目录,快捷变更每一层文件夹的图标样式。
4 |
5 | [English Version](README-EN-US.md)
6 |
7 | ---
8 |
9 | ### 预览图:
10 |
11 | 
12 |
13 | ## 从源码构建
14 |
15 | #### 开发环境要求
16 |
17 | * **Visual Studio 2022** (17.0 或更高版本)
18 | * **.NET 桌面开发** 工作负载
19 | * **Windows 应用 SDK** 开发工具
20 |
21 | #### 构建步骤
22 |
23 | - [ ] 安装**Visual Studio 2022**,安装 .Net桌面开发以及WinUI 应用程序开发
24 | - [ ] 克隆项目到本地:`get clone https://github.com/YILING0013/ChangeFolderIcon.git`
25 | - [ ] 打开`ChangeFolderIcon`文件夹内的`ChangeFolderIcon.sln`
26 | - [ ] 在` Visual Studio` 中右键解决方案 → 还原 `NuGet` 包
27 | - [ ] 按 `Ctrl+Shift+B` 或菜单栏 **生成** → **生成解决方案**
28 | - [ ] 右键 `ElevatedWorker` 项目->发布,点击发布按钮
29 | - [ ] 选择 `ChangeFolderIcon` 项目,按 `F5` 启动调试,或 `Ctrl+F5` 无调试启动
30 |
31 | ### 注意事项
32 | - 确保您的系统为 Windows 10 或更高版本。
33 | - 初次运行请点击设置页,等待图标资源加载完成后点击检查更新。
34 | - 如果无法正常拉取图标资源,请检查网络连接或手动下载图标资源包,并在设置页中选择本地图标资源包。
35 | - 切换语言请在设置页中选择语言后重启程序。
36 |
37 | ## 许可证
38 |
39 | 本项目开源许可采用 [GPL-3.0 license](LICENSE)。
40 |
41 | ---
42 |
43 | ### 致谢
44 |
45 | 本项目部分图标资源来源于 [Folder-Ico](https://github.com/icon11-community/Folder-Ico),特此感谢该项目的贡献者。
46 |
47 | ---
48 |
49 | ⭐ 如果这个项目对您有帮助,请给我一个 Star!🚀
50 |
--------------------------------------------------------------------------------
/ElevatedWorker/Program.cs:
--------------------------------------------------------------------------------
1 | using ElevatedWorker.WindowsAPI;
2 | using System;
3 | using System.IO;
4 |
5 | namespace ElevatedWorker
6 | {
7 | class Program
8 | {
9 | // 入口点
10 | static void Main(string[] args)
11 | {
12 | // 设计命令行参数:
13 | // args[0]: 命令 ("set" 或 "clear")
14 | // args[1]: 目标文件夹路径
15 | // args[2]: 图标文件路径 (仅 "set" 命令需要)
16 | if (args.Length < 2)
17 | {
18 | // 参数不足,直接退出
19 | return;
20 | }
21 |
22 | string command = args[0].ToLower();
23 | string folderPath = args[1];
24 |
25 | try
26 | {
27 | switch (command)
28 | {
29 | case "set":
30 | if (args.Length < 3) return; // "set" 命令需要3个参数
31 | string iconPath = args[2];
32 | IconManager.SetFolderIcon(folderPath, iconPath);
33 | break;
34 |
35 | case "clear":
36 | IconManager.ClearFolderIcon(folderPath);
37 | break;
38 | }
39 | }
40 | catch (Exception ex)
41 | {
42 | string logPath = Path.Combine(Path.GetTempPath(), "ChangeFolderIcon_ElevatedWorker_Error.log");
43 | File.WriteAllText(logPath, $"Command: {command}\nFolder: {folderPath}\nError: {ex}");
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/README-EN-US.md:
--------------------------------------------------------------------------------
1 | # ChangeFolderIcon - Folder Icon Customization Tool
2 |
3 | [ChangeFolderIcon](https://github.com/YILING0013/ChangeFolderIcon) is a Windows folder icon customization tool developed using **WinUI3** and **Windows API**. It allows quick icon modification via drag-and-drop, supports batch icon updates for nested folders, and provides predefined icon styles for easy application.
4 |
5 | [中文版本](README.md)
6 |
7 | ---
8 |
9 | ### Preview:
10 |
11 | 
12 |
13 | ## Build from Source
14 |
15 | #### Development Requirements
16 |
17 | * **Visual Studio 2022** (Version 17.0 or later)
18 | * **.NET Desktop Development** workload
19 | * **Windows App SDK** development tools
20 |
21 | #### Build Steps
22 |
23 | - [ ] Install **Visual Studio 2022** with **.NET Desktop Development** and **WinUI App Development** workloads.
24 | - [ ] Clone the repository: `git clone https://github.com/YILING0013/ChangeFolderIcon.git`
25 | - [ ] Open `ChangeFolderIcon.sln` inside the `ChangeFolderIcon` folder.
26 | - [ ] In **Visual Studio**, right-click the solution → **Restore NuGet Packages**.
27 | - [ ] Press `Ctrl+Shift+B` or go to **Build** → **Build Solution**.
28 | - [ ] right-click `ElevatedWorker` project → **Publish**. Click the **Publish** button.
29 | - [ ] Select `ChangeFolderIcon` project, press `F5` to start debugging, or `Ctrl+F5` to start without debugging.
30 |
31 | ### Notes
32 | - Requires **Windows 10 or later**.
33 | - On first launch, go to **Settings** and wait for icon resources to load, then check for updates.
34 | - if you encounter issues fetching icon resources, check your network connection or manually download the icon resource package and select it in the settings page.
35 | - To change the language, select a language in **Settings** and restart the app.
36 |
37 | ## License
38 |
39 | This project is open-source under the [GPL-3.0 license](LICENSE).
40 |
41 | ---
42 | ### Acknowledgments
43 |
44 | Special thanks to the contributors of [Folder-Ico](https://github.com/icon11-community/Folder-Ico) for providing some of the icon resources used in this project.
45 |
46 | ---
47 |
48 | ⭐ If you find this project useful, please give it a **Star**! 🚀
49 |
--------------------------------------------------------------------------------
/ChangeFolderIcon.iss:
--------------------------------------------------------------------------------
1 | ; -- Inno Setup Script --
2 | [Setup]
3 | ; 定义应用程序的基本信息
4 | AppName=ChangeFolderIcon APP
5 | AppVersion=1.0.4
6 | AppPublisher=IDLE_CLOUD
7 | AppPublisherURL=https://github.com/YILING0013
8 | ; 安装路径, {commonpf} 表示 Program Files/Program Files (x86) 文件夹
9 | DefaultDirName={autopf}\ChangeFolderIcon
10 | ; 开始菜单组名称
11 | DefaultGroupName=ChangeFolderIcon
12 | ; 输出的安装包名称
13 | OutputBaseFilename=ChangeFolderIcon_Setup
14 | ; 使用 lzma 压缩算法
15 | Compression=lzma
16 | ; 使用固体压缩
17 | SolidCompression=yes
18 | ; 不创建程序组
19 | DisableProgramGroupPage=yes
20 | ; 设置安装程序的图标
21 | SetupIconFile=E:\Visual_Studio_project\ChangeFolderIcon\ChangeFolderIcon\Assets\icon\app_icon.ico
22 | ; 使用现代风格向导
23 | WizardStyle=modern
24 |
25 | ; 卸载设置
26 | UninstallDisplayIcon={app}\app_icon.ico
27 | UninstallDisplayName=卸载 ChangeFolderIcon
28 |
29 | [Tasks]
30 | ; 自动处理桌面图标的创建
31 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkablealone
32 | Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
33 |
34 |
35 | [Files]
36 | #define SourcePath "E:\Visual_Studio_project\ChangeFolderIcon\ChangeFolderIcon\bin\unpackage\publish\arm64"
37 | ; 将应用程序的文件添加到安装程序中
38 | Source: "{#SourcePath}\ChangeFolderIcon.exe"; DestDir: "{app}"; Flags: ignoreversion
39 | Source: "{#SourcePath}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
40 | ; 将图标文件添加到安装程序中
41 | Source: "E:\Visual_Studio_project\ChangeFolderIcon\ChangeFolderIcon\Assets\icon\app_icon.ico"; DestDir: "{app}"; Flags: ignoreversion
42 |
43 | [Icons]
44 | ; 创建桌面和开始菜单快捷方式,并设置快捷方式图标
45 | Name: "{group}\ChangeFolderIcon"; Filename: "{app}\ChangeFolderIcon.exe"; IconFilename: "{app}\app_icon.ico"
46 | Name: "{commondesktop}\ChangeFolderIcon"; Filename: "{app}\ChangeFolderIcon.exe"; Tasks: desktopicon; IconFilename: "{app}\app_icon.ico"
47 | Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\ChangeFolderIcon"; Filename: "{app}\ChangeFolderIcon.exe"; Tasks: quicklaunchicon; IconFilename: "{app}\app_icon.ico"
48 |
49 | [Run]
50 | ; 安装后自动运行应用程序
51 | Filename: "{app}\ChangeFolderIcon.exe"; Description: "{cm:LaunchProgram,ChangeFolderIcon}"; Flags: nowait postinstall skipifsilent unchecked
52 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using ChangeFolderIcon.Utils.Services;
2 | using Microsoft.UI.Xaml;
3 | using Microsoft.UI.Xaml.Controls;
4 | using Microsoft.UI.Xaml.Controls.Primitives;
5 | using Microsoft.UI.Xaml.Data;
6 | using Microsoft.UI.Xaml.Input;
7 | using Microsoft.UI.Xaml.Media;
8 | using Microsoft.UI.Xaml.Navigation;
9 | using Microsoft.UI.Xaml.Shapes;
10 | using System;
11 | using System.Collections.Generic;
12 | using System.IO;
13 | using System.Linq;
14 | using System.Runtime.InteropServices.WindowsRuntime;
15 | using Windows.ApplicationModel;
16 | using Windows.ApplicationModel.Activation;
17 | using Windows.Foundation;
18 | using Windows.Foundation.Collections;
19 |
20 | // To learn more about WinUI, the WinUI project structure,
21 | // and more about our project templates, see: http://aka.ms/winui-project-info.
22 |
23 | namespace ChangeFolderIcon
24 | {
25 | public partial class App : Application
26 | {
27 | public static Window? window;
28 | public static SettingsService? SettingsService { get; private set; }
29 | public static IconPackService? IconPackService { get; private set; }
30 |
31 | public App()
32 | {
33 | InitializeComponent();
34 | SettingsService = new SettingsService();
35 | IconPackService = new IconPackService(SettingsService.Settings.IconPackRepo, SettingsService.Settings.IconRepoLocalPath);
36 | }
37 |
38 | protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
39 | {
40 | window = new MainWindow();
41 |
42 | ApplyThemeAndBackdropSetting();
43 | _ = IconPackService?.UpdateIconPackAsync();
44 | window.Activate();
45 | }
46 |
47 | private void ApplyThemeAndBackdropSetting()
48 | {
49 | if (window is MainWindow mainWindow && SettingsService != null)
50 | {
51 | SettingsService.ApplyTheme(mainWindow, SettingsService.Settings.Theme);
52 | SettingsService.ApplyBackdrop(mainWindow, SettingsService.Settings.Backdrop);
53 | }
54 | else
55 | {
56 | throw new InvalidOperationException("Window or SettingsService is not initialized.");
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
14 |
15 |
16 |
17 |
18 | ChangeFolderIcon
19 | xieli
20 | Assets\VisualAssets\StoreLogo.png
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/ChangeFolderIcon.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | E:\Visual_Studio_project\ChangeFolderIcon\Assets\icon\microsoft_windows_11.png
5 | E:\Visual_Studio_project\ChangeFolderIcon\Assets\icon\microsoft_windows_11.png
6 | E:\Visual_Studio_project\ChangeFolderIcon\Assets\icon\microsoft_windows_11.png
7 | E:\Visual_Studio_project\ChangeFolderIcon\Assets\icon\microsoft_windows_11.png
8 | E:\Visual_Studio_project\ChangeFolderIcon\Assets\icon\microsoft_windows_11.png
9 | E:\Visual_Studio_project\ChangeFolderIcon\Assets\icon\microsoft_windows_11.png
10 | E:\Visual_Studio_project\ChangeFolderIcon\Assets\icon\microsoft_windows_11.png
11 | E:\Visual_Studio_project\ChangeFolderIcon\Assets\icon\microsoft_windows_11.png
12 | E:\Visual_Studio_project\ChangeFolderIcon\Assets\icon\microsoft_windows_11.png
13 | ChangeFolderIcon (Unpackaged)
14 | <_LastSelectedProfileId>E:\Visual_Studio_project\ChangeFolderIcon\Properties\PublishProfiles\win-arm64.pubxml
15 |
16 |
17 | ProjectDebugger
18 |
19 |
20 | ProjectDebugger
21 |
22 |
23 |
24 | Designer
25 |
26 |
27 | Designer
28 |
29 |
30 | Designer
31 |
32 |
33 | Designer
34 |
35 |
36 | Designer
37 |
38 |
39 | Designer
40 |
41 |
42 | Designer
43 |
44 |
45 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Utils/Services/SettingsService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using Microsoft.UI.Xaml;
6 |
7 | namespace ChangeFolderIcon.Utils.Services
8 | {
9 | public class AppSettings
10 | {
11 | public string Language { get; set; } = "en-US";
12 | public string Theme { get; set; } = "Default"; // "Light", "Dark", "Default"
13 | public string Backdrop { get; set; } = "Mica"; // "Mica", "Acrylic", "Transparent"
14 |
15 | // 克隆仓库的目标路径
16 | public string IconRepoLocalPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "Assets", "Folder11-ico");
17 |
18 | // 图标文件所在的真实路径 (由仓库路径和ico子目录构成)
19 | [JsonIgnore] // 此属性不需要保存到json,因为它可以被推导出来
20 | public string IconPackPath => Path.Combine(IconRepoLocalPath, "ico");
21 |
22 | public string IconPackRepo { get; set; } = "https://github.com/icon11-community/Folder11-ico.git";
23 | }
24 |
25 | public class SettingsService
26 | {
27 | private const string ConfigFileName = "settings.json";
28 | private static readonly string ConfigFilePath = Path.Combine(AppContext.BaseDirectory, ConfigFileName);
29 |
30 | public AppSettings Settings { get; private set; }
31 |
32 | public SettingsService()
33 | {
34 | Settings = LoadSettings();
35 | }
36 |
37 | // 从文件加载配置
38 | private AppSettings LoadSettings()
39 | {
40 | try
41 | {
42 | if (File.Exists(ConfigFilePath))
43 | {
44 | var json = File.ReadAllText(ConfigFilePath);
45 | return JsonSerializer.Deserialize(json) ?? new AppSettings();
46 | }
47 | }
48 | catch (Exception)
49 | {
50 | // 如果加载失败,返回默认配置
51 | }
52 | return new AppSettings();
53 | }
54 |
55 | // 保存配置到文件
56 | public void SaveSettings()
57 | {
58 | try
59 | {
60 | var options = new JsonSerializerOptions { WriteIndented = true };
61 | var json = JsonSerializer.Serialize(Settings, options);
62 | File.WriteAllText(ConfigFilePath, json);
63 | }
64 | catch (Exception)
65 | {
66 | // 处理保存失败的异常
67 | }
68 | }
69 |
70 | // 应用主题设置
71 | public static void ApplyTheme(Window window, string theme)
72 | {
73 | if (window.Content is FrameworkElement rootElement)
74 | {
75 | switch (theme.ToLower())
76 | {
77 | case "light":
78 | rootElement.RequestedTheme = ElementTheme.Light;
79 | break;
80 | case "dark":
81 | rootElement.RequestedTheme = ElementTheme.Dark;
82 | break;
83 | default:
84 | rootElement.RequestedTheme = ElementTheme.Default;
85 | break;
86 | }
87 | }
88 | }
89 |
90 | // 应用背景材质设置
91 | public static void ApplyBackdrop(MainWindow window, string backdrop)
92 | {
93 | window.SetBackdrop(backdrop);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Utils/Services/IconPackService.cs:
--------------------------------------------------------------------------------
1 | using LibGit2Sharp;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ChangeFolderIcon.Utils.Services
10 | {
11 | public class IconPackService
12 | {
13 | private readonly string? _repoUrl;
14 | private readonly string? _localPath;
15 | public event Action? StatusUpdated;
16 |
17 | // 新增: 公共属性,用于暴露本地路径
18 | public string? IconPackDirectory => _localPath;
19 |
20 | public IconPackService(string repoUrl, string localPath)
21 | {
22 | _repoUrl = repoUrl;
23 | _localPath = localPath; // 直接使用传入的专用路径
24 | }
25 |
26 | public async Task UpdateIconPackAsync()
27 | {
28 | return await Task.Run(() =>
29 | {
30 | try
31 | {
32 | if (string.IsNullOrEmpty(_localPath) || string.IsNullOrEmpty(_repoUrl))
33 | {
34 | StatusUpdated?.Invoke("Repository URL or local path is not configured.");
35 | return false;
36 | }
37 |
38 | // 如果目标目录存在但不是一个有效的Git仓库,则先清理
39 | if (Directory.Exists(_localPath) && !Repository.IsValid(_localPath))
40 | {
41 | StatusUpdated?.Invoke("Invalid repository found. Cleaning up...");
42 | Directory.Delete(_localPath, true);
43 | }
44 |
45 | // 如果仓库目录不存在,则克隆
46 | if (!Repository.IsValid(_localPath))
47 | {
48 | StatusUpdated?.Invoke("Cloning icon repository...");
49 | Repository.Clone(_repoUrl, _localPath, new CloneOptions());
50 | StatusUpdated?.Invoke("Clone successful.");
51 | return true;
52 | }
53 |
54 | // 如果目录存在且是有效仓库,则拉取更新
55 | using (var repo = new Repository(_localPath))
56 | {
57 | StatusUpdated?.Invoke("Fetching updates...");
58 | var remote = repo.Network.Remotes["origin"];
59 | var refSpecs = remote.FetchRefSpecs.Select(x => x.Specification);
60 | Commands.Fetch(repo, remote.Name, refSpecs, new FetchOptions(), "");
61 | StatusUpdated?.Invoke("Fetch complete. Merging changes...");
62 |
63 | var result = repo.MergeFetchedRefs(new Signature("user", "user@example.com", DateTimeOffset.Now), new MergeOptions());
64 |
65 | if (result.Status == MergeStatus.UpToDate)
66 | {
67 | StatusUpdated?.Invoke("Icon pack is up to date.");
68 | }
69 | else
70 | {
71 | StatusUpdated?.Invoke("Icon pack updated successfully.");
72 | }
73 | return true;
74 | }
75 | }
76 | catch (Exception ex)
77 | {
78 | StatusUpdated?.Invoke($"Error updating icon pack: {ex.Message}");
79 | return false;
80 | }
81 | });
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/ChangeFolderIcon.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.14.36301.6
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChangeFolderIcon", "ChangeFolderIcon\ChangeFolderIcon.csproj", "{B0047555-34AA-4500-92EF-418C66078372}"
7 | ProjectSection(ProjectDependencies) = postProject
8 | {E8459169-620E-4EFC-AA5F-03319FC275C3} = {E8459169-620E-4EFC-AA5F-03319FC275C3}
9 | EndProjectSection
10 | EndProject
11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElevatedWorker", "ElevatedWorker\ElevatedWorker.csproj", "{E8459169-620E-4EFC-AA5F-03319FC275C3}"
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|ARM64 = Debug|ARM64
16 | Debug|x64 = Debug|x64
17 | Debug|x86 = Debug|x86
18 | Release|ARM64 = Release|ARM64
19 | Release|x64 = Release|x64
20 | Release|x86 = Release|x86
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {B0047555-34AA-4500-92EF-418C66078372}.Debug|ARM64.ActiveCfg = Debug|ARM64
24 | {B0047555-34AA-4500-92EF-418C66078372}.Debug|ARM64.Build.0 = Debug|ARM64
25 | {B0047555-34AA-4500-92EF-418C66078372}.Debug|ARM64.Deploy.0 = Debug|ARM64
26 | {B0047555-34AA-4500-92EF-418C66078372}.Debug|x64.ActiveCfg = Debug|x64
27 | {B0047555-34AA-4500-92EF-418C66078372}.Debug|x64.Build.0 = Debug|x64
28 | {B0047555-34AA-4500-92EF-418C66078372}.Debug|x64.Deploy.0 = Debug|x64
29 | {B0047555-34AA-4500-92EF-418C66078372}.Debug|x86.ActiveCfg = Debug|x86
30 | {B0047555-34AA-4500-92EF-418C66078372}.Debug|x86.Build.0 = Debug|x86
31 | {B0047555-34AA-4500-92EF-418C66078372}.Debug|x86.Deploy.0 = Debug|x86
32 | {B0047555-34AA-4500-92EF-418C66078372}.Release|ARM64.ActiveCfg = Release|ARM64
33 | {B0047555-34AA-4500-92EF-418C66078372}.Release|ARM64.Build.0 = Release|ARM64
34 | {B0047555-34AA-4500-92EF-418C66078372}.Release|ARM64.Deploy.0 = Release|ARM64
35 | {B0047555-34AA-4500-92EF-418C66078372}.Release|x64.ActiveCfg = Release|x64
36 | {B0047555-34AA-4500-92EF-418C66078372}.Release|x64.Build.0 = Release|x64
37 | {B0047555-34AA-4500-92EF-418C66078372}.Release|x64.Deploy.0 = Release|x64
38 | {B0047555-34AA-4500-92EF-418C66078372}.Release|x86.ActiveCfg = Release|x86
39 | {B0047555-34AA-4500-92EF-418C66078372}.Release|x86.Build.0 = Release|x86
40 | {B0047555-34AA-4500-92EF-418C66078372}.Release|x86.Deploy.0 = Release|x86
41 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Debug|ARM64.ActiveCfg = Debug|Any CPU
42 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Debug|ARM64.Build.0 = Debug|Any CPU
43 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Debug|x64.ActiveCfg = Debug|Any CPU
44 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Debug|x64.Build.0 = Debug|Any CPU
45 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Debug|x86.ActiveCfg = Debug|Any CPU
46 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Debug|x86.Build.0 = Debug|Any CPU
47 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Release|ARM64.ActiveCfg = Release|Any CPU
48 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Release|ARM64.Build.0 = Release|Any CPU
49 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Release|x64.ActiveCfg = Release|Any CPU
50 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Release|x64.Build.0 = Release|Any CPU
51 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Release|x86.ActiveCfg = Release|Any CPU
52 | {E8459169-620E-4EFC-AA5F-03319FC275C3}.Release|x86.Build.0 = Release|Any CPU
53 | EndGlobalSection
54 | GlobalSection(SolutionProperties) = preSolution
55 | HideSolutionNode = FALSE
56 | EndGlobalSection
57 | GlobalSection(ExtensibilityGlobals) = postSolution
58 | SolutionGuid = {FCF87F20-CE28-4D70-8A3F-CFA0413219D3}
59 | EndGlobalSection
60 | EndGlobal
61 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/ChangeFolderIcon.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | None
5 | true
6 | net8.0-windows10.0.22621.0
7 | 10.0.17763.0
8 | ChangeFolderIcon
9 | app.manifest
10 | x86;x64;ARM64
11 | win-x86;win-x64;win-arm64
12 | win-$(Platform).pubxml
13 | true
14 | true
15 | enable
16 | DISABLE_XAML_GENERATED_MAIN;$(DefineConstants)
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
38 |
39 |
40 | PreserveNewest
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | MSBuild:Compile
52 |
53 |
54 |
55 |
56 | MSBuild:Compile
57 |
58 |
59 |
60 |
61 | MSBuild:Compile
62 |
63 |
64 |
65 |
66 | MSBuild:Compile
67 |
68 |
69 |
70 |
71 | MSBuild:Compile
72 |
73 |
74 |
75 |
80 |
81 | true
82 |
83 |
84 |
85 |
86 | False
87 | True
88 | False
89 | True
90 | en-us
91 | Assets\icon\app_icon.ico
92 |
93 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Utils/Services/FolderNavigationService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ChangeFolderIcon.Utils.Services
9 | {
10 | ///
11 | /// 提供加载文件夹结构相关的功能。
12 | ///
13 | public class FolderNavigationService
14 | {
15 | ///
16 | /// 递归地使用 System.IO 构建子文件夹的数据模型树。
17 | ///
18 | /// 父文件夹的路径。
19 | /// 子文件夹节点列表。
20 | public List BuildChildNodes(string path)
21 | {
22 | var children = new List();
23 | try
24 | {
25 | foreach (var dirPath in Directory.EnumerateDirectories(path))
26 | {
27 | var childNode = new FolderNode
28 | {
29 | Name = Path.GetFileName(dirPath),
30 | Path = dirPath,
31 | // 从 desktop.ini 获取图标路径
32 | IconPath = GetIconPathFromDesktopIni(dirPath),
33 | // 递归为子文件夹构建它们的子节点
34 | SubFolders = BuildChildNodes(dirPath)
35 | };
36 | children.Add(childNode);
37 | }
38 | }
39 | catch (UnauthorizedAccessException)
40 | {
41 | // 忽略无权访问的文件夹,避免程序崩溃
42 | }
43 | catch (IOException)
44 | {
45 | // 忽略其他可能的IO错误
46 | }
47 | return children;
48 | }
49 |
50 | ///
51 | /// 尝试从文件夹的 desktop.ini 文件中读取图标路径。
52 | ///
53 | /// 文件夹的完整路径。
54 | /// 图标文件的路径,如果不存在或解析失败则返回 null。
55 | private string? GetIconPathFromDesktopIni(string folderPath)
56 | {
57 | var iniPath = Path.Combine(folderPath, "desktop.ini");
58 | if (!File.Exists(iniPath)) return null;
59 |
60 | try
61 | {
62 | var lines = File.ReadAllLines(iniPath);
63 | var iconResourceLine = lines.FirstOrDefault(line => line.Trim().StartsWith("IconResource=", StringComparison.OrdinalIgnoreCase));
64 |
65 | if (iconResourceLine != null)
66 | {
67 | // 提取等号后面的路径部分
68 | var pathPart = iconResourceLine.Split('=')[1].Trim();
69 |
70 | // 移除图标索引 (例如 ",0")
71 | int commaIndex = pathPart.LastIndexOf(',');
72 | if (commaIndex != -1)
73 | {
74 | if (int.TryParse(pathPart.Substring(commaIndex + 1), out _))
75 | {
76 | pathPart = pathPart.Substring(0, commaIndex);
77 | }
78 | }
79 |
80 | // 展开路径中的环境变量 (例如 %SystemRoot%)
81 | var expandedPath = Environment.ExpandEnvironmentVariables(pathPart);
82 |
83 | // 如果路径不是绝对路径,则视为相对于当前文件夹
84 | if (!Path.IsPathRooted(expandedPath))
85 | {
86 | expandedPath = Path.GetFullPath(Path.Combine(folderPath, expandedPath));
87 | }
88 |
89 | // 只有当路径指向一个实际存在的文件时才返回它
90 | if (File.Exists(expandedPath))
91 | {
92 | return expandedPath;
93 | }
94 | }
95 | }
96 | catch (Exception)
97 | {
98 | // 忽略解析过程中的任何错误,并返回null
99 | }
100 |
101 | return null;
102 | }
103 |
104 |
105 | public class FolderNode
106 | {
107 | public string? Name { get; set; }
108 | public string? Path { get; set; }
109 | ///
110 | /// 文件夹自定义图标的路径 (如果有)。
111 | ///
112 | public string? IconPath { get; set; }
113 | public List SubFolders { get; set; } = new List();
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/UserControls/WindowControl/CustomTitleBar.xaml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
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 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Utils/Services/ElevationService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Threading.Tasks;
5 |
6 | namespace ChangeFolderIcon.Utils.Services
7 | {
8 | public static class ElevationService
9 | {
10 | ///
11 | /// 检查写入指定路径是否需要管理员权限。
12 | ///
13 | /// 要检查的文件夹路径。
14 | /// 如果需要提权则返回 true,否则返回 false。
15 | public static bool NeedsElevation(string folderPath)
16 | {
17 | try
18 | {
19 | // 检查一些已知的受保护系统文件夹
20 | string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
21 | string programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
22 | string windows = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
23 |
24 | if (!string.IsNullOrEmpty(programFiles) && folderPath.StartsWith(programFiles, StringComparison.OrdinalIgnoreCase)) return true;
25 | if (!string.IsNullOrEmpty(programFilesX86) && folderPath.StartsWith(programFilesX86, StringComparison.OrdinalIgnoreCase)) return true;
26 | if (!string.IsNullOrEmpty(windows) && folderPath.StartsWith(windows, StringComparison.OrdinalIgnoreCase)) return true;
27 |
28 | // 尝试创建一个临时文件来直接测试写权限
29 | string testFile = Path.Combine(folderPath, Guid.NewGuid().ToString("N") + ".tmp");
30 | File.WriteAllText(testFile, "test");
31 | File.Delete(testFile);
32 | return false; // 成功写入并删除,说明有权限
33 | }
34 | catch (UnauthorizedAccessException)
35 | {
36 | return true; // 捕获到“无权限”异常,说明需要提权
37 | }
38 | catch
39 | {
40 | // 其他异常,为安全起见,也认为需要提权
41 | return true;
42 | }
43 | }
44 |
45 | ///
46 | /// 以管理员权限运行辅助工具。
47 | ///
48 | /// 传递给 ElevatedWorker.exe 的命令行参数。
49 | /// 操作成功完成返回 true,用户取消UAC或发生错误则返回 false。
50 | private static Task RunWorkerAsync(string arguments)
51 | {
52 | var tcs = new TaskCompletionSource();
53 |
54 | // 获取 ElevatedWorker.exe 的路径,假设它和主程序在同一目录下
55 | string workerPath = Path.Combine(AppContext.BaseDirectory, "ElevatedWorker.exe");
56 |
57 | if (!File.Exists(workerPath))
58 | {
59 | // 如果找不到辅助工具,直接返回失败
60 | tcs.SetResult(false);
61 | return tcs.Task;
62 | }
63 |
64 | try
65 | {
66 | var process = new Process
67 | {
68 | StartInfo =
69 | {
70 | FileName = workerPath,
71 | Arguments = arguments,
72 | // Verb = "runas" 是触发 UAC 提权的关键
73 | Verb = "runas",
74 | UseShellExecute = true,
75 | // 隐藏控制台窗口
76 | WindowStyle = ProcessWindowStyle.Hidden,
77 | CreateNoWindow = true
78 | },
79 | EnableRaisingEvents = true
80 | };
81 |
82 | process.Exited += (sender, args) =>
83 | {
84 | // 进程退出后,根据退出代码判断是否成功
85 | tcs.SetResult(process.ExitCode == 0);
86 | process.Dispose();
87 | };
88 |
89 | process.Start();
90 | }
91 | catch (Exception)
92 | {
93 | // 如果用户在UAC提示框中点击“否”,会抛出异常
94 | tcs.SetResult(false);
95 | }
96 |
97 | return tcs.Task;
98 | }
99 |
100 | ///
101 | /// 提权设置文件夹图标。
102 | ///
103 | public static Task SetFolderIconElevatedAsync(string folderPath, string iconPath)
104 | {
105 | // 将路径用引号包裹,以正确处理带空格的路径
106 | string arguments = $"set \"{folderPath}\" \"{iconPath}\"";
107 | return RunWorkerAsync(arguments);
108 | }
109 |
110 | ///
111 | /// 提权清除文件夹图标。
112 | ///
113 | public static Task ClearFolderIconElevatedAsync(string folderPath)
114 | {
115 | string arguments = $"clear \"{folderPath}\"";
116 | return RunWorkerAsync(arguments);
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
56 |
57 |
58 |
96 |
97 |
98 |
99 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/UserControls/IconControl.xaml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
53 |
54 |
55 |
56 |
59 |
60 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
93 |
96 |
97 |
98 |
99 |
106 |
107 |
108 |
109 |
113 |
118 |
119 |
120 |
121 |
127 |
130 |
133 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Pages/SettingsPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using ChangeFolderIcon.Utils.Services;
2 | using Microsoft.UI.Xaml;
3 | using Microsoft.UI.Xaml.Controls;
4 | using Microsoft.UI.Xaml.Navigation;
5 | using Microsoft.Windows.ApplicationModel.Resources;
6 | using System;
7 | using System.IO;
8 | using System.Linq;
9 |
10 | namespace ChangeFolderIcon.Pages
11 | {
12 | public sealed partial class SettingsPage : Page
13 | {
14 | private readonly SettingsService? _settingsService;
15 | private readonly IconPackService? _iconPackService;
16 | private bool _isInitializing = true;
17 | private readonly ResourceLoader resourceLoader = new();
18 |
19 | // 当图标包路径改变时触发的事件
20 | public event EventHandler? IconPackPathChanged;
21 |
22 | public SettingsPage()
23 | {
24 | this.InitializeComponent();
25 | _settingsService = App.SettingsService;
26 | _iconPackService = App.IconPackService;
27 |
28 | if (_iconPackService != null)
29 | {
30 | _iconPackService.StatusUpdated += OnIconPackStatusUpdated;
31 | }
32 |
33 | LoadSettings();
34 | _isInitializing = false;
35 | }
36 |
37 | private void LoadSettings()
38 | {
39 | // Language
40 | var currentLang = _settingsService?.Settings.Language;
41 | LanguageComboBox.SelectedItem = LanguageComboBox.Items
42 | .Cast()
43 | .FirstOrDefault(item => (string)item.Tag == currentLang) ?? LanguageComboBox.Items[0];
44 |
45 | // Theme
46 | var currentTheme = _settingsService?.Settings.Theme;
47 | ThemeComboBox.SelectedItem = ThemeComboBox.Items
48 | .Cast()
49 | .FirstOrDefault(item => (string)item.Tag == currentTheme) ?? ThemeComboBox.Items[2];
50 |
51 | // Backdrop
52 | var currentBackdrop = _settingsService?.Settings.Backdrop;
53 | BackdropComboBox.SelectedItem = BackdropComboBox.Items
54 | .Cast()
55 | .FirstOrDefault(item => (string)item.Tag == currentBackdrop) ?? BackdropComboBox.Items[0];
56 |
57 | // Icon Pack Path
58 | UpdateIconPackPathDisplay();
59 | }
60 |
61 | private void UpdateIconPackPathDisplay()
62 | {
63 | if (_settingsService != null)
64 | {
65 | // 显示当前设置的ico文件夹路径
66 | string path = _settingsService.Settings.IconPackPath ?? resourceLoader.GetString("NotSet");
67 | string displayPath = _settingsService.Settings.IconRepoLocalPath ?? resourceLoader.GetString("NotSet");
68 | IconPackPathTextBlock.Text = $"{resourceLoader.GetString("CurrentPath")}: {displayPath}";
69 | }
70 | }
71 |
72 | private void LanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
73 | {
74 | if (_isInitializing) return;
75 |
76 | var selectedTag = ((ComboBoxItem)LanguageComboBox.SelectedItem)?.Tag as string;
77 | if (selectedTag != null && _settingsService != null)
78 | {
79 | // Check if language actually changed
80 | if (_settingsService.Settings.Language != selectedTag)
81 | {
82 | _settingsService.Settings.Language = selectedTag;
83 | _settingsService.SaveSettings();
84 | LanguageService.SetLanguage(selectedTag);
85 |
86 | // Show restart info bar
87 | LanguageRestartInfoBar.IsOpen = true;
88 | }
89 | }
90 | }
91 |
92 | private void ThemeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
93 | {
94 | if (_isInitializing) return;
95 |
96 | var selectedTag = ((ComboBoxItem)ThemeComboBox.SelectedItem)?.Tag as string;
97 | if (selectedTag != null && _settingsService != null)
98 | {
99 | _settingsService.Settings.Theme = selectedTag;
100 | _settingsService.SaveSettings();
101 |
102 | if (App.window != null)
103 | {
104 | SettingsService.ApplyTheme(App.window, selectedTag);
105 | }
106 | }
107 | }
108 |
109 | private void BackdropComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
110 | {
111 | if (_isInitializing) return;
112 |
113 | var selectedTag = ((ComboBoxItem)BackdropComboBox.SelectedItem)?.Tag as string;
114 | if (selectedTag != null && _settingsService != null)
115 | {
116 | if (App.window is MainWindow mainWindow)
117 | {
118 | _settingsService.Settings.Backdrop = selectedTag;
119 | _settingsService.SaveSettings();
120 | SettingsService.ApplyBackdrop(mainWindow, selectedTag);
121 | }
122 | }
123 | }
124 |
125 | private async void UpdateIconsButton_Click(object sender, RoutedEventArgs e)
126 | {
127 | if (_iconPackService == null || _settingsService == null) return;
128 |
129 | UpdateIconsButton.IsEnabled = false;
130 | UpdateProgressRing.Visibility = Visibility.Visible;
131 | IconPackStatusTextBlock.Text = "";
132 |
133 | try
134 | {
135 | bool success = await _iconPackService.UpdateIconPackAsync();
136 | if (success && _iconPackService.IconPackDirectory != null)
137 | {
138 | _settingsService.Settings.IconRepoLocalPath = _iconPackService.IconPackDirectory;
139 | _settingsService.SaveSettings();
140 | UpdateIconPackPathDisplay();
141 | IconPackPathChanged?.Invoke(this, EventArgs.Empty);
142 | }
143 | }
144 | catch (Exception ex)
145 | {
146 | IconPackStatusTextBlock.Text = $"Error: {ex.Message}";
147 | }
148 | finally
149 | {
150 | UpdateIconsButton.IsEnabled = true;
151 | UpdateProgressRing.Visibility = Visibility.Collapsed;
152 | }
153 | }
154 |
155 | private async void SelectIconsFolderButton_Click(object sender, RoutedEventArgs e)
156 | {
157 | var picker = new Windows.Storage.Pickers.FolderPicker
158 | {
159 | SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop
160 | };
161 | picker.FileTypeFilter.Add("*");
162 | var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.window);
163 | WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
164 |
165 | var folder = await picker.PickSingleFolderAsync();
166 | if (folder != null && _settingsService != null)
167 | {
168 | _settingsService.Settings.IconRepoLocalPath = folder.Path;
169 | _settingsService.SaveSettings();
170 | UpdateIconPackPathDisplay();
171 | // 触发事件通知MainWindow刷新IconsPage
172 | IconPackPathChanged?.Invoke(this, EventArgs.Empty);
173 | }
174 | }
175 |
176 | private void OnIconPackStatusUpdated(string status)
177 | {
178 | DispatcherQueue.TryEnqueue(() =>
179 | {
180 | IconPackStatusTextBlock.Text = status;
181 | });
182 | }
183 |
184 | protected override void OnNavigatedFrom(NavigationEventArgs e)
185 | {
186 | base.OnNavigatedFrom(e);
187 |
188 | // Unsubscribe from events
189 | if (_iconPackService != null)
190 | {
191 | _iconPackService.StatusUpdated -= OnIconPackStatusUpdated;
192 | }
193 | }
194 | }
195 | }
--------------------------------------------------------------------------------
/ChangeFolderIcon/Pages/IconsPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
70 |
71 |
72 |
73 |
74 |
78 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
96 |
97 |
98 |
99 |
100 |
101 |
105 |
106 |
107 |
111 |
117 |
122 |
123 |
124 |
125 |
126 |
134 |
135 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
151 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
166 |
169 |
173 |
174 |
175 |
176 |
179 |
184 |
189 |
194 |
199 |
200 |
201 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Pages/IconsPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using ChangeFolderIcon.Models;
2 | using ChangeFolderIcon.Utils.Events;
3 | using ChangeFolderIcon.Utils.Services;
4 | using ChangeFolderIcon.Utils.WindowsAPI;
5 | using Microsoft.UI.Xaml;
6 | using Microsoft.UI.Xaml.Controls;
7 | using Microsoft.UI.Xaml.Data;
8 | using Microsoft.Windows.ApplicationModel.Resources;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Collections.ObjectModel;
12 | using System.ComponentModel;
13 | using System.IO;
14 | using System.Linq;
15 | using System.Threading.Tasks;
16 |
17 | namespace ChangeFolderIcon.Pages
18 | {
19 | public sealed partial class IconsPage : Page
20 | {
21 | public ObservableCollection Icons { get; } = new();
22 | public ICollectionView? GroupedIcons { get; private set; }
23 |
24 | private string? _selectedFolderPath;
25 | private IconInfo? _selectedIcon;
26 | private readonly List? _groupedCollection = new();
27 | private readonly ResourceLoader resourceLoader = new();
28 | private readonly SettingsService? _settingsService;
29 |
30 | // 通知 MainWindow 图标已更改的事件
31 | public event EventHandler? IconChanged;
32 | // 请求 MainWindow 导航到设置页面的事件
33 | public event EventHandler? RequestNavigateToSettings;
34 |
35 | public IconsPage()
36 | {
37 | this.InitializeComponent();
38 | _settingsService = App.SettingsService;
39 | InitializeIcons();
40 | }
41 |
42 | ///
43 | /// 初始化图标库,如果找不到则显示提示信息
44 | ///
45 | public void InitializeIcons()
46 | {
47 | string? icoDir = _settingsService?.Settings?.IconPackPath;
48 | if (icoDir != null && Directory.Exists(icoDir) && Directory.EnumerateFiles(icoDir, "*.ico").Any())
49 | {
50 | // 找到图标,正常加载
51 | MissingIconsOverlay.Visibility = Visibility.Collapsed;
52 | IconsGrid.Visibility = Visibility.Visible;
53 | AlphabetIndexBorder.Visibility = Visibility.Visible;
54 |
55 | Icons.Clear();
56 | foreach (string ico in Directory.EnumerateFiles(icoDir, "*.ico"))
57 | {
58 | try { Icons.Add(IconInfo.FromPath(ico)); }
59 | catch { /* ignore */ }
60 | }
61 |
62 | SetupGrouping();
63 | SetupAlphabetIndex();
64 | UpdateHeaderAndActions();
65 | }
66 | else
67 | {
68 | // 未找到图标,显示覆盖层提示
69 | MissingIconsOverlay.Visibility = Visibility.Visible;
70 | IconsGrid.Visibility = Visibility.Collapsed;
71 | AlphabetIndexBorder.Visibility = Visibility.Collapsed;
72 | }
73 | }
74 |
75 | // 公共方法,用于从外部(如MainWindow)滚动到指定图标
76 | public void ScrollToIcon(IconInfo? targetIcon)
77 | {
78 | if (targetIcon == null) return;
79 |
80 | // 滚动到视图
81 | IconsGrid.ScrollIntoView(targetIcon, ScrollIntoViewAlignment.Default);
82 |
83 | // 选中该项以高亮显示
84 | IconsGrid.SelectedItem = targetIcon;
85 | _selectedIcon = targetIcon;
86 | UpdateSelectedIconText();
87 | }
88 |
89 | private void SetupGrouping()
90 | {
91 | _groupedCollection?.Clear();
92 |
93 | var groups = Icons.GroupBy(icon =>
94 | {
95 | if (string.IsNullOrEmpty(icon.Name)) return "#";
96 | char firstChar = char.ToUpper(icon.Name[0]);
97 | if (char.IsDigit(firstChar)) return "0-9";
98 | if (char.IsLetter(firstChar)) return firstChar.ToString();
99 | return "#";
100 | }).OrderBy(g =>
101 | {
102 | if (g.Key == "#") return "ZZZ";
103 | if (g.Key == "0-9") return "000";
104 | return g.Key;
105 | });
106 |
107 | foreach (var group in groups)
108 | {
109 | _groupedCollection?.Add(new IconGroup(group.Key, group.OrderBy(i => i.Name)));
110 | }
111 |
112 | var cvs = new CollectionViewSource
113 | {
114 | Source = _groupedCollection,
115 | IsSourceGrouped = true
116 | };
117 |
118 | GroupedIcons = cvs.View;
119 | }
120 |
121 | private void SetupAlphabetIndex()
122 | {
123 | // 清理旧的按钮以防重复添加
124 | AlphabetIndexPanel.Children.Clear();
125 |
126 | // 创建索引按钮
127 | var indexChars = new List { "0-9" };
128 | indexChars.AddRange(Enumerable.Range('A', 26).Select(i => ((char)i).ToString()));
129 | indexChars.Add("#");
130 |
131 | foreach (var indexChar in indexChars)
132 | {
133 | var button = new Button
134 | {
135 | Content = indexChar,
136 | Width = 24,
137 | Height = 24,
138 | Padding = new Thickness(0),
139 | Margin = new Thickness(0, 2, 0, 2),
140 | FontSize = 11,
141 | HorizontalAlignment = HorizontalAlignment.Center,
142 | };
143 |
144 | button.Click += (s, e) => OnIndexButtonClick(indexChar);
145 | AlphabetIndexPanel.Children.Add(button);
146 | }
147 | }
148 |
149 | private void OnIndexButtonClick(string index)
150 | {
151 | var targetGroup = _groupedCollection?.FirstOrDefault(g => g.Key == index);
152 | if (targetGroup != null)
153 | {
154 | IconsGrid.ScrollIntoView(targetGroup, ScrollIntoViewAlignment.Leading);
155 | }
156 | }
157 |
158 | // 由 MainWindow 调用以更新所选文件夹的状态
159 | public void UpdateState(string? selectedFolderPath)
160 | {
161 | _selectedFolderPath = selectedFolderPath;
162 | UpdateHeaderAndActions();
163 | }
164 |
165 | #region UI 状态
166 | private void UpdateHeaderAndActions()
167 | {
168 | if (string.IsNullOrEmpty(_selectedFolderPath))
169 | {
170 | HeaderIcon.Glyph = "\uE946";
171 | HeaderTitle.Text = resourceLoader.GetString("IconPageHeaderTitleText");
172 | HeaderDescription.Text = resourceLoader.GetString("IconPageHeaderDescriptionText");
173 | ActionPanel.Visibility = Visibility.Collapsed;
174 | }
175 | else
176 | {
177 | HeaderIcon.Glyph = "\uE8B7";
178 | HeaderTitle.Text = Path.GetFileName(_selectedFolderPath);
179 | HeaderDescription.Text = _selectedFolderPath;
180 | ActionPanel.Visibility = Visibility.Visible;
181 | }
182 | UpdateSelectedIconText();
183 | }
184 |
185 | private void UpdateSelectedIconText()
186 | {
187 | SelectedIconText.Text = _selectedIcon != null ? resourceLoader.GetString("Selected") + _selectedIcon.Name : resourceLoader.GetString("IconPageSelectedIconTextText");
188 | }
189 | #endregion
190 |
191 | private void IconsGrid_ItemClick(object sender, ItemClickEventArgs e)
192 | {
193 | _selectedIcon = e.ClickedItem as IconInfo;
194 | UpdateSelectedIconText();
195 | }
196 |
197 | private void GoToSettingsButton_Click(object sender, RoutedEventArgs e)
198 | {
199 | RequestNavigateToSettings?.Invoke(this, EventArgs.Empty);
200 | }
201 |
202 | #region "单文件夹"按钮操作
203 | private async void ApplyButton_Click(object sender, RoutedEventArgs e)
204 | {
205 | if (!CheckPreCondition()) return;
206 | try
207 | {
208 | var progressDialog = ProgressDialog();
209 | _ = progressDialog.ShowAsync();
210 | await IconManager.SetFolderIconAsync(_selectedFolderPath!, _selectedIcon!.FullPath);
211 | progressDialog.Hide();
212 | IconChanged?.Invoke(this, new IconChangedEventArgs(_selectedFolderPath!, _selectedIcon.FullPath));
213 | await ShowMsg(resourceLoader.GetString("Done"), resourceLoader.GetString("AppliedFolder"));
214 | }
215 | catch (Exception ex) { await ShowMsg(resourceLoader.GetString("Failed"), ex.Message); }
216 | }
217 |
218 | private async void ClearButton_Click(object sender, RoutedEventArgs e)
219 | {
220 | if (string.IsNullOrEmpty(_selectedFolderPath))
221 | { await ShowMsg(resourceLoader.GetString("Note"), resourceLoader.GetString("SelectTargetFolder")); return; }
222 | try
223 | {
224 | await IconManager.ClearFolderIconAsync(_selectedFolderPath!);
225 | IconChanged?.Invoke(this, new IconChangedEventArgs(_selectedFolderPath!, null));
226 | await ShowMsg(resourceLoader.GetString("Done"), resourceLoader.GetString("clearedFolder"));
227 | }
228 | catch (Exception ex) { await ShowMsg(resourceLoader.GetString("Failed"), ex.Message); }
229 | }
230 | #endregion
231 |
232 | #region "递归/批量"按钮操作
233 | private async void ApplyAllButton_Click(object sender, RoutedEventArgs e)
234 | {
235 | if (!CheckPreCondition()) return;
236 | var progressDialog = ProgressDialog();
237 | _ = progressDialog.ShowAsync();
238 | int count = await IconManager.ApplyIconToAllSubfoldersAsync(_selectedFolderPath!, _selectedIcon!.FullPath);
239 | progressDialog.Hide();
240 | RaiseForAllSubFolders(_selectedFolderPath!, _selectedIcon!.FullPath);
241 | await ShowMsg(resourceLoader.GetString("Done"), resourceLoader.GetString("clearedFolderText_1") + count + resourceLoader.GetString("clearedFolderText_2"));
242 | }
243 |
244 | private async void ClearAllButton_Click(object sender, RoutedEventArgs e)
245 | {
246 | if (string.IsNullOrEmpty(_selectedFolderPath))
247 | { await ShowMsg(resourceLoader.GetString("Note"), resourceLoader.GetString("SelectTargetFolderFirst")); return; }
248 | int count = await IconManager.ClearIconRecursivelyAsync(_selectedFolderPath!);
249 | RaiseForAllSubFolders(_selectedFolderPath!, null);
250 | await ShowMsg(resourceLoader.GetString("Done"), resourceLoader.GetString("clearedFoldersText_1") + count + resourceLoader.GetString("clearedFoldersText_2"));
251 | }
252 | #endregion
253 |
254 | #region 共用小工具
255 | private bool CheckPreCondition()
256 | {
257 | if (_selectedIcon == null)
258 | {
259 | _ = ShowMsg(resourceLoader.GetString("Note"), resourceLoader.GetString("ClickToSelect"));
260 | return false;
261 | }
262 | if (string.IsNullOrEmpty(_selectedFolderPath))
263 | {
264 | _ = ShowMsg(resourceLoader.GetString("Note"), resourceLoader.GetString("ClickToSelectLeftNavigation"));
265 | return false;
266 | }
267 | return true;
268 | }
269 |
270 | private static async Task ShowMsg(string title, string msg) =>
271 | await new ContentDialog
272 | {
273 | Title = title,
274 | Content = msg,
275 | CloseButtonText = new ResourceLoader().GetString("CloseButtonText"),
276 | XamlRoot = App.window?.Content.XamlRoot
277 | }.ShowAsync();
278 |
279 | private static ContentDialog ProgressDialog() =>
280 | new ContentDialog
281 | {
282 | Title = new ResourceLoader().GetString("ApplyingIcons"),
283 | Content = new ProgressRing { IsActive = true },
284 | XamlRoot = App.window?.Content.XamlRoot,
285 | IsPrimaryButtonEnabled = false,
286 | IsSecondaryButtonEnabled = false
287 | };
288 |
289 | private void RaiseForAllSubFolders(string root, string? iconPath)
290 | {
291 | IconChanged?.Invoke(this, new IconChangedEventArgs(root, iconPath));
292 | foreach (string dir in Directory.EnumerateDirectories(root, "*", SearchOption.AllDirectories))
293 | {
294 | IconChanged?.Invoke(this, new IconChangedEventArgs(dir, iconPath));
295 | }
296 | }
297 | #endregion
298 | }
299 |
300 | public class IconGroup : List
301 | {
302 | public string Key { get; set; }
303 | public IconGroup(string key, IEnumerable items) : base(items)
304 | {
305 | Key = key;
306 | }
307 | }
308 | }
--------------------------------------------------------------------------------
/ChangeFolderIcon/Utils/WindowsAPI/IconManager.cs:
--------------------------------------------------------------------------------
1 | using ChangeFolderIcon.Utils.Services;
2 | using System;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Threading.Tasks;
8 |
9 | namespace ChangeFolderIcon.Utils.WindowsAPI
10 | {
11 | ///
12 | ///在文件夹上设置自定义图标的工具类
13 | ///
14 | public static class IconManager
15 | {
16 | #region P/Invoke Signatures and Constants
17 |
18 | // --- Win32 P/Invoke for file attributes ---
19 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
20 | [return: MarshalAs(UnmanagedType.Bool)]
21 | private static extern bool SetFileAttributes(string lpFileName, FileAttributes dwFileAttributes);
22 |
23 | // --- Shell P/Invoke for notifications ---
24 | [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
25 | private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
26 |
27 | [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
28 | private static extern IntPtr ILCreateFromPath(string pszPath);
29 |
30 | [DllImport("ole32.dll", PreserveSig = false)]
31 | private static extern void CoTaskMemFree(IntPtr pv);
32 |
33 | // --- P/Invoke for the recommended SHGetSetFolderCustomSettings API ---
34 | [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
35 | private static extern int SHGetSetFolderCustomSettings(ref SHFOLDERCUSTOMSETTINGS pfcs, string pszPath, uint dwReadWrite);
36 |
37 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
38 | private struct SHFOLDERCUSTOMSETTINGS
39 | {
40 | public uint dwSize;
41 | public uint dwMask;
42 | public IntPtr pvid;
43 | [MarshalAs(UnmanagedType.LPWStr)]
44 | public string pszWebViewTemplate;
45 | public uint cchWebViewTemplate;
46 | [MarshalAs(UnmanagedType.LPWStr)]
47 | public string pszWebViewTemplateVersion;
48 | [MarshalAs(UnmanagedType.LPWStr)]
49 | public string pszInfoTip;
50 | public uint cchInfoTip;
51 | public IntPtr pclsid;
52 | public uint dwFlags;
53 | [MarshalAs(UnmanagedType.LPWStr)]
54 | public string pszIconFile;
55 | public uint cchIconFile;
56 | public int iIconIndex;
57 | [MarshalAs(UnmanagedType.LPWStr)]
58 | public string pszLogo;
59 | public uint cchLogo;
60 | }
61 |
62 | // --- P/Invoke for broadcasting system-wide setting changes ("nuclear option") ---
63 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
64 | private static extern IntPtr SendMessageTimeout(
65 | IntPtr hWnd,
66 | uint Msg,
67 | IntPtr wParam,
68 | IntPtr lParam,
69 | SendMessageTimeoutFlags fuFlags,
70 | uint uTimeout,
71 | out IntPtr lpdwResult);
72 |
73 | [Flags]
74 | private enum SendMessageTimeoutFlags : uint
75 | {
76 | SMTO_NORMAL = 0x0,
77 | SMTO_BLOCK = 0x1,
78 | SMTO_ABORTIFHUNG = 0x2,
79 | SMTO_NOTIMEOUTIFNOTHUNG = 0x8,
80 | SMTO_ERRORONEXIT = 0x20
81 | }
82 |
83 | // Constants for SHChangeNotify
84 | private const uint SHCNE_UPDATEITEM = 0x2000;
85 | private const uint SHCNE_UPDATEDIR = 0x1000;
86 | private const uint SHCNE_ASSOCCHANGED = 0x08000000;
87 | private const uint SHCNF_IDLIST = 0x0000;
88 | private const uint SHCNF_FLUSH = 0x1000; // Waits for the notification to be processed.
89 |
90 | // Constants for SHGetSetFolderCustomSettings
91 | private const uint FCSM_ICONFILE = 0x00000010;
92 | private const uint FCS_FORCEWRITE = 0x00000002; // Forces the write, even if settings are already present.
93 |
94 | // Constants for SendMessageTimeout
95 | private const uint WM_SETTINGCHANGE = 0x1A;
96 | private const int SPI_SETICONS = 0x58;
97 | private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
98 |
99 | #endregion
100 |
101 | #region 为单个文件夹设置图标
102 | ///
103 | /// 为单个文件夹设置图标
104 | ///
105 | /// 目标文件夹的完整路径
106 | /// 图标文件的完整路径 (.ico)
107 | private static void SetFolderIconInternal(string folderPath, string iconPath)
108 | {
109 | if (string.IsNullOrWhiteSpace(folderPath) || !Directory.Exists(folderPath))
110 | throw new DirectoryNotFoundException("The specified folder does not exist.");
111 | if (string.IsNullOrWhiteSpace(iconPath) || !File.Exists(iconPath))
112 | throw new FileNotFoundException("The specified icon file does not exist.");
113 |
114 | ClearOldIconFiles(folderPath);
115 |
116 | string stamp = DateTime.UtcNow.Ticks.ToString();
117 | string uniqueIconName = $"folder_{stamp}.ico";
118 | string targetIconPath = Path.Combine(folderPath, uniqueIconName);
119 | File.Copy(iconPath, targetIconPath, true);
120 | SetFileAttributes(targetIconPath, FileAttributes.Hidden | FileAttributes.System);
121 |
122 | var fcs = new SHFOLDERCUSTOMSETTINGS
123 | {
124 | dwSize = (uint)Marshal.SizeOf(),
125 | dwMask = FCSM_ICONFILE,
126 | pszIconFile = targetIconPath,
127 | cchIconFile = (uint)targetIconPath.Length,
128 | iIconIndex = 0
129 | };
130 |
131 | int hr = SHGetSetFolderCustomSettings(ref fcs, folderPath, FCS_FORCEWRITE);
132 | Marshal.ThrowExceptionForHR(hr);
133 |
134 | var folderAttrs = File.GetAttributes(folderPath);
135 | SetFileAttributes(folderPath, folderAttrs | FileAttributes.ReadOnly);
136 |
137 | NotifyExplorerOfUpdate(folderPath);
138 | }
139 |
140 | ///
141 | /// 清除单个文件夹的自定义图标
142 | ///
143 | /// 目标文件夹路径
144 | private static void ClearFolderIconInternal(string folderPath)
145 | {
146 | if (string.IsNullOrWhiteSpace(folderPath) || !Directory.Exists(folderPath))
147 | return;
148 |
149 | var folderAttrs = File.GetAttributes(folderPath);
150 | if (folderAttrs.HasFlag(FileAttributes.ReadOnly))
151 | {
152 | SetFileAttributes(folderPath, folderAttrs & ~FileAttributes.ReadOnly);
153 | }
154 |
155 | ClearOldIconFiles(folderPath);
156 |
157 | string iniPath = Path.Combine(folderPath, "desktop.ini");
158 | if (File.Exists(iniPath))
159 | {
160 | SetFileAttributes(iniPath, FileAttributes.Normal);
161 | File.Delete(iniPath);
162 | }
163 |
164 | NotifyExplorerOfUpdate(folderPath);
165 | }
166 | #endregion
167 |
168 | #region 公共调度方法
169 | ///
170 | /// 为单个文件夹设置图标(自动处理权限)
171 | ///
172 | /// 目标文件夹路径
173 | /// 图标文件的完整路径 (.ico)
174 | public static async Task SetFolderIconAsync(string folderPath, string iconPath)
175 | {
176 | if (ElevationService.NeedsElevation(folderPath))
177 | {
178 | // 需要提权
179 | bool success = await ElevationService.SetFolderIconElevatedAsync(folderPath, iconPath);
180 | if (!success)
181 | {
182 | throw new Exception("Elevated operation failed or was canceled by the user.");
183 | }
184 | }
185 | else
186 | {
187 | await Task.Run(() => SetFolderIconInternal(folderPath, iconPath));
188 | }
189 | }
190 |
191 | ///
192 | /// 清除单个文件夹的图标(自动处理权限)
193 | ///
194 | /// 目标文件夹路径
195 | public static async Task ClearFolderIconAsync(string folderPath)
196 | {
197 | if (ElevationService.NeedsElevation(folderPath))
198 | {
199 | // 需要提权
200 | bool success = await ElevationService.ClearFolderIconElevatedAsync(folderPath);
201 | if (!success)
202 | {
203 | throw new Exception("Elevated operation failed or was canceled by the user.");
204 | }
205 | }
206 | else
207 | {
208 | await Task.Run(() => ClearFolderIconInternal(folderPath));
209 | }
210 | }
211 |
212 | ///
213 | /// 递归地为所有子文件夹应用图标(自动处理权限)
214 | ///
215 | /// 根文件夹路径
216 | /// 图标文件的完整路径 (.ico)
217 | public static async Task ApplyIconToAllSubfoldersAsync(string rootFolderPath, string iconPath)
218 | {
219 | if (!Directory.Exists(rootFolderPath)) return 0;
220 |
221 | int applied = 0;
222 | var allDirs = Directory.EnumerateDirectories(rootFolderPath, "*", SearchOption.AllDirectories).ToList();
223 | // 包含根目录自身
224 | allDirs.Insert(0, rootFolderPath);
225 |
226 | foreach (var dir in allDirs)
227 | {
228 | try
229 | {
230 | await SetFolderIconAsync(dir, iconPath);
231 | applied++;
232 | }
233 | catch (Exception ex)
234 | {
235 | Debug.WriteLine($"Failed to set icon for {dir}: {ex.Message}");
236 | }
237 | }
238 | return applied;
239 | }
240 |
241 | ///
242 | /// 递归地清除所有子文件夹的图标(自动处理权限)
243 | ///
244 | /// 根文件夹路径
245 | public static async Task ClearIconRecursivelyAsync(string rootFolderPath)
246 | {
247 | if (!Directory.Exists(rootFolderPath)) return 0;
248 | int count = 0;
249 |
250 | var allDirs = Directory.EnumerateDirectories(rootFolderPath, "*", SearchOption.AllDirectories).ToList();
251 | // 包含根目录自身
252 | allDirs.Insert(0, rootFolderPath);
253 |
254 | foreach (var dir in allDirs)
255 | {
256 | try
257 | {
258 | await ClearFolderIconAsync(dir);
259 | count++;
260 | }
261 | catch (Exception ex)
262 | {
263 | Debug.WriteLine($"Failed to clear icon for {dir}: {ex.Message}");
264 | }
265 | }
266 | return count;
267 | }
268 | #endregion
269 |
270 | #region 私有辅助方法
271 | private static void NotifyExplorerOfUpdate(string path)
272 | {
273 | IntPtr pidl = IntPtr.Zero;
274 | try
275 | {
276 | pidl = ILCreateFromPath(path);
277 | if (pidl == IntPtr.Zero) return;
278 |
279 | const uint flags = SHCNF_IDLIST | SHCNF_FLUSH;
280 | SHChangeNotify(SHCNE_UPDATEITEM, flags, pidl, IntPtr.Zero);
281 | SHChangeNotify(SHCNE_UPDATEDIR, flags, pidl, IntPtr.Zero);
282 | SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST | SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
283 | }
284 | finally
285 | {
286 | if (pidl != IntPtr.Zero)
287 | {
288 | CoTaskMemFree(pidl);
289 | }
290 | }
291 |
292 | RefreshIconCacheViaIe4uinit();
293 |
294 | SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, (IntPtr)SPI_SETICONS, IntPtr.Zero, SendMessageTimeoutFlags.SMTO_ABORTIFHUNG, 1000, out _);
295 | }
296 |
297 | private static void ClearOldIconFiles(string folderPath)
298 | {
299 | try
300 | {
301 | var oldIcons = Directory.EnumerateFiles(folderPath, "folder_*.ico");
302 | foreach (var oldIcon in oldIcons)
303 | {
304 | SetFileAttributes(oldIcon, FileAttributes.Normal);
305 | File.Delete(oldIcon);
306 | }
307 | string legacyIconPath = Path.Combine(folderPath, "folder.ico");
308 | if (File.Exists(legacyIconPath))
309 | {
310 | SetFileAttributes(legacyIconPath, FileAttributes.Normal);
311 | File.Delete(legacyIconPath);
312 | }
313 | }
314 | catch (Exception ex)
315 | {
316 | Debug.WriteLine($"Error clearing old icon files in {folderPath}: {ex.Message}");
317 | }
318 | }
319 |
320 | private static void RefreshIconCacheViaIe4uinit()
321 | {
322 | try
323 | {
324 | var startInfo = new ProcessStartInfo
325 | {
326 | FileName = "ie4uinit.exe",
327 | Arguments = "-show",
328 | CreateNoWindow = true,
329 | UseShellExecute = false,
330 | WindowStyle = ProcessWindowStyle.Hidden
331 | };
332 | Process.Start(startInfo)?.WaitForExit(1000);
333 | }
334 | catch (Exception ex)
335 | {
336 | Debug.WriteLine($"Failed to run ie4uinit.exe: {ex.Message}");
337 | }
338 | }
339 | #endregion
340 | }
341 | }
--------------------------------------------------------------------------------
/ElevatedWorker/WindowsAPI/IconManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Runtime.InteropServices;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ElevatedWorker.WindowsAPI
9 | {
10 | ///
11 | ///在文件夹上设置自定义图标的工具类
12 | ///
13 | public static class IconManager
14 | {
15 | #region P/Invoke Signatures and Constants
16 |
17 | // --- Win32 P/Invoke for file attributes ---
18 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
19 | [return: MarshalAs(UnmanagedType.Bool)]
20 | private static extern bool SetFileAttributes(string lpFileName, FileAttributes dwFileAttributes);
21 |
22 | // --- Shell P/Invoke for notifications ---
23 | [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
24 | private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
25 |
26 | [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
27 | private static extern IntPtr ILCreateFromPath(string pszPath);
28 |
29 | [DllImport("ole32.dll", PreserveSig = false)]
30 | private static extern void CoTaskMemFree(IntPtr pv);
31 |
32 | // --- P/Invoke for the recommended SHGetSetFolderCustomSettings API ---
33 | [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
34 | private static extern int SHGetSetFolderCustomSettings(ref SHFOLDERCUSTOMSETTINGS pfcs, string pszPath, uint dwReadWrite);
35 |
36 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
37 | private struct SHFOLDERCUSTOMSETTINGS
38 | {
39 | public uint dwSize;
40 | public uint dwMask;
41 | public IntPtr pvid;
42 | [MarshalAs(UnmanagedType.LPWStr)]
43 | public string pszWebViewTemplate;
44 | public uint cchWebViewTemplate;
45 | [MarshalAs(UnmanagedType.LPWStr)]
46 | public string pszWebViewTemplateVersion;
47 | [MarshalAs(UnmanagedType.LPWStr)]
48 | public string pszInfoTip;
49 | public uint cchInfoTip;
50 | public IntPtr pclsid;
51 | public uint dwFlags;
52 | [MarshalAs(UnmanagedType.LPWStr)]
53 | public string pszIconFile;
54 | public uint cchIconFile;
55 | public int iIconIndex;
56 | [MarshalAs(UnmanagedType.LPWStr)]
57 | public string pszLogo;
58 | public uint cchLogo;
59 | }
60 |
61 | // --- P/Invoke for broadcasting system-wide setting changes ("nuclear option") ---
62 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
63 | private static extern IntPtr SendMessageTimeout(
64 | IntPtr hWnd,
65 | uint Msg,
66 | IntPtr wParam,
67 | IntPtr lParam,
68 | SendMessageTimeoutFlags fuFlags,
69 | uint uTimeout,
70 | out IntPtr lpdwResult);
71 |
72 | [Flags]
73 | private enum SendMessageTimeoutFlags : uint
74 | {
75 | SMTO_NORMAL = 0x0,
76 | SMTO_BLOCK = 0x1,
77 | SMTO_ABORTIFHUNG = 0x2,
78 | SMTO_NOTIMEOUTIFNOTHUNG = 0x8,
79 | SMTO_ERRORONEXIT = 0x20
80 | }
81 |
82 | // Constants for SHChangeNotify
83 | private const uint SHCNE_UPDATEITEM = 0x2000;
84 | private const uint SHCNE_UPDATEDIR = 0x1000;
85 | private const uint SHCNE_ASSOCCHANGED = 0x08000000;
86 | private const uint SHCNF_IDLIST = 0x0000;
87 | private const uint SHCNF_FLUSH = 0x1000; // Waits for the notification to be processed.
88 |
89 | // Constants for SHGetSetFolderCustomSettings
90 | private const uint FCSM_ICONFILE = 0x00000010;
91 | private const uint FCS_FORCEWRITE = 0x00000002; // Forces the write, even if settings are already present.
92 | private const uint FCS_CLEAR = 0x00000004; // Clears custom settings.
93 |
94 | // Constants for SendMessageTimeout
95 | private const uint WM_SETTINGCHANGE = 0x1A;
96 | private const int SPI_SETICONS = 0x58;
97 | private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
98 |
99 | #endregion
100 |
101 | ///
102 | /// 为单个文件夹设置图标,采用多种策略确保立即刷新
103 | ///
104 | /// 目标文件夹的完整路径
105 | /// 图标文件的完整路径 (.ico)
106 | public static void SetFolderIcon(string folderPath, string iconPath)
107 | {
108 | if (string.IsNullOrWhiteSpace(folderPath) || !Directory.Exists(folderPath))
109 | throw new DirectoryNotFoundException("The specified folder does not exist.");
110 | if (string.IsNullOrWhiteSpace(iconPath) || !File.Exists(iconPath))
111 | throw new FileNotFoundException("The specified icon file does not exist.");
112 |
113 | // 1. 清理旧的图标文件
114 | ClearOldIconFiles(folderPath);
115 |
116 | // 2.为了破坏缓存,复制图标文件到目标文件夹并使用唯一的时间戳命名
117 | string stamp = DateTime.UtcNow.Ticks.ToString();
118 | string uniqueIconName = $"folder_{stamp}.ico";
119 | string targetIconPath = Path.Combine(folderPath, uniqueIconName);
120 | File.Copy(iconPath, targetIconPath, true);
121 | SetFileAttributes(targetIconPath, FileAttributes.Hidden | FileAttributes.System);
122 |
123 | // 3.使用 SHGetSetFolderCustomSettings API
124 | var fcs = new SHFOLDERCUSTOMSETTINGS
125 | {
126 | dwSize = (uint)Marshal.SizeOf(),
127 | dwMask = FCSM_ICONFILE,
128 | pszIconFile = targetIconPath,
129 | cchIconFile = (uint)targetIconPath.Length,
130 | iIconIndex = 0
131 | };
132 |
133 | int hr = SHGetSetFolderCustomSettings(ref fcs, folderPath, FCS_FORCEWRITE);
134 | Marshal.ThrowExceptionForHR(hr);
135 |
136 | var folderAttrs = File.GetAttributes(folderPath);
137 | SetFileAttributes(folderPath, folderAttrs | FileAttributes.ReadOnly);
138 |
139 | NotifyExplorerOfUpdate(folderPath);
140 | }
141 |
142 | ///
143 | /// 异步为单个文件夹设置图标,采用多种策略确保立即刷新
144 | ///
145 | /// 目标文件夹的完整路径
146 | /// 图标文件的完整路径 (.ico)
147 | public static Task SetFolderIconAsync(string folderPath, string iconPath)
148 | {
149 | return Task.Run(() =>
150 | {
151 | SetFolderIcon(folderPath, iconPath);
152 | });
153 | }
154 |
155 | ///
156 | /// 删除/重置单个文件夹的自定义图标
157 | ///
158 | /// 目标文件夹路径
159 | public static void ClearFolderIcon(string folderPath)
160 | {
161 | if (string.IsNullOrWhiteSpace(folderPath) || !Directory.Exists(folderPath))
162 | return;
163 |
164 | // 1. 取消文件夹的只读属性,以便删除文件
165 | var folderAttrs = File.GetAttributes(folderPath);
166 | if (folderAttrs.HasFlag(FileAttributes.ReadOnly))
167 | {
168 | SetFileAttributes(folderPath, folderAttrs & ~FileAttributes.ReadOnly);
169 | }
170 |
171 | // 2. 清理旧的图标文件
172 | ClearOldIconFiles(folderPath);
173 |
174 | // 3. 删除 desktop.ini 文件
175 | string iniPath = Path.Combine(folderPath, "desktop.ini");
176 | if (File.Exists(iniPath))
177 | {
178 | SetFileAttributes(iniPath, FileAttributes.Normal);
179 | File.Delete(iniPath);
180 | }
181 |
182 | // 4. 通知系统更新
183 | NotifyExplorerOfUpdate(folderPath);
184 | }
185 |
186 | ///
187 | /// 异步删除/重置单个文件夹的自定义图标
188 | ///
189 | /// 目标文件夹路径
190 | public static Task ClearFolderIconAsync(string folderPath)
191 | {
192 | return Task.Run(() => {
193 | ClearFolderIcon(folderPath);
194 | });
195 | }
196 |
197 | ///
198 | /// 递归地为指定文件夹下的所有子文件夹应用图标
199 | ///
200 | public static int ApplyIconToAllSubfolders(string rootFolderPath, string iconPath)
201 | {
202 | if (!Directory.Exists(rootFolderPath)) return 0;
203 |
204 | int applied = 0;
205 | foreach (var sub in Directory.EnumerateDirectories(rootFolderPath, "*", SearchOption.AllDirectories))
206 | {
207 | try
208 | {
209 | SetFolderIcon(sub, iconPath);
210 | applied++;
211 | }
212 | catch (Exception ex)
213 | {
214 | // Log or handle individual failures
215 | Debug.WriteLine($"Failed to set icon for {sub}: {ex.Message}");
216 | }
217 | }
218 | return applied;
219 | }
220 |
221 | ///
222 | /// 异步递归地为指定文件夹下的所有子文件夹应用图标
223 | ///
224 | public static Task ApplyIconToAllSubfoldersAsync(string rootFolderPath, string iconPath)
225 | {
226 | return Task.Run(() => {
227 | return ApplyIconToAllSubfolders(rootFolderPath, iconPath);
228 | });
229 | }
230 |
231 | ///
232 | /// 递归地清除指定文件夹及其所有子文件夹的自定义图标
233 | ///
234 | public static int ClearIconRecursively(string rootFolderPath)
235 | {
236 | if (!Directory.Exists(rootFolderPath)) return 0;
237 | int count = 0;
238 |
239 | var allDirs = Directory.EnumerateDirectories(rootFolderPath, "*", SearchOption.AllDirectories);
240 |
241 | foreach (var dir in allDirs)
242 | {
243 | try { ClearFolderIcon(dir); count++; } catch { /* ignore */ }
244 | }
245 | // 也清理根目录自身
246 | try { ClearFolderIcon(rootFolderPath); count++; } catch { /* ignore */ }
247 |
248 | return count;
249 | }
250 |
251 | ///
252 | /// 异步递归地清除指定文件夹及其所有子文件夹的自定义图标
253 | ///
254 | public static Task ClearIconRecursivelyAsync(string rootFolderPath)
255 | {
256 | return Task.Run(() => {
257 | return ClearIconRecursively(rootFolderPath);
258 | });
259 | }
260 |
261 | ///
262 | /// 多层次的通知以确保 Explorer 图标被刷新
263 | ///
264 | private static void NotifyExplorerOfUpdate(string path)
265 | {
266 | IntPtr pidl = IntPtr.Zero;
267 | try
268 | {
269 | pidl = ILCreateFromPath(path);
270 | if (pidl == IntPtr.Zero) return;
271 |
272 | // 1.SHCNF_IDLIST | SHCNF_FLUSH 发送同步通知
273 | const uint flags = SHCNF_IDLIST | SHCNF_FLUSH;
274 | SHChangeNotify(SHCNE_UPDATEITEM, flags, pidl, IntPtr.Zero);
275 | SHChangeNotify(SHCNE_UPDATEDIR, flags, pidl, IntPtr.Zero);
276 | SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST | SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
277 | }
278 | finally
279 | {
280 | if (pidl != IntPtr.Zero)
281 | {
282 | CoTaskMemFree(pidl);
283 | }
284 | }
285 |
286 | // 2.调用 ie4uinit.exe -show
287 | RefreshIconCacheViaIe4uinit();
288 |
289 | // 3.广播 WM_SETTINGCHANGE 消息,强制所有程序重载系统图标
290 | SendMessageTimeout(
291 | HWND_BROADCAST,
292 | WM_SETTINGCHANGE,
293 | (IntPtr)SPI_SETICONS,
294 | IntPtr.Zero,
295 | SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
296 | 1000,
297 | out _);
298 | }
299 |
300 | ///
301 | /// 删除文件夹内所有先前生成的图标文件
302 | ///
303 | private static void ClearOldIconFiles(string folderPath)
304 | {
305 | try
306 | {
307 | var oldIcons = Directory.EnumerateFiles(folderPath, "folder_*.ico");
308 | foreach (var oldIcon in oldIcons)
309 | {
310 | SetFileAttributes(oldIcon, FileAttributes.Normal);
311 | File.Delete(oldIcon);
312 | }
313 | // Also handle the legacy "folder.ico"
314 | string legacyIconPath = Path.Combine(folderPath, "folder.ico");
315 | if (File.Exists(legacyIconPath))
316 | {
317 | SetFileAttributes(legacyIconPath, FileAttributes.Normal);
318 | File.Delete(legacyIconPath);
319 | }
320 | }
321 | catch (Exception ex)
322 | {
323 | Debug.WriteLine($"Error clearing old icon files in {folderPath}: {ex.Message}");
324 | }
325 | }
326 |
327 | ///
328 | /// 调用 ie4uinit.exe 来强制刷新图标缓存
329 | ///
330 | private static void RefreshIconCacheViaIe4uinit()
331 | {
332 | try
333 | {
334 | var startInfo = new ProcessStartInfo
335 | {
336 | FileName = "ie4uinit.exe",
337 | // For Win 10/11, use -show. For older systems, -ClearIconCache might be used.
338 | Arguments = "-show",
339 | CreateNoWindow = true,
340 | UseShellExecute = false,
341 | WindowStyle = ProcessWindowStyle.Hidden
342 | };
343 | Process.Start(startInfo)?.WaitForExit(1000);
344 | }
345 | catch (Exception ex)
346 | {
347 | // This can fail if the exe is not found or permissions are insufficient.
348 | // It's an optional step, so we just log the error and continue.
349 | Debug.WriteLine($"Failed to run ie4uinit.exe: {ex.Message}");
350 | }
351 | }
352 | }
353 | }
354 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Strings/en-US/Resources.resw:
--------------------------------------------------------------------------------
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 | About
122 |
123 |
124 | Applying icons
125 |
126 |
127 | Help
128 |
129 |
130 | icon
131 |
132 |
133 | Search for icons or folders.
134 |
135 |
136 | Load Failed
137 |
138 |
139 | Loding...
140 |
141 |
142 | OK
143 |
144 |
145 | Result
146 |
147 |
148 | No results found.
149 |
150 |
151 | Setting
152 |
153 |
154 | Select the root folder.
155 |
156 |
157 | Browse and select the folder.
158 |
159 |
160 | Folder has not been selected yet.
161 |
162 |
163 | Successfully applied to
164 |
165 |
166 | folders
167 |
168 |
169 | Folder-Icon-Change
170 |
171 |
172 | use
173 |
174 |
175 | Success
176 |
177 |
178 | Failed
179 |
180 |
181 | Please select a folder
182 |
183 |
184 | Please select a folder
185 |
186 |
187 | Select a folder from the left navigation bar, or drag and drop a folder onto the icon
188 |
189 |
190 | Select a folder from the left navigation bar, or drag and drop a folder onto the icon
191 |
192 |
193 | No icon selected.
194 |
195 |
196 | No icon selected.
197 |
198 |
199 | Apply to folder
200 |
201 |
202 | Apply to all subfolders
203 |
204 |
205 | Reset icon
206 |
207 |
208 | Reset all
209 |
210 |
211 | Selected
212 |
213 |
214 | No icon selected
215 |
216 |
217 | Done
218 |
219 |
220 | Applied to the selected folder(s).
221 |
222 |
223 | Note
224 |
225 |
226 | Please select a target folder from the left navigation.
227 |
228 |
229 | The folder's icon has been cleared.
230 |
231 |
232 | Icons applied to
233 |
234 |
235 | subfolders.
236 |
237 |
238 | Please select a target folder first.
239 |
240 |
241 | Icons cleared for
242 |
243 |
244 | folders (including subfolders).
245 |
246 |
247 | Please click to select an icon first.
248 |
249 |
250 | Please select a folder from the left navigation first.
251 |
252 |
253 | Release to apply
254 |
255 |
256 | Settings
257 |
258 |
259 | Customize your app experience
260 |
261 |
262 | Language
263 |
264 |
265 | Choose the display language for the application
266 |
267 |
268 | English
269 |
270 |
271 | 简体中文
272 |
273 |
274 | Restart required
275 |
276 |
277 | Please restart the application to apply the language change.
278 |
279 |
280 | Appearance
281 |
282 |
283 | App Theme
284 |
285 |
286 | Light
287 |
288 |
289 | Dark
290 |
291 |
292 | Use system setting
293 |
294 |
295 | Window Backdrop
296 |
297 |
298 | Choose the visual effect for the window background
299 |
300 |
301 | Mica
302 |
303 |
304 | Subtle transparency that adapts to desktop wallpaper
305 |
306 |
307 | Acrylic
308 |
309 |
310 | Blurred transparency effect
311 |
312 |
313 | None
314 |
315 |
316 | No transparency effect
317 |
318 |
319 | Icon Pack
320 |
321 |
322 | Manage the icon pack from the GitHub repository
323 |
324 |
325 | Check for Updates
326 |
327 |
328 | About
329 |
330 |
331 | Change Folder Icon
332 |
333 |
334 | Version 1.0.1
335 |
336 |
337 | View on GitHub
338 |
339 |
340 | Checking for updates...
341 |
342 |
343 | Downloading icon pack...
344 |
345 |
346 | Extracting files...
347 |
348 |
349 | Icon pack is up to date
350 |
351 |
352 | Icon pack updated successfully
353 |
354 |
355 | Error updating icon pack
356 |
357 |
358 | No internet connection
359 |
360 |
361 | Failed to load the icon pack. Please update it in settings or manually select the folder.
362 |
363 |
364 | No icon pack found.
365 |
366 |
367 | Go to Settings Page
368 |
369 |
370 | Manually select icon pack folder
371 |
372 |
373 | Not Set
374 |
375 |
376 | Current Path
377 |
378 |
379 | Icon pack folder path
380 |
381 |
382 | Check for Updates
383 |
384 |
385 | Appearance & behavior
386 |
387 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Strings/zh-CN/Resources.resw:
--------------------------------------------------------------------------------
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 | 侧边栏关于按钮
123 |
124 |
125 | 正在应用图标
126 | 应用图标时的提示
127 |
128 |
129 | 帮助
130 | 侧边栏帮助按钮
131 |
132 |
133 | 图标
134 | 图标
135 |
136 |
137 | 搜索图标或文件夹
138 | 标题搜索框提示词
139 |
140 |
141 | 加载失败
142 | 加载失败提示语
143 |
144 |
145 | 加载中...
146 | 加载中的提示
147 |
148 |
149 | 确定
150 | 确定
151 |
152 |
153 | 应用结果
154 | 应用结果的弹窗
155 |
156 |
157 | 未找到结果
158 | 搜索框无内容时提示
159 |
160 |
161 | 设置
162 | 设置按钮的ToolTip
163 |
164 |
165 | 选择根文件夹
166 | 侧边栏选择文件夹按钮
167 |
168 |
169 | 浏览并选择文件夹
170 | 侧边栏选择文件夹按钮
171 |
172 |
173 | 尚未选择文件夹
174 | 侧边栏提示
175 |
176 |
177 | 成功应用到
178 | 成功应用提示词前部分
179 |
180 |
181 | 个文件夹
182 | 成功应用提示词后半部分
183 |
184 |
185 | Folder-Icon-Change
186 | 程序标题
187 |
188 |
189 | 使用
190 | 使用
191 |
192 |
193 | 成功
194 | 成功
195 |
196 |
197 | 失败
198 | 失败
199 |
200 |
201 | 请选择文件夹
202 | IconPage页的头部提示词
203 |
204 |
205 | 请选择文件夹
206 | IconPage页的头部提示词
207 |
208 |
209 | 从左侧导航栏选择文件夹,或直接拖拽文件夹到图标上
210 | IconPage页的头部详细说明文本
211 |
212 |
213 | 从左侧导航栏选择文件夹,或直接拖拽文件夹到图标上
214 | IconPage页的头部详细说明文本
215 |
216 |
217 | 未选择图标
218 | IconPage页的底部提示文本
219 |
220 |
221 | 未选择图标
222 | IconPage页的底部提示文本
223 |
224 |
225 | 应用到文件夹
226 | IconPage页下方按钮文本
227 |
228 |
229 | 应用到所有子文件夹
230 | IconPage页下方按钮文本
231 |
232 |
233 | 重置图标
234 | IconPage页下方按钮文本
235 |
236 |
237 | 重置所有
238 | IconPage页下方按钮文本
239 |
240 |
241 | 已选择
242 | 已选择
243 |
244 |
245 | 未选择图标
246 | 未选择图标
247 |
248 |
249 | 完成
250 | 完成
251 |
252 |
253 | 已应用到选中文件夹。
254 | 提示文本
255 |
256 |
257 | 提示
258 | 提示
259 |
260 |
261 | 请在左侧导航中选择一个目标文件夹。
262 | 提示文本
263 |
264 |
265 | 已清除该文件夹的图标。
266 | 提示文本
267 |
268 |
269 | 已为
270 | 文件夹清除图标提示前半段
271 |
272 |
273 | 个子文件夹应用图标。
274 | 文件夹清除图标提示后半段
275 |
276 |
277 | 请先选择目标文件夹。
278 | 提示文本
279 |
280 |
281 | 已清除
282 | 子文件夹清除提示词前半段
283 |
284 |
285 | 个文件夹(包括子文件夹)的图标。
286 | 子文件夹清除后半段
287 |
288 |
289 | 请先点击选择一个图标。
290 | 提示文本
291 |
292 |
293 | 请先在左侧导航中选择一个文件夹。
294 | 提示文本
295 |
296 |
297 | 释放以应用
298 | 图标控件拖入文件夹后提示词
299 |
300 |
301 | 设置
302 | 设置页面标题
303 |
304 |
305 | 自定义您的应用体验
306 | 设置页面副标题
307 |
308 |
309 | 语言
310 | 语言设置标题
311 |
312 |
313 | 选择应用程序的显示语言
314 | 语言设置描述
315 |
316 |
317 | English
318 | 英语选项
319 |
320 |
321 | 简体中文
322 | 中文选项
323 |
324 |
325 | 需要重启
326 | 重启提示标题
327 |
328 |
329 | 请重启应用程序以应用语言更改。
330 | 重启提示消息
331 |
332 |
333 | 外观
334 | 外观设置标题
335 |
336 |
337 | 应用主题
338 | 主题设置标题
339 |
340 |
341 | 浅色
342 | 浅色主题选项
343 |
344 |
345 | 深色
346 | 深色主题选项
347 |
348 |
349 | 使用系统设置
350 | 跟随系统主题选项
351 |
352 |
353 | 窗口背景效果
354 | 背景效果标题
355 |
356 |
357 | 选择窗口背景的视觉效果
358 | 背景效果描述
359 |
360 |
361 | 云母效果
362 | 云母效果选项
363 |
364 |
365 | 根据桌面壁纸自适应的细微透明效果
366 | 云母效果描述
367 |
368 |
369 | 亚克力效果
370 | 亚克力效果选项
371 |
372 |
373 | 模糊透明效果
374 | 亚克力效果描述
375 |
376 |
377 | 无效果
378 | 无背景效果选项
379 |
380 |
381 | 不使用透明效果
382 | 无背景效果描述
383 |
384 |
385 | 图标包
386 | 图标包设置标题
387 |
388 |
389 | 从 GitHub 仓库管理图标包
390 | 图标包设置描述
391 |
392 |
393 | 检查更新
394 | 检查更新按钮文本
395 |
396 |
397 | 关于
398 | 关于部分标题
399 |
400 |
401 | Change Folder Icon
402 | 应用程序名称
403 |
404 |
405 | 版本 1.0.1
406 | Version 1.0.1
407 |
408 |
409 | 在 GitHub 上查看
410 | GitHub链接文本
411 |
412 |
413 | 正在检查更新...
414 | 检查更新状态
415 |
416 |
417 | 正在下载图标包...
418 | 下载状态
419 |
420 |
421 | 正在解压文件...
422 | 解压状态
423 |
424 |
425 | 图标包已是最新版本
426 | 已是最新状态
427 |
428 |
429 | 图标包更新成功
430 | 更新成功状态
431 |
432 |
433 | 更新图标包时出错
434 | 更新错误状态
435 |
436 |
437 | 无网络连接
438 | 无网络连接状态
439 |
440 |
441 | 无法加载图标包。请在设置中更新或手动选择文件夹。
442 | 缺失图标包提示文本
443 |
444 |
445 | 未找到图标包
446 | 缺失图标提示标题
447 |
448 |
449 | 前往设置
450 | 前往设置按钮文本
451 |
452 |
453 | 手动选择图标包路径
454 | 手动设置按钮文本
455 |
456 |
457 | 未设置
458 | 未设置
459 |
460 |
461 | 当前路径
462 | 当前路径
463 |
464 |
465 | 图标包文件夹路径
466 | 图标包文件夹路径
467 |
468 |
469 | 检查更新
470 | 检查更新按钮文本
471 |
472 |
473 | 外观与行为
474 | 外观与行为分类
475 |
476 |
--------------------------------------------------------------------------------
/ChangeFolderIcon/Pages/SettingsPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
42 |
43 |
44 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
65 |
68 |
72 |
73 |
74 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
97 |
98 |
99 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
116 |
117 |
120 |
123 |
126 |
127 |
128 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
163 |
164 |
167 |
170 |
174 |
175 |
176 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
211 |
212 |
215 |
218 |
222 |
223 |
224 |
228 |
233 |
237 |
238 |
239 |
240 |
241 |
242 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
259 |
260 |
263 |
266 |
270 |
271 |
272 |
277 |
278 |
279 |
280 |
281 |
286 |
287 |
288 |
291 |
292 |
293 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
312 |
315 |
316 |
317 |
320 |
323 |
326 |
327 |
328 |
331 |
336 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
--------------------------------------------------------------------------------