添加项目文件。
This commit is contained in:
43
PRD.md
Normal file
43
PRD.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# TodoList 产品需求文档 (PRD)
|
||||
|
||||
## 1. 项目概述
|
||||
本项目是一个基于 C# WPF (.NET 10) 开发的桌面待办事项管理应用 (TodoList)。旨在提供轻量、高效的任务管理体验,特别是通过快捷键快速唤起记录功能,最大化用户的操作效率。
|
||||
|
||||
## 2. 技术架构
|
||||
- **开发语言**: C#
|
||||
- **UI 框架**: WPF (Windows Presentation Foundation)
|
||||
- **目标框架**: .NET 10
|
||||
- **操作系统**: Windows
|
||||
|
||||
## 3. 功能需求
|
||||
|
||||
### 3.1 核心功能:快速记录 (Quick Entry)
|
||||
- **全局快捷键**:
|
||||
- 允许用户注册/使用系统级全局快捷键(例如 `Ctrl + Alt + A` 或其他不冲突的组合)。
|
||||
- 支持在应用后台运行时响应快捷键。
|
||||
- **快速唤起**:
|
||||
- 按下快捷键时,若应用最小化或隐藏,应立即弹出“新建任务”窗口或主界面。
|
||||
- 窗口弹出后,输入框应自动获取焦点,用户可直接打字。
|
||||
|
||||
### 3.2 任务模型 (Task Model)
|
||||
每个任务需包含以下核心字段:
|
||||
1. **任务名称 (Title/Content)**: 任务的具体描述。
|
||||
2. **紧急程度 (Priority/Urgency)**:
|
||||
- 用于区分任务优先级(如:高、中、低)。
|
||||
- 需在界面上有直观的视觉区分(如颜色标记)。
|
||||
3. **完成状态 (IsCompleted)**:
|
||||
- 标记任务是否已完成。
|
||||
|
||||
### 3.3 任务列表与视图 (Task List & View)
|
||||
- **列表展示**: 展示当前所有未完成的任务。
|
||||
- **默认过滤**:
|
||||
- 应用启动或刷新时,**默认隐藏已完成的任务**。
|
||||
- (可选) 提供“显示已完成任务”的切换开关以便查看历史记录。
|
||||
|
||||
### 3.4 离线与同步 (Offline & Sync)
|
||||
- **离线记录**: 支持完全离线使用,数据优先保存于本地。
|
||||
- **数据同步**: 在网络可用时(或特定时机),自动将本地数据同步到服务端(预留同步机制)。
|
||||
|
||||
## 4. 非功能需求
|
||||
- **性能**: 启动速度快,快捷键响应低延迟。
|
||||
- **持久化**: 任务数据需保存到本地(如 SQLite, JSON, 或 XML),保证关闭应用后数据不丢失。
|
||||
956
Setup/Setup.vdproj
Normal file
956
Setup/Setup.vdproj
Normal file
@@ -0,0 +1,956 @@
|
||||
"DeployProject"
|
||||
{
|
||||
"VSVersion" = "3:800"
|
||||
"ProjectType" = "8:{978C614F-708E-4E1A-B201-565925725DBA}"
|
||||
"IsWebType" = "8:FALSE"
|
||||
"ProjectName" = "8:Setup"
|
||||
"LanguageId" = "3:2052"
|
||||
"CodePage" = "3:936"
|
||||
"UILanguageId" = "3:2052"
|
||||
"SccProjectName" = "8:"
|
||||
"SccLocalPath" = "8:"
|
||||
"SccAuxPath" = "8:"
|
||||
"SccProvider" = "8:"
|
||||
"Hierarchy"
|
||||
{
|
||||
"Entry"
|
||||
{
|
||||
"MsmKey" = "8:_17BA67E2733E421586A690779AE10839"
|
||||
"OwnerKey" = "8:_UNDEFINED"
|
||||
"MsmSig" = "8:_UNDEFINED"
|
||||
}
|
||||
"Entry"
|
||||
{
|
||||
"MsmKey" = "8:_1E301C03308E4C0AADE54375ACCA27E9"
|
||||
"OwnerKey" = "8:_UNDEFINED"
|
||||
"MsmSig" = "8:_UNDEFINED"
|
||||
}
|
||||
"Entry"
|
||||
{
|
||||
"MsmKey" = "8:_34EE79020CA44431B5F09CF100F2B372"
|
||||
"OwnerKey" = "8:_UNDEFINED"
|
||||
"MsmSig" = "8:_UNDEFINED"
|
||||
}
|
||||
"Entry"
|
||||
{
|
||||
"MsmKey" = "8:_42AD4BACEFC94D43B5DE5DDF9177EFAD"
|
||||
"OwnerKey" = "8:_UNDEFINED"
|
||||
"MsmSig" = "8:_UNDEFINED"
|
||||
}
|
||||
"Entry"
|
||||
{
|
||||
"MsmKey" = "8:_4C87263B310A4D3F8CC1BD3265F5B929"
|
||||
"OwnerKey" = "8:_UNDEFINED"
|
||||
"MsmSig" = "8:_UNDEFINED"
|
||||
}
|
||||
"Entry"
|
||||
{
|
||||
"MsmKey" = "8:_65835E291AAA6A7D2371E2E621AF3DC9"
|
||||
"OwnerKey" = "8:_17BA67E2733E421586A690779AE10839"
|
||||
"MsmSig" = "8:_UNDEFINED"
|
||||
}
|
||||
"Entry"
|
||||
{
|
||||
"MsmKey" = "8:_6792D68F0F43409CADA40277C6FE4B43"
|
||||
"OwnerKey" = "8:_UNDEFINED"
|
||||
"MsmSig" = "8:_UNDEFINED"
|
||||
}
|
||||
"Entry"
|
||||
{
|
||||
"MsmKey" = "8:_D50F9EB4AAE7436FBAD775A446017958"
|
||||
"OwnerKey" = "8:_UNDEFINED"
|
||||
"MsmSig" = "8:_UNDEFINED"
|
||||
}
|
||||
"Entry"
|
||||
{
|
||||
"MsmKey" = "8:_UNDEFINED"
|
||||
"OwnerKey" = "8:_17BA67E2733E421586A690779AE10839"
|
||||
"MsmSig" = "8:_UNDEFINED"
|
||||
}
|
||||
}
|
||||
"Configurations"
|
||||
{
|
||||
"Debug"
|
||||
{
|
||||
"DisplayName" = "8:Debug"
|
||||
"IsDebugOnly" = "11:TRUE"
|
||||
"IsReleaseOnly" = "11:FALSE"
|
||||
"OutputFilename" = "8:Debug\\Setup.msi"
|
||||
"PackageFilesAs" = "3:2"
|
||||
"PackageFileSize" = "3:-2147483648"
|
||||
"CabType" = "3:1"
|
||||
"Compression" = "3:2"
|
||||
"SignOutput" = "11:FALSE"
|
||||
"CertificateFile" = "8:"
|
||||
"PrivateKeyFile" = "8:"
|
||||
"TimeStampServer" = "8:"
|
||||
"InstallerBootstrapper" = "3:2"
|
||||
}
|
||||
"Release"
|
||||
{
|
||||
"DisplayName" = "8:Release"
|
||||
"IsDebugOnly" = "11:FALSE"
|
||||
"IsReleaseOnly" = "11:TRUE"
|
||||
"OutputFilename" = "8:Release\\Setup.msi"
|
||||
"PackageFilesAs" = "3:2"
|
||||
"PackageFileSize" = "3:-2147483648"
|
||||
"CabType" = "3:1"
|
||||
"Compression" = "3:2"
|
||||
"SignOutput" = "11:FALSE"
|
||||
"CertificateFile" = "8:"
|
||||
"PrivateKeyFile" = "8:"
|
||||
"TimeStampServer" = "8:"
|
||||
"InstallerBootstrapper" = "3:2"
|
||||
"BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}"
|
||||
{
|
||||
"Enabled" = "11:FALSE"
|
||||
"PromptEnabled" = "11:TRUE"
|
||||
"PrerequisitesLocation" = "2:1"
|
||||
"Url" = "8:"
|
||||
"ComponentsUrl" = "8:"
|
||||
"Items"
|
||||
{
|
||||
"{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2"
|
||||
{
|
||||
"Name" = "8:Microsoft .NET Framework 4.7.2 (x86 和 x64)"
|
||||
"ProductCode" = "8:.NETFramework,Version=v4.7.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"Deployable"
|
||||
{
|
||||
"CustomAction"
|
||||
{
|
||||
}
|
||||
"DefaultFeature"
|
||||
{
|
||||
"Name" = "8:DefaultFeature"
|
||||
"Title" = "8:"
|
||||
"Description" = "8:"
|
||||
}
|
||||
"ExternalPersistence"
|
||||
{
|
||||
"LaunchCondition"
|
||||
{
|
||||
"{A06ECF26-33A3-4562-8140-9B0E340D4F24}:_3DB760AE720B4C9DB0261DBC0A13DD96"
|
||||
{
|
||||
"Name" = "8:.NET Framework"
|
||||
"Message" = "8:[VSDNETMSG]"
|
||||
"FrameworkVersion" = "8:.NETFramework,Version=v4.7.2"
|
||||
"AllowLaterVersions" = "11:FALSE"
|
||||
"InstallUrl" = "8:http://go.microsoft.com/fwlink/?LinkId=863262"
|
||||
}
|
||||
}
|
||||
}
|
||||
"File"
|
||||
{
|
||||
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_17BA67E2733E421586A690779AE10839"
|
||||
{
|
||||
"AssemblyRegister" = "3:1"
|
||||
"AssemblyIsInGAC" = "11:FALSE"
|
||||
"AssemblyAsmDisplayName" = "8:TodoList, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"
|
||||
"ScatterAssemblies"
|
||||
{
|
||||
"_17BA67E2733E421586A690779AE10839"
|
||||
{
|
||||
"Name" = "8:TodoList.dll"
|
||||
"Attributes" = "3:512"
|
||||
}
|
||||
}
|
||||
"SourcePath" = "8:..\\TodoList\\bin\\Debug\\net10.0-windows\\TodoList.dll"
|
||||
"TargetName" = "8:"
|
||||
"Tag" = "8:"
|
||||
"Folder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Vital" = "11:TRUE"
|
||||
"ReadOnly" = "11:FALSE"
|
||||
"Hidden" = "11:FALSE"
|
||||
"System" = "11:FALSE"
|
||||
"Permanent" = "11:FALSE"
|
||||
"SharedLegacy" = "11:FALSE"
|
||||
"PackageAs" = "3:1"
|
||||
"Register" = "3:1"
|
||||
"Exclude" = "11:FALSE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"IsolateTo" = "8:"
|
||||
}
|
||||
"{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_1E301C03308E4C0AADE54375ACCA27E9"
|
||||
{
|
||||
"SourcePath" = "8:..\\TodoList\\bin\\Debug\\net10.0-windows\\TodoList.exe"
|
||||
"TargetName" = "8:TodoList.exe"
|
||||
"Tag" = "8:"
|
||||
"Folder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Vital" = "11:TRUE"
|
||||
"ReadOnly" = "11:FALSE"
|
||||
"Hidden" = "11:FALSE"
|
||||
"System" = "11:FALSE"
|
||||
"Permanent" = "11:FALSE"
|
||||
"SharedLegacy" = "11:FALSE"
|
||||
"PackageAs" = "3:1"
|
||||
"Register" = "3:1"
|
||||
"Exclude" = "11:FALSE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"IsolateTo" = "8:"
|
||||
}
|
||||
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_34EE79020CA44431B5F09CF100F2B372"
|
||||
{
|
||||
"AssemblyRegister" = "3:1"
|
||||
"AssemblyIsInGAC" = "11:FALSE"
|
||||
"AssemblyAsmDisplayName" = "8:CommunityToolkit.Mvvm, Version=8.4.0.0, Culture=neutral, PublicKeyToken=4aff67a105548ee2, processorArchitecture=MSIL"
|
||||
"ScatterAssemblies"
|
||||
{
|
||||
"_34EE79020CA44431B5F09CF100F2B372"
|
||||
{
|
||||
"Name" = "8:CommunityToolkit.Mvvm.dll"
|
||||
"Attributes" = "3:512"
|
||||
}
|
||||
}
|
||||
"SourcePath" = "8:..\\TodoList\\bin\\Debug\\net10.0-windows\\CommunityToolkit.Mvvm.dll"
|
||||
"TargetName" = "8:"
|
||||
"Tag" = "8:"
|
||||
"Folder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Vital" = "11:TRUE"
|
||||
"ReadOnly" = "11:FALSE"
|
||||
"Hidden" = "11:FALSE"
|
||||
"System" = "11:FALSE"
|
||||
"Permanent" = "11:FALSE"
|
||||
"SharedLegacy" = "11:FALSE"
|
||||
"PackageAs" = "3:1"
|
||||
"Register" = "3:1"
|
||||
"Exclude" = "11:FALSE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"IsolateTo" = "8:"
|
||||
}
|
||||
"{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_42AD4BACEFC94D43B5DE5DDF9177EFAD"
|
||||
{
|
||||
"SourcePath" = "8:..\\TodoList\\bin\\Debug\\net10.0-windows\\TodoList.runtimeconfig.json"
|
||||
"TargetName" = "8:TodoList.runtimeconfig.json"
|
||||
"Tag" = "8:"
|
||||
"Folder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Vital" = "11:TRUE"
|
||||
"ReadOnly" = "11:FALSE"
|
||||
"Hidden" = "11:FALSE"
|
||||
"System" = "11:FALSE"
|
||||
"Permanent" = "11:FALSE"
|
||||
"SharedLegacy" = "11:FALSE"
|
||||
"PackageAs" = "3:1"
|
||||
"Register" = "3:1"
|
||||
"Exclude" = "11:FALSE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"IsolateTo" = "8:"
|
||||
}
|
||||
"{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_4C87263B310A4D3F8CC1BD3265F5B929"
|
||||
{
|
||||
"SourcePath" = "8:..\\TodoList\\bin\\Debug\\net10.0-windows\\TodoList.deps.json"
|
||||
"TargetName" = "8:TodoList.deps.json"
|
||||
"Tag" = "8:"
|
||||
"Folder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Vital" = "11:TRUE"
|
||||
"ReadOnly" = "11:FALSE"
|
||||
"Hidden" = "11:FALSE"
|
||||
"System" = "11:FALSE"
|
||||
"Permanent" = "11:FALSE"
|
||||
"SharedLegacy" = "11:FALSE"
|
||||
"PackageAs" = "3:1"
|
||||
"Register" = "3:1"
|
||||
"Exclude" = "11:FALSE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"IsolateTo" = "8:"
|
||||
}
|
||||
"{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_65835E291AAA6A7D2371E2E621AF3DC9"
|
||||
{
|
||||
"AssemblyRegister" = "3:1"
|
||||
"AssemblyIsInGAC" = "11:FALSE"
|
||||
"AssemblyAsmDisplayName" = "8:CommunityToolkit.Mvvm, Version=8.4.0.0, Culture=neutral, PublicKeyToken=4aff67a105548ee2, processorArchitecture=MSIL"
|
||||
"ScatterAssemblies"
|
||||
{
|
||||
"_65835E291AAA6A7D2371E2E621AF3DC9"
|
||||
{
|
||||
"Name" = "8:CommunityToolkit.Mvvm.dll"
|
||||
"Attributes" = "3:512"
|
||||
}
|
||||
}
|
||||
"SourcePath" = "8:CommunityToolkit.Mvvm.dll"
|
||||
"TargetName" = "8:"
|
||||
"Tag" = "8:"
|
||||
"Folder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Vital" = "11:TRUE"
|
||||
"ReadOnly" = "11:FALSE"
|
||||
"Hidden" = "11:FALSE"
|
||||
"System" = "11:FALSE"
|
||||
"Permanent" = "11:FALSE"
|
||||
"SharedLegacy" = "11:FALSE"
|
||||
"PackageAs" = "3:1"
|
||||
"Register" = "3:1"
|
||||
"Exclude" = "11:FALSE"
|
||||
"IsDependency" = "11:TRUE"
|
||||
"IsolateTo" = "8:"
|
||||
}
|
||||
"{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_D50F9EB4AAE7436FBAD775A446017958"
|
||||
{
|
||||
"SourcePath" = "8:..\\TodoList\\bin\\Debug\\net10.0-windows\\icon.ico"
|
||||
"TargetName" = "8:icon.ico"
|
||||
"Tag" = "8:"
|
||||
"Folder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Vital" = "11:TRUE"
|
||||
"ReadOnly" = "11:FALSE"
|
||||
"Hidden" = "11:FALSE"
|
||||
"System" = "11:FALSE"
|
||||
"Permanent" = "11:FALSE"
|
||||
"SharedLegacy" = "11:FALSE"
|
||||
"PackageAs" = "3:1"
|
||||
"Register" = "3:1"
|
||||
"Exclude" = "11:FALSE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"IsolateTo" = "8:"
|
||||
}
|
||||
}
|
||||
"FileType"
|
||||
{
|
||||
}
|
||||
"Folder"
|
||||
{
|
||||
"{1525181F-901A-416C-8A58-119130FE478E}:_4CF5CB48101445E3B47CD2485BDDA511"
|
||||
{
|
||||
"Name" = "8:#1916"
|
||||
"AlwaysCreate" = "11:FALSE"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Property" = "8:DesktopFolder"
|
||||
"Folders"
|
||||
{
|
||||
}
|
||||
}
|
||||
"{3C67513D-01DD-4637-8A68-80971EB9504F}:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
{
|
||||
"DefaultLocation" = "8:[ProgramFilesFolder][Manufacturer]\\[ProductName]"
|
||||
"Name" = "8:#1925"
|
||||
"AlwaysCreate" = "11:FALSE"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Property" = "8:TARGETDIR"
|
||||
"Folders"
|
||||
{
|
||||
}
|
||||
}
|
||||
"{1525181F-901A-416C-8A58-119130FE478E}:_BF5ADB1EDBF04FA0A47DA23313C521B9"
|
||||
{
|
||||
"Name" = "8:#1919"
|
||||
"AlwaysCreate" = "11:FALSE"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Property" = "8:ProgramMenuFolder"
|
||||
"Folders"
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
"LaunchCondition"
|
||||
{
|
||||
}
|
||||
"Locator"
|
||||
{
|
||||
}
|
||||
"MsiBootstrapper"
|
||||
{
|
||||
"LangId" = "3:2052"
|
||||
"RequiresElevation" = "11:FALSE"
|
||||
}
|
||||
"Product"
|
||||
{
|
||||
"Name" = "8:Microsoft Visual Studio"
|
||||
"ProductName" = "8:TodoList"
|
||||
"ProductCode" = "8:{146611B1-AB9F-46C3-8FD4-EE7BCEC54C33}"
|
||||
"PackageCode" = "8:{151EF683-7149-4D7A-9134-3092572B04CD}"
|
||||
"UpgradeCode" = "8:{82F2F6EB-C24C-477B-81E6-43EDB0EE36B0}"
|
||||
"AspNetVersion" = "8:4.0.30319.0"
|
||||
"RestartWWWService" = "11:FALSE"
|
||||
"RemovePreviousVersions" = "11:TRUE"
|
||||
"DetectNewerInstalledVersion" = "11:TRUE"
|
||||
"InstallAllUsers" = "11:TRUE"
|
||||
"ProductVersion" = "8:1.0.0"
|
||||
"Manufacturer" = "8:ShaoHua"
|
||||
"ARPHELPTELEPHONE" = "8:"
|
||||
"ARPHELPLINK" = "8:"
|
||||
"Title" = "8:TodoList"
|
||||
"Subject" = "8:"
|
||||
"ARPCONTACT" = "8:ShaoHua"
|
||||
"Keywords" = "8:"
|
||||
"ARPCOMMENTS" = "8:"
|
||||
"ARPURLINFOABOUT" = "8:"
|
||||
"ARPPRODUCTICON" = "8:"
|
||||
"ARPIconIndex" = "3:0"
|
||||
"SearchPath" = "8:"
|
||||
"UseSystemSearchPath" = "11:TRUE"
|
||||
"TargetPlatform" = "3:0"
|
||||
"PreBuildEvent" = "8:"
|
||||
"PostBuildEvent" = "8:"
|
||||
"RunPostBuildEvent" = "3:0"
|
||||
}
|
||||
"Registry"
|
||||
{
|
||||
"HKLM"
|
||||
{
|
||||
"Keys"
|
||||
{
|
||||
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_8522CD2219284FA48C170669A2A3D9B3"
|
||||
{
|
||||
"Name" = "8:Software"
|
||||
"Condition" = "8:"
|
||||
"AlwaysCreate" = "11:FALSE"
|
||||
"DeleteAtUninstall" = "11:FALSE"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Keys"
|
||||
{
|
||||
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_2553544E11294AD0ACF21B6F2D65909C"
|
||||
{
|
||||
"Name" = "8:[Manufacturer]"
|
||||
"Condition" = "8:"
|
||||
"AlwaysCreate" = "11:FALSE"
|
||||
"DeleteAtUninstall" = "11:FALSE"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Keys"
|
||||
{
|
||||
}
|
||||
"Values"
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
"Values"
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"HKCU"
|
||||
{
|
||||
"Keys"
|
||||
{
|
||||
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_BD961664BECD4769871995ED2576A507"
|
||||
{
|
||||
"Name" = "8:Software"
|
||||
"Condition" = "8:"
|
||||
"AlwaysCreate" = "11:FALSE"
|
||||
"DeleteAtUninstall" = "11:FALSE"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Keys"
|
||||
{
|
||||
"{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_885FDEC4E0C3484CA36F1A96BEAD1D41"
|
||||
{
|
||||
"Name" = "8:[Manufacturer]"
|
||||
"Condition" = "8:"
|
||||
"AlwaysCreate" = "11:FALSE"
|
||||
"DeleteAtUninstall" = "11:FALSE"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Keys"
|
||||
{
|
||||
}
|
||||
"Values"
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
"Values"
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"HKCR"
|
||||
{
|
||||
"Keys"
|
||||
{
|
||||
}
|
||||
}
|
||||
"HKU"
|
||||
{
|
||||
"Keys"
|
||||
{
|
||||
}
|
||||
}
|
||||
"HKPU"
|
||||
{
|
||||
"Keys"
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
"Sequences"
|
||||
{
|
||||
}
|
||||
"Shortcut"
|
||||
{
|
||||
"{970C0BB2-C7D0-45D7-ABFA-7EC378858BC0}:_0D031B70CF7542448AC7303C2ED6DC6A"
|
||||
{
|
||||
"Name" = "8:TodoList"
|
||||
"Arguments" = "8:"
|
||||
"Description" = "8:"
|
||||
"ShowCmd" = "3:1"
|
||||
"IconIndex" = "3:0"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Target" = "8:_1E301C03308E4C0AADE54375ACCA27E9"
|
||||
"Folder" = "8:_BF5ADB1EDBF04FA0A47DA23313C521B9"
|
||||
"WorkingFolder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Icon" = "8:_D50F9EB4AAE7436FBAD775A446017958"
|
||||
"Feature" = "8:"
|
||||
}
|
||||
"{970C0BB2-C7D0-45D7-ABFA-7EC378858BC0}:_AE6A93A941DD4EEDA219EAF034A157CC"
|
||||
{
|
||||
"Name" = "8:todoList主输出"
|
||||
"Arguments" = "8:"
|
||||
"Description" = "8:"
|
||||
"ShowCmd" = "3:1"
|
||||
"IconIndex" = "3:0"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Target" = "8:_6792D68F0F43409CADA40277C6FE4B43"
|
||||
"Folder" = "8:_BF5ADB1EDBF04FA0A47DA23313C521B9"
|
||||
"WorkingFolder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Icon" = "8:_D50F9EB4AAE7436FBAD775A446017958"
|
||||
"Feature" = "8:"
|
||||
}
|
||||
}
|
||||
"UserInterface"
|
||||
{
|
||||
"{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_1A5922191B3444D2ADBFAAAEE654AFBE"
|
||||
{
|
||||
"UseDynamicProperties" = "11:FALSE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdBasicDialogs.wim"
|
||||
}
|
||||
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_1A7F6DF07CA34F1290B53C77E9A24DCD"
|
||||
{
|
||||
"Name" = "8:#1901"
|
||||
"Sequence" = "3:1"
|
||||
"Attributes" = "3:2"
|
||||
"Dialogs"
|
||||
{
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_12C9A09546FF47E89C2F76E5857982C5"
|
||||
{
|
||||
"Sequence" = "3:100"
|
||||
"DisplayName" = "8:进度"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdProgressDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
"ShowProgress"
|
||||
{
|
||||
"Name" = "8:ShowProgress"
|
||||
"DisplayName" = "8:#1009"
|
||||
"Description" = "8:#1109"
|
||||
"Type" = "3:5"
|
||||
"ContextData" = "8:1;True=1;False=0"
|
||||
"Attributes" = "3:0"
|
||||
"Setting" = "3:0"
|
||||
"Value" = "3:1"
|
||||
"DefaultValue" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_1BED33199298468C95ACE087FD867323"
|
||||
{
|
||||
"Name" = "8:#1902"
|
||||
"Sequence" = "3:1"
|
||||
"Attributes" = "3:3"
|
||||
"Dialogs"
|
||||
{
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_3F862D9E99F34183BC0479F9A6500399"
|
||||
{
|
||||
"Sequence" = "3:100"
|
||||
"DisplayName" = "8:已完成"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdFinishedDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
"UpdateText"
|
||||
{
|
||||
"Name" = "8:UpdateText"
|
||||
"DisplayName" = "8:#1058"
|
||||
"Description" = "8:#1158"
|
||||
"Type" = "3:15"
|
||||
"ContextData" = "8:"
|
||||
"Attributes" = "3:0"
|
||||
"Setting" = "3:1"
|
||||
"Value" = "8:#1258"
|
||||
"DefaultValue" = "8:#1258"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_2579AFB8C446463A9A8BCBDE6897AF55"
|
||||
{
|
||||
"Name" = "8:#1901"
|
||||
"Sequence" = "3:2"
|
||||
"Attributes" = "3:2"
|
||||
"Dialogs"
|
||||
{
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_1F08E1AAFAC14C4BA560722C839F686C"
|
||||
{
|
||||
"Sequence" = "3:100"
|
||||
"DisplayName" = "8:进度"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminProgressDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
"ShowProgress"
|
||||
{
|
||||
"Name" = "8:ShowProgress"
|
||||
"DisplayName" = "8:#1009"
|
||||
"Description" = "8:#1109"
|
||||
"Type" = "3:5"
|
||||
"ContextData" = "8:1;True=1;False=0"
|
||||
"Attributes" = "3:0"
|
||||
"Setting" = "3:0"
|
||||
"Value" = "3:1"
|
||||
"DefaultValue" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_669B474BCA014E7C9871D52674DC793E"
|
||||
{
|
||||
"Name" = "8:#1900"
|
||||
"Sequence" = "3:1"
|
||||
"Attributes" = "3:1"
|
||||
"Dialogs"
|
||||
{
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_0EFA62CCD2C843EAA044829F13217B57"
|
||||
{
|
||||
"Sequence" = "3:300"
|
||||
"DisplayName" = "8:确认安装"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdConfirmDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_860D684F21184955A2394B2FAE3B85B3"
|
||||
{
|
||||
"Sequence" = "3:200"
|
||||
"DisplayName" = "8:安装文件夹"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdFolderDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
"InstallAllUsersVisible"
|
||||
{
|
||||
"Name" = "8:InstallAllUsersVisible"
|
||||
"DisplayName" = "8:#1059"
|
||||
"Description" = "8:#1159"
|
||||
"Type" = "3:5"
|
||||
"ContextData" = "8:1;True=1;False=0"
|
||||
"Attributes" = "3:0"
|
||||
"Setting" = "3:0"
|
||||
"Value" = "3:1"
|
||||
"DefaultValue" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_F62A5F384AC4466DAA5560FD53102C9B"
|
||||
{
|
||||
"Sequence" = "3:100"
|
||||
"DisplayName" = "8:欢迎使用"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdWelcomeDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
"CopyrightWarning"
|
||||
{
|
||||
"Name" = "8:CopyrightWarning"
|
||||
"DisplayName" = "8:#1002"
|
||||
"Description" = "8:#1102"
|
||||
"Type" = "3:3"
|
||||
"ContextData" = "8:"
|
||||
"Attributes" = "3:0"
|
||||
"Setting" = "3:1"
|
||||
"Value" = "8:#1202"
|
||||
"DefaultValue" = "8:#1202"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
"Welcome"
|
||||
{
|
||||
"Name" = "8:Welcome"
|
||||
"DisplayName" = "8:#1003"
|
||||
"Description" = "8:#1103"
|
||||
"Type" = "3:3"
|
||||
"ContextData" = "8:"
|
||||
"Attributes" = "3:0"
|
||||
"Setting" = "3:1"
|
||||
"Value" = "8:#1203"
|
||||
"DefaultValue" = "8:#1203"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_B97CCC8EE8F848C2A4276A2E58150A45"
|
||||
{
|
||||
"Name" = "8:#1902"
|
||||
"Sequence" = "3:2"
|
||||
"Attributes" = "3:3"
|
||||
"Dialogs"
|
||||
{
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_1CDE38F50B454FFA8F5C722E23D7165D"
|
||||
{
|
||||
"Sequence" = "3:100"
|
||||
"DisplayName" = "8:已完成"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminFinishedDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_F85F3CEDA84E4C21AA1D2BEB630006E7"
|
||||
{
|
||||
"UseDynamicProperties" = "11:FALSE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdUserInterface.wim"
|
||||
}
|
||||
"{DF760B10-853B-4699-99F2-AFF7185B4A62}:_F879EDB3FFCC474E9059D6DC3BED5CBE"
|
||||
{
|
||||
"Name" = "8:#1900"
|
||||
"Sequence" = "3:2"
|
||||
"Attributes" = "3:1"
|
||||
"Dialogs"
|
||||
{
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_4384C55A823E4238A33E43C392ADB6C2"
|
||||
{
|
||||
"Sequence" = "3:200"
|
||||
"DisplayName" = "8:安装文件夹"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminFolderDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_4D730CD1E9A541A9A0357FD14F274504"
|
||||
{
|
||||
"Sequence" = "3:100"
|
||||
"DisplayName" = "8:欢迎使用"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminWelcomeDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
"CopyrightWarning"
|
||||
{
|
||||
"Name" = "8:CopyrightWarning"
|
||||
"DisplayName" = "8:#1002"
|
||||
"Description" = "8:#1102"
|
||||
"Type" = "3:3"
|
||||
"ContextData" = "8:"
|
||||
"Attributes" = "3:0"
|
||||
"Setting" = "3:1"
|
||||
"Value" = "8:#1202"
|
||||
"DefaultValue" = "8:#1202"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
"Welcome"
|
||||
{
|
||||
"Name" = "8:Welcome"
|
||||
"DisplayName" = "8:#1003"
|
||||
"Description" = "8:#1103"
|
||||
"Type" = "3:3"
|
||||
"ContextData" = "8:"
|
||||
"Attributes" = "3:0"
|
||||
"Setting" = "3:1"
|
||||
"Value" = "8:#1203"
|
||||
"DefaultValue" = "8:#1203"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
"{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_88BC734F7F1D4494AEE5F11E9797CD6B"
|
||||
{
|
||||
"Sequence" = "3:300"
|
||||
"DisplayName" = "8:确认安装"
|
||||
"UseDynamicProperties" = "11:TRUE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"SourcePath" = "8:<VsdDialogDir>\\VsdAdminConfirmDlg.wid"
|
||||
"Properties"
|
||||
{
|
||||
"BannerBitmap"
|
||||
{
|
||||
"Name" = "8:BannerBitmap"
|
||||
"DisplayName" = "8:#1001"
|
||||
"Description" = "8:#1101"
|
||||
"Type" = "3:8"
|
||||
"ContextData" = "8:Bitmap"
|
||||
"Attributes" = "3:4"
|
||||
"Setting" = "3:1"
|
||||
"UsePlugInResources" = "11:TRUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"MergeModule"
|
||||
{
|
||||
}
|
||||
"ProjectOutput"
|
||||
{
|
||||
"{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_6792D68F0F43409CADA40277C6FE4B43"
|
||||
{
|
||||
"SourcePath" = "8:..\\TodoList\\obj\\Debug\\net10.0-windows\\TodoList.dll"
|
||||
"TargetName" = "8:"
|
||||
"Tag" = "8:"
|
||||
"Folder" = "8:_8A20A7DDF24B44E8AC7BBB584966EEF1"
|
||||
"Condition" = "8:"
|
||||
"Transitive" = "11:FALSE"
|
||||
"Vital" = "11:TRUE"
|
||||
"ReadOnly" = "11:FALSE"
|
||||
"Hidden" = "11:FALSE"
|
||||
"System" = "11:FALSE"
|
||||
"Permanent" = "11:FALSE"
|
||||
"SharedLegacy" = "11:FALSE"
|
||||
"PackageAs" = "3:1"
|
||||
"Register" = "3:1"
|
||||
"Exclude" = "11:FALSE"
|
||||
"IsDependency" = "11:FALSE"
|
||||
"IsolateTo" = "8:"
|
||||
"ProjectOutputGroupRegister" = "3:1"
|
||||
"OutputConfiguration" = "8:"
|
||||
"OutputGroupCanonicalName" = "8:Built"
|
||||
"OutputProjectGuid" = "8:{6CD40A2D-545E-C111-D437-1BE9E9C2C98C}"
|
||||
"ShowKeyOutput" = "11:TRUE"
|
||||
"ExcludeFilters"
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
TodoList.slnx
Normal file
10
TodoList.slnx
Normal file
@@ -0,0 +1,10 @@
|
||||
<Solution>
|
||||
<Configurations>
|
||||
<Platform Name="Any CPU" />
|
||||
<Platform Name="ARM64" />
|
||||
<Platform Name="x64" />
|
||||
<Platform Name="x86" />
|
||||
</Configurations>
|
||||
<Project Path="Setup/Setup.vdproj" Type="Installer" />
|
||||
<Project Path="TodoList/TodoList.csproj" />
|
||||
</Solution>
|
||||
9
TodoList/App.xaml
Normal file
9
TodoList/App.xaml
Normal file
@@ -0,0 +1,9 @@
|
||||
<Application x:Class="TodoList.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:TodoList"
|
||||
ShutdownMode="OnExplicitShutdown">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
308
TodoList/App.xaml.cs
Normal file
308
TodoList/App.xaml.cs
Normal file
@@ -0,0 +1,308 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using Microsoft.Win32;
|
||||
using TodoList.Services;
|
||||
using TodoList.ViewModels;
|
||||
using TodoList.Views;
|
||||
using System.Linq;
|
||||
|
||||
namespace TodoList
|
||||
{
|
||||
public partial class App : System.Windows.Application
|
||||
{
|
||||
private IDataService _dataService;
|
||||
private GlobalShortcutService _shortcutService;
|
||||
private MainWindow _mainWindow;
|
||||
private SettingsService _settingsService;
|
||||
private System.Windows.Forms.NotifyIcon _notifyIcon;
|
||||
private Mutex _mutex;
|
||||
private EventWaitHandle _eventWaitHandle;
|
||||
private const string UniqueEventName = "Global\\TodoListApp_Event_v1";
|
||||
|
||||
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
||||
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||
|
||||
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
public App()
|
||||
{
|
||||
// Disable hardware acceleration to prevent black screen issues
|
||||
// Must be set before any UI is created
|
||||
System.Windows.Media.RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly;
|
||||
}
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
// Ensure app doesn't shutdown when main window closes (we hide it)
|
||||
this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
||||
|
||||
const string appName = "Global\\TodoListApp_Unique_Mutex_v1";
|
||||
bool createdNew;
|
||||
_mutex = new Mutex(true, appName, out createdNew);
|
||||
|
||||
if (!createdNew)
|
||||
{
|
||||
// Signal the existing instance
|
||||
try
|
||||
{
|
||||
using (var evt = EventWaitHandle.OpenExisting(UniqueEventName))
|
||||
{
|
||||
evt.Set();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback to old method if event open fails
|
||||
var hWnd = FindWindow(null, "待办事项");
|
||||
if (hWnd != IntPtr.Zero)
|
||||
{
|
||||
ShowWindow(hWnd, 9); // SW_RESTORE
|
||||
SetForegroundWindow(hWnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Force exit to prevent second instance from running
|
||||
Environment.Exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the event handle for this instance
|
||||
_eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
|
||||
|
||||
// Start a thread to listen for signals
|
||||
Thread thread = new Thread(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_eventWaitHandle.WaitOne();
|
||||
this.Dispatcher.Invoke(() => ShowMainWindow());
|
||||
}
|
||||
});
|
||||
thread.IsBackground = true;
|
||||
thread.Start();
|
||||
|
||||
base.OnStartup(e);
|
||||
|
||||
// Configure Auto Start
|
||||
ConfigureAutoStart();
|
||||
|
||||
// Create Desktop/StartMenu Shortcut if needed (optional feature)
|
||||
// CreateShortcut();
|
||||
|
||||
this.DispatcherUnhandledException += App_DispatcherUnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
|
||||
try
|
||||
{
|
||||
_settingsService = new SettingsService();
|
||||
_dataService = new FileDataService();
|
||||
_shortcutService = new GlobalShortcutService();
|
||||
|
||||
var mainViewModel = new MainViewModel(_dataService, _settingsService);
|
||||
|
||||
_mainWindow = new MainWindow(mainViewModel);
|
||||
_mainWindow.Loaded += MainWindow_Loaded;
|
||||
|
||||
// Initialize Tray Icon
|
||||
InitializeTrayIcon();
|
||||
|
||||
// Check for silent mode
|
||||
bool silent = e.Args.Contains("--silent") || e.Args.Contains("-s");
|
||||
|
||||
if (!silent)
|
||||
{
|
||||
_mainWindow.Show();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log("Startup Error: " + ex.ToString());
|
||||
System.Windows.MessageBox.Show("Startup Error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureAutoStart()
|
||||
{
|
||||
try
|
||||
{
|
||||
var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
|
||||
string cmd = $"\"{exePath}\" --silent";
|
||||
|
||||
// If running as dotnet tool, try to find the shim or stable entry point
|
||||
// Usually %USERPROFILE%\.dotnet\tools\todo.exe
|
||||
var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var toolShim = System.IO.Path.Combine(userProfile, ".dotnet", "tools", "todo.exe");
|
||||
|
||||
if (System.IO.File.Exists(toolShim))
|
||||
{
|
||||
// If the shim exists and we are likely running it (or just installed it), prefer the shim
|
||||
// This handles updates better as the shim path stays constant.
|
||||
// But we should verify if the current process IS related to it?
|
||||
// Actually, if installed as tool, we definitely want to use the shim.
|
||||
cmd = $"\"{toolShim}\" --silent";
|
||||
}
|
||||
|
||||
var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true);
|
||||
if (key != null)
|
||||
{
|
||||
var existing = key.GetValue("TodoListApp");
|
||||
if (existing == null || existing.ToString() != cmd)
|
||||
{
|
||||
key.SetValue("TodoListApp", cmd);
|
||||
}
|
||||
key.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log("AutoStart Error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTrayIcon()
|
||||
{
|
||||
_notifyIcon = new System.Windows.Forms.NotifyIcon();
|
||||
|
||||
// Try load icon from resource or file
|
||||
try
|
||||
{
|
||||
// Load from embedded resource
|
||||
var resourceUri = new Uri("pack://application:,,,/icon.ico");
|
||||
var streamInfo = System.Windows.Application.GetResourceStream(resourceUri);
|
||||
|
||||
if (streamInfo != null)
|
||||
{
|
||||
using (var stream = streamInfo.Stream)
|
||||
{
|
||||
_notifyIcon.Icon = new System.Drawing.Icon(stream);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_notifyIcon.Icon = System.Drawing.SystemIcons.Application;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_notifyIcon.Icon = System.Drawing.SystemIcons.Application;
|
||||
}
|
||||
|
||||
_notifyIcon.Visible = true;
|
||||
_notifyIcon.Text = "TodoList";
|
||||
_notifyIcon.DoubleClick += (s, e) => ShowMainWindow();
|
||||
|
||||
var contextMenu = new System.Windows.Forms.ContextMenuStrip();
|
||||
contextMenu.Items.Add("打开主界面", null, (s, e) => ShowMainWindow());
|
||||
contextMenu.Items.Add("退出", null, (s, e) => ExitApplication());
|
||||
_notifyIcon.ContextMenuStrip = contextMenu;
|
||||
}
|
||||
|
||||
private void ShowMainWindow()
|
||||
{
|
||||
Log("ShowMainWindow called");
|
||||
if (_mainWindow != null)
|
||||
{
|
||||
if (_mainWindow.WindowState == WindowState.Minimized)
|
||||
{
|
||||
_mainWindow.WindowState = WindowState.Normal;
|
||||
}
|
||||
_mainWindow.Show();
|
||||
_mainWindow.Activate();
|
||||
|
||||
// Force foreground
|
||||
var helper = new WindowInteropHelper(_mainWindow);
|
||||
var handle = helper.Handle;
|
||||
if (handle != IntPtr.Zero)
|
||||
{
|
||||
SetForegroundWindow(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExitApplication()
|
||||
{
|
||||
_notifyIcon?.Dispose();
|
||||
_notifyIcon = null;
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
Log("Dispatcher Error: " + e.Exception.ToString());
|
||||
System.Windows.MessageBox.Show("Dispatcher Error: " + e.Exception.Message);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Log("Domain Error: " + e.ExceptionObject.ToString());
|
||||
System.Windows.MessageBox.Show("Critical Error: " + e.ExceptionObject.ToString());
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.File.AppendAllText("error.log", DateTime.Now + ": " + message + Environment.NewLine);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private bool _isHotkeyRegistered;
|
||||
|
||||
private void RegisterHotkey()
|
||||
{
|
||||
if (_isHotkeyRegistered || _shortcutService == null || _settingsService == null) return;
|
||||
|
||||
var helper = new WindowInteropHelper(_mainWindow);
|
||||
var handle = helper.Handle;
|
||||
|
||||
if (handle != IntPtr.Zero)
|
||||
{
|
||||
var settings = _settingsService.Settings;
|
||||
var mods = GlobalShortcutService.GetModifier(settings.ShortcutModifiers);
|
||||
var key = GlobalShortcutService.GetKey(settings.ShortcutKey);
|
||||
|
||||
_shortcutService.Register(handle, OnHotKeyPressed, mods, key);
|
||||
_isHotkeyRegistered = true;
|
||||
|
||||
// Subscribe to settings changes to update hotkey
|
||||
_settingsService.Settings.PropertyChanged += (s, args) =>
|
||||
{
|
||||
if (args.PropertyName == nameof(AppSettings.ShortcutModifiers) ||
|
||||
args.PropertyName == nameof(AppSettings.ShortcutKey))
|
||||
{
|
||||
var newMods = GlobalShortcutService.GetModifier(_settingsService.Settings.ShortcutModifiers);
|
||||
var newKey = GlobalShortcutService.GetKey(_settingsService.Settings.ShortcutKey);
|
||||
_shortcutService.UpdateShortcut(newMods, newKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHotKeyPressed()
|
||||
{
|
||||
Log("Hotkey pressed.");
|
||||
ShowMainWindow();
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
_notifyIcon?.Dispose();
|
||||
_shortcutService?.Dispose();
|
||||
base.OnExit(e);
|
||||
}
|
||||
|
||||
// We can hook into MainWindow's Loaded event to register hotkey
|
||||
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RegisterHotkey();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
TodoList/AssemblyInfo.cs
Normal file
10
TodoList/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly:ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
31
TodoList/Converters/EnumDescriptionConverter.cs
Normal file
31
TodoList/Converters/EnumDescriptionConverter.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace TodoList.Converters
|
||||
{
|
||||
public class EnumDescriptionConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value == null) return string.Empty;
|
||||
|
||||
var type = value.GetType();
|
||||
var name = Enum.GetName(type, value);
|
||||
if (name == null) return value.ToString();
|
||||
|
||||
var field = type.GetField(name);
|
||||
if (field == null) return value.ToString();
|
||||
|
||||
var attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
|
||||
return attr?.Description ?? value.ToString();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
TodoList/Models/TodoItem.cs
Normal file
45
TodoList/Models/TodoItem.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace TodoList.Models
|
||||
{
|
||||
public enum TodoPriority
|
||||
{
|
||||
[Description("低")]
|
||||
Low,
|
||||
[Description("中")]
|
||||
Medium,
|
||||
[Description("高")]
|
||||
High
|
||||
}
|
||||
|
||||
public enum SyncStatus
|
||||
{
|
||||
Synced,
|
||||
Pending,
|
||||
Failed
|
||||
}
|
||||
|
||||
public partial class TodoItem : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string id = Guid.NewGuid().ToString();
|
||||
|
||||
[ObservableProperty]
|
||||
private string content = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isCompleted;
|
||||
|
||||
[ObservableProperty]
|
||||
private TodoPriority priority = TodoPriority.Medium;
|
||||
|
||||
[ObservableProperty]
|
||||
private DateTime createdAt = DateTime.Now;
|
||||
|
||||
[ObservableProperty]
|
||||
private SyncStatus syncStatus = SyncStatus.Pending;
|
||||
}
|
||||
}
|
||||
70
TodoList/Services/FileDataService.cs
Normal file
70
TodoList/Services/FileDataService.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using TodoList.Models;
|
||||
|
||||
namespace TodoList.Services
|
||||
{
|
||||
public class FileDataService : IDataService
|
||||
{
|
||||
private readonly string _filePath;
|
||||
|
||||
public FileDataService()
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var folder = Path.Combine(appData, "TodoListApp");
|
||||
Directory.CreateDirectory(folder);
|
||||
_filePath = Path.Combine(folder, "tasks.json");
|
||||
}
|
||||
|
||||
public async Task<List<TodoItem>> LoadTasksAsync()
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
{
|
||||
return new List<TodoItem>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = File.OpenRead(_filePath);
|
||||
var items = await JsonSerializer.DeserializeAsync<List<TodoItem>>(stream);
|
||||
return items ?? new List<TodoItem>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<TodoItem>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveTaskAsync(TodoItem task)
|
||||
{
|
||||
var tasks = await LoadTasksAsync();
|
||||
var existing = tasks.Find(t => t.Id == task.Id);
|
||||
if (existing != null)
|
||||
{
|
||||
tasks.Remove(existing);
|
||||
}
|
||||
tasks.Add(task);
|
||||
await SaveAllAsync(tasks);
|
||||
}
|
||||
|
||||
public async Task SaveAllAsync(List<TodoItem> tasks)
|
||||
{
|
||||
using var stream = File.Create(_filePath);
|
||||
await JsonSerializer.SerializeAsync(stream, tasks, new JsonSerializerOptions { WriteIndented = true });
|
||||
}
|
||||
|
||||
public async Task DeleteTaskAsync(string id)
|
||||
{
|
||||
var tasks = await LoadTasksAsync();
|
||||
var existing = tasks.Find(t => t.Id == id);
|
||||
if (existing != null)
|
||||
{
|
||||
tasks.Remove(existing);
|
||||
await SaveAllAsync(tasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
TodoList/Services/GlobalShortcutService.cs
Normal file
127
TodoList/Services/GlobalShortcutService.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace TodoList.Services
|
||||
{
|
||||
public class GlobalShortcutService : IDisposable
|
||||
{
|
||||
private const int HOTKEY_ID = 9000;
|
||||
|
||||
public const uint MOD_ALT = 0x0001;
|
||||
public const uint MOD_CONTROL = 0x0002;
|
||||
public const uint MOD_SHIFT = 0x0004;
|
||||
public const uint MOD_WIN = 0x0008;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
||||
|
||||
private IntPtr _windowHandle;
|
||||
private HwndSource _source;
|
||||
private Action _onHotKeyPressed;
|
||||
private bool _isRegistered;
|
||||
|
||||
public void Register(IntPtr windowHandle, Action onHotKeyPressed, uint modifiers, uint key)
|
||||
{
|
||||
// If already registered, unregister first (to support updating)
|
||||
if (_isRegistered)
|
||||
{
|
||||
UnregisterHotKey(_windowHandle, HOTKEY_ID);
|
||||
_source?.RemoveHook(HwndHook);
|
||||
_isRegistered = false;
|
||||
}
|
||||
|
||||
_windowHandle = windowHandle;
|
||||
_onHotKeyPressed = onHotKeyPressed;
|
||||
|
||||
_source = HwndSource.FromHwnd(_windowHandle);
|
||||
if (_source == null) return; // Should not happen if handle is valid
|
||||
|
||||
_source.AddHook(HwndHook);
|
||||
|
||||
if (RegisterHotKey(_windowHandle, HOTKEY_ID, modifiers, key))
|
||||
{
|
||||
_isRegistered = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Failed to register hotkey.");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateShortcut(uint modifiers, uint key)
|
||||
{
|
||||
if (_windowHandle != IntPtr.Zero && _onHotKeyPressed != null)
|
||||
{
|
||||
// Re-register with new keys
|
||||
Register(_windowHandle, _onHotKeyPressed, modifiers, key);
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
const int WM_HOTKEY = 0x0312;
|
||||
if (msg == WM_HOTKEY)
|
||||
{
|
||||
if (wParam.ToInt32() == HOTKEY_ID)
|
||||
{
|
||||
_onHotKeyPressed?.Invoke();
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
public void Unregister()
|
||||
{
|
||||
if (_isRegistered)
|
||||
{
|
||||
_source?.RemoveHook(HwndHook);
|
||||
UnregisterHotKey(_windowHandle, HOTKEY_ID);
|
||||
_isRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Unregister();
|
||||
}
|
||||
|
||||
public static uint GetModifier(string modifiers)
|
||||
{
|
||||
uint mod = 0;
|
||||
if (string.IsNullOrEmpty(modifiers)) return mod;
|
||||
|
||||
var parts = modifiers.Split(',');
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var p = part.Trim();
|
||||
if (p.Equals("Control", StringComparison.OrdinalIgnoreCase)) mod |= MOD_CONTROL;
|
||||
if (p.Equals("Alt", StringComparison.OrdinalIgnoreCase)) mod |= MOD_ALT;
|
||||
if (p.Equals("Shift", StringComparison.OrdinalIgnoreCase)) mod |= MOD_SHIFT;
|
||||
if (p.Equals("Windows", StringComparison.OrdinalIgnoreCase)) mod |= MOD_WIN;
|
||||
}
|
||||
return mod;
|
||||
}
|
||||
|
||||
public static uint GetKey(string key)
|
||||
{
|
||||
if (Enum.TryParse<Key>(key, out var k))
|
||||
{
|
||||
return (uint)KeyInterop.VirtualKeyFromKey(k);
|
||||
}
|
||||
// Fallback for simple letters if Key enum doesn't match directly (though it should for A-Z)
|
||||
if (key.Length == 1)
|
||||
{
|
||||
char c = char.ToUpper(key[0]);
|
||||
if (c >= 'A' && c <= 'Z') return (uint)c;
|
||||
if (c >= '0' && c <= '9') return (uint)c;
|
||||
}
|
||||
return 0x41; // Default 'A'
|
||||
}
|
||||
}
|
||||
}
|
||||
14
TodoList/Services/IDataService.cs
Normal file
14
TodoList/Services/IDataService.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using TodoList.Models;
|
||||
|
||||
namespace TodoList.Services
|
||||
{
|
||||
public interface IDataService
|
||||
{
|
||||
Task<List<TodoItem>> LoadTasksAsync();
|
||||
Task SaveTaskAsync(TodoItem task);
|
||||
Task SaveAllAsync(List<TodoItem> tasks);
|
||||
Task DeleteTaskAsync(string id);
|
||||
}
|
||||
}
|
||||
56
TodoList/Services/SettingsService.cs
Normal file
56
TodoList/Services/SettingsService.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace TodoList.Services
|
||||
{
|
||||
public partial class AppSettings : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string shortcutModifiers = "Control,Alt"; // Comma separated
|
||||
|
||||
[ObservableProperty]
|
||||
private string shortcutKey = "A";
|
||||
}
|
||||
|
||||
public class SettingsService
|
||||
{
|
||||
private readonly string _filePath;
|
||||
public AppSettings Settings { get; private set; }
|
||||
|
||||
public SettingsService()
|
||||
{
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var folder = Path.Combine(appData, "TodoListApp");
|
||||
Directory.CreateDirectory(folder);
|
||||
_filePath = Path.Combine(folder, "settings.json");
|
||||
Settings = LoadSettings();
|
||||
}
|
||||
|
||||
private AppSettings LoadSettings()
|
||||
{
|
||||
if (!File.Exists(_filePath)) return new AppSettings();
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(_filePath);
|
||||
return JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new AppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(Settings, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(_filePath, json);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
21
TodoList/TodoList.csproj
Normal file
21
TodoList/TodoList.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="icon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
163
TodoList/ViewModels/MainViewModel.cs
Normal file
163
TodoList/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using TodoList.Models;
|
||||
using TodoList.Services;
|
||||
|
||||
namespace TodoList.ViewModels
|
||||
{
|
||||
public partial class MainViewModel : ObservableObject, IRecipient<TaskAddedMessage>
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
private readonly SettingsService _settingsService;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<TodoItem> tasks = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool showCompleted = false;
|
||||
|
||||
[ObservableProperty]
|
||||
private string newContent;
|
||||
|
||||
[ObservableProperty]
|
||||
private TodoPriority newPriority = TodoPriority.Medium;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isSettingsOpen;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(FullShortcut))]
|
||||
private string shortcutKey;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(FullShortcut))]
|
||||
private string shortcutModifiers;
|
||||
|
||||
public string FullShortcut
|
||||
{
|
||||
get
|
||||
{
|
||||
var mods = ShortcutModifiers?.Replace(",", " + ");
|
||||
return string.IsNullOrEmpty(mods) ? ShortcutKey : $"{mods} + {ShortcutKey}";
|
||||
}
|
||||
}
|
||||
|
||||
public MainViewModel(IDataService dataService, SettingsService settingsService)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_settingsService = settingsService;
|
||||
ShortcutKey = _settingsService.Settings.ShortcutKey;
|
||||
ShortcutModifiers = _settingsService.Settings.ShortcutModifiers;
|
||||
|
||||
WeakReferenceMessenger.Default.Register(this);
|
||||
LoadTasksCommand.Execute(null);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenSettings()
|
||||
{
|
||||
IsSettingsOpen = true;
|
||||
ShortcutKey = _settingsService.Settings.ShortcutKey;
|
||||
ShortcutModifiers = _settingsService.Settings.ShortcutModifiers;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CloseSettings()
|
||||
{
|
||||
IsSettingsOpen = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SaveSettings()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ShortcutKey))
|
||||
{
|
||||
_settingsService.Settings.ShortcutKey = ShortcutKey.ToUpper();
|
||||
_settingsService.Settings.ShortcutModifiers = ShortcutModifiers;
|
||||
_settingsService.SaveSettings();
|
||||
}
|
||||
IsSettingsOpen = false;
|
||||
}
|
||||
|
||||
async partial void OnShowCompletedChanged(bool value)
|
||||
{
|
||||
await LoadTasksAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddTaskAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(NewContent)) return;
|
||||
|
||||
var newTask = new TodoItem
|
||||
{
|
||||
Content = NewContent,
|
||||
Priority = NewPriority,
|
||||
IsCompleted = false,
|
||||
SyncStatus = SyncStatus.Pending
|
||||
};
|
||||
|
||||
await _dataService.SaveTaskAsync(newTask);
|
||||
NewContent = string.Empty;
|
||||
NewPriority = TodoPriority.Medium;
|
||||
await LoadTasksAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task LoadTasksAsync()
|
||||
{
|
||||
var allTasks = await _dataService.LoadTasksAsync();
|
||||
|
||||
var filtered = ShowCompleted
|
||||
? allTasks
|
||||
: allTasks.Where(t => !t.IsCompleted).ToList();
|
||||
|
||||
// Sort: Uncompleted first, then by priority (High -> Low), then date
|
||||
var sorted = filtered
|
||||
.OrderBy(t => t.IsCompleted)
|
||||
.ThenByDescending(t => t.Priority)
|
||||
.ThenByDescending(t => t.CreatedAt)
|
||||
.ToList();
|
||||
|
||||
Tasks.Clear();
|
||||
foreach (var t in sorted)
|
||||
{
|
||||
Tasks.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ToggleCompleteAsync(TodoItem item)
|
||||
{
|
||||
if (item == null) return;
|
||||
// item.IsCompleted is already toggled by UI binding before this command if TwoWay binding
|
||||
// But usually CheckBox command parameter is the item.
|
||||
// Let's assume the binding updates the property.
|
||||
|
||||
item.SyncStatus = SyncStatus.Pending; // Mark as pending sync
|
||||
await _dataService.SaveTaskAsync(item);
|
||||
await LoadTasksAsync(); // Refresh list to apply filter
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task DeleteAsync(TodoItem item)
|
||||
{
|
||||
if (item == null) return;
|
||||
await _dataService.DeleteTaskAsync(item.Id);
|
||||
Tasks.Remove(item);
|
||||
}
|
||||
|
||||
public async void Receive(TaskAddedMessage message)
|
||||
{
|
||||
await LoadTasksAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class TaskAddedMessage
|
||||
{
|
||||
}
|
||||
}
|
||||
58
TodoList/ViewModels/QuickEntryViewModel.cs
Normal file
58
TodoList/ViewModels/QuickEntryViewModel.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using TodoList.Models;
|
||||
using TodoList.Services;
|
||||
|
||||
namespace TodoList.ViewModels
|
||||
{
|
||||
public partial class QuickEntryViewModel : ObservableObject
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
private Action _closeAction;
|
||||
|
||||
[ObservableProperty]
|
||||
private string content;
|
||||
|
||||
[ObservableProperty]
|
||||
private TodoPriority priority = TodoPriority.Medium;
|
||||
|
||||
public QuickEntryViewModel(IDataService dataService, Action closeAction)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_closeAction = closeAction;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SaveAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Content)) return;
|
||||
|
||||
var newTask = new TodoItem
|
||||
{
|
||||
Content = Content,
|
||||
Priority = Priority,
|
||||
IsCompleted = false,
|
||||
SyncStatus = SyncStatus.Pending
|
||||
};
|
||||
|
||||
await _dataService.SaveTaskAsync(newTask);
|
||||
|
||||
// Notify MainViewModel
|
||||
WeakReferenceMessenger.Default.Send(new TaskAddedMessage());
|
||||
|
||||
// Reset and close
|
||||
Content = string.Empty;
|
||||
Priority = TodoPriority.Medium;
|
||||
_closeAction?.Invoke();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Cancel()
|
||||
{
|
||||
_closeAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
256
TodoList/Views/MainWindow.xaml
Normal file
256
TodoList/Views/MainWindow.xaml
Normal file
@@ -0,0 +1,256 @@
|
||||
<Window x:Class="TodoList.Views.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:TodoList.Views"
|
||||
xmlns:models="clr-namespace:TodoList.Models"
|
||||
xmlns:converters="clr-namespace:TodoList.Converters"
|
||||
mc:Ignorable="d"
|
||||
Title="待办事项" Height="600" Width="450"
|
||||
Background="#F5F5F7"
|
||||
Icon="/icon.ico"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
|
||||
<Window.Resources>
|
||||
<ObjectDataProvider x:Key="PriorityEnum" MethodName="GetValues"
|
||||
ObjectType="{x:Type sys:Enum}"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib">
|
||||
<ObjectDataProvider.MethodParameters>
|
||||
<x:Type TypeName="models:TodoPriority"/>
|
||||
</ObjectDataProvider.MethodParameters>
|
||||
</ObjectDataProvider>
|
||||
|
||||
<Style x:Key="ModernButton" TargetType="Button">
|
||||
<Setter Property="Background" Value="#007AFF"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="10,5"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="5">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#0062CC"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||
<converters:EnumDescriptionConverter x:Key="EnumDescConverter"/>
|
||||
</Window.Resources>
|
||||
|
||||
<!-- Force Rebuild Trigger -->
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/> <!-- Toolbar -->
|
||||
<RowDefinition Height="Auto"/> <!-- Input -->
|
||||
<RowDefinition Height="*"/> <!-- List -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header / Toolbar -->
|
||||
<Border Grid.Row="0" Background="White" Padding="15" Effect="{DynamicResource {x:Static DropShadowEffect.ShadowDepthProperty}}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="我的待办" FontSize="24" FontWeight="Bold" Foreground="#333"/>
|
||||
|
||||
<Button Grid.Column="1" Content="设置快捷键"
|
||||
Command="{Binding OpenSettingsCommand}"
|
||||
Background="Transparent" Foreground="#007AFF" Margin="0,0,15,0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
|
||||
<CheckBox Grid.Column="2" Content="显示已完成"
|
||||
IsChecked="{Binding ShowCompleted}"
|
||||
VerticalAlignment="Center" Foreground="#555"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Input Area -->
|
||||
<Border Grid.Row="1" Margin="15" Background="White" CornerRadius="8" Padding="10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox Text="{Binding NewContent, UpdateSourceTrigger=PropertyChanged}"
|
||||
FontSize="14" Padding="5" Margin="0,0,10,0" BorderThickness="0,0,0,1"
|
||||
VerticalContentAlignment="Center"
|
||||
Tag="添加新任务...">
|
||||
<TextBox.Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Text" Value="">
|
||||
<!-- Placeholder could be done with a visual brush or adornment, keeping it simple for now -->
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding AddTaskCommand}"/>
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
|
||||
<ComboBox Grid.Column="1" ItemsSource="{Binding Source={StaticResource PriorityEnum}}"
|
||||
SelectedItem="{Binding NewPriority}"
|
||||
Width="80" Margin="0,0,10,0" VerticalContentAlignment="Center">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource EnumDescConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Button Grid.Column="2" Content="添加" Command="{Binding AddTaskCommand}" Style="{StaticResource ModernButton}"
|
||||
VerticalAlignment="Center" Height="32" Width="80"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Task List -->
|
||||
<ListBox Grid.Row="2" ItemsSource="{Binding Tasks}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Margin="15,0,15,15"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Margin" Value="0,0,0,10"/>
|
||||
<Setter Property="Padding" Value="10"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="8" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<CheckBox IsChecked="{Binding IsCompleted}"
|
||||
Command="{Binding DataContext.ToggleCompleteCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="{Binding}"
|
||||
VerticalAlignment="Center">
|
||||
<CheckBox.LayoutTransform>
|
||||
<ScaleTransform ScaleX="1.2" ScaleY="1.2"/>
|
||||
</CheckBox.LayoutTransform>
|
||||
</CheckBox>
|
||||
|
||||
<StackPanel Grid.Column="1" Margin="15,0">
|
||||
<TextBlock Text="{Binding Content}" FontSize="16" VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsCompleted}" Value="True">
|
||||
<Setter Property="TextDecorations" Value="Strikethrough"/>
|
||||
<Setter Property="Foreground" Value="#999"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsCompleted}" Value="False">
|
||||
<Setter Property="Foreground" Value="#333"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{Binding Priority, Converter={StaticResource EnumDescConverter}}" FontSize="12" Foreground="#888" Margin="0,2,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Column="3" Content="✕"
|
||||
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="{Binding}"
|
||||
Width="24" Height="24"
|
||||
Background="Transparent" Foreground="#FF3B30"
|
||||
BorderThickness="0" FontSize="12" FontWeight="Bold"
|
||||
Cursor="Hand">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border" Background="{TemplateBinding Background}" CornerRadius="12">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Background" Value="#1AFF3B30"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<!-- Settings Dialog Overlay -->
|
||||
<Grid Grid.RowSpan="3" Background="#80000000" Visibility="{Binding IsSettingsOpen, Converter={StaticResource BoolToVis}}">
|
||||
<Border Background="White" Width="300" Height="200" CornerRadius="10" VerticalAlignment="Center" HorizontalAlignment="Center" Padding="20">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="快捷键设置" FontSize="18" FontWeight="Bold" HorizontalAlignment="Center"/>
|
||||
|
||||
<StackPanel Grid.Row="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="当前支持组合键 (如 Alt+X, Ctrl+Alt+A)" Foreground="#666" Margin="0,0,0,5" FontSize="12" HorizontalAlignment="Center"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<TextBox x:Name="ShortcutBox"
|
||||
Text="{Binding FullShortcut, Mode=OneWay}"
|
||||
Width="200" FontSize="16"
|
||||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||||
PreviewKeyDown="ShortcutBox_PreviewKeyDown"
|
||||
CaretBrush="Transparent"
|
||||
IsReadOnly="True"
|
||||
Cursor="Hand"
|
||||
Padding="5"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="点击上方框并按下快捷键" Foreground="#999" FontSize="12" HorizontalAlignment="Center" Margin="0,5,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Content="取消" Command="{Binding CloseSettingsCommand}" Width="80" Margin="0,0,10,0"
|
||||
Background="#EEE" Foreground="#333" Height="30" BorderThickness="0">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="5">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
<Button Content="保存" Command="{Binding SaveSettingsCommand}" Width="80" Height="30" Style="{StaticResource ModernButton}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
62
TodoList/Views/MainWindow.xaml.cs
Normal file
62
TodoList/Views/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using TodoList.ViewModels;
|
||||
|
||||
namespace TodoList.Views
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow(MainViewModel viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
}
|
||||
|
||||
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
this.Hide();
|
||||
// Verify if app shuts down? No, ShutdownMode is Explicit.
|
||||
}
|
||||
|
||||
private void ShortcutBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
|
||||
// Ignore modifier keys alone being the "main" key
|
||||
if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl ||
|
||||
e.Key == Key.LeftAlt || e.Key == Key.RightAlt ||
|
||||
e.Key == Key.LeftShift || e.Key == Key.RightShift ||
|
||||
e.Key == Key.LWin || e.Key == Key.RWin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var key = e.Key;
|
||||
if (key == Key.System) key = e.SystemKey; // Handle Alt+Key
|
||||
|
||||
// Build modifier string
|
||||
var modifiers = new System.Collections.Generic.List<string>();
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) modifiers.Add("Control");
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt) modifiers.Add("Alt");
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift) modifiers.Add("Shift");
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Windows) == ModifierKeys.Windows) modifiers.Add("Windows");
|
||||
|
||||
// Map key to string
|
||||
string keyStr = key.ToString();
|
||||
|
||||
// Simple mapping for letters/digits (A-Z, 0-9)
|
||||
if (keyStr.Length == 2 && keyStr.StartsWith("D") && char.IsDigit(keyStr[1]))
|
||||
{
|
||||
keyStr = keyStr.Substring(1);
|
||||
}
|
||||
|
||||
// Update ViewModel
|
||||
if (DataContext is MainViewModel vm)
|
||||
{
|
||||
vm.ShortcutModifiers = string.Join(",", modifiers);
|
||||
vm.ShortcutKey = keyStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
TodoList/Views/QuickEntryWindow.xaml
Normal file
84
TodoList/Views/QuickEntryWindow.xaml
Normal file
@@ -0,0 +1,84 @@
|
||||
<Window x:Class="TodoList.Views.QuickEntryWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:TodoList.Views"
|
||||
xmlns:models="clr-namespace:TodoList.Models"
|
||||
xmlns:converters="clr-namespace:TodoList.Converters"
|
||||
mc:Ignorable="d"
|
||||
Title="新建待办" Height="220" Width="400"
|
||||
WindowStyle="None" ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Topmost="True"
|
||||
Background="White"
|
||||
BorderBrush="#007AFF" BorderThickness="1">
|
||||
|
||||
<Window.Effect>
|
||||
<DropShadowEffect BlurRadius="20" ShadowDepth="5" Opacity="0.3"/>
|
||||
</Window.Effect>
|
||||
|
||||
<Window.Resources>
|
||||
<ObjectDataProvider x:Key="PriorityEnum" MethodName="GetValues"
|
||||
ObjectType="{x:Type sys:Enum}"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib">
|
||||
<ObjectDataProvider.MethodParameters>
|
||||
<x:Type TypeName="models:TodoPriority"/>
|
||||
</ObjectDataProvider.MethodParameters>
|
||||
</ObjectDataProvider>
|
||||
<converters:EnumDescriptionConverter x:Key="EnumDescConverter"/>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="5" BorderThickness="0">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="新建待办" FontSize="18" FontWeight="Bold" Foreground="#333"/>
|
||||
|
||||
<TextBox Grid.Row="1" Margin="0,15,0,15"
|
||||
Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
|
||||
FontSize="14" Padding="8" BorderThickness="0,0,0,1"
|
||||
x:Name="InputBox">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Enter" Command="{Binding SaveCommand}"/>
|
||||
<KeyBinding Key="Esc" Command="{Binding CancelCommand}"/>
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" VerticalAlignment="Top">
|
||||
<TextBlock Text="优先级:" VerticalAlignment="Center" Margin="0,0,10,0" Foreground="#666"/>
|
||||
<ComboBox ItemsSource="{Binding Source={StaticResource PriorityEnum}}"
|
||||
SelectedItem="{Binding Priority}"
|
||||
Width="100">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource EnumDescConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="取消" Command="{Binding CancelCommand}" Margin="0,0,10,0" Width="80" Height="32"
|
||||
Background="#F0F0F0" Foreground="#333"/>
|
||||
<Button Content="保存" Command="{Binding SaveCommand}" Width="80" Height="32" IsDefault="True"
|
||||
Background="#007AFF" Foreground="White"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
23
TodoList/Views/QuickEntryWindow.xaml.cs
Normal file
23
TodoList/Views/QuickEntryWindow.xaml.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using TodoList.Services;
|
||||
using TodoList.ViewModels;
|
||||
|
||||
namespace TodoList.Views
|
||||
{
|
||||
public partial class QuickEntryWindow : Window
|
||||
{
|
||||
public QuickEntryWindow(IDataService dataService)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = new QuickEntryViewModel(dataService, () => this.Hide());
|
||||
}
|
||||
|
||||
protected override void OnActivated(EventArgs e)
|
||||
{
|
||||
base.OnActivated(e);
|
||||
InputBox.Focus();
|
||||
InputBox.SelectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
TodoList/icon.ico
Normal file
BIN
TodoList/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 192 KiB |
2
publish.ps1
Normal file
2
publish.ps1
Normal file
@@ -0,0 +1,2 @@
|
||||
dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true --self-contained true -p:IncludeNativeLibrariesForSelfExtract=true -o bin\PublishSingleFile
|
||||
dotnet new wpf -n TodoList.Installer -o TodoList.Installer
|
||||
Reference in New Issue
Block a user