添加项目文件。
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