添加项目文件。
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user