├── .gitignore
├── LICENSE
├── README.md
├── Shared
├── Appearance
│ ├── BackgroundManager.cs
│ ├── DictionaryManager.cs
│ └── ThemeManager.cs
├── AssemblyInfo.cs
├── Assets
│ ├── .net-48x48.png
│ ├── databaseEmpty.png
│ ├── gplv3-127x51.png
│ ├── homeSetting.png
│ ├── warning.png
│ └── wpfui-48x48.png
├── Converters
│ ├── BoolToAppearanceConverter.cs
│ ├── FallbackBrushConverter.cs
│ ├── InverseBooleanConverter.cs
│ ├── IsCustomizedAccentColorConverter.cs
│ ├── StringToBrushConverter.cs
│ └── StringToImageSourceConverter.cs
├── Extensions
│ └── TextBoxAttachedProperties.cs
├── Helpers
│ ├── FaceRecognition.cs
│ ├── FileOccupancy.cs
│ ├── ImageProcess.cs
│ ├── SQLiteHelper.cs
│ ├── Settings.cs
│ ├── UsersDb.cs
│ ├── Utils.cs
│ └── Win32Helper.cs
├── Models
│ ├── EncodingFace.cs
│ ├── PageButton.cs
│ └── User.cs
├── Services
│ ├── Contracts
│ │ └── IWindow.cs
│ └── WindowsProviderService.cs
├── Shared.csproj
├── Style
│ ├── MyButton.xaml
│ ├── MyCheckBox.xaml
│ ├── MyScrollBar.xaml
│ └── SelectableTextBlock.xaml
└── branding
│ ├── borrowAttitude.psd
│ └── cameraEmpty.psd
├── SmartLibrary.sln
├── SmartLibrary
├── App.config
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── Assets
│ ├── DynamicPic
│ │ ├── dark
│ │ │ ├── LoadingPictureError.png
│ │ │ ├── PictureEmpty.png
│ │ │ ├── cameraEmpty.jpg
│ │ │ ├── cross.png
│ │ │ └── tick.png
│ │ └── light
│ │ │ ├── LoadingPictureError.png
│ │ │ ├── PictureEmpty.png
│ │ │ ├── cameraEmpty.jpg
│ │ │ ├── cross.png
│ │ │ └── tick.png
│ ├── applicationIcon-512.png
│ ├── background.jpg
│ ├── bluetooth-connected.png
│ ├── bluetooth-disabled.png
│ ├── bluetooth.png
│ ├── bookinfo-512x512.png
│ ├── borrowbook-512x512.png
│ ├── database.png
│ ├── findbook-512x512.png
│ ├── homeBluetooth.png
│ ├── loading.gif
│ ├── right.png
│ └── wrong.png
├── Converters
│ ├── BookExistedConverter.cs
│ ├── BoolToBorrowOrReturnTextConverter.cs
│ ├── BoolToImageSourceConverter.cs
│ └── BoolToOpenCameraTextConverter.cs
├── Extensions
│ └── ImageDecoder.cs
├── Helpers
│ ├── BooksDb.cs
│ ├── ImageQueue.cs
│ ├── LocalStorage.cs
│ ├── Network.cs
│ ├── ResourceManager.cs
│ └── Utilities.cs
├── Models
│ ├── BookInfo.cs
│ ├── BookInfoSimple.cs
│ ├── BookShelfInfo.cs
│ ├── FaceInfoSimple.cs
│ └── ImageQueueInfo.cs
├── Services
│ └── ApplicationHostService.cs
├── SmartLibrary.csproj
├── Style
│ ├── dark.xaml
│ └── light.xaml
├── Usings.cs
├── ViewModels
│ ├── AddBookViewModel.cs
│ ├── AddUserViewModel.cs
│ ├── BookManageViewModel.cs
│ ├── BookshelfViewModel.cs
│ ├── Borrow_Return_BookViewModel.cs
│ ├── EditBookViewModel.cs
│ ├── EditUserViewModel.cs
│ ├── HomeViewModel.cs
│ ├── MainWindowViewModel.cs
│ ├── SettingsViewModel.cs
│ └── UserManageViewModel.cs
├── Views
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ └── Pages
│ │ ├── AddBook.xaml
│ │ ├── AddBook.xaml.cs
│ │ ├── AddUser.xaml
│ │ ├── AddUser.xaml.cs
│ │ ├── BookManage.xaml
│ │ ├── BookManage.xaml.cs
│ │ ├── Bookshelf.xaml
│ │ ├── Bookshelf.xaml.cs
│ │ ├── Borrow_Return_Book.xaml
│ │ ├── Borrow_Return_Book.xaml.cs
│ │ ├── EditBook.xaml
│ │ ├── EditBook.xaml.cs
│ │ ├── EditUser.xaml
│ │ ├── EditUser.xaml.cs
│ │ ├── Home.xaml
│ │ ├── Home.xaml.cs
│ │ ├── Settings.xaml
│ │ ├── Settings.xaml.cs
│ │ ├── UserManage.xaml
│ │ └── UserManage.xaml.cs
└── applicationIcon.ico
├── docs
├── book.png
├── reference.md
├── screenshot.webp
├── screenshot1.webp
├── screenshot2.webp
├── screenshot3.webp
├── screenshot4.webp
└── screenshot5.webp
└── test
└── test.smartlibrary
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
4 |
5 | # DocFX
6 | docs/api/
7 |
8 | # User-specific files
9 | *.rsuser
10 | *.suo
11 | *.user
12 | *.userosscache
13 | *.sln.docstates
14 |
15 | # User-specific files (MonoDevelop/Xamarin Studio)
16 | *.userprefs
17 |
18 | # Visual Studio Code files
19 | .vscode
20 |
21 | # IntelliJ IDEA files
22 | .idea
23 |
24 | # Mono auto generated files
25 | mono_crash.*
26 |
27 | # Build results
28 | [Dd]ebug/
29 | [Dd]ebugPublic/
30 | [Rr]elease/
31 | [Rr]eleases/
32 | x64/
33 | x86/
34 | [Aa][Rr][Mm]/
35 | [Aa][Rr][Mm]64/
36 | bld/
37 | [Bb]in/
38 | [Oo]bj/
39 | [Ll]og/
40 | [Ll]ogs/
41 |
42 | # Visual Studio 2015/2017 cache/options directory
43 | .vs/
44 | # Uncomment if you have tasks that create the project's static files in wwwroot
45 | #wwwroot/
46 |
47 | # Visual Studio 2017 auto generated files
48 | Generated\ Files/
49 |
50 | # MSTest test Results
51 | [Tt]est[Rr]esult*/
52 | [Bb]uild[Ll]og.*
53 |
54 | # NUnit
55 | *.VisualState.xml
56 | TestResult.xml
57 | nunit-*.xml
58 |
59 | # Build Results of an ATL Project
60 | [Dd]ebugPS/
61 | [Rr]eleasePS/
62 | dlldata.c
63 |
64 | # Benchmark Results
65 | BenchmarkDotNet.Artifacts/
66 |
67 | # .NET Core
68 | project.lock.json
69 | project.fragment.lock.json
70 | artifacts/
71 |
72 | # StyleCop
73 | StyleCopReport.xml
74 |
75 | # Files built by Visual Studio
76 | *_i.c
77 | *_p.c
78 | *_h.h
79 | *.ilk
80 | *.meta
81 | *.obj
82 | *.iobj
83 | *.pch
84 | *.pdb
85 | *.ipdb
86 | *.pgc
87 | *.pgd
88 | *.rsp
89 | *.sbr
90 | *.tlb
91 | *.tli
92 | *.tlh
93 | *.tmp
94 | *.tmp_proj
95 | *_wpftmp.csproj
96 | *.log
97 | *.vspscc
98 | *.vssscc
99 | .builds
100 | *.pidb
101 | *.svclog
102 | *.scc
103 |
104 | # Chutzpah Test files
105 | _Chutzpah*
106 |
107 | # Visual C++ cache files
108 | ipch/
109 | *.aps
110 | *.ncb
111 | *.opendb
112 | *.opensdf
113 | *.sdf
114 | *.cachefile
115 | *.VC.db
116 | *.VC.VC.opendb
117 |
118 | # Visual Studio profiler
119 | *.psess
120 | *.vsp
121 | *.vspx
122 | *.sap
123 |
124 | # Visual Studio Trace Files
125 | *.e2e
126 |
127 | # TFS 2012 Local Workspace
128 | $tf/
129 |
130 | # Guidance Automation Toolkit
131 | *.gpState
132 |
133 | # ReSharper is a .NET coding add-in
134 | _ReSharper*/
135 | *.[Rr]e[Ss]harper
136 | *.DotSettings.user
137 |
138 | # TeamCity is a build add-in
139 | _TeamCity*
140 |
141 | # DotCover is a Code Coverage Tool
142 | *.dotCover
143 |
144 | # AxoCover is a Code Coverage Tool
145 | .axoCover/*
146 | !.axoCover/settings.json
147 |
148 | # Visual Studio code coverage results
149 | *.coverage
150 | *.coveragexml
151 |
152 | # NCrunch
153 | _NCrunch_*
154 | .*crunch*.local.xml
155 | nCrunchTemp_*
156 |
157 | # MightyMoose
158 | *.mm.*
159 | AutoTest.Net/
160 |
161 | # Web workbench (sass)
162 | .sass-cache/
163 |
164 | # Installshield output folder
165 | [Ee]xpress/
166 |
167 | # DocProject is a documentation generator add-in
168 | DocProject/buildhelp/
169 | DocProject/Help/*.HxT
170 | DocProject/Help/*.HxC
171 | DocProject/Help/*.hhc
172 | DocProject/Help/*.hhk
173 | DocProject/Help/*.hhp
174 | DocProject/Help/Html2
175 | DocProject/Help/html
176 |
177 | # Click-Once directory
178 | publish/
179 |
180 | # Publish Web Output
181 | *.[Pp]ublish.xml
182 | *.azurePubxml
183 | # Note: Comment the next line if you want to checkin your web deploy settings,
184 | # but database connection strings (with potential passwords) will be unencrypted
185 | *.pubxml
186 | *.publishproj
187 |
188 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
189 | # checkin your Azure Web App publish settings, but sensitive information contained
190 | # in these scripts will be unencrypted
191 | PublishScripts/
192 |
193 | # NuGet Packages
194 | *.nupkg
195 | # NuGet Symbol Packages
196 | *.snupkg
197 | # The packages folder can be ignored because of Package Restore
198 | **/[Pp]ackages/*
199 | # except build/, which is used as an MSBuild target.
200 | !**/[Pp]ackages/build/
201 | # Uncomment if necessary however generally it will be regenerated when needed
202 | #!**/[Pp]ackages/repositories.config
203 | # NuGet v3's project.json files produces more ignorable files
204 | *.nuget.props
205 | *.nuget.targets
206 |
207 | # Microsoft Azure Build Output
208 | csx/
209 | *.build.csdef
210 |
211 | # Microsoft Azure Emulator
212 | ecf/
213 | rcf/
214 |
215 | # Windows Store app package directories and files
216 | AppPackages/
217 | BundleArtifacts/
218 | Package.StoreAssociation.xml
219 | _pkginfo.txt
220 | *.appx
221 | *.appxbundle
222 | *.appxupload
223 |
224 | # Visual Studio cache files
225 | # files ending in .cache can be ignored
226 | *.[Cc]ache
227 | # but keep track of directories ending in .cache
228 | !?*.[Cc]ache/
229 |
230 | # Others
231 | ClientBin/
232 | ~$*
233 | *~
234 | *.dbmdl
235 | *.dbproj.schemaview
236 | *.jfm
237 | *.pfx
238 | *.publishsettings
239 | orleans.codegen.cs
240 |
241 | # Including strong name files can present a security risk
242 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
243 | #*.snk
244 |
245 | # Since there are multiple workflows, uncomment next line to ignore bower_components
246 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
247 | #bower_components/
248 |
249 | # RIA/Silverlight projects
250 | Generated_Code/
251 |
252 | # Backup & report files from converting an old project file
253 | # to a newer Visual Studio version. Backup files are not needed,
254 | # because we have git ;-)
255 | _UpgradeReport_Files/
256 | Backup*/
257 | UpgradeLog*.XML
258 | UpgradeLog*.htm
259 | ServiceFabricBackup/
260 | *.rptproj.bak
261 |
262 | # SQL Server files
263 | *.mdf
264 | *.ldf
265 | *.ndf
266 |
267 | # Business Intelligence projects
268 | *.rdl.data
269 | *.bim.layout
270 | *.bim_*.settings
271 | *.rptproj.rsuser
272 | *- [Bb]ackup.rdl
273 | *- [Bb]ackup ([0-9]).rdl
274 | *- [Bb]ackup ([0-9][0-9]).rdl
275 |
276 | # Microsoft Fakes
277 | FakesAssemblies/
278 |
279 | # GhostDoc plugin setting file
280 | *.GhostDoc.xml
281 |
282 | # Node.js Tools for Visual Studio
283 | .ntvs_analysis.dat
284 | node_modules/
285 |
286 | # Visual Studio 6 build log
287 | *.plg
288 |
289 | # Visual Studio 6 workspace options file
290 | *.opt
291 |
292 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
293 | *.vbw
294 |
295 | # Visual Studio LightSwitch build output
296 | **/*.HTMLClient/GeneratedArtifacts
297 | **/*.DesktopClient/GeneratedArtifacts
298 | **/*.DesktopClient/ModelManifest.xml
299 | **/*.Server/GeneratedArtifacts
300 | **/*.Server/ModelManifest.xml
301 | _Pvt_Extensions
302 |
303 | # Paket dependency manager
304 | .paket/paket.exe
305 | paket-files/
306 |
307 | # FAKE - F# Make
308 | .fake/
309 |
310 | # CodeRush personal settings
311 | .cr/personal
312 |
313 | # Python Tools for Visual Studio (PTVS)
314 | __pycache__/
315 | *.pyc
316 |
317 | # Cake - Uncomment if you are using it
318 | # tools/**
319 | # !tools/packages.config
320 |
321 | # Tabs Studio
322 | *.tss
323 |
324 | # Telerik's JustMock configuration file
325 | *.jmconfig
326 |
327 | # BizTalk build output
328 | *.btp.cs
329 | *.btm.cs
330 | *.odx.cs
331 | *.xsd.cs
332 |
333 | # OpenCover UI analysis results
334 | OpenCover/
335 |
336 | # Azure Stream Analytics local run output
337 | ASALocalRun/
338 |
339 | # MSBuild Binary and Structured Log
340 | *.binlog
341 |
342 | # NVidia Nsight GPU debugger configuration file
343 | *.nvuser
344 |
345 | # MFractors (Xamarin productivity tool) working folder
346 | .mfractor/
347 |
348 | # Local History for Visual Studio
349 | .localhistory/
350 |
351 | # BeatPulse healthcheck temp database
352 | healthchecksdb
353 |
354 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
355 | MigrationBackup/
356 |
357 | # Ionide (cross platform F# VS Code tools) working folder
358 | .ionide/
359 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Smart Library
5 | 一个基于WPF和Arduino的智慧图书管理系统
6 |
7 | ## 简介
8 | 随着我国文化教育事业的飞速发展,图书馆藏书量不断增多,仅依靠人工操作已经不能高效地提供阅读服务。我们通过调研走访昆明市多家图书馆,了解到图书馆普遍存在书籍种类繁多、找书难;借阅书籍的流程繁琐,借书难;大量书籍需要清点,工作量大且效率低,归位难;现有图书馆管理系统功能单一,以机器人技术为基础,减少繁琐、重复手工操作的自动化管理流程有很大改进空间等问题。为解决这些问题,我们构思了一款采用基于 Arduino,综合应用 RFID 模块、蓝牙模块和条形码扫码等模块来实现位置引导、找书、借书、还书等功能的图书馆查、借、还智慧一体机器人。
9 |
10 | ## 研究内容和难点
11 | 我们课题小组研究的内容为:借助Arduino机器人的无线射频识别系统(RFID)和条形码扫描模块,通过扫描图书实现信息采集,实现书名或者作者等关键信息搜索,同步展示出图书相关信息,读者可以快速找到书籍和书籍相关信息(如:价格、库存、类别和存放位置等)。采用多传感器组合的机器人将实现引导、找书、借书、还书等功能。
12 | 当前研究的难点主要集中在几个方面:
13 | 1、机器人由于每一个要实现的功能都需要不同模块堆叠,随着部件的增加,工作量和设计难度也大大提高,本次课题只能围绕基础功能需求展开,在一期成果上再展开再次研究和开发。
14 | 2、课题研究所采用Arduino打造图书馆智慧机器人系统是需要打通图书馆原有管理系统,实现数据动态共享、查询、分析、回传等功能还是有一定难度。
15 | 3、智慧机器人系统基于Arduino平台,系统功率低,持续工作能力不高,可通过成果转化后实现。
16 |
17 | ## 研究目标
18 | 基于Arduino底层系统的开放性和扩展性,我们所设计的图书馆智慧机器人保有高扩展性,本课题研究基础上后续可以持续实现功能扩展及升级满足管理需求变化。
19 | 我们的设计目标是图书馆采用图书馆智慧机器人后,实现图书管理智慧化,用机器人代替人工,实现图书分拣、搬运、查重、贴码、上下架等日常操作,简化找书、借书、还书的环节,将人员从繁重传统操作中解放出来,为读者提供更加优质、便捷的图书阅读服务。
20 |
21 | ## 功能介绍
22 | 我们采用 .Net 9和 WPF 框架设计了智慧图书馆软件,用户可在软件上快速浏览或查找图书馆所有藏书详细信息,包括所在书架号、借阅状态、出版信息等。如要找书,通过软件,小车可直接引导顾客到达图书对应书架,缩短在书海中徘徊寻找时间。同时,借书、还书功能仅需通过扫描图书条形码即可完成。还书后,小车可自动将书送到对应书架前的收集框中,管理员仅需将框中的书放入书架即可。同时,该软件还具备图书管理功能,图书管理员可通过程序向本地数据库进行增、删、查、改等操作,界面美观,操作简单;以及添加图书时,可通过书籍 ISBN 号,进行图书数据联网查询,免去了大量图书信息手动录入的时间。
23 | 另外,我们还使用 C++语言编写了小车的 Arduino 主控板控制程序,通过红外巡线模块使小车按照指定路线前进;通过 PID 算法控制小车电机,使小车能够平稳前行和转弯;采用 RFID 识别技术,判断是否到达指定书架,并通过蓝牙通信与电脑交换数据,自主设计了通信数据结构,达到控制小车执行多种任务的目的,实现小车与电脑协同工作。
24 |
25 | ## 🖼 截图
26 |
27 | 主界面
28 |
29 | 书架界面
30 |
31 | 借还界面
32 |
33 | 用户管理界面
34 |
35 | 图书管理界面
36 |
37 | 设置界面
38 |
39 | ## 📐使用教程
40 | * 在图书设置界面点击导入按钮,导入test文件夹下的测试书籍
41 | * 在用户管理界面点击加号,创建一个用户
42 | * 接着就可以使用借还功能
43 | * 添加书籍需要填写API Key,用于在线查询书籍信息,在设置界面中可找到API Key的设置项,设置完即可使用在线查询功能
44 |
45 | ## 🌏 路线
46 | - [x] 增加用户管理系统
47 | - [ ] 增加 AI 个性化推荐
48 |
49 | ## ⌨️ 开发环境
50 | [Visual Studio 2022](https://visualstudio.microsoft.com/zh-hans/vs)
51 |
52 | - 系统要求
53 | - [Windows 11 版本 21H2 或更高版本:家庭版、专业版、专业教育版、专业工作站版、企业版和教育版](https://learn.microsoft.com/zh-cn/visualstudio/releases/2022/system-requirements)
54 |
55 | - 工作负荷
56 | - .NET 桌面开发(.Net 8 or 9)
57 |
58 | - 插件
59 | - [XAML Styler for Visual Studio 2022(可选)](https://marketplace.visualstudio.com/items?itemName=TeamXavalon.XAMLStyler2022)
60 |
61 | ## 💡技术分享
62 | - [可复制的TextBlock](Shared/Style/SelectableTextBlock.xaml)
63 | - [TextBox数字限定](Shared/Extensions/TextBoxAttachedProperties.cs)
64 | - [DataGrid分页](SmartLibrary/ViewModels/BookManageViewModel.cs)
65 | - [图像等比例缩放](Shared/Helpers/ImageProcess.cs)
66 | - [图片队列下载](SmartLibrary/Helpers/ImageQueue.cs)
67 | - [文件大小计算](Shared/Helpers/FileOccupancy.cs)
68 | - [本地人脸识别](Shared/Helpers/FaceRecognition.cs)
69 | - [无需管理员设置开机自启](SmartLibrary/Helpers/Utilities.cs)
70 | - [互斥锁实现程序单例及后台激活](SmartLibrary/App.xaml.cs)
71 |
72 | ## 📄 引用
73 | 详见 [./docs/reference.md](docs/reference.md)
74 |
75 | ## 图标
76 | Reading icons created by Freepik - Flaticon
77 |
78 | Refresh data icons created by Freepik - Flaticon
79 |
80 | Borrow icons created by max.icons - Flaticon
81 |
82 | Database icons created by Freepik - Flaticon
83 |
84 | Warning icons created by Freepik - Flaticon
85 |
86 | ## License
87 | SmartLibrary is licensed under [GPLv3](./LICENSE).
88 | Copyright © 2025 by Zhao Yanglei.
--------------------------------------------------------------------------------
/Shared/Appearance/BackgroundManager.cs:
--------------------------------------------------------------------------------
1 | using Shared.Helpers;
2 | using System.Windows;
3 | using Wpf.Ui.Appearance;
4 | using Wpf.Ui.Controls;
5 | using Wpf.Ui.Interop;
6 |
7 | namespace Shared.Appearance
8 | {
9 | public static class BackgroundManager
10 | {
11 | ///
12 | /// Tries to apply dark theme to .
13 | ///
14 | public static void ApplyDarkThemeToWindow(Window? window)
15 | {
16 | if (window is null)
17 | {
18 | return;
19 | }
20 |
21 | if (window.IsLoaded)
22 | {
23 | _ = UnsafeNativeMethods.ApplyWindowDarkMode(window);
24 | }
25 |
26 | window.Loaded += (sender, _) => UnsafeNativeMethods.ApplyWindowDarkMode(sender as Window);
27 | }
28 |
29 | ///
30 | /// Tries to remove dark theme from .
31 | ///
32 | public static void RemoveDarkThemeFromWindow(Window? window)
33 | {
34 | if (window is null)
35 | {
36 | return;
37 | }
38 |
39 | if (window.IsLoaded)
40 | {
41 | _ = UnsafeNativeMethods.RemoveWindowDarkMode(window);
42 | }
43 |
44 | window.Loaded += (sender, _) => UnsafeNativeMethods.RemoveWindowDarkMode(sender as Window);
45 | }
46 |
47 | ///
48 | /// Forces change to application background. Required if custom background effect was previously applied.
49 | ///
50 | public static void UpdateBackground(
51 | Window? window,
52 | ApplicationTheme applicationTheme,
53 | WindowBackdropType backdrop
54 | )
55 | {
56 | if (window is null)
57 | {
58 | return;
59 | }
60 |
61 | _ = WindowBackdrop.RemoveBackdrop(window);
62 |
63 | if (applicationTheme == ApplicationTheme.HighContrast)
64 | {
65 | backdrop = WindowBackdropType.None;
66 | }
67 |
68 | // This was required to update the background when moving from a HC theme to light/dark theme. However, this breaks theme proper light/dark theme changing on Windows 10.
69 | // But window backdrop effects are not applied when it has an opaque (or any) background on W11 (so removing this breaks backdrop effects when switching themes), however, for legacy MICA it may not be required
70 | // using existing variable, though the OS build which (officially) supports setting DWM_SYSTEMBACKDROP_TYPE attribute is build 22621
71 | // source: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type
72 | if (Utils.IsOSWindows11Insider1OrNewer && backdrop is not WindowBackdropType.None)
73 | {
74 | _ = WindowBackdrop.RemoveBackground(window);
75 | }
76 |
77 | _ = WindowBackdrop.ApplyBackdrop(window, backdrop);
78 |
79 | if (applicationTheme is ApplicationTheme.Dark)
80 | {
81 | ApplyDarkThemeToWindow(window);
82 | }
83 | else
84 | {
85 | RemoveDarkThemeFromWindow(window);
86 | }
87 |
88 | _ = WindowBackdrop.RemoveTitlebarBackground(window);
89 |
90 | foreach (object? subWindow in window.OwnedWindows)
91 | {
92 | if (subWindow is Window windowSubWindow)
93 | {
94 | _ = WindowBackdrop.ApplyBackdrop(windowSubWindow, backdrop);
95 |
96 | if (applicationTheme is ApplicationTheme.Dark)
97 | {
98 | ApplyDarkThemeToWindow(windowSubWindow);
99 | }
100 | else
101 | {
102 | RemoveDarkThemeFromWindow(windowSubWindow);
103 | }
104 |
105 | _ = WindowBackdrop.RemoveTitlebarBackground(window);
106 | }
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Shared/Appearance/DictionaryManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using System.Windows;
3 | using Wpf.Ui;
4 |
5 | namespace Shared.Appearance
6 | {
7 | internal class DictionaryManager
8 | {
9 | ///
10 | /// Gets the namespace, e.g. the library the resource is being searched for.
11 | ///
12 | public string SearchNamespace { get; }
13 |
14 | public DictionaryManager(string searchNamespace)
15 | {
16 | SearchNamespace = searchNamespace;
17 | }
18 |
19 | ///
20 | /// Shows whether the application contains the .
21 | ///
22 | /// Any part of the resource name.
23 | /// if it doesn't exist.
24 | public bool HasDictionary(string resourceLookup)
25 | {
26 | return GetDictionary(resourceLookup) != null;
27 | }
28 |
29 | ///
30 | /// Gets the if exists.
31 | ///
32 | /// Any part of the resource name.
33 | /// , if it doesn't exist.
34 | public ResourceDictionary? GetDictionary(string resourceLookup)
35 | {
36 | Collection applicationDictionaries = GetApplicationMergedDictionaries();
37 |
38 | if (applicationDictionaries.Count == 0)
39 | {
40 | return null;
41 | }
42 |
43 | foreach (ResourceDictionary t in applicationDictionaries)
44 | {
45 | string resourceDictionaryUri;
46 |
47 | if (t?.Source != null)
48 | {
49 | resourceDictionaryUri = t.Source.ToString();
50 |
51 | if (
52 | resourceDictionaryUri.Contains(SearchNamespace, StringComparison.OrdinalIgnoreCase)
53 | && resourceDictionaryUri.Contains(resourceLookup, StringComparison.OrdinalIgnoreCase)
54 | )
55 | {
56 | return t;
57 | }
58 | }
59 |
60 | foreach (ResourceDictionary? t1 in t!.MergedDictionaries)
61 | {
62 | if (t1?.Source == null)
63 | {
64 | continue;
65 | }
66 |
67 | resourceDictionaryUri = t1.Source.ToString();
68 |
69 | if (
70 | !resourceDictionaryUri.Contains(SearchNamespace, StringComparison.OrdinalIgnoreCase)
71 | || !resourceDictionaryUri.Contains(resourceLookup, StringComparison.OrdinalIgnoreCase)
72 | )
73 | {
74 | continue;
75 | }
76 |
77 | return t1;
78 | }
79 | }
80 |
81 | return null;
82 | }
83 |
84 | ///
85 | /// Shows whether the application contains the .
86 | ///
87 | /// Any part of the resource name.
88 | /// A valid for the replaced resource.
89 | /// if the dictionary was updated. otherwise.
90 | public bool UpdateDictionary(string resourceLookup, Uri? newResourceUri)
91 | {
92 | Collection applicationDictionaries = UiApplication
93 | .Current
94 | .Resources
95 | .MergedDictionaries;
96 |
97 | if (applicationDictionaries.Count == 0 || newResourceUri is null)
98 | {
99 | return false;
100 | }
101 |
102 | for (var i = 0; i < applicationDictionaries.Count; i++)
103 | {
104 | string sourceUri;
105 |
106 | if (applicationDictionaries[i]?.Source != null)
107 | {
108 | sourceUri = applicationDictionaries[i].Source.ToString();
109 |
110 | if (
111 | sourceUri.Contains(SearchNamespace, StringComparison.OrdinalIgnoreCase)
112 | && sourceUri.Contains(resourceLookup, StringComparison.OrdinalIgnoreCase)
113 | )
114 | {
115 | applicationDictionaries[i] = new() { Source = newResourceUri };
116 |
117 | return true;
118 | }
119 | }
120 |
121 | for (var j = 0; j < applicationDictionaries[i].MergedDictionaries.Count; j++)
122 | {
123 | if (applicationDictionaries[i].MergedDictionaries[j]?.Source == null)
124 | {
125 | continue;
126 | }
127 |
128 | sourceUri = applicationDictionaries[i].MergedDictionaries[j].Source.ToString();
129 |
130 | if (
131 | !sourceUri.Contains(SearchNamespace, StringComparison.OrdinalIgnoreCase)
132 | || !sourceUri.Contains(resourceLookup, StringComparison.OrdinalIgnoreCase)
133 | )
134 | {
135 | continue;
136 | }
137 |
138 | applicationDictionaries[i].MergedDictionaries[j] = new() { Source = newResourceUri };
139 |
140 | return true;
141 | }
142 | }
143 |
144 | return false;
145 | }
146 |
147 | private Collection GetApplicationMergedDictionaries()
148 | {
149 | return UiApplication.Current.Resources.MergedDictionaries;
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/Shared/Appearance/ThemeManager.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using Wpf.Ui;
3 | using Wpf.Ui.Appearance;
4 | using Wpf.Ui.Controls;
5 |
6 | namespace Shared.Appearance
7 | {
8 | public static class ThemeManager
9 | {
10 | private static ApplicationTheme _cachedApplicationTheme = ApplicationTheme.Unknown;
11 |
12 | internal const string LibraryNamespace = "wpf.ui;";
13 |
14 | internal const string ThemesDictionaryPath = "pack://application:,,,/Wpf.Ui;component/Resources/Theme/";
15 |
16 | ///
17 | /// Event triggered when the application's theme is changed.
18 | ///
19 | public static event ThemeChangedEvent? Changed;
20 |
21 | ///
22 | /// Gets a value that indicates whether the application is currently using the high contrast theme.
23 | ///
24 | /// if application uses high contrast theme.
25 | public static bool IsHighContrast() => _cachedApplicationTheme == ApplicationTheme.HighContrast;
26 |
27 | ///
28 | /// Gets a value that indicates whether the Windows is currently using the high contrast theme.
29 | ///
30 | /// if system uses high contrast theme.
31 | public static bool IsSystemHighContrast() => SystemThemeManager.HighContrast;
32 |
33 | ///
34 | /// Changes the current application theme.
35 | ///
36 | /// Theme to set.
37 | /// Whether the custom background effect should be applied.
38 | /// Whether the color accents should be changed.
39 | public static void Apply(
40 | ApplicationTheme applicationTheme,
41 | WindowBackdropType backgroundEffect = WindowBackdropType.Mica,
42 | bool updateAccent = true
43 | )
44 | {
45 | if (updateAccent)
46 | {
47 | ApplicationAccentColorManager.Apply(
48 | ApplicationAccentColorManager.GetColorizationColor(),
49 | applicationTheme,
50 | false
51 | );
52 | }
53 |
54 | if (applicationTheme == ApplicationTheme.Unknown)
55 | {
56 | return;
57 | }
58 |
59 | DictionaryManager appDictionaries = new(LibraryNamespace);
60 |
61 | string themeDictionaryName = "Light";
62 |
63 | switch (applicationTheme)
64 | {
65 | case ApplicationTheme.Dark:
66 | themeDictionaryName = "Dark";
67 | break;
68 | case ApplicationTheme.HighContrast:
69 | themeDictionaryName = ApplicationThemeManager.GetSystemTheme() switch
70 | {
71 | SystemTheme.HC1 => "HC1",
72 | SystemTheme.HC2 => "HC2",
73 | SystemTheme.HCBlack => "HCBlack",
74 | SystemTheme.HCWhite => "HCWhite",
75 | _ => "HCWhite",
76 | };
77 | break;
78 | }
79 |
80 | bool isUpdated = appDictionaries.UpdateDictionary(
81 | "theme",
82 | new Uri(ThemesDictionaryPath + themeDictionaryName + ".xaml", UriKind.Absolute)
83 | );
84 |
85 | System.Diagnostics.Debug.WriteLine(
86 | $"INFO | {typeof(ApplicationThemeManager)} tries to update theme to {themeDictionaryName} ({applicationTheme}): {isUpdated}",
87 | nameof(ApplicationThemeManager)
88 | );
89 |
90 | if (!isUpdated)
91 | {
92 | return;
93 | }
94 |
95 | SystemThemeManager.UpdateSystemThemeCache();
96 |
97 | _cachedApplicationTheme = applicationTheme;
98 |
99 | Changed?.Invoke(applicationTheme, ApplicationAccentColorManager.SystemAccent);
100 |
101 | if (UiApplication.Current.MainWindow is Window mainWindow)
102 | {
103 | WindowBackgroundManager.UpdateBackground(mainWindow, applicationTheme, backgroundEffect);
104 | }
105 | }
106 |
107 | ///
108 | /// Applies Resources in the .
109 | ///
110 | public static void Apply(FrameworkElement frameworkElement)
111 | {
112 | if (frameworkElement is null)
113 | {
114 | return;
115 | }
116 |
117 | ResourceDictionary[] resourcesRemove = frameworkElement
118 | .Resources.MergedDictionaries.Where(e => e.Source is not null)
119 | .Where(e => e.Source.ToString().Contains(LibraryNamespace, StringComparison.OrdinalIgnoreCase))
120 | .ToArray();
121 |
122 | foreach (ResourceDictionary? resource in UiApplication.Current.Resources.MergedDictionaries)
123 | {
124 | System.Diagnostics.Debug.WriteLine(
125 | $"INFO | {typeof(ApplicationThemeManager)} Add {resource.Source}",
126 | "Wpf.Ui.Appearance"
127 | );
128 | frameworkElement.Resources.MergedDictionaries.Add(resource);
129 | }
130 |
131 | foreach (ResourceDictionary resource in resourcesRemove)
132 | {
133 | System.Diagnostics.Debug.WriteLine(
134 | $"INFO | {typeof(ApplicationThemeManager)} Remove {resource.Source}",
135 | "Wpf.Ui.Appearance"
136 | );
137 |
138 | _ = frameworkElement.Resources.MergedDictionaries.Remove(resource);
139 | }
140 |
141 | foreach (System.Collections.DictionaryEntry resource in UiApplication.Current.Resources)
142 | {
143 | System.Diagnostics.Debug.WriteLine(
144 | $"INFO | {typeof(ApplicationThemeManager)} Copy Resource {resource.Key} - {resource.Value}",
145 | "Wpf.Ui.Appearance"
146 | );
147 | frameworkElement.Resources[resource.Key] = resource.Value;
148 | }
149 | }
150 |
151 | public static void ApplySystemTheme()
152 | {
153 | ApplySystemTheme(true);
154 | }
155 |
156 | public static void ApplySystemTheme(bool updateAccent)
157 | {
158 | SystemThemeManager.UpdateSystemThemeCache();
159 |
160 | SystemTheme systemTheme = GetSystemTheme();
161 |
162 | ApplicationTheme themeToSet = ApplicationTheme.Light;
163 |
164 | if (systemTheme is SystemTheme.Dark or SystemTheme.CapturedMotion or SystemTheme.Glow)
165 | {
166 | themeToSet = ApplicationTheme.Dark;
167 | }
168 | else if (
169 | systemTheme is SystemTheme.HC1 or SystemTheme.HC2 or SystemTheme.HCBlack or SystemTheme.HCWhite
170 | )
171 | {
172 | themeToSet = ApplicationTheme.HighContrast;
173 | }
174 |
175 | Apply(themeToSet, updateAccent: updateAccent);
176 | }
177 |
178 | ///
179 | /// Gets currently set application theme.
180 | ///
181 | /// if something goes wrong.
182 | public static ApplicationTheme GetAppTheme()
183 | {
184 | if (_cachedApplicationTheme == ApplicationTheme.Unknown)
185 | {
186 | FetchApplicationTheme();
187 | }
188 |
189 | return _cachedApplicationTheme;
190 | }
191 |
192 | ///
193 | /// Gets currently set system theme.
194 | ///
195 | /// if something goes wrong.
196 | public static SystemTheme GetSystemTheme()
197 | {
198 | return SystemThemeManager.GetCachedSystemTheme();
199 | }
200 |
201 | ///
202 | /// Gets a value that indicates whether the application is matching the system theme.
203 | ///
204 | /// if the application has the same theme as the system.
205 | public static bool IsAppMatchesSystem()
206 | {
207 | ApplicationTheme appApplicationTheme = GetAppTheme();
208 | SystemTheme sysTheme = GetSystemTheme();
209 |
210 | return appApplicationTheme switch
211 | {
212 | ApplicationTheme.Dark => sysTheme
213 | is SystemTheme.Dark
214 | or SystemTheme.CapturedMotion
215 | or SystemTheme.Glow,
216 | ApplicationTheme.Light => sysTheme
217 | is SystemTheme.Light
218 | or SystemTheme.Flow
219 | or SystemTheme.Sunrise,
220 | _ => appApplicationTheme == ApplicationTheme.HighContrast && SystemThemeManager.HighContrast,
221 | };
222 | }
223 |
224 | ///
225 | /// Checks if the application and the operating system are currently working in a dark theme.
226 | ///
227 | public static bool IsMatchedDark()
228 | {
229 | ApplicationTheme appApplicationTheme = GetAppTheme();
230 | SystemTheme sysTheme = GetSystemTheme();
231 |
232 | if (appApplicationTheme != ApplicationTheme.Dark)
233 | {
234 | return false;
235 | }
236 |
237 | return sysTheme is SystemTheme.Dark or SystemTheme.CapturedMotion or SystemTheme.Glow;
238 | }
239 |
240 | ///
241 | /// Checks if the application and the operating system are currently working in a light theme.
242 | ///
243 | public static bool IsMatchedLight()
244 | {
245 | ApplicationTheme appApplicationTheme = GetAppTheme();
246 | SystemTheme sysTheme = GetSystemTheme();
247 |
248 | if (appApplicationTheme != ApplicationTheme.Light)
249 | {
250 | return false;
251 | }
252 |
253 | return sysTheme is SystemTheme.Light or SystemTheme.Flow or SystemTheme.Sunrise;
254 | }
255 |
256 | ///
257 | /// Tries to guess the currently set application theme.
258 | ///
259 | private static void FetchApplicationTheme()
260 | {
261 | DictionaryManager appDictionaries = new(LibraryNamespace);
262 | ResourceDictionary? themeDictionary = appDictionaries.GetDictionary("theme");
263 |
264 | if (themeDictionary == null)
265 | {
266 | return;
267 | }
268 |
269 | string themeUri = themeDictionary.Source.ToString();
270 |
271 | if (themeUri.Contains("light", StringComparison.OrdinalIgnoreCase))
272 | {
273 | _cachedApplicationTheme = ApplicationTheme.Light;
274 | }
275 |
276 | if (themeUri.Contains("dark", StringComparison.OrdinalIgnoreCase))
277 | {
278 | _cachedApplicationTheme = ApplicationTheme.Dark;
279 | }
280 |
281 | if (themeUri.Contains("highcontrast", StringComparison.OrdinalIgnoreCase))
282 | {
283 | _cachedApplicationTheme = ApplicationTheme.HighContrast;
284 | }
285 | }
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/Shared/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/Shared/Assets/.net-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/.net-48x48.png
--------------------------------------------------------------------------------
/Shared/Assets/databaseEmpty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/databaseEmpty.png
--------------------------------------------------------------------------------
/Shared/Assets/gplv3-127x51.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/gplv3-127x51.png
--------------------------------------------------------------------------------
/Shared/Assets/homeSetting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/homeSetting.png
--------------------------------------------------------------------------------
/Shared/Assets/warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/warning.png
--------------------------------------------------------------------------------
/Shared/Assets/wpfui-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanglei-er/SmartLibrary/04fd31da172579d0c26253ff2660e277ddd51746/Shared/Assets/wpfui-48x48.png
--------------------------------------------------------------------------------
/Shared/Converters/BoolToAppearanceConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Windows.Data;
3 |
4 | namespace Shared.Converters
5 | {
6 | public sealed class BoolToAppearanceConverter : IValueConverter
7 | {
8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
9 | {
10 | if ((bool)value)
11 | {
12 | return "Primary";
13 | }
14 | else
15 | {
16 | return "Secondary";
17 | }
18 | }
19 |
20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
21 | {
22 | throw new NotImplementedException();
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Shared/Converters/FallbackBrushConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Windows.Data;
3 | using System.Windows.Media;
4 |
5 | namespace Shared.Converters;
6 |
7 | public class FallbackBrushConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | return value switch
12 | {
13 | SolidColorBrush brush => brush,
14 | Color color => new SolidColorBrush(color),
15 | _ => new SolidColorBrush(Colors.Red)
16 | };
17 | }
18 |
19 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
20 | {
21 | throw new NotImplementedException();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Shared/Converters/InverseBooleanConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Windows.Data;
3 |
4 | namespace Shared.Converters
5 | {
6 | public sealed class InverseBooleanConverter : IValueConverter
7 | {
8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
9 | {
10 | return !(bool)value;
11 | }
12 |
13 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
14 | {
15 | throw new NotImplementedException();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Shared/Converters/IsCustomizedAccentColorConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Windows.Data;
3 |
4 | namespace Shared.Converters
5 | {
6 | public sealed class IsCustomizedAccentColorConverter : IValueConverter
7 | {
8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
9 | {
10 | if ((bool)value)
11 | {
12 | if ((string)parameter == "System")
13 | {
14 | return false;
15 | }
16 | else
17 | {
18 | return true;
19 | }
20 | }
21 | else
22 | {
23 | if ((string)parameter == "System")
24 | {
25 | return true;
26 | }
27 | else
28 | {
29 | return false;
30 | }
31 | }
32 | }
33 |
34 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
35 | {
36 | if ((string)parameter == "System")
37 | {
38 | return false;
39 | }
40 | else
41 | {
42 | return true;
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Shared/Converters/StringToBrushConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Windows.Data;
3 | using System.Windows.Media;
4 |
5 | namespace Shared.Converters
6 | {
7 | public sealed class StringToBrushConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | return new SolidColorBrush((Color)ColorConverter.ConvertFromString((string)value));
12 | }
13 |
14 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
15 | {
16 | throw new NotImplementedException();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Shared/Converters/StringToImageSourceConverter.cs:
--------------------------------------------------------------------------------
1 | using Shared.Helpers;
2 | using System.Globalization;
3 | using System.Windows.Data;
4 |
5 | namespace Shared.Converters
6 | {
7 | public sealed class StringToImageSourceConverter : IValueConverter
8 | {
9 | public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | return ImageProcess.StringToBitmapImage((string)value);
12 | }
13 |
14 | public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
15 | {
16 | return null;
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/Shared/Extensions/TextBoxAttachedProperties.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Input;
5 |
6 | namespace Shared.Extensions
7 | {
8 | public partial class TextBoxAttachedProperties
9 | {
10 | [GeneratedRegex("[^0-9]+")]
11 | private static partial Regex MyRegex();
12 |
13 | public static bool GetIsOnlyNumber(DependencyObject obj)
14 | {
15 | return (bool)obj.GetValue(IsOnlyNumberProperty);
16 | }
17 |
18 | public static void SetIsOnlyNumber(DependencyObject obj, bool value)
19 | {
20 | obj.SetValue(IsOnlyNumberProperty, value);
21 | }
22 |
23 | public static readonly DependencyProperty IsOnlyNumberProperty =
24 | DependencyProperty.RegisterAttached("IsOnlyNumber", typeof(bool), typeof(TextBox), new PropertyMetadata(false,
25 | (s, e) =>
26 | {
27 | if (s is TextBox textBox)
28 | {
29 | textBox.SetValue(InputMethod.IsInputMethodEnabledProperty, !(bool)e.NewValue);
30 | textBox.PreviewTextInput -= TxtInput;
31 | if ((bool)e.NewValue)
32 | {
33 | textBox.PreviewTextInput += TxtInput;
34 | }
35 | }
36 | }));
37 |
38 | private static void TxtInput(object sender, TextCompositionEventArgs e)
39 | {
40 | e.Handled = MyRegex().IsMatch(e.Text);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Shared/Helpers/FaceRecognition.cs:
--------------------------------------------------------------------------------
1 | using Hompus.VideoInputDevices;
2 | using Newtonsoft.Json;
3 | using OpenCvSharp;
4 | using Shared.Models;
5 | using System.Drawing;
6 | using System.Windows.Media.Imaging;
7 | using ViewFaceCore;
8 | using ViewFaceCore.Configs;
9 | using ViewFaceCore.Core;
10 | using ViewFaceCore.Model;
11 |
12 | namespace Shared.Helpers
13 | {
14 | public static class FaceRecognition
15 | {
16 | private static VideoCapture videoCapture = new();
17 | private static readonly FaceDetector faceDetector;
18 | private static readonly FaceLandmarker faceMark;
19 | private static readonly FaceRecognizer faceRecognizer = new();
20 | private static readonly Pen pen = new(Color.Red, 2);
21 | private static readonly float videoCaptureFps = 18;
22 |
23 | public static bool IsCameraOpened { get; set; }
24 |
25 | public static int SleepTime
26 | {
27 | get
28 | {
29 | return (int)Math.Round(1000 / videoCaptureFps);
30 | }
31 | }
32 |
33 | public static Dictionary SystemCameraDevices
34 | {
35 | get
36 | {
37 | using SystemDeviceEnumerator sde = new();
38 | Dictionary devices = [];
39 | try
40 | {
41 | IReadOnlyDictionary systemDevices = sde.ListVideoInputDevice();
42 | foreach (var device in systemDevices)
43 | {
44 | devices.Add(device.Value, device.Key);
45 | }
46 | }
47 | catch
48 | {
49 | devices.Add("暂无摄像头", -1);
50 | }
51 | return devices;
52 | }
53 | }
54 |
55 | static FaceRecognition()
56 | {
57 | FaceDetectConfig config = new()
58 | {
59 | FaceSize = 80
60 | };
61 | faceDetector = new(config);
62 |
63 | FaceLandmarkConfig faceLandmarkconfig = new()
64 | {
65 | MarkType = MarkType.Light,
66 | DeviceType = DeviceType.CPU
67 | };
68 | faceMark = new(faceLandmarkconfig);
69 | }
70 |
71 | public static Bitmap GetImage()
72 | {
73 | using Mat v_mat = new();
74 | videoCapture.Read(v_mat);
75 | if (v_mat != null)
76 | {
77 | return (Bitmap)Image.FromStream(v_mat.ToMemoryStream());
78 | }
79 | else
80 | {
81 | return new Bitmap(2, 2);
82 | }
83 | }
84 |
85 | public static Bitmap GetMaskImage(Bitmap cameraImage)
86 | {
87 | Bitmap mask = new(cameraImage.Width, cameraImage.Height);
88 | using Graphics maskGraphics = Graphics.FromImage(mask);
89 |
90 | FaceInfo[] faceInfos = faceDetector.Detect(cameraImage);
91 | foreach (FaceInfo face in faceInfos)
92 | {
93 | maskGraphics.DrawRectangle(pen, face.Location.X, face.Location.Y, face.Location.Width, face.Location.Height);
94 | }
95 | return mask;
96 | }
97 |
98 | public static (Bitmap, float[], int, int) GetMaskAndName(Bitmap cameraImage)
99 | {
100 | Bitmap mask = new(cameraImage.Width, cameraImage.Height);
101 | using Graphics maskGraphics = Graphics.FromImage(mask);
102 |
103 | FaceInfo[] faceInfos = faceDetector.Detect(cameraImage);
104 | if (faceInfos.Length > 0)
105 | {
106 | FaceInfo faceInfo = faceInfos[0];
107 | FaceMarkPoint[] faceMarkPoint = faceMark.Mark(cameraImage, faceInfo);
108 |
109 | maskGraphics.DrawRectangle(pen, faceInfo.Location.X, faceInfo.Location.Y, faceInfo.Location.Width, faceInfo.Location.Height);
110 |
111 | float[] feature = faceRecognizer.Extract(cameraImage, faceMarkPoint);
112 | return (mask, feature, faceInfo.Location.X, faceInfo.Location.Y);
113 | }
114 | else
115 | {
116 | return (mask, [], 0, 0);
117 | }
118 |
119 | }
120 |
121 | public async static ValueTask GetFace(Bitmap cameraImage)
122 | {
123 | FaceInfo[] faceInfos = await faceDetector.DetectAsync(cameraImage);
124 |
125 | if (faceInfos.Length > 0)
126 | {
127 | FaceInfo faceInfo = faceInfos[0];
128 | FaceMarkPoint[] faceMarkPoint = faceMark.Mark(cameraImage, faceInfo);
129 |
130 | Rectangle cropArea = new(faceInfo.Location.X, faceInfo.Location.Y, faceInfo.Location.Width, faceInfo.Location.Height);
131 | using Bitmap croppedBitmap = cameraImage.Clone(cropArea, cameraImage.PixelFormat);
132 | Bitmap a = ImageProcess.Resize(croppedBitmap, 164, 216);
133 |
134 | return new EncodingFace(cameraImage, ImageProcess.BitmapToBitmapImage(a), faceInfo, faceMarkPoint);
135 | }
136 | else
137 | {
138 | return new EncodingFace(new Bitmap(2, 2), new BitmapImage(), new FaceInfo(), []);
139 | }
140 | }
141 |
142 | public static List GetFaceQualitys(List faces)
143 | {
144 | using FaceQuality faceQuality = new(new());
145 | List faceQualitys = [];
146 | foreach (EncodingFace face in faces)
147 | {
148 | faceQualitys.Add(faceQuality.Detect(face.FullImage, face.FaceInfo, face.FaceMarkPoints, QualityType.Pose).Score);
149 | }
150 | return faceQualitys;
151 | }
152 |
153 | public static float[] GetFaceFeature(EncodingFace face)
154 | {
155 | return faceRecognizer.Extract(face.FullImage, face.FaceMarkPoints);
156 | }
157 |
158 | public static string GetFaceFeatureString(EncodingFace face)
159 | {
160 | return JsonConvert.SerializeObject(GetFaceFeature(face));
161 | }
162 |
163 | public static float[] GetFaceFeatureFromString(string faceFeature)
164 | {
165 | return JsonConvert.DeserializeObject(faceFeature) ?? [];
166 | }
167 |
168 | public static bool IsSelf(float[] a, float[] b)
169 | {
170 | return faceRecognizer.IsSelf(a, b);
171 | }
172 |
173 | public static bool OpenCamera(int videoCapture_id, out string message)
174 | {
175 | videoCapture = new();
176 | videoCapture.Set(VideoCaptureProperties.FrameWidth, 960);
177 | videoCapture.Set(VideoCaptureProperties.FrameHeight, 564);//高度
178 | videoCapture.Set(VideoCaptureProperties.Fps, (int)videoCaptureFps);
179 | try
180 | {
181 | message = string.Empty;
182 | videoCapture.Open(videoCapture_id, VideoCaptureAPIs.DSHOW);
183 | if (videoCapture.IsOpened())
184 | {
185 | IsCameraOpened = true;
186 | return true;
187 | }
188 | return false;
189 | }
190 | catch (Exception e)
191 | {
192 | {
193 | message = e.Message;
194 | return false;
195 | }
196 | }
197 | }
198 |
199 | public static void CloseCamera()
200 | {
201 | if (videoCapture.IsOpened())
202 | {
203 | IsCameraOpened = false;
204 | videoCapture.Release();
205 | }
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/Shared/Helpers/FileOccupancy.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace Shared.Helpers
4 | {
5 | public static class FileOccupancy
6 | {
7 | public static string GetDirectorySize(string path)
8 | {
9 | if (Directory.Exists(path))
10 | {
11 | DirectoryInfo folder = new(path);
12 | return FormatBytes(FolderSize(folder));
13 | }
14 | else
15 | {
16 | return "0 bytes";
17 | }
18 | }
19 |
20 | public static string GetFileSize(string path)
21 | {
22 | if (File.Exists(path))
23 | {
24 | FileInfo fileInfo = new(path);
25 | return FormatBytes(fileInfo.Length);
26 | }
27 | else
28 | {
29 | return "0 bytes";
30 | }
31 | }
32 |
33 | private static long FolderSize(DirectoryInfo folder)
34 | {
35 | long totalSizeOfDir = 0;
36 |
37 | // 获取目录中的所有文件
38 | FileInfo[] allFiles = folder.GetFiles();
39 |
40 | // 循环遍历每个文件并获取其大小
41 | foreach (FileInfo file in allFiles)
42 | {
43 | totalSizeOfDir += file.Length;
44 |
45 | // 在这里计算长度。
46 | }
47 |
48 | DirectoryInfo[] subFolders = folder.GetDirectories();
49 |
50 | // 在这里,我们查看文件中是否存在子文件夹或目录。
51 | foreach (DirectoryInfo dir in subFolders)
52 | {
53 | totalSizeOfDir += FolderSize(dir);
54 |
55 | // 在这里,我们递归调用来检查所有子文件夹。
56 | }
57 | return totalSizeOfDir;
58 |
59 | // 我们返回总大小在这里。
60 | }
61 |
62 | private static string FormatBytes(long bytes)
63 | {
64 | /*此方法基本上用于确定文件的大小。它首先确定我们是否必须在字节,KB,MB或GB中完成。如果大小在1KB和1MB之间,那么我们将计算KB的大小。同样,如果在MB和GB之间,则将其计算为MB。*/
65 | string[] sizes = ["bytes", "KB", "MB", "GB", "TB"];
66 |
67 | // 在这里,我们将所有大小存储在字符串中。
68 | int order = 0;
69 |
70 | // 我们使用零初始化顺序,以便它不返回一些垃圾值。
71 | while (bytes >= 1024 && order < sizes.Length - 1)
72 | {
73 | order++;
74 | bytes /= 1024;
75 | }
76 | return $"{bytes:0.##} {sizes[order]}";
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Shared/Helpers/ImageProcess.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Drawing.Imaging;
3 | using System.IO;
4 | using System.Windows;
5 | using System.Windows.Media;
6 | using System.Windows.Media.Imaging;
7 |
8 | namespace Shared.Helpers
9 | {
10 | public static class ImageProcess
11 | {
12 | public static BitmapImage? StringToBitmapImage(string path)
13 | {
14 | BitmapImage? bitmapImage = null;
15 | if (!string.IsNullOrEmpty(path))
16 | {
17 | if (path.StartsWith("pack"))
18 | {
19 | bitmapImage = new(new Uri(path));
20 | }
21 | else
22 | {
23 | using BinaryReader reader = new(File.Open(path, FileMode.Open));
24 | FileInfo fi = new(path);
25 | byte[] bytes = reader.ReadBytes((int)fi.Length);
26 | reader.Close();
27 | bitmapImage = new()
28 | {
29 | CacheOption = BitmapCacheOption.OnLoad
30 | };
31 | bitmapImage.BeginInit();
32 | bitmapImage.StreamSource = new MemoryStream(bytes);
33 | bitmapImage.EndInit();
34 | }
35 | if (bitmapImage.CanFreeze)
36 | {
37 | bitmapImage.Freeze();
38 | }
39 | }
40 | return bitmapImage;
41 | }
42 |
43 | public static Bitmap Resize(Image image, int maxWidth, int maxHeight)
44 | {
45 | //模版的宽高比例
46 | double resultRate = (double)maxWidth / maxHeight;
47 | //原图片的宽高比例
48 | double initRate = (double)image.Width / image.Height;
49 |
50 | //原图与模版比例相等,直接缩放
51 | if (resultRate == initRate)
52 | {
53 | //按模版大小生成最终图片
54 | Bitmap resultImage = new(maxWidth, maxHeight);
55 | using Graphics graphics = Graphics.FromImage(resultImage);
56 | graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
57 | graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
58 | graphics.Clear(System.Drawing.Color.Transparent);
59 | graphics.DrawImage(image, new Rectangle(-1, -1, maxWidth, maxHeight), new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);
60 | return resultImage;
61 | }
62 | //原图与模版比例不等,裁剪后缩放
63 | else
64 | {
65 | //裁剪对象
66 | Bitmap tempImage;
67 | Graphics tempGraphics;
68 |
69 | //定位
70 | Rectangle fromR = new(0, 0, 0, 0);//原图裁剪定位
71 | Rectangle toR = new(0, 0, 0, 0);//目标定位
72 |
73 | //宽为标准进行裁剪
74 | if (resultRate > initRate)
75 | {
76 | //裁剪对象实例化
77 | tempImage = new Bitmap(image.Width, (int)Math.Floor(image.Width / resultRate));
78 | tempGraphics = Graphics.FromImage(tempImage);
79 |
80 | //裁剪源定位
81 | fromR.X = 0;
82 | fromR.Y = (int)Math.Floor((image.Height - image.Width / resultRate) / 2);
83 | fromR.Width = image.Width;
84 | fromR.Height = (int)Math.Floor(image.Width / resultRate);
85 |
86 | //裁剪目标定位
87 | toR.X = 0;
88 | toR.Y = 0;
89 | toR.Width = image.Width;
90 | toR.Height = (int)Math.Floor(image.Width / resultRate);
91 | }
92 | //高为标准进行裁剪
93 | else
94 | {
95 | tempImage = new Bitmap((int)Math.Floor(image.Height * resultRate), image.Height);
96 | tempGraphics = Graphics.FromImage(tempImage);
97 |
98 | fromR.X = (int)Math.Floor((image.Width - image.Height * resultRate) / 2);
99 | fromR.Y = 0;
100 | fromR.Width = (int)Math.Floor(image.Height * resultRate);
101 | fromR.Height = image.Height;
102 |
103 | toR.X = 0;
104 | toR.Y = 0;
105 | toR.Width = (int)Math.Floor(image.Height * resultRate);
106 | toR.Height = image.Height;
107 | }
108 |
109 | //设置质量
110 | tempGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
111 | tempGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
112 |
113 | //裁剪
114 | tempGraphics.DrawImage(image, toR, fromR, GraphicsUnit.Pixel);
115 |
116 | //按模版大小生成最终图片
117 | Bitmap resultImage = new(maxWidth, maxHeight);
118 | using Graphics resultGraphics = Graphics.FromImage(resultImage);
119 | resultGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
120 | resultGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
121 | resultGraphics.Clear(System.Drawing.Color.Transparent);
122 | resultGraphics.DrawImage(tempImage, new Rectangle(0, 0, maxWidth, maxHeight), new Rectangle(0, 0, tempImage.Width, tempImage.Height), GraphicsUnit.Pixel);
123 |
124 | tempGraphics.Dispose();
125 | tempImage.Dispose();
126 | return resultImage;
127 | }
128 | }
129 |
130 | public static void DrawText(this Bitmap image, string text, int x, int y)
131 | {
132 | using Graphics graphics = Graphics.FromImage(image);
133 | using Font font = new("Microsoft YaHei", 16);
134 | using SolidBrush brush = new(System.Drawing.Color.Red);
135 | graphics.DrawString(text, font, brush, x - 6, y - 40);
136 | }
137 |
138 | public static Bitmap ImageSourceToBitmap(ImageSource imageSource)
139 | {
140 | BitmapSource m = (BitmapSource)imageSource;
141 |
142 | Bitmap bmp = new(m.PixelWidth, m.PixelHeight, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
143 |
144 | BitmapData data = bmp.LockBits(
145 | new Rectangle(System.Drawing.Point.Empty, bmp.Size), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
146 |
147 | m.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride); bmp.UnlockBits(data);
148 |
149 | return bmp;
150 | }
151 |
152 | public static BitmapImage BitmapToBitmapImage(Bitmap bitmap)
153 | {
154 | using MemoryStream stream = new();
155 | bitmap.Save(stream, ImageFormat.Jpeg);
156 |
157 | stream.Position = 0;
158 | BitmapImage result = new();
159 | result.BeginInit();
160 | result.CacheOption = BitmapCacheOption.OnLoad;
161 | result.StreamSource = stream;
162 | result.EndInit();
163 | result.Freeze();
164 | return result;
165 | }
166 |
167 | public static BitmapImage BitmapToPngBitmapImage(Bitmap bitmap)
168 | {
169 | using MemoryStream stream = new();
170 | bitmap.Save(stream, ImageFormat.Png);
171 |
172 | stream.Position = 0;
173 | BitmapImage result = new();
174 | result.BeginInit();
175 | result.CacheOption = BitmapCacheOption.OnLoad;
176 | result.StreamSource = stream;
177 | result.EndInit();
178 | result.Freeze();
179 | return result;
180 | }
181 |
182 | public static byte[] BitmapImageToByte(BitmapImage bmp)
183 | {
184 | JpegBitmapEncoder encoder = new();
185 | encoder.Frames.Add(BitmapFrame.Create(bmp));
186 | using MemoryStream stream = new();
187 | encoder.Save(stream);
188 | return stream.ToArray();
189 | }
190 |
191 | public static BitmapImage ByteToBitmapImage(byte[] bytes)
192 | {
193 | BitmapImage bitmapImage = new();
194 | bitmapImage.BeginInit();
195 | bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
196 | bitmapImage.StreamSource = new MemoryStream(bytes);
197 | bitmapImage.EndInit();
198 | if (bitmapImage.CanFreeze)
199 | {
200 | bitmapImage.Freeze();
201 | }
202 | return bitmapImage;
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/Shared/Helpers/SQLiteHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Data;
2 | using System.Data.Common;
3 | using System.Data.SQLite;
4 |
5 | namespace Shared.Helpers
6 | {
7 | public class SQLiteHelper
8 | {
9 | protected string DataSource = string.Empty;
10 |
11 | protected SQLiteHelper(string filename)
12 | {
13 | DataSource = @".\database\" + filename;
14 | }
15 |
16 | protected SQLiteConnection GetSQLiteConnection()
17 | {
18 | string connStr = string.Format("Data Source={0}", DataSource);
19 | var con = new SQLiteConnection(connStr);
20 | return con;
21 | }
22 |
23 | protected static void PrepareCommand(SQLiteCommand cmd, SQLiteConnection conn, string sqlStr, params SQLiteParameter[]? p)
24 | {
25 | if (conn.State != ConnectionState.Open)
26 | {
27 | conn.Open();
28 | }
29 | cmd.Parameters.Clear();
30 | cmd.Connection = conn;
31 | cmd.CommandText = sqlStr;
32 | cmd.CommandType = CommandType.Text;
33 | cmd.CommandTimeout = 30;
34 | if (p != null && p.Length >= 1)
35 | {
36 | foreach (SQLiteParameter parm in p)
37 | {
38 | cmd.Parameters.AddWithValue(parm.ParameterName, parm.Value);
39 | }
40 | }
41 | }
42 |
43 | protected static async Task PrepareCommandAsync(SQLiteCommand cmd, SQLiteConnection conn, string sqlStr, params SQLiteParameter[]? p)
44 | {
45 | if (conn.State != ConnectionState.Open)
46 | {
47 | await conn.OpenAsync();
48 | }
49 | cmd.Parameters.Clear();
50 | cmd.Connection = conn;
51 | cmd.CommandText = sqlStr;
52 | cmd.CommandType = CommandType.Text;
53 | cmd.CommandTimeout = 30;
54 | if (p != null && p.Length >= 1)
55 | {
56 | foreach (SQLiteParameter parm in p)
57 | {
58 | cmd.Parameters.AddWithValue(parm.ParameterName, parm.Value);
59 | }
60 | }
61 | }
62 |
63 | protected DataTable ExecuteDataTable(string cmdText, params SQLiteParameter[]? data)
64 | {
65 | var dt = new DataTable();
66 | using (SQLiteConnection connection = GetSQLiteConnection())
67 | {
68 | var command = new SQLiteCommand();
69 | PrepareCommand(command, connection, cmdText, data);
70 | SQLiteDataReader reader = command.ExecuteReader();
71 | dt.Load(reader);
72 | }
73 | return dt;
74 | }
75 |
76 | protected async ValueTask ExecuteDataTableAsync(string cmdText, params SQLiteParameter[]? data)
77 | {
78 | var dt = new DataTable();
79 | using (SQLiteConnection connection = GetSQLiteConnection())
80 | {
81 | var command = new SQLiteCommand();
82 | await PrepareCommandAsync(command, connection, cmdText, data);
83 | DbDataReader reader = await command.ExecuteReaderAsync();
84 | dt.Load(reader);
85 | }
86 | return dt;
87 | }
88 |
89 | protected int ExecuteNonQuery(string cmdText, params SQLiteParameter[]? data)
90 | {
91 | using SQLiteConnection connection = GetSQLiteConnection();
92 | var command = new SQLiteCommand();
93 | PrepareCommand(command, connection, cmdText, data);
94 | return command.ExecuteNonQuery();
95 | }
96 |
97 | protected async ValueTask ExecuteNonQueryAsync(string cmdText, params SQLiteParameter[]? data)
98 | {
99 | using SQLiteConnection connection = GetSQLiteConnection();
100 | SQLiteCommand command = new();
101 | await PrepareCommandAsync(command, connection, cmdText, data);
102 | return await command.ExecuteNonQueryAsync();
103 | }
104 |
105 | protected SQLiteDataReader ExecuteReader(string cmdText, params SQLiteParameter[]? data)
106 | {
107 | var command = new SQLiteCommand();
108 | using SQLiteConnection connection = GetSQLiteConnection();
109 | PrepareCommand(command, connection, cmdText, data);
110 | SQLiteDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);
111 | return reader;
112 | }
113 |
114 | protected async ValueTask ExecuteReaderAsync(string cmdText, params SQLiteParameter[]? data)
115 | {
116 | var command = new SQLiteCommand();
117 | using SQLiteConnection connection = GetSQLiteConnection();
118 | await PrepareCommandAsync(command, connection, cmdText, data);
119 | DbDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.CloseConnection);
120 | return reader;
121 | }
122 |
123 | protected object ExecuteScalar(string cmdText, params SQLiteParameter[]? data)
124 | {
125 | using SQLiteConnection connection = GetSQLiteConnection();
126 | var cmd = new SQLiteCommand();
127 | PrepareCommand(cmd, connection, cmdText, data);
128 | return cmd.ExecuteScalar();
129 | }
130 |
131 | protected async ValueTask