529 lines
17 KiB
C#
529 lines
17 KiB
C#
using System.ComponentModel;
|
||
using System.Diagnostics;
|
||
using System.Windows;
|
||
using System.Windows.Input;
|
||
|
||
namespace SHH.CameraDashboard
|
||
{
|
||
/// <summary>
|
||
/// 主窗口的视图模型 (ViewModel)。
|
||
/// 它封装了主窗口的所有业务逻辑、状态和用户交互命令。
|
||
/// 它还作为一个中心协调者,响应全局事件并管理子视图(如右侧编辑面板和蒙板)。
|
||
/// </summary>
|
||
public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
|
||
{
|
||
#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(); }
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
}
|
||
}
|
||
} |