├── DewUSB
├── ud.ico
├── ud.png
├── handle.exe
├── uddark.png
├── udlight.png
├── Properties
│ ├── Settings.settings
│ ├── Settings.Designer.cs
│ ├── Resources.Designer.cs
│ ├── app.manifest
│ └── Resources.resx
├── App.xaml.cs
├── App.config
├── AboutWindow.xaml.cs
├── MultiplyConverter.cs
├── App.xaml
├── packages.config
├── Settings.cs
├── AboutWindow.xaml
├── ProcessInfoWindow.xaml
├── DewUSB.csproj
├── MainWindow.xaml
├── SettingsWindow.xaml.cs
├── SettingsWindow.xaml
├── ProcessInfoWindow.xaml.cs
└── MainWindow.xaml.cs
├── art
└── dark.jpg
├── LICENSE
├── DewUSB.sln
├── README.md
└── .gitignore
/DewUSB/ud.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starry-source/DewUSB/HEAD/DewUSB/ud.ico
--------------------------------------------------------------------------------
/DewUSB/ud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starry-source/DewUSB/HEAD/DewUSB/ud.png
--------------------------------------------------------------------------------
/art/dark.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starry-source/DewUSB/HEAD/art/dark.jpg
--------------------------------------------------------------------------------
/DewUSB/handle.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starry-source/DewUSB/HEAD/DewUSB/handle.exe
--------------------------------------------------------------------------------
/DewUSB/uddark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starry-source/DewUSB/HEAD/DewUSB/uddark.png
--------------------------------------------------------------------------------
/DewUSB/udlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starry-source/DewUSB/HEAD/DewUSB/udlight.png
--------------------------------------------------------------------------------
/DewUSB/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/DewUSB/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace DewUSB
10 | {
11 | ///
12 | /// App.xaml 的交互逻辑
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/DewUSB/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/DewUSB/AboutWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Windows;
3 | using Wpf.Ui.Controls;
4 |
5 | namespace DewUSB
6 | {
7 | ///
8 | /// 关于窗口的交互逻辑,显示应用程序信息和项目链接
9 | ///
10 | public partial class AboutWindow : FluentWindow
11 | {
12 | ///
13 | /// 初始化关于窗口
14 | ///
15 | public AboutWindow()
16 | {
17 | InitializeComponent();
18 |
19 | // 注册访问项目按钮的点击事件
20 | // 点击时在默认浏览器中打开项目地址
21 | VisitProjectButton.Click += (s, e) =>
22 | {
23 | Process.Start("https://github.com/starry-source/DewUSB");
24 | };
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/DewUSB/MultiplyConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace DewUSB
6 | {
7 | public class MultiplyConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | if (value is double number && parameter is string multiplier)
12 | {
13 | if (double.TryParse(multiplier, out double factor))
14 | {
15 | return number * factor;
16 | }
17 | }
18 | return value;
19 | }
20 |
21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
22 | {
23 | throw new NotImplementedException();
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/DewUSB/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | pack://application:,,,/;component/Fonts/#Segoe Fluent Icons
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/DewUSB/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Starry Source
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/DewUSB.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.14.36301.6 d17.14
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DewUSB", "DewUSB\DewUSB.csproj", "{67B5AEA6-72D6-4727-9516-EA37330A2500}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {67B5AEA6-72D6-4727-9516-EA37330A2500}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {67B5AEA6-72D6-4727-9516-EA37330A2500}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {67B5AEA6-72D6-4727-9516-EA37330A2500}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {67B5AEA6-72D6-4727-9516-EA37330A2500}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {5715E2BA-783B-4266-A5E7-C07C90E735B4}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/DewUSB/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace DewUSB.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
DewUSB
7 | 简约的U盘助手
8 | 由星源开发 · Developed by Starry Source
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ## 简介
26 | 一个简约美观的U盘助手
27 | 
28 | (界面有变化,请以实际为准)
29 | ## 正常使用
30 |
31 | - 在右侧 Releases 中下载最新版本的压缩包(`.rar` 文件)。
32 | - 解压到你喜欢的位置,双击 `DewUSB.exe` 即可运行。
33 |
34 | ## 代码部署
35 |
36 | 克隆存储卡,双击 `DewUSB.sln` 打开 Visual Studio,配置好依赖即可。
37 |
38 | ## 开发环境
39 |
40 | Windows 11 24h2\
41 | .NET 8
--------------------------------------------------------------------------------
/DewUSB/Settings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text.Json;
4 |
5 | namespace DewUSB
6 | {
7 | public class Settings
8 | {
9 | public bool StartWithWindows { get; set; }= false; // 默认不开机自启
10 | public bool MinimizeToTray { get; set; }= true; // 默认最小化到托盘
11 | public int Theme { get; set; } = 2;
12 |
13 | public bool TopMost { get; set; } = false; // 默认不设置为置顶
14 |
15 | public int Position { get; set; } = 2; // 默认右下
16 | public int IconType { get; set; } = 0; // 默认彩色
17 |
18 | public bool ShowOpen { get; set; } = true; // 显示打开按钮
19 |
20 | public int WindowWidth { get; set; } = 400; // 默认窗口宽度
21 |
22 | private static readonly string SettingsPath = Path.Combine(
23 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
24 | "DewUSB",
25 | "settings.json"
26 | );
27 |
28 | public static Settings Load()
29 | {
30 | try
31 | {
32 | var directory = Path.GetDirectoryName(SettingsPath);
33 | if (!Directory.Exists(directory))
34 | {
35 | Directory.CreateDirectory(directory);
36 | }
37 |
38 | if (File.Exists(SettingsPath))
39 | {
40 | var json = File.ReadAllText(SettingsPath);
41 | return JsonSerializer.Deserialize(json) ?? new Settings();
42 | }
43 | }
44 | catch { }
45 |
46 | return new Settings();
47 | }
48 |
49 | public void Save()
50 | {
51 | try
52 | {
53 | var directory = Path.GetDirectoryName(SettingsPath);
54 | if (!Directory.Exists(directory))
55 | {
56 | Directory.CreateDirectory(directory);
57 | }
58 |
59 | var json = JsonSerializer.Serialize(this);
60 | File.WriteAllText(SettingsPath, json);
61 | }
62 | catch { }
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/DewUSB/AboutWindow.xaml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
33 |
34 |
38 |
39 |
43 |
44 |
48 |
49 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/DewUSB/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本: 4.0.30319.42000
5 | //
6 | // 对此文件的更改可能导致不正确的行为,如果
7 | // 重新生成代码,则所做更改将丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace DewUSB.Properties
12 | {
13 |
14 |
15 | ///
16 | /// 强类型资源类,用于查找本地化字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// 返回此类使用的缓存 ResourceManager 实例。
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DewUSB.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// 重写当前线程的 CurrentUICulture 属性,对
56 | /// 使用此强类型资源类的所有资源查找执行重写。
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/DewUSB/ProcessInfoWindow.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 如果这个能正常功能工作,请务必告诉我!这对我很重要。
25 |
26 |
27 |
28 |
29 |
33 |
36 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/DewUSB/Properties/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
50 |
58 |
59 |
73 |
--------------------------------------------------------------------------------
/DewUSB/DewUSB.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0-windows
4 | WinExe
5 | false
6 | E:\vsot\DewUSB\ass\pack\
7 | true
8 | Disk
9 | false
10 | Foreground
11 | 7
12 | Days
13 | false
14 | false
15 | true
16 | 0
17 | 0.0.0.0
18 | false
19 | true
20 | true
21 | true
22 | true
23 | true
24 | E5FA16F37172125FADF8CCE0E29BF7C50D7AE2FD
25 | DewUSB_TemporaryKey.pfx
26 | false
27 | false
28 | ud.ico
29 | LocalIntranet
30 | Properties\app.manifest
31 |
32 |
33 |
34 |
35 |
36 |
37 | MSBuild:Compile
38 |
39 |
40 | MSBuild:Compile
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Always
51 |
52 |
53 | Always
54 |
55 |
56 |
57 |
58 | False
59 | Microsoft .NET Framework 4.7.2 %28x86 和 x64%29
60 | true
61 |
62 |
63 | False
64 | .NET Framework 3.5 SP1
65 | false
66 |
67 |
68 |
69 |
70 | Never
71 |
72 |
73 |
74 |
75 | Always
76 |
77 |
78 | Always
79 |
80 |
81 |
--------------------------------------------------------------------------------
/DewUSB/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
93 |
94 |
95 |
96 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/DewUSB/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/DewUSB/SettingsWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using DewUSB.Properties;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Windows;
5 | using System.Windows.Forms;
6 | using System.Windows.Media.TextFormatting;
7 | using Wpf.Ui;
8 | using Wpf.Ui.Controls;
9 | using static System.Windows.Forms.VisualStyles.VisualStyleElement;
10 |
11 | namespace DewUSB
12 | {
13 | ///
14 | /// 设置窗口的交互逻辑,处理应用程序设置的显示和保存
15 | ///
16 | public partial class SettingsWindow : FluentWindow
17 | {
18 | ///
19 | /// 当前应用程序设置的引用
20 | ///
21 | private readonly Settings settings;
22 |
23 | ///
24 | /// 初始化设置窗口
25 | ///
26 | /// 当前应用程序的设置实例
27 | public SettingsWindow(Settings currentSettings)
28 | {
29 | InitializeComponent();
30 | Topmost = false;
31 | settings = currentSettings;
32 |
33 | // 根据当前设置更新UI控件状态
34 | StartWithWindows.IsChecked = settings.StartWithWindows;
35 | MinimizeToTray.IsChecked = settings.MinimizeToTray;
36 |
37 | ThemeSelector.ItemsSource = new List{
38 | "深色",
39 | "浅色",
40 | "跟随系统"
41 | };
42 |
43 | ThemeSelector.SelectedIndex = settings.Theme;
44 |
45 |
46 | TopMost.IsChecked = settings.TopMost;
47 |
48 | Position.ItemsSource = new List{
49 | "左上角",
50 | "左下角",
51 | "右下角",
52 | "居中"
53 | };
54 | Position.SelectedIndex = settings.Position;
55 |
56 | IconType0.IsChecked = settings.IconType == 0; // 彩色图标
57 | IconType1.IsChecked = settings.IconType == 1; // 灰色图标
58 |
59 | WindowWidth.Value = settings.WindowWidth;
60 |
61 | ShowOpen.IsChecked = settings.ShowOpen;
62 | //StartWithWindows.Checked += change;
63 | //StartWithWindows.Unchecked += change;
64 | //MinimizeToTray.Checked += change;
65 | //MinimizeToTray.Unchecked += change;
66 | ThemeSelector.SelectionChanged += change;
67 | //TopMost.Checked += change;
68 | //TopMost.Unchecked += change;
69 | //Position.SelectionChanged += change;
70 | //IconType0.Checked += change;
71 | //IconType1.Checked += change;
72 | //WindowWidth.ValueChanged += change;
73 | //ShowOpen.Checked += change;
74 | }
75 |
76 | ///
77 | /// 处理设置保存操作
78 | ///
79 | /// 事件源
80 | /// 事件参数
81 | private void change(object sender, RoutedEventArgs e)
82 | {
83 |
84 | if (ThemeSelector.SelectedIndex != settings.Theme)
85 | {
86 | Tip.Text = "部分设置需要重启程序才能生效。";
87 | Tip.Opacity = 1;
88 | CloseButton.Content = "暂不";
89 | RestartButton.Visibility = Visibility.Visible;
90 | }
91 | else
92 | {
93 | Tip.Text = "关闭设置后,更改才能生效。";
94 | Tip.Opacity = 0.5;
95 | CloseButton.Content = "关闭";
96 | RestartButton.Visibility= Visibility.Collapsed;
97 |
98 | }
99 | }
100 |
101 | private void apply()
102 | {
103 | settings.StartWithWindows = (bool)StartWithWindows.IsChecked;
104 | settings.MinimizeToTray = (bool)MinimizeToTray.IsChecked;
105 | settings.Theme = ThemeSelector.SelectedIndex;
106 | settings.TopMost = (bool)TopMost.IsChecked;
107 | settings.Position = Position.SelectedIndex;
108 | settings.IconType = IconType0.IsChecked == true ? 0 : 1;
109 | settings.WindowWidth = (int)WindowWidth.Value;
110 | settings.ShowOpen = (bool)ShowOpen.IsChecked;
111 |
112 | settings.Save();
113 | }
114 |
115 | private void CloseButton_Click(object sender, RoutedEventArgs e)
116 | {
117 | // 取消设置,直接关闭窗口
118 | apply();
119 | Close();
120 | }
121 |
122 | private void RestartButton_Click(object sender, RoutedEventArgs e)
123 | {
124 | // 重启应用程序
125 | apply();
126 | System.Diagnostics.Process.Start(AppDomain.CurrentDomain.FriendlyName);
127 | System.Windows.Application.Current.Shutdown();
128 | }
129 |
130 | private void Button_Click(object sender, RoutedEventArgs e)
131 | {
132 | // 引导用户关闭自动播放通知
133 | Hide();
134 | System.Diagnostics.Process.Start("ms-settings:autoplay");
135 | // wpfui message box
136 | var messageBox = new Wpf.Ui.Controls.MessageBox
137 | {
138 | Owner = this,
139 | Title = "操作引导",
140 | Content = new TextBlock
141 | {
142 | Text = "请将「可移动驱动器」默认行为改为「不执行操作」。\n这可以避免程序被遮挡或影响。\n设置完后,关闭此引导即可。",
143 | TextWrapping = TextWrapping.Wrap,
144 | FontSize = 15,
145 | },
146 | ShowInTaskbar = false,
147 | Topmost = true,
148 | WindowStartupLocation = WindowStartupLocation.CenterScreen,
149 | ResizeMode = ResizeMode.NoResize,
150 | };
151 | messageBox.ShowDialogAsync();
152 | Show();
153 | }
154 |
155 | private void IconType0_Checked(object sender, RoutedEventArgs e)
156 | {
157 | IconImage.Source = new System.Windows.Media.Imaging.BitmapImage(
158 | new Uri("pack://application:,,,/ud.png"));
159 | }
160 |
161 | private void IconType1_Checked(object sender, RoutedEventArgs e)
162 | {
163 | // 根据当前应用的颜色主题
164 | if (Wpf.Ui.Appearance.ApplicationThemeManager.GetAppTheme() == Wpf.Ui.Appearance.ApplicationTheme.Dark)
165 | IconImage.Source = new System.Windows.Media.Imaging.BitmapImage(
166 | new Uri("pack://application:,,,/uddark.png"));
167 | else
168 | IconImage.Source = new System.Windows.Media.Imaging.BitmapImage(
169 | new Uri("pack://application:,,,/udlight.png"));
170 | }
171 |
172 | private void Restore_WindowWidth_Click(object sender, RoutedEventArgs e)
173 | {
174 | // 恢复窗口宽度
175 | WindowWidth.Value = 400;
176 | }
177 |
178 | private void FluentWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
179 | {
180 | apply();
181 | }
182 | }
183 | }
--------------------------------------------------------------------------------
/DewUSB/SettingsWindow.xaml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 重置
60 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | U盘图标
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 其它功能需求、建议?
100 |
103 |
104 |
105 |
106 |
107 |
108 |
110 |
114 |
117 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 | *.env
13 |
14 | # User-specific files (MonoDevelop/Xamarin Studio)
15 | *.userprefs
16 |
17 | # Mono auto generated files
18 | mono_crash.*
19 |
20 | # Build results
21 | [Dd]ebug/
22 | [Dd]ebugPublic/
23 | [Rr]elease/
24 | [Rr]eleases/
25 | x64/
26 | x86/
27 | [Ww][Ii][Nn]32/
28 | [Aa][Rr][Mm]/
29 | [Aa][Rr][Mm]64/
30 | [Aa][Rr][Mm]64[Ee][Cc]/
31 | bld/
32 | [Oo]bj/
33 | [Oo]ut/
34 | [Ll]og/
35 | [Ll]ogs/
36 |
37 | # Build results on 'Bin' directories
38 | **/[Bb]in/*
39 | # Uncomment if you have tasks that rely on *.refresh files to move binaries
40 | # (https://github.com/github/gitignore/pull/3736)
41 | #!**/[Bb]in/*.refresh
42 |
43 | # Visual Studio 2015/2017 cache/options directory
44 | .vs/
45 | # Uncomment if you have tasks that create the project's static files in wwwroot
46 | #wwwroot/
47 |
48 | # Visual Studio 2017 auto generated files
49 | Generated\ Files/
50 |
51 | # MSTest test Results
52 | [Tt]est[Rr]esult*/
53 | [Bb]uild[Ll]og.*
54 | *.trx
55 |
56 | # NUnit
57 | *.VisualState.xml
58 | TestResult.xml
59 | nunit-*.xml
60 |
61 | # Approval Tests result files
62 | *.received.*
63 |
64 | # Build Results of an ATL Project
65 | [Dd]ebugPS/
66 | [Rr]eleasePS/
67 | dlldata.c
68 |
69 | # Benchmark Results
70 | BenchmarkDotNet.Artifacts/
71 |
72 | # .NET Core
73 | project.lock.json
74 | project.fragment.lock.json
75 | artifacts/
76 |
77 | # ASP.NET Scaffolding
78 | ScaffoldingReadMe.txt
79 |
80 | # StyleCop
81 | StyleCopReport.xml
82 |
83 | # Files built by Visual Studio
84 | *_i.c
85 | *_p.c
86 | *_h.h
87 | *.ilk
88 | *.meta
89 | *.obj
90 | *.idb
91 | *.iobj
92 | *.pch
93 | *.pdb
94 | *.ipdb
95 | *.pgc
96 | *.pgd
97 | *.rsp
98 | # but not Directory.Build.rsp, as it configures directory-level build defaults
99 | !Directory.Build.rsp
100 | *.sbr
101 | *.tlb
102 | *.tli
103 | *.tlh
104 | *.tmp
105 | *.tmp_proj
106 | *_wpftmp.csproj
107 | *.log
108 | *.tlog
109 | *.vspscc
110 | *.vssscc
111 | .builds
112 | *.pidb
113 | *.svclog
114 | *.scc
115 |
116 | # Chutzpah Test files
117 | _Chutzpah*
118 |
119 | # Visual C++ cache files
120 | ipch/
121 | *.aps
122 | *.ncb
123 | *.opendb
124 | *.opensdf
125 | *.sdf
126 | *.cachefile
127 | *.VC.db
128 | *.VC.VC.opendb
129 |
130 | # Visual Studio profiler
131 | *.psess
132 | *.vsp
133 | *.vspx
134 | *.sap
135 |
136 | # Visual Studio Trace Files
137 | *.e2e
138 |
139 | # TFS 2012 Local Workspace
140 | $tf/
141 |
142 | # Guidance Automation Toolkit
143 | *.gpState
144 |
145 | # ReSharper is a .NET coding add-in
146 | _ReSharper*/
147 | *.[Rr]e[Ss]harper
148 | *.DotSettings.user
149 |
150 | # TeamCity is a build add-in
151 | _TeamCity*
152 |
153 | # DotCover is a Code Coverage Tool
154 | *.dotCover
155 |
156 | # AxoCover is a Code Coverage Tool
157 | .axoCover/*
158 | !.axoCover/settings.json
159 |
160 | # Coverlet is a free, cross platform Code Coverage Tool
161 | coverage*.json
162 | coverage*.xml
163 | coverage*.info
164 |
165 | # Visual Studio code coverage results
166 | *.coverage
167 | *.coveragexml
168 |
169 | # NCrunch
170 | _NCrunch_*
171 | .NCrunch_*
172 | .*crunch*.local.xml
173 | nCrunchTemp_*
174 |
175 | # MightyMoose
176 | *.mm.*
177 | AutoTest.Net/
178 |
179 | # Web workbench (sass)
180 | .sass-cache/
181 |
182 | # Installshield output folder
183 | [Ee]xpress/
184 |
185 | # DocProject is a documentation generator add-in
186 | DocProject/buildhelp/
187 | DocProject/Help/*.HxT
188 | DocProject/Help/*.HxC
189 | DocProject/Help/*.hhc
190 | DocProject/Help/*.hhk
191 | DocProject/Help/*.hhp
192 | DocProject/Help/Html2
193 | DocProject/Help/html
194 |
195 | # Click-Once directory
196 | publish/
197 |
198 | # Publish Web Output
199 | *.[Pp]ublish.xml
200 | *.azurePubxml
201 | # Note: Comment the next line if you want to checkin your web deploy settings,
202 | # but database connection strings (with potential passwords) will be unencrypted
203 | *.pubxml
204 | *.publishproj
205 |
206 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
207 | # checkin your Azure Web App publish settings, but sensitive information contained
208 | # in these scripts will be unencrypted
209 | PublishScripts/
210 |
211 | # NuGet Packages
212 | *.nupkg
213 | # NuGet Symbol Packages
214 | *.snupkg
215 | # The packages folder can be ignored because of Package Restore
216 | **/[Pp]ackages/*
217 | # except build/, which is used as an MSBuild target.
218 | !**/[Pp]ackages/build/
219 | # Uncomment if necessary however generally it will be regenerated when needed
220 | #!**/[Pp]ackages/repositories.config
221 | # NuGet v3's project.json files produces more ignorable files
222 | *.nuget.props
223 | *.nuget.targets
224 |
225 | # Microsoft Azure Build Output
226 | csx/
227 | *.build.csdef
228 |
229 | # Microsoft Azure Emulator
230 | ecf/
231 | rcf/
232 |
233 | # Windows Store app package directories and files
234 | AppPackages/
235 | BundleArtifacts/
236 | Package.StoreAssociation.xml
237 | _pkginfo.txt
238 | *.appx
239 | *.appxbundle
240 | *.appxupload
241 |
242 | # Visual Studio cache files
243 | # files ending in .cache can be ignored
244 | *.[Cc]ache
245 | # but keep track of directories ending in .cache
246 | !?*.[Cc]ache/
247 |
248 | # Others
249 | ClientBin/
250 | ~$*
251 | *~
252 | *.dbmdl
253 | *.dbproj.schemaview
254 | *.jfm
255 | *.pfx
256 | *.publishsettings
257 | orleans.codegen.cs
258 |
259 | # Including strong name files can present a security risk
260 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
261 | #*.snk
262 |
263 | # Since there are multiple workflows, uncomment next line to ignore bower_components
264 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
265 | #bower_components/
266 |
267 | # RIA/Silverlight projects
268 | Generated_Code/
269 |
270 | # Backup & report files from converting an old project file
271 | # to a newer Visual Studio version. Backup files are not needed,
272 | # because we have git ;-)
273 | _UpgradeReport_Files/
274 | Backup*/
275 | UpgradeLog*.XML
276 | UpgradeLog*.htm
277 | ServiceFabricBackup/
278 | *.rptproj.bak
279 |
280 | # SQL Server files
281 | *.mdf
282 | *.ldf
283 | *.ndf
284 |
285 | # Business Intelligence projects
286 | *.rdl.data
287 | *.bim.layout
288 | *.bim_*.settings
289 | *.rptproj.rsuser
290 | *- [Bb]ackup.rdl
291 | *- [Bb]ackup ([0-9]).rdl
292 | *- [Bb]ackup ([0-9][0-9]).rdl
293 |
294 | # Microsoft Fakes
295 | FakesAssemblies/
296 |
297 | # GhostDoc plugin setting file
298 | *.GhostDoc.xml
299 |
300 | # Node.js Tools for Visual Studio
301 | .ntvs_analysis.dat
302 | node_modules/
303 |
304 | # Visual Studio 6 build log
305 | *.plg
306 |
307 | # Visual Studio 6 workspace options file
308 | *.opt
309 |
310 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
311 | *.vbw
312 |
313 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
314 | *.vbp
315 |
316 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
317 | *.dsw
318 | *.dsp
319 |
320 | # Visual Studio 6 technical files
321 | *.ncb
322 | *.aps
323 |
324 | # Visual Studio LightSwitch build output
325 | **/*.HTMLClient/GeneratedArtifacts
326 | **/*.DesktopClient/GeneratedArtifacts
327 | **/*.DesktopClient/ModelManifest.xml
328 | **/*.Server/GeneratedArtifacts
329 | **/*.Server/ModelManifest.xml
330 | _Pvt_Extensions
331 |
332 | # Paket dependency manager
333 | **/.paket/paket.exe
334 | paket-files/
335 |
336 | # FAKE - F# Make
337 | **/.fake/
338 |
339 | # CodeRush personal settings
340 | **/.cr/personal
341 |
342 | # Python Tools for Visual Studio (PTVS)
343 | **/__pycache__/
344 | *.pyc
345 |
346 | # Cake - Uncomment if you are using it
347 | #tools/**
348 | #!tools/packages.config
349 |
350 | # Tabs Studio
351 | *.tss
352 |
353 | # Telerik's JustMock configuration file
354 | *.jmconfig
355 |
356 | # BizTalk build output
357 | *.btp.cs
358 | *.btm.cs
359 | *.odx.cs
360 | *.xsd.cs
361 |
362 | # OpenCover UI analysis results
363 | OpenCover/
364 |
365 | # Azure Stream Analytics local run output
366 | ASALocalRun/
367 |
368 | # MSBuild Binary and Structured Log
369 | *.binlog
370 | MSBuild_Logs/
371 |
372 | # AWS SAM Build and Temporary Artifacts folder
373 | .aws-sam
374 |
375 | # NVidia Nsight GPU debugger configuration file
376 | *.nvuser
377 |
378 | # MFractors (Xamarin productivity tool) working folder
379 | **/.mfractor/
380 |
381 | # Local History for Visual Studio
382 | **/.localhistory/
383 |
384 | # Visual Studio History (VSHistory) files
385 | .vshistory/
386 |
387 | # BeatPulse healthcheck temp database
388 | healthchecksdb
389 |
390 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
391 | MigrationBackup/
392 |
393 | # Ionide (cross platform F# VS Code tools) working folder
394 | **/.ionide/
395 |
396 | # Fody - auto-generated XML schema
397 | FodyWeavers.xsd
398 |
399 | # VS Code files for those working on multiple tools
400 | .vscode/*
401 | !.vscode/settings.json
402 | !.vscode/tasks.json
403 | !.vscode/launch.json
404 | !.vscode/extensions.json
405 | !.vscode/*.code-snippets
406 |
407 | # Local History for Visual Studio Code
408 | .history/
409 |
410 | # Built Visual Studio Code Extensions
411 | *.vsix
412 |
413 | # Windows Installer files from build outputs
414 | *.cab
415 | *.msi
416 | *.msix
417 | *.msm
418 | *.msp
419 |
420 |
421 | /ass
--------------------------------------------------------------------------------
/DewUSB/ProcessInfoWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Management;
7 | using System.Reflection;
8 | using System.Runtime.InteropServices;
9 | using System.Text;
10 | using System.Text.RegularExpressions;
11 | using System.Windows;
12 | using System.Windows.Media;
13 | using Wpf.Ui.Controls;
14 |
15 | namespace DewUSB
16 | {
17 | public partial class ProcessInfoWindow : FluentWindow
18 | {
19 | private readonly string drivePath;
20 |
21 | public ProcessInfoWindow(string driveLetter)
22 | {
23 | InitializeComponent();
24 | drivePath = driveLetter;
25 | Title = $"进程管理 - {driveLetter}";
26 |
27 | Loaded += (s, e) => RefreshProcessList();
28 | }
29 |
30 | private void RefreshProcessList()
31 | {
32 | ProcessPanel.Children.Clear();
33 | var processes = GetProcessesUsingDrive();
34 |
35 | if (processes.Count == 0)
36 | {
37 | NoProcessText.Visibility = Visibility.Visible;
38 | return;
39 | }
40 |
41 | NoProcessText.Visibility = Visibility.Collapsed;
42 | foreach (var process in processes)
43 | {
44 | ProcessPanel.Children.Add(CreateProcessCard(process));
45 | }
46 | }
47 |
48 | private UIElement CreateProcessCard(Process process)
49 | {
50 | var card = new CardExpander
51 | {
52 | Header = process.ProcessName,
53 | Margin = new Thickness(0, 0, 0, 8),
54 | Padding = new Thickness(16)
55 | };
56 |
57 | var stackPanel = new System.Windows.Controls.StackPanel
58 | {
59 | Orientation = System.Windows.Controls.Orientation.Vertical
60 | };
61 |
62 | var pidText = new TextBlock
63 | {
64 | Text = $"进程ID: {process.Id}",
65 | Margin = new Thickness(0, 0, 0, 8)
66 | };
67 | stackPanel.Children.Add(pidText);
68 |
69 | try
70 | {
71 | var mainWindowText = new TextBlock
72 | {
73 | Text = $"窗口标题: {(string.IsNullOrEmpty(process.MainWindowTitle) ? "(无窗口)" : process.MainWindowTitle)}",
74 | Margin = new Thickness(0, 0, 0, 8)
75 | };
76 | stackPanel.Children.Add(mainWindowText);
77 | }
78 | catch { }
79 |
80 | var buttonsPanel = new System.Windows.Controls.StackPanel
81 | {
82 | Orientation = System.Windows.Controls.Orientation.Horizontal,
83 | Margin = new Thickness(0, 8, 0, 0)
84 | };
85 |
86 | var closeButton = new Button
87 | {
88 | Content = "关闭进程",
89 | Appearance = ControlAppearance.Secondary,
90 | Margin = new Thickness(0, 0, 8, 0)
91 | };
92 | closeButton.Click += (s, e) => CloseProcess(process);
93 |
94 | var killButton = new Button
95 | {
96 | Content = "强制结束",
97 | Appearance = ControlAppearance.Danger
98 | };
99 | var warningIcon = new SymbolIcon { Symbol = SymbolRegular.Warning24 };
100 | killButton.Icon = warningIcon;
101 | killButton.Click += (s, e) => KillProcess(process);
102 |
103 | buttonsPanel.Children.Add(closeButton);
104 | buttonsPanel.Children.Add(killButton);
105 |
106 | stackPanel.Children.Add(buttonsPanel);
107 | card.Content = stackPanel;
108 |
109 | return card;
110 | }
111 |
112 | private List GetProcessesUsingDrive()
113 | {
114 | var processList = new List();
115 | var pids = new HashSet();
116 | string resourceName = "DewUSB.handle.exe";
117 | string tempPath = Path.Combine(Path.GetTempPath(), "handle.exe");
118 |
119 | using (Stream resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
120 | using (FileStream file = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
121 | {
122 | resource.CopyTo(file);
123 | }
124 | var fileInfo = new FileInfo(tempPath);
125 | if (fileInfo.Length == 0)
126 | throw new Exception("释放的EXE文件大小为0,请检查资源名和嵌入方式。");
127 |
128 | // 构造命令参数
129 | string arguments = $"F: /accepteula";
130 | var startInfo = new ProcessStartInfo
131 | {
132 | FileName = tempPath,
133 | Arguments = arguments,
134 | UseShellExecute = false,
135 | RedirectStandardOutput = true,
136 | CreateNoWindow = true
137 | };
138 |
139 | using (var process = Process.Start(startInfo))
140 | {
141 | string output = process.StandardOutput.ReadToEnd();
142 | process.WaitForExit();
143 |
144 | // 示例行: notepad.exe pid: 1234 ...
145 | var matches = Regex.Matches(output, @"(?[^\s]+)\s+pid: (?\d+)", RegexOptions.IgnoreCase);
146 |
147 | foreach (Match match in matches)
148 | {
149 | int pid = int.Parse(match.Groups["pid"].Value);
150 | if (!pids.Contains(pid))
151 | {
152 | try
153 | {
154 | var proc = Process.GetProcessById(pid);
155 | processList.Add(proc);
156 | pids.Add(pid);
157 | }
158 | catch
159 | {
160 | // 进程可能已退出,忽略
161 | }
162 | }
163 | }
164 | }
165 |
166 | return processList;
167 | }
168 |
169 | //private List GetProcessesUsingDrive()
170 | //{
171 | // var processes = new HashSet();
172 |
173 | // try
174 | // {
175 | // // 使用WMI查询打开的文件
176 | // var scope = new ManagementScope("\\\\.\\root\\cimv2");
177 | // var query = new SelectQuery("SELECT * FROM Win32_Process");
178 |
179 | // using (var searcher = new ManagementObjectSearcher(scope, query))
180 | // {
181 | // foreach (ManagementObject process in searcher.Get())
182 | // {
183 | // try
184 | // {
185 | // var pid = Convert.ToInt32(process["ProcessId"]);
186 | // var proc = Process.GetProcessById(pid);
187 |
188 | // // 检查进程的所有模块是否使用了该驱动器
189 | // foreach (ProcessModule module in proc.Modules)
190 | // {
191 | // if (module.FileName?.StartsWith(drivePath, StringComparison.OrdinalIgnoreCase) == true)
192 | // {
193 | // processes.Add(proc);
194 | // break;
195 | // }
196 | // }
197 |
198 | // // 检查工作目录
199 | // var workingSet = process["WorkingSetSize"];
200 | // if (workingSet != null && proc.MainModule?.FileName?.StartsWith(drivePath, StringComparison.OrdinalIgnoreCase) == true)
201 | // {
202 | // processes.Add(proc);
203 | // }
204 | // }
205 | // catch { }
206 | // }
207 | // }
208 |
209 | // // 检查当前目录在U盘上的进程
210 | // var currentDriveProcesses = Process.GetProcesses().Where(p =>
211 | // {
212 | // try
213 | // {
214 | // return p.MainModule?.FileName?.StartsWith(drivePath, StringComparison.OrdinalIgnoreCase) == true ||
215 | // (p.MainWindowTitle?.Length > 0 &&
216 | // p.Modules.Cast().Any(m =>
217 | // m.FileName?.StartsWith(drivePath, StringComparison.OrdinalIgnoreCase) == true));
218 | // }
219 | // catch { return false; }
220 | // });
221 |
222 | // foreach (var proc in currentDriveProcesses)
223 | // {
224 | // processes.Add(proc);
225 | // }
226 | // }
227 | // catch (Exception ex)
228 | // {
229 | // var dialog = new Wpf.Ui.Controls.MessageBox
230 | // {
231 | // Title = "错误",
232 | // Content = $"无法获取进程信息: {ex.Message}",
233 | // PrimaryButtonText = "确定"
234 | // };
235 | // dialog.ShowDialogAsync();
236 | // }
237 |
238 | // return processes.ToList();
239 | //}
240 |
241 | private async void CloseProcess(Process process)
242 | {
243 | try
244 | {
245 | if (!process.HasExited && process.CloseMainWindow())
246 | {
247 | await process.WaitForExitAsync();
248 | }
249 | else if (!process.HasExited)
250 | {
251 | process.Kill(true);
252 | }
253 | RefreshProcessList();
254 | }
255 | catch (Exception ex)
256 | {
257 | var dialog = new Wpf.Ui.Controls.MessageBox
258 | {
259 | Title = "错误",
260 | Content = $"无法关闭进程: {ex.Message}",
261 | PrimaryButtonText = "确定"
262 | };
263 | await dialog.ShowDialogAsync();
264 | }
265 | }
266 |
267 | private async void KillProcess(Process process)
268 | {
269 | try
270 | {
271 | if (!process.HasExited)
272 | {
273 | process.Kill(true);
274 | }
275 | RefreshProcessList();
276 | }
277 | catch (Exception ex)
278 | {
279 | var dialog = new Wpf.Ui.Controls.MessageBox
280 | {
281 | Title = "错误",
282 | Content = $"无法强制结束进程: {ex.Message}",
283 | PrimaryButtonText = "确定"
284 | };
285 | await dialog.ShowDialogAsync();
286 | }
287 | }
288 |
289 | private void CloseButton_Click(object sender, RoutedEventArgs e)
290 | {
291 | Close();
292 | }
293 | }
294 | }
--------------------------------------------------------------------------------
/DewUSB/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Drawing;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Forms;
10 | using System.Windows.Interop;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Animation;
13 | using System.Windows.Threading;
14 | using Wpf.Ui;
15 | using Wpf.Ui.Controls;
16 | using Application = System.Windows.Application;
17 | using MessageBox = System.Windows.MessageBox;
18 |
19 | namespace DewUSB
20 | {
21 | ///
22 | /// 主窗口逻辑
23 | ///
24 | public partial class MainWindow : FluentWindow
25 | {
26 | private NotifyIcon tray;
27 | private readonly Dictionary deviceGrids = new Dictionary();
28 | private DispatcherTimer messageTimer;
29 | private ScrollViewer scrollViewer;
30 | private System.Windows.Controls.StackPanel devicesPanel;
31 | private Wpf.Ui.Controls.TextBlock messageText;
32 | private Settings settings;
33 |
34 | // Win32 API 常量
35 | private const int WM_NCHITTEST = 0x0084;
36 | private const int WM_MOVING = 0x0216;
37 | private const int HTCLIENT = 1;
38 |
39 | public MainWindow()
40 | {
41 | InitializeComponent();
42 | // 获取控件引用
43 | devicesPanel = (System.Windows.Controls.StackPanel)FindName("DevicesPanel");
44 | messageText = (Wpf.Ui.Controls.TextBlock)FindName("MessageText");
45 | scrollViewer = (ScrollViewer)FindName("MainScrollViewer");
46 | settings = Settings.Load();
47 | InitializeTrayIcon();
48 | SetupWindowTheme();
49 | InitializeMessageTimer();
50 | //SetupEventHandlers();
51 | if (settings.StartWithWindows)
52 | {
53 | SetStartup(true);
54 | }
55 | // 确保窗口完全加载后再设置位置
56 | Loaded += (s, e) =>
57 | {
58 | UpdateWindowPosition();
59 | RegisterForDeviceNotifications();
60 | UpdateDevices();
61 | };
62 |
63 | StateChanged += OnStateChanged;
64 |
65 | //UpdateDevices();
66 | // 初始启动时,如果没有U盘则隐藏窗口
67 | //Loaded += (s, e) =>
68 | //{
69 | // UpdateDevices();
70 | //};
71 | }
72 |
73 |
74 | ///
75 | /// 初始化托盘图标及菜单
76 | ///
77 | private void InitializeTrayIcon()
78 | {
79 | tray = new NotifyIcon
80 | {
81 | Icon = new Icon(System.Windows.Application.GetResourceStream(new Uri("pack://application:,,,/ud.ico")).Stream),
82 | Visible = true,
83 | Text = "DewUSB"
84 | };
85 |
86 | var contextMenu = new ContextMenuStrip();
87 | var settingsItem = new ToolStripMenuItem("设置");
88 | settingsItem.Click += (s, e) => ShowSettingsWindow();
89 | var aboutItem = new ToolStripMenuItem("关于");
90 | aboutItem.Click += (s, e) => ShowAboutWindow();
91 | var exitItem = new ToolStripMenuItem("退出");
92 | exitItem.Click += (s, e) =>
93 | {
94 | tray.Dispose();
95 | Application.Current.Shutdown();
96 | };
97 |
98 | contextMenu.Items.AddRange(new ToolStripItem[]
99 | {
100 | settingsItem,
101 | aboutItem,
102 | new ToolStripSeparator(),
103 | exitItem
104 | });
105 |
106 | tray.ContextMenuStrip = contextMenu;
107 | tray.MouseClick += (s, e) =>
108 | {
109 | if (e.Button == MouseButtons.Left)
110 | {
111 | if (deviceGrids.Count > 0)
112 | {
113 | Show();
114 | WindowState = WindowState.Normal;
115 | //UpdateDevices();
116 | UpdateWindowPosition();
117 | Activate();
118 | ShowWithFadeIn();
119 | }
120 | }
121 | };
122 | }
123 |
124 | ///
125 | /// 状态变化时处理最小化到托盘
126 | ///
127 | private void OnStateChanged(object sender, EventArgs e)
128 | {
129 | if (WindowState == WindowState.Minimized)
130 | {
131 | //if (settings.MinimizeToTray)
132 | //{
133 |
134 | Hide();
135 | //}
136 | }
137 | }
138 |
139 | ///
140 | /// 设置窗口主题
141 | ///
142 | private void SetupWindowTheme()
143 | {
144 |
145 | if (settings.Theme == 2)
146 | {
147 | // 跟随系统主题
148 | Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this as System.Windows.Window);
149 | }
150 | else if (settings.Theme == 0)
151 | {
152 | // 深色主题
153 | (new ThemeService()).SetTheme(Wpf.Ui.Appearance.ApplicationTheme.Dark);
154 | }
155 | else if (settings.Theme == 1)
156 | {
157 | // 浅色主题
158 | (new ThemeService()).SetTheme(Wpf.Ui.Appearance.ApplicationTheme.Light);
159 | }
160 | }
161 |
162 | ///
163 | /// 初始化消息定时器
164 | ///
165 | private void InitializeMessageTimer()
166 | {
167 | messageTimer = new DispatcherTimer
168 | {
169 | Interval = TimeSpan.FromSeconds(3)
170 | };
171 | messageTimer.Tick += (s, e) =>
172 | {
173 | UpdateDevices();
174 | messageTimer.Stop();
175 | messageText.Visibility = Visibility.Collapsed;
176 | MainScrollViewer.Effect = null;
177 | MainScrollViewer.IsEnabled = true;
178 | };
179 | }
180 |
181 | ///
182 | /// 显示消息文本
183 | ///
184 | private void ShowMessage(string message)
185 | {
186 | messageText.Text = message;
187 | messageText.Visibility = Visibility.Visible;
188 | MainScrollViewer.Effect = new System.Windows.Media.Effects.BlurEffect
189 | {
190 | Radius = 25
191 | };
192 | MainScrollViewer.IsEnabled = false;
193 | messageTimer.Start();
194 | System.Windows.Controls.Panel.SetZIndex(messageText, 1000);
195 | }
196 |
197 | ///
198 | /// 创建设备卡片
199 | ///
200 | private UIElement CreateDeviceGrid(DriveInfo drive)
201 | {
202 | var containerGrid = new Grid();
203 | var deviceCard = new CardAction
204 | {
205 | Margin=new Thickness(10,0,10,0),
206 | Padding=new Thickness(0),
207 | IsChevronVisible = false,
208 | };
209 |
210 | var contentGrid = new Grid();
211 | contentGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(92) });
212 | contentGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
213 | deviceCard.Click += (s, e) => System.Diagnostics.Process.Start("explorer.exe", drive.Name);
214 | string uri;
215 | if (settings.IconType == 0)
216 | {
217 | uri = "pack://application:,,,/ud.png";
218 | }
219 | else
220 | {
221 | if (Wpf.Ui.Appearance.ApplicationThemeManager.GetAppTheme() == Wpf.Ui.Appearance.ApplicationTheme.Dark)
222 | uri = "pack://application:,,,/uddark.png";
223 | else
224 | uri = "pack://application:,,,/udlight.png";
225 | }
226 | var image = new Wpf.Ui.Controls.Image
227 | {
228 | Source = new System.Windows.Media.Imaging.BitmapImage(new Uri(uri)),
229 | Width = 60,
230 | Height = 70,
231 | Margin = new Thickness(10, 10, 5, 10),
232 | VerticalAlignment = VerticalAlignment.Center,
233 | };
234 | Grid.SetColumn(image, 0);
235 | var infoPanel = new System.Windows.Controls.StackPanel { Margin = new Thickness(0, 20, 20, 20) };
236 | Grid.SetColumn(infoPanel, 1);
237 | var headerPanel = new System.Windows.Controls.StackPanel
238 | {
239 | Orientation = System.Windows.Controls.Orientation.Horizontal,
240 | Margin = new Thickness(0, 0, 0, 2)
241 | };
242 | var driveName = new Wpf.Ui.Controls.TextBlock
243 | {
244 | Text = string.IsNullOrEmpty(drive.VolumeLabel) ? "U 盘" : drive.VolumeLabel,
245 | FontSize = 16,
246 | VerticalAlignment = VerticalAlignment.Center
247 | };
248 | var driveLabel = new Border
249 | {
250 | Background = new SolidColorBrush(System.Windows.Media.Color.FromArgb(40, 128, 128, 128)),
251 | CornerRadius = new CornerRadius(5),
252 | Margin = new Thickness(8, 0, 0, 0),
253 | Padding = new Thickness(6, 4, 6, 0)
254 | };
255 | driveLabel.Child = new Wpf.Ui.Controls.TextBlock
256 | {
257 | Text = drive.Name.TrimEnd('\\'),
258 | FontSize = 12,
259 | Foreground = new SolidColorBrush(System.Windows.Media.Color.FromArgb(225, 140, 140, 140))
260 | };
261 | headerPanel.Children.Add(driveName);
262 | headerPanel.Children.Add(driveLabel);
263 | var progressContainer = new Grid();
264 | progressContainer.ColumnDefinitions.Add(new ColumnDefinition());
265 | progressContainer.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
266 | var progressBar = new System.Windows.Controls.ProgressBar
267 | {
268 | Height = 6,
269 | Minimum = 0,
270 | Maximum = 100,
271 | Value = ((double)(drive.TotalSize - drive.AvailableFreeSpace) / drive.TotalSize) * 100,
272 | Margin = new Thickness(0, 0, 5, 0),
273 | };
274 | Grid.SetColumn(progressBar, 0);
275 | var spaceInfo = new Wpf.Ui.Controls.TextBlock
276 | {
277 | Text = $"{FormatBytes(drive.AvailableFreeSpace)}/{FormatBytes(drive.TotalSize)}",
278 | FontSize = 12,
279 | Margin = new Thickness(8, 0, 2, 0),
280 | VerticalAlignment = VerticalAlignment.Center
281 | };
282 | Grid.SetColumn(spaceInfo, 1);
283 | progressContainer.Children.Add(progressBar);
284 | progressContainer.Children.Add(spaceInfo);
285 | var buttonsPanel = new System.Windows.Controls.StackPanel
286 | {
287 | Orientation = System.Windows.Controls.Orientation.Horizontal,
288 | Margin = new Thickness(0, 5, 0, 0)
289 | };
290 | if (settings.ShowOpen)
291 | {
292 | var openButton = new Wpf.Ui.Controls.Button
293 | {
294 | Content = "打开 U 盘",
295 | Margin = new Thickness(0, 0, 10, 0),
296 | Appearance = Wpf.Ui.Controls.ControlAppearance.Primary
297 | };
298 | openButton.Click += (s, e) => {
299 | e.Handled = true;
300 | System.Diagnostics.Process.Start("explorer.exe", drive.Name);
301 |
302 | };
303 | buttonsPanel.Children.Add(openButton);
304 | }
305 |
306 |
307 | var ejectButton = new Wpf.Ui.Controls.Button
308 | {
309 | Content = "弹出",
310 | Appearance = Wpf.Ui.Controls.ControlAppearance.Secondary,
311 | Margin=new Thickness(0,0, 10, 0)
312 | };
313 | // 弹出U盘逻辑,带反馈
314 | ejectButton.Click += (s, e) =>
315 | {
316 | e.Handled = true;
317 | bool result = EjectDrive(drive.Name);
318 | if (result)
319 | {
320 | ShowMessage("已安全弹出");
321 | }
322 | else
323 | {
324 | ShowMessage("弹出失败");
325 | }
326 | };
327 | buttonsPanel.Children.Add(ejectButton);
328 |
329 | var processButton = new Wpf.Ui.Controls.Button
330 | {
331 | Content = "进程",
332 | Margin = new Thickness(0, 0, 10, 0),
333 | Appearance = Wpf.Ui.Controls.ControlAppearance.Secondary
334 | };
335 | processButton.Click += (s, e) => {
336 | e.Handled = true;
337 | ShowProcessWindow(drive.Name);
338 | };
339 | buttonsPanel.Children.Add(processButton);
340 |
341 | infoPanel.Children.Add(headerPanel);
342 | infoPanel.Children.Add(progressContainer);
343 | infoPanel.Children.Add(buttonsPanel);
344 | contentGrid.Children.Add(image);
345 | contentGrid.Children.Add(infoPanel);
346 | deviceCard.Content = contentGrid;
347 | containerGrid.Children.Add(deviceCard);
348 | // 淡入动画
349 | var fade = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(300));
350 | deviceCard.BeginAnimation(Border.OpacityProperty, fade);
351 | return containerGrid;
352 | }
353 |
354 | ///
355 | /// 显示窗口并淡入
356 | ///
357 | private void ShowWithFadeIn()
358 | {
359 | if (!IsVisible)
360 | Show();
361 | var fade = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200));
362 | BeginAnimation(OpacityProperty, fade);
363 | }
364 |
365 | ///
366 | /// 设置开机自启
367 | ///
368 | private void SetStartup(bool enable)
369 | {
370 | const string AppName = "DewUSB";
371 | var key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
372 | if (enable)
373 | {
374 | key.SetValue(AppName, System.Reflection.Assembly.GetExecutingAssembly().Location);
375 | }
376 | else
377 | {
378 | key.DeleteValue(AppName, false);
379 | }
380 | }
381 |
382 | ///
383 | /// 字节格式化
384 | ///
385 | private string FormatBytes(long bytes)
386 | {
387 | string[] suffixes = { "B", "KB", "MB", "GB", "TB" };
388 | int counter = 0;
389 | decimal number = bytes;
390 | while (Math.Round(number / 1024) >= 1)
391 | {
392 | number = number / 1024;
393 | counter++;
394 | }
395 | return string.Format("{0:n1}{1}", number, suffixes[counter]);
396 | }
397 |
398 | ///
399 | /// 弹出U盘,返回是否成功
400 | ///
401 | private bool EjectDrive(string driveLetter)
402 | {
403 | try
404 | {
405 | // 等待一小段时间确保所有文件操作完成
406 | System.Threading.Thread.Sleep(500);
407 |
408 | // 使用设备管理API弹出设备
409 | using (var process = new System.Diagnostics.Process())
410 | {
411 | process.StartInfo.FileName = "powershell.exe";
412 | process.StartInfo.Arguments = $"-Command \"$driveEject = New-Object -comObject Shell.Application; $driveEject.Namespace(17).ParseName('{driveLetter}').InvokeVerb('Eject'); Start-Sleep -Seconds 1; $removable = Get-WmiObject Win32_LogicalDisk | Where-Object {{$_.DeviceID -eq '{driveLetter.TrimEnd('\\')}' -and $_.DriveType -eq 2}}; return ($removable -eq $null)\"";
413 | process.StartInfo.UseShellExecute = false;
414 | process.StartInfo.RedirectStandardOutput = true;
415 | process.StartInfo.RedirectStandardError = true;
416 | process.StartInfo.CreateNoWindow = true;
417 | process.Start();
418 |
419 | // 等待弹出操作完成
420 | string output = process.StandardOutput.ReadToEnd();
421 | process.WaitForExit(3000);
422 |
423 | // 验证设备是否真的已经弹出
424 | bool ejected = !Directory.Exists(driveLetter) || output.Contains("True");
425 |
426 | if (!ejected)
427 | {
428 | ShowMessage("设备正在使用中,请关闭所有程序后重试");
429 | return false;
430 | }
431 |
432 | return true;
433 | }
434 | }
435 | catch (Exception ex)
436 | {
437 | MessageBox.Show($"弹出设备时发生错误: {ex.Message}", "错误", System.Windows.MessageBoxButton.OK, MessageBoxImage.Error);
438 | return false;
439 | }
440 | }
441 |
442 | ///
443 | /// 更新设备列表和分割线
444 | ///
445 | private void UpdateDevices()
446 | {
447 | var currentDrives = DriveInfo.GetDrives()
448 | .Where(d => d.DriveType == DriveType.Removable && d.IsReady)
449 | .ToDictionary(d => d.Name);
450 |
451 | // 移除断开设备
452 | var disconnectedDevices = deviceGrids.Keys
453 | .Where(k => !currentDrives.ContainsKey(k))
454 | .ToList();
455 |
456 | foreach (var device in disconnectedDevices)
457 | {
458 | devicesPanel.Children.Remove(deviceGrids[device]);
459 | deviceGrids.Remove(device);
460 | }
461 |
462 | // 清理所有分割线
463 | var separators = devicesPanel.Children.OfType().ToList();
464 | foreach (var separator in separators)
465 | {
466 | devicesPanel.Children.Remove(separator);
467 | }
468 |
469 | // 重新添加设备和分割线
470 | devicesPanel.Children.Clear();
471 | deviceGrids.Clear();
472 | int idx = 0;
473 |
474 | foreach (var drive in currentDrives.Values)
475 | {
476 | if (idx > 0)
477 | {
478 | devicesPanel.Children.Add(new Separator { Margin = new Thickness(20, 5, 20, 5) });
479 | }
480 | var deviceGrid = CreateDeviceGrid(drive);
481 | devicesPanel.Children.Add(deviceGrid);
482 | deviceGrids[drive.Name] = deviceGrid;
483 | idx++;
484 | }
485 |
486 | // 更新窗口位置和显示状态
487 | UpdateWindowPosition();
488 |
489 | // 处理设备变化
490 | if (disconnectedDevices.Any())
491 | {
492 | ShowMessage("设备已拔出");
493 | }
494 | else if (currentDrives.Count > 0 && !IsVisible)
495 | {
496 | // 有设备且窗口未显示时,显示窗口
497 | ShowWithFadeIn();
498 | WindowState = WindowState.Normal;
499 | Activate();
500 | }
501 | }
502 |
503 | ///
504 | /// 根据内容和屏幕动态调整窗口位置和高度
505 | ///
506 | private void UpdateWindowPosition()
507 | {
508 | // 获取当前窗口所在的屏幕
509 | var windowHandle = new WindowInteropHelper(this).Handle;
510 | var screen = Screen.FromHandle(windowHandle);
511 | var workingArea = screen.WorkingArea;
512 |
513 | // 固定宽度
514 | Width = settings.WindowWidth;
515 |
516 | // 处理 DPI 缩放问题
517 | double dpiFactor = 1.0;
518 | var source = PresentationSource.FromVisual(this);
519 | if (source != null)
520 | {
521 | // 该矩阵可将设备像素转换为DIP
522 | dpiFactor = source.CompositionTarget.TransformFromDevice.M11;
523 | }
524 |
525 | // 计算内容高度(标题栏 + 设备卡片 * 数量 + 边距)
526 | double contentHeight = 38 + (deviceGrids.Count * 150) + 10;
527 | double maxHeight = Math.Min(workingArea.Height * dpiFactor * 0.5, contentHeight); // 最大高度为屏幕高度的一半或内容高度
528 | Height = Math.Max(100, maxHeight); // 最小高度100
529 |
530 |
531 | if(settings.Position == 0) // 左上
532 | {
533 | Left = 20;
534 | Top = 20;
535 | }
536 | else if(settings.Position == 1) // 左下
537 | {
538 | Left = 20;
539 | Top = (workingArea.Bottom * dpiFactor) - Height - 20;
540 | }
541 | else if(settings.Position==3) // 居中
542 | {
543 | Left = (workingArea.Width * dpiFactor - Width) / 2 + workingArea.Left;
544 | Top = (workingArea.Height * dpiFactor - Height) / 2 + workingArea.Top;
545 | }
546 | else
547 | {
548 | // 右下角位置
549 | Left = (workingArea.Right * dpiFactor) - Width - 20;
550 | Top = (workingArea.Bottom * dpiFactor) - Height - 20;
551 | }
552 |
553 | if (settings.TopMost)
554 | {
555 | Topmost = true;
556 | }
557 | else
558 | {
559 | Topmost = false;
560 | }
561 |
562 | // 没有U盘时,隐藏窗口
563 | if (deviceGrids.Count == 0)
564 | {
565 | WindowState = WindowState.Minimized;
566 | Hide();
567 | }
568 | }
569 |
570 | ///
571 | /// 显示设置窗口
572 | ///
573 | private void ShowSettingsWindow()
574 | {
575 | var settingsWindow = new SettingsWindow(settings);
576 | //settingsWindow.Owner = this;
577 | settingsWindow.ShowDialog();
578 | settings = Settings.Load();
579 | try
580 | {
581 | UpdateDevices();
582 | }
583 | catch (System.NullReferenceException) { }
584 | catch (Exception) { }
585 | }
586 |
587 | ///
588 | /// 显示关于窗口
589 | ///
590 | private void ShowAboutWindow()
591 | {
592 | var aboutWindow = new AboutWindow();
593 | aboutWindow.Owner = this;
594 | aboutWindow.ShowDialog();
595 | }
596 |
597 | // 在 MainWindow 类中添加以下方法
598 |
599 | private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
600 | {
601 | if (msg == WM_DEVICECHANGE)
602 | {
603 | int eventType = wParam.ToInt32();
604 | if (eventType == DBT_DEVICEARRIVAL || eventType == DBT_DEVICEREMOVECOMPLETE)
605 | {
606 | Dispatcher.Invoke(() =>
607 | {
608 | UpdateDevices();
609 | });
610 | handled = true;
611 | }
612 | }
613 | return IntPtr.Zero;
614 | }
615 |
616 | // 修改 RegisterForDeviceNotifications 方法,添加钩子监听
617 | private void RegisterForDeviceNotifications()
618 | {
619 | //UpdateDevices();
620 | var windowHandle = new WindowInteropHelper(this).Handle;
621 | var source = HwndSource.FromHwnd(windowHandle);
622 | if (source != null)
623 | {
624 | source.AddHook(WndProc);
625 | }
626 | }
627 |
628 | private const int WM_DEVICECHANGE = 0x0219;
629 | private const int DBT_DEVICEARRIVAL = 0x8000;
630 | private const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
631 |
632 | ///
633 | /// 关闭时释放托盘资源
634 | ///
635 | protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
636 | {
637 | // 阻止程序关闭
638 | base.OnClosing(e);
639 | }
640 |
641 | private void Minimize_Button_Click(object sender, RoutedEventArgs e)
642 | {
643 | WindowState = WindowState.Minimized;
644 | }
645 |
646 | private void Refresh_Button_Click(object sender, RoutedEventArgs e)
647 | {
648 | UpdateDevices();
649 | }
650 |
651 | private void ShowProcessWindow(string driveLetter)
652 | {
653 | var processWindow = new ProcessInfoWindow(driveLetter);
654 | processWindow.Owner = this;
655 | processWindow.ShowDialog();
656 | }
657 | }
658 | }
659 |
--------------------------------------------------------------------------------