移除1.0.0版本

This commit is contained in:
ShaoHua
2026-04-05 00:57:04 +08:00
parent ceb77e624e
commit 4daa0c4eba
20 changed files with 0 additions and 1862 deletions
-9
View File
@@ -1,9 +0,0 @@
<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>
-364
View File
@@ -1,364 +0,0 @@
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;
using System.Reflection;
namespace TodoList
{
public partial class App : System.Windows.Application
{
private const string MainWindowTitle = "待办事项";
private IDataService _dataService;
private GlobalShortcutService _shortcutService;
private MainWindow _mainWindow;
private QuickEntryWindow? _quickEntryWindow;
private SettingsService _settingsService;
private 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, MainWindowTitle);
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 SqliteDataService();
_shortcutService = new GlobalShortcutService();
var mainViewModel = new MainViewModel(_dataService, _settingsService);
_mainWindow = new MainWindow(mainViewModel);
_mainWindow.Title = MainWindowTitle;
_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 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 = GetNotifyIconText();
_notifyIcon.DoubleClick += (s, e) => ShowMainWindow();
var contextMenu = new ContextMenuStrip();
contextMenu.Items.Add("打开主界面", null, (s, e) => ShowMainWindow());
contextMenu.Items.Add("退出", null, (s, e) => ExitApplication());
_notifyIcon.ContextMenuStrip = contextMenu;
}
private static string GetNotifyIconText()
{
var version = GetDisplayVersion();
var text = string.IsNullOrWhiteSpace(version) ? MainWindowTitle : $"{MainWindowTitle} v{version}";
return text.Length > 63 ? text[..63] : text;
}
private static string? GetDisplayVersion()
{
var asm = Assembly.GetExecutingAssembly();
var info = asm.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion?.Trim();
if (!string.IsNullOrWhiteSpace(info))
{
var plus = info.IndexOf('+');
return plus >= 0 ? info[..plus] : info;
}
var file = asm.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version?.Trim();
if (!string.IsNullOrWhiteSpace(file))
{
return file;
}
return asm.GetName().Version?.ToString();
}
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.");
ShowQuickEntryWindow();
}
private void ShowQuickEntryWindow()
{
if (_quickEntryWindow == null)
{
_quickEntryWindow = new QuickEntryWindow(_dataService);
_quickEntryWindow.Title = "新建待办";
}
if (_quickEntryWindow.WindowState == WindowState.Minimized)
{
_quickEntryWindow.WindowState = WindowState.Normal;
}
_quickEntryWindow.Show();
_quickEntryWindow.Activate();
var helper = new WindowInteropHelper(_quickEntryWindow);
var handle = helper.Handle;
if (handle != IntPtr.Zero)
{
SetForegroundWindow(handle);
}
}
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
View File
@@ -1,10 +0,0 @@
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)
)]
-55
View File
@@ -1,55 +0,0 @@
$ErrorActionPreference = "Stop"
# Basic configuration
$ScriptPath = $PSScriptRoot
$ProjectFile = (Get-ChildItem -Path $ScriptPath -Filter "*.csproj" -File)[0].FullName
$SetupScript = Join-Path $ScriptPath "setup.iss"
# Read version from project file
$currentVersion = "1.0.0"
[xml]$csproj = Get-Content $ProjectFile
if ($csproj.Project.PropertyGroup.Version) {
$currentVersion = $csproj.Project.PropertyGroup.Version
}
# Increment version
$versionParts = $currentVersion.Split(".")
$patch = [int]$versionParts[2] + 1
$newVersion = $versionParts[0] + "." + $versionParts[1] + "." + $patch
# Update project version
$content = Get-Content $ProjectFile -Raw
$content = $content -replace "<Version>.*</Version>", "<Version>$newVersion</Version>"
Set-Content $ProjectFile -Value $content
# Update setup script version
if (Test-Path $SetupScript) {
$issContent = Get-Content $SetupScript
for ($i = 0; $i -lt $issContent.Count; $i++) {
if ($issContent[$i] -like '#define MyAppVersion *') {
$issContent[$i] = '#define MyAppVersion "' + $newVersion + '"'
break
}
}
Set-Content $SetupScript -Value $issContent
}
# Build project
dotnet publish $ProjectFile -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true
if ($LASTEXITCODE -ne 0) {
Write-Error "Build failed"
exit 1
}
# Package
$ISCC = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe"
if (Test-Path $ISCC) {
& $ISCC $SetupScript
if ($LASTEXITCODE -eq 0) {
Write-Host "Setup package created successfully!" -ForegroundColor Green
} else {
Write-Error "Packaging failed"
}
} else {
Write-Error "Inno Setup compiler not found"
}
@@ -1,31 +0,0 @@
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;
}
}
}
-67
View File
@@ -1,67 +0,0 @@
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 enum SortBy
{
[Description("创建时间")]
CreatedAt,
[Description("完成时间")]
CompletedAt,
[Description("优先级")]
Priority
}
public enum SortOrder
{
[Description("升序")]
Ascending,
[Description("降序")]
Descending
}
public partial class TodoItem : ObservableObject
{
[ObservableProperty]
[property: SQLite.PrimaryKey]
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 DateTime? completedAt;
[ObservableProperty]
private SyncStatus syncStatus = SyncStatus.Pending;
}
}
-105
View File
@@ -1,105 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
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(bool? completed = null)
{
if (!File.Exists(_filePath))
{
return new List<TodoItem>();
}
try
{
using var stream = File.OpenRead(_filePath);
var items = await JsonSerializer.DeserializeAsync<List<TodoItem>>(stream);
var tasks = items ?? new List<TodoItem>();
if (completed.HasValue)
{
return tasks.Where(t => t.IsCompleted == completed.Value).ToList();
}
return tasks;
}
catch
{
return new List<TodoItem>();
}
}
public async Task<TodoItem> 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);
return task;
}
public async Task<TodoItem> UpdateTaskAsync(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);
}
return task;
}
public async Task<TodoItem> ToggleCompleteAsync(string id)
{
var tasks = await LoadTasksAsync();
var task = tasks.Find(t => t.Id == id);
if (task != null)
{
task.IsCompleted = !task.IsCompleted;
task.CompletedAt = task.IsCompleted ? DateTime.Now : null;
task.SyncStatus = SyncStatus.Pending;
await SaveAllAsync(tasks);
}
return task;
}
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
View File
@@ -1,127 +0,0 @@
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'
}
}
}
-15
View File
@@ -1,15 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using TodoList.Models;
namespace TodoList.Services
{
public interface IDataService
{
Task<List<TodoItem>> LoadTasksAsync(bool? completed = null);
Task<TodoItem> SaveTaskAsync(TodoItem task);
Task<TodoItem> UpdateTaskAsync(TodoItem task);
Task<TodoItem> ToggleCompleteAsync(string id);
Task DeleteTaskAsync(string id);
}
}
-56
View File
@@ -1,56 +0,0 @@
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 { }
}
}
}
-76
View File
@@ -1,76 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using SQLite;
using TodoList.Models;
namespace TodoList.Services
{
public class SqliteDataService : IDataService
{
private readonly SQLiteAsyncConnection _database;
public SqliteDataService()
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var folder = Path.Combine(appData, "TodoListApp");
Directory.CreateDirectory(folder);
var databasePath = Path.Combine(folder, "TodoList.sqlite");
_database = new SQLiteAsyncConnection(databasePath);
_database.CreateTableAsync<TodoItem>().Wait();
}
public async Task<List<TodoItem>> LoadTasksAsync(bool? completed = null)
{
var query = _database.Table<TodoItem>();
if (completed.HasValue)
{
query = query.Where(t => t.IsCompleted == completed.Value);
}
return await query.ToListAsync();
}
public async Task<TodoItem> SaveTaskAsync(TodoItem task)
{
await _database.InsertOrReplaceAsync(task);
return task;
}
public async Task<TodoItem> UpdateTaskAsync(TodoItem task)
{
await _database.UpdateAsync(task);
return task;
}
public async Task<TodoItem> ToggleCompleteAsync(string id)
{
var task = await _database.FindAsync<TodoItem>(id);
if (task != null)
{
task.IsCompleted = !task.IsCompleted;
task.CompletedAt = task.IsCompleted ? DateTime.Now : null;
task.SyncStatus = SyncStatus.Pending;
await _database.UpdateAsync(task);
}
return task;
}
public async Task SaveAllAsync(List<TodoItem> tasks)
{
await _database.RunInTransactionAsync(tran =>
{
foreach (var task in tasks)
{
tran.InsertOrReplace(task);
}
});
}
public async Task DeleteTaskAsync(string id)
{
await _database.DeleteAsync<TodoItem>(id);
}
}
}
-46
View File
@@ -1,46 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CodePage>65001</CodePage>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>icon.ico</ApplicationIcon>
<Version>1.0.21</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Net.Http.Json" Version="10.0.5" />
</ItemGroup>
<ItemGroup>
<Resource Include="icon.ico" />
</ItemGroup>
</Project>
-244
View File
@@ -1,244 +0,0 @@
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 isEditDialogOpen;
[ObservableProperty]
private TodoItem editingTask;
[ObservableProperty]
private string editContent;
[ObservableProperty]
private TodoPriority editPriority = TodoPriority.Medium;
[ObservableProperty]
private bool isSettingsOpen;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullShortcut))]
private string shortcutKey;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FullShortcut))]
private string shortcutModifiers;
[ObservableProperty]
private SortBy sortBy = SortBy.Priority;
[ObservableProperty]
private Models.SortOrder sortOrder = Models.SortOrder.Descending;
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();
}
async partial void OnSortByChanged(SortBy value)
{
await LoadTasksAsync();
}
async partial void OnSortOrderChanged(Models.SortOrder 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();
IOrderedEnumerable<TodoItem> sorted;
if (SortBy == SortBy.Priority)
{
sorted = SortOrder == Models.SortOrder.Ascending
? filtered.OrderBy(t => t.Priority).ThenBy(t => t.CreatedAt)
: filtered.OrderByDescending(t => t.Priority).ThenBy(t => t.CreatedAt);
}
else if (SortBy == SortBy.CreatedAt)
{
sorted = SortOrder == Models.SortOrder.Ascending
? filtered.OrderBy(t => t.CreatedAt)
: filtered.OrderByDescending(t => t.CreatedAt);
}
else
{
sorted = SortOrder == Models.SortOrder.Ascending
? filtered.OrderBy(t => t.CompletedAt).ThenBy(t => t.CreatedAt)
: filtered.OrderByDescending(t => t.CompletedAt).ThenBy(t => t.CreatedAt);
}
Tasks.Clear();
foreach (var t in sorted)
{
Tasks.Add(t);
}
}
[RelayCommand]
private async Task ToggleCompleteAsync(TodoItem item)
{
if (item == null) return;
if (item.IsCompleted)
{
item.CompletedAt = DateTime.Now;
}
else
{
item.CompletedAt = null;
}
item.SyncStatus = SyncStatus.Pending;
await _dataService.SaveTaskAsync(item);
await LoadTasksAsync();
}
[RelayCommand]
private async Task DeleteAsync(TodoItem item)
{
if (item == null) return;
await _dataService.DeleteTaskAsync(item.Id);
Tasks.Remove(item);
}
[RelayCommand]
private void OpenEditDialog(TodoItem item)
{
if (item == null) return;
EditingTask = item;
EditContent = item.Content;
EditPriority = item.Priority;
IsEditDialogOpen = true;
}
[RelayCommand]
private void CloseEditDialog()
{
IsEditDialogOpen = false;
EditingTask = null;
EditContent = string.Empty;
EditPriority = TodoPriority.Medium;
}
[RelayCommand]
private async Task SaveEditAsync()
{
if (EditingTask == null || string.IsNullOrWhiteSpace(EditContent)) return;
EditingTask.Content = EditContent;
EditingTask.Priority = EditPriority;
EditingTask.SyncStatus = SyncStatus.Pending;
await _dataService.SaveTaskAsync(EditingTask);
await LoadTasksAsync();
CloseEditDialog();
}
public async void Receive(TaskAddedMessage message)
{
await LoadTasksAsync();
}
}
public class TaskAddedMessage
{
}
}
@@ -1,58 +0,0 @@
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();
}
}
}
-340
View File
@@ -1,340 +0,0 @@
<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="{Binding AppVersion, StringFormat='待办事项 v{0}'}" Height="450" Width="350"
Background="#F5F5F7"
Icon="/icon.ico"
WindowStartupLocation="CenterScreen"
PreviewKeyDown="Window_PreviewKeyDown">
<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>
<ObjectDataProvider x:Key="SortByEnum" MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="models:SortBy"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider x:Key="SortOrderEnum" MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="models:SortOrder"/>
</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 -->
<RowDefinition Height="Auto"/> <!-- Footer -->
</Grid.RowDefinitions>
<!-- Header / Toolbar -->
<Border Grid.Row="0" Background="White" Padding="10" Effect="{DynamicResource {x:Static DropShadowEffect.ShadowDepthProperty}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="5,0,10,0" VerticalAlignment="Center">
<ComboBox ItemsSource="{Binding Source={StaticResource SortByEnum}}"
SelectedItem="{Binding SortBy}"
Width="70" VerticalContentAlignment="Center" Margin="0,0,3,0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumDescConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox ItemsSource="{Binding Source={StaticResource SortOrderEnum}}"
SelectedItem="{Binding SortOrder}"
Width="50" VerticalContentAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumDescConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<Button Grid.Column="2" Content="设置快捷键"
Command="{Binding OpenSettingsCommand}"
Background="Transparent" Foreground="#007AFF" Margin="0,0,10,0" FontSize="11">
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</ControlTemplate>
</Button.Template>
</Button>
<CheckBox Grid.Column="3" Content="已完成"
IsChecked="{Binding ShowCompleted}"
VerticalAlignment="Center" Foreground="#555" FontSize="11"/>
</Grid>
</Border>
<!-- Input Area -->
<Border Grid.Row="1" Margin="10" Background="White" CornerRadius="6" Padding="8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding NewContent, UpdateSourceTrigger=PropertyChanged}"
FontSize="12" Padding="4" Margin="0,0,8,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="65" Margin="0,0,8,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="28" Width="65" FontSize="12"/>
</Grid>
</Border>
<!-- Task List -->
<ListBox Grid.Row="2" ItemsSource="{Binding Tasks}"
HorizontalContentAlignment="Stretch"
Background="Transparent" BorderThickness="0"
Margin="10,0,10,10"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="White"/>
<Setter Property="Margin" Value="0,0,0,6"/>
<Setter Property="Padding" Value="8"/>
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Background="{TemplateBinding Background}" CornerRadius="8" Padding="{TemplateBinding Padding}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Priority}" Value="High">
<Setter Property="Background" Value="#FFCDD2"/>
</DataTrigger>
<DataTrigger Binding="{Binding Priority}" Value="Medium">
<Setter Property="Background" Value="#FFE0B2"/>
</DataTrigger>
<DataTrigger Binding="{Binding Priority}" Value="Low">
<Setter Property="Background" Value="#C8E6C9"/>
</DataTrigger>
</Style.Triggers>
</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.0" ScaleY="1.0"/>
</CheckBox.LayoutTransform>
</CheckBox>
<StackPanel Grid.Column="1" Margin="10,0">
<TextBlock Text="{Binding Content}" FontSize="13" 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="10" Foreground="#888" Margin="0,1,0,0"/>
</StackPanel>
<Button Grid.Column="2" Content="✎"
Command="{Binding DataContext.OpenEditDialogCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Width="20" Height="20"
Background="Transparent" Foreground="#007AFF"
BorderThickness="0" FontSize="10" FontWeight="Bold"
Cursor="Hand" Margin="0,0,4,0">
<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="#1A007AFF"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
<Button Grid.Column="3" Content="✕"
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Width="20" Height="20"
Background="Transparent" Foreground="#FF3B30"
BorderThickness="0" FontSize="10" 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>
<!-- Edit Dialog Overlay -->
<Grid Grid.RowSpan="3" Background="#80000000" Visibility="{Binding IsEditDialogOpen, Converter={StaticResource BoolToVis}}">
<Border Background="White" Width="300" Height="220" CornerRadius="8" VerticalAlignment="Center" HorizontalAlignment="Center" Padding="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="编辑任务" FontSize="15" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,0,0,12"/>
<StackPanel Grid.Row="1" VerticalAlignment="Center">
<TextBlock Text="任务内容" Foreground="#666" Margin="0,0,0,4" FontSize="10"/>
<TextBox Text="{Binding EditContent, UpdateSourceTrigger=PropertyChanged}"
FontSize="12" Padding="6" Margin="0,0,0,12" BorderThickness="1" BorderBrush="#DDD"
VerticalContentAlignment="Center"/>
<TextBlock Text="优先级" Foreground="#666" Margin="0,0,0,4" FontSize="10"/>
<ComboBox ItemsSource="{Binding Source={StaticResource PriorityEnum}}"
SelectedItem="{Binding EditPriority}"
Width="260" VerticalContentAlignment="Center" BorderThickness="1" BorderBrush="#DDD" FontSize="11">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EnumDescConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,12,0,0">
<Button Content="取消" Command="{Binding CloseEditDialogCommand}" Width="70" Margin="0,0,8,0"
Background="#EEE" Foreground="#333" Height="26" BorderThickness="0" FontSize="11">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
<Button Content="保存" Command="{Binding SaveEditCommand}" Width="70" Height="26" Style="{StaticResource ModernButton}" FontSize="11"/>
</StackPanel>
</Grid>
</Border>
</Grid>
<!-- Footer -->
<Border Grid.Row="3" Background="#F5F5F7" Padding="10,8" HorizontalAlignment="Center">
<TextBlock FontSize="10">
<Hyperlink NavigateUri="https://github.com/xinshoushangdao/TodoList" RequestNavigate="Hyperlink_RequestNavigate">
<Run Text="GitHub Repository"/>
</Hyperlink>
</TextBlock>
</Border>
</Grid>
</Window>
-96
View File
@@ -1,96 +0,0 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
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 Window_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
// If settings are open, close settings?
// But user requirement is "Equals pressing X button", which usually means Close/Hide window.
// However, if we want better UX:
if (DataContext is MainViewModel { IsSettingsOpen: true } vm)
{
vm.IsSettingsOpen = false;
e.Handled = true;
return;
}
// Default behavior: Close (Hide) Window
Close();
}
}
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;
}
}
private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (sender is ListBoxItem item && item.DataContext is Models.TodoItem todoItem && DataContext is MainViewModel vm)
{
vm.OpenEditDialogCommand.Execute(todoItem);
}
}
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true });
}
}
}
-84
View File
@@ -1,84 +0,0 @@
<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="180" Width="320"
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="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="新建待办" FontSize="15" FontWeight="Bold" Foreground="#333"/>
<TextBox Grid.Row="1" Margin="0,12,0,12"
Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
FontSize="12" Padding="6" 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,8,0" Foreground="#666" FontSize="11"/>
<ComboBox ItemsSource="{Binding Source={StaticResource PriorityEnum}}"
SelectedItem="{Binding Priority}"
Width="80" FontSize="11">
<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,8,0" Width="65" Height="26"
Background="#F0F0F0" Foreground="#333" FontSize="11"/>
<Button Content="保存" Command="{Binding SaveCommand}" Width="65" Height="26" IsDefault="True"
Background="#007AFF" Foreground="#White" FontSize="11"/>
</StackPanel>
</Grid>
</Window>
-23
View File
@@ -1,23 +0,0 @@
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
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

-56
View File
@@ -1,56 +0,0 @@
#define MyAppName "TodoList"
#define MyAppVersion "1.0.21"
#define MyAppPublisher "ShaoHua"
#define MyAppURL "https://git.we965.cn/Tools/TodoList"
#define MyAppExeName "TodoList.exe"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{8B8A6E3F-1234-5678-9ABC-DEF012345678}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
DisableProgramGroupPage=yes
; Remove the following line to run in administrative install mode (install for all users.)
PrivilegesRequired=lowest
OutputDir=Output
OutputBaseFilename={#MyAppName}_Setup_v{#MyAppVersion}
SetupIconFile=icon.ico
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "bin\Release\net8.0-windows\win-x64\publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent