A:增加悬浮窗口,与控制台标识联动

This commit is contained in:
jimmy committed 2025-07-27 16:07:37 +08:00
1 parent 797fd99f4c
commit 7655d25080
7 files changed
+744 -241

No files matched your search

@@ -6,246 +6,92 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModel="clr-namespace:Shell.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
Background="#F0F2F5">
d:DesignHeight="120"
d:DesignWidth="200"
Background="Transparent">
<d:UserControl.DataContext>
<viewModel:NetworkMonitorViewModel />
</d:UserControl.DataContext>
<Grid
Margin="10">
<Grid.RowDefinitions>
<RowDefinition
Height="Auto" />
<RowDefinition
Height="20" />
<RowDefinition
Height="*" />
</Grid.RowDefinitions>
<!-- 网络接口选择 -->
<Grid
Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto" />
<ColumnDefinition
Width="*" />
<ColumnDefinition
Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Content="选择网络接口:"
VerticalAlignment="Center" />
<ComboBox
Grid.Column="1"
Margin="5,0"
VerticalAlignment="Center"
ItemsSource="{Binding NetworkInterfaces}"
SelectedItem="{Binding SelectedInterface}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto" />
<ColumnDefinition
Width="*" />
<ColumnDefinition
Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="{Binding Name}"
Margin="0,0,10,0" />
<TextBlock
Grid.Column="1"
Text="{Binding Description}"
Foreground="Gray" />
<TextBlock
Grid.Column="2"
Margin="10,0,0,0">
<TextBlock.Style>
<Style
TargetType="TextBlock">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsConnected}"
Value="True">
<Setter
Property="Text"
Value="[已连接]" />
<Setter
Property="Foreground"
Value="Green" />
</DataTrigger>
<DataTrigger
Binding="{Binding IsConnected}"
Value="False">
<Setter
Property="Text"
Value="[未连接]" />
<Setter
Property="Foreground"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button
Grid.Column="2"
Content="刷新"
Padding="10,5"
Command="{Binding RefreshCommand}" />
</Grid>
<!-- 网速显示区域 -->
<Border
Grid.Row="2"
BorderBrush="#CCCCCC"
BorderThickness="1"
CornerRadius="5"
Background="#F9F9F9">
<Grid
Margin="20">
<Grid.RowDefinitions>
<RowDefinition
Height="Auto" />
<RowDefinition
Height="20" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="20" />
<RowDefinition
Height="Auto" />
</Grid.RowDefinitions>
<Border
CornerRadius="8"
Background="#F0FFFFFF"
BorderBrush="#20000000"
BorderThickness="1"
Margin="5">
<Border.Effect>
<DropShadowEffect
ShadowDepth="2"
Direction="315"
Color="#40000000"
Opacity="0.3"
BlurRadius="8" />
</Border.Effect>
<Grid Margin="10,8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 上传速度 -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Text="实时网速监控"
FontSize="18"
FontWeight="Bold"
HorizontalAlignment="Center" />
<!-- 下载速度 -->
<Grid
Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="120" />
<ColumnDefinition
Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
Orientation="Horizontal">
<TextBlock
Text="▼"
Foreground="Green"
FontWeight="Bold"
Margin="0,0,5,0" />
<TextBlock
Text="下载速度:"
FontSize="14"
VerticalAlignment="Center" />
</StackPanel>
<TextBlock
Grid.Column="1"
Text="{Binding DownloadSpeed}"
FontSize="16"
FontWeight="Bold"
Foreground="Green"
VerticalAlignment="Center" />
</Grid>
<!-- 上传速度 -->
<Grid
Grid.Row="3"
Margin="0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="120" />
<ColumnDefinition
Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
Orientation="Horizontal">
<TextBlock
Text="▲"
Foreground="Blue"
FontWeight="Bold"
Margin="0,0,5,0" />
<TextBlock
Text="上传速度:"
FontSize="14"
VerticalAlignment="Center" />
</StackPanel>
<TextBlock
Grid.Column="1"
Text="{Binding UploadSpeed}"
FontSize="16"
FontWeight="Bold"
Foreground="Blue"
VerticalAlignment="Center" />
</Grid>
<!-- 总速度 -->
<Grid
Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="120" />
<ColumnDefinition
Width="*" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
Orientation="Horizontal">
<TextBlock
Text="Σ"
Foreground="DarkOrange"
FontWeight="Bold"
FontSize="16"
Margin="0,0,5,0" />
<TextBlock
Text="总速度:"
FontSize="14"
VerticalAlignment="Center" />
</StackPanel>
<TextBlock
Grid.Column="1"
Text="{Binding TotalSpeed}"
FontSize="16"
FontWeight="Bold"
Foreground="DarkOrange"
VerticalAlignment="Center" />
</Grid>
<!-- 状态信息 -->
<TextBlock
Grid.Row="6"
Text="{Binding StatusText}"
Grid.Column="0"
Text="↑"
Foreground="#FF6B6B"
FontSize="12"
Foreground="Gray"
HorizontalAlignment="Center" />
FontWeight="Bold"
VerticalAlignment="Center"
Margin="0,0,6,0" />
<TextBlock
Grid.Column="1"
Text="{Binding UploadSpeed}"
FontSize="12"
FontWeight="SemiBold"
Foreground="#333333"
VerticalAlignment="Center" />
</Grid>
</Border>
</Grid>
<!-- 下载速度 -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="↓"
Foreground="#4ECDC4"
FontSize="12"
FontWeight="Bold"
VerticalAlignment="Center"
Margin="0,0,6,0" />
<TextBlock
Grid.Column="1"
Text="{Binding DownloadSpeed}"
FontSize="12"
FontWeight="SemiBold"
Foreground="#333333"
VerticalAlignment="Center" />
</Grid>
<!-- 状态信息(可选显示) -->
<TextBlock
Grid.Row="3"
Text="{Binding StatusText}"
FontSize="8"
Foreground="#666666"
HorizontalAlignment="Center"
Margin="0,4,0,0"
Visibility="Collapsed" />
</Grid>
</Border>
</UserControl>
@@ -7,7 +7,7 @@
xmlns:local="clr-namespace:Shell.UserControls"
mc:Ignorable="d"
Title="TrayMenu"
Height="180"
Height="320"
Width="150"
Topmost="True"
ShowInTaskbar="False"
@@ -133,6 +133,68 @@
</StackPanel>
</Border>
<Border
Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="ShowNetMonitor_Click">
<StackPanel
Orientation="Horizontal">
<TextBlock
Text=""
Style="{StaticResource IconStyle}" />
<TextBlock
Text="显示网速悬浮窗"
Style="{StaticResource TextStyle}"
x:Name="NetMonitorText" />
</StackPanel>
</Border>
<Rectangle
Style="{StaticResource SeparatorStyle}" />
<Border
Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="ResetWindowPosition_Click">
<StackPanel
Orientation="Horizontal">
<TextBlock
Text=""
Style="{StaticResource IconStyle}" />
<TextBlock
Text="重置悬浮窗位置"
Style="{StaticResource TextStyle}" />
</StackPanel>
</Border>
<Border
Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="ToggleClickThrough_Click">
<StackPanel
Orientation="Horizontal">
<TextBlock
Text=""
Style="{StaticResource IconStyle}" />
<TextBlock
Text="切换点击穿透"
Style="{StaticResource TextStyle}"
x:Name="ClickThroughText" />
</StackPanel>
</Border>
<Border
Style="{StaticResource MenuItemStyle}"
MouseLeftButtonDown="ToggleTopmost_Click">
<StackPanel
Orientation="Horizontal">
<TextBlock
Text=""
Style="{StaticResource IconStyle}" />
<TextBlock
Text="切换置顶状态"
Style="{StaticResource TextStyle}"
x:Name="TopmostText" />
</StackPanel>
</Border>
<Rectangle
Style="{StaticResource SeparatorStyle}" />
@@ -4,6 +4,7 @@ using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Controls;
namespace Shell.UserControls;
@@ -12,10 +13,20 @@ public partial class TrayMenuWindow : Window
public event Action OnShowMainWindow;
public event Action OnExitApplication;
public event Action OnHideToTray;
public event Action OnShowNetMonitor;
public event Action OnResetWindowPosition;
public event Action OnToggleClickThrough;
public event Action OnToggleTopmost;
// Properties to track NetView state
public bool IsNetViewVisible { get; set; } = true;
public bool IsClickThroughEnabled { get; set; } = false;
public bool IsTopmostEnabled { get; set; } = true;
public bool IsPositionLocked { get; set; } = false;
// 菜单的固定尺寸(与XAML中设置的一致)
private const double MENU_WIDTH = 150;
private const double MENU_HEIGHT = 180;
private const double MENU_HEIGHT = 320;
[DllImport("dwmapi.dll")]
private static extern int DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind);
@@ -287,6 +298,48 @@ public partial class TrayMenuWindow : Window
this.Hide();
}
private void ShowNetMonitor_Click(object sender, MouseButtonEventArgs e)
{
OnShowNetMonitor?.Invoke();
this.Hide();
}
private void ResetWindowPosition_Click(object sender, MouseButtonEventArgs e)
{
OnResetWindowPosition?.Invoke();
this.Hide();
}
private void ToggleClickThrough_Click(object sender, MouseButtonEventArgs e)
{
OnToggleClickThrough?.Invoke();
this.Hide();
}
private void ToggleTopmost_Click(object sender, MouseButtonEventArgs e)
{
OnToggleTopmost?.Invoke();
this.Hide();
}
public void UpdateMenuStates(bool netViewVisible, bool clickThrough, bool topmost, bool positionLocked)
{
IsNetViewVisible = netViewVisible;
IsClickThroughEnabled = clickThrough;
IsTopmostEnabled = topmost;
IsPositionLocked = positionLocked;
// Update menu item texts directly
if (NetMonitorText != null)
NetMonitorText.Text = IsNetViewVisible ? "隐藏网速悬浮窗" : "显示网速悬浮窗";
if (ClickThroughText != null)
ClickThroughText.Text = IsClickThroughEnabled ? "禁用点击穿透" : "启用点击穿透";
if (TopmostText != null)
TopmostText.Text = IsTopmostEnabled ? "取消置顶" : "设为置顶";
}
private void Window_Deactivated(object sender, EventArgs e)
{
// 当窗口失去焦点时自动隐藏
@@ -171,9 +171,19 @@ public class TrayIconManager : IDisposable
_trayMenu.OnShowMainWindow += () => ShowMainWindow();
_trayMenu.OnExitApplication += () => System.Windows.Application.Current.Shutdown();
_trayMenu.OnHideToTray += () => _mainWindow?.Hide();
// Connect new NetView-specific functionality
_trayMenu.OnShowNetMonitor += () => ShowNetMonitor();
_trayMenu.OnResetWindowPosition += () => ResetNetViewPosition();
_trayMenu.OnToggleClickThrough += () => ToggleNetViewClickThrough();
_trayMenu.OnToggleTopmost += () => ToggleNetViewTopmost();
Console.WriteLine("创建新的TrayMenuWindow");
}
// Update menu states before showing
UpdateTrayMenuStates();
// 使用Win32 API获取鼠标位置
if (GetCursorPos(out POINT mousePos))
{
@@ -290,6 +300,102 @@ public class TrayIconManager : IDisposable
return (workArea, dpiScaleX, dpiScaleY);
}
private void ShowNetMonitor()
{
if (_mainWindow != null)
{
_mainWindow.Show();
_mainWindow.Activate();
}
}
private void ResetNetViewPosition()
{
if (_mainWindow is Shell.Views.NetView netView)
{
// Call the public method to reset position
var workArea = SystemParameters.WorkArea;
netView.Left = workArea.Right - netView.Width - 20;
netView.Top = workArea.Top + 20;
}
}
private void ToggleNetViewClickThrough()
{
if (_mainWindow is Shell.Views.NetView netView)
{
// Access the NetView's private method through a public interface
// For now, let's access the property directly if possible
var contextMenu = netView.ContextMenu;
if (contextMenu?.Items != null)
{
foreach (var item in contextMenu.Items)
{
if (item is System.Windows.Controls.MenuItem menuItem &&
menuItem.Name == "ClickThroughMenuItem")
{
menuItem.IsChecked = !menuItem.IsChecked;
var routedEventArgs = new System.Windows.RoutedEventArgs(System.Windows.Controls.MenuItem.ClickEvent);
menuItem.RaiseEvent(routedEventArgs);
break;
}
}
}
}
}
private void ToggleNetViewTopmost()
{
if (_mainWindow is Shell.Views.NetView netView)
{
netView.Topmost = !netView.Topmost;
// Update the context menu item
var contextMenu = netView.ContextMenu;
if (contextMenu?.Items != null)
{
foreach (var item in contextMenu.Items)
{
if (item is System.Windows.Controls.MenuItem menuItem &&
menuItem.Name == "TopmostMenuItem")
{
menuItem.IsChecked = netView.Topmost;
break;
}
}
}
}
}
private void UpdateTrayMenuStates()
{
if (_mainWindow is Shell.Views.NetView netView && _trayMenu != null)
{
// Get current state from NetView
bool isVisible = netView.IsVisible;
bool isTopmost = netView.Topmost;
// Get click-through state from context menu
bool isClickThrough = false;
var contextMenu = netView.ContextMenu;
if (contextMenu?.Items != null)
{
foreach (var item in contextMenu.Items)
{
if (item is System.Windows.Controls.MenuItem menuItem &&
menuItem.Name == "ClickThroughMenuItem")
{
isClickThrough = menuItem.IsChecked;
break;
}
}
}
// Update tray menu with current states
_trayMenu.UpdateMenuStates(isVisible, isClickThrough, isTopmost, false);
}
}
public void Dispose()
{
_notifyIcon?.Dispose();
+73 -4
View File
@@ -7,10 +7,79 @@
xmlns:local="clr-namespace:Shell"
xmlns:userControls="clr-namespace:Shell.UserControls"
mc:Ignorable="d"
Title="NetView"
Height="450"
Width="800">
<Grid>
Title="NetVizor - 网速监控"
Height="60"
Width="100"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
Topmost="True"
ShowInTaskbar="False"
ResizeMode="CanResize"
Left="{Binding WindowLeft, Mode=TwoWay}"
Top="{Binding WindowTop, Mode=TwoWay}">
<Window.ContextMenu>
<ContextMenu>
<MenuItem Header="重置窗口位置" Click="ResetPosition_Click">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="锁定窗口位置" Click="LockPosition_Click" IsCheckable="True" x:Name="LockPositionMenuItem">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="启用点击穿透" Click="ToggleClickThrough_Click" IsCheckable="True" x:Name="ClickThroughMenuItem">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="始终置顶" Click="ToggleTopmost_Click" IsCheckable="True" IsChecked="True" x:Name="TopmostMenuItem">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="不能超出屏幕" Click="ToggleSnapToScreen_Click" IsCheckable="True" x:Name="SnapToScreenMenuItem">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="切换监控网络" x:Name="NetworkSelectionMenuItem">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" />
</MenuItem.Icon>
<!-- Submenu items will be populated dynamically -->
</MenuItem>
<MenuItem Header="显示详细信息" Click="ToggleDetailedInfo_Click" IsCheckable="True" x:Name="DetailedInfoMenuItem">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="流量统计与分析" Click="OpenTrafficAnalysis_Click">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="隐藏到托盘" Click="HideToTray_Click">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="退出程序" Click="ExitApplication_Click">
<MenuItem.Icon>
<TextBlock Text="" FontFamily="Segoe Fluent Icons" Foreground="#D32F2F" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Window.ContextMenu>
<Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<userControls:NetworkMonitorView />
</Grid>
</Window>
@@ -1,5 +1,8 @@
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using Shell.Utils;
namespace Shell.Views;
@@ -7,11 +10,143 @@ namespace Shell.Views;
public partial class NetView : Window
{
private TrayIconManager _trayIconManager;
private bool _isClickThrough = false;
private bool _isPositionLocked = false;
private bool _snapToScreen = false;
private bool _showDetailedInfo = false;
// Win32 API for click-through functionality
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
// Win32 API to disable window snapping
[DllImport("user32.dll")]
private static extern bool SystemParametersInfo(int uiAction, int uiParam, IntPtr pvParam, int fWinIni);
private const int GWL_EXSTYLE = -20;
private const int WS_EX_TRANSPARENT = 0x00000020;
private const int SPI_SETDRAGFULLWINDOWS = 0x0025;
public NetView()
{
InitializeComponent();
InitializeTrayIcon();
this.Loaded += NetView_Loaded;
// Prevent window snapping by setting ResizeMode appropriately
this.ResizeMode = ResizeMode.NoResize;
}
private void NetView_Loaded(object sender, RoutedEventArgs e)
{
// Initialize default position if needed
if (this.Left == 0 && this.Top == 0)
{
ResetToDefaultPosition();
}
// Disable automatic window arrangement features
DisableWindowSnapping();
// Populate network interfaces in context menu
PopulateNetworkMenu();
}
private void PopulateNetworkMenu()
{
if (NetworkSelectionMenuItem != null)
{
// Get the NetworkMonitorView and its ViewModel
var networkMonitorView = FindChild<UserControls.NetworkMonitorView>(this);
if (networkMonitorView?.DataContext is Shell.ViewModel.NetworkMonitorViewModel viewModel)
{
// Clear existing items
NetworkSelectionMenuItem.Items.Clear();
// Add network interfaces from ViewModel
foreach (var netInterface in viewModel.NetworkInterfaces)
{
var menuItem = new System.Windows.Controls.MenuItem();
// Create header with status indicator
var headerText = netInterface.Name;
if (netInterface.IsConnected)
{
headerText += " [已连接]";
}
else
{
headerText += " [未连接]";
}
menuItem.Header = headerText;
menuItem.IsCheckable = true;
menuItem.IsChecked = netInterface == viewModel.SelectedInterface;
menuItem.Tag = netInterface;
// Set different styles for connected/disconnected
if (netInterface.IsConnected)
{
menuItem.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
}
else
{
menuItem.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Gray);
}
menuItem.Click += (s, e) => {
if (s is System.Windows.Controls.MenuItem clickedItem &&
clickedItem.Tag is Shell.ViewModel.NetworkInterfaceItem selectedInterface)
{
// Update selection in ViewModel
viewModel.SelectedInterface = selectedInterface;
// Update check states
foreach (System.Windows.Controls.MenuItem item in NetworkSelectionMenuItem.Items)
{
item.IsChecked = item == clickedItem;
}
}
};
NetworkSelectionMenuItem.Items.Add(menuItem);
}
}
}
}
private T FindChild<T>(DependencyObject parent) where T : DependencyObject
{
if (parent == null) return null;
for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = System.Windows.Media.VisualTreeHelper.GetChild(parent, i);
if (child is T result)
return result;
var childResult = FindChild<T>(child);
if (childResult != null)
return childResult;
}
return null;
}
private void DisableWindowSnapping()
{
// Additional protection against window snapping
var hwnd = new WindowInteropHelper(this).Handle;
if (hwnd != IntPtr.Zero)
{
// Set window style to prevent automatic sizing
var style = GetWindowLong(hwnd, -16); // GWL_STYLE
// Remove maximize and minimize capabilities that can trigger snapping
SetWindowLong(hwnd, -16, style & ~0x10000 & ~0x20000); // Remove WS_MAXIMIZEBOX and WS_MINIMIZEBOX
}
}
private void InitializeTrayIcon()
@@ -35,7 +170,214 @@ public partial class NetView : Window
// 如果你想要真正关闭程序的方法
public void ForceClose()
{
// Close TrafficAnalysisWindow if it exists
try
{
if (TrafficAnalysisWindow.Instance != null)
{
TrafficAnalysisWindow.Instance.ForceClose();
}
}
catch (Exception ex)
{
// Log but don't prevent shutdown
System.Diagnostics.Debug.WriteLine($"Error closing TrafficAnalysisWindow: {ex.Message}");
}
_trayIconManager?.Dispose();
System.Windows.Application.Current.Shutdown();
}
}
// Right-click menu event handlers
private void ResetPosition_Click(object sender, RoutedEventArgs e)
{
ResetToDefaultPosition();
}
private void ResetToDefaultPosition()
{
var workArea = SystemParameters.WorkArea;
this.Left = workArea.Right - this.Width - 20;
this.Top = workArea.Top + 20;
}
private void LockPosition_Click(object sender, RoutedEventArgs e)
{
_isPositionLocked = !_isPositionLocked;
LockPositionMenuItem.IsChecked = _isPositionLocked;
// Don't change ResizeMode to maintain consistent window behavior
// The dragging logic will check _isPositionLocked instead
}
private void ToggleClickThrough_Click(object sender, RoutedEventArgs e)
{
_isClickThrough = !_isClickThrough;
ClickThroughMenuItem.IsChecked = _isClickThrough;
SetClickThrough(_isClickThrough);
}
private void SetClickThrough(bool enabled)
{
var hwnd = new WindowInteropHelper(this).Handle;
if (hwnd != IntPtr.Zero)
{
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
if (enabled)
{
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
}
else
{
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle & ~WS_EX_TRANSPARENT);
}
}
}
private void ToggleTopmost_Click(object sender, RoutedEventArgs e)
{
this.Topmost = !this.Topmost;
TopmostMenuItem.IsChecked = this.Topmost;
}
private void ToggleSnapToScreen_Click(object sender, RoutedEventArgs e)
{
_snapToScreen = !_snapToScreen;
SnapToScreenMenuItem.IsChecked = _snapToScreen;
if (_snapToScreen)
{
SnapToScreen();
}
}
private void SnapToScreen()
{
var workArea = SystemParameters.WorkArea;
var left = Math.Max(workArea.Left, Math.Min(this.Left, workArea.Right - this.Width));
var top = Math.Max(workArea.Top, Math.Min(this.Top, workArea.Bottom - this.Height));
this.Left = left;
this.Top = top;
}
private void ToggleDetailedInfo_Click(object sender, RoutedEventArgs e)
{
_showDetailedInfo = !_showDetailedInfo;
DetailedInfoMenuItem.IsChecked = _showDetailedInfo;
// Toggle window height for detailed info
if (_showDetailedInfo)
{
this.Height = 200; // Expanded height for detailed info
}
else
{
this.Height = 120; // Compact height
}
}
private void HideToTray_Click(object sender, RoutedEventArgs e)
{
this.Hide();
}
private void ExitApplication_Click(object sender, RoutedEventArgs e)
{
var result = System.Windows.MessageBox.Show("确定要退出程序吗?", "确认退出",
MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
ForceClose();
}
}
private void OpenTrafficAnalysis_Click(object sender, RoutedEventArgs e)
{
try
{
TrafficAnalysisWindow.ShowWindow();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show($"无法打开流量分析窗口: {ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
protected override void OnLocationChanged(EventArgs e)
{
base.OnLocationChanged(e);
// Snap to screen if enabled
if (_snapToScreen && !_isPositionLocked)
{
SnapToScreen();
}
}
// Handle window dragging
private void Grid_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// Only allow dragging if position is not locked and click-through is disabled
if (!_isPositionLocked && !_isClickThrough)
{
try
{
this.DragMove();
// After drag move, check and snap back if outside screen bounds
SnapBackToScreen();
}
catch (InvalidOperationException)
{
// DragMove can throw exception if called when mouse is not pressed
// This can happen in edge cases, so we catch and ignore
}
}
}
private void SnapBackToScreen()
{
var workArea = SystemParameters.WorkArea;
var originalLeft = this.Left;
var originalTop = this.Top;
// Calculate bounds with some tolerance (allow partial off-screen)
var tolerance = 20; // Allow 20 pixels to be off-screen
var minLeft = workArea.Left - (this.Width - tolerance);
var maxLeft = workArea.Right - tolerance;
var minTop = workArea.Top - (this.Height - tolerance);
var maxTop = workArea.Bottom - tolerance;
// Adjust position if outside bounds
var newLeft = Math.Max(minLeft, Math.Min(this.Left, maxLeft));
var newTop = Math.Max(minTop, Math.Min(this.Top, maxTop));
// Only animate if position actually changed
if (Math.Abs(newLeft - originalLeft) > 1 || Math.Abs(newTop - originalTop) > 1)
{
AnimateToPosition(newLeft, newTop);
}
}
private void AnimateToPosition(double targetLeft, double targetTop)
{
var duration = TimeSpan.FromMilliseconds(200);
var leftAnimation = new System.Windows.Media.Animation.DoubleAnimation(
this.Left, targetLeft, duration)
{
EasingFunction = new System.Windows.Media.Animation.QuadraticEase()
};
var topAnimation = new System.Windows.Media.Animation.DoubleAnimation(
this.Top, targetTop, duration)
{
EasingFunction = new System.Windows.Media.Animation.QuadraticEase()
};
this.BeginAnimation(Window.LeftProperty, leftAnimation);
this.BeginAnimation(Window.TopProperty, topAnimation);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<!-- Windows 兼容性声明 -->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{4f476546-9373-4e33-b969-1e0b7c6f6d46}"/>
<!-- Windows 8 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<!-- DPI 设置 -->
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<dpiAwareness>PerMonitorV2</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>