@@ -18,8 +18,7 @@ namespace Services.App
1818{
1919 public sealed partial class MainWindow : Window
2020 {
21- private System . Windows . Forms . NotifyIcon ? _notifyIcon ;
22-
21+ private H . NotifyIcon . TaskbarIcon ? TrayIcon ;
2322 private readonly WindowsServiceManager _serviceManager ;
2423 private readonly EnvironmentManager _envManager ;
2524 private readonly LogManager _logManager ;
@@ -36,94 +35,127 @@ public MainWindow()
3635 ExtendsContentIntoTitleBar = true ;
3736 SetTitleBar ( AppTitleBar ) ;
3837
39- InitializeTrayIcon ( ) ;
40-
4138 var hWnd = WindowNative . GetWindowHandle ( this ) ;
4239 var windowId = Microsoft . UI . Win32Interop . GetWindowIdFromWindow ( hWnd ) ;
4340 _appWindow = AppWindow . GetFromWindowId ( windowId ) ;
44- _appWindow . Closing += OnAppWindowClosing ;
4541 _appWindow . Resize ( new Windows . Graphics . SizeInt32 ( 1800 , 1200 ) ) ;
4642
43+ // Hide window instead of closing
44+ _appWindow . Closing += ( s , args ) =>
45+ {
46+ if ( ! _isRealExit )
47+ {
48+ args . Cancel = true ;
49+ _appWindow . Hide ( ) ;
50+ }
51+ } ;
52+
4753 _serviceManager = new WindowsServiceManager ( ) ;
4854 _serviceManager . ServiceUpdated += OnServiceUpdated ;
4955
5056 _envManager = new EnvironmentManager ( ) ;
5157 _logManager = new LogManager ( ) ;
5258
59+ InitializeTrayIcon ( ) ;
60+
5361 LoadServices ( ) ;
5462 Title = "ServicesApp" ;
5563
5664 this . Closed += ( s , e ) => _serviceManager . Dispose ( ) ;
65+
66+ var timer = new DispatcherTimer { Interval = TimeSpan . FromSeconds ( 2 ) } ;
67+ timer . Tick += ( s , e ) => LoadServices ( true ) ;
68+ timer . Start ( ) ;
5769 }
5870
5971 private void InitializeTrayIcon ( )
6072 {
6173 try
6274 {
63- _notifyIcon = new System . Windows . Forms . NotifyIcon ( ) ;
64- _notifyIcon . Text = "ServicesApp" ;
75+ TrayIcon = new H . NotifyIcon . TaskbarIcon ( ) ;
76+ TrayIcon . ToolTipText = "ServicesApp" ;
6577
78+ // Use absolute path for icon
6679 var iconPath = System . IO . Path . Combine ( AppContext . BaseDirectory , "Assets" , "icon.ico" ) ;
6780 if ( System . IO . File . Exists ( iconPath ) )
6881 {
69- _notifyIcon . Icon = new System . Drawing . Icon ( iconPath ) ;
70- }
71- else
72- {
73- _notifyIcon . Icon = System . Drawing . SystemIcons . Application ;
82+ TrayIcon . IconSource = new Microsoft . UI . Xaml . Media . Imaging . BitmapImage ( new Uri ( iconPath ) ) ;
7483 }
75-
76- _notifyIcon . Visible = true ;
77- _notifyIcon . DoubleClick += ( s , e ) => OnTrayOpenClick ( null , null ) ;
78-
79- var contextMenu = new System . Windows . Forms . ContextMenuStrip ( ) ;
80- var showItem = new System . Windows . Forms . ToolStripMenuItem ( "显示窗口" ) ;
81- showItem . Click += ( s , e ) => OnTrayOpenClick ( null , null ) ;
8284
83- var exitItem = new System . Windows . Forms . ToolStripMenuItem ( "退出" ) ;
84- exitItem . Click += ( s , e ) =>
85- {
86- _isRealExit = true ;
87- _notifyIcon . Visible = false ;
88- _notifyIcon . Dispose ( ) ;
89- Application . Current . Exit ( ) ;
90- } ;
85+ // Important: Add to visual tree FIRST to ensure it has a XamlRoot and Dispatcher
86+ RootGrid . Children . Add ( TrayIcon ) ;
9187
92- contextMenu . Items . Add ( showItem ) ;
93- contextMenu . Items . Add ( new System . Windows . Forms . ToolStripSeparator ( ) ) ;
94- contextMenu . Items . Add ( exitItem ) ;
88+ // Use SecondWindow mode to support context menu in unpackaged apps
89+ TrayIcon . ContextMenuMode = H . NotifyIcon . ContextMenuMode . SecondWindow ;
90+ TrayIcon . NoLeftClickDelay = true ; // Improve responsiveness
9591
96- _notifyIcon . ContextMenuStrip = contextMenu ;
92+ // Setup events
93+ TrayIcon . DoubleTapped += ( s , e ) => ShowWindow ( ) ;
94+
95+ // Initialize Flyout
96+ var flyout = new MenuFlyout ( ) ;
97+ flyout . AreOpenCloseAnimationsEnabled = false ; // Disable animations for better performance
98+
99+ // Use standard style with necessary adjustments only
100+ var presenterStyle = new Style ( typeof ( MenuFlyoutPresenter ) ) ;
101+ presenterStyle . Setters . Add ( new Setter ( ScrollViewer . VerticalScrollBarVisibilityProperty , ScrollBarVisibility . Disabled ) ) ;
102+ presenterStyle . Setters . Add ( new Setter ( ScrollViewer . HorizontalScrollBarVisibilityProperty , ScrollBarVisibility . Disabled ) ) ;
103+ presenterStyle . Setters . Add ( new Setter ( Control . PaddingProperty , new Thickness ( 4 ) ) ) ;
104+ presenterStyle . Setters . Add ( new Setter ( Control . CornerRadiusProperty , new CornerRadius ( 4 ) ) ) ;
105+ presenterStyle . Setters . Add ( new Setter ( FrameworkElement . MaxWidthProperty , 240 ) ) ;
106+
107+ flyout . MenuFlyoutPresenterStyle = presenterStyle ;
108+ flyout . Placement = Microsoft . UI . Xaml . Controls . Primitives . FlyoutPlacementMode . Top ; // Prefer top placement
109+
110+ var showItem = new MenuFlyoutItem { Text = "显示窗口" , Icon = new FontIcon { Glyph = "\uE7F4 " } } ;
111+ showItem . Click += OnShowWindowClick ;
112+
113+ var exitItem = new MenuFlyoutItem { Text = "退出" , Icon = new FontIcon { Glyph = "\uE711 " } } ;
114+ exitItem . Click += OnExitClick ;
115+
116+ flyout . Items . Add ( showItem ) ;
117+ flyout . Items . Add ( new MenuFlyoutSeparator ( ) ) ;
118+ flyout . Items . Add ( exitItem ) ;
119+
120+ TrayIcon . ContextFlyout = flyout ;
121+
122+ // Ensure icon is created
123+ TrayIcon . ForceCreate ( ) ;
124+
125+ // Ensure icon is visible
126+ TrayIcon . Visibility = Visibility . Visible ;
97127 }
98128 catch ( Exception ex )
99129 {
100130 System . Diagnostics . Debug . WriteLine ( $ "Tray Icon Init Failed: { ex } ") ;
101131 }
102132 }
103133
104- private void OnAppWindowClosing ( AppWindow sender , AppWindowClosingEventArgs args )
105- {
106- if ( ! _isRealExit )
107- {
108- args . Cancel = true ;
109- _appWindow . Hide ( ) ;
110- }
111- }
112-
113- private void OnTrayOpenClick ( object ? sender , object ? e )
134+ public void ShowWindow ( )
114135 {
115136 _appWindow . Show ( ) ;
137+ _appWindow . MoveInZOrderAtTop ( ) ;
116138 this . Activate ( ) ;
117139 }
118140
119- private void OnTrayExitClick ( object sender , RoutedEventArgs e )
141+ public void RealExit ( )
120142 {
121143 _isRealExit = true ;
122- _notifyIcon ? . Dispose ( ) ;
123-
144+ TrayIcon ? . Dispose ( ) ;
124145 Application . Current . Exit ( ) ;
125146 }
126147
148+ private void OnShowWindowClick ( object sender , RoutedEventArgs e )
149+ {
150+ ShowWindow ( ) ;
151+ }
152+
153+ private void OnExitClick ( object sender , RoutedEventArgs e )
154+ {
155+ RealExit ( ) ;
156+ }
157+
158+
127159 private void OnServiceUpdated ( object ? sender , Service service )
128160 {
129161 this . DispatcherQueue . TryEnqueue ( ( ) =>
@@ -457,7 +489,109 @@ private async void OnEnvVarsClick(object sender, RoutedEventArgs e)
457489
458490 private async void OnEditClick ( object sender , RoutedEventArgs e )
459491 {
460- await ShowDialog ( "提示" , "编辑功能暂未实现。" ) ;
492+ if ( sender is Button btn && btn . Tag is string serviceId )
493+ {
494+ var service = Services . FirstOrDefault ( s => s . Id == serviceId ) ;
495+ if ( service == null ) return ;
496+
497+ var dialog = new ContentDialog
498+ {
499+ Title = $ "编辑服务 - { service . Name } ",
500+ PrimaryButtonText = "保存" ,
501+ CloseButtonText = "取消" ,
502+ XamlRoot = this . Content . XamlRoot
503+ } ;
504+
505+ var stack = new StackPanel { Spacing = 10 } ;
506+ var nameBox = new TextBox { Header = "服务名称 (不可修改)" , Text = service . Name , IsReadOnly = true } ;
507+ var exeBox = new TextBox { Header = "可执行文件路径" , Text = service . ExePath , PlaceholderText = "C:\\ Path\\ To\\ App.exe" } ;
508+ var argsBox = new TextBox { Header = "启动参数 (可选)" , Text = service . Args ?? "" } ;
509+ var workDirBox = new TextBox { Header = "工作目录 (可选)" , Text = service . WorkingDir ?? "" } ;
510+
511+ var browseBtn = new Button { Content = "浏览..." } ;
512+ browseBtn . Click += async ( s , args ) => {
513+ string ? pickedPath = null ;
514+ try
515+ {
516+ var picker = new FileOpenPicker ( ) ;
517+ picker . ViewMode = PickerViewMode . List ;
518+ picker . SuggestedStartLocation = PickerLocationId . ComputerFolder ;
519+ picker . FileTypeFilter . Add ( ".exe" ) ;
520+ picker . FileTypeFilter . Add ( ".bat" ) ;
521+ picker . FileTypeFilter . Add ( ".cmd" ) ;
522+
523+ var hwnd = WindowNative . GetWindowHandle ( this ) ;
524+ WinRT . Interop . InitializeWithWindow . Initialize ( picker , hwnd ) ;
525+
526+ var file = await picker . PickSingleFileAsync ( ) ;
527+ if ( file != null ) pickedPath = file . Path ;
528+ }
529+ catch ( Exception ex )
530+ {
531+ System . Diagnostics . Debug . WriteLine ( $ "WinUI Picker failed: { ex } ") ;
532+ pickedPath = await PickFileWithPowerShell ( ) ;
533+ }
534+
535+ if ( pickedPath != null )
536+ {
537+ exeBox . Text = pickedPath ;
538+ if ( string . IsNullOrWhiteSpace ( workDirBox . Text ) )
539+ {
540+ workDirBox . Text = System . IO . Path . GetDirectoryName ( pickedPath ) ?? "" ;
541+ }
542+ }
543+ } ;
544+
545+ stack . Children . Add ( nameBox ) ;
546+ stack . Children . Add ( exeBox ) ;
547+ stack . Children . Add ( browseBtn ) ;
548+ stack . Children . Add ( argsBox ) ;
549+ stack . Children . Add ( workDirBox ) ;
550+ dialog . Content = stack ;
551+
552+ var result = await dialog . ShowAsync ( ) ;
553+ if ( result == ContentDialogResult . Primary )
554+ {
555+ if ( string . IsNullOrWhiteSpace ( exeBox . Text ) )
556+ {
557+ await ShowDialog ( "验证错误" , "可执行文件路径不能为空。" ) ;
558+ return ;
559+ }
560+
561+ try
562+ {
563+ var config = new ServiceConfig
564+ {
565+ Name = service . Name ,
566+ ExePath = exeBox . Text ,
567+ Args = argsBox . Text ,
568+ WorkingDir = workDirBox . Text
569+ } ;
570+
571+ if ( service . Status == "运行中" || service . Status == "启动中" )
572+ {
573+ var confirm = await ShowConfirmDialog ( "需要重启" , "修改服务配置需要重启服务才能生效。是否立即重启?" ) ;
574+ await _serviceManager . UpdateServiceAsync ( service . Id , config ) ;
575+ if ( confirm )
576+ {
577+ await _serviceManager . StopServiceAsync ( service . Id ) ;
578+ await _serviceManager . StartServiceAsync ( service . Id ) ;
579+ }
580+ }
581+ else
582+ {
583+ await _serviceManager . UpdateServiceAsync ( service . Id , config ) ;
584+ }
585+
586+ LoadServices ( ) ;
587+ UpdateStatus ( $ "服务 { config . Name } 配置已更新。") ;
588+ }
589+ catch ( Exception ex )
590+ {
591+ await ShowDialog ( "错误" , $ "更新服务失败: { ex . Message } ") ;
592+ }
593+ }
594+ }
461595 }
462596
463597 private async Task ShowDialog ( string title , string content )
0 commit comments