如何避免重复打开同一员工详情窗口并实现窗口聚焦?
员工详情窗口重复打开管控方案
要解决重复打开同一员工窗口的问题,完全可以在ViewModel里用字典管控已打开窗口,同时实现激活置顶的逻辑,具体改造如下:
核心思路
- 用
Dictionary<int, IDialogWindow>存储已打开的员工窗口:键为员工ID,值为对应的对话框窗口实例 - 打开窗口前先检查字典:如果该员工窗口已打开,直接激活置顶;否则新建窗口并加入字典
- 监听窗口关闭事件,关闭时从字典移除对应条目,避免内存泄漏
改造后的完整ViewModel代码
public class EmployeeListViewModel : BindableBase { private readonly IUnitOfWork _unitOfWork; private readonly IDialogService _dialogService; // 存储已打开的员工窗口:员工ID -> 对话框窗口实例 private readonly Dictionary<int, IDialogWindow> _openEmployeeWindows = new Dictionary<int, IDialogWindow>(); private ObservableCollection<Employee> employees; public ObservableCollection<Employee> Employees { get => employees; set => SetProperty(ref employees, value); } private Employee selectedEmployee; public Employee SelectedEmployee { get => selectedEmployee; set => SetProperty(ref selectedEmployee, value); } public DelegateCommand OpenSelectedEmployeeCardCommand { get; } public EmployeeListViewModel(IUnitOfWork unitOfWork, IDialogService dialogService) { _unitOfWork = unitOfWork; _dialogService = dialogService; OpenSelectedEmployeeCardCommand = new DelegateCommand(OpenSelectedEmployeeCardCommandExecute); } private void OpenSelectedEmployeeCardCommandExecute() { if (SelectedEmployee == null) return; var employeeId = SelectedEmployee.Id; // 检查是否已打开该员工的窗口 if (_openEmployeeWindows.TryGetValue(employeeId, out var existingWindow)) { // 恢复窗口正常状态(如果是最小化) if (existingWindow.WindowState == WindowState.Minimized) { existingWindow.WindowState = WindowState.Normal; } // 激活并置顶窗口 existingWindow.Activate(); existingWindow.Focus(); // 临时设置置顶,确保用户能立刻看到 existingWindow.Topmost = true; existingWindow.Topmost = false; return; } // 准备打开新窗口的参数 var parameters = new DialogParameters { { "EmployeeId", employeeId } }; // 打开对话框 _dialogService.Show(nameof(EmployeeCard), parameters, result => { // 对话框关闭时,从字典中移除(双重保障) if (_openEmployeeWindows.ContainsKey(employeeId)) { _openEmployeeWindows.Remove(employeeId); } }); // 获取刚打开的对话框窗口实例并加入字典 Application.Current.Dispatcher.BeginInvoke(() => { // 找到对应员工的对话框窗口(这里假设窗口的DataContext是EmployeeCardViewModel,且包含EmployeeId属性) var newWindow = Application.Current.Windows.OfType<IDialogWindow>() .FirstOrDefault(w => w.DataContext is EmployeeCardViewModel vm && vm.EmployeeId == employeeId); if (newWindow != null) { _openEmployeeWindows[employeeId] = newWindow; // 监听窗口关闭事件,确保字典同步更新 newWindow.Closed += (s, e) => { if (_openEmployeeWindows.ContainsKey(employeeId)) { _openEmployeeWindows.Remove(employeeId); } }; } }); } }
关键逻辑说明
- 字典管控:用
_openEmployeeWindows字典实时跟踪已打开的窗口,确保每个员工最多只有一个窗口实例 - 重复窗口处理:通过
TryGetValue快速判断窗口是否已打开,避免遍历所有窗口的低效操作 - 窗口激活置顶:先恢复窗口正常状态(防止最小化时看不到),再调用
Activate()和Focus()激活窗口,临时设置Topmost确保窗口能立刻出现在用户视野 - 内存泄漏防护:同时通过对话框关闭回调和窗口
Closed事件双重保障,确保窗口关闭时从字典中移除对应条目,避免内存泄漏
补充优化方案
如果你的EmployeeCardViewModel实现了IDialogAware接口,可以在OnDialogOpened方法中直接将窗口实例传递给EmployeeListViewModel,这样就不需要通过遍历窗口的方式获取实例,代码会更可靠:
// 在EmployeeCardViewModel中 public class EmployeeCardViewModel : BindableBase, IDialogAware { public int EmployeeId { get; set; } public event Action<int, IDialogWindow> DialogOpened; public void OnDialogOpened(IDialogParameters parameters) { if (parameters.TryGetValue<int>("EmployeeId", out var employeeId)) { EmployeeId = employeeId; // 通知父ViewModel窗口已打开 DialogOpened?.Invoke(employeeId, DialogWindow); } } // 其他IDialogAware接口实现代码... }
然后在EmployeeListViewModel的打开方法中,先获取EmployeeCardViewModel实例并订阅事件:
// 修改OpenSelectedEmployeeCardCommandExecute中的打开逻辑 var cardViewModel = _container.Resolve<EmployeeCardViewModel>(); cardViewModel.DialogOpened += (id, window) => { _openEmployeeWindows[id] = window; window.Closed += (s, e) => _openEmployeeWindows.Remove(id); }; parameters.Add("ViewModel", cardViewModel); _dialogService.Show(nameof(EmployeeCard), parameters, result => { if (_openEmployeeWindows.ContainsKey(employeeId)) { _openEmployeeWindows.Remove(employeeId); } });
内容的提问来源于stack exchange,提问作者Stormhead
相关产品推荐
相关产品推荐

