using System.ComponentModel; using System.Windows; using System.Windows.Input; namespace SHH.CameraDashboard { /// /// 主窗口的视图模型 (ViewModel)。 /// 它封装了主窗口的所有业务逻辑、状态和用户交互命令。 /// 它还作为一个中心协调者,响应全局事件并管理子视图(如右侧编辑面板和蒙板)。 /// public class MainWindowViewModel : INotifyPropertyChanged { #region --- 构造函数 --- /// /// 初始化 MainWindowViewModel 的新实例。 /// public MainWindowViewModel() { #region 窗体控制命令 // 最小化窗口控制命令 MinimizeCommand = new RelayCommand(win => { if (win != null) { win.WindowState = WindowState.Minimized; } }); // 最大化、还原命令 ToggleMaximizeCommand = new RelayCommand(win => { if (win == null) return; if (win.WindowState == WindowState.Maximized) { win.WindowState = WindowState.Normal; MaxButtonContent = "⬜"; } else { win.WindowState = WindowState.Maximized; MaxButtonContent = "❐"; } }); // 关闭应用程序命令 CloseCommand = new RelayCommand(win => { App.ShutdownApp(); }); // 窗体置顶 ToggleTopMostCommand = new RelayCommand(_ => IsTopMost = !IsTopMost); #endregion #region 其它控制命令 // 向导功能命令 OpenWizardCommand = new RelayCommand(_ => { var wizardVM = new WizardClientsViewModel(); ShowOverlay(wizardVM); }); // 更换主题命令 SwitchThemeCommand = new RelayCommand(themeName => { if (!string.IsNullOrEmpty(themeName)) ThemeManager.SetTheme(themeName); }); #endregion #region 订阅事件 // 订阅事件 AppGlobal.OnRequestAddCamera += HandleAddRequest; // 新增 AppGlobal.OnRequestEditCamera += HandleEditRequest; // 编辑 AppGlobal.OnRequestDeleteCamera += HandleDeleteRequest; // 删除 AppGlobal.OnRequestPtzCamera += HandlePtzRequest; // 云台 AppGlobal.OnRequestImgProc += HandleImgProcRequest; //订阅图像处理请求 AppGlobal.OnRequestSubscription += ShowSubscriptionPanel; // 订阅图像处理请求 #endregion // 打开或折叠日志面板 ToggleLogPanelCommand = new RelayCommand(_ => IsLogPanelExpanded = !IsLogPanelExpanded); // 初始化子 ViewModel DiagnosticVM = new ServiceNodesViewModel(); MainContent = new VideoWallViewModel(); // 启动异步初始化流程 _ = InitializeAsync(); } #endregion #region --- 事件处理 --- #region HandleAddRequest /// /// 处理添加请求 /// public void HandleAddRequest() { // 1. 创建一个新的、空白的 DTO,并提供一些合理的默认值以提升用户体验 var newDto = new CameraEditInfo { Id = 0, // 0 表示新增 Name = "新摄像头", IpAddress = "192.168.1.64", Brand = 1, // 默认为海康(1) 或你想要的默认值 Port = 8000, Username = "admin", Password = "abcd1234", ChannelIndex = 1, StreamType = 0, // 主码流 EnhanceImage = false, AllowCompress = true, }; // 2. 创建用于编辑的 ViewModel var editVm = new CameraEditViewModel(newDto); // 3. 监听编辑窗口的关闭请求,以便在操作完成后进行清理和刷新 editVm.RequestClose += (isSaved, data) => { CurrentRightPanelViewModel = null; if (isSaved) { System.Diagnostics.Debug.WriteLine("新增成功,刷新列表..."); AppGlobal.RequestRefresh(); } }; // 4. 在右侧面板中显示编辑界面 CurrentRightPanelViewModel = editVm; } #endregion #region HandleEditRequest /// /// 处理编辑请求:调用 Repository 获取数据 /// private async void HandleEditRequest(WebApiCameraModel listModel) { if (listModel == null) return; try { // 1. 调用 API 客户端获取该摄像头的完整详细信息 var editModel = await ApiClient.Instance.Cameras.GetCameraDetailsAsync(listModel.Id); if (editModel == null) { System.Diagnostics.Debug.WriteLine("获取设备详情失败 (Repository返回null)"); return; } // 2. 创建用于编辑的 ViewModel var editVm = new CameraEditViewModel(editModel); // 3. 监听编辑窗口的关闭请求 editVm.RequestClose += (isSaved, newConfig) => { CurrentRightPanelViewModel = null; if (isSaved && newConfig != null) { // 这里以后可以调用 Update 方法 System.Diagnostics.Debug.WriteLine($"保存成功: {newConfig.Name}"); AppGlobal.RequestRefresh(); } }; // 4. 在右侧面板中显示编辑界面 CurrentRightPanelViewModel = editVm; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"HandleEditRequest 异常: {ex.Message}"); } } #endregion #region HandleDeleteRequest /// /// 处理删除请求 /// private async void HandleDeleteRequest(WebApiCameraModel camera) { if (camera == null) return; // 1. 弹窗确认 (防止误删) var result = MessageBox.Show( $"确定要删除摄像头 \"{camera.Name}\" 吗?\n此操作不可恢复。", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result != MessageBoxResult.Yes) return; bool success = await ApiClient.Instance.Cameras.DeleteCameraAsync(camera.Id); if (success) { // 3. 成功后刷新列表 System.Diagnostics.Debug.WriteLine($"删除 {camera.Name} 成功,刷新列表..."); AppGlobal.RequestRefresh(); } else { MessageBox.Show("删除失败,请检查网络或日志。", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } #endregion #region HandlePtzRequest (新增的处理逻辑) /// /// [新增] 处理云台控制请求 /// private void HandlePtzRequest(WebApiCameraModel camera) { if (camera == null) return; try { // 1. 创建云台控制的 ViewModel var ptzVm = new CameraPtzViewModel(camera); // 2. 监听关闭事件 (以便点击面板上的关闭按钮时,能收起右侧面板) // 注意:我们需要在 CameraPtzViewModel 中定义这个 RequestClose 事件 ptzVm.RequestClose += () => { CurrentRightPanelViewModel = null; }; // 3. 将右侧面板的内容切换为 云台控制 CurrentRightPanelViewModel = ptzVm; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"打开云台失败: {ex.Message}"); } } #endregion private async void HandleImgProcRequest(WebApiCameraModel camera) { if (camera == null) return; try { // 获取详情 (因为列表模型可能不包含 TargetResolution 等深层字段) var detailDto = await ApiClient.Instance.Cameras.GetCameraDetailsAsync(camera.Id); if (detailDto == null) return; // 创建 VM var vm = new CameraImgProcViewModel(camera, detailDto); // 处理关闭回调 vm.RequestClose += () => CurrentRightPanelViewModel = null; CurrentRightPanelViewModel = vm; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"打开图像处理失败: {ex.Message}"); } } // 2. 添加处理方法 (仿照 HandlePtzRequest 写法) private void ShowSubscriptionPanel(WebApiCameraModel camera) { if (camera == null) return; try { // A. 复用模式:只创建 ViewModel var vm = new CameraImageSubscriptionViewModels(camera); // B. 复用关闭逻辑:当子页面请求关闭时,清空当前面板对象 vm.RequestClose += () => CurrentRightPanelViewModel = null; // C. 复用显示通道:直接赋值,UI 会自动更新 CurrentRightPanelViewModel = vm; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"打开订阅面板失败: {ex.Message}"); } } #endregion #region --- 通用右侧面板控制 --- // 这里的类型是 object (或 INotifyPropertyChanged) // 它可以存放 CameraEditViewModel,也可以存放 LogViewModel, SettingsViewModel 等 private object? _currentRightPanelViewModel; public object? CurrentRightPanelViewModel { get => _currentRightPanelViewModel; set { _currentRightPanelViewModel = value; OnPropertyChanged(); // 每次内容变了,通知 Visibility 属性也更新一下 OnPropertyChanged(nameof(IsRightPanelVisible)); } } // 界面绑定的可见性:只要 Content 不为空,就显示面板 public bool IsRightPanelVisible => _currentRightPanelViewModel != null; #endregion #region --- 命令 (Commands) --- /// /// 获取最小化窗口的命令。 /// public ICommand MinimizeCommand { get; } /// /// 获取切换窗口最大化/还原状态的命令。 /// public ICommand ToggleMaximizeCommand { get; } /// /// 获取关闭应用程序的命令。 /// public ICommand CloseCommand { get; } /// /// 获取打开客户端配置向导的命令。 /// public ICommand OpenWizardCommand { get; } /// /// 获取根据名称切换主题的命令。 /// public ICommand SwitchThemeCommand { get; } /// /// 获取切换窗口置顶状态的命令。 /// public ICommand ToggleTopMostCommand { get; } /// /// 获取展开或折叠日志面板的命令。 /// public ICommand ToggleLogPanelCommand { get; } #endregion #region --- 属性 (Properties) --- private string _maxButtonContent = "⬜"; /// /// 获取或设置最大化/还原按钮的显示内容。 /// public string MaxButtonContent { get => _maxButtonContent; set { _maxButtonContent = value; OnPropertyChanged(); } } private bool _isTopMost; /// /// 获取或设置一个值,该值指示窗口是否应始终显示在其他窗口之上。 /// public bool IsTopMost { get => _isTopMost; set { if (_isTopMost != value) { _isTopMost = value; OnPropertyChanged(nameof(IsTopMost)); OnPropertyChanged(nameof(TopMostTooltip)); } } } /// /// 获取用于窗口置顶按钮的动态工具提示文本。 /// public string TopMostTooltip => IsTopMost ? "取消置顶" : "窗口置顶"; private bool _isLogPanelExpanded = true; /// /// 获取或设置一个值,该值指示日志面板是否展开。 /// public bool IsLogPanelExpanded { get => _isLogPanelExpanded; set { if (_isLogPanelExpanded != value) { _isLogPanelExpanded = value; OnPropertyChanged(nameof(IsLogPanelExpanded)); } } } /// /// 获取用于服务节点诊断面板的 ViewModel。 /// public ServiceNodesViewModel DiagnosticVM { get; } #endregion #region --- 蒙板/覆盖层管理 --- private bool _isOverlayVisible; /// /// 获取或设置全局蒙板的可见性。 /// public bool IsOverlayVisible { get => _isOverlayVisible; set { _isOverlayVisible = value; OnPropertyChanged(); } } private object? _overlayContent; /// /// 获取或设置蒙板内显示的内容(一个子 ViewModel)。 /// public object? OverlayContent { get => _overlayContent; set { _overlayContent = value; OnPropertyChanged(); } } /// /// 显示一个带有指定内容的全局蒙板。 /// /// 要在蒙板中显示的子 ViewModel。 public void ShowOverlay(object contentViewModel) { if (contentViewModel == null) return; if (contentViewModel is IOverlayClosable closable) { closable.RequestClose += () => { IsOverlayVisible = false; OverlayContent = null; }; } OverlayContent = contentViewModel; IsOverlayVisible = true; } #endregion #region --- 辅助方法 --- /// /// 执行应用程序的异步初始化逻辑。 /// private async Task InitializeAsync() { await Task.Delay(500); if (CheckIfNeedWizard()) { var wizardVM = new WizardClientsViewModel(); ShowOverlay(wizardVM); } } /// /// 检查是否需要在启动时显示设置向导。 /// private bool CheckIfNeedWizard() { return true; } #endregion #region --- INotifyPropertyChanged 实现 --- /// /// 当属性值更改时发生。 /// public event PropertyChangedEventHandler? PropertyChanged; /// /// 引发 PropertyChanged 事件,通知UI更新。 /// protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } #endregion private object _mainContent; public object MainContent { get => _mainContent; set { _mainContent = value; OnPropertyChanged(); } } } }