Compare commits

...

5 Commits

Author SHA1 Message Date
15147b88a6 增强窗口关闭处理和资源清理
在 `LoginWindow.xaml` 中添加了 `Closing` 事件处理程序,以便在窗口关闭时执行特定逻辑。新增的 `Window_Closing` 方法会检查 `MainWindow.token` 是否为 `null`,并在必要时关闭应用程序。此外,在 `MainWindow.xaml.cs` 中确保在清理资源时将 `token` 设置为 `null`,以避免潜在的资源泄漏或错误。
2025-06-07 16:17:28 +08:00
424311f088 增强托盘功能和界面样式
在 `App.xaml` 中更新资源字典,添加 `MaterialTrayMenuItem.xaml` 以支持托盘菜单样式。
在 `MainWindow.xaml` 中添加 `Closed` 和 `Loaded` 事件处理程序。
在 `MainWindow.xaml.cs` 中引入 `TrayIconManager`,管理系统托盘图标及其行为。
更新 `chatclient.csproj` 中 `chat.ico` 的属性以确保复制到输出目录。
在 `MaterialTrayMenuItem.xaml` 中定义 Material Design 风格的托盘菜单项和上下文菜单样式。
创建 `TrayIconManager.cs` 类以优化托盘图标的创建和事件处理,提升用户体验。
2025-06-07 16:10:38 +08:00
d7e9a93bf6 清空消息框以更新用户界面
在 `MainWindow.xaml.cs` 中,修改了发送数据的逻辑。添加了使用 `Application.Current.Dispatcher.Invoke` 方法清空 `txtMessage` 文本框的代码,以确保在发送数据之前用户界面得到更新。删除了原来的发送数据代码行。
2025-06-07 13:26:21 +08:00
635eb14c9c 重构消息输入区域并优化异步发送逻辑
在 `MainWindow.xaml` 中,将消息输入区域替换为 `materialDesign:Card`,调整了 `TextBox` 和 `Button` 的布局,并新增了 `Snackbar` 用于信息提示。

在 `MainWindow.xaml.cs` 中,将 `SendMessage_Click` 和 `SendMessage` 方法修改为异步,增加了对 `Socket` 连接状态的检查,并在连接失败时提供用户提示。同时新增了 `QueueMessage` 方法以支持将消息添加到 `Snackbar` 的消息队列中。
2025-06-07 13:25:09 +08:00
e82ae53a42 优化用户登录和聊天消息处理逻辑
在 `chatapi.cs` 中为 `LoginResultData` 添加 `username` 属性。
更新 `MainWindow.xaml` 中的 `TextBlock` 绑定属性为 `UserName`,并调整头像的 `Margin` 属性。
在 `MainWindow.xaml.cs` 中优化登录成功后的 `UserName` 赋值逻辑,改进聊天消息的创建方式,注释掉旧逻辑,更新默认消息内容为 `(无内容)`,提升代码可读性和用户体验。
2025-06-07 12:13:12 +08:00
9 changed files with 313 additions and 80 deletions

View File

@ -5,14 +5,15 @@
xmlns:local="clr-namespace:chatclient"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign2.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml" />
<ResourceDictionary Source="pack://application:,,,/chatclient;component/Data/MaterialTrayMenuItem.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -0,0 +1,45 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
<!-- Material Design 托盘菜单样式 -->
<Style x:Key="MaterialTrayMenuItem" TargetType="MenuItem" BasedOn="{StaticResource MaterialDesignMenuItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="12,8"/>
<Setter Property="Foreground" Value="{DynamicResource MaterialDesignBody}"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="MenuItem">
<Border x:Name="Border"
Background="Transparent"
CornerRadius="4"
SnapsToDevicePixels="True">
<Grid>
<materialDesign:Ripple Content="{TemplateBinding Header}" Background="Transparent" Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center" Padding="{TemplateBinding Padding}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter TargetName="Border" Property="Background" Value="{DynamicResource MaterialDesignSelection}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MaterialTrayMenu" TargetType="ContextMenu">
<Setter Property="Background" Value="{DynamicResource MaterialDesignPaper}"/>
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesignDivider}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="8"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="16" ShadowDepth="4" Color="#40000000"/>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@ -0,0 +1,122 @@
using System.Windows;
using Hardcodet.Wpf.TaskbarNotification;
using System.Windows.Controls;
using MaterialDesignThemes.Wpf;
using System.Windows.Input;
using System.Drawing;
using System.Diagnostics;
namespace chatclient.Data
{
public class TrayIconManager
{
private readonly TaskbarIcon _trayIcon;
private readonly Window _mainWindow;
public TrayIconManager(Window mainWindow)
{
_mainWindow = mainWindow;
// 创建托盘图标
_trayIcon = new TaskbarIcon
{
Icon = new System.Drawing.Icon("resource/chat.ico"),
ToolTipText = "Material Design Application",
ContextMenu = CreateContextMenu()
};
// 注册事件
_trayIcon.TrayMouseDoubleClick += TrayIcon_TrayMouseDoubleClick;
_mainWindow.Closing += MainWindow_ClosingHandler;
}
private void MainWindow_ClosingHandler(object? sender, System.ComponentModel.CancelEventArgs e)
{
// 取消关闭操作,改为最小化到托盘
e.Cancel = true;
_mainWindow.Hide();
// 显示通知
//_trayIcon.ShowBalloonTip("Application minimized",
// "The application is running in the system tray",
// BalloonIcon.Info);
}
private ContextMenu CreateContextMenu()
{
// 创建Material Design风格的上下文菜单
var contextMenu = new ContextMenu
{
Style = (Style)Application.Current.Resources["MaterialTrayMenu"]
};
// 添加菜单项
contextMenu.Items.Add(CreateMenuItem("Open Application", PackIconKind.WindowRestore, OpenApp_Click));
contextMenu.Items.Add(CreateMenuItem("Settings", PackIconKind.Cog, Settings_Click));
contextMenu.Items.Add(new Separator { Style = (Style)Application.Current.Resources["MaterialDesignLightSeparator"] });
contextMenu.Items.Add(CreateMenuItem("Check for Updates", PackIconKind.Update, Updates_Click));
contextMenu.Items.Add(CreateMenuItem("Help", PackIconKind.HelpCircle, Help_Click));
contextMenu.Items.Add(new Separator { Style = (Style)Application.Current.Resources["MaterialDesignLightSeparator"] });
contextMenu.Items.Add(CreateMenuItem("Exit", PackIconKind.Power, Exit_Click));
return contextMenu;
}
private MenuItem CreateMenuItem(string header, PackIconKind iconKind, RoutedEventHandler clickHandler)
{
var menuItem = new MenuItem
{
Header = header,
Style = (Style)Application.Current.Resources["MaterialTrayMenuItem"],
Icon = new PackIcon { Kind = iconKind, Width = 20, Height = 20 }
};
menuItem.Click += clickHandler;
return menuItem;
}
private void TrayIcon_TrayMouseDoubleClick(object sender, RoutedEventArgs e)
{
RestoreApplication();
}
private void OpenApp_Click(object sender, RoutedEventArgs e)
{
RestoreApplication();
}
private void RestoreApplication()
{
_mainWindow.Show();
_mainWindow.WindowState = WindowState.Normal;
_mainWindow.Activate();
}
private void Settings_Click(object sender, RoutedEventArgs e)
{
// 实现设置逻辑
}
private void Updates_Click(object sender, RoutedEventArgs e)
{
// 实现更新检查逻辑
}
private void Help_Click(object sender, RoutedEventArgs e)
{
// 实现帮助逻辑
}
private void Exit_Click(object sender, RoutedEventArgs e)
{
// 释放托盘图标资源
Dispose();
Application.Current.Shutdown();
}
public void Dispose()
{
_trayIcon.Dispose();
}
}
}

View File

@ -20,6 +20,7 @@ namespace chatclient.Data
public string? status { get; set; }
public string? message { get; set; }
public string? token { get; set; }
public string? username { get; set; }
}
internal class SignData
{

View File

@ -8,7 +8,7 @@
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" x:Class="chatclient.LoginWindow"
mc:Ignorable="d"
Title="LoginWindow" Height="590" Width="360" MinHeight="590" MinWidth="360" MaxHeight="590" MaxWidth="360"
ResizeMode="NoResize">
ResizeMode="NoResize" Closing="Window_Closing">
<TabControl>
<TabItem Header="登录账号" Cursor="Hand" Height="40">

View File

@ -213,6 +213,10 @@ namespace chatclient
// 例如UserName = LoadSavedUsername();
// Update("UserName");
}
public void Window_Closing(object sender, CancelEventArgs e)
{
if(MainWindow.token == null) Application.Current.Shutdown();
}
}
}

View File

@ -8,16 +8,16 @@
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" x:Class="chatclient.MainWindow"
mc:Ignorable="d"
Title="ChatWindow" Height="450" Width="800" MinHeight="240" MinWidth="380"
Style="{StaticResource MaterialDesignWindow}">
Style="{StaticResource MaterialDesignWindow}" Closed="MainWindow_Closed" Loaded="MainWindow_Loaded">
<Grid>
<materialDesign:Card>
<TabControl VerticalContentAlignment="Bottom" materialDesign:ColorZoneAssist.Mode="PrimaryMid" Style="{StaticResource MaterialDesignNavigationRailTabControl}">
<materialDesign:NavigationRailAssist.FloatingContent>
<StackPanel>
<Button Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}" Margin="12" >
<Button Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}" Margin="12">
<Image Source="/resource/user.png" />
</Button>
<TextBlock Text="{Binding Username}"/>
<TextBlock Text="{Binding UserName}" TextAlignment="Center" MaxWidth="64" MaxHeight="64" TextWrapping="Wrap"/>
</StackPanel>
</materialDesign:NavigationRailAssist.FloatingContent>
<TabItem>
@ -69,7 +69,7 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- 左侧头像仅当Alignment=Left时显示 -->
<Border x:Name="leftAvatar" Grid.Column="0" Width="40" Height="40" Margin="10,0,10,0" CornerRadius="20" VerticalAlignment="Top">
<Border x:Name="leftAvatar" Grid.Column="0" Width="40" Height="40" Margin="10,0,5,0" CornerRadius="20" VerticalAlignment="Top">
<!-- 图片头像 -->
<Image Source="{Binding Image}" Stretch="UniformToFill" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image.Clip>
@ -86,7 +86,7 @@
</StackPanel>
</materialDesign:Card>
<!-- 右侧头像仅当Alignment=Right时显示 -->
<Border x:Name="rightAvatar" Grid.Column="2" Width="40" Height="40" Margin="10,0,10,0" CornerRadius="20" VerticalAlignment="Top">
<Border x:Name="rightAvatar" Grid.Column="2" Width="40" Height="40" Margin="5,0,10,0" CornerRadius="20" VerticalAlignment="Top">
<Image Source="{Binding Image}" Stretch="UniformToFill" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image.Clip>
<EllipseGeometry Center="20,20" RadiusX="20" RadiusY="20"/>
@ -112,21 +112,18 @@
</ItemsControl>
</ScrollViewer>
<Grid Grid.Row="2" Background="{DynamicResource MaterialDesign.Brush.Primary.Foreground}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtMessage" Grid.Column="0"
materialDesign:HintAssist.Hint="输入消息..."
AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap" MinHeight="60" MaxHeight="120" Margin="5"/>
<Button x:Name="btnSend" Grid.Column="1" Content="发送" MinWidth="80"
Style="{StaticResource MaterialDesignRaisedButton}"
Click="SendMessage_Click"/>
</Grid>
<materialDesign:Card materialDesign:ElevationAssist.Elevation="Dp8" Grid.Row="2">
<Grid Margin="5,0,5,0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox x:Name="txtMessage" Grid.Row="0" materialDesign:HintAssist.Hint="输入消息..." AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap" MinHeight="50" MaxHeight="100" Margin="5" BorderBrush="#00000000"/>
<Button x:Name="btnSend" Grid.Row="1" Content="发送" MinWidth="80" Style="{StaticResource MaterialDesignRaisedButton}"
Click="SendMessage_Click" Width="20" HorizontalAlignment="Right" Margin="2,2,4,4"/>
</Grid>
</materialDesign:Card>
</Grid>
</Grid>
</TabItem>
@ -143,5 +140,7 @@
</TabItem>
</TabControl>
</materialDesign:Card>
<!--信息框-->
<materialDesign:Snackbar x:Name="SnackbarThree" MessageQueue="{materialDesign:MessageQueue}" />
</Grid>
</Window>

View File

@ -56,6 +56,7 @@ namespace chatclient
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(UpdateName));
}
private TrayIconManager? _trayManager;
public MainWindow()
{
InitializeComponent();
@ -84,6 +85,8 @@ namespace chatclient
MessageScroller.ScrollToEnd();
}), DispatcherPriority.ContextIdle);
};
//Loaded += MainWindow_Loaded;
//Closed += MainWindow_Closed;
}
public static void StartReceive()
{
@ -135,6 +138,7 @@ namespace chatclient
if (LoginResponse!.status == "success" && LoginResponse != null)
{
token = LoginResponse.token;
UserName = LoginResponse.username ?? "Unnamed";
Application.Current.Dispatcher.Invoke(() =>
{
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
@ -181,18 +185,37 @@ namespace chatclient
Application.Current.Dispatcher.Invoke(() =>
{
// 处理聊天消息
var chatmessage = new ChatMessage
if (chat.user == UserName)
{
Sender = chat.user ?? "未知用户",
Type = MessageType.Text,
Image = new BitmapImage(new Uri(chat.image ?? "pack://application:,,,/resource/user.png", UriKind.Absolute)),
Content = chat.message ?? "无内容",
Timestamp = chat.timestamp,
Alignment = HorizontalAlignment.Left,
SenderColor = new SolidColorBrush(Colors.Black)
};
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
mainWindow?.Messages.Add(chatmessage);
var chatmessage = new ChatMessage
{
Sender = chat.user ?? "未知用户",
Type = MessageType.Text,
Image = new BitmapImage(new Uri(chat.image ?? "pack://application:,,,/resource/user.png", UriKind.Absolute)),
Content = chat.message ?? "(无内容)",
Timestamp = DateTime.Now,
Alignment = HorizontalAlignment.Right,
SenderColor = new SolidColorBrush(Colors.Blue)
};
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
mainWindow?.Messages.Add(chatmessage);
}
else
{
var chatmessage = new ChatMessage
{
Sender = chat.user ?? "未知用户",
Type = MessageType.Text,
Image = new BitmapImage(new Uri(chat.image ?? "pack://application:,,,/resource/user.png", UriKind.Absolute)),
Content = chat.message ?? "(无内容)",
Timestamp = chat.timestamp,
Alignment = HorizontalAlignment.Left,
SenderColor = new SolidColorBrush(Colors.Black)
};
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
mainWindow?.Messages.Add(chatmessage);
}
});
}
else
@ -220,11 +243,11 @@ namespace chatclient
log.Error("处理响应时发生错误", ex);
}
}
private void SendMessage_Click(object sender, RoutedEventArgs e)
private async void SendMessage_Click(object sender, RoutedEventArgs e)
{
SendMessage();
await SendMessage();
}
private void SendMessage()
private async Task SendMessage()
{
if (string.IsNullOrWhiteSpace(txtMessage.Text))
return;
@ -234,54 +257,90 @@ namespace chatclient
// 判断是否为群组,若是则收件人设为“所有人”,否则为联系人显示名
//string recipient = contact?.IsGroup == true ? "所有人" : contact?.DisplayName;
// 弃用的方法
// 创建新消息
var newMessage = new ChatMessage
{
Sender = "我",
Type = MessageType.Text,
Image = new BitmapImage(new Uri("pack://application:,,,/resource/user.png", UriKind.Absolute)), // 默认头像
Content = txtMessage.Text,
Timestamp = DateTime.Now,
Alignment = HorizontalAlignment.Right, // 自己发送的消息靠右
SenderColor = new SolidColorBrush(Colors.Blue)
};
//var newMessage = new ChatMessage
//{
// Sender = "我",
// Type = MessageType.Text,
// Image = new BitmapImage(new Uri("pack://application:,,,/resource/user.png", UriKind.Absolute)), // 默认头像
// Content = txtMessage.Text,
// Timestamp = DateTime.Now,
// Alignment = HorizontalAlignment.Right, // 自己发送的消息靠右
// SenderColor = new SolidColorBrush(Colors.Blue)
//};
var newChatMessage = new ChatData
{
type = "chat",
message = newMessage.Content
message = txtMessage.Text
};
string ChatJsonData = JsonSerializer.Serialize(newChatMessage);
byte[] dataBytes = Encoding.UTF8.GetBytes(ChatJsonData);
log.Info($"向服务器聊天信息(长度:{dataBytes.Length})");
if (Client != null)
// 检查Socket是否可用
if (Client?.Connected == true)
{
if (Client.Connected)
Client.Send(dataBytes);
Application.Current.Dispatcher.Invoke(() =>
{
Client.Send(dataBytes);
}
else
{
try
{
log.Info("未连接服务器,尝试连接");
Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Client.Connect(IPAddress.Parse(Server.ServerIP), Server.ServerPort);
}
catch (Exception ex)
{
log.Error(ex);
Client.Close();
}
//finally
//{
//
//}
}
txtMessage.Clear();
});
return;
}
log.Info("未连接服务器,尝试异步连接");
// 异步连接操作
await Task.Run(() =>
{
try
{
Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Client?.Connect(IPAddress.Parse(Server.ServerIP), Server.ServerPort);
StartReceive();
Client?.Send(dataBytes);
Application.Current.Dispatcher.Invoke(() =>
{
txtMessage.Clear();
});
}
catch (Exception ex)
{
log.Error($"连接失败: {ex.Message}");
Client?.Close();
Application.Current.Dispatcher.Invoke(() =>
{
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
if (mainWindow != null)
{
QueueMessage("连接失败,请检查网络设置或服务器状态。");
}
});
}
});
// 添加到消息列表
Messages.Add(newMessage);
// 清空输入框
txtMessage.Clear();
//Messages.Add(newMessage);
}
private void QueueMessage(string message)
{
if (SnackbarThree.MessageQueue is { } messageQueue)
{
//use the message queue to send a message.
//the message queue can be called from any thread
Task.Factory.StartNew(() => messageQueue.Enqueue(message));
}
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
// 初始化托盘管理器
_trayManager = new TrayIconManager(this);
}
private void MainWindow_Closed(object sender, System.EventArgs e)
{
// 清理资源
Client?.Shutdown(SocketShutdown.Both);
Client?.Close();
Client?.Dispose();
token = null;
_trayManager?.Dispose();
}
}
}

View File

@ -18,7 +18,9 @@
</PropertyGroup>
<ItemGroup>
<Content Include="resource\chat.ico" />
<Content Include="resource\chat.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>