Files
Ayay/SHH.CameraDashboard/MainWindowViewModel.cs

524 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
namespace SHH.CameraDashboard
{
/// <summary>
/// 主窗口的视图模型 (ViewModel)。
/// 它封装了主窗口的所有业务逻辑、状态和用户交互命令。
/// 它还作为一个中心协调者,响应全局事件并管理子视图(如右侧编辑面板和蒙板)。
/// </summary>
public class MainWindowViewModel : INotifyPropertyChanged
{
#region --- ---
/// <summary>
/// 初始化 MainWindowViewModel 的新实例。
/// </summary>
public MainWindowViewModel()
{
#region
// 最小化窗口控制命令
MinimizeCommand = new RelayCommand<Window>(win =>
{
if (win != null)
{
win.WindowState = WindowState.Minimized;
}
});
// 最大化、还原命令
ToggleMaximizeCommand = new RelayCommand<Window>(win =>
{
if (win == null) return;
if (win.WindowState == WindowState.Maximized)
{
win.WindowState = WindowState.Normal;
MaxButtonContent = "⬜";
}
else
{
win.WindowState = WindowState.Maximized;
MaxButtonContent = "❐";
}
});
// 关闭应用程序命令
CloseCommand = new RelayCommand<Window>(win =>
{
App.ShutdownApp();
});
// 窗体置顶
ToggleTopMostCommand = new RelayCommand<object>(_ => IsTopMost = !IsTopMost);
#endregion
#region
// 向导功能命令
OpenWizardCommand = new RelayCommand<object>(_ =>
{
var wizardVM = new WizardClientsViewModel();
ShowOverlay(wizardVM);
});
// 更换主题命令
SwitchThemeCommand = new RelayCommand<string>(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<object>(_ => IsLogPanelExpanded = !IsLogPanelExpanded);
// 初始化子 ViewModel
DiagnosticVM = new ServiceNodesViewModel();
MainContent = new VideoWallViewModel();
// 启动异步初始化流程
_ = InitializeAsync();
}
#endregion
#region --- ---
#region HandleAddRequest
/// <summary>
/// 处理添加请求
/// </summary>
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
/// <summary>
/// 处理编辑请求:调用 Repository 获取数据
/// </summary>
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
/// <summary>
/// 处理删除请求
/// </summary>
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 ()
/// <summary>
/// [新增] 处理云台控制请求
/// </summary>
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) ---
/// <summary>
/// 获取最小化窗口的命令。
/// </summary>
public ICommand MinimizeCommand { get; }
/// <summary>
/// 获取切换窗口最大化/还原状态的命令。
/// </summary>
public ICommand ToggleMaximizeCommand { get; }
/// <summary>
/// 获取关闭应用程序的命令。
/// </summary>
public ICommand CloseCommand { get; }
/// <summary>
/// 获取打开客户端配置向导的命令。
/// </summary>
public ICommand OpenWizardCommand { get; }
/// <summary>
/// 获取根据名称切换主题的命令。
/// </summary>
public ICommand SwitchThemeCommand { get; }
/// <summary>
/// 获取切换窗口置顶状态的命令。
/// </summary>
public ICommand ToggleTopMostCommand { get; }
/// <summary>
/// 获取展开或折叠日志面板的命令。
/// </summary>
public ICommand ToggleLogPanelCommand { get; }
#endregion
#region --- (Properties) ---
private string _maxButtonContent = "⬜";
/// <summary>
/// 获取或设置最大化/还原按钮的显示内容。
/// </summary>
public string MaxButtonContent
{
get => _maxButtonContent;
set { _maxButtonContent = value; OnPropertyChanged(); }
}
private bool _isTopMost;
/// <summary>
/// 获取或设置一个值,该值指示窗口是否应始终显示在其他窗口之上。
/// </summary>
public bool IsTopMost
{
get => _isTopMost;
set
{
if (_isTopMost != value)
{
_isTopMost = value;
OnPropertyChanged(nameof(IsTopMost));
OnPropertyChanged(nameof(TopMostTooltip));
}
}
}
/// <summary>
/// 获取用于窗口置顶按钮的动态工具提示文本。
/// </summary>
public string TopMostTooltip => IsTopMost ? "取消置顶" : "窗口置顶";
private bool _isLogPanelExpanded = true;
/// <summary>
/// 获取或设置一个值,该值指示日志面板是否展开。
/// </summary>
public bool IsLogPanelExpanded
{
get => _isLogPanelExpanded;
set
{
if (_isLogPanelExpanded != value)
{
_isLogPanelExpanded = value;
OnPropertyChanged(nameof(IsLogPanelExpanded));
}
}
}
/// <summary>
/// 获取用于服务节点诊断面板的 ViewModel。
/// </summary>
public ServiceNodesViewModel DiagnosticVM { get; }
#endregion
#region --- / ---
private bool _isOverlayVisible;
/// <summary>
/// 获取或设置全局蒙板的可见性。
/// </summary>
public bool IsOverlayVisible
{
get => _isOverlayVisible;
set { _isOverlayVisible = value; OnPropertyChanged(); }
}
private object? _overlayContent;
/// <summary>
/// 获取或设置蒙板内显示的内容(一个子 ViewModel
/// </summary>
public object? OverlayContent
{
get => _overlayContent;
set { _overlayContent = value; OnPropertyChanged(); }
}
/// <summary>
/// 显示一个带有指定内容的全局蒙板。
/// </summary>
/// <param name="contentViewModel">要在蒙板中显示的子 ViewModel。</param>
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 --- ---
/// <summary>
/// 执行应用程序的异步初始化逻辑。
/// </summary>
private async Task InitializeAsync()
{
await Task.Delay(500);
if (CheckIfNeedWizard())
{
var wizardVM = new WizardClientsViewModel();
ShowOverlay(wizardVM);
}
}
/// <summary>
/// 检查是否需要在启动时显示设置向导。
/// </summary>
private bool CheckIfNeedWizard()
{
return true;
}
#endregion
#region --- INotifyPropertyChanged ---
/// <summary>
/// 当属性值更改时发生。
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// 引发 PropertyChanged 事件通知UI更新。
/// </summary>
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(); }
}
}
}