WPF聊天应用:登录后在ListView显示所有在线用户的最优方案
解决WPF聊天应用在线用户列表显示问题的最优实现方案
核心问题分析
现有代码存在以下关键缺陷导致无法显示所有在线用户:
- 职责划分混乱:LoginVM同时承担登录逻辑与主窗口用户列表管理,违反MVVM单一职责原则
- 缺少初始用户同步:仅监听新用户连接事件,登录成功后未获取服务器当前全量在线用户列表
- 未处理用户下线场景:没有监听用户断开连接的事件,列表无法动态更新
最优实现步骤
1. 重构ViewModel职责分离
- LoginVM:专注登录认证、服务器连接初始化,登录成功后将必要数据传递给MainViewModel
- MainViewModel:负责维护在线用户集合、处理用户连接/断开事件
2. 修改LoginVM代码
调整登录流程,先获取全量在线用户再初始化主窗口:
public class LoginVM : viewModelBase { private readonly User user; private Server _server; public ICommand CombinedCommand { get; } public ICommand CloseLoginWindowCommand { get; } public LoginVM() { user = new User(); _server = new Server(); CombinedCommand = new RelayCommand(o => { // 先连接服务器并获取全量在线用户 _server.ConnectToServer(UserName); var initialUsers = _server.GetOnlineUsers(); // 新增服务器方法:返回当前所有在线用户 // 初始化主窗口ViewModel InitializeMainWindow(initialUsers); CloseLoginWindow(); }, o => canExecuteCommands(o)); } public string UserName { get => user.Name; set { user.Name = value; OnPropertyChanged(nameof(UserName)); } } private void InitializeMainWindow(IEnumerable<User> initialUsers) { var mainVM = new MainViewModel(_server, initialUsers); MainWindow mainWindow = new MainWindow(); mainWindow.DataContext = mainVM; mainWindow.Show(); } private bool canExecuteCommands(object parameter) { return parameter is string userName && !string.IsNullOrEmpty(userName); } private void CloseLoginWindow() { Application.Current.Windows[0]?.Close(); } }
3. 实现MainViewModel
负责用户集合管理与事件监听:
public class MainViewModel : viewModelBase { private readonly Server _server; private readonly SynchronizationContext _syncContext = SynchronizationContext.Current; public ObservableCollection<User> OnlineUsers { get; } public MainViewModel(Server server, IEnumerable<User> initialUsers) { _server = server; OnlineUsers = new ObservableCollection<User>(initialUsers); // 订阅用户连接/断开事件 _server.connectedEvent += OnUserConnected; _server.disconnectedEvent += OnUserDisconnected; } private void OnUserConnected() { var newUser = new User { Name = _server._packetReader.ReadMessage() }; // 线程安全更新UI集合 _syncContext.Post(_ => { if (!OnlineUsers.Contains(newUser)) { OnlineUsers.Add(newUser); } }, null); } private void OnUserDisconnected() { var disconnectedUserName = _server._packetReader.ReadMessage(); _syncContext.Post(_ => { var userToRemove = OnlineUsers.FirstOrDefault(u => u.Name == disconnectedUserName); if (userToRemove != null) { OnlineUsers.Remove(userToRemove); } }, null); } // 清理事件订阅,避免内存泄漏 ~MainViewModel() { _server.connectedEvent -= OnUserConnected; _server.disconnectedEvent -= OnUserDisconnected; } }
4. 优化User类
实现相等性判断,确保集合操作准确:
public class User : IEquatable<User> { public string Name { get; set; } public bool Equals(User other) { return other != null && string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase); } public override bool Equals(object obj) => Equals(obj as User); public override int GetHashCode() => Name?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0; }
5. 主窗口ListView绑定
在MainWindow.xaml中绑定OnlineUsers集合:
<ListView ItemsSource="{Binding OnlineUsers}" DisplayMemberPath="Name" Margin="10"/>
关键优化点说明
- 使用
SynchronizationContext替代Dispatcher.Invoke,实现更通用的线程安全UI更新 - 明确ViewModel职责边界,LoginVM只处理登录流程,MainVM专注用户列表管理
- 补充用户下线事件处理,保证列表实时性
- 实现User类的相等性接口,避免重复添加或错误删除用户
内容的提问来源于stack exchange,提问作者Dima Natchkebia
相关产品推荐
相关产品推荐

