A:增加悬浮窗口,与控制台标识联动
This commit is contained in:
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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user