| | |
| | | bld/ |
| | | [Bb]in/ |
| | | [Oo]bj/ |
| | | **/packages/ |
| | | |
| | | # Roslyn cache directories |
| | | *.ide/ |
| | |
| | | *.psess |
| | | *.vsp |
| | | *.vspx |
| | | *.vs |
| | | |
| | | # TFS 2012 Local Workspace |
| | | $tf/ |
| New file |
| | |
| | | <Project Sdk="Microsoft.NET.Sdk"> |
| | | |
| | | <PropertyGroup> |
| | | <OutputType>Library</OutputType> |
| | | <TargetFramework>net472</TargetFramework> |
| | | <Nullable>enable</Nullable> |
| | | <LangVersion>10.0</LangVersion> |
| | | <ImplicitUsings>enable</ImplicitUsings> |
| | | <UseWPF>true</UseWPF> |
| | | <AutoGenerateBindingRedirects>True</AutoGenerateBindingRedirects> |
| | | </PropertyGroup> |
| | | |
| | | <ItemGroup> |
| | | <PackageReference Include="log4net" Version="3.0.4" /> |
| | | <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" /> |
| | | </ItemGroup> |
| | | |
| | | <ItemGroup> |
| | | <Reference Include="HslCommunication"> |
| | | <HintPath>..\Libs\HslCommunication.dll</HintPath> |
| | | </Reference> |
| | | </ItemGroup> |
| | | |
| | | <ItemGroup> |
| | | <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" /> |
| | | <ProjectReference Include="..\HMI.Util\HMI.Util.csproj" /> |
| | | <ProjectReference Include="..\ConnectionUtil\ConnectionUtil.csproj" /> |
| | | <ProjectReference Include="..\PLCHelper\PLCHelper.csproj" /> |
| | | <ProjectReference Include="..\S7Driver\S7Driver.csproj" /> |
| | | <ProjectReference Include="..\ModBusTcpController\ModBusTcpController.csproj" /> |
| | | <ProjectReference Include="..\CanSimulator\CanSimulator.csproj" /> |
| | | </ItemGroup> |
| | | |
| | | </Project> |
| New file |
| | |
| | | namespace Aging.Infrastructure.Contracts |
| | | { |
| | | /// <summary> |
| | | /// 设备能力枚举,标识硬件设备支持的功能类型 |
| | | /// </summary> |
| | | public enum DeviceCapability |
| | | { |
| | | /// <summary> |
| | | /// 电源设备 |
| | | /// </summary> |
| | | PowerSupply, |
| | | |
| | | /// <summary> |
| | | /// CAN总线设备 |
| | | /// </summary> |
| | | CAN, |
| | | |
| | | /// <summary> |
| | | /// LIN总线设备 |
| | | /// </summary> |
| | | LIN, |
| | | |
| | | /// <summary> |
| | | /// PLC数字IO设备 |
| | | /// </summary> |
| | | PLC_IO, |
| | | |
| | | /// <summary> |
| | | /// 温箱设备 |
| | | /// </summary> |
| | | Chamber, |
| | | |
| | | /// <summary> |
| | | /// 数据采集设备 |
| | | /// </summary> |
| | | DAQ, |
| | | |
| | | /// <summary> |
| | | /// 串口设备 |
| | | /// </summary> |
| | | Serial, |
| | | |
| | | /// <summary> |
| | | /// 通用设备 |
| | | /// </summary> |
| | | Generic |
| | | } |
| | | } |
| New file |
| | |
| | | namespace Aging.Infrastructure.Contracts |
| | | { |
| | | /// <summary> |
| | | /// 设备驱动状态信息 |
| | | /// </summary> |
| | | public sealed class DeviceDriverStatus |
| | | { |
| | | /// <summary> |
| | | /// 设备是否已连接 |
| | | /// </summary> |
| | | public bool IsConnected { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 设备是否健康(无故障) |
| | | /// </summary> |
| | | public bool IsHealthy { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 最后一次错误信息 |
| | | /// </summary> |
| | | public string LastError { get; set; } = ""; |
| | | |
| | | /// <summary> |
| | | /// 最后一次错误发生的时间 |
| | | /// </summary> |
| | | public DateTime? LastErrorTime { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 通信延迟(毫秒) |
| | | /// </summary> |
| | | public double LatencyMs { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 重连次数 |
| | | /// </summary> |
| | | public int ReconnectCount { get; set; } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 设备连接状态变化事件参数 |
| | | /// </summary> |
| | | public sealed class DeviceConnectionEventArgs : EventArgs |
| | | { |
| | | /// <summary> |
| | | /// 设备标识符 |
| | | /// </summary> |
| | | public string DeviceId { get; set; } = ""; |
| | | |
| | | /// <summary> |
| | | /// 是否已连接 |
| | | /// </summary> |
| | | public bool IsConnected { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 状态变化原因(可选) |
| | | /// </summary> |
| | | public string? Reason { get; set; } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 设备驱动统一接口。 |
| | | /// 所有硬件设备驱动必须实现此接口,提供连接、断开、命令发送和查询功能。 |
| | | /// </summary> |
| | | public interface IDeviceDriver : IDisposable |
| | | { |
| | | /// <summary> |
| | | /// 设备唯一标识符 |
| | | /// </summary> |
| | | string DeviceId { get; } |
| | | |
| | | /// <summary> |
| | | /// 设备当前是否已连接 |
| | | /// </summary> |
| | | bool IsConnected { get; } |
| | | |
| | | /// <summary> |
| | | /// 异步连接设备 |
| | | /// </summary> |
| | | /// <param name="ct">取消令牌</param> |
| | | Task ConnectAsync(CancellationToken ct); |
| | | |
| | | /// <summary> |
| | | /// 异步断开设备连接 |
| | | /// </summary> |
| | | Task DisconnectAsync(); |
| | | |
| | | /// <summary> |
| | | /// 发送命令到设备 |
| | | /// </summary> |
| | | /// <param name="command">命令字符串</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | /// <returns>设备响应字符串</returns> |
| | | Task<string> SendCommandAsync(string command, CancellationToken ct); |
| | | |
| | | /// <summary> |
| | | /// 向设备发送查询并获取响应 |
| | | /// </summary> |
| | | /// <param name="query">查询字符串</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | /// <returns>设备查询响应字符串</returns> |
| | | Task<string> QueryAsync(string query, CancellationToken ct); |
| | | |
| | | /// <summary> |
| | | /// 获取设备当前状态信息 |
| | | /// </summary> |
| | | /// <returns>设备驱动状态</returns> |
| | | DeviceDriverStatus GetStatus(); |
| | | |
| | | /// <summary> |
| | | /// 设备连接状态变化事件 |
| | | /// </summary> |
| | | event EventHandler<DeviceConnectionEventArgs>? ConnectionChanged; |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace Aging.Infrastructure.Contracts |
| | | { |
| | | /// <summary> |
| | | /// 设备管理接口。 |
| | | /// 提供设备驱动的注册、连接管理和状态查询功能。 |
| | | /// </summary> |
| | | public interface IDeviceManager |
| | | { |
| | | /// <summary> |
| | | /// 根据设备ID获取设备驱动实例 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <returns>设备驱动实例,不存在时返回 null</returns> |
| | | IDeviceDriver? GetDriver(string deviceId); |
| | | |
| | | /// <summary> |
| | | /// 获取设备当前状态 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <returns>设备驱动状态</returns> |
| | | DeviceDriverStatus GetStatus(string deviceId); |
| | | |
| | | /// <summary> |
| | | /// 注册设备驱动 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <param name="driver">设备驱动实例</param> |
| | | void RegisterDriver(string deviceId, IDeviceDriver driver); |
| | | |
| | | /// <summary> |
| | | /// 注销设备驱动 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | void UnregisterDriver(string deviceId); |
| | | |
| | | /// <summary> |
| | | /// 异步连接设备 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | Task ConnectAsync(string deviceId, CancellationToken ct); |
| | | |
| | | /// <summary> |
| | | /// 异步断开设备连接 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | Task DisconnectAsync(string deviceId); |
| | | |
| | | /// <summary> |
| | | /// 获取所有已注册设备的ID列表 |
| | | /// </summary> |
| | | /// <returns>设备ID只读列表</returns> |
| | | IReadOnlyList<string> GetRegisteredDevices(); |
| | | |
| | | /// <summary> |
| | | /// 检查设备是否已注册 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <returns>设备是否已注册</returns> |
| | | bool IsDeviceRegistered(string deviceId); |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace Aging.Infrastructure.Contracts |
| | | { |
| | | /// <summary> |
| | | /// 设备命令执行结果 |
| | | /// </summary> |
| | | public sealed class DeviceCommandResult |
| | | { |
| | | /// <summary> |
| | | /// 命令是否执行成功 |
| | | /// </summary> |
| | | public bool Success { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 设备原始响应字符串 |
| | | /// </summary> |
| | | public string? RawResponse { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 回读值(解析后的有效数据) |
| | | /// </summary> |
| | | public string? Readback { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 错误代码 |
| | | /// </summary> |
| | | public string? ErrorCode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 错误信息 |
| | | /// </summary> |
| | | public string? ErrorMessage { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 命令执行延迟(毫秒) |
| | | /// </summary> |
| | | public double LatencyMs { get; set; } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 设备服务接口。 |
| | | /// 提供基于资源预约的命令执行和安全关机功能,协调资源管理和设备驱动。 |
| | | /// </summary> |
| | | public interface IDeviceService |
| | | { |
| | | /// <summary> |
| | | /// 异步执行设备命令。 |
| | | /// 需要有效的资源预约才能执行。 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <param name="command">命令字符串</param> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | /// <returns>命令执行结果</returns> |
| | | Task<DeviceCommandResult> ExecuteCommandAsync( |
| | | string deviceId, string command, ResourceOwnerToken owner, CancellationToken ct); |
| | | |
| | | /// <summary> |
| | | /// 异步执行设备查询。 |
| | | /// 需要有效的资源预约才能执行。 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <param name="query">查询字符串</param> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | /// <returns>命令执行结果</returns> |
| | | Task<DeviceCommandResult> ExecuteQueryAsync( |
| | | string deviceId, string query, ResourceOwnerToken owner, CancellationToken ct); |
| | | |
| | | /// <summary> |
| | | /// 异步安全关闭设备。 |
| | | /// 执行设备特定的关机序列,确保设备处于安全状态。 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | Task SafeShutdownAsync(string deviceId, ResourceOwnerToken owner, CancellationToken ct); |
| | | } |
| | | } |
| New file |
| | |
| | | namespace Aging.Infrastructure.Contracts |
| | | { |
| | | /// <summary> |
| | | /// 短期操作租约接口。 |
| | | /// 在执行具体硬件操作前获取,确保操作期间资源不被抢占。 |
| | | /// 支持同步和异步释放。 |
| | | /// </summary> |
| | | public interface IOperationLease : IDisposable, IAsyncDisposable |
| | | { |
| | | /// <summary> |
| | | /// 租约唯一标识 |
| | | /// </summary> |
| | | string LeaseId { get; } |
| | | |
| | | /// <summary> |
| | | /// 租约关联的资源键 |
| | | /// </summary> |
| | | ResourceKey Resource { get; } |
| | | |
| | | /// <summary> |
| | | /// 租约所有者令牌 |
| | | /// </summary> |
| | | ResourceOwnerToken Owner { get; } |
| | | |
| | | /// <summary> |
| | | /// 租约获取时间 |
| | | /// </summary> |
| | | DateTime AcquiredAt { get; } |
| | | |
| | | /// <summary> |
| | | /// 租约过期时间(可选) |
| | | /// </summary> |
| | | DateTime? ExpiresAt { get; } |
| | | |
| | | /// <summary> |
| | | /// 租约是否仍然有效 |
| | | /// </summary> |
| | | bool IsValid { get; } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace Aging.Infrastructure.Contracts |
| | | { |
| | | /// <summary> |
| | | /// 资源冲突异常。 |
| | | /// 当尝试预约已被其他所有者占用的资源时抛出。 |
| | | /// </summary> |
| | | public sealed class ResourceConflictException : Exception |
| | | { |
| | | /// <summary> |
| | | /// 初始化资源冲突异常实例 |
| | | /// </summary> |
| | | /// <param name="message">冲突描述信息</param> |
| | | public ResourceConflictException(string message) : base(message) |
| | | { |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 初始化资源冲突异常实例 |
| | | /// </summary> |
| | | /// <param name="message">冲突描述信息</param> |
| | | /// <param name="innerException">内部异常</param> |
| | | public ResourceConflictException(string message, Exception innerException) : base(message, innerException) |
| | | { |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 资源管理接口。 |
| | | /// 提供硬件资源的预约、租约获取和释放功能,确保资源不被多个会话同时占用。 |
| | | /// </summary> |
| | | public interface IResourceManager |
| | | { |
| | | /// <summary> |
| | | /// 异步预约资源。 |
| | | /// 创建长期预约,阻止其他所有者使用该资源。 |
| | | /// </summary> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="resource">资源键</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | /// <returns>资源预约实体</returns> |
| | | /// <exception cref="ResourceConflictException">资源已被其他所有者预约时抛出</exception> |
| | | Task<ResourceReservation> ReserveAsync(ResourceOwnerToken owner, ResourceKey resource, CancellationToken ct); |
| | | |
| | | /// <summary> |
| | | /// 异步获取操作租约。 |
| | | /// 在执行具体硬件操作前调用,确保操作期间资源不被抢占。 |
| | | /// </summary> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="resource">资源键</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | /// <returns>操作租约</returns> |
| | | /// <exception cref="InvalidOperationException">资源未预约或所有者不匹配时抛出</exception> |
| | | Task<IOperationLease> AcquireOperationAsync(ResourceOwnerToken owner, ResourceKey resource, CancellationToken ct); |
| | | |
| | | /// <summary> |
| | | /// 异步释放资源预约。 |
| | | /// 取消对资源的占用,允许其他所有者预约。 |
| | | /// </summary> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="resource">资源键</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | Task ReleaseReservationAsync(ResourceOwnerToken owner, ResourceKey resource, CancellationToken ct); |
| | | |
| | | /// <summary> |
| | | /// 检查资源是否已被预约 |
| | | /// </summary> |
| | | /// <param name="resource">资源键</param> |
| | | /// <param name="reservation">输出预约实体(如果已预约)</param> |
| | | /// <returns>资源是否已被预约</returns> |
| | | bool IsReserved(ResourceKey resource, out ResourceReservation? reservation); |
| | | |
| | | /// <summary> |
| | | /// 验证所有者令牌是否匹配指定资源的预约 |
| | | /// </summary> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="resource">资源键</param> |
| | | /// <returns>所有者是否匹配</returns> |
| | | bool ValidateOwner(ResourceOwnerToken owner, ResourceKey resource); |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | |
| | | namespace Aging.Infrastructure.Contracts |
| | | { |
| | | /// <summary> |
| | | /// 资源键值对象,唯一标识一个可管理的硬件资源。 |
| | | /// 格式: "DeviceId:Channel",如 "PSU024:CH1"、"CAN_CARD01:CH2"、"PLC01:OUTPUT_GROUP_A" |
| | | /// </summary> |
| | | public sealed class ResourceKey : IEquatable<ResourceKey> |
| | | { |
| | | /// <summary> |
| | | /// 设备标识符 |
| | | /// </summary> |
| | | public string DeviceId { get; } |
| | | |
| | | /// <summary> |
| | | /// 通道标识(可选) |
| | | /// </summary> |
| | | public string? Channel { get; } |
| | | |
| | | /// <summary> |
| | | /// 资源类型(PowerSupply/CAN/PLC/Chamber等) |
| | | /// </summary> |
| | | public string ResourceType { get; } |
| | | |
| | | /// <summary> |
| | | /// 初始化资源键实例 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <param name="resourceType">资源类型</param> |
| | | /// <param name="channel">通道标识(可选)</param> |
| | | public ResourceKey(string deviceId, string resourceType, string? channel = null) |
| | | { |
| | | DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId)); |
| | | ResourceType = resourceType ?? throw new ArgumentNullException(nameof(resourceType)); |
| | | Channel = channel; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 从字符串解析资源键,格式为 "DeviceId:Channel" |
| | | /// </summary> |
| | | /// <param name="key">资源键字符串</param> |
| | | /// <returns>解析后的资源键实例</returns> |
| | | /// <exception cref="ArgumentException">格式无效时抛出</exception> |
| | | public static ResourceKey FromString(string key) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(key)) |
| | | throw new ArgumentException("资源键不能为空", nameof(key)); |
| | | |
| | | var parts = key.Split(':'); |
| | | if (parts.Length < 2) |
| | | throw new ArgumentException($"无效的资源键格式: '{key}',期望格式为 'DeviceId:Channel'"); |
| | | |
| | | var deviceId = parts[0].Trim(); |
| | | var channel = string.Join(":", parts.Skip(1)).Trim(); |
| | | |
| | | if (string.IsNullOrEmpty(deviceId)) |
| | | throw new ArgumentException($"无效的资源键格式: '{key}',DeviceId 不能为空"); |
| | | |
| | | // 根据设备ID前缀推断资源类型 |
| | | var resourceType = InferResourceType(deviceId); |
| | | |
| | | return new ResourceKey(deviceId, resourceType, string.IsNullOrEmpty(channel) ? null : channel); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取操作资源键,用于物理会话锁 |
| | | /// </summary> |
| | | public string OperationResourceKey => $"{DeviceId}:SESSION"; |
| | | |
| | | /// <summary> |
| | | /// 根据设备ID前缀推断资源类型 |
| | | /// </summary> |
| | | private static string InferResourceType(string deviceId) |
| | | { |
| | | var upper = deviceId.ToUpperInvariant(); |
| | | if (upper.StartsWith("PSU") || upper.StartsWith("POWER")) |
| | | return "PowerSupply"; |
| | | if (upper.StartsWith("CAN")) |
| | | return "CAN"; |
| | | if (upper.StartsWith("PLC")) |
| | | return "PLC"; |
| | | if (upper.StartsWith("CHAMBER") || upper.StartsWith("TEMP")) |
| | | return "Chamber"; |
| | | if (upper.StartsWith("DAQ")) |
| | | return "DAQ"; |
| | | if (upper.StartsWith("SERIAL") || upper.StartsWith("COM")) |
| | | return "Serial"; |
| | | return "Generic"; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool Equals(ResourceKey? other) |
| | | { |
| | | if (other is null) return false; |
| | | if (ReferenceEquals(this, other)) return true; |
| | | return string.Equals(DeviceId, other.DeviceId, StringComparison.OrdinalIgnoreCase) && |
| | | string.Equals(Channel, other.Channel, StringComparison.OrdinalIgnoreCase) && |
| | | string.Equals(ResourceType, other.ResourceType, StringComparison.OrdinalIgnoreCase); |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public override bool Equals(object? obj) => Equals(obj as ResourceKey); |
| | | |
| | | /// <inheritdoc/> |
| | | public override int GetHashCode() |
| | | { |
| | | unchecked |
| | | { |
| | | int hash = 17; |
| | | hash = hash * 31 + (DeviceId?.ToUpperInvariant().GetHashCode() ?? 0); |
| | | hash = hash * 31 + (Channel?.ToUpperInvariant().GetHashCode() ?? 0); |
| | | hash = hash * 31 + (ResourceType?.ToUpperInvariant().GetHashCode() ?? 0); |
| | | return hash; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public override string ToString() => |
| | | string.IsNullOrEmpty(Channel) ? DeviceId : $"{DeviceId}:{Channel}"; |
| | | |
| | | /// <summary> |
| | | /// 相等运算符重载 |
| | | /// </summary> |
| | | public static bool operator ==(ResourceKey? left, ResourceKey? right) => |
| | | Equals(left, right); |
| | | |
| | | /// <summary> |
| | | /// 不等运算符重载 |
| | | /// </summary> |
| | | public static bool operator !=(ResourceKey? left, ResourceKey? right) => |
| | | !Equals(left, right); |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | |
| | | namespace Aging.Infrastructure.Contracts |
| | | { |
| | | /// <summary> |
| | | /// 所有者类型枚举 |
| | | /// </summary> |
| | | public enum OwnerType |
| | | { |
| | | /// <summary> |
| | | /// 生产模式(正常老化测试流程) |
| | | /// </summary> |
| | | Production, |
| | | |
| | | /// <summary> |
| | | /// 维护模式(调试/维护会话) |
| | | /// </summary> |
| | | Maintenance |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 所有者令牌,标识资源预约的归属。 |
| | | /// 用于区分不同会话对硬件资源的所有权。 |
| | | /// </summary> |
| | | public sealed class ResourceOwnerToken |
| | | { |
| | | /// <summary> |
| | | /// 令牌唯一标识 |
| | | /// </summary> |
| | | public string TokenId { get; } |
| | | |
| | | /// <summary> |
| | | /// 所有者类型(生产/维护) |
| | | /// </summary> |
| | | public OwnerType OwnerType { get; } |
| | | |
| | | /// <summary> |
| | | /// 所有者标识(RunID 或 DebugSessionID) |
| | | /// </summary> |
| | | public string OwnerId { get; } |
| | | |
| | | /// <summary> |
| | | /// 世代号,每次新预约递增,防止重放攻击 |
| | | /// </summary> |
| | | public long Generation { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 令牌签发时间 |
| | | /// </summary> |
| | | public DateTime IssuedAt { get; } |
| | | |
| | | /// <summary> |
| | | /// 令牌过期时间(可选) |
| | | /// </summary> |
| | | public DateTime? ExpiresAt { get; } |
| | | |
| | | /// <summary> |
| | | /// 初始化所有者令牌实例 |
| | | /// </summary> |
| | | /// <param name="ownerType">所有者类型</param> |
| | | /// <param name="ownerId">所有者标识</param> |
| | | /// <param name="expiresAt">过期时间(可选)</param> |
| | | public ResourceOwnerToken(OwnerType ownerType, string ownerId, DateTime? expiresAt = null) |
| | | { |
| | | TokenId = Guid.NewGuid().ToString(); |
| | | OwnerType = ownerType; |
| | | OwnerId = ownerId ?? throw new ArgumentNullException(nameof(ownerId)); |
| | | Generation = 0; |
| | | IssuedAt = DateTime.UtcNow; |
| | | ExpiresAt = expiresAt; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查令牌是否已过期 |
| | | /// </summary> |
| | | /// <returns>过期返回 true;未设置过期时间或尚未过期返回 false</returns> |
| | | public bool IsExpired() |
| | | { |
| | | if (!ExpiresAt.HasValue) |
| | | return false; |
| | | return DateTime.UtcNow > ExpiresAt.Value; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | |
| | | namespace Aging.Infrastructure.Contracts |
| | | { |
| | | /// <summary> |
| | | /// 预约状态枚举 |
| | | /// </summary> |
| | | public enum ReservationState |
| | | { |
| | | /// <summary> |
| | | /// 活跃状态 |
| | | /// </summary> |
| | | Active, |
| | | |
| | | /// <summary> |
| | | /// 已释放 |
| | | /// </summary> |
| | | Released, |
| | | |
| | | /// <summary> |
| | | /// 冲突状态 |
| | | /// </summary> |
| | | Conflicted |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 长期预约实体,表示对某个硬件资源的持久占用。 |
| | | /// 预约期间,其他所有者无法获取该资源。 |
| | | /// </summary> |
| | | public sealed class ResourceReservation |
| | | { |
| | | /// <summary> |
| | | /// 预约唯一标识 |
| | | /// </summary> |
| | | public string ReservationId { get; } |
| | | |
| | | /// <summary> |
| | | /// 被预约的资源键 |
| | | /// </summary> |
| | | public ResourceKey Resource { get; } |
| | | |
| | | /// <summary> |
| | | /// 预约所有者令牌 |
| | | /// </summary> |
| | | public ResourceOwnerToken Owner { get; } |
| | | |
| | | /// <summary> |
| | | /// 预约状态 |
| | | /// </summary> |
| | | public ReservationState State { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 预约创建时间 |
| | | /// </summary> |
| | | public DateTime ReservedAt { get; } |
| | | |
| | | /// <summary> |
| | | /// 预约描述(可选) |
| | | /// </summary> |
| | | public string? Description { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 初始化资源预约实例 |
| | | /// </summary> |
| | | /// <param name="resource">资源键</param> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="description">预约描述(可选)</param> |
| | | public ResourceReservation(ResourceKey resource, ResourceOwnerToken owner, string? description = null) |
| | | { |
| | | ReservationId = Guid.NewGuid().ToString(); |
| | | Resource = resource ?? throw new ArgumentNullException(nameof(resource)); |
| | | Owner = owner ?? throw new ArgumentNullException(nameof(owner)); |
| | | State = ReservationState.Active; |
| | | ReservedAt = DateTime.UtcNow; |
| | | Description = description; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Collections.Concurrent; |
| | | using Aging.Infrastructure.Contracts; |
| | | using log4net; |
| | | |
| | | namespace Aging.Infrastructure.Core |
| | | { |
| | | /// <summary> |
| | | /// 设备管理实现。 |
| | | /// 提供设备驱动的注册、连接管理和状态查询功能。 |
| | | /// 使用 ConcurrentDictionary 管理驱动实例和缓存状态。 |
| | | /// </summary> |
| | | public sealed class DeviceManager : IDeviceManager |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(DeviceManager)); |
| | | |
| | | /// <summary> |
| | | /// 设备驱动字典(key = deviceId) |
| | | /// </summary> |
| | | private readonly ConcurrentDictionary<string, IDeviceDriver> _drivers = new(); |
| | | |
| | | /// <summary> |
| | | /// 设备状态缓存字典(key = deviceId) |
| | | /// </summary> |
| | | private readonly ConcurrentDictionary<string, DeviceDriverStatus> _statusCache = new(); |
| | | |
| | | /// <inheritdoc/> |
| | | public void RegisterDriver(string deviceId, IDeviceDriver driver) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentException("设备ID不能为空", nameof(deviceId)); |
| | | if (driver == null) throw new ArgumentNullException(nameof(driver)); |
| | | |
| | | if (_drivers.ContainsKey(deviceId)) |
| | | { |
| | | Log.Warn($"[DeviceManager] 设备 {deviceId} 已注册,将覆盖现有驱动"); |
| | | UnregisterDriver(deviceId); |
| | | } |
| | | |
| | | _drivers[deviceId] = driver; |
| | | |
| | | // 初始化状态缓存 |
| | | var initialStatus = driver.GetStatus(); |
| | | _statusCache[deviceId] = initialStatus; |
| | | |
| | | // 订阅连接状态变化事件 |
| | | driver.ConnectionChanged += OnConnectionChanged; |
| | | |
| | | Log.Info($"[DeviceManager] 设备 {deviceId} 驱动注册成功,当前连接状态: {initialStatus.IsConnected}"); |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public void UnregisterDriver(string deviceId) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentException("设备ID不能为空", nameof(deviceId)); |
| | | |
| | | if (_drivers.TryRemove(deviceId, out var driver)) |
| | | { |
| | | // 取消事件订阅 |
| | | driver.ConnectionChanged -= OnConnectionChanged; |
| | | |
| | | // 清理状态缓存 |
| | | _statusCache.TryRemove(deviceId, out _); |
| | | |
| | | Log.Info($"[DeviceManager] 设备 {deviceId} 驱动已注销"); |
| | | } |
| | | else |
| | | { |
| | | Log.Warn($"[DeviceManager] 尝试注销不存在的设备: {deviceId}"); |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public IDeviceDriver? GetDriver(string deviceId) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentException("设备ID不能为空", nameof(deviceId)); |
| | | |
| | | _drivers.TryGetValue(deviceId, out var driver); |
| | | return driver; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public DeviceDriverStatus GetStatus(string deviceId) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentException("设备ID不能为空", nameof(deviceId)); |
| | | |
| | | if (_statusCache.TryGetValue(deviceId, out var status)) |
| | | { |
| | | return status; |
| | | } |
| | | |
| | | // 如果缓存中没有,返回默认状态 |
| | | Log.Warn($"[DeviceManager] 设备 {deviceId} 状态缓存不存在,返回默认状态"); |
| | | return new DeviceDriverStatus |
| | | { |
| | | IsConnected = false, |
| | | IsHealthy = false, |
| | | LastError = "设备未注册或状态未初始化" |
| | | }; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task ConnectAsync(string deviceId, CancellationToken ct) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentException("设备ID不能为空", nameof(deviceId)); |
| | | |
| | | if (!_drivers.TryGetValue(deviceId, out var driver)) |
| | | { |
| | | throw new InvalidOperationException($"设备 {deviceId} 未注册驱动"); |
| | | } |
| | | |
| | | try |
| | | { |
| | | Log.Info($"[DeviceManager] 正在连接设备: {deviceId}"); |
| | | await driver.ConnectAsync(ct).ConfigureAwait(false); |
| | | |
| | | // 更新状态缓存 |
| | | _statusCache[deviceId] = driver.GetStatus(); |
| | | |
| | | Log.Info($"[DeviceManager] 设备 {deviceId} 连接成功"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | // 更新错误状态 |
| | | if (_statusCache.TryGetValue(deviceId, out var status)) |
| | | { |
| | | status.IsConnected = false; |
| | | status.IsHealthy = false; |
| | | status.LastError = ex.Message; |
| | | status.LastErrorTime = DateTime.UtcNow; |
| | | _statusCache[deviceId] = status; |
| | | } |
| | | |
| | | Log.Error($"[DeviceManager] 设备 {deviceId} 连接失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task DisconnectAsync(string deviceId) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentException("设备ID不能为空", nameof(deviceId)); |
| | | |
| | | if (!_drivers.TryGetValue(deviceId, out var driver)) |
| | | { |
| | | Log.Warn($"[DeviceManager] 尝试断开未注册的设备: {deviceId}"); |
| | | return; |
| | | } |
| | | |
| | | try |
| | | { |
| | | Log.Info($"[DeviceManager] 正在断开设备: {deviceId}"); |
| | | await driver.DisconnectAsync().ConfigureAwait(false); |
| | | |
| | | // 更新状态缓存 |
| | | _statusCache[deviceId] = driver.GetStatus(); |
| | | |
| | | Log.Info($"[DeviceManager] 设备 {deviceId} 已断开连接"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[DeviceManager] 设备 {deviceId} 断开连接失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public IReadOnlyList<string> GetRegisteredDevices() |
| | | { |
| | | return _drivers.Keys.ToList().AsReadOnly(); |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool IsDeviceRegistered(string deviceId) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | return false; |
| | | |
| | | return _drivers.ContainsKey(deviceId); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理设备连接状态变化事件 |
| | | /// </summary> |
| | | /// <param name="sender">事件发送者</param> |
| | | /// <param name="e">事件参数</param> |
| | | private void OnConnectionChanged(object? sender, DeviceConnectionEventArgs e) |
| | | { |
| | | var deviceId = e.DeviceId; |
| | | |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | { |
| | | Log.Warn("[DeviceManager] 收到连接变化事件但 DeviceId 为空"); |
| | | return; |
| | | } |
| | | |
| | | if (_statusCache.TryGetValue(deviceId, out var status)) |
| | | { |
| | | status.IsConnected = e.IsConnected; |
| | | if (!e.IsConnected) |
| | | { |
| | | status.IsHealthy = false; |
| | | if (!string.IsNullOrEmpty(e.Reason)) |
| | | { |
| | | status.LastError = e.Reason; |
| | | status.LastErrorTime = DateTime.UtcNow; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | status.IsHealthy = true; |
| | | status.LastError = ""; |
| | | } |
| | | |
| | | _statusCache[deviceId] = status; |
| | | |
| | | Log.Info($"[DeviceManager] 设备 {deviceId} 连接状态变化: {(e.IsConnected ? "已连接" : "已断开")}" + |
| | | (string.IsNullOrEmpty(e.Reason) ? "" : $", 原因: {e.Reason}")); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using Aging.Infrastructure.Contracts; |
| | | |
| | | namespace Aging.Infrastructure.Core |
| | | { |
| | | /// <summary> |
| | | /// 操作租约实现。 |
| | | /// 提供短期资源占用保证,支持同步和异步释放。 |
| | | /// 释放时通过回调通知 ResourceManager。 |
| | | /// </summary> |
| | | public sealed class OperationLease : IOperationLease |
| | | { |
| | | private readonly Action? _onRelease; |
| | | private bool _disposed; |
| | | |
| | | /// <inheritdoc/> |
| | | public string LeaseId { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public ResourceKey Resource { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public ResourceOwnerToken Owner { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public DateTime AcquiredAt { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public DateTime? ExpiresAt { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool IsValid => !_disposed && !IsExpired(); |
| | | |
| | | /// <summary> |
| | | /// 初始化操作租约实例 |
| | | /// </summary> |
| | | /// <param name="resource">资源键</param> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="onRelease">释放时的回调通知</param> |
| | | /// <param name="leaseDurationSeconds">租约有效期(秒),默认30秒</param> |
| | | public OperationLease( |
| | | ResourceKey resource, |
| | | ResourceOwnerToken owner, |
| | | Action? onRelease = null, |
| | | int leaseDurationSeconds = 30) |
| | | { |
| | | LeaseId = Guid.NewGuid().ToString(); |
| | | Resource = resource ?? throw new ArgumentNullException(nameof(resource)); |
| | | Owner = owner ?? throw new ArgumentNullException(nameof(owner)); |
| | | AcquiredAt = DateTime.UtcNow; |
| | | ExpiresAt = leaseDurationSeconds > 0 |
| | | ? AcquiredAt.AddSeconds(leaseDurationSeconds) |
| | | : null; |
| | | _onRelease = onRelease; |
| | | _disposed = false; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查租约是否已过期 |
| | | /// </summary> |
| | | private bool IsExpired() |
| | | { |
| | | if (!ExpiresAt.HasValue) |
| | | return false; |
| | | return DateTime.UtcNow > ExpiresAt.Value; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public void Dispose() |
| | | { |
| | | if (_disposed) return; |
| | | _disposed = true; |
| | | _onRelease?.Invoke(); |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public ValueTask DisposeAsync() |
| | | { |
| | | Dispose(); |
| | | return default; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Collections.Concurrent; |
| | | using Aging.Infrastructure.Contracts; |
| | | using log4net; |
| | | |
| | | namespace Aging.Infrastructure.Core |
| | | { |
| | | /// <summary> |
| | | /// 资源管理实现。 |
| | | /// 提供线程安全的资源预约、租约获取和释放功能。 |
| | | /// 使用 ConcurrentDictionary 存储活跃预约,使用 SemaphoreSlim 实现 per-resource 锁。 |
| | | /// </summary> |
| | | public sealed class ResourceManager : IResourceManager |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(ResourceManager)); |
| | | |
| | | /// <summary> |
| | | /// 活跃预约字典(key = ResourceKey.ToString()) |
| | | /// </summary> |
| | | private readonly ConcurrentDictionary<string, ResourceReservation> _reservations = new(); |
| | | |
| | | /// <summary> |
| | | /// 每资源信号量字典,用于实现细粒度锁 |
| | | /// </summary> |
| | | private readonly ConcurrentDictionary<string, SemaphoreSlim> _resourceLocks = new(); |
| | | |
| | | /// <summary> |
| | | /// 全局世代号,每次新预约递增 |
| | | /// </summary> |
| | | private long _generation; |
| | | |
| | | /// <summary> |
| | | /// 默认租约有效期(秒) |
| | | /// </summary> |
| | | private const int DefaultLeaseDurationSeconds = 30; |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task<ResourceReservation> ReserveAsync( |
| | | ResourceOwnerToken owner, ResourceKey resource, CancellationToken ct) |
| | | { |
| | | if (owner == null) throw new ArgumentNullException(nameof(owner)); |
| | | if (resource == null) throw new ArgumentNullException(nameof(resource)); |
| | | |
| | | var resourceKey = resource.ToString(); |
| | | var semaphore = _resourceLocks.GetOrAdd(resourceKey, _ => new SemaphoreSlim(1, 1)); |
| | | |
| | | try |
| | | { |
| | | await semaphore.WaitAsync(ct).ConfigureAwait(false); |
| | | |
| | | try |
| | | { |
| | | // 检查是否已有 Active 预约 |
| | | if (_reservations.TryGetValue(resourceKey, out var existingReservation)) |
| | | { |
| | | if (existingReservation.State == ReservationState.Active) |
| | | { |
| | | // 检查是否为同一 Owner(续期场景) |
| | | if (existingReservation.Owner.TokenId == owner.TokenId) |
| | | { |
| | | // 同一所有者续期:更新世代号 |
| | | var renewedGeneration = Interlocked.Increment(ref _generation); |
| | | owner.Generation = renewedGeneration; |
| | | existingReservation.Owner.Generation = renewedGeneration; |
| | | |
| | | Log.Info($"[ResourceManager] 资源 {resourceKey} 续期成功,新世代号: {renewedGeneration}"); |
| | | return existingReservation; |
| | | } |
| | | |
| | | // 不同所有者冲突 |
| | | var conflictMsg = $"资源 {resourceKey} 已被所有者 {existingReservation.Owner.OwnerId} 预约," + |
| | | $"当前请求者: {owner.OwnerId}"; |
| | | Log.Warn($"[ResourceManager] 资源冲突: {conflictMsg}"); |
| | | throw new ResourceConflictException(conflictMsg); |
| | | } |
| | | } |
| | | |
| | | // 创建新预约 |
| | | var newGeneration = Interlocked.Increment(ref _generation); |
| | | owner.Generation = newGeneration; |
| | | |
| | | var reservation = new ResourceReservation(resource, owner) |
| | | { |
| | | State = ReservationState.Active |
| | | }; |
| | | |
| | | _reservations[resourceKey] = reservation; |
| | | |
| | | Log.Info($"[ResourceManager] 资源 {resourceKey} 预约成功,所有者: {owner.OwnerId},世代号: {newGeneration}"); |
| | | return reservation; |
| | | } |
| | | finally |
| | | { |
| | | semaphore.Release(); |
| | | } |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | Log.Warn($"[ResourceManager] 资源 {resourceKey} 预约被取消"); |
| | | throw; |
| | | } |
| | | catch (ResourceConflictException) |
| | | { |
| | | throw; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[ResourceManager] 资源 {resourceKey} 预约失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task<IOperationLease> AcquireOperationAsync( |
| | | ResourceOwnerToken owner, ResourceKey resource, CancellationToken ct) |
| | | { |
| | | if (owner == null) throw new ArgumentNullException(nameof(owner)); |
| | | if (resource == null) throw new ArgumentNullException(nameof(resource)); |
| | | |
| | | var resourceKey = resource.ToString(); |
| | | |
| | | try |
| | | { |
| | | // 检查资源是否有有效预约 |
| | | if (!_reservations.TryGetValue(resourceKey, out var reservation)) |
| | | { |
| | | throw new InvalidOperationException($"资源 {resourceKey} 未被预约,无法获取操作租约"); |
| | | } |
| | | |
| | | if (reservation.State != ReservationState.Active) |
| | | { |
| | | throw new InvalidOperationException($"资源 {resourceKey} 预约状态为 {reservation.State},无法获取操作租约"); |
| | | } |
| | | |
| | | // 验证 Owner 匹配(TokenId + Generation) |
| | | if (reservation.Owner.TokenId != owner.TokenId || reservation.Owner.Generation != owner.Generation) |
| | | { |
| | | throw new InvalidOperationException( |
| | | $"资源 {resourceKey} 所有者不匹配,期望 TokenId: {reservation.Owner.TokenId},Generation: {reservation.Owner.Generation}"); |
| | | } |
| | | |
| | | // 创建 OperationLease(短期,默认30秒过期) |
| | | var lease = new OperationLease( |
| | | resource, |
| | | owner, |
| | | onRelease: () => Log.Debug($"[ResourceManager] 租约释放: {resourceKey}"), |
| | | leaseDurationSeconds: DefaultLeaseDurationSeconds); |
| | | |
| | | Log.Info($"[ResourceManager] 获取操作租约: {resourceKey},所有者: {owner.OwnerId},有效期: {DefaultLeaseDurationSeconds}秒"); |
| | | |
| | | await Task.CompletedTask; // 保持异步签名 |
| | | return lease; |
| | | } |
| | | catch (InvalidOperationException) |
| | | { |
| | | throw; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[ResourceManager] 获取操作租约失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task ReleaseReservationAsync( |
| | | ResourceOwnerToken owner, ResourceKey resource, CancellationToken ct) |
| | | { |
| | | if (owner == null) throw new ArgumentNullException(nameof(owner)); |
| | | if (resource == null) throw new ArgumentNullException(nameof(resource)); |
| | | |
| | | var resourceKey = resource.ToString(); |
| | | var semaphore = _resourceLocks.GetOrAdd(resourceKey, _ => new SemaphoreSlim(1, 1)); |
| | | |
| | | try |
| | | { |
| | | await semaphore.WaitAsync(ct).ConfigureAwait(false); |
| | | |
| | | try |
| | | { |
| | | if (!_reservations.TryGetValue(resourceKey, out var reservation)) |
| | | { |
| | | Log.Warn($"[ResourceManager] 尝试释放不存在的资源预约: {resourceKey}"); |
| | | return; |
| | | } |
| | | |
| | | // 验证 Owner 匹配 |
| | | if (reservation.Owner.TokenId != owner.TokenId) |
| | | { |
| | | throw new InvalidOperationException( |
| | | $"资源 {resourceKey} 释放失败:所有者不匹配,期望 TokenId: {reservation.Owner.TokenId}"); |
| | | } |
| | | |
| | | // 标记预约为 Released |
| | | reservation.State = ReservationState.Released; |
| | | _reservations.TryRemove(resourceKey, out _); |
| | | |
| | | Log.Info($"[ResourceManager] 资源 {resourceKey} 预约已释放,所有者: {owner.OwnerId}"); |
| | | } |
| | | finally |
| | | { |
| | | semaphore.Release(); |
| | | } |
| | | |
| | | await Task.CompletedTask; // 保持异步签名 |
| | | } |
| | | catch (InvalidOperationException) |
| | | { |
| | | throw; |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | Log.Warn($"[ResourceManager] 资源 {resourceKey} 释放操作被取消"); |
| | | throw; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[ResourceManager] 释放资源 {resourceKey} 失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool IsReserved(ResourceKey resource, out ResourceReservation? reservation) |
| | | { |
| | | if (resource == null) throw new ArgumentNullException(nameof(resource)); |
| | | |
| | | var resourceKey = resource.ToString(); |
| | | var result = _reservations.TryGetValue(resourceKey, out var res); |
| | | |
| | | if (result && res != null && res.State == ReservationState.Active) |
| | | { |
| | | reservation = res; |
| | | return true; |
| | | } |
| | | |
| | | reservation = null; |
| | | return false; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool ValidateOwner(ResourceOwnerToken owner, ResourceKey resource) |
| | | { |
| | | if (owner == null) throw new ArgumentNullException(nameof(owner)); |
| | | if (resource == null) throw new ArgumentNullException(nameof(resource)); |
| | | |
| | | var resourceKey = resource.ToString(); |
| | | |
| | | if (!_reservations.TryGetValue(resourceKey, out var reservation)) |
| | | return false; |
| | | |
| | | if (reservation.State != ReservationState.Active) |
| | | return false; |
| | | |
| | | return reservation.Owner.TokenId == owner.TokenId && |
| | | reservation.Owner.Generation == owner.Generation; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Diagnostics; |
| | | using System.Linq; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | using Aging.Infrastructure.Contracts; |
| | | using CanSimulator; |
| | | using log4net; |
| | | |
| | | namespace Aging.Infrastructure.Drivers |
| | | { |
| | | /// <summary> |
| | | /// CAN 通讯驱动适配器。 |
| | | /// 封装 CanSimulator,实现统一的 IDeviceDriver 接口。 |
| | | /// 命令格式约定: |
| | | /// 发送: "SEND:frameId:data" 如 "SEND:0x100:01 02 03 04" |
| | | /// 查询: |
| | | /// "RECV" - 获取接收邮箱中的下一帧 |
| | | /// "RECV_ALL" - 获取接收邮箱中的所有帧 |
| | | /// "STATUS" - 获取节点/总线状态 |
| | | /// "FILTER:id:mask" - 设置接收滤波器 |
| | | /// "FILTER_CLEAR" - 清除所有滤波器 |
| | | /// "PROMISCUOUS" - 设置混杂模式(接收所有帧) |
| | | /// "LOG" - 获取总线日志 |
| | | /// "BUS_STATUS" - 获取总线状态摘要 |
| | | /// </summary> |
| | | public sealed class CanDriverAdapter : IDeviceDriver |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(CanDriverAdapter)); |
| | | |
| | | /// <summary> |
| | | /// 全局 CAN 总线模拟器实例(所有 CAN 驱动共享) |
| | | /// </summary> |
| | | private static readonly Dictionary<string, CanBusSimulator> _sharedBuses = new(); |
| | | private static readonly object _busLock = new(); |
| | | |
| | | private readonly string _ip; // 总线名称(用 ip 字段映射) |
| | | private readonly int _port; // 节点编号(用 port 字段映射) |
| | | private CanBusSimulator? _bus; |
| | | private CanNode? _node; |
| | | private bool _disposed; |
| | | private readonly Stopwatch _latencyStopwatch = new(); |
| | | private double _lastLatencyMs; |
| | | private string _lastError = string.Empty; |
| | | private DateTime? _lastErrorTime; |
| | | private int _reconnectCount; |
| | | |
| | | /// <summary> |
| | | /// 接收到的帧缓存(用于 RECV 查询) |
| | | /// </summary> |
| | | private readonly Queue<CanFrame> _recvBuffer = new(); |
| | | |
| | | /// <inheritdoc/> |
| | | public string DeviceId { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool IsConnected => _node != null && _node.IsActive; |
| | | |
| | | /// <inheritdoc/> |
| | | public event EventHandler<DeviceConnectionEventArgs>? ConnectionChanged; |
| | | |
| | | /// <summary> |
| | | /// 初始化 CAN 驱动适配器 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备唯一标识符</param> |
| | | /// <param name="busName">总线名称(如 CAN_BUS_1)</param> |
| | | /// <param name="nodeIndex">节点编号(用于区分同一总线上的不同设备)</param> |
| | | public CanDriverAdapter(string deviceId, string busName, int nodeIndex) |
| | | { |
| | | DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId)); |
| | | _ip = busName ?? "CAN_BUS_1"; |
| | | _port = nodeIndex; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取或创建共享总线 |
| | | /// </summary> |
| | | private CanBusSimulator GetOrCreateBus(string busName) |
| | | { |
| | | lock (_busLock) |
| | | { |
| | | if (!_sharedBuses.TryGetValue(busName, out var bus)) |
| | | { |
| | | bus = new CanBusSimulator(busName); |
| | | _sharedBuses[busName] = bus; |
| | | Log.Info($"[CAN] 创建新的虚拟总线: {busName}"); |
| | | } |
| | | return bus; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task ConnectAsync(CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[CAN] 设备 {DeviceId} 正在连接到总线 {_ip} (节点: {_port})..."); |
| | | |
| | | try |
| | | { |
| | | // 获取或创建共享总线 |
| | | _bus = GetOrCreateBus(_ip); |
| | | |
| | | // 如果总线未启动,自动启动 |
| | | if (_bus.State != CanBusState.Running) |
| | | { |
| | | _bus.Start(); |
| | | } |
| | | |
| | | // 注册节点 |
| | | string nodeId = $"{DeviceId}_node{_port}"; |
| | | _node = _bus.RegisterNode(nodeId, DeviceId); |
| | | |
| | | // 设置默认滤波器(接收所有帧) |
| | | _node.SetPromiscuousMode(); |
| | | |
| | | // 激活节点 |
| | | _node.Activate(); |
| | | |
| | | // 订阅接收事件 |
| | | _node.FrameReceived += OnFrameReceived; |
| | | |
| | | _lastError = string.Empty; |
| | | Log.Info($"[CAN] 设备 {DeviceId} 已成功连接到总线 {_ip} (节点: {_node.NodeId})"); |
| | | OnConnectionChanged(true, "连接成功"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[CAN] 设备 {DeviceId} 连接异常: {ex.Message}", ex); |
| | | _node?.Deactivate(); |
| | | _node = null; |
| | | throw; |
| | | } |
| | | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task DisconnectAsync() |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[CAN] 设备 {DeviceId} 正在断开连接..."); |
| | | |
| | | try |
| | | { |
| | | if (_node != null) |
| | | { |
| | | _node.FrameReceived -= OnFrameReceived; |
| | | _node.Deactivate(); |
| | | _bus?.UnregisterNode(_node.NodeId); |
| | | _node = null; |
| | | } |
| | | |
| | | Log.Info($"[CAN] 设备 {DeviceId} 已断开连接"); |
| | | OnConnectionChanged(false, "主动断开"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[CAN] 设备 {DeviceId} 断开连接异常: {ex.Message}", ex); |
| | | _node = null; |
| | | } |
| | | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task<string> SendCommandAsync(string command, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[CAN] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[CAN] 设备 {DeviceId} 发送命令: {command}"); |
| | | |
| | | // 解析命令格式: "SEND:frameId:data" |
| | | string[] parts = command.Split(':'); |
| | | if (parts.Length < 3) |
| | | { |
| | | throw new ArgumentException($"[CAN] 命令格式错误,应为 SEND:frameId:data,实际: {command}"); |
| | | } |
| | | |
| | | string operation = parts[0].ToUpper(); |
| | | if (operation != "SEND") |
| | | { |
| | | throw new ArgumentException($"[CAN] 不支持的操作类型: {operation},应为 SEND"); |
| | | } |
| | | |
| | | // 解析帧 ID |
| | | string frameIdStr = parts[1]; |
| | | uint frameId = CanFrame.ParseFrameId(frameIdStr); |
| | | |
| | | // 解析数据 |
| | | string dataStr = parts[2]; |
| | | byte[] data = CanFrame.ParseData(dataStr); |
| | | |
| | | // 验证数据长度 |
| | | if (data.Length > 8) |
| | | { |
| | | throw new ArgumentException($"[CAN] 数据长度超限: {data.Length} 字节(最大 8 字节)"); |
| | | } |
| | | |
| | | // 创建并发送帧 |
| | | var frame = new CanFrame |
| | | { |
| | | FrameId = frameId, |
| | | Format = frameId > 0x7FF ? CanFrameFormat.Extended : CanFrameFormat.Standard, |
| | | Type = CanFrameType.Data, |
| | | Data = data |
| | | }; |
| | | |
| | | _node!.SendFrame(frame); |
| | | |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | |
| | | string response = $"OK:TX:{frame.FrameIdHex}:{frame.Dlc}"; |
| | | Log.Debug($"[CAN] 设备 {DeviceId} 发送成功 (延迟 {_lastLatencyMs:F2}ms): {response}"); |
| | | return Task.FromResult(response); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[CAN] 设备 {DeviceId} 发送命令失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task<string> QueryAsync(string query, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[CAN] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[CAN] 设备 {DeviceId} 查询: {query}"); |
| | | |
| | | string[] parts = query.Split(':'); |
| | | string operation = parts[0].ToUpper(); |
| | | |
| | | string response; |
| | | switch (operation) |
| | | { |
| | | case "RECV": |
| | | response = HandleRecv(); |
| | | break; |
| | | |
| | | case "RECV_ALL": |
| | | response = HandleRecvAll(); |
| | | break; |
| | | |
| | | case "STATUS": |
| | | response = HandleStatus(); |
| | | break; |
| | | |
| | | case "FILTER": |
| | | response = HandleFilter(parts); |
| | | break; |
| | | |
| | | case "FILTER_CLEAR": |
| | | _node!.ClearFilters(); |
| | | response = "OK:滤波器已清除"; |
| | | break; |
| | | |
| | | case "PROMISCUOUS": |
| | | _node!.SetPromiscuousMode(); |
| | | response = "OK:混杂模式已启用"; |
| | | break; |
| | | |
| | | case "AUTO_RESPONSE": |
| | | response = HandleAutoResponse(parts); |
| | | break; |
| | | |
| | | case "RESPONSE_DELAY": |
| | | response = HandleResponseDelay(parts); |
| | | break; |
| | | |
| | | case "LOG": |
| | | response = HandleLog(); |
| | | break; |
| | | |
| | | case "BUS_STATUS": |
| | | response = _bus?.GetStatusSummary() ?? "总线未初始化"; |
| | | break; |
| | | |
| | | default: |
| | | throw new ArgumentException($"[CAN] 不支持的查询类型: {operation}"); |
| | | } |
| | | |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | Log.Debug($"[CAN] 设备 {DeviceId} 查询响应: {response} (延迟 {_lastLatencyMs:F2}ms)"); |
| | | return Task.FromResult(response); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[CAN] 设备 {DeviceId} 查询失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理 RECV 查询 - 获取下一帧 |
| | | /// </summary> |
| | | private string HandleRecv() |
| | | { |
| | | // 先从缓冲区取 |
| | | if (_recvBuffer.Count > 0) |
| | | { |
| | | var frame = _recvBuffer.Dequeue(); |
| | | return $"RECV:{frame.FrameIdHex}:{frame.DataHex}:{frame.Timestamp:HH:mm:ss.fff}"; |
| | | } |
| | | |
| | | // 从节点邮箱取 |
| | | if (_node!.TryDequeueReceived(out var frame2)) |
| | | { |
| | | return $"RECV:{frame2!.FrameIdHex}:{frame2.DataHex}:{frame2.Timestamp:HH:mm:ss.fff}"; |
| | | } |
| | | |
| | | return "RECV:EMPTY"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理 RECV_ALL 查询 - 获取所有帧 |
| | | /// </summary> |
| | | private string HandleRecvAll() |
| | | { |
| | | var frames = new List<string>(); |
| | | |
| | | // 先取缓冲区 |
| | | while (_recvBuffer.Count > 0) |
| | | { |
| | | var f = _recvBuffer.Dequeue(); |
| | | frames.Add($"{f.FrameIdHex}:{f.DataHex}"); |
| | | } |
| | | |
| | | // 再取节点邮箱 |
| | | var mailbox = _node!.PeekReceiveMailbox(); |
| | | _node.ClearReceiveMailbox(); |
| | | foreach (var f in mailbox) |
| | | { |
| | | frames.Add($"{f.FrameIdHex}:{f.DataHex}:{f.Timestamp:HH:mm:ss.fff}"); |
| | | } |
| | | |
| | | return frames.Count > 0 ? $"RECV_ALL:{frames.Count}:{string.Join("|", frames)}" : "RECV_ALL:0"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理 STATUS 查询 |
| | | /// </summary> |
| | | private string HandleStatus() |
| | | { |
| | | var nodeStatus = _node?.GetStatusSummary() ?? "节点未初始化"; |
| | | var busStatus = _bus?.GetStatusSummary() ?? "总线未初始化"; |
| | | return $"STATUS:{nodeStatus}|{busStatus}"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理 FILTER 设置 |
| | | /// </summary> |
| | | private string HandleFilter(string[] parts) |
| | | { |
| | | if (parts.Length < 3) |
| | | { |
| | | throw new ArgumentException("[CAN] FILTER 命令格式错误,应为 FILTER:id:mask"); |
| | | } |
| | | |
| | | uint filterId = CanFrame.ParseFrameId(parts[1]); |
| | | uint mask = CanFrame.ParseFrameId(parts[2]); |
| | | |
| | | _node!.AddFilter(new AcceptanceFilter |
| | | { |
| | | FilterId = filterId, |
| | | Mask = mask, |
| | | IsExtended = filterId > 0x7FF |
| | | }); |
| | | |
| | | return $"OK:滤波器已添加 ID=0x{filterId:X3}, Mask=0x{mask:X3}"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理 AUTO_RESPONSE 命令 |
| | | /// 格式: AUTO_RESPONSE:on|off |
| | | /// </summary> |
| | | private string HandleAutoResponse(string[] parts) |
| | | { |
| | | if (parts.Length < 2) |
| | | { |
| | | // 查询当前状态 |
| | | bool current = _node!.AutoResponseEnabled; |
| | | return $"AUTO_RESPONSE:{(current ? "ON" : "OFF")}"; |
| | | } |
| | | |
| | | string mode = parts[1].Trim().ToUpper(); |
| | | switch (mode) |
| | | { |
| | | case "ON": |
| | | case "1": |
| | | case "TRUE": |
| | | case "ENABLE": |
| | | _node!.AutoResponseEnabled = true; |
| | | Log.Info($"[CAN] 设备 {DeviceId} 启用自动响应模式"); |
| | | return "OK:自动响应模式已启用"; |
| | | |
| | | case "OFF": |
| | | case "0": |
| | | case "FALSE": |
| | | case "DISABLE": |
| | | _node!.AutoResponseEnabled = false; |
| | | Log.Info($"[CAN] 设备 {DeviceId} 禁用自动响应模式"); |
| | | return "OK:自动响应模式已禁用"; |
| | | |
| | | default: |
| | | throw new ArgumentException($"[CAN] AUTO_RESPONSE 参数错误: {mode},应为 on/off"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理 RESPONSE_DELAY 命令 |
| | | /// 格式: RESPONSE_DELAY:毫秒数 |
| | | /// </summary> |
| | | private string HandleResponseDelay(string[] parts) |
| | | { |
| | | if (parts.Length < 2) |
| | | { |
| | | // 查询当前延迟 |
| | | return $"RESPONSE_DELAY:{_node!.AutoResponseDelayMs}ms"; |
| | | } |
| | | |
| | | if (!int.TryParse(parts[1].Trim(), out int delayMs) || delayMs < 0) |
| | | { |
| | | throw new ArgumentException($"[CAN] RESPONSE_DELAY 参数错误: {parts[1]},应为非负整数(毫秒)"); |
| | | } |
| | | |
| | | _node!.AutoResponseDelayMs = delayMs; |
| | | Log.Info($"[CAN] 设备 {DeviceId} 设置响应延迟: {delayMs}ms"); |
| | | return $"OK:响应延迟已设置为 {delayMs}ms"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理 LOG 查询 |
| | | /// </summary> |
| | | private string HandleLog() |
| | | { |
| | | var log = _bus?.GetBusLog(50) ?? new List<CanFrame>(); |
| | | if (log.Count == 0) return "LOG:EMPTY"; |
| | | |
| | | var entries = log.Select(f => |
| | | $"[{f.Timestamp:HH:mm:ss.fff}]{f.SourceNodeId}>{f.FrameIdHex}:{f.DataHex}"); |
| | | return $"LOG:{log.Count}:{string.Join("|", entries)}"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 帧接收事件(公开给外部订阅) |
| | | /// </summary> |
| | | public event EventHandler<CanFrame>? DriverFrameReceived; |
| | | |
| | | /// <summary> |
| | | /// 帧接收事件处理 |
| | | /// </summary> |
| | | private void OnFrameReceived(object? sender, CanFrame frame) |
| | | { |
| | | // 放入接收缓冲区(用于 RECV/RECV_ALL 查询) |
| | | while (_recvBuffer.Count >= 1000) |
| | | { |
| | | _recvBuffer.Dequeue(); |
| | | } |
| | | _recvBuffer.Enqueue(frame); |
| | | |
| | | // 触发公开事件(用于即时显示) |
| | | DriverFrameReceived?.Invoke(this, frame); |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public DeviceDriverStatus GetStatus() |
| | | { |
| | | return new DeviceDriverStatus |
| | | { |
| | | IsConnected = IsConnected, |
| | | IsHealthy = IsConnected && string.IsNullOrEmpty(_lastError), |
| | | LastError = _lastError, |
| | | LastErrorTime = _lastErrorTime, |
| | | LatencyMs = _lastLatencyMs, |
| | | ReconnectCount = _reconnectCount |
| | | }; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public void Dispose() |
| | | { |
| | | if (_disposed) return; |
| | | _disposed = true; |
| | | |
| | | try |
| | | { |
| | | if (_node != null) |
| | | { |
| | | _node.FrameReceived -= OnFrameReceived; |
| | | _node.Deactivate(); |
| | | _bus?.UnregisterNode(_node.NodeId); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[CAN] 设备 {DeviceId} 释放资源异常: {ex.Message}", ex); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 触发所有总线和节点(用于测试清理) |
| | | /// </summary> |
| | | public static void ResetAllBuses() |
| | | { |
| | | lock (_busLock) |
| | | { |
| | | foreach (var bus in _sharedBuses.Values) |
| | | { |
| | | bus.Dispose(); |
| | | } |
| | | _sharedBuses.Clear(); |
| | | Log.Info("[CAN] 所有虚拟总线已重置"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 触发所有总线实例(供外部查询) |
| | | /// </summary> |
| | | public static IReadOnlyDictionary<string, CanBusSimulator> GetAllBuses() |
| | | { |
| | | lock (_busLock) |
| | | { |
| | | return new Dictionary<string, CanBusSimulator>(_sharedBuses); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 触发连接状态变化事件 |
| | | /// </summary> |
| | | private void OnConnectionChanged(bool isConnected, string reason) |
| | | { |
| | | ConnectionChanged?.Invoke(this, new DeviceConnectionEventArgs |
| | | { |
| | | DeviceId = DeviceId, |
| | | IsConnected = isConnected, |
| | | Reason = reason |
| | | }); |
| | | |
| | | if (!isConnected) |
| | | { |
| | | _reconnectCount++; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查是否已释放 |
| | | /// </summary> |
| | | private void ThrowIfDisposed() |
| | | { |
| | | if (_disposed) throw new ObjectDisposedException(nameof(CanDriverAdapter)); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using Aging.Infrastructure.Contracts; |
| | | using log4net; |
| | | |
| | | namespace Aging.Infrastructure.Drivers |
| | | { |
| | | /// <summary> |
| | | /// 设备驱动工厂。 |
| | | /// 根据传输协议类型和端点信息创建对应的驱动适配器实例。 |
| | | /// </summary> |
| | | public static class DriverFactory |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(DriverFactory)); |
| | | |
| | | /// <summary> |
| | | /// 根据传输协议类型和端点创建驱动适配器 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备唯一标识符</param> |
| | | /// <param name="transport">传输协议类型: "Tcp", "SCPI", "ModBus", "S7", "Serial"</param> |
| | | /// <param name="endpoint">端点信息,格式: "ip:port" 或 "ip:port:extra"</param> |
| | | /// <returns>IDeviceDriver 实例</returns> |
| | | /// <exception cref="NotSupportedException">不支持的传输协议类型</exception> |
| | | /// <exception cref="ArgumentException">端点格式错误</exception> |
| | | public static IDeviceDriver CreateDriver(string deviceId, string transport, string endpoint) |
| | | { |
| | | return CreateDriver(deviceId, transport, endpoint, null); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据传输协议类型、端点和额外参数创建驱动适配器 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备唯一标识符</param> |
| | | /// <param name="transport">传输协议类型: "Tcp", "SCPI", "ModBus", "S7", "Serial"</param> |
| | | /// <param name="endpoint">端点信息,格式: "ip:port" 或 "ip:port:extra"</param> |
| | | /// <param name="extraParams">额外参数(可选)</param> |
| | | /// <returns>IDeviceDriver 实例</returns> |
| | | /// <exception cref="NotSupportedException">不支持的传输协议类型</exception> |
| | | /// <exception cref="ArgumentException">端点格式错误</exception> |
| | | public static IDeviceDriver CreateDriver(string deviceId, string transport, string endpoint, Dictionary<string, string>? extraParams) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentNullException(nameof(deviceId)); |
| | | if (string.IsNullOrWhiteSpace(transport)) |
| | | throw new ArgumentNullException(nameof(transport)); |
| | | if (string.IsNullOrWhiteSpace(endpoint)) |
| | | throw new ArgumentNullException(nameof(endpoint)); |
| | | |
| | | Log.Info($"[DriverFactory] 创建设备驱动: DeviceId={deviceId}, Transport={transport}, Endpoint={endpoint}"); |
| | | |
| | | string[] parts = endpoint.Split(':'); |
| | | if (parts.Length < 2) |
| | | { |
| | | throw new ArgumentException($"端点格式错误,应为 'ip:port' 或 'ip:port:extra',实际: {endpoint}"); |
| | | } |
| | | |
| | | string ip = parts[0]; |
| | | if (!int.TryParse(parts[1], out int port)) |
| | | { |
| | | throw new ArgumentException($"端口号格式错误: {parts[1]}"); |
| | | } |
| | | |
| | | switch (transport.ToUpper()) |
| | | { |
| | | case "TCP": |
| | | Log.Info($"[DriverFactory] 创建 TCP 原始通信驱动: {ip}:{port}"); |
| | | return new TcpRawDriverAdapter(deviceId, ip, port); |
| | | |
| | | case "SCPI": |
| | | Log.Info($"[DriverFactory] 创建 SCPI 协议驱动: {ip}:{port}"); |
| | | return new ScpiDriverAdapter(deviceId, ip, port); |
| | | |
| | | case "MODBUS": |
| | | byte slaveId = 1; |
| | | if (extraParams != null && extraParams.TryGetValue("slaveId", out string slaveIdStr)) |
| | | { |
| | | if (!byte.TryParse(slaveIdStr, out slaveId)) |
| | | { |
| | | slaveId = 1; |
| | | } |
| | | } |
| | | // 如果 endpoint 有第三部分,作为 slaveId |
| | | else if (parts.Length >= 3 && byte.TryParse(parts[2], out byte parsedSlaveId)) |
| | | { |
| | | slaveId = parsedSlaveId; |
| | | } |
| | | Log.Info($"[DriverFactory] 创建 ModBus TCP 驱动: {ip}:{port} (从站: {slaveId})"); |
| | | return new ModBusDriverAdapter(deviceId, ip, port, slaveId); |
| | | |
| | | case "S7": |
| | | Log.Info($"[DriverFactory] 创建 S7 PLC 驱动: {ip}:{port}"); |
| | | return new S7DriverAdapter(deviceId, ip, port); |
| | | |
| | | case "SERIAL": |
| | | // TODO: 实现串口驱动适配器 |
| | | throw new NotSupportedException("串口驱动适配器尚未实现"); |
| | | |
| | | case "CAN": |
| | | // CAN 驱动: endpoint 格式为 "busName:nodeIndex" 如 "CAN_BUS_1:0" |
| | | string busName = ip; // ip 字段用作总线名称 |
| | | int nodeIndex = port; // port 字段用作节点编号 |
| | | Log.Info($"[DriverFactory] 创建 CAN 虚拟驱动: Bus={busName}, Node={nodeIndex}"); |
| | | return new CanDriverAdapter(deviceId, busName, nodeIndex); |
| | | |
| | | default: |
| | | throw new NotSupportedException($"不支持的传输协议类型: {transport}。支持的类型: Tcp, SCPI, ModBus, S7, Serial, CAN"); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Diagnostics; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | using Aging.Infrastructure.Contracts; |
| | | using HslCommunication; |
| | | using HslCommunication.ModBus; |
| | | using log4net; |
| | | |
| | | namespace Aging.Infrastructure.Drivers |
| | | { |
| | | /// <summary> |
| | | /// ModBus TCP 驱动适配器。 |
| | | /// 封装 HslCommunication.ModBusTcpNet,实现统一的 IDeviceDriver 接口。 |
| | | /// 命令格式约定: |
| | | /// 写入: "WRITE:address:value" 如 "WRITE:40001:100" 或 "WRITE:00001:TRUE" |
| | | /// 读取: "READ:address" 如 "READ:00001" 或 "READ:40001" |
| | | /// </summary> |
| | | public sealed class ModBusDriverAdapter : IDeviceDriver |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(ModBusDriverAdapter)); |
| | | |
| | | private readonly string _ip; |
| | | private readonly int _port; |
| | | private readonly byte _slaveId; |
| | | private ModbusTcpNet? _client; |
| | | private bool _disposed; |
| | | private readonly Stopwatch _latencyStopwatch = new Stopwatch(); |
| | | private double _lastLatencyMs; |
| | | private string _lastError = string.Empty; |
| | | private DateTime? _lastErrorTime; |
| | | private int _reconnectCount; |
| | | |
| | | /// <inheritdoc/> |
| | | public string DeviceId { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool IsConnected => _client != null; |
| | | |
| | | /// <inheritdoc/> |
| | | public event EventHandler<DeviceConnectionEventArgs>? ConnectionChanged; |
| | | |
| | | /// <summary> |
| | | /// 初始化 ModBus TCP 驱动适配器 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备唯一标识符</param> |
| | | /// <param name="ip">目标 IP 地址</param> |
| | | /// <param name="port">目标端口号</param> |
| | | /// <param name="slaveId">从站地址(默认为 1)</param> |
| | | public ModBusDriverAdapter(string deviceId, string ip, int port, byte slaveId = 1) |
| | | { |
| | | DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId)); |
| | | _ip = ip ?? throw new ArgumentNullException(nameof(ip)); |
| | | _port = port; |
| | | _slaveId = slaveId; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task ConnectAsync(CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[ModBus] 设备 {DeviceId} 正在连接 {_ip}:{_port} (从站: {_slaveId})..."); |
| | | |
| | | try |
| | | { |
| | | _client = new ModbusTcpNet(_ip, _port, _slaveId); |
| | | OperateResult result = _client.ConnectServer(); |
| | | |
| | | if (!result.IsSuccess) |
| | | { |
| | | _lastError = result.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | throw new InvalidOperationException($"[ModBus] 设备 {DeviceId} 连接失败: {result.Message}"); |
| | | } |
| | | |
| | | _lastError = string.Empty; |
| | | Log.Info($"[ModBus] 设备 {DeviceId} 已成功连接到 {_ip}:{_port}"); |
| | | OnConnectionChanged(true, "连接成功"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[ModBus] 设备 {DeviceId} 连接异常: {ex.Message}", ex); |
| | | _client?.ConnectClose(); |
| | | _client?.Dispose(); |
| | | _client = null; |
| | | throw; |
| | | } |
| | | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task DisconnectAsync() |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[ModBus] 设备 {DeviceId} 正在断开连接..."); |
| | | |
| | | try |
| | | { |
| | | if (_client != null) |
| | | { |
| | | _client.ConnectClose(); |
| | | _client.Dispose(); |
| | | _client = null; |
| | | } |
| | | |
| | | Log.Info($"[ModBus] 设备 {DeviceId} 已断开连接"); |
| | | OnConnectionChanged(false, "主动断开"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[ModBus] 设备 {DeviceId} 断开连接异常: {ex.Message}", ex); |
| | | _client = null; |
| | | } |
| | | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task<string> SendCommandAsync(string command, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[ModBus] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[ModBus] 设备 {DeviceId} 发送命令: {command}"); |
| | | |
| | | // 解析命令格式: "WRITE:address:value" |
| | | string[] parts = command.Split(':'); |
| | | if (parts.Length < 3) |
| | | { |
| | | throw new ArgumentException($"[ModBus] 命令格式错误,应为 WRITE:address:value,实际: {command}"); |
| | | } |
| | | |
| | | string operation = parts[0].ToUpper(); |
| | | string address = parts[1]; |
| | | string value = parts[2]; |
| | | |
| | | if (operation != "WRITE") |
| | | { |
| | | throw new ArgumentException($"[ModBus] 不支持的操作类型: {operation},应为 WRITE"); |
| | | } |
| | | |
| | | // 根据值判断数据类型并写入 |
| | | OperateResult result; |
| | | if (bool.TryParse(value, out bool boolVal)) |
| | | { |
| | | result = _client.Write(address, boolVal); |
| | | } |
| | | else if (short.TryParse(value, out short shortVal)) |
| | | { |
| | | result = _client.Write(address, shortVal); |
| | | } |
| | | else if (int.TryParse(value, out int intVal)) |
| | | { |
| | | result = _client.Write(address, intVal); |
| | | } |
| | | else if (float.TryParse(value, out float floatVal)) |
| | | { |
| | | result = _client.Write(address, floatVal); |
| | | } |
| | | else |
| | | { |
| | | throw new ArgumentException($"[ModBus] 无法解析的值: {value}"); |
| | | } |
| | | |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | |
| | | if (!result.IsSuccess) |
| | | { |
| | | _lastError = result.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | throw new InvalidOperationException($"[ModBus] 写入失败: {result.Message}"); |
| | | } |
| | | |
| | | string response = "OK"; |
| | | Log.Debug($"[ModBus] 设备 {DeviceId} 写入成功 (延迟 {_lastLatencyMs:F2}ms)"); |
| | | return Task.FromResult(response); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[ModBus] 设备 {DeviceId} 发送命令失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task<string> QueryAsync(string query, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[ModBus] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[ModBus] 设备 {DeviceId} 发送查询: {query}"); |
| | | |
| | | // 解析查询格式: "READ:address" |
| | | string[] parts = query.Split(':'); |
| | | if (parts.Length < 2) |
| | | { |
| | | throw new ArgumentException($"[ModBus] 查询格式错误,应为 READ:address,实际: {query}"); |
| | | } |
| | | |
| | | string operation = parts[0].ToUpper(); |
| | | string address = parts[1]; |
| | | |
| | | if (operation != "READ") |
| | | { |
| | | throw new ArgumentException($"[ModBus] 不支持的操作类型: {operation},应为 READ"); |
| | | } |
| | | |
| | | // 尝试读取不同类型(根据地址范围自动判断) |
| | | // 00001-09999: 线圈(BOOL) |
| | | // 10001-19999: 离散输入(BOOL) |
| | | // 30001-39999: 输入寄存器(INT16) |
| | | // 40001-49999: 保持寄存器(INT16) |
| | | string response; |
| | | int addrNum; |
| | | if (int.TryParse(address, out addrNum)) |
| | | { |
| | | if (addrNum >= 1 && addrNum <= 9999) |
| | | { |
| | | // 线圈读取 |
| | | var result = _client.ReadBool(address); |
| | | response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; |
| | | } |
| | | else if (addrNum >= 10001 && addrNum <= 19999) |
| | | { |
| | | // 离散输入读取 |
| | | var result = _client.ReadBool(address); |
| | | response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; |
| | | } |
| | | else if (addrNum >= 30001 && addrNum <= 39999) |
| | | { |
| | | // 输入寄存器读取 |
| | | var result = _client.ReadInt16(address); |
| | | response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; |
| | | } |
| | | else if (addrNum >= 40001 && addrNum <= 49999) |
| | | { |
| | | // 保持寄存器读取 |
| | | var result = _client.ReadInt16(address); |
| | | response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; |
| | | } |
| | | else |
| | | { |
| | | // 默认读取 INT16 |
| | | var result = _client.ReadInt16(address); |
| | | response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | // 非数字地址,尝试读取 INT16 |
| | | var result = _client.ReadInt16(address); |
| | | response = result.IsSuccess ? result.Content.ToString() : $"ERROR: {result.Message}"; |
| | | } |
| | | |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | Log.Debug($"[ModBus] 设备 {DeviceId} 查询响应: {response} (延迟 {_lastLatencyMs:F2}ms)"); |
| | | return Task.FromResult(response); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[ModBus] 设备 {DeviceId} 查询失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public DeviceDriverStatus GetStatus() |
| | | { |
| | | return new DeviceDriverStatus |
| | | { |
| | | IsConnected = IsConnected, |
| | | IsHealthy = IsConnected && string.IsNullOrEmpty(_lastError), |
| | | LastError = _lastError, |
| | | LastErrorTime = _lastErrorTime, |
| | | LatencyMs = _lastLatencyMs, |
| | | ReconnectCount = _reconnectCount |
| | | }; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public void Dispose() |
| | | { |
| | | if (_disposed) return; |
| | | _disposed = true; |
| | | |
| | | try |
| | | { |
| | | if (_client != null) |
| | | { |
| | | _client.ConnectClose(); |
| | | _client.Dispose(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[ModBus] 设备 {DeviceId} 释放资源异常: {ex.Message}", ex); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 触发连接状态变化事件 |
| | | /// </summary> |
| | | private void OnConnectionChanged(bool isConnected, string reason) |
| | | { |
| | | ConnectionChanged?.Invoke(this, new DeviceConnectionEventArgs |
| | | { |
| | | DeviceId = DeviceId, |
| | | IsConnected = isConnected, |
| | | Reason = reason |
| | | }); |
| | | |
| | | if (!isConnected) |
| | | { |
| | | _reconnectCount++; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查是否已释放 |
| | | /// </summary> |
| | | private void ThrowIfDisposed() |
| | | { |
| | | if (_disposed) throw new ObjectDisposedException(nameof(ModBusDriverAdapter)); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Diagnostics; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | using Aging.Infrastructure.Contracts; |
| | | using log4net; |
| | | using S7Driver; |
| | | |
| | | namespace Aging.Infrastructure.Drivers |
| | | { |
| | | /// <summary> |
| | | /// 西门子 S7 PLC 驱动适配器。 |
| | | /// 封装 S7Driver.PLCS7Driver,实现统一的 IDeviceDriver 接口。 |
| | | /// 命令格式约定: |
| | | /// 写入: "WRITE:DBx.DBXy.z:value" 如 "WRITE:DB1.DBX0.0:TRUE" |
| | | /// 读取: "READ:DBx.DBWy" 如 "READ:DB1.DBD0" |
| | | /// 支持数据类型: BOOL, INT, REAL |
| | | /// </summary> |
| | | public sealed class S7DriverAdapter : IDeviceDriver |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(S7DriverAdapter)); |
| | | |
| | | private readonly string _ip; |
| | | private readonly int _port; |
| | | private PLCS7Driver? _driver; |
| | | private bool _disposed; |
| | | private readonly Stopwatch _latencyStopwatch = new Stopwatch(); |
| | | private double _lastLatencyMs; |
| | | private string _lastError = string.Empty; |
| | | private DateTime? _lastErrorTime; |
| | | private int _reconnectCount; |
| | | |
| | | /// <inheritdoc/> |
| | | public string DeviceId { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool IsConnected => _driver != null; |
| | | |
| | | /// <inheritdoc/> |
| | | public event EventHandler<DeviceConnectionEventArgs>? ConnectionChanged; |
| | | |
| | | /// <summary> |
| | | /// 初始化 S7 PLC 驱动适配器 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备唯一标识符</param> |
| | | /// <param name="ip">PLC IP 地址</param> |
| | | /// <param name="port">PLC 端口号(通常为 102)</param> |
| | | public S7DriverAdapter(string deviceId, string ip, int port) |
| | | { |
| | | DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId)); |
| | | _ip = ip ?? throw new ArgumentNullException(nameof(ip)); |
| | | _port = port; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task ConnectAsync(CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[S7] 设备 {DeviceId} 正在连接 {_ip}:{_port}..."); |
| | | |
| | | try |
| | | { |
| | | _driver = new PLCS7Driver(_ip, _port); |
| | | bool success = _driver.Connect(out string errMsg); |
| | | |
| | | if (!success) |
| | | { |
| | | _lastError = errMsg; |
| | | _lastErrorTime = DateTime.Now; |
| | | throw new InvalidOperationException($"[S7] 设备 {DeviceId} 连接失败: {errMsg}"); |
| | | } |
| | | |
| | | _lastError = string.Empty; |
| | | Log.Info($"[S7] 设备 {DeviceId} 已成功连接到 {_ip}:{_port}"); |
| | | OnConnectionChanged(true, "连接成功"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[S7] 设备 {DeviceId} 连接异常: {ex.Message}", ex); |
| | | _driver?.Dispose(); |
| | | _driver = null; |
| | | throw; |
| | | } |
| | | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task DisconnectAsync() |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[S7] 设备 {DeviceId} 正在断开连接..."); |
| | | |
| | | try |
| | | { |
| | | if (_driver != null) |
| | | { |
| | | _driver.DisConnect(out string errMsg); |
| | | _driver.Dispose(); |
| | | _driver = null; |
| | | } |
| | | |
| | | Log.Info($"[S7] 设备 {DeviceId} 已断开连接"); |
| | | OnConnectionChanged(false, "主动断开"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[S7] 设备 {DeviceId} 断开连接异常: {ex.Message}", ex); |
| | | _driver?.Dispose(); |
| | | _driver = null; |
| | | } |
| | | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task<string> SendCommandAsync(string command, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[S7] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[S7] 设备 {DeviceId} 发送命令: {command}"); |
| | | |
| | | // 解析命令格式: "WRITE:DBx.DBXy.z:value" 或 "WRITE:DBx.DBWy:value" |
| | | string[] parts = command.Split(':'); |
| | | if (parts.Length < 3) |
| | | { |
| | | throw new ArgumentException($"[S7] 命令格式错误,应为 WRITE:address:value,实际: {command}"); |
| | | } |
| | | |
| | | string operation = parts[0].ToUpper(); |
| | | string address = parts[1]; |
| | | string value = parts[2]; |
| | | |
| | | if (operation != "WRITE") |
| | | { |
| | | throw new ArgumentException($"[S7] 不支持的操作类型: {operation},应为 WRITE"); |
| | | } |
| | | |
| | | // 解析地址和值,执行写入 |
| | | bool success = ParseAndWrite(address, value, out string msg); |
| | | |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | |
| | | if (!success) |
| | | { |
| | | _lastError = msg; |
| | | _lastErrorTime = DateTime.Now; |
| | | throw new InvalidOperationException($"[S7] 写入失败: {msg}"); |
| | | } |
| | | |
| | | string response = "OK"; |
| | | Log.Debug($"[S7] 设备 {DeviceId} 写入成功 (延迟 {_lastLatencyMs:F2}ms)"); |
| | | return Task.FromResult(response); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[S7] 设备 {DeviceId} 发送命令失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task<string> QueryAsync(string query, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[S7] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[S7] 设备 {DeviceId} 发送查询: {query}"); |
| | | |
| | | // 解析查询格式: "READ:DBx.DBWy" 或 "READ:DBx.DBXy.z" |
| | | string[] parts = query.Split(':'); |
| | | if (parts.Length < 2) |
| | | { |
| | | throw new ArgumentException($"[S7] 查询格式错误,应为 READ:address,实际: {query}"); |
| | | } |
| | | |
| | | string operation = parts[0].ToUpper(); |
| | | string address = parts[1]; |
| | | |
| | | if (operation != "READ") |
| | | { |
| | | throw new ArgumentException($"[S7] 不支持的操作类型: {operation},应为 READ"); |
| | | } |
| | | |
| | | // 解析地址并执行读取 |
| | | string response = ParseAndRead(address); |
| | | |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | Log.Debug($"[S7] 设备 {DeviceId} 查询响应: {response} (延迟 {_lastLatencyMs:F2}ms)"); |
| | | return Task.FromResult(response); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[S7] 设备 {DeviceId} 查询失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public DeviceDriverStatus GetStatus() |
| | | { |
| | | return new DeviceDriverStatus |
| | | { |
| | | IsConnected = IsConnected, |
| | | IsHealthy = IsConnected && string.IsNullOrEmpty(_lastError), |
| | | LastError = _lastError, |
| | | LastErrorTime = _lastErrorTime, |
| | | LatencyMs = _lastLatencyMs, |
| | | ReconnectCount = _reconnectCount |
| | | }; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public void Dispose() |
| | | { |
| | | if (_disposed) return; |
| | | _disposed = true; |
| | | |
| | | try |
| | | { |
| | | _driver?.Dispose(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[S7] 设备 {DeviceId} 释放资源异常: {ex.Message}", ex); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 解析地址并执行写入操作 |
| | | /// </summary> |
| | | /// <param name="address">S7 地址,如 "DB1.DBX0.0" 或 "DB1.DBW10"</param> |
| | | /// <param name="value">要写入的值</param> |
| | | /// <param name="msg">错误信息</param> |
| | | /// <returns>是否成功</returns> |
| | | private bool ParseAndWrite(string address, string value, out string msg) |
| | | { |
| | | msg = null; |
| | | |
| | | try |
| | | { |
| | | // 解析地址格式: DBx.DBXy.z (BOOL) 或 DBx.DBWy (INT/REAL) |
| | | if (address.Contains(".DBX")) |
| | | { |
| | | // BOOL 类型: DB1.DBX0.0 |
| | | if (bool.TryParse(value, out bool boolVal)) |
| | | { |
| | | var addrParts = ParseBoolAddress(address); |
| | | return _driver.Simens_S7_WriteBool(addrParts.db, addrParts.startAddr, addrParts.pos, boolVal, out msg); |
| | | } |
| | | throw new ArgumentException($"[S7] BOOL 值解析失败: {value}"); |
| | | } |
| | | else if (address.Contains(".DBW")) |
| | | { |
| | | // 尝试解析为不同类型 |
| | | if (float.TryParse(value, out float realVal)) |
| | | { |
| | | var addrParts = ParseWordAddress(address); |
| | | return _driver.Simens_S7_WriteReal(addrParts.db, addrParts.startAddr, realVal, out msg); |
| | | } |
| | | else if (short.TryParse(value, out short shortVal)) |
| | | { |
| | | var addrParts = ParseWordAddress(address); |
| | | return _driver.Simens_S7_WriteInt(addrParts.db, addrParts.startAddr, shortVal, out msg); |
| | | } |
| | | throw new ArgumentException($"[S7] 值解析失败: {value}"); |
| | | } |
| | | else |
| | | { |
| | | msg = $"[S7] 不支持的地址格式: {address}"; |
| | | return false; |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | msg = ex.Message; |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 解析地址并执行读取操作 |
| | | /// </summary> |
| | | /// <param name="address">S7 地址,如 "DB1.DBX0.0" 或 "DB1.DBW10"</param> |
| | | /// <returns>读取的值字符串</returns> |
| | | private string ParseAndRead(string address) |
| | | { |
| | | if (address.Contains(".DBX")) |
| | | { |
| | | // BOOL 类型读取 |
| | | var addrParts = ParseBoolAddress(address); |
| | | if (_driver.Simens_S7_ReadBool(addrParts.db, addrParts.startAddr, addrParts.pos, out bool value, out string msg)) |
| | | { |
| | | return value.ToString(); |
| | | } |
| | | throw new InvalidOperationException($"[S7] 读取失败: {msg}"); |
| | | } |
| | | else if (address.Contains(".DBD")) |
| | | { |
| | | // REAL 类型读取 (DBD = Double Word) |
| | | var addrParts = ParseWordAddress(address); |
| | | if (_driver.Simens_S7_ReadReal(addrParts.db, addrParts.startAddr, out float realValue, out string msg)) |
| | | { |
| | | return realValue.ToString(); |
| | | } |
| | | throw new InvalidOperationException($"[S7] 读取失败: {msg}"); |
| | | } |
| | | else if (address.Contains(".DBW")) |
| | | { |
| | | // INT 类型读取 |
| | | var addrParts = ParseWordAddress(address); |
| | | if (_driver.Simens_S7_ReadInt(addrParts.db, addrParts.startAddr, out int value, out string msg)) |
| | | { |
| | | return value.ToString(); |
| | | } |
| | | throw new InvalidOperationException($"[S7] 读取失败: {msg}"); |
| | | } |
| | | else |
| | | { |
| | | throw new ArgumentException($"[S7] 不支持的地址格式: {address}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 解析 BOOL 地址格式: DBx.DBXy.z |
| | | /// </summary> |
| | | private (int db, int startAddr, int pos) ParseBoolAddress(string address) |
| | | { |
| | | // 格式: DB1.DBX0.0 |
| | | string upper = address.ToUpper(); |
| | | int dbStart = upper.IndexOf("DB") + 2; |
| | | int dbxStart = upper.IndexOf(".DBX"); |
| | | int db = int.Parse(upper.Substring(dbStart, dbxStart - dbStart)); |
| | | |
| | | int posStart = dbxStart + 5; // .DBX 后 |
| | | int dotPos = address.IndexOf('.', posStart); |
| | | int startAddr = int.Parse(address.Substring(posStart, dotPos - posStart)); |
| | | int pos = int.Parse(address.Substring(dotPos + 1)); |
| | | |
| | | return (db, startAddr, pos); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 解析 Word 地址格式: DBx.DBWy 或 DBx.DBDy |
| | | /// </summary> |
| | | private (int db, int startAddr) ParseWordAddress(string address) |
| | | { |
| | | // 格式: DB1.DBW10 或 DB1.DBD0 |
| | | string upper = address.ToUpper(); |
| | | int dbStart = upper.IndexOf("DB") + 2; |
| | | int dbwStart = upper.Contains(".DBW") ? upper.IndexOf(".DBW") : upper.IndexOf(".DBD"); |
| | | int db = int.Parse(upper.Substring(dbStart, dbwStart - dbStart)); |
| | | |
| | | int addrStart = dbwStart + 5; // .DBW 或 .DBD 后 |
| | | int startAddr = int.Parse(address.Substring(addrStart)); |
| | | |
| | | return (db, startAddr); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 触发连接状态变化事件 |
| | | /// </summary> |
| | | private void OnConnectionChanged(bool isConnected, string reason) |
| | | { |
| | | ConnectionChanged?.Invoke(this, new DeviceConnectionEventArgs |
| | | { |
| | | DeviceId = DeviceId, |
| | | IsConnected = isConnected, |
| | | Reason = reason |
| | | }); |
| | | |
| | | if (!isConnected) |
| | | { |
| | | _reconnectCount++; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查是否已释放 |
| | | /// </summary> |
| | | private void ThrowIfDisposed() |
| | | { |
| | | if (_disposed) throw new ObjectDisposedException(nameof(S7DriverAdapter)); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Diagnostics; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | using Aging.Infrastructure.Contracts; |
| | | using log4net; |
| | | using PLCHelper; |
| | | |
| | | namespace Aging.Infrastructure.Drivers |
| | | { |
| | | /// <summary> |
| | | /// SCPI 协议驱动适配器。 |
| | | /// 封装 PLCHelper.RestTcpClient,实现统一的 IDeviceDriver 接口。 |
| | | /// 用于控制支持 SCPI 命令的仪器(如电源、电子负载等)。 |
| | | /// </summary> |
| | | public sealed class ScpiDriverAdapter : IDeviceDriver |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(ScpiDriverAdapter)); |
| | | |
| | | private readonly string _ip; |
| | | private readonly int _port; |
| | | private RestTcpClient? _client; |
| | | private bool _disposed; |
| | | private readonly Stopwatch _latencyStopwatch = new Stopwatch(); |
| | | private double _lastLatencyMs; |
| | | private string _lastError = string.Empty; |
| | | private DateTime? _lastErrorTime; |
| | | private int _reconnectCount; |
| | | |
| | | /// <inheritdoc/> |
| | | public string DeviceId { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool IsConnected => _client != null; |
| | | |
| | | /// <inheritdoc/> |
| | | public event EventHandler<DeviceConnectionEventArgs>? ConnectionChanged; |
| | | |
| | | /// <summary> |
| | | /// 初始化 SCPI 协议驱动适配器 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备唯一标识符</param> |
| | | /// <param name="ip">目标 IP 地址</param> |
| | | /// <param name="port">目标端口号</param> |
| | | public ScpiDriverAdapter(string deviceId, string ip, int port) |
| | | { |
| | | DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId)); |
| | | _ip = ip ?? throw new ArgumentNullException(nameof(ip)); |
| | | _port = port; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task ConnectAsync(CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[SCPI] 设备 {DeviceId} 正在连接 {_ip}:{_port}..."); |
| | | |
| | | try |
| | | { |
| | | _client = new RestTcpClient(_ip, _port); |
| | | _client.ReceiveTimeout = 5000; |
| | | _client.ConnectServer(); |
| | | |
| | | // 发送复位命令验证连接 |
| | | if (_client.Request("*RST", out string response, out string errMsg)) |
| | | { |
| | | Log.Info($"[SCPI] 设备 {DeviceId} 复位响应: {response}"); |
| | | } |
| | | else |
| | | { |
| | | Log.Warn($"[SCPI] 设备 {DeviceId} 复位失败: {errMsg}"); |
| | | } |
| | | |
| | | _lastError = string.Empty; |
| | | Log.Info($"[SCPI] 设备 {DeviceId} 已成功连接到 {_ip}:{_port}"); |
| | | OnConnectionChanged(true, "连接成功"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[SCPI] 设备 {DeviceId} 连接异常: {ex.Message}", ex); |
| | | _client?.Dispose(); |
| | | _client = null; |
| | | throw; |
| | | } |
| | | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task DisconnectAsync() |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[SCPI] 设备 {DeviceId} 正在断开连接..."); |
| | | |
| | | try |
| | | { |
| | | _client?.Dispose(); |
| | | _client = null; |
| | | |
| | | Log.Info($"[SCPI] 设备 {DeviceId} 已断开连接"); |
| | | OnConnectionChanged(false, "主动断开"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[SCPI] 设备 {DeviceId} 断开连接异常: {ex.Message}", ex); |
| | | _client = null; |
| | | } |
| | | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task<string> SendCommandAsync(string command, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[SCPI] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[SCPI] 设备 {DeviceId} 发送命令: {command}"); |
| | | |
| | | // SCPI 命令以 \r 结尾(RestTcpClient 协议自动处理) |
| | | bool success = _client.Request(command, out string response, out string errMsg); |
| | | |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | |
| | | if (!success) |
| | | { |
| | | _lastError = errMsg; |
| | | _lastErrorTime = DateTime.Now; |
| | | throw new InvalidOperationException($"[SCPI] 命令执行失败: {errMsg}"); |
| | | } |
| | | |
| | | Log.Debug($"[SCPI] 设备 {DeviceId} 命令响应: {response} (延迟 {_lastLatencyMs:F2}ms)"); |
| | | return Task.FromResult(response); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[SCPI] 设备 {DeviceId} 发送命令失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task<string> QueryAsync(string query, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[SCPI] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[SCPI] 设备 {DeviceId} 发送查询: {query}"); |
| | | |
| | | // 发送查询命令并等待回读(如 "MEAS:CURR?") |
| | | bool success = _client.Request(query, out string response, out string errMsg); |
| | | |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | |
| | | if (!success) |
| | | { |
| | | _lastError = errMsg; |
| | | _lastErrorTime = DateTime.Now; |
| | | throw new TimeoutException($"[SCPI] 查询超时或失败: {errMsg}"); |
| | | } |
| | | |
| | | Log.Debug($"[SCPI] 设备 {DeviceId} 查询响应: {response} (延迟 {_lastLatencyMs:F2}ms)"); |
| | | return Task.FromResult(response); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[SCPI] 设备 {DeviceId} 查询失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public DeviceDriverStatus GetStatus() |
| | | { |
| | | return new DeviceDriverStatus |
| | | { |
| | | IsConnected = IsConnected, |
| | | IsHealthy = IsConnected && string.IsNullOrEmpty(_lastError), |
| | | LastError = _lastError, |
| | | LastErrorTime = _lastErrorTime, |
| | | LatencyMs = _lastLatencyMs, |
| | | ReconnectCount = _reconnectCount |
| | | }; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public void Dispose() |
| | | { |
| | | if (_disposed) return; |
| | | _disposed = true; |
| | | |
| | | try |
| | | { |
| | | _client?.Dispose(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[SCPI] 设备 {DeviceId} 释放资源异常: {ex.Message}", ex); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 触发连接状态变化事件 |
| | | /// </summary> |
| | | private void OnConnectionChanged(bool isConnected, string reason) |
| | | { |
| | | ConnectionChanged?.Invoke(this, new DeviceConnectionEventArgs |
| | | { |
| | | DeviceId = DeviceId, |
| | | IsConnected = isConnected, |
| | | Reason = reason |
| | | }); |
| | | |
| | | if (!isConnected) |
| | | { |
| | | _reconnectCount++; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查是否已释放 |
| | | /// </summary> |
| | | private void ThrowIfDisposed() |
| | | { |
| | | if (_disposed) throw new ObjectDisposedException(nameof(ScpiDriverAdapter)); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Diagnostics; |
| | | using System.Net.Sockets; |
| | | using System.Text; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | using Aging.Infrastructure.Contracts; |
| | | using ConnectionUtil; |
| | | using log4net; |
| | | |
| | | namespace Aging.Infrastructure.Drivers |
| | | { |
| | | /// <summary> |
| | | /// TCP 原始通信驱动适配器。 |
| | | /// 封装 ConnectionUtil.TcpClientWrapper,实现统一的 IDeviceDriver 接口。 |
| | | /// </summary> |
| | | public sealed class TcpRawDriverAdapter : IDeviceDriver |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(TcpRawDriverAdapter)); |
| | | |
| | | private readonly string _ip; |
| | | private readonly int _port; |
| | | private TcpClientWrapper? _client; |
| | | private bool _disposed; |
| | | private readonly Stopwatch _latencyStopwatch = new Stopwatch(); |
| | | private double _lastLatencyMs; |
| | | private string _lastError = string.Empty; |
| | | private DateTime? _lastErrorTime; |
| | | private int _reconnectCount; |
| | | |
| | | /// <inheritdoc/> |
| | | public string DeviceId { get; } |
| | | |
| | | /// <inheritdoc/> |
| | | public bool IsConnected => _client?.IsConnected ?? false; |
| | | |
| | | /// <inheritdoc/> |
| | | public event EventHandler<DeviceConnectionEventArgs>? ConnectionChanged; |
| | | |
| | | /// <summary> |
| | | /// 初始化 TCP 原始通信驱动适配器 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备唯一标识符</param> |
| | | /// <param name="ip">目标 IP 地址</param> |
| | | /// <param name="port">目标端口号</param> |
| | | public TcpRawDriverAdapter(string deviceId, string ip, int port) |
| | | { |
| | | DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId)); |
| | | _ip = ip ?? throw new ArgumentNullException(nameof(ip)); |
| | | _port = port; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task ConnectAsync(CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[TCP] 设备 {DeviceId} 正在连接 {_ip}:{_port}..."); |
| | | |
| | | try |
| | | { |
| | | _client = new TcpClientWrapper(_ip, _port); |
| | | bool success = await _client.ConnectAsync(); |
| | | |
| | | if (!success) |
| | | { |
| | | _lastError = "连接失败"; |
| | | _lastErrorTime = DateTime.Now; |
| | | throw new InvalidOperationException($"[TCP] 设备 {DeviceId} 连接 {_ip}:{_port} 失败"); |
| | | } |
| | | |
| | | _lastError = string.Empty; |
| | | Log.Info($"[TCP] 设备 {DeviceId} 已成功连接到 {_ip}:{_port}"); |
| | | OnConnectionChanged(true, "连接成功"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[TCP] 设备 {DeviceId} 连接异常: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public Task DisconnectAsync() |
| | | { |
| | | ThrowIfDisposed(); |
| | | Log.Info($"[TCP] 设备 {DeviceId} 正在断开连接..."); |
| | | |
| | | try |
| | | { |
| | | if (_client != null) |
| | | { |
| | | _client?.Close(); |
| | | _client = null; |
| | | } |
| | | |
| | | Log.Info($"[TCP] 设备 {DeviceId} 已断开连接"); |
| | | OnConnectionChanged(false, "主动断开"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[TCP] 设备 {DeviceId} 断开连接异常: {ex.Message}", ex); |
| | | } |
| | | |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task<string> SendCommandAsync(string command, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[TCP] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[TCP] 设备 {DeviceId} 发送命令: {command}"); |
| | | string response = await _client.SendMessageAsync(command); |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | Log.Debug($"[TCP] 设备 {DeviceId} 收到响应: {response} (延迟 {_lastLatencyMs:F2}ms)"); |
| | | return response; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[TCP] 设备 {DeviceId} 发送命令失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task<string> QueryAsync(string query, CancellationToken ct) |
| | | { |
| | | ThrowIfDisposed(); |
| | | if (!IsConnected) throw new InvalidOperationException("[TCP] 设备未连接"); |
| | | |
| | | _latencyStopwatch.Restart(); |
| | | try |
| | | { |
| | | Log.Debug($"[TCP] 设备 {DeviceId} 发送查询: {query}"); |
| | | // 使用较短超时进行查询操作 |
| | | string response = await _client.SendMessageAsync(query, TimeSpan.FromSeconds(5)); |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | Log.Debug($"[TCP] 设备 {DeviceId} 查询响应: {response} (延迟 {_lastLatencyMs:F2}ms)"); |
| | | return response; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _latencyStopwatch.Stop(); |
| | | _lastLatencyMs = _latencyStopwatch.Elapsed.TotalMilliseconds; |
| | | _lastError = ex.Message; |
| | | _lastErrorTime = DateTime.Now; |
| | | Log.Error($"[TCP] 设备 {DeviceId} 查询失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public DeviceDriverStatus GetStatus() |
| | | { |
| | | return new DeviceDriverStatus |
| | | { |
| | | IsConnected = IsConnected, |
| | | IsHealthy = IsConnected && string.IsNullOrEmpty(_lastError), |
| | | LastError = _lastError, |
| | | LastErrorTime = _lastErrorTime, |
| | | LatencyMs = _lastLatencyMs, |
| | | ReconnectCount = _reconnectCount |
| | | }; |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public void Dispose() |
| | | { |
| | | if (_disposed) return; |
| | | _disposed = true; |
| | | |
| | | try |
| | | { |
| | | _client?.Dispose(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[TCP] 设备 {DeviceId} 释放资源异常: {ex.Message}", ex); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 触发连接状态变化事件 |
| | | /// </summary> |
| | | private void OnConnectionChanged(bool isConnected, string reason) |
| | | { |
| | | ConnectionChanged?.Invoke(this, new DeviceConnectionEventArgs |
| | | { |
| | | DeviceId = DeviceId, |
| | | IsConnected = isConnected, |
| | | Reason = reason |
| | | }); |
| | | |
| | | if (!isConnected) |
| | | { |
| | | _reconnectCount++; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查是否已释放 |
| | | /// </summary> |
| | | private void ThrowIfDisposed() |
| | | { |
| | | if (_disposed) throw new ObjectDisposedException(nameof(TcpRawDriverAdapter)); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using log4net; |
| | | using HMI.Core.Util; |
| | | using Aging.Infrastructure.Contracts; |
| | | using Aging.Infrastructure.Core; |
| | | using Aging.Infrastructure.Services; |
| | | using Aging.Infrastructure.Drivers; |
| | | |
| | | namespace Aging.Infrastructure |
| | | { |
| | | /// <summary> |
| | | /// 基础设施启动引导 - 初始化并注册所有核心服务到 UtilContainer |
| | | /// </summary> |
| | | public static class InfrastructureBootstrapper |
| | | { |
| | | private static readonly ILog log = LogManager.GetLogger(typeof(InfrastructureBootstrapper)); |
| | | private static bool _initialized = false; |
| | | |
| | | /// <summary> |
| | | /// 初始化基础设施服务 |
| | | /// 应在 WPF 主程序启动时或插件 InitFinish 中调用 |
| | | /// </summary> |
| | | /// <param name="deviceConfigDbPath">DeviceConfig 插件的 SQLite 数据库路径</param> |
| | | public static void Initialize(string deviceConfigDbPath) |
| | | { |
| | | if (_initialized) |
| | | { |
| | | log.Warn("基础设施已初始化,跳过重复初始化"); |
| | | return; |
| | | } |
| | | |
| | | try |
| | | { |
| | | log.Info("开始初始化基础设施服务..."); |
| | | |
| | | // 1. 创建 ResourceManager 单例 |
| | | var resourceManager = new ResourceManager(); |
| | | UtilContainer.RegisterUtil("ResourceManager", resourceManager); |
| | | log.Info("ResourceManager 已注册"); |
| | | |
| | | // 2. 创建 DeviceManager 单例 |
| | | var deviceManager = new DeviceManager(); |
| | | UtilContainer.RegisterUtil("DeviceManager", deviceManager); |
| | | log.Info("DeviceManager 已注册"); |
| | | |
| | | // 3. 创建 SlotDeviceMappingResolver(需要 DeviceConfig DB 路径) |
| | | var mappingResolver = new SlotDeviceMappingResolver(deviceConfigDbPath); |
| | | UtilContainer.RegisterUtil("SlotDeviceMappingResolver", mappingResolver); |
| | | log.Info("SlotDeviceMappingResolver 已注册"); |
| | | |
| | | // 4. 创建 DeviceService |
| | | var deviceService = new DeviceService(resourceManager, deviceManager, mappingResolver); |
| | | UtilContainer.RegisterUtil("DeviceService", deviceService); |
| | | log.Info("DeviceService 已注册"); |
| | | |
| | | // 5. 创建 PowerSupplyService |
| | | var powerSupplyService = new PowerSupplyService(resourceManager, deviceManager, mappingResolver); |
| | | UtilContainer.RegisterUtil("PowerSupplyService", powerSupplyService); |
| | | log.Info("PowerSupplyService 已注册"); |
| | | |
| | | // 6. 从数据库加载设备定义并注册驱动 |
| | | LoadDevicesFromConfig(deviceManager, mappingResolver); |
| | | |
| | | _initialized = true; |
| | | log.Info("基础设施服务初始化完成"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("基础设施初始化失败", ex); |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 从 DeviceConfig 数据库加载设备定义并注册驱动 |
| | | /// </summary> |
| | | private static void LoadDevicesFromConfig(IDeviceManager deviceManager, SlotDeviceMappingResolver mappingResolver) |
| | | { |
| | | try |
| | | { |
| | | // 读取所有设备映射 |
| | | var mappings = mappingResolver.GetAllMappings(); |
| | | // 从映射中提取唯一的设备ID |
| | | var deviceIds = mappings |
| | | .Select(m => m.DeviceId) |
| | | .Distinct() |
| | | .ToList(); |
| | | |
| | | int loaded = 0; |
| | | foreach (var deviceId in deviceIds) |
| | | { |
| | | try |
| | | { |
| | | // 这里暂时使用通用 TCP 驱动作为占位 |
| | | // 实际应该从 device_definition 表读取 transport 和 endpoint |
| | | // 然后通过 DriverFactory.CreateDriver 创建对应驱动 |
| | | // |
| | | // TODO: 从数据库读取设备详细信息(transport, endpoint) |
| | | // var device = GetDeviceDefinition(deviceId); |
| | | // var driver = DriverFactory.CreateDriver(deviceId, device.Transport, device.Endpoint); |
| | | |
| | | log.Debug($"设备 {deviceId} 驱动注册待配置详细信息"); |
| | | loaded++; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"注册设备 {deviceId} 驱动失败: {ex.Message}", ex); |
| | | } |
| | | } |
| | | |
| | | log.Info($"从设备配置加载了 {loaded} 个设备驱动"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("加载设备配置失败", ex); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 注册单个设备驱动(供外部调用,用于动态添加设备) |
| | | /// </summary> |
| | | public static void RegisterDevice(string deviceId, string transport, string endpoint) |
| | | { |
| | | var container = new UtilContainer(); |
| | | var deviceManager = container.GetUtil<IDeviceManager>("DeviceManager"); |
| | | if (deviceManager == null) |
| | | { |
| | | log.Error("DeviceManager 未初始化"); |
| | | return; |
| | | } |
| | | |
| | | try |
| | | { |
| | | var driver = DriverFactory.CreateDriver(deviceId, transport, endpoint); |
| | | deviceManager.RegisterDriver(deviceId, driver); |
| | | log.Info($"设备 {deviceId} ({transport}) 驱动已注册"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"注册设备 {deviceId} 失败: {ex.Message}", ex); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 重置基础设施(用于测试或重新初始化) |
| | | /// </summary> |
| | | public static void Reset() |
| | | { |
| | | _initialized = false; |
| | | log.Info("基础设施已重置"); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Diagnostics; |
| | | using Aging.Infrastructure.Contracts; |
| | | using log4net; |
| | | |
| | | namespace Aging.Infrastructure.Services |
| | | { |
| | | /// <summary> |
| | | /// 设备服务实现。 |
| | | /// 协调 ResourceManager、DeviceManager 和 SlotMapping, |
| | | /// 提供面向 DeviceId 的命令执行、查询和安全关机功能。 |
| | | /// 所有操作均需通过资源预约验证和租约管理,确保硬件资源的安全访问。 |
| | | /// </summary> |
| | | public sealed class DeviceService : IDeviceService |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(DeviceService)); |
| | | |
| | | /// <summary> |
| | | /// 资源管理器 |
| | | /// </summary> |
| | | private readonly IResourceManager _resourceManager; |
| | | |
| | | /// <summary> |
| | | /// 设备管理器 |
| | | /// </summary> |
| | | private readonly IDeviceManager _deviceManager; |
| | | |
| | | /// <summary> |
| | | /// Slot-设备-通道映射解析器 |
| | | /// </summary> |
| | | private readonly SlotDeviceMappingResolver _slotMappingResolver; |
| | | |
| | | /// <summary> |
| | | /// 初始化设备服务实例 |
| | | /// </summary> |
| | | /// <param name="resourceManager">资源管理器</param> |
| | | /// <param name="deviceManager">设备管理器</param> |
| | | /// <param name="slotMappingResolver">Slot 映射解析器</param> |
| | | public DeviceService( |
| | | IResourceManager resourceManager, |
| | | IDeviceManager deviceManager, |
| | | SlotDeviceMappingResolver slotMappingResolver) |
| | | { |
| | | _resourceManager = resourceManager ?? throw new ArgumentNullException(nameof(resourceManager)); |
| | | _deviceManager = deviceManager ?? throw new ArgumentNullException(nameof(deviceManager)); |
| | | _slotMappingResolver = slotMappingResolver ?? throw new ArgumentNullException(nameof(slotMappingResolver)); |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task<DeviceCommandResult> ExecuteCommandAsync( |
| | | string deviceId, string command, ResourceOwnerToken owner, CancellationToken ct) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentException("设备ID不能为空", nameof(deviceId)); |
| | | if (string.IsNullOrWhiteSpace(command)) |
| | | throw new ArgumentException("命令不能为空", nameof(command)); |
| | | if (owner == null) |
| | | throw new ArgumentNullException(nameof(owner)); |
| | | |
| | | var stopwatch = Stopwatch.StartNew(); |
| | | var resourceKey = new ResourceKey(deviceId, "Generic"); |
| | | |
| | | try |
| | | { |
| | | Log.Info($"[DeviceService] 执行命令: Device={deviceId}, Command={command}, Owner={owner.OwnerId}"); |
| | | |
| | | // 1. 获取设备驱动 |
| | | var driver = _deviceManager.GetDriver(deviceId); |
| | | if (driver == null) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[DeviceService] 设备 {deviceId} 驱动不存在"); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "DRIVER_NOT_FOUND", |
| | | ErrorMessage = $"设备 {deviceId} 的驱动未注册", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | |
| | | // 2. 验证预约 |
| | | if (!_resourceManager.ValidateOwner(owner, resourceKey)) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[DeviceService] 权限验证失败: Device={deviceId}, Owner={owner.OwnerId}"); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "PERMISSION_DENIED", |
| | | ErrorMessage = $"所有者 {owner.OwnerId} 未预约设备 {deviceId} 或令牌已过期", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | |
| | | // 3. 获取租约并执行命令 |
| | | using var lease = await _resourceManager.AcquireOperationAsync(owner, resourceKey, ct).ConfigureAwait(false); |
| | | |
| | | var response = await driver.SendCommandAsync(command, ct).ConfigureAwait(false); |
| | | |
| | | stopwatch.Stop(); |
| | | Log.Info($"[DeviceService] 命令执行成功: Device={deviceId}, Command={command}, 延迟={stopwatch.Elapsed.TotalMilliseconds:F2}ms"); |
| | | |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = true, |
| | | RawResponse = response, |
| | | Readback = response, |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | catch (ResourceConflictException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[DeviceService] 资源冲突: Device={deviceId}, 错误: {ex.Message}", ex); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "RESOURCE_CONFLICT", |
| | | ErrorMessage = ex.Message, |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[DeviceService] 命令执行被取消: Device={deviceId}"); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "OPERATION_CANCELLED", |
| | | ErrorMessage = "操作已被取消", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | catch (TimeoutException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[DeviceService] 命令执行超时: Device={deviceId}, 错误: {ex.Message}", ex); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "TIMEOUT", |
| | | ErrorMessage = $"设备 {deviceId} 命令执行超时: {ex.Message}", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[DeviceService] 命令执行异常: Device={deviceId}, 错误: {ex.Message}", ex); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "UNKNOWN_ERROR", |
| | | ErrorMessage = $"设备 {deviceId} 命令执行失败: {ex.Message}", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task<DeviceCommandResult> ExecuteQueryAsync( |
| | | string deviceId, string query, ResourceOwnerToken owner, CancellationToken ct) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentException("设备ID不能为空", nameof(deviceId)); |
| | | if (string.IsNullOrWhiteSpace(query)) |
| | | throw new ArgumentException("查询命令不能为空", nameof(query)); |
| | | if (owner == null) |
| | | throw new ArgumentNullException(nameof(owner)); |
| | | |
| | | var stopwatch = Stopwatch.StartNew(); |
| | | var resourceKey = new ResourceKey(deviceId, "Generic"); |
| | | |
| | | try |
| | | { |
| | | Log.Info($"[DeviceService] 执行查询: Device={deviceId}, Query={query}, Owner={owner.OwnerId}"); |
| | | |
| | | // 1. 获取设备驱动 |
| | | var driver = _deviceManager.GetDriver(deviceId); |
| | | if (driver == null) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[DeviceService] 设备 {deviceId} 驱动不存在"); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "DRIVER_NOT_FOUND", |
| | | ErrorMessage = $"设备 {deviceId} 的驱动未注册", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | |
| | | // 2. 验证预约 |
| | | if (!_resourceManager.ValidateOwner(owner, resourceKey)) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[DeviceService] 权限验证失败: Device={deviceId}, Owner={owner.OwnerId}"); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "PERMISSION_DENIED", |
| | | ErrorMessage = $"所有者 {owner.OwnerId} 未预约设备 {deviceId} 或令牌已过期", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | |
| | | // 3. 获取租约并执行查询 |
| | | using var lease = await _resourceManager.AcquireOperationAsync(owner, resourceKey, ct).ConfigureAwait(false); |
| | | |
| | | var response = await driver.QueryAsync(query, ct).ConfigureAwait(false); |
| | | |
| | | stopwatch.Stop(); |
| | | Log.Info($"[DeviceService] 查询执行成功: Device={deviceId}, Query={query}, 延迟={stopwatch.Elapsed.TotalMilliseconds:F2}ms"); |
| | | |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = true, |
| | | RawResponse = response, |
| | | Readback = response, |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | catch (ResourceConflictException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[DeviceService] 资源冲突: Device={deviceId}, 错误: {ex.Message}", ex); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "RESOURCE_CONFLICT", |
| | | ErrorMessage = ex.Message, |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[DeviceService] 查询执行被取消: Device={deviceId}"); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "OPERATION_CANCELLED", |
| | | ErrorMessage = "操作已被取消", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | catch (TimeoutException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[DeviceService] 查询执行超时: Device={deviceId}, 错误: {ex.Message}", ex); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "TIMEOUT", |
| | | ErrorMessage = $"设备 {deviceId} 查询执行超时: {ex.Message}", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[DeviceService] 查询执行异常: Device={deviceId}, 错误: {ex.Message}", ex); |
| | | return new DeviceCommandResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = "UNKNOWN_ERROR", |
| | | ErrorMessage = $"设备 {deviceId} 查询执行失败: {ex.Message}", |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public async Task SafeShutdownAsync(string deviceId, ResourceOwnerToken owner, CancellationToken ct) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(deviceId)) |
| | | throw new ArgumentException("设备ID不能为空", nameof(deviceId)); |
| | | if (owner == null) |
| | | throw new ArgumentNullException(nameof(owner)); |
| | | |
| | | var resourceKey = new ResourceKey(deviceId, "Generic"); |
| | | |
| | | try |
| | | { |
| | | Log.Info($"[DeviceService] 开始安全关机: Device={deviceId}, Owner={owner.OwnerId}"); |
| | | |
| | | // 1. 获取驱动 |
| | | var driver = _deviceManager.GetDriver(deviceId); |
| | | if (driver == null) |
| | | { |
| | | Log.Warn($"[DeviceService] 安全关机失败: 设备 {deviceId} 驱动不存在"); |
| | | return; |
| | | } |
| | | |
| | | // 2. 验证预约 |
| | | if (!_resourceManager.ValidateOwner(owner, resourceKey)) |
| | | { |
| | | Log.Warn($"[DeviceService] 安全关机权限验证失败: Device={deviceId}, Owner={owner.OwnerId}"); |
| | | return; |
| | | } |
| | | |
| | | // 3. 获取租约 |
| | | using var lease = await _resourceManager.AcquireOperationAsync(owner, resourceKey, ct).ConfigureAwait(false); |
| | | |
| | | // 4. 发送安全关闭命令 |
| | | Log.Info($"[DeviceService] 发送安全关闭命令: Device={deviceId}"); |
| | | await driver.SendCommandAsync("OUTP OFF", ct).ConfigureAwait(false); |
| | | |
| | | // 5. 等待确认(短暂延迟确保命令执行完成) |
| | | await Task.Delay(500, ct).ConfigureAwait(false); |
| | | |
| | | // 6. 验证关闭状态 |
| | | var statusResponse = await driver.QueryAsync("OUTP?", ct).ConfigureAwait(false); |
| | | Log.Info($"[DeviceService] 设备 {deviceId} 关闭状态确认: {statusResponse}"); |
| | | |
| | | Log.Info($"[DeviceService] 安全关机完成: Device={deviceId}"); |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | Log.Warn($"[DeviceService] 安全关机操作被取消: Device={deviceId}"); |
| | | throw; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[DeviceService] 安全关机异常: Device={deviceId}, 错误: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Diagnostics; |
| | | using Aging.Infrastructure.Contracts; |
| | | using log4net; |
| | | |
| | | namespace Aging.Infrastructure.Services |
| | | { |
| | | /// <summary> |
| | | /// 电源配置命令 |
| | | /// </summary> |
| | | public sealed class PowerCommand |
| | | { |
| | | /// <summary> |
| | | /// 目标电压值(可选,单位: V) |
| | | /// </summary> |
| | | public double? Voltage { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 目标电流限制值(可选,单位: A) |
| | | /// </summary> |
| | | public double? CurrentLimit { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 输出开关状态(true=ON, false=OFF, null=不改变) |
| | | /// </summary> |
| | | public bool? Output { get; set; } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 电源操作结果 |
| | | /// </summary> |
| | | public sealed class PowerOperationResult |
| | | { |
| | | /// <summary> |
| | | /// 操作是否成功 |
| | | /// </summary> |
| | | public bool Success { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 实测电压值(单位: V) |
| | | /// </summary> |
| | | public double? MeasuredVoltage { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 实测电流值(单位: A) |
| | | /// </summary> |
| | | public double? MeasuredCurrent { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 设备原始响应字符串 |
| | | /// </summary> |
| | | public string? RawResponse { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 错误代码 |
| | | /// </summary> |
| | | public string? ErrorCode { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 错误信息 |
| | | /// </summary> |
| | | public string? ErrorMessage { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 操作延迟(毫秒) |
| | | /// </summary> |
| | | public double LatencyMs { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 创建一个成功的结果 |
| | | /// </summary> |
| | | public static PowerOperationResult Succeed(double? voltage, double? current, string? rawResponse, double latencyMs) |
| | | { |
| | | return new PowerOperationResult |
| | | { |
| | | Success = true, |
| | | MeasuredVoltage = voltage, |
| | | MeasuredCurrent = current, |
| | | RawResponse = rawResponse, |
| | | LatencyMs = latencyMs |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 创建一个失败的结果 |
| | | /// </summary> |
| | | public static PowerOperationResult Failed(string errorCode, string errorMessage, double latencyMs) |
| | | { |
| | | return new PowerOperationResult |
| | | { |
| | | Success = false, |
| | | ErrorCode = errorCode, |
| | | ErrorMessage = errorMessage, |
| | | LatencyMs = latencyMs |
| | | }; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 电源配置服务 - 面向 Slot 的高层电源操作。 |
| | | /// 通过 SlotDeviceMappingResolver 解析 Slot 到具体设备端点的映射, |
| | | /// 协调 ResourceManager 和 DeviceManager 完成电源配置、测量和状态查询。 |
| | | /// </summary> |
| | | public sealed class PowerSupplyService |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(PowerSupplyService)); |
| | | |
| | | /// <summary> |
| | | /// 资源管理器 |
| | | /// </summary> |
| | | private readonly IResourceManager _resourceManager; |
| | | |
| | | /// <summary> |
| | | /// 设备管理器 |
| | | /// </summary> |
| | | private readonly IDeviceManager _deviceManager; |
| | | |
| | | /// <summary> |
| | | /// Slot-设备-通道映射解析器 |
| | | /// </summary> |
| | | private readonly SlotDeviceMappingResolver _slotMappingResolver; |
| | | |
| | | /// <summary> |
| | | /// 初始化电源配置服务实例 |
| | | /// </summary> |
| | | /// <param name="resourceManager">资源管理器</param> |
| | | /// <param name="deviceManager">设备管理器</param> |
| | | /// <param name="slotMappingResolver">Slot 映射解析器</param> |
| | | public PowerSupplyService( |
| | | IResourceManager resourceManager, |
| | | IDeviceManager deviceManager, |
| | | SlotDeviceMappingResolver slotMappingResolver) |
| | | { |
| | | _resourceManager = resourceManager ?? throw new ArgumentNullException(nameof(resourceManager)); |
| | | _deviceManager = deviceManager ?? throw new ArgumentNullException(nameof(deviceManager)); |
| | | _slotMappingResolver = slotMappingResolver ?? throw new ArgumentNullException(nameof(slotMappingResolver)); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 异步配置 Slot 对应的电源参数。 |
| | | /// 根据 SlotId 解析映射获取设备端点,验证资源预约后发送 SCPI 命令配置电源。 |
| | | /// </summary> |
| | | /// <param name="slotId">Slot 标识符</param> |
| | | /// <param name="command">电源配置命令</param> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | /// <returns>电源操作结果,包含测量值</returns> |
| | | public async Task<PowerOperationResult> ConfigureAsync( |
| | | string slotId, PowerCommand command, ResourceOwnerToken owner, CancellationToken ct) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(slotId)) |
| | | throw new ArgumentException("SlotId 不能为空", nameof(slotId)); |
| | | if (command == null) |
| | | throw new ArgumentNullException(nameof(command)); |
| | | if (owner == null) |
| | | throw new ArgumentNullException(nameof(owner)); |
| | | |
| | | var stopwatch = Stopwatch.StartNew(); |
| | | |
| | | try |
| | | { |
| | | Log.Info($"[PowerSupplyService] 配置电源: Slot={slotId}, Owner={owner.OwnerId}"); |
| | | |
| | | // 1. 解析 Slot 映射 |
| | | var endpoint = _slotMappingResolver.ResolveEndpoint(slotId, "PowerSupply"); |
| | | if (endpoint == null) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] Slot {slotId} 未找到 PowerSupply 映射"); |
| | | return PowerOperationResult.Failed("MAPPING_NOT_FOUND", |
| | | $"Slot {slotId} 未找到 PowerSupply 类型的设备映射", stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | |
| | | var resourceKey = endpoint.ToResourceKey(); |
| | | |
| | | // 2. 验证预约 |
| | | if (!_resourceManager.ValidateOwner(owner, resourceKey)) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] 权限验证失败: Slot={slotId}, Device={endpoint.DeviceId}, Owner={owner.OwnerId}"); |
| | | return PowerOperationResult.Failed("PERMISSION_DENIED", |
| | | $"所有者 {owner.OwnerId} 未预约 Slot {slotId} 对应的设备 {endpoint.DeviceId} 或令牌已过期", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | |
| | | // 3. 获取租约 |
| | | using var lease = await _resourceManager.AcquireOperationAsync(owner, resourceKey, ct).ConfigureAwait(false); |
| | | |
| | | // 4. 获取设备驱动 |
| | | var driver = _deviceManager.GetDriver(endpoint.DeviceId); |
| | | if (driver == null) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] 设备 {endpoint.DeviceId} 驱动不存在"); |
| | | return PowerOperationResult.Failed("DRIVER_NOT_FOUND", |
| | | $"设备 {endpoint.DeviceId} 的驱动未注册", stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | |
| | | // 5. 构建并发送 SCPI 命令 |
| | | var scpiCommands = BuildScpiCommands(command); |
| | | foreach (var scpiCommand in scpiCommands) |
| | | { |
| | | Log.Info($"[PowerSupplyService] 发送 SCPI 命令: Device={endpoint.DeviceId}, Command={scpiCommand}"); |
| | | await driver.SendCommandAsync(scpiCommand, ct).ConfigureAwait(false); |
| | | } |
| | | |
| | | // 6. 回读测量值 |
| | | string rawResponse; |
| | | try |
| | | { |
| | | // 优先尝试 MEAS:ALL? 一次性获取所有测量值 |
| | | rawResponse = await driver.QueryAsync("MEAS:ALL?", ct).ConfigureAwait(false); |
| | | Log.Info($"[PowerSupplyService] MEAS:ALL? 响应: {rawResponse}"); |
| | | } |
| | | catch |
| | | { |
| | | // 如果 MEAS:ALL? 失败,分别查询电压和电流 |
| | | Log.Info($"[PowerSupplyService] MEAS:ALL? 失败,尝试分别查询 VOLT? 和 CURR?"); |
| | | var voltageResponse = await driver.QueryAsync("VOLT?", ct).ConfigureAwait(false); |
| | | var currentResponse = await driver.QueryAsync("CURR?", ct).ConfigureAwait(false); |
| | | rawResponse = $"{voltageResponse},{currentResponse}"; |
| | | } |
| | | |
| | | // 7. 解析回读值 |
| | | ParseMeasurementResponse(rawResponse, out var measuredVoltage, out var measuredCurrent); |
| | | |
| | | stopwatch.Stop(); |
| | | Log.Info($"[PowerSupplyService] 电源配置完成: Slot={slotId}, Device={endpoint.DeviceId}, " + |
| | | $"V={measuredVoltage}, I={measuredCurrent}, 延迟={stopwatch.Elapsed.TotalMilliseconds:F2}ms"); |
| | | |
| | | return PowerOperationResult.Succeed(measuredVoltage, measuredCurrent, rawResponse, |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (ResourceConflictException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[PowerSupplyService] 资源冲突: Slot={slotId}, 错误: {ex.Message}", ex); |
| | | return PowerOperationResult.Failed("RESOURCE_CONFLICT", ex.Message, stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] 电源配置被取消: Slot={slotId}"); |
| | | return PowerOperationResult.Failed("OPERATION_CANCELLED", "操作已被取消", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (TimeoutException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[PowerSupplyService] 电源配置超时: Slot={slotId}, 错误: {ex.Message}", ex); |
| | | return PowerOperationResult.Failed("TIMEOUT", $"电源配置超时: {ex.Message}", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[PowerSupplyService] 电源配置异常: Slot={slotId}, 错误: {ex.Message}", ex); |
| | | return PowerOperationResult.Failed("UNKNOWN_ERROR", $"电源配置失败: {ex.Message}", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 异步测量 Slot 对应电源的输出值(电压、电流)。 |
| | | /// 根据 SlotId 解析映射获取设备端点,查询实时测量值。 |
| | | /// </summary> |
| | | /// <param name="slotId">Slot 标识符</param> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | /// <returns>电源操作结果,包含测量值</returns> |
| | | public async Task<PowerOperationResult> MeasureAsync( |
| | | string slotId, ResourceOwnerToken owner, CancellationToken ct) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(slotId)) |
| | | throw new ArgumentException("SlotId 不能为空", nameof(slotId)); |
| | | if (owner == null) |
| | | throw new ArgumentNullException(nameof(owner)); |
| | | |
| | | var stopwatch = Stopwatch.StartNew(); |
| | | |
| | | try |
| | | { |
| | | Log.Info($"[PowerSupplyService] 测量电源: Slot={slotId}, Owner={owner.OwnerId}"); |
| | | |
| | | // 1. 解析 Slot 映射 |
| | | var endpoint = _slotMappingResolver.ResolveEndpoint(slotId, "PowerSupply"); |
| | | if (endpoint == null) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] Slot {slotId} 未找到 PowerSupply 映射"); |
| | | return PowerOperationResult.Failed("MAPPING_NOT_FOUND", |
| | | $"Slot {slotId} 未找到 PowerSupply 类型的设备映射", stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | |
| | | var resourceKey = endpoint.ToResourceKey(); |
| | | |
| | | // 2. 验证预约 |
| | | if (!_resourceManager.ValidateOwner(owner, resourceKey)) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] 权限验证失败: Slot={slotId}, Device={endpoint.DeviceId}, Owner={owner.OwnerId}"); |
| | | return PowerOperationResult.Failed("PERMISSION_DENIED", |
| | | $"所有者 {owner.OwnerId} 未预约 Slot {slotId} 对应的设备 {endpoint.DeviceId} 或令牌已过期", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | |
| | | // 3. 获取租约 |
| | | using var lease = await _resourceManager.AcquireOperationAsync(owner, resourceKey, ct).ConfigureAwait(false); |
| | | |
| | | // 4. 获取设备驱动 |
| | | var driver = _deviceManager.GetDriver(endpoint.DeviceId); |
| | | if (driver == null) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] 设备 {endpoint.DeviceId} 驱动不存在"); |
| | | return PowerOperationResult.Failed("DRIVER_NOT_FOUND", |
| | | $"设备 {endpoint.DeviceId} 的驱动未注册", stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | |
| | | // 5. 查询测量值 |
| | | string rawResponse; |
| | | try |
| | | { |
| | | rawResponse = await driver.QueryAsync("MEAS:ALL?", ct).ConfigureAwait(false); |
| | | } |
| | | catch |
| | | { |
| | | var voltageResponse = await driver.QueryAsync("VOLT?", ct).ConfigureAwait(false); |
| | | var currentResponse = await driver.QueryAsync("CURR?", ct).ConfigureAwait(false); |
| | | rawResponse = $"{voltageResponse},{currentResponse}"; |
| | | } |
| | | |
| | | // 6. 解析回读值 |
| | | ParseMeasurementResponse(rawResponse, out var measuredVoltage, out var measuredCurrent); |
| | | |
| | | stopwatch.Stop(); |
| | | Log.Info($"[PowerSupplyService] 测量完成: Slot={slotId}, Device={endpoint.DeviceId}, " + |
| | | $"V={measuredVoltage}, I={measuredCurrent}, 延迟={stopwatch.Elapsed.TotalMilliseconds:F2}ms"); |
| | | |
| | | return PowerOperationResult.Succeed(measuredVoltage, measuredCurrent, rawResponse, |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (ResourceConflictException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[PowerSupplyService] 资源冲突: Slot={slotId}, 错误: {ex.Message}", ex); |
| | | return PowerOperationResult.Failed("RESOURCE_CONFLICT", ex.Message, stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] 测量操作被取消: Slot={slotId}"); |
| | | return PowerOperationResult.Failed("OPERATION_CANCELLED", "操作已被取消", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (TimeoutException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[PowerSupplyService] 测量操作超时: Slot={slotId}, 错误: {ex.Message}", ex); |
| | | return PowerOperationResult.Failed("TIMEOUT", $"测量操作超时: {ex.Message}", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[PowerSupplyService] 测量操作异常: Slot={slotId}, 错误: {ex.Message}", ex); |
| | | return PowerOperationResult.Failed("UNKNOWN_ERROR", $"测量操作失败: {ex.Message}", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 异步获取 Slot 对应电源的当前状态(电压、电流、输出开关状态)。 |
| | | /// </summary> |
| | | /// <param name="slotId">Slot 标识符</param> |
| | | /// <param name="owner">所有者令牌</param> |
| | | /// <param name="ct">取消令牌</param> |
| | | /// <returns>电源操作结果,包含状态信息</returns> |
| | | public async Task<PowerOperationResult> GetPowerStatusAsync( |
| | | string slotId, ResourceOwnerToken owner, CancellationToken ct) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(slotId)) |
| | | throw new ArgumentException("SlotId 不能为空", nameof(slotId)); |
| | | if (owner == null) |
| | | throw new ArgumentNullException(nameof(owner)); |
| | | |
| | | var stopwatch = Stopwatch.StartNew(); |
| | | |
| | | try |
| | | { |
| | | Log.Info($"[PowerSupplyService] 查询电源状态: Slot={slotId}, Owner={owner.OwnerId}"); |
| | | |
| | | // 1. 解析 Slot 映射 |
| | | var endpoint = _slotMappingResolver.ResolveEndpoint(slotId, "PowerSupply"); |
| | | if (endpoint == null) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] Slot {slotId} 未找到 PowerSupply 映射"); |
| | | return PowerOperationResult.Failed("MAPPING_NOT_FOUND", |
| | | $"Slot {slotId} 未找到 PowerSupply 类型的设备映射", stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | |
| | | var resourceKey = endpoint.ToResourceKey(); |
| | | |
| | | // 2. 验证预约 |
| | | if (!_resourceManager.ValidateOwner(owner, resourceKey)) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] 权限验证失败: Slot={slotId}, Device={endpoint.DeviceId}, Owner={owner.OwnerId}"); |
| | | return PowerOperationResult.Failed("PERMISSION_DENIED", |
| | | $"所有者 {owner.OwnerId} 未预约 Slot {slotId} 对应的设备 {endpoint.DeviceId} 或令牌已过期", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | |
| | | // 3. 获取租约 |
| | | using var lease = await _resourceManager.AcquireOperationAsync(owner, resourceKey, ct).ConfigureAwait(false); |
| | | |
| | | // 4. 获取设备驱动 |
| | | var driver = _deviceManager.GetDriver(endpoint.DeviceId); |
| | | if (driver == null) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] 设备 {endpoint.DeviceId} 驱动不存在"); |
| | | return PowerOperationResult.Failed("DRIVER_NOT_FOUND", |
| | | $"设备 {endpoint.DeviceId} 的驱动未注册", stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | |
| | | // 5. 查询电压、电流和输出状态 |
| | | var voltageResponse = await driver.QueryAsync("VOLT?", ct).ConfigureAwait(false); |
| | | var currentResponse = await driver.QueryAsync("CURR?", ct).ConfigureAwait(false); |
| | | var outputResponse = await driver.QueryAsync("OUTP?", ct).ConfigureAwait(false); |
| | | |
| | | var rawResponse = $"VOLT={voltageResponse},CURR={currentResponse},OUTP={outputResponse}"; |
| | | |
| | | // 6. 解析测量值 |
| | | ParseMeasurementResponse($"{voltageResponse},{currentResponse}", |
| | | out var measuredVoltage, out var measuredCurrent); |
| | | |
| | | stopwatch.Stop(); |
| | | Log.Info($"[PowerSupplyService] 电源状态查询完成: Slot={slotId}, Device={endpoint.DeviceId}, " + |
| | | $"V={measuredVoltage}, I={measuredCurrent}, OUTP={outputResponse}, " + |
| | | $"延迟={stopwatch.Elapsed.TotalMilliseconds:F2}ms"); |
| | | |
| | | return new PowerOperationResult |
| | | { |
| | | Success = true, |
| | | MeasuredVoltage = measuredVoltage, |
| | | MeasuredCurrent = measuredCurrent, |
| | | RawResponse = rawResponse, |
| | | LatencyMs = stopwatch.Elapsed.TotalMilliseconds |
| | | }; |
| | | } |
| | | catch (ResourceConflictException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[PowerSupplyService] 资源冲突: Slot={slotId}, 错误: {ex.Message}", ex); |
| | | return PowerOperationResult.Failed("RESOURCE_CONFLICT", ex.Message, stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Warn($"[PowerSupplyService] 状态查询被取消: Slot={slotId}"); |
| | | return PowerOperationResult.Failed("OPERATION_CANCELLED", "操作已被取消", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (TimeoutException ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[PowerSupplyService] 状态查询超时: Slot={slotId}, 错误: {ex.Message}", ex); |
| | | return PowerOperationResult.Failed("TIMEOUT", $"状态查询超时: {ex.Message}", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | stopwatch.Stop(); |
| | | Log.Error($"[PowerSupplyService] 状态查询异常: Slot={slotId}, 错误: {ex.Message}", ex); |
| | | return PowerOperationResult.Failed("UNKNOWN_ERROR", $"状态查询失败: {ex.Message}", |
| | | stopwatch.Elapsed.TotalMilliseconds); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据电源配置命令构建 SCPI 命令列表 |
| | | /// </summary> |
| | | /// <param name="command">电源配置命令</param> |
| | | /// <returns>SCPI 命令字符串列表</returns> |
| | | private static List<string> BuildScpiCommands(PowerCommand command) |
| | | { |
| | | var commands = new List<string>(); |
| | | |
| | | if (command.Voltage.HasValue) |
| | | { |
| | | commands.Add($"VOLT {command.Voltage.Value:G}"); |
| | | } |
| | | |
| | | if (command.CurrentLimit.HasValue) |
| | | { |
| | | commands.Add($"CURR {command.CurrentLimit.Value:G}"); |
| | | } |
| | | |
| | | if (command.Output.HasValue) |
| | | { |
| | | commands.Add(command.Output.Value ? "OUTP ON" : "OUTP OFF"); |
| | | } |
| | | |
| | | return commands; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 解析设备返回的测量值响应字符串。 |
| | | /// 支持格式: "voltage,current" 或单个数值。 |
| | | /// </summary> |
| | | /// <param name="rawResponse">原始响应字符串</param> |
| | | /// <param name="voltage">解析后的电压值</param> |
| | | /// <param name="current">解析后的电流值</param> |
| | | private static void ParseMeasurementResponse(string rawResponse, out double? voltage, out double? current) |
| | | { |
| | | voltage = null; |
| | | current = null; |
| | | |
| | | if (string.IsNullOrWhiteSpace(rawResponse)) |
| | | return; |
| | | |
| | | try |
| | | { |
| | | var parts = rawResponse.Split(','); |
| | | |
| | | if (parts.Length >= 1 && double.TryParse(parts[0].Trim(), out var v)) |
| | | { |
| | | voltage = v; |
| | | } |
| | | |
| | | if (parts.Length >= 2 && double.TryParse(parts[1].Trim(), out var c)) |
| | | { |
| | | current = c; |
| | | } |
| | | } |
| | | catch (Exception) |
| | | { |
| | | // 解析失败时保持 null 值 |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Data; |
| | | using Aging.Infrastructure.Contracts; |
| | | using HMI.Core.Util; |
| | | using log4net; |
| | | |
| | | namespace Aging.Infrastructure.Services |
| | | { |
| | | /// <summary> |
| | | /// 电源端点信息,描述 Slot 映射到的具体设备通道 |
| | | /// </summary> |
| | | public sealed class PowerEndpoint |
| | | { |
| | | /// <summary> |
| | | /// 设备标识符 |
| | | /// </summary> |
| | | public string DeviceId { get; } |
| | | |
| | | /// <summary> |
| | | /// 通道标识 |
| | | /// </summary> |
| | | public int ChannelId { get; } |
| | | |
| | | /// <summary> |
| | | /// 生效版本号 |
| | | /// </summary> |
| | | public string EffectiveVersion { get; } |
| | | |
| | | /// <summary> |
| | | /// 初始化电源端点实例 |
| | | /// </summary> |
| | | /// <param name="deviceId">设备标识符</param> |
| | | /// <param name="channelId">通道标识</param> |
| | | /// <param name="effectiveVersion">生效版本号</param> |
| | | public PowerEndpoint(string deviceId, int channelId, string effectiveVersion) |
| | | { |
| | | DeviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId)); |
| | | ChannelId = channelId; |
| | | EffectiveVersion = effectiveVersion ?? string.Empty; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 转换为资源键,用于资源管理器的预约和租约操作 |
| | | /// </summary> |
| | | /// <returns>对应的资源键实例</returns> |
| | | public ResourceKey ToResourceKey() |
| | | { |
| | | return new ResourceKey(DeviceId, "PowerSupply", ChannelId.ToString()); |
| | | } |
| | | |
| | | /// <inheritdoc/> |
| | | public override string ToString() => $"{DeviceId}:CH{ChannelId}(v{EffectiveVersion})"; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Slot-设备-通道映射解析器。 |
| | | /// 从 Plugin.DeviceConfig 的 SQLite 数据库读取 Slot-Device-Channel 映射关系, |
| | | /// 提供根据 SlotId 和能力类型解析具体设备端点的功能。 |
| | | /// </summary> |
| | | public sealed class SlotDeviceMappingResolver |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(SlotDeviceMappingResolver)); |
| | | |
| | | /// <summary> |
| | | /// DeviceConfig 的 SQLite 数据库文件路径 |
| | | /// </summary> |
| | | private readonly string _deviceConfigDbPath; |
| | | |
| | | /// <summary> |
| | | /// 初始化映射解析器实例 |
| | | /// </summary> |
| | | /// <param name="deviceConfigDbPath">DeviceConfig 的 SQLite 数据库文件路径</param> |
| | | public SlotDeviceMappingResolver(string deviceConfigDbPath) |
| | | { |
| | | _deviceConfigDbPath = deviceConfigDbPath ?? throw new ArgumentNullException(nameof(deviceConfigDbPath)); |
| | | Log.Info($"[SlotDeviceMappingResolver] 初始化完成,数据库路径: {deviceConfigDbPath}"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据 SlotId 和能力类型解析具体的电源端点。 |
| | | /// 从 slot_device_mapping 表查询匹配的最新版本映射记录。 |
| | | /// </summary> |
| | | /// <param name="slotId">Slot 标识符</param> |
| | | /// <param name="capability">能力类型(如 "PowerSupply")</param> |
| | | /// <returns>解析后的电源端点;未找到匹配映射时返回 null</returns> |
| | | public PowerEndpoint? ResolveEndpoint(string slotId, string capability) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(slotId)) |
| | | throw new ArgumentException("SlotId 不能为空", nameof(slotId)); |
| | | if (string.IsNullOrWhiteSpace(capability)) |
| | | throw new ArgumentException("能力类型不能为空", nameof(capability)); |
| | | |
| | | SqliteHelper? sqliteHelper = null; |
| | | try |
| | | { |
| | | sqliteHelper = new SqliteHelper(_deviceConfigDbPath); |
| | | |
| | | var sql = $"SELECT slot_id, capability, version, device_id, channel_id, mapping_version " + |
| | | $"FROM slot_device_mapping " + |
| | | $"WHERE slot_id = '{slotId}' AND capability = '{capability}' " + |
| | | $"ORDER BY mapping_version DESC LIMIT 1"; |
| | | |
| | | var dataTable = sqliteHelper.GetDataTable(sql); |
| | | |
| | | if (dataTable.Rows.Count == 0) |
| | | { |
| | | Log.Warn($"[SlotDeviceMappingResolver] 未找到 Slot={slotId}, Capability={capability} 的映射记录"); |
| | | return null; |
| | | } |
| | | |
| | | var row = dataTable.Rows[0]; |
| | | var deviceId = row["device_id"]?.ToString() ?? string.Empty; |
| | | var channelId = Convert.ToInt32(row["channel_id"]); |
| | | var effectiveVersion = row["mapping_version"]?.ToString() ?? string.Empty; |
| | | |
| | | var endpoint = new PowerEndpoint(deviceId, channelId, effectiveVersion); |
| | | |
| | | Log.Info($"[SlotDeviceMappingResolver] 解析映射: Slot={slotId}, Capability={capability} → {endpoint}"); |
| | | return endpoint; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[SlotDeviceMappingResolver] 解析映射失败: Slot={slotId}, Capability={capability}, 错误: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | finally |
| | | { |
| | | sqliteHelper?.Close(); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取数据库中所有的 Slot-设备-通道映射记录 |
| | | /// </summary> |
| | | /// <returns>所有映射记录的列表</returns> |
| | | public List<PowerEndpoint> GetAllMappings() |
| | | { |
| | | SqliteHelper? sqliteHelper = null; |
| | | try |
| | | { |
| | | sqliteHelper = new SqliteHelper(_deviceConfigDbPath); |
| | | |
| | | var sql = "SELECT slot_id, capability, version, device_id, channel_id, mapping_version " + |
| | | "FROM slot_device_mapping " + |
| | | "ORDER BY slot_id, capability, mapping_version DESC"; |
| | | |
| | | var dataTable = sqliteHelper.GetDataTable(sql); |
| | | var results = new List<PowerEndpoint>(); |
| | | |
| | | foreach (DataRow row in dataTable.Rows) |
| | | { |
| | | var deviceId = row["device_id"]?.ToString() ?? string.Empty; |
| | | var channelId = Convert.ToInt32(row["channel_id"]); |
| | | var effectiveVersion = row["mapping_version"]?.ToString() ?? string.Empty; |
| | | |
| | | results.Add(new PowerEndpoint(deviceId, channelId, effectiveVersion)); |
| | | } |
| | | |
| | | Log.Info($"[SlotDeviceMappingResolver] 获取全部映射记录,共 {results.Count} 条"); |
| | | return results; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[SlotDeviceMappingResolver] 获取全部映射记录失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | finally |
| | | { |
| | | sqliteHelper?.Close(); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 按能力类型筛选映射记录 |
| | | /// </summary> |
| | | /// <param name="capability">能力类型(如 "PowerSupply"、"CAN" 等)</param> |
| | | /// <returns>匹配指定能力类型的映射记录列表</returns> |
| | | public List<PowerEndpoint> GetMappingsByCapability(string capability) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(capability)) |
| | | throw new ArgumentException("能力类型不能为空", nameof(capability)); |
| | | |
| | | SqliteHelper? sqliteHelper = null; |
| | | try |
| | | { |
| | | sqliteHelper = new SqliteHelper(_deviceConfigDbPath); |
| | | |
| | | var sql = $"SELECT slot_id, capability, version, device_id, channel_id, mapping_version " + |
| | | $"FROM slot_device_mapping " + |
| | | $"WHERE capability = '{capability}' " + |
| | | $"ORDER BY slot_id, mapping_version DESC"; |
| | | |
| | | var dataTable = sqliteHelper.GetDataTable(sql); |
| | | var results = new List<PowerEndpoint>(); |
| | | |
| | | foreach (DataRow row in dataTable.Rows) |
| | | { |
| | | var deviceId = row["device_id"]?.ToString() ?? string.Empty; |
| | | var channelId = Convert.ToInt32(row["channel_id"]); |
| | | var effectiveVersion = row["mapping_version"]?.ToString() ?? string.Empty; |
| | | |
| | | results.Add(new PowerEndpoint(deviceId, channelId, effectiveVersion)); |
| | | } |
| | | |
| | | Log.Info($"[SlotDeviceMappingResolver] 按能力类型 {capability} 筛选映射记录,共 {results.Count} 条"); |
| | | return results; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[SlotDeviceMappingResolver] 按能力类型筛选映射记录失败: {ex.Message}", ex); |
| | | throw; |
| | | } |
| | | finally |
| | | { |
| | | sqliteHelper?.Close(); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Concurrent; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Threading; |
| | | using log4net; |
| | | |
| | | namespace CanSimulator |
| | | { |
| | | /// <summary> |
| | | /// CAN 总线状态 |
| | | /// </summary> |
| | | public enum CanBusState |
| | | { |
| | | /// <summary>停止状态</summary> |
| | | Stopped, |
| | | /// <summary>运行中</summary> |
| | | Running, |
| | | /// <summary>暂停</summary> |
| | | Paused, |
| | | /// <summary>错误</summary> |
| | | Error |
| | | } |
| | | |
| | | /// <summary> |
| | | /// CAN 总线模拟器事件参数 |
| | | /// </summary> |
| | | public sealed class CanBusFrameEventArgs : EventArgs |
| | | { |
| | | public CanFrame Frame { get; } |
| | | public string SourceNodeId { get; } |
| | | |
| | | public CanBusFrameEventArgs(CanFrame frame, string sourceNodeId) |
| | | { |
| | | Frame = frame; |
| | | SourceNodeId = sourceNodeId; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// CAN 总线模拟器 |
| | | /// 模拟一条虚拟 CAN 总线,多个虚拟节点可以挂载到总线上进行通讯 |
| | | /// </summary> |
| | | public sealed class CanBusSimulator : IDisposable |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(CanBusSimulator)); |
| | | |
| | | /// <summary> |
| | | /// 总线名称 |
| | | /// </summary> |
| | | public string BusName { get; } |
| | | |
| | | /// <summary> |
| | | /// 总线状态 |
| | | /// </summary> |
| | | public CanBusState State { get; private set; } = CanBusState.Stopped; |
| | | |
| | | /// <summary> |
| | | /// 波特率(仅用于模拟显示,不影响实际逻辑) |
| | | /// </summary> |
| | | public int BaudRate { get; set; } = 500000; // 默认 500kbps |
| | | |
| | | /// <summary> |
| | | /// 模拟延迟(毫秒)- 模拟真实总线传输延迟 |
| | | /// </summary> |
| | | public int SimulatedLatencyMs { get; set; } = 0; |
| | | |
| | | /// <summary> |
| | | /// 已注册的节点 |
| | | /// </summary> |
| | | private readonly ConcurrentDictionary<string, CanNode> _nodes = new(); |
| | | |
| | | /// <summary> |
| | | /// 总线日志(最近 N 条帧) |
| | | /// </summary> |
| | | private readonly ConcurrentQueue<CanFrame> _busLog = new(); |
| | | private int _maxLogSize = 5000; |
| | | |
| | | /// <summary> |
| | | /// 总线统计 |
| | | /// </summary> |
| | | public long TotalFramesTransmitted { get; private set; } |
| | | public long TotalFramesReceived { get; private set; } |
| | | public DateTime? LastActivityTime { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 帧在总线上传输事件(可用于监控/日志) |
| | | /// </summary> |
| | | public event EventHandler<CanBusFrameEventArgs>? FrameTransmitted; |
| | | |
| | | /// <summary> |
| | | /// 总线状态变化事件 |
| | | /// </summary> |
| | | public event EventHandler<CanBusState>? StateChanged; |
| | | |
| | | public CanBusSimulator(string busName = "CAN_BUS_1") |
| | | { |
| | | BusName = busName ?? throw new ArgumentNullException(nameof(busName)); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 启动总线 |
| | | /// </summary> |
| | | public void Start() |
| | | { |
| | | if (State == CanBusState.Running) |
| | | { |
| | | Log.Warn($"[CanBus] 总线 {BusName} 已在运行中"); |
| | | return; |
| | | } |
| | | |
| | | State = CanBusState.Running; |
| | | Log.Info($"[CanBus] 总线 {BusName} 已启动 (波特率: {BaudRate}bps)"); |
| | | StateChanged?.Invoke(this, State); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 停止总线 |
| | | /// </summary> |
| | | public void Stop() |
| | | { |
| | | if (State == CanBusState.Stopped) return; |
| | | |
| | | State = CanBusState.Stopped; |
| | | Log.Info($"[CanBus] 总线 {BusName} 已停止"); |
| | | StateChanged?.Invoke(this, State); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 暂停总线(帧仍在保留但不路由) |
| | | /// </summary> |
| | | public void Pause() |
| | | { |
| | | if (State != CanBusState.Running) return; |
| | | |
| | | State = CanBusState.Paused; |
| | | Log.Info($"[CanBus] 总线 {BusName} 已暂停"); |
| | | StateChanged?.Invoke(this, State); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 恢复总线 |
| | | /// </summary> |
| | | public void Resume() |
| | | { |
| | | if (State != CanBusState.Paused) return; |
| | | |
| | | State = CanBusState.Running; |
| | | Log.Info($"[CanBus] 总线 {BusName} 已恢复"); |
| | | StateChanged?.Invoke(this, State); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 注册节点到总线 |
| | | /// </summary> |
| | | public CanNode RegisterNode(string nodeId, string? name = null) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(nodeId)) |
| | | throw new ArgumentException("节点 ID 不能为空", nameof(nodeId)); |
| | | |
| | | var node = new CanNode(nodeId, name); |
| | | node.FrameSent += OnNodeFrameSent; |
| | | |
| | | if (!_nodes.TryAdd(nodeId, node)) |
| | | { |
| | | Log.Warn($"[CanBus] 节点 {nodeId} 已存在,将被替换"); |
| | | _nodes.TryRemove(nodeId, out var oldNode); |
| | | oldNode?.Deactivate(); |
| | | _nodes.TryAdd(nodeId, node); |
| | | } |
| | | |
| | | // 如果总线已运行,自动激活节点 |
| | | if (State == CanBusState.Running) |
| | | { |
| | | node.Activate(); |
| | | } |
| | | |
| | | Log.Info($"[CanBus] 节点 {nodeId} ({name ?? nodeId}) 已注册到总线 {BusName}"); |
| | | return node; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 注销节点 |
| | | /// </summary> |
| | | public bool UnregisterNode(string nodeId) |
| | | { |
| | | if (_nodes.TryRemove(nodeId, out var node)) |
| | | { |
| | | node.FrameSent -= OnNodeFrameSent; |
| | | node.Deactivate(); |
| | | Log.Info($"[CanBus] 节点 {nodeId} 已从总线 {BusName} 注销"); |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取节点 |
| | | /// </summary> |
| | | public CanNode? GetNode(string nodeId) |
| | | { |
| | | _nodes.TryGetValue(nodeId, out var node); |
| | | return node; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取所有已注册节点 |
| | | /// </summary> |
| | | public IReadOnlyList<CanNode> GetAllNodes() |
| | | { |
| | | return _nodes.Values.ToList(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 向总线发送帧(由节点发送,总线负责路由) |
| | | /// </summary> |
| | | private void OnNodeFrameSent(object? sender, CanFrame frame) |
| | | { |
| | | if (State != CanBusState.Running) return; |
| | | |
| | | // 模拟延迟 |
| | | if (SimulatedLatencyMs > 0) |
| | | { |
| | | Thread.Sleep(SimulatedLatencyMs); |
| | | } |
| | | |
| | | // 记录到总线日志 |
| | | while (_busLog.Count >= _maxLogSize) |
| | | { |
| | | _busLog.TryDequeue(out _); |
| | | } |
| | | _busLog.Enqueue(frame); |
| | | TotalFramesTransmitted++; |
| | | LastActivityTime = DateTime.Now; |
| | | |
| | | // 路由到所有其他节点 |
| | | int routed = 0; |
| | | foreach (var kvp in _nodes) |
| | | { |
| | | if (kvp.Key == frame.SourceNodeId) continue; // 不路由回源节点 |
| | | |
| | | try |
| | | { |
| | | kvp.Value.ReceiveFrame(frame); |
| | | routed++; |
| | | TotalFramesReceived++; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[CanBus] 路由帧到节点 {kvp.Key} 失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | // 检查源节点是否启用自动响应模式 |
| | | if (_nodes.TryGetValue(frame.SourceNodeId, out var sourceNode) && sourceNode.AutoResponseEnabled) |
| | | { |
| | | try |
| | | { |
| | | // 模拟响应延迟 |
| | | if (sourceNode.AutoResponseDelayMs > 0) |
| | | { |
| | | Thread.Sleep(sourceNode.AutoResponseDelayMs); |
| | | } |
| | | |
| | | // 创建响应帧(复制原始帧数据,来源为源节点) |
| | | var responseFrame = new CanFrame |
| | | { |
| | | FrameId = frame.FrameId, |
| | | Format = frame.Format, |
| | | Type = frame.Type, |
| | | Data = (byte[])frame.Data.Clone(), |
| | | SourceNodeId = sourceNode.NodeId, |
| | | Timestamp = DateTime.Now |
| | | }; |
| | | |
| | | // 将响应帧路由到所有其他节点(不包括响应源自身) |
| | | foreach (var kvp in _nodes) |
| | | { |
| | | if (kvp.Key == sourceNode.NodeId) continue; |
| | | try |
| | | { |
| | | kvp.Value.ReceiveFrame(responseFrame); |
| | | TotalFramesReceived++; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[CanBus] 路由响应帧到节点 {kvp.Key} 失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | // 将响应帧送入源节点的接收邮箱(让源节点能看到自己的响应) |
| | | sourceNode.ReceiveFrame(responseFrame); |
| | | TotalFramesReceived++; |
| | | |
| | | // 记录响应帧到总线日志 |
| | | _busLog.Enqueue(responseFrame); |
| | | TotalFramesTransmitted++; |
| | | |
| | | Log.Debug($"[CanBus] 节点 {sourceNode.NodeId} 自动响应帧 {responseFrame.FrameIdHex}"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[CanBus] 源节点自动响应失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | Log.Debug($"[CanBus] 帧 {frame.FrameIdHex} 从 {frame.SourceNodeId} 路由到 {routed} 个节点"); |
| | | FrameTransmitted?.Invoke(this, new CanBusFrameEventArgs(frame, frame.SourceNodeId)); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 直接注入帧到总线(用于模拟外部设备发送) |
| | | /// </summary> |
| | | public void InjectFrame(CanFrame frame) |
| | | { |
| | | if (State != CanBusState.Running) |
| | | { |
| | | Log.Warn($"[CanBus] 总线未运行,无法注入帧"); |
| | | return; |
| | | } |
| | | |
| | | frame.Timestamp = DateTime.Now; |
| | | |
| | | // 记录到日志 |
| | | while (_busLog.Count >= _maxLogSize) |
| | | { |
| | | _busLog.TryDequeue(out _); |
| | | } |
| | | _busLog.Enqueue(frame); |
| | | TotalFramesTransmitted++; |
| | | LastActivityTime = DateTime.Now; |
| | | |
| | | // 路由到所有节点 |
| | | foreach (var kvp in _nodes) |
| | | { |
| | | try |
| | | { |
| | | kvp.Value.ReceiveFrame(frame); |
| | | TotalFramesReceived++; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Log.Error($"[CanBus] 注入帧路由到节点 {kvp.Key} 失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | FrameTransmitted?.Invoke(this, new CanBusFrameEventArgs(frame, frame.SourceNodeId)); |
| | | Log.Debug($"[CanBus] 注入帧 {frame.FrameIdHex} 到总线"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取总线日志 |
| | | /// </summary> |
| | | public List<CanFrame> GetBusLog(int maxCount = 100) |
| | | { |
| | | var all = _busLog.ToList(); |
| | | if (all.Count <= maxCount) return all; |
| | | return all.Skip(all.Count - maxCount).ToList(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 清空总线日志 |
| | | /// </summary> |
| | | public void ClearBusLog() |
| | | { |
| | | while (_busLog.TryDequeue(out _)) { } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 重置所有统计 |
| | | /// </summary> |
| | | public void ResetStatistics() |
| | | { |
| | | TotalFramesTransmitted = 0; |
| | | TotalFramesReceived = 0; |
| | | LastActivityTime = null; |
| | | foreach (var node in _nodes.Values) |
| | | { |
| | | node.ResetCounters(); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取总线状态摘要 |
| | | /// </summary> |
| | | public string GetStatusSummary() |
| | | { |
| | | return $"Bus={BusName}, State={State}, BaudRate={BaudRate}, Nodes={_nodes.Count}, " + |
| | | $"Tx={TotalFramesTransmitted}, Rx={TotalFramesReceived}, " + |
| | | $"LastActivity={LastActivityTime?.ToString("HH:mm:ss") ?? "N/A"}"; |
| | | } |
| | | |
| | | public void Dispose() |
| | | { |
| | | Stop(); |
| | | foreach (var node in _nodes.Values) |
| | | { |
| | | node.Deactivate(); |
| | | } |
| | | _nodes.Clear(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | |
| | | namespace CanSimulator |
| | | { |
| | | /// <summary> |
| | | /// CAN 帧格式类型 |
| | | /// </summary> |
| | | public enum CanFrameFormat |
| | | { |
| | | /// <summary>标准帧 (11-bit ID)</summary> |
| | | Standard, |
| | | /// <summary>扩展帧 (29-bit ID)</summary> |
| | | Extended |
| | | } |
| | | |
| | | /// <summary> |
| | | /// CAN 帧类型 |
| | | /// </summary> |
| | | public enum CanFrameType |
| | | { |
| | | /// <summary>数据帧</summary> |
| | | Data, |
| | | /// <summary>远程帧</summary> |
| | | Remote |
| | | } |
| | | |
| | | /// <summary> |
| | | /// CAN 总线帧数据结构 |
| | | /// </summary> |
| | | public sealed class CanFrame |
| | | { |
| | | /// <summary> |
| | | /// 帧 ID(标准帧 11-bit,扩展帧 29-bit) |
| | | /// </summary> |
| | | public uint FrameId { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 帧格式(标准/扩展) |
| | | /// </summary> |
| | | public CanFrameFormat Format { get; set; } = CanFrameFormat.Standard; |
| | | |
| | | /// <summary> |
| | | /// 帧类型(数据帧/远程帧) |
| | | /// </summary> |
| | | public CanFrameType Type { get; set; } = CanFrameType.Data; |
| | | |
| | | /// <summary> |
| | | /// 数据负载(最大 8 字节,CAN FD 可达 64 字节) |
| | | /// </summary> |
| | | public byte[] Data { get; set; } = Array.Empty<byte>(); |
| | | |
| | | /// <summary> |
| | | /// 时间戳 |
| | | /// </summary> |
| | | public DateTime Timestamp { get; set; } = DateTime.Now; |
| | | |
| | | /// <summary> |
| | | /// 发送节点标识 |
| | | /// </summary> |
| | | public string SourceNodeId { get; set; } = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// DLC (Data Length Code) |
| | | /// </summary> |
| | | public int Dlc => Data.Length; |
| | | |
| | | /// <summary> |
| | | /// 将帧 ID 格式化为十六进制字符串 |
| | | /// </summary> |
| | | public string FrameIdHex => Format == CanFrameFormat.Extended |
| | | ? $"0x{FrameId:X8}" |
| | | : $"0x{FrameId:X3}"; |
| | | |
| | | /// <summary> |
| | | /// 将数据格式化为十六进制字符串 |
| | | /// </summary> |
| | | public string DataHex => BitConverter.ToString(Data).Replace("-", " "); |
| | | |
| | | /// <summary> |
| | | /// 从十六进制字符串解析帧 ID |
| | | /// </summary> |
| | | public static uint ParseFrameId(string frameIdStr) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(frameIdStr)) |
| | | throw new ArgumentException("帧 ID 不能为空", nameof(frameIdStr)); |
| | | |
| | | if (frameIdStr.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) |
| | | { |
| | | if (!uint.TryParse(frameIdStr.Substring(2), System.Globalization.NumberStyles.HexNumber, null, out uint id)) |
| | | throw new ArgumentException($"无法解析帧 ID: {frameIdStr}"); |
| | | return id; |
| | | } |
| | | else |
| | | { |
| | | if (!uint.TryParse(frameIdStr, out uint id)) |
| | | throw new ArgumentException($"无法解析帧 ID: {frameIdStr}"); |
| | | return id; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 从十六进制字符串解析数据 |
| | | /// </summary> |
| | | public static byte[] ParseData(string dataStr) |
| | | { |
| | | if (string.IsNullOrWhiteSpace(dataStr)) |
| | | return Array.Empty<byte>(); |
| | | |
| | | try |
| | | { |
| | | var parts = dataStr.Split(new[] { ' ', '-', ',' }, StringSplitOptions.RemoveEmptyEntries); |
| | | var bytes = new byte[parts.Length]; |
| | | for (int i = 0; i < parts.Length; i++) |
| | | { |
| | | bytes[i] = byte.Parse(parts[i], System.Globalization.NumberStyles.HexNumber); |
| | | } |
| | | return bytes; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new ArgumentException($"无法解析数据字符串: {dataStr}, 错误: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | public override string ToString() |
| | | { |
| | | return $"[{Timestamp:HH:mm:ss.fff}] {FrameIdHex} [{Dlc}] {DataHex} (from {SourceNodeId})"; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Concurrent; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using log4net; |
| | | |
| | | namespace CanSimulator |
| | | { |
| | | /// <summary> |
| | | /// 接收滤波器配置 |
| | | /// </summary> |
| | | public sealed class AcceptanceFilter |
| | | { |
| | | /// <summary> |
| | | /// 滤波器 ID(匹配值) |
| | | /// </summary> |
| | | public uint FilterId { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 掩码(为 1 的位参与比较) |
| | | /// </summary> |
| | | public uint Mask { get; set; } = 0x7FF; // 默认标准帧全匹配 |
| | | |
| | | /// <summary> |
| | | /// 是否为扩展帧滤波器 |
| | | /// </summary> |
| | | public bool IsExtended { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 检查给定帧 ID 是否通过此滤波器 |
| | | /// </summary> |
| | | public bool Accepts(uint frameId, bool isExtended) |
| | | { |
| | | if (IsExtended != isExtended) return false; |
| | | return (frameId & Mask) == (FilterId & Mask); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 虚拟 CAN 节点 |
| | | /// 模拟一个 CAN 通讯卡的端点,具有发送和接收功能 |
| | | /// </summary> |
| | | public sealed class CanNode |
| | | { |
| | | private static readonly ILog Log = LogManager.GetLogger(typeof(CanNode)); |
| | | |
| | | /// <summary> |
| | | /// 节点唯一标识 |
| | | /// </summary> |
| | | public string NodeId { get; } |
| | | |
| | | /// <summary> |
| | | /// 节点名称(可选描述) |
| | | /// </summary> |
| | | public string Name { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 节点是否已激活(连接到总线) |
| | | /// </summary> |
| | | public bool IsActive { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 接收滤波器列表 |
| | | /// </summary> |
| | | private readonly List<AcceptanceFilter> _filters = new(); |
| | | |
| | | /// <summary> |
| | | /// 接收邮箱(FIFO 队列) |
| | | /// </summary> |
| | | private readonly ConcurrentQueue<CanFrame> _receiveMailbox = new(); |
| | | |
| | | /// <summary> |
| | | /// 发送历史记录 |
| | | /// </summary> |
| | | private readonly ConcurrentQueue<CanFrame> _sendHistory = new(); |
| | | |
| | | /// <summary> |
| | | /// 最大邮箱容量 |
| | | /// </summary> |
| | | public int MaxMailboxSize { get; set; } = 1000; |
| | | |
| | | /// <summary> |
| | | /// 最大历史记录容量 |
| | | /// </summary> |
| | | public int MaxHistorySize { get; set; } = 500; |
| | | |
| | | /// <summary> |
| | | /// 接收帧计数器 |
| | | /// </summary> |
| | | public long RxCount { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 发送帧计数器 |
| | | /// </summary> |
| | | public long TxCount { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 错误计数器 |
| | | /// </summary> |
| | | public long ErrorCount { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 是否启用自动响应模式(收到帧后原封不动回复) |
| | | /// </summary> |
| | | public bool AutoResponseEnabled { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 自动响应延迟(毫秒)- 模拟真实设备响应时间 |
| | | /// </summary> |
| | | public int AutoResponseDelayMs { get; set; } = 0; |
| | | |
| | | /// <summary> |
| | | /// 自动响应事件(用于日志/监控) |
| | | /// </summary> |
| | | public event EventHandler<CanFrame>? AutoResponseSent; |
| | | |
| | | /// <summary> |
| | | /// 帧接收事件 |
| | | /// </summary> |
| | | public event EventHandler<CanFrame>? FrameReceived; |
| | | |
| | | /// <summary> |
| | | /// 帧发送事件 |
| | | /// </summary> |
| | | public event EventHandler<CanFrame>? FrameSent; |
| | | |
| | | public CanNode(string nodeId, string? name = null) |
| | | { |
| | | NodeId = nodeId ?? throw new ArgumentNullException(nameof(nodeId)); |
| | | Name = name ?? nodeId; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 激活节点(连接到总线) |
| | | /// </summary> |
| | | public void Activate() |
| | | { |
| | | IsActive = true; |
| | | Log.Info($"[CanNode] 节点 {NodeId} ({Name}) 已激活"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 停用节点(从总线断开) |
| | | /// </summary> |
| | | public void Deactivate() |
| | | { |
| | | IsActive = false; |
| | | Log.Info($"[CanNode] 节点 {NodeId} ({Name}) 已停用"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 添加接收滤波器 |
| | | /// </summary> |
| | | public void AddFilter(AcceptanceFilter filter) |
| | | { |
| | | lock (_filters) |
| | | { |
| | | _filters.Add(filter); |
| | | } |
| | | Log.Debug($"[CanNode] 节点 {NodeId} 添加滤波器: ID=0x{filter.FilterId:X3}, Mask=0x{filter.Mask:X3}"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 清除所有滤波器 |
| | | /// </summary> |
| | | public void ClearFilters() |
| | | { |
| | | lock (_filters) |
| | | { |
| | | _filters.Clear(); |
| | | } |
| | | Log.Debug($"[CanNode] 节点 {NodeId} 清除所有滤波器"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 设置通配符滤波器(接收所有帧) |
| | | /// </summary> |
| | | public void SetPromiscuousMode() |
| | | { |
| | | lock (_filters) |
| | | { |
| | | _filters.Clear(); |
| | | _filters.Add(new AcceptanceFilter { FilterId = 0, Mask = 0, IsExtended = false }); |
| | | _filters.Add(new AcceptanceFilter { FilterId = 0, Mask = 0, IsExtended = true }); |
| | | } |
| | | Log.Debug($"[CanNode] 节点 {NodeId} 设置为混杂模式(接收所有帧)"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查帧是否通过滤波器 |
| | | /// </summary> |
| | | internal bool AcceptsFrame(CanFrame frame) |
| | | { |
| | | lock (_filters) |
| | | { |
| | | // 如果没有设置滤波器,默认拒绝 |
| | | if (_filters.Count == 0) return false; |
| | | |
| | | // 任一滤波器匹配即接受 |
| | | return _filters.Any(f => f.Accepts(frame.FrameId, frame.Format == CanFrameFormat.Extended)); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 接收帧(由总线调用) |
| | | /// </summary> |
| | | internal void ReceiveFrame(CanFrame frame) |
| | | { |
| | | if (!IsActive) return; |
| | | if (!AcceptsFrame(frame)) return; |
| | | |
| | | // 入队接收邮箱 |
| | | while (_receiveMailbox.Count >= MaxMailboxSize) |
| | | { |
| | | _receiveMailbox.TryDequeue(out _); // 丢弃最旧的帧 |
| | | } |
| | | _receiveMailbox.Enqueue(frame); |
| | | RxCount++; |
| | | |
| | | // 触发事件 |
| | | FrameReceived?.Invoke(this, frame); |
| | | Log.Debug($"[CanNode] 节点 {NodeId} 接收帧: {frame}"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 发送帧 |
| | | /// </summary> |
| | | public void SendFrame(CanFrame frame) |
| | | { |
| | | if (!IsActive) |
| | | { |
| | | ErrorCount++; |
| | | throw new InvalidOperationException($"节点 {NodeId} 未激活,无法发送帧"); |
| | | } |
| | | |
| | | frame.SourceNodeId = NodeId; |
| | | frame.Timestamp = DateTime.Now; |
| | | |
| | | // 记录发送历史 |
| | | while (_sendHistory.Count >= MaxHistorySize) |
| | | { |
| | | _sendHistory.TryDequeue(out _); |
| | | } |
| | | _sendHistory.Enqueue(frame); |
| | | TxCount++; |
| | | |
| | | // 触发事件(由总线处理路由) |
| | | FrameSent?.Invoke(this, frame); |
| | | Log.Debug($"[CanNode] 节点 {NodeId} 发送帧: {frame}"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 从接收邮箱中取出一帧 |
| | | /// </summary> |
| | | public bool TryDequeueReceived(out CanFrame? frame) |
| | | { |
| | | return _receiveMailbox.TryDequeue(out frame); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 查看接收邮箱中的所有帧(不移除) |
| | | /// </summary> |
| | | public List<CanFrame> PeekReceiveMailbox() |
| | | { |
| | | return _receiveMailbox.ToList(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 清空接收邮箱 |
| | | /// </summary> |
| | | public void ClearReceiveMailbox() |
| | | { |
| | | while (_receiveMailbox.TryDequeue(out _)) { } |
| | | Log.Debug($"[CanNode] 节点 {NodeId} 清空接收邮箱"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取发送历史 |
| | | /// </summary> |
| | | public List<CanFrame> GetSendHistory() |
| | | { |
| | | return _sendHistory.ToList(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 清空发送历史 |
| | | /// </summary> |
| | | public void ClearSendHistory() |
| | | { |
| | | while (_sendHistory.TryDequeue(out _)) { } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 重置节点统计 |
| | | /// </summary> |
| | | public void ResetCounters() |
| | | { |
| | | RxCount = 0; |
| | | TxCount = 0; |
| | | ErrorCount = 0; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取节点状态摘要 |
| | | /// </summary> |
| | | public string GetStatusSummary() |
| | | { |
| | | return $"Node={NodeId}, Active={IsActive}, Rx={RxCount}, Tx={TxCount}, Err={ErrorCount}, Mailbox={_receiveMailbox.Count}"; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <Project Sdk="Microsoft.NET.Sdk"> |
| | | |
| | | <PropertyGroup> |
| | | <OutputType>Library</OutputType> |
| | | <TargetFramework>net472</TargetFramework> |
| | | <Nullable>enable</Nullable> |
| | | <LangVersion>10.0</LangVersion> |
| | | <ImplicitUsings>enable</ImplicitUsings> |
| | | </PropertyGroup> |
| | | |
| | | <ItemGroup> |
| | | <PackageReference Include="log4net" Version="3.0.4" /> |
| | | </ItemGroup> |
| | | |
| | | </Project> |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| | | <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
| | | <PropertyGroup> |
| | | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
| | | <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
| | | <ProjectGuid>{84EDEAC7-6763-46CD-B098-0B508AAA705F}</ProjectGuid> |
| | | <OutputType>Library</OutputType> |
| | | <AppDesignerFolder>Properties</AppDesignerFolder> |
| | | <RootNamespace>CellStepSender</RootNamespace> |
| | | <AssemblyName>CellStepSender</AssemblyName> |
| | | <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> |
| | | <FileAlignment>512</FileAlignment> |
| | | <Deterministic>true</Deterministic> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <DebugType>full</DebugType> |
| | | <Optimize>false</Optimize> |
| | | <OutputPath>bin\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
| | | <DebugType>pdbonly</DebugType> |
| | | <Optimize>true</Optimize> |
| | | <OutputPath>bin\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <ItemGroup> |
| | | <Reference Include="System" /> |
| | | <Reference Include="System.Core" /> |
| | | <Reference Include="System.Xml.Linq" /> |
| | | <Reference Include="System.Data.DataSetExtensions" /> |
| | | <Reference Include="Microsoft.CSharp" /> |
| | | <Reference Include="System.Data" /> |
| | | <Reference Include="System.Net.Http" /> |
| | | <Reference Include="System.Xml" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="StepSender.cs" /> |
| | | <Compile Include="TcpClientTool.cs" /> |
| | | <Compile Include="TcpClientTool3.cs" /> |
| | | <Compile Include="Properties\AssemblyInfo.cs" /> |
| | | </ItemGroup> |
| | | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| | | </Project> |
| New file |
| | |
| | | using System.Reflection; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Runtime.InteropServices; |
| | | |
| | | // 有关程序集的一般信息由以下 |
| | | // 控制。更改这些特性值可修改 |
| | | // 与程序集关联的信息。 |
| | | [assembly: AssemblyTitle("CellStepSender")] |
| | | [assembly: AssemblyDescription("")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("")] |
| | | [assembly: AssemblyProduct("CellStepSender")] |
| | | [assembly: AssemblyCopyright("Copyright © 2025")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // 将 ComVisible 设置为 false 会使此程序集中的类型 |
| | | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 |
| | | //请将此类型的 ComVisible 特性设置为 true。 |
| | | [assembly: ComVisible(false)] |
| | | |
| | | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID |
| | | [assembly: Guid("84edeac7-6763-46cd-b098-0b508aaa705f")] |
| | | |
| | | // 程序集的版本信息由下列四个值组成: |
| | | // |
| | | // 主版本 |
| | | // 次版本 |
| | | // 生成号 |
| | | // 修订号 |
| | | // |
| | | [assembly: AssemblyVersion("1.0.0.1")] |
| | | [assembly: AssemblyFileVersion("1.0.0.1")] |
| New file |
| | |
| | | using System; |
| | | using System.Threading; |
| | | |
| | | namespace CellStepSender |
| | | { |
| | | internal class StepSender |
| | | { |
| | | /// <summary> |
| | | /// 发送测试数据 |
| | | /// </summary> |
| | | /// <param name="ip">IP</param> |
| | | /// <param name="port">端口</param> |
| | | /// <param name="ovenIndex">炉下标</param> |
| | | /// <param name="cellIndex">穴位下标</param> |
| | | /// <param name="step">测试项</param> |
| | | /// <param name="value">测试值</param> |
| | | /// <param name="limitUp">上限</param> |
| | | /// <param name="limitDown">下限</param> |
| | | /// <param name="unit">单位</param> |
| | | /// <param name="result">测试结果</param> |
| | | /// <param name="updateTime">测试时间</param> |
| | | /// <param name="errorMsg">异常信息</param> |
| | | /// <returns>是否发送成功</returns> |
| | | public static bool SendStep( |
| | | string ip, int port, |
| | | int ovenIndex, int cellIndex, string step, string value, string limitUp, |
| | | string limitDown, string unit, string result, string updateTime, out string errorMsg) |
| | | { |
| | | |
| | | errorMsg = null; |
| | | TcpClientTool3 client = null; |
| | | try |
| | | { |
| | | client = new TcpClientTool3(ip, port); |
| | | string res = null; |
| | | client.ExecuteMessageChanged += (sender, data) => |
| | | { |
| | | res = data; |
| | | }; |
| | | client.Connect(); |
| | | |
| | | string msg = $"{ovenIndex};{cellIndex};{step},{value},{limitUp},{limitDown},{unit},{result},{updateTime}\n"; |
| | | client.Send(msg); |
| | | |
| | | int time = 1; |
| | | while (res == null && time <= 50) |
| | | { |
| | | Thread.Sleep(100); |
| | | time++; |
| | | } |
| | | if (res == null) |
| | | { |
| | | errorMsg = "Time Out"; |
| | | } |
| | | return res == "1"; |
| | | |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | errorMsg = ex.Message; |
| | | return false; |
| | | } |
| | | finally |
| | | { |
| | | client?.Disconnect(); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Net; |
| | | using System.Net.Sockets; |
| | | using System.Text; |
| | | using System.Threading; |
| | | |
| | | namespace CellStepSender |
| | | { |
| | | public class TcpClientTool |
| | | { |
| | | private string _ip; |
| | | private int _port; |
| | | private bool _isConnected = false; // 是否连接 |
| | | private Socket _clientSocket; // 套接字 |
| | | private Thread _receiveThread = null; // 接收线程 |
| | | private volatile bool needRecieve = true; |
| | | |
| | | public event EventHandler<string> ExecuteMessageChanged; |
| | | |
| | | public TcpClientTool(string ip, int port) |
| | | { |
| | | this._ip = ip; |
| | | this._port = port; |
| | | } |
| | | |
| | | public void Connect() |
| | | { |
| | | try |
| | | { |
| | | _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); |
| | | IPAddress ipaddress = IPAddress.Parse(this._ip); |
| | | IPEndPoint endpoint = new IPEndPoint(ipaddress, _port); |
| | | _clientSocket.Connect(endpoint); |
| | | _isConnected = true; |
| | | _receiveThread = new Thread(Received); |
| | | _receiveThread.IsBackground = true; |
| | | _receiveThread.Start(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | needRecieve = false; |
| | | _receiveThread?.Abort(); |
| | | _receiveThread = null; |
| | | _clientSocket.Close(); |
| | | _isConnected = false; |
| | | throw ex; |
| | | } |
| | | } |
| | | |
| | | public void Disconnect() |
| | | { |
| | | needRecieve = false; |
| | | _clientSocket.Close(); |
| | | _receiveThread?.Abort(); |
| | | _receiveThread = null; |
| | | _isConnected = false; |
| | | } |
| | | |
| | | public void Send(string data) |
| | | { |
| | | byte[] arrClientSendMsg = Encoding.UTF8.GetBytes(data); |
| | | _clientSocket.Send(arrClientSendMsg); |
| | | } |
| | | |
| | | public void Received() |
| | | { |
| | | //while (needRecieve) |
| | | //{ |
| | | // byte[] arrRecMsg = new byte[1024 * 1024]; |
| | | // int length = _clientSocket.Receive(arrRecMsg); |
| | | // string strRecMsg = Encoding.UTF8.GetString(arrRecMsg, 0, length); |
| | | // ExecuteMessageChanged?.Invoke(this, strRecMsg); |
| | | // Thread.Sleep(100); |
| | | //} |
| | | } |
| | | |
| | | |
| | | public bool SendStep( |
| | | string ip, int port, |
| | | int ovenIndex, int cellIndex, string step, string value, string limitUp, |
| | | string limitDown, string unit, string result, string updateTime, out string errorMsg) |
| | | { |
| | | |
| | | errorMsg = null; |
| | | try |
| | | { |
| | | if (!_isConnected) |
| | | { |
| | | errorMsg = "Not Connected"; |
| | | return false; |
| | | } |
| | | |
| | | string msg = $"{ovenIndex};{cellIndex};{step},{value},{limitUp},{limitDown},{unit},{result},{updateTime}\n"; |
| | | Send(msg); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | errorMsg = ex.Message; |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Net; |
| | | using System.Net.Sockets; |
| | | using System.Text; |
| | | |
| | | namespace CellStepSender |
| | | { |
| | | public class TcpClientTool3 |
| | | { |
| | | private string _ip; |
| | | private int _port; |
| | | private bool _isConnected = false; // 是否连接 |
| | | private Socket _clientSocket; // 套接字 |
| | | |
| | | public event EventHandler<string> ExecuteMessageChanged; |
| | | |
| | | public TcpClientTool3(string ip, int port) |
| | | { |
| | | this._ip = ip; |
| | | this._port = port; |
| | | } |
| | | |
| | | public void Connect() |
| | | { |
| | | try |
| | | { |
| | | _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); |
| | | IPAddress ipaddress = IPAddress.Parse(this._ip); |
| | | IPEndPoint endpoint = new IPEndPoint(ipaddress, _port); |
| | | _clientSocket.Connect(endpoint); |
| | | _isConnected = true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | _clientSocket.Dispose(); |
| | | _isConnected = false; |
| | | throw ex; |
| | | } |
| | | } |
| | | |
| | | public void Disconnect() |
| | | { |
| | | _clientSocket.Dispose(); |
| | | } |
| | | |
| | | public void Send(string data) |
| | | { |
| | | byte[] arrClientSendMsg = Encoding.UTF8.GetBytes(data); |
| | | lock (this) |
| | | { |
| | | _clientSocket.Send(arrClientSendMsg); |
| | | } |
| | | } |
| | | |
| | | public bool SendStep( |
| | | string ip, int port, |
| | | int ovenIndex, int cellIndex, string step, string value, string limitUp, |
| | | string limitDown, string unit, string result, string updateTime, out string errorMsg) |
| | | { |
| | | |
| | | errorMsg = null; |
| | | try |
| | | { |
| | | if (!_isConnected) |
| | | { |
| | | errorMsg = "Not Connected"; |
| | | return false; |
| | | } |
| | | |
| | | string msg = $"{ovenIndex};{cellIndex};{step},{value},{limitUp},{limitDown},{unit},{result},{updateTime}\n"; |
| | | Send(msg); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | errorMsg = ex.Message; |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| | | <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
| | | <PropertyGroup> |
| | | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
| | | <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
| | | <ProjectGuid>{D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}</ProjectGuid> |
| | | <OutputType>Library</OutputType> |
| | | <AppDesignerFolder>Properties</AppDesignerFolder> |
| | | <RootNamespace>ConnectionUtil</RootNamespace> |
| | | <AssemblyName>ConnectionUtil</AssemblyName> |
| | | <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> |
| | | <FileAlignment>512</FileAlignment> |
| | | <Deterministic>true</Deterministic> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <DebugType>full</DebugType> |
| | | <Optimize>false</Optimize> |
| | | <OutputPath>bin\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
| | | <DebugType>pdbonly</DebugType> |
| | | <Optimize>true</Optimize> |
| | | <OutputPath>bin\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <OutputPath>bin\x64\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <DebugType>full</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> |
| | | <OutputPath>bin\x64\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <Optimize>true</Optimize> |
| | | <DebugType>pdbonly</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <ItemGroup> |
| | | <Reference Include="System" /> |
| | | <Reference Include="System.Core" /> |
| | | <Reference Include="System.Xml.Linq" /> |
| | | <Reference Include="System.Data.DataSetExtensions" /> |
| | | <Reference Include="Microsoft.CSharp" /> |
| | | <Reference Include="System.Data" /> |
| | | <Reference Include="System.Net.Http" /> |
| | | <Reference Include="System.Xml" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="PipeClient.cs" /> |
| | | <Compile Include="PipeClient2.cs" /> |
| | | <Compile Include="PipeServer.cs" /> |
| | | <Compile Include="PipeServer2.cs" /> |
| | | <Compile Include="Properties\AssemblyInfo.cs" /> |
| | | <Compile Include="SCADAApi.cs" /> |
| | | <Compile Include="TcpClient.cs" /> |
| | | </ItemGroup> |
| | | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| | | </Project> |
| New file |
| | |
| | | using System; |
| | | using System.IO; |
| | | using System.IO.Pipes; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace ConnectionUtil |
| | | { |
| | | public class PipeClient : IDisposable |
| | | { |
| | | private readonly string _pipeName; |
| | | private readonly string _serverName; |
| | | private readonly CancellationTokenSource _cancellationTokenSource; |
| | | private NamedPipeClientStream _pipeClient; |
| | | private bool _isDisposed; |
| | | private bool _isConnected; |
| | | private readonly object _sendLock = new object(); |
| | | |
| | | // 事件定义 |
| | | public event EventHandler<PipeDataEventArgs> OnData; |
| | | public event EventHandler OnConnected; |
| | | public event EventHandler OnDisconnected; |
| | | |
| | | public PipeClient(string pipeName, string serverName = ".") |
| | | { |
| | | _pipeName = pipeName; |
| | | _serverName = serverName; |
| | | _cancellationTokenSource = new CancellationTokenSource(); |
| | | } |
| | | |
| | | public void Connect(int timeoutMs = 5000) |
| | | { |
| | | if (_isConnected) |
| | | return; |
| | | _pipeClient = new NamedPipeClientStream(_pipeName); |
| | | |
| | | _pipeClient.Connect(timeoutMs); |
| | | _pipeClient.ReadMode = PipeTransmissionMode.Message; |
| | | _isConnected = true; |
| | | Console.WriteLine("连接成功!"); |
| | | |
| | | // 触发连接成功事件 |
| | | OnConnected?.Invoke(this, EventArgs.Empty); |
| | | } |
| | | |
| | | public bool SendMessage(string message, out string response, int timeoutMs = 5000) |
| | | { |
| | | if (!_isConnected) |
| | | { |
| | | response = "客户端未连接到服务器"; |
| | | return false; |
| | | } |
| | | response = null; |
| | | lock (_sendLock) |
| | | { |
| | | try |
| | | { |
| | | // 发送消息 |
| | | _pipeClient.WriteMessage(message); |
| | | return true; |
| | | while (_isConnected && !_cancellationTokenSource.Token.IsCancellationRequested) |
| | | { |
| | | try |
| | | { |
| | | response = _pipeClient.ReadMessage(); |
| | | break; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Console.WriteLine($"接收数据时发生错误: {ex.Message}"); |
| | | break; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | public void Disconnect() |
| | | { |
| | | _isConnected = false; |
| | | _cancellationTokenSource.Cancel(); |
| | | } |
| | | |
| | | public void Dispose() |
| | | { |
| | | if (!_isDisposed) |
| | | { |
| | | Disconnect(); |
| | | _pipeClient?.Dispose(); |
| | | _cancellationTokenSource.Dispose(); |
| | | _isDisposed = true; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.IO; |
| | | using System.IO.Pipes; |
| | | using System.Threading; |
| | | |
| | | namespace ConnectionUtil |
| | | { |
| | | public class PipeClient2 : IDisposable |
| | | { |
| | | private readonly string _pipeName; |
| | | private readonly string _serverName; |
| | | private readonly CancellationTokenSource _cancellationTokenSource; |
| | | private readonly ReaderWriterLockSlim _lock; |
| | | private NamedPipeClientStream _pipeClient; |
| | | private bool _isDisposed; |
| | | private bool _isConnected; |
| | | private Thread _receiveThread; |
| | | |
| | | // 事件定义 |
| | | public event EventHandler<PipeDataEventArgs> OnData; |
| | | public event EventHandler OnConnected; |
| | | public event EventHandler OnDisconnected; |
| | | |
| | | public PipeClient2(string pipeName, string serverName = ".") |
| | | { |
| | | _pipeName = pipeName; |
| | | _serverName = serverName; |
| | | _cancellationTokenSource = new CancellationTokenSource(); |
| | | _lock = new ReaderWriterLockSlim(); |
| | | } |
| | | |
| | | public void Connect(int timeoutMs = 5000) |
| | | { |
| | | if (_isConnected) |
| | | return; |
| | | |
| | | try |
| | | { |
| | | // 加上 PipeOptions.Asynchronous 可以异步接受服务器发送的数据,但是很影响性能 |
| | | _pipeClient = new NamedPipeClientStream(_serverName, _pipeName, PipeDirection.InOut); |
| | | |
| | | // 创建超时取消令牌 |
| | | using (var timeoutCts = new CancellationTokenSource(timeoutMs)) |
| | | { |
| | | _pipeClient.Connect(timeoutMs); |
| | | _pipeClient.ReadMode = PipeTransmissionMode.Message; |
| | | _isConnected = true; |
| | | |
| | | // 触发连接成功事件 |
| | | OnConnected?.Invoke(this, EventArgs.Empty); |
| | | |
| | | // 开始接收数据 |
| | | //_receiveThread = new Thread(ReceiveData); |
| | | //_receiveThread.Start(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | private void ReceiveData() |
| | | { |
| | | try |
| | | { |
| | | while (_isConnected && !_cancellationTokenSource.Token.IsCancellationRequested) |
| | | { |
| | | string message = _pipeClient.ReadMessage(); |
| | | if (string.IsNullOrEmpty(message)) |
| | | break; |
| | | // 触发数据接收事件 |
| | | OnData?.Invoke(this, new PipeDataEventArgs("", message)); |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | _isConnected = false; |
| | | // 触发断开连接事件 |
| | | OnDisconnected?.Invoke(this, EventArgs.Empty); |
| | | } |
| | | } |
| | | |
| | | public bool SendMessage(string message, out string response, int timeoutMs = 5000) |
| | | { |
| | | response = null; |
| | | if (!_isConnected) |
| | | return false; |
| | | try |
| | | { |
| | | |
| | | _lock.EnterWriteLock(); |
| | | try |
| | | { |
| | | _pipeClient.WriteMessage(message); |
| | | } |
| | | finally |
| | | { |
| | | _lock.ExitWriteLock(); |
| | | } |
| | | response = _pipeClient.ReadMessage(); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | response = ex.Message; |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | public void Disconnect() |
| | | { |
| | | _isConnected = false; |
| | | _cancellationTokenSource.Cancel(); |
| | | _receiveThread?.Join(1000); // 等待接收线程结束 |
| | | } |
| | | |
| | | public void Dispose() |
| | | { |
| | | if (!_isDisposed) |
| | | { |
| | | Disconnect(); |
| | | _pipeClient?.Dispose(); |
| | | _cancellationTokenSource.Dispose(); |
| | | _lock.Dispose(); |
| | | _isDisposed = true; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Concurrent; |
| | | using System.IO; |
| | | using System.IO.Pipes; |
| | | using System.Text; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace ConnectionUtil |
| | | { |
| | | public class PipeServer : IDisposable |
| | | { |
| | | private readonly string _pipeName; |
| | | private readonly int _maxNumberOfServerInstances; |
| | | private readonly CancellationTokenSource _cancellationTokenSource; |
| | | private readonly ConcurrentDictionary<string, NamedPipeServerStream> _activeConnections; |
| | | private bool _isDisposed; |
| | | |
| | | // 事件定义 |
| | | public event EventHandler<PipeDataEventArgs> OnData; |
| | | public event EventHandler<PipeConnectionEventArgs> OnNewConnection; |
| | | public event EventHandler<PipeConnectionEventArgs> OnClientDisconnected; |
| | | public event EventHandler<PipeErrorEventArgs> OnError; |
| | | |
| | | public PipeServer(string pipeName, int maxNumberOfServerInstances = NamedPipeServerStream.MaxAllowedServerInstances) |
| | | { |
| | | _pipeName = pipeName; |
| | | _maxNumberOfServerInstances = maxNumberOfServerInstances; |
| | | _cancellationTokenSource = new CancellationTokenSource(); |
| | | _activeConnections = new ConcurrentDictionary<string, NamedPipeServerStream>(); |
| | | } |
| | | |
| | | public void Start() |
| | | { |
| | | Task.Run(() => |
| | | { |
| | | try |
| | | { |
| | | while (!_cancellationTokenSource.Token.IsCancellationRequested) |
| | | { |
| | | var pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.InOut, |
| | | _maxNumberOfServerInstances, PipeTransmissionMode.Message); |
| | | try |
| | | { |
| | | pipeServer.WaitForConnection(); |
| | | |
| | | // 生成唯一的连接ID |
| | | string connectionId = Guid.NewGuid().ToString(); |
| | | _activeConnections.TryAdd(connectionId, pipeServer); |
| | | |
| | | // 触发新连接事件 |
| | | OnNewConnection?.Invoke(this, new PipeConnectionEventArgs(connectionId)); |
| | | |
| | | // 开始处理该连接的数据 |
| | | HandleClient(connectionId, pipeServer); |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | pipeServer.Dispose(); |
| | | break; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | pipeServer.Dispose(); |
| | | OnError?.Invoke(this, new PipeErrorEventArgs("等待连接时发生错误", ex)); |
| | | } |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | OnError?.Invoke(this, new PipeErrorEventArgs("服务器运行时发生错误", ex)); |
| | | } |
| | | |
| | | }); |
| | | } |
| | | |
| | | static string ReadMessage(PipeStream s) |
| | | { |
| | | MemoryStream ms = new MemoryStream(); |
| | | byte[] buffer = new byte[0x1000]; // Read in 4 KB blocks |
| | | do |
| | | { |
| | | ms.Write(buffer, 0, s.Read(buffer, 0, buffer.Length)); |
| | | } |
| | | while (!s.IsMessageComplete); |
| | | return Encoding.UTF8.GetString(ms.ToArray()); |
| | | } |
| | | |
| | | private void HandleClient(string connectionId, NamedPipeServerStream pipeServer) |
| | | { |
| | | Task.Run(() => |
| | | { |
| | | try |
| | | { |
| | | while (pipeServer.IsConnected && !_cancellationTokenSource.Token.IsCancellationRequested) |
| | | { |
| | | try |
| | | { |
| | | string message = ReadMessage(pipeServer); |
| | | if (string.IsNullOrEmpty(message)) |
| | | continue; |
| | | Console.WriteLine(message); |
| | | if (message == "ping") |
| | | { |
| | | message = "pong"; |
| | | } |
| | | else |
| | | { |
| | | message += "Response"; |
| | | } |
| | | SendMessage(connectionId, message); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | OnError?.Invoke(this, new PipeErrorEventArgs($"处理客户端 {connectionId} 数据时发生错误", ex)); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | // 清理连接 |
| | | _activeConnections.TryRemove(connectionId, out _); |
| | | pipeServer.Dispose(); |
| | | |
| | | // 触发断开连接事件 |
| | | OnClientDisconnected?.Invoke(this, new PipeConnectionEventArgs(connectionId)); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | public void SendMessage(string connectionId, string message) |
| | | { |
| | | if (_activeConnections.TryGetValue(connectionId, out var pipeServer)) |
| | | { |
| | | try |
| | | { |
| | | var msg = Encoding.UTF8.GetBytes(message); |
| | | pipeServer.Write(msg, 0, msg.Length); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | OnError?.Invoke(this, new PipeErrorEventArgs($"向客户端 {connectionId} 发送消息时发生错误", ex)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | public async Task StartAsync() |
| | | { |
| | | try |
| | | { |
| | | while (!_cancellationTokenSource.Token.IsCancellationRequested) |
| | | { |
| | | var pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.InOut, |
| | | _maxNumberOfServerInstances, PipeTransmissionMode.Message); |
| | | |
| | | try |
| | | { |
| | | await pipeServer.WaitForConnectionAsync(_cancellationTokenSource.Token); |
| | | |
| | | // 生成唯一的连接ID |
| | | string connectionId = Guid.NewGuid().ToString(); |
| | | _activeConnections.TryAdd(connectionId, pipeServer); |
| | | |
| | | // 触发新连接事件 |
| | | OnNewConnection?.Invoke(this, new PipeConnectionEventArgs(connectionId)); |
| | | |
| | | // 开始处理该连接的数据 |
| | | _ = HandleClientAsync(connectionId, pipeServer); |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | pipeServer.Dispose(); |
| | | break; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | pipeServer.Dispose(); |
| | | OnError?.Invoke(this, new PipeErrorEventArgs("等待连接时发生错误", ex)); |
| | | } |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | OnError?.Invoke(this, new PipeErrorEventArgs("服务器运行时发生错误", ex)); |
| | | } |
| | | } |
| | | |
| | | private async Task HandleClientAsync(string connectionId, NamedPipeServerStream pipeServer) |
| | | { |
| | | try |
| | | { |
| | | using (var reader = new StreamReader(pipeServer)) |
| | | using (var writer = new StreamWriter(pipeServer)) |
| | | { |
| | | writer.AutoFlush = true; |
| | | |
| | | while (pipeServer.IsConnected && !_cancellationTokenSource.Token.IsCancellationRequested) |
| | | { |
| | | try |
| | | { |
| | | string message = await reader.ReadLineAsync(); |
| | | if (string.IsNullOrEmpty(message)) |
| | | continue; |
| | | |
| | | // 触发数据接收事件 |
| | | OnData?.Invoke(this, new PipeDataEventArgs(connectionId, message)); |
| | | |
| | | // 发送响应 |
| | | await writer.WriteLineAsync($"服务器已收到消息: {message}"); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | OnError?.Invoke(this, new PipeErrorEventArgs($"处理客户端 {connectionId} 数据时发生错误", ex)); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | // 清理连接 |
| | | _activeConnections.TryRemove(connectionId, out _); |
| | | pipeServer.Dispose(); |
| | | |
| | | // 触发断开连接事件 |
| | | OnClientDisconnected?.Invoke(this, new PipeConnectionEventArgs(connectionId)); |
| | | } |
| | | } |
| | | |
| | | public async Task SendMessageAsync(string connectionId, string message) |
| | | { |
| | | if (_activeConnections.TryGetValue(connectionId, out var pipeServer)) |
| | | { |
| | | try |
| | | { |
| | | using (var writer = new StreamWriter(pipeServer)) |
| | | { |
| | | writer.AutoFlush = true; |
| | | await writer.WriteLineAsync(message); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | OnError?.Invoke(this, new PipeErrorEventArgs($"向客户端 {connectionId} 发送消息时发生错误", ex)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public void Stop() |
| | | { |
| | | _cancellationTokenSource.Cancel(); |
| | | } |
| | | |
| | | public void Dispose() |
| | | { |
| | | if (!_isDisposed) |
| | | { |
| | | Stop(); |
| | | foreach (var connection in _activeConnections.Values) |
| | | { |
| | | connection.Dispose(); |
| | | } |
| | | _activeConnections.Clear(); |
| | | _cancellationTokenSource.Dispose(); |
| | | _isDisposed = true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | public class PipeDataEventArgs : EventArgs |
| | | { |
| | | public string ConnectionId { get; } |
| | | public string Data { get; } |
| | | |
| | | public PipeDataEventArgs(string connectionId, string data) |
| | | { |
| | | ConnectionId = connectionId; |
| | | Data = data; |
| | | } |
| | | } |
| | | |
| | | public class PipeConnectionEventArgs : EventArgs |
| | | { |
| | | public string ConnectionId { get; } |
| | | |
| | | public PipeConnectionEventArgs(string connectionId) |
| | | { |
| | | ConnectionId = connectionId; |
| | | } |
| | | } |
| | | |
| | | public class PipeErrorEventArgs : EventArgs |
| | | { |
| | | public string Message { get; } |
| | | public Exception Exception { get; } |
| | | |
| | | public PipeErrorEventArgs(string message, Exception exception) |
| | | { |
| | | Message = message; |
| | | Exception = exception; |
| | | } |
| | | } |
| | | |
| | | public class StreamString |
| | | { |
| | | private Stream ioStream; |
| | | private UnicodeEncoding streamEncoding; |
| | | |
| | | public StreamString(Stream ioStream) |
| | | { |
| | | this.ioStream = ioStream; |
| | | streamEncoding = new UnicodeEncoding(); |
| | | } |
| | | |
| | | public string ReadString() |
| | | { |
| | | int len = 0; |
| | | |
| | | |
| | | len = ioStream.ReadByte() * 256; |
| | | len += ioStream.ReadByte(); |
| | | byte[] inBuffer = new byte[len]; |
| | | ioStream.Read(inBuffer, 0, len); |
| | | |
| | | return streamEncoding.GetString(inBuffer); |
| | | } |
| | | |
| | | public int WriteString(string outString) |
| | | { |
| | | byte[] outBuffer = streamEncoding.GetBytes(outString); |
| | | int len = outBuffer.Length; |
| | | if (len > UInt16.MaxValue) |
| | | { |
| | | len = (int)UInt16.MaxValue; |
| | | } |
| | | ioStream.WriteByte((byte)(len / 256)); |
| | | ioStream.WriteByte((byte)(len & 255)); |
| | | ioStream.Write(outBuffer, 0, len); |
| | | ioStream.Flush(); |
| | | |
| | | return outBuffer.Length + 2; |
| | | } |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Concurrent; |
| | | using System.IO; |
| | | using System.IO.Pipes; |
| | | using System.Text; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace ConnectionUtil |
| | | { |
| | | public static class PipeStreamUtil |
| | | { |
| | | public static string ReadMessage(this PipeStream ps) |
| | | { |
| | | |
| | | MemoryStream ms = new MemoryStream(); |
| | | byte[] buffer = new byte[0x1000]; |
| | | do |
| | | { |
| | | ms.Write(buffer, 0, ps.Read(buffer, 0, buffer.Length)); |
| | | } |
| | | while (!ps.IsMessageComplete); |
| | | return Encoding.UTF8.GetString(ms.ToArray()); |
| | | } |
| | | |
| | | public static void WriteMessage(this PipeStream ps, string message) |
| | | { |
| | | var msg = Encoding.UTF8.GetBytes(message); |
| | | ps.Write(msg, 0, msg.Length); |
| | | ps.Flush(); |
| | | } |
| | | } |
| | | |
| | | public class PipeServer2 : IDisposable |
| | | { |
| | | private readonly string _pipeName; |
| | | private readonly int _maxNumberOfServerInstances; |
| | | private readonly CancellationTokenSource _cancellationTokenSource; |
| | | private readonly ConcurrentDictionary<string, NamedPipeServerStream> _activeConnections; |
| | | private readonly ReaderWriterLockSlim _lock; |
| | | private bool _isDisposed; |
| | | private bool _isRunning; |
| | | |
| | | // 事件定义 |
| | | public event EventHandler<PipeDataEventArgs> OnData; |
| | | public event EventHandler<PipeConnectionEventArgs> OnClientConnected; |
| | | public event EventHandler<PipeConnectionEventArgs> OnClientDisconnected; |
| | | public event EventHandler<PipeErrorEventArgs> OnError; |
| | | |
| | | public PipeServer2(string pipeName, int maxNumberOfServerInstances = 254) |
| | | { |
| | | _pipeName = pipeName; |
| | | _maxNumberOfServerInstances = maxNumberOfServerInstances; |
| | | _cancellationTokenSource = new CancellationTokenSource(); |
| | | _activeConnections = new ConcurrentDictionary<string, NamedPipeServerStream>(); |
| | | _lock = new ReaderWriterLockSlim(); |
| | | } |
| | | |
| | | public void Start() |
| | | { |
| | | if (_isRunning) |
| | | return; |
| | | |
| | | _isRunning = true; |
| | | |
| | | Task.Run(() => |
| | | { |
| | | while (_isRunning && !_cancellationTokenSource.Token.IsCancellationRequested) |
| | | { |
| | | try |
| | | { |
| | | var pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.InOut, _maxNumberOfServerInstances |
| | | , PipeTransmissionMode.Message); |
| | | pipeServer.WaitForConnection(); |
| | | |
| | | string clientId = Guid.NewGuid().ToString(); |
| | | if (_activeConnections.TryAdd(clientId, pipeServer)) |
| | | { |
| | | // 启动处理客户端消息的线程 |
| | | Thread clientThread = new Thread(() => HandleClient(clientId, pipeServer)); |
| | | clientThread.Start(); |
| | | |
| | | OnClientConnected?.Invoke(this, new PipeConnectionEventArgs(clientId)); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | OnError?.Invoke(this, new PipeErrorEventArgs("Connect", ex)); |
| | | Thread.Sleep(1000); // 发生错误时等待一秒再重试 |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | |
| | | |
| | | private void HandleClient(string clientId, NamedPipeServerStream pipeServer) |
| | | { |
| | | try |
| | | { |
| | | while (!_cancellationTokenSource.Token.IsCancellationRequested) |
| | | { |
| | | try |
| | | { |
| | | // 等待客户端发送消息 |
| | | string message = pipeServer.ReadMessage(); |
| | | if (string.IsNullOrEmpty(message)) |
| | | break; |
| | | |
| | | OnData?.Invoke(this, new PipeDataEventArgs(clientId, message)); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | OnError?.Invoke(this, new PipeErrorEventArgs("HandleClientMessageError", ex)); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | if (_activeConnections.TryRemove(clientId, out var pipe)) |
| | | { |
| | | pipe.Dispose(); |
| | | } |
| | | OnClientDisconnected?.Invoke(this, new PipeConnectionEventArgs(clientId)); |
| | | } |
| | | } |
| | | |
| | | public void SendMessage(string clientId, string message) |
| | | { |
| | | if (!_activeConnections.TryGetValue(clientId, out var pipeServer)) |
| | | { |
| | | throw new InvalidOperationException($"找不到客户端 {clientId}"); |
| | | } |
| | | |
| | | _lock.EnterWriteLock(); |
| | | try |
| | | { |
| | | try |
| | | { |
| | | pipeServer.WriteMessage(message); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | OnError?.Invoke(this, new PipeErrorEventArgs($"向客户端 {clientId} 发送消息时发生错误", ex)); |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | _lock.ExitWriteLock(); |
| | | } |
| | | } |
| | | |
| | | public void BroadcastMessage(string message) |
| | | { |
| | | _lock.EnterWriteLock(); |
| | | try |
| | | { |
| | | foreach (var pipeServer in _activeConnections.Values) |
| | | { |
| | | pipeServer.WriteMessage(message); |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | _lock.ExitWriteLock(); |
| | | } |
| | | } |
| | | |
| | | public void Stop() |
| | | { |
| | | _isRunning = false; |
| | | _cancellationTokenSource.Cancel(); |
| | | } |
| | | |
| | | public void Dispose() |
| | | { |
| | | if (!_isDisposed) |
| | | { |
| | | Stop(); |
| | | foreach (var pipeServer in _activeConnections.Values) |
| | | { |
| | | pipeServer.Dispose(); |
| | | } |
| | | _activeConnections.Clear(); |
| | | _cancellationTokenSource.Dispose(); |
| | | _lock.Dispose(); |
| | | _isDisposed = true; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Reflection; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Runtime.InteropServices; |
| | | |
| | | // 有关程序集的一般信息由以下 |
| | | // 控制。更改这些特性值可修改 |
| | | // 与程序集关联的信息。 |
| | | [assembly: AssemblyTitle("ConnectionUtil")] |
| | | [assembly: AssemblyDescription("")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("")] |
| | | [assembly: AssemblyProduct("ConnectionUtil")] |
| | | [assembly: AssemblyCopyright("Copyright © 2025")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // 将 ComVisible 设置为 false 会使此程序集中的类型 |
| | | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 |
| | | //请将此类型的 ComVisible 特性设置为 true。 |
| | | [assembly: ComVisible(false)] |
| | | |
| | | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID |
| | | [assembly: Guid("d0806fa6-3f6d-4304-81b8-ead9da7cb5b2")] |
| | | |
| | | // 程序集的版本信息由下列四个值组成: |
| | | // |
| | | // 主版本 |
| | | // 次版本 |
| | | // 生成号 |
| | | // 修订号 |
| | | // |
| | | [assembly: AssemblyVersion("1.0.0.0")] |
| | | [assembly: AssemblyFileVersion("1.0.0.0")] |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Net.Sockets; |
| | | |
| | | namespace ConnectionUtil |
| | | { |
| | | public interface SCADAApi : IDisposable |
| | | { |
| | | /// <summary> |
| | | /// 连接方法 |
| | | /// </summary> |
| | | /// <param name="config"></param> |
| | | /// <param name="errMsg"></param> |
| | | /// <returns></returns> |
| | | bool Connect(object config, out string errMsg); |
| | | /// <summary> |
| | | /// IP连接Scada服务器 |
| | | /// </summary> |
| | | /// <param name="IP">MES服务器IP</param> |
| | | /// <param name="Port"> MES服务器Port口</param> |
| | | /// <param name="LocalIP">本地连接IP</param> |
| | | /// <param name="LocalPort">本地连接端口</param> |
| | | /// <returns></returns> |
| | | bool Connect(string IP, int Port, string LocalIP, int LocalPort); |
| | | |
| | | /// <summary> |
| | | /// 连接Scada管理服务器 |
| | | /// </summary> |
| | | /// <param name="IP"> Scada服务器IP</param> |
| | | /// <param name="Port">ScadPort口</param> |
| | | /// <param name="LocalPort">本地连接端口</param> |
| | | /// <param name="errMsg"></param> |
| | | /// <param name="socket"></param> |
| | | /// <returns></returns> |
| | | bool Connect2(string IP, int Port, int LocalPort, out string errMsg, Socket socket = null); |
| | | |
| | | /// <summary> |
| | | /// 连接Scada管理服务器 |
| | | /// </summary> |
| | | /// <param name="IP">ScadaManagerIP</param> |
| | | /// <param name="Port">ScadaManagerPort口</param> |
| | | /// <param name="LocalPort">本地连接端口</param> |
| | | /// <param name="errMsg"></param> |
| | | /// <returns></returns> |
| | | bool ConnectManager(string IP, int Port, int LocalPort, out string errMsg); |
| | | |
| | | /// <summary> |
| | | /// 断开服务器连接 |
| | | /// </summary> |
| | | void DisConnect(); |
| | | |
| | | /// <summary> |
| | | /// 批量写增加参数 |
| | | /// </summary> |
| | | /// <param name="id"></param> |
| | | /// <param name="par"></param> |
| | | /// <returns></returns> |
| | | bool DoAddParWriteMultiple(int id, string par); |
| | | |
| | | /// <summary> |
| | | /// 换型 |
| | | /// </summary> |
| | | /// <param name="_ProductLineName"></param> |
| | | /// <param name="_ProductName"></param> |
| | | /// <param name="errMsg"></param> |
| | | /// <param name="_WorkStationName"></param> |
| | | /// <returns></returns> |
| | | bool DoDoChangeModel(string _ProductLineName, string _ProductName, out string errMsg, string _WorkStationName = null); |
| | | |
| | | /// <summary> |
| | | /// 批量读值 |
| | | /// </summary> |
| | | /// <param name="_Id"></param> |
| | | /// <returns></returns> |
| | | string[,] DoFctReadMultiple(int[] _Id); |
| | | |
| | | /// <summary> |
| | | /// 单一读值 |
| | | /// </summary> |
| | | /// <param name="_name"></param> |
| | | /// <param name="result"></param> |
| | | /// <param name="errMsg"></param> |
| | | /// <returns></returns> |
| | | bool DoFctReadSingle(string _name, out string result, out string errMsg); |
| | | |
| | | /// <summary> |
| | | /// 单一写值 |
| | | /// </summary> |
| | | /// <param name="errMsg"></param> |
| | | /// <returns></returns> |
| | | bool DoFctWriteMultiple(out string errMsg); |
| | | |
| | | /// <summary> |
| | | /// 写单一值 |
| | | /// </summary> |
| | | /// <param name="_name"></param> |
| | | /// <param name="_value"></param> |
| | | /// <param name="errMsg"></param> |
| | | /// <returns></returns> |
| | | bool DoFctWriteSingle(string _name, string _value, out string errMsg); |
| | | |
| | | /// <summary> |
| | | /// mqtt发布消息 |
| | | /// </summary> |
| | | /// <param name="topic">主题</param> |
| | | /// <param name="_value">值</param> |
| | | /// <param name="errMsg">错误信息></param> |
| | | /// <returns></returns> |
| | | bool DoMqttPublish(string topic, string _value, out string errMsg); |
| | | |
| | | /// <summary> |
| | | /// |
| | | /// </summary> |
| | | /// <param name="_user"></param> |
| | | /// <param name="_password"></param> |
| | | /// <param name="errMsg"></param> |
| | | /// <returns></returns> |
| | | bool DoLogin(string _user, string _password, out string errMsg); |
| | | |
| | | /// <summary> |
| | | /// 重启DataServer服务器 |
| | | /// </summary> |
| | | /// <param name="errMsg"></param> |
| | | /// <returns></returns> |
| | | bool DoReSETDataServer(out string errMsg); |
| | | |
| | | /// <summary> |
| | | /// 获取动态IP |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | string getLocalIP(); |
| | | |
| | | /// <summary> |
| | | /// |
| | | /// </summary> |
| | | /// <param name="_paraMPairs"></param> |
| | | /// <param name="errMsg"></param> |
| | | /// <returns></returns> |
| | | short[,] GetLowerComputerStatus(short[] _paraMPairs, out string errMsg); |
| | | |
| | | /// <summary> |
| | | /// 初始化批量写参数 |
| | | /// </summary> |
| | | void InitParWriteMultiple(); |
| | | |
| | | /// <summary> |
| | | /// 检查与LTSCADA是否断开连接了 |
| | | /// </summary> |
| | | /// <param name="errMsg"></param> |
| | | /// <returns></returns> |
| | | bool IsConnected(out string errMsg); |
| | | |
| | | /// <summary> |
| | | /// 批量写 |
| | | /// </summary> |
| | | /// <param name="_paraMPairs">参数列表</param> |
| | | /// <param name="errMsg">错误信息</param> |
| | | /// <returns>处理结果</returns> |
| | | bool ToFctWriteMultiple(Dictionary<int, string> _paraMPairs, out string errMsg); |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Net.Sockets; |
| | | using System.Text; |
| | | using System.Threading; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace ConnectionUtil |
| | | { |
| | | /// <summary> |
| | | /// TCP客户端包装类 |
| | | /// </summary> |
| | | /// <summary> |
| | | /// TCP客户端包装类 |
| | | /// </summary> |
| | | public class TcpClientWrapper : IDisposable |
| | | { |
| | | private System.Net.Sockets.TcpClient _client; |
| | | private NetworkStream _stream; |
| | | private readonly string _host; |
| | | private readonly int _port; |
| | | private bool _isConnected; |
| | | private readonly CancellationTokenSource _cts; |
| | | private Task _heartbeatTask; |
| | | private readonly SemaphoreSlim _sendReceiveLock; |
| | | |
| | | /// <summary> |
| | | /// 初始化TCP客户端 |
| | | /// </summary> |
| | | /// <param name="host">服务器地址</param> |
| | | /// <param name="port">服务器端口</param> |
| | | public TcpClientWrapper(string host = "127.0.0.1", int port = 6543) |
| | | { |
| | | _host = host; |
| | | _port = port; |
| | | _cts = new CancellationTokenSource(); |
| | | _sendReceiveLock = new SemaphoreSlim(1, 1); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 异步连接到服务器 |
| | | /// </summary> |
| | | /// <returns>连接是否成功</returns> |
| | | public async Task<bool> ConnectAsync() |
| | | { |
| | | try |
| | | { |
| | | _client = new System.Net.Sockets.TcpClient(); |
| | | await _client.ConnectAsync(_host, _port); |
| | | _stream = _client.GetStream(); |
| | | _isConnected = true; |
| | | |
| | | // 启动心跳任务 |
| | | _heartbeatTask = StartHeartbeatAsync(_cts.Token); |
| | | |
| | | Console.WriteLine($"成功连接到服务器 {_host}:{_port}"); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Console.WriteLine($"连接服务器失败:{ex.Message}"); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 同步连接到服务器 |
| | | /// </summary> |
| | | /// <returns>连接是否成功</returns> |
| | | public bool Connect() |
| | | { |
| | | try |
| | | { |
| | | _client = new System.Net.Sockets.TcpClient(); |
| | | _client.Connect(_host, _port); |
| | | _stream = _client.GetStream(); |
| | | _isConnected = true; |
| | | |
| | | // 启动心跳任务 |
| | | _heartbeatTask = StartHeartbeatAsync(_cts.Token); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Console.WriteLine($"连接服务器失败:{ex.Message}"); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 启动心跳检测 |
| | | /// </summary> |
| | | private async Task StartHeartbeatAsync(CancellationToken token) |
| | | { |
| | | while (!token.IsCancellationRequested && _isConnected) |
| | | { |
| | | try |
| | | { |
| | | await Task.Delay(20000, token); // 20秒发送一次心跳 |
| | | if (_isConnected) |
| | | { |
| | | await SendMessageAsync("ping"); |
| | | } |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | break; |
| | | } |
| | | catch (Exception) |
| | | { |
| | | _isConnected = false; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 发送消息并接收响应 |
| | | /// </summary> |
| | | /// <typeparam name="T">响应数据类型</typeparam> |
| | | /// <param name="data">要发送的数据对象</param> |
| | | /// <param name="timeout">超时时间</param> |
| | | /// <returns>服务器响应数据</returns> |
| | | public async Task<string> SendMessageAsync(string message, TimeSpan? timeout = null) |
| | | { |
| | | if (!_isConnected) |
| | | { |
| | | throw new InvalidOperationException("未连接到服务器"); |
| | | } |
| | | |
| | | TimeSpan actualTimeout = timeout ?? TimeSpan.FromSeconds(30); |
| | | |
| | | if (!await _sendReceiveLock.WaitAsync(actualTimeout)) |
| | | { |
| | | throw new TimeoutException("等待发送超时"); |
| | | } |
| | | |
| | | try |
| | | { |
| | | byte[] messageBytes = Encoding.UTF8.GetBytes(message + "\r\n"); |
| | | |
| | | // 发送消息 |
| | | await _stream.WriteAsync(messageBytes, 0, messageBytes.Length); |
| | | |
| | | // 接收响应 |
| | | byte[] buffer = new byte[1024]; |
| | | StringBuilder response = new StringBuilder(); |
| | | int bytesRead; |
| | | |
| | | CancellationTokenSource cts = new CancellationTokenSource(actualTimeout); |
| | | |
| | | try |
| | | { |
| | | // 读取直到遇到换行符 |
| | | while ((bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length)) > 0) |
| | | { |
| | | string received = Encoding.UTF8.GetString(buffer, 0, bytesRead); |
| | | response.Append(received); |
| | | |
| | | if (received.EndsWith("\r\n")) |
| | | { |
| | | break; |
| | | } |
| | | |
| | | if (cts.Token.IsCancellationRequested) |
| | | { |
| | | throw new TimeoutException("接收响应超时"); |
| | | } |
| | | } |
| | | } |
| | | finally |
| | | { |
| | | cts.Dispose(); |
| | | } |
| | | |
| | | string jsonResponse = response.ToString().TrimEnd('\r', '\n'); |
| | | Console.WriteLine($"收到服务器响应:{jsonResponse}"); |
| | | |
| | | // 反序列化响应 |
| | | return jsonResponse; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Console.WriteLine($"发送消息失败:{ex.Message}"); |
| | | _isConnected = false; |
| | | throw; |
| | | } |
| | | finally |
| | | { |
| | | _sendReceiveLock.Release(); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 同步发送消息并接收响应 |
| | | /// </summary> |
| | | /// <typeparam name="T">响应数据类型</typeparam> |
| | | /// <param name="data">要发送的数据对象</param> |
| | | /// <param name="timeout">超时时间</param> |
| | | /// <returns>服务器响应数据</returns> |
| | | public string SendMessage(string message, TimeSpan? timeout = null) |
| | | { |
| | | timeout = timeout ?? TimeSpan.FromSeconds(30); |
| | | |
| | | try |
| | | { |
| | | // 使用 Task.Run 来执行异步操作,并等待结果 |
| | | return Task.Run(() => SendMessageAsync(message, timeout)).Result; |
| | | } |
| | | catch (AggregateException ae) |
| | | { |
| | | // 解包异常 |
| | | throw ae.InnerException; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查是否已连接 |
| | | /// </summary> |
| | | public bool IsConnected |
| | | { |
| | | get { return _isConnected; } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 释放资源 |
| | | /// </summary> |
| | | public void Dispose() |
| | | { |
| | | _cts.Cancel(); |
| | | _isConnected = false; |
| | | if (_stream != null) |
| | | { |
| | | _stream.Dispose(); |
| | | } |
| | | if (_client != null) |
| | | { |
| | | _client.Dispose(); |
| | | } |
| | | _cts.Dispose(); |
| | | if (_sendReceiveLock != null) |
| | | { |
| | | _sendReceiveLock.Dispose(); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 关闭连接 |
| | | /// </summary> |
| | | public void Close() |
| | | { |
| | | Dispose(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8" ?> |
| | | <configuration> |
| | | <startup> |
| | | <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> |
| | | </startup> |
| | | </configuration> |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| | | <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
| | | <PropertyGroup> |
| | | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
| | | <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
| | | <ProjectGuid>{2BDDB984-199B-4913-96BB-39C252E101F2}</ProjectGuid> |
| | | <OutputType>Exe</OutputType> |
| | | <RootNamespace>ConsoleTest</RootNamespace> |
| | | <AssemblyName>ConsoleTest</AssemblyName> |
| | | <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> |
| | | <FileAlignment>512</FileAlignment> |
| | | <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> |
| | | <Deterministic>true</Deterministic> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
| | | <PlatformTarget>AnyCPU</PlatformTarget> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <DebugType>full</DebugType> |
| | | <Optimize>false</Optimize> |
| | | <OutputPath>bin\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
| | | <PlatformTarget>AnyCPU</PlatformTarget> |
| | | <DebugType>pdbonly</DebugType> |
| | | <Optimize>true</Optimize> |
| | | <OutputPath>bin\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <OutputPath>bin\x64\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <DebugType>full</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <Prefer32Bit>true</Prefer32Bit> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> |
| | | <OutputPath>bin\x64\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <Optimize>true</Optimize> |
| | | <DebugType>pdbonly</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <Prefer32Bit>true</Prefer32Bit> |
| | | </PropertyGroup> |
| | | <ItemGroup> |
| | | <Reference Include="System" /> |
| | | <Reference Include="System.Core" /> |
| | | <Reference Include="System.Xml.Linq" /> |
| | | <Reference Include="System.Data.DataSetExtensions" /> |
| | | <Reference Include="Microsoft.CSharp" /> |
| | | <Reference Include="System.Data" /> |
| | | <Reference Include="System.Net.Http" /> |
| | | <Reference Include="System.Xml" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="Program.cs" /> |
| | | <Compile Include="Properties\AssemblyInfo.cs" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <None Include="App.config" /> |
| | | <None Include="Properties\app.manifest" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <ProjectReference Include="..\ModBusTcpController\ModBusTcpController.csproj"> |
| | | <Project>{49db0c9b-b239-4608-a91e-f5ed43afaf96}</Project> |
| | | <Name>ModBusTcpController</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\SimpleUtil\DIOController\DIOController.csproj"> |
| | | <Project>{34ffbafe-478f-4b6e-95e7-92a949011a9b}</Project> |
| | | <Name>DIOController</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\SimpleUtil\HMI.Util.Base\HMI.Util.Base.csproj"> |
| | | <Project>{d59e25d1-9b27-46a1-8438-92661dd23fae}</Project> |
| | | <Name>HMI.Util.Base</Name> |
| | | </ProjectReference> |
| | | </ItemGroup> |
| | | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| | | </Project> |
| New file |
| | |
| | | using HMI.Util.Base; |
| | | using ModBusTcp; |
| | | using System; |
| | | using System.Text; |
| | | |
| | | namespace ConsoleTest |
| | | { |
| | | class Program |
| | | { |
| | | static void Main(string[] args) |
| | | { |
| | | string msg; |
| | | ModBusTcpController controller = new ModBusTcpController("127.0.0.1", 502); |
| | | if (controller.Start(out msg)) |
| | | { |
| | | bool res; |
| | | int? target; |
| | | |
| | | controller.WriteBool("1", true, out msg); |
| | | controller.ReadDOBool("1", out res, out msg); |
| | | Console.WriteLine(res); |
| | | controller.ReadDOInt("1", out target, out msg); |
| | | Console.WriteLine(target); |
| | | controller.WriteInt("1", 1, out msg); |
| | | controller.ReadDOInt("1", out target, out msg); |
| | | Console.WriteLine(target); |
| | | } |
| | | controller.Close(); |
| | | |
| | | //AlarmUtil.GetProcessIdByName("TestPlatform"); |
| | | |
| | | /// ModbusTcp |
| | | //IDIOController controller = DIOControllerCreator |
| | | // .CreateController("C:\\Workspace\\VisualStudio\\WPFProject\\SimpleUtil\\DIOController\\bin\\Release\\modbus.ini"); |
| | | //bool s; |
| | | //controller.Start(out string msg); |
| | | //controller.ReadDIBool("0", out bool res, out msg); |
| | | //controller.ReadDOBool("0", out res, out msg); |
| | | //controller.WriteBool("0", true, out msg); |
| | | |
| | | //s = controller.WriteBool("100", new bool[] { false, false }, out msg); |
| | | //s = controller.ReadDOBool("100", 2, out bool[] ress, out msg); |
| | | |
| | | //controller.Dispose(); |
| | | |
| | | ///// 7432 |
| | | //controller = controller = DIOControllerCreator |
| | | // .CreateController("C:\\Workspace\\VisualStudio\\WPFProject\\SimpleUtil\\DIOController\\bin\\Release\\7432.ini"); |
| | | //controller.Start(out msg); |
| | | //controller.ReadDIBool("0", out res, out msg); |
| | | //controller.ReadDOBool("0", out res, out msg); |
| | | //controller.WriteBool("0", true, out msg); |
| | | //controller.Dispose(); |
| | | //string recvstr = "62 FD 82 39 39 39 37 32 32 36 36 30 38 34 33 39 35 33 38 53 38 44 41 30 30 32 32"; |
| | | //string recvData; |
| | | //if (recvstr.Contains("FF")) |
| | | //{ |
| | | // recvData = recvstr.Replace(" ", "").Substring(6); |
| | | //} |
| | | //else |
| | | //{ |
| | | // byte[] bus = HexToByteArray(recvstr.Replace(" ", "").Substring(6)); |
| | | // recvData = Encoding.Default.GetString(bus); |
| | | //} |
| | | } |
| | | |
| | | public static byte[] HexToByteArray(string hexString) |
| | | { |
| | | hexString = hexString.Replace(" ", ""); |
| | | byte[] buffer = new byte[hexString.Length / 2]; |
| | | for (int i = 0; i < buffer.Length; i++) |
| | | { |
| | | buffer[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); |
| | | } |
| | | return buffer; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Reflection; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Runtime.InteropServices; |
| | | |
| | | // 有关程序集的一般信息由以下 |
| | | // 控制。更改这些特性值可修改 |
| | | // 与程序集关联的信息。 |
| | | [assembly: AssemblyTitle("ConsoleTest")] |
| | | [assembly: AssemblyDescription("")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("")] |
| | | [assembly: AssemblyProduct("ConsoleTest")] |
| | | [assembly: AssemblyCopyright("Copyright © 2025")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // 将 ComVisible 设置为 false 会使此程序集中的类型 |
| | | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 |
| | | //请将此类型的 ComVisible 特性设置为 true。 |
| | | [assembly: ComVisible(false)] |
| | | |
| | | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID |
| | | [assembly: Guid("2bddb984-199b-4913-96bb-39c252e101f2")] |
| | | |
| | | // 程序集的版本信息由下列四个值组成: |
| | | // |
| | | // 主版本 |
| | | // 次版本 |
| | | // 生成号 |
| | | // 修订号 |
| | | // |
| | | [assembly: AssemblyVersion("1.0.0.0")] |
| | | [assembly: AssemblyFileVersion("1.0.0.0")] |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> |
| | | <assemblyIdentity version="1.0.0.0" name="MyApplication.app" /> |
| | | <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"> |
| | | <security> |
| | | <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"> |
| | | <!-- UAC 清单选项 |
| | | 如果想要更改 Windows 用户帐户控制级别,请使用 |
| | | 以下节点之一替换 requestedExecutionLevel 节点。 |
| | | |
| | | <requestedExecutionLevel level="asInvoker" uiAccess="false" /> |
| | | <requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> |
| | | <requestedExecutionLevel level="highestAvailable" uiAccess="false" /> |
| | | |
| | | 指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。 |
| | | 如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此 |
| | | 元素。 |
| | | --> |
| | | <requestedExecutionLevel level="requireAdministrator" uiAccess="false" /> |
| | | </requestedPrivileges> |
| | | <applicationRequestMinimum> |
| | | <defaultAssemblyRequest permissionSetReference="Custom" /> |
| | | <PermissionSet class="System.Security.PermissionSet" version="1" ID="Custom" SameSite="site" Unrestricted="true" /> |
| | | </applicationRequestMinimum> |
| | | </security> |
| | | </trustInfo> |
| | | <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> |
| | | <application> |
| | | <!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的 |
| | | Windows 版本的列表。取消评论适当的元素, |
| | | Windows 将自动选择最兼容的环境。 --> |
| | | <!-- Windows Vista --> |
| | | <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />--> |
| | | <!-- Windows 7 --> |
| | | <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />--> |
| | | <!-- Windows 8 --> |
| | | <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />--> |
| | | <!-- Windows 8.1 --> |
| | | <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />--> |
| | | <!-- Windows 10 --> |
| | | <!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />--> |
| | | </application> |
| | | </compatibility> |
| | | <!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行 |
| | | 自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需 |
| | | 选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应 |
| | | 在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。 |
| | | |
| | | 将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation --> |
| | | <!-- |
| | | <application xmlns="urn:schemas-microsoft-com:asm.v3"> |
| | | <windowsSettings> |
| | | <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> |
| | | <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware> |
| | | </windowsSettings> |
| | | </application> |
| | | --> |
| | | <!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) --> |
| | | <!-- |
| | | <dependency> |
| | | <dependentAssembly> |
| | | <assemblyIdentity |
| | | type="win32" |
| | | name="Microsoft.Windows.Common-Controls" |
| | | version="6.0.0.0" |
| | | processorArchitecture="*" |
| | | publicKeyToken="6595b64144ccf1df" |
| | | language="*" |
| | | /> |
| | | </dependentAssembly> |
| | | </dependency> |
| | | --> |
| | | </assembly> |
| New file |
| | |
| | | <hb:PluginControl x:Class="HMI.AlarmInfoManage.AlarmInfoManage" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | xmlns:local="clr-namespace:HMI.AlarmInfoManage" |
| | | xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | mc:Ignorable="d" |
| | | Background="{DynamicResource WorkspaceBackground}" |
| | | Loaded="UserControl_Loaded" |
| | | d:DesignHeight="450" d:DesignWidth="800"> |
| | | <UserControl.Resources> |
| | | <ResourceDictionary> |
| | | <ResourceDictionary.MergedDictionaries> |
| | | <ResourceDictionary Source="/HMI.UI;component/Style/PanuonListView.xaml"/> |
| | | </ResourceDictionary.MergedDictionaries> |
| | | </ResourceDictionary> |
| | | </UserControl.Resources> |
| | | <Border Style="{StaticResource CardBoarder}"> |
| | | <Grid> |
| | | <Grid.RowDefinitions> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition/> |
| | | </Grid.RowDefinitions> |
| | | <DockPanel Grid.Row="0" Margin="0,0,0,10"> |
| | | <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}"> |
| | | <Button Style="{StaticResource SettingBtn}" x:Name="settingBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource RfhBtn}" x:Name="refreshBtn" Click="Button_Click"/> |
| | | <!--<Button Style="{StaticResource ImportBtn}" x:Name="importBtn" Click="Button_Click"/>--> |
| | | </StackPanel> |
| | | |
| | | <!--<TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" |
| | | VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Alm.Title}"/>--> |
| | | <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" |
| | | VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Alm.Record}"/> |
| | | </DockPanel> |
| | | <DockPanel Grid.Row="1"> |
| | | <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right"> |
| | | <pu:Pagination |
| | | Height="36" Margin="10" |
| | | HorizontalAlignment="Left" |
| | | Foreground="Black" |
| | | ItemsBackground="Transparent" |
| | | ItemsCornerRadius="12" |
| | | ItemsSelectedBackground="{DynamicResource SecondPrimaryColor}" |
| | | x:Name="page" |
| | | CurrentPage="{Binding CurrentPage}" |
| | | MaxPage="{Binding MaxPage}"/> |
| | | </StackPanel> |
| | | <!--<ListView x:Name="table" Style="{StaticResource DefaultListTable}" |
| | | VirtualizingPanel.VirtualizationMode="Standard" |
| | | ItemsSource="{Binding Datasource}"> |
| | | <ListView.View> |
| | | <GridView> |
| | | <GridViewColumn Width="50"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="#"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource lvRowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="300"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Source}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding Source}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="500"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Code}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding AlarmCode}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="800"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Info}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding AlarmInfo}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="200"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Time}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding Time}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | </GridView> |
| | | </ListView.View> |
| | | </ListView>--> |
| | | <DataGrid x:Name="table" Style="{StaticResource DefaultDataGrid}" |
| | | dp:DataGridHelper.ShowRowNumber="True" |
| | | VirtualizingPanel.VirtualizationMode="Standard" |
| | | ItemsSource="{Binding Datasource}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTextColumn Header="#" |
| | | Binding="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}, Mode=FindAncestor}}" |
| | | Width="40"/> |
| | | <DataGridTextColumn Width="200" Binding="{Binding Source}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Source}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="200" Binding="{Binding AlarmCode}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Code}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="*" Binding="{Binding AlarmInfo}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Info}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="200" Binding="{Binding Time}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Time}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | |
| | | <!--<DataGrid x:Name="table" Style="{StaticResource DefaultDataGrid}" ItemsSource="{Binding Datasource}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | <DataGridTemplateColumn Header="#" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding Source}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Source}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding AlarmCode}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Code}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding AlarmInfo}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Info}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding Time}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Time}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid>--> |
| | | </DockPanel> |
| | | |
| | | <!--<TabControl Grid.Row="1" Style="{StaticResource HeaderTabControl}"> |
| | | <TabItem Header=""> |
| | | |
| | | </TabItem> |
| | | <TabItem Header="报警信息"> |
| | | <Grid> |
| | | <Grid.RowDefinitions> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition/> |
| | | </Grid.RowDefinitions> |
| | | |
| | | <DataGrid Grid.Row="1" x:Name="alarmTextTable" Visibility="Hidden" Style="{x:Null}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="" Width="50"/> |
| | | <DataGridTextColumn x:Name="sb1" Width="*" Binding="{Binding AlarmCode}"/> |
| | | <DataGridTextColumn x:Name="sb2" Width="*" Binding="{Binding MAlarmText_CN}"/> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | <ListView Grid.Row="1" Style="{StaticResource DefaultListTable}" ItemsSource="{Binding AlarmTexts}"> |
| | | <ListView.View> |
| | | <GridView> |
| | | <GridViewColumn Width="50"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="#"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource lvRowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="{Binding ActualWidth,ElementName=sb1}"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Code}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding AlarmCode}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="{Binding ActualWidth,Converter={StaticResource sbC},ConverterParameter=50,ElementName=sb2}"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Info}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding MAlarmText_CN}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | </GridView> |
| | | </ListView.View> |
| | | </ListView> |
| | | </Grid> |
| | | </TabItem> |
| | | </TabControl>--> |
| | | </Grid> |
| | | </Border> |
| | | </hb:PluginControl> |
| New file |
| | |
| | | using HMI.Base; |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using HMI.UI.Util; |
| | | using HMI.Util; |
| | | using HMI.Util.Alarm; |
| | | using Microsoft.Win32; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.Data; |
| | | using System.IO; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.AlarmInfoManage |
| | | { |
| | | /// <summary> |
| | | /// UserControl1.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class AlarmInfoManage : PluginControl |
| | | { |
| | | private string alarmTextPath; |
| | | |
| | | |
| | | private string dbPath; |
| | | |
| | | private SqliteHelper sqliteHelper; |
| | | |
| | | //private string opcIP = "127.0.0.1"; |
| | | //private int opcPort = 6543; |
| | | //private int localPort = 18888; |
| | | //private SCADAApi lTSCADAApi = new OPCClient(); |
| | | |
| | | private ObservableCollection<AlarmText> datasource = new ObservableCollection<AlarmText>(); |
| | | |
| | | public ObservableCollection<AlarmText> Datasource |
| | | { |
| | | get { return datasource; } |
| | | set { Set(ref datasource, value); } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 报警文本列表 |
| | | /// </summary> |
| | | private ObservableCollection<AlarmText> alarmTexts = new ObservableCollection<AlarmText>(); |
| | | public ObservableCollection<AlarmText> AlarmTexts |
| | | { |
| | | get { return alarmTexts; } |
| | | set { Set(ref alarmTexts, value); } |
| | | } |
| | | |
| | | private double size = 30; |
| | | |
| | | private int currentPage = 0; |
| | | public int CurrentPage |
| | | { |
| | | get => currentPage; |
| | | set |
| | | { |
| | | Set(ref currentPage, value); |
| | | RefreshData(); |
| | | } |
| | | } |
| | | |
| | | private int maxPage = 0; |
| | | public int MaxPage |
| | | { |
| | | get => maxPage; |
| | | set |
| | | { |
| | | Set(ref maxPage, value); |
| | | } |
| | | } |
| | | |
| | | public AlarmInfoManage() |
| | | { |
| | | DataContext = this; |
| | | InitializeComponent(); |
| | | } |
| | | |
| | | public override void SetBaseFolder(string folder) |
| | | { |
| | | base.SetBaseFolder(folder); |
| | | dbPath = Path.Combine(BaseFolder, "alarm.db"); |
| | | alarmTextPath = Path.Combine(BaseFolder, "AlarmText.data"); |
| | | } |
| | | |
| | | public override void InitFinish() |
| | | { |
| | | if (!File.Exists(dbPath)) |
| | | { |
| | | SqliteHelper.CreateDatabase(dbPath); |
| | | } |
| | | sqliteHelper = new SqliteHelper(dbPath); |
| | | sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS Alarm ( |
| | | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| | | source TEXT NOT NULL, |
| | | alarmcode TEXT, |
| | | alarminfo TEXT, |
| | | time DATETIME);"); |
| | | |
| | | LoadAlarmText(); |
| | | |
| | | AlarmUtil.OnAlarm += AlarmUtil_OnAlarm; |
| | | //if (!lTSCADAApi.Connect2(opcIP, opcPort, localPort, out string errMsg)) |
| | | //{ |
| | | //} |
| | | //else |
| | | //{ |
| | | // this.MonitorTimer.Start(); |
| | | //} |
| | | } |
| | | |
| | | private void AlarmUtil_OnAlarm(object sender, List<AlarmText> e) |
| | | { |
| | | foreach (AlarmText alarm in e) |
| | | { |
| | | var sql = string.Empty; |
| | | if (alarm.IsPLCAlarm) |
| | | { |
| | | var currentInfo = alarm.AlarmInfo; |
| | | sql = string.Format("INSERT INTO Alarm (source, alarmcode, alarminfo, time) VALUES('{0}','{1}','{2}','{3}');", |
| | | alarm.Source, alarm.AlarmCode, currentInfo, alarm.Time); |
| | | } |
| | | else |
| | | { |
| | | sql = string.Format("INSERT INTO Alarm (source, alarmcode, alarminfo, time) VALUES('{0}','{1}','{2}','{3}');", |
| | | alarm.Source, alarm.AlarmCode, alarm.AlarmInfo, alarm.Time); |
| | | } |
| | | sqliteHelper.ExecuteSql(sql); |
| | | } |
| | | } |
| | | |
| | | private void Button_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | Button btn = sender as Button; |
| | | switch (btn.Name) |
| | | { |
| | | case "settingBtn": |
| | | OpenSettingWindow(); |
| | | break; |
| | | case "refreshBtn": |
| | | LoadAlarmText(); |
| | | RefreshPage(CurrentPage); |
| | | break; |
| | | case "importBtn": |
| | | ImportText(); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | private void OpenSettingWindow() |
| | | { |
| | | AlarmSettingList listModal = new AlarmSettingList(log) |
| | | { |
| | | Owner = Window.GetWindow(this) |
| | | }; |
| | | listModal.ShowDialog(); |
| | | } |
| | | |
| | | private void RefreshPage(int? page = null) |
| | | { |
| | | var sql = "SELECT COUNT (*) as num FROM Alarm;"; |
| | | var dt = sqliteHelper.GetDataTable(sql); |
| | | if (dt.Rows.Count <= 0) |
| | | { |
| | | CurrentPage = 1; |
| | | MaxPage = 1; |
| | | return; |
| | | } |
| | | int count = int.Parse(dt.Rows[0]["num"].ToString()); |
| | | MaxPage = (int)Math.Ceiling(count / size); |
| | | |
| | | if (page != null) |
| | | { |
| | | CurrentPage = page.Value; |
| | | } |
| | | else if (CurrentPage > MaxPage) |
| | | { |
| | | CurrentPage = MaxPage; |
| | | } |
| | | } |
| | | |
| | | private void RefreshData() |
| | | { |
| | | var list = new ObservableCollection<AlarmText>(); |
| | | var sql = $"SELECT source, alarmcode, alarminfo, time FROM Alarm ORDER BY time DESC LIMIT ({CurrentPage - 1} * {size}), {size};"; |
| | | var dt = sqliteHelper.GetDataTable(sql); |
| | | if (dt.Rows.Count > 0) |
| | | { |
| | | foreach (DataRow dr in dt.Rows) |
| | | { |
| | | var at = new AlarmText |
| | | { |
| | | Source = dr["source"].ToString(), |
| | | AlarmCode = dr["alarmcode"].ToString(), |
| | | AlarmInfo = dr["alarminfo"].ToString(), |
| | | Time = dr["time"].ToString(), |
| | | }; |
| | | list.Add(at); |
| | | } |
| | | } |
| | | Datasource = list; |
| | | } |
| | | |
| | | private void ImportText() |
| | | { |
| | | try |
| | | { |
| | | OpenFileDialog openDialog = new OpenFileDialog(); |
| | | openDialog.Filter = "Files (*.xlsm)|*.xlsm"; |
| | | if (openDialog.ShowDialog() == true) |
| | | { |
| | | this.ImportExcelAlarmText(openDialog.FileName); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("AlarmText Import Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{this.lang("App.ImportFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 导入报警代码文本 |
| | | /// </summary> |
| | | /// <param name="excelFile">电子设计表格文件</param> |
| | | //private void ImportExcelAlarmText(string excelFile) |
| | | //{ |
| | | // int index = 0; |
| | | |
| | | // try |
| | | // { |
| | | // if (excelFile.IndexOf(".xlsm") < 0) |
| | | // { |
| | | // this.Warning(this.lang("Alm.OnlySupportXlsm")); |
| | | // return; |
| | | // } |
| | | |
| | | // this.alarmTexts.Clear(); |
| | | // XSSFWorkbook workbook = new XSSFWorkbook(excelFile); |
| | | // XSSFSheet sheet = (XSSFSheet)workbook.GetSheet("sheet1"); |
| | | // XSSFRow row = null; |
| | | |
| | | // for (int i = 2; i < (sheet.LastRowNum + 1); i++) |
| | | // { |
| | | // index = i; |
| | | // row = (XSSFRow)sheet.GetRow(i); |
| | | // if (row != null) |
| | | // { |
| | | // AlarmText alarm = new AlarmText(); |
| | | // alarm.AlarmCode = row.Cells[0].StringCellValue; |
| | | // alarm.OpNo = row.Cells[1].StringCellValue; |
| | | // alarm.ModuleNo = row.Cells[2].StringCellValue; |
| | | // alarm.ModuleName_CN = row.Cells[3].StringCellValue; |
| | | // alarm.ModuleName_EN = row.Cells[4].StringCellValue; |
| | | // alarm.MAlarmCode = row.Cells[5].StringCellValue; |
| | | // alarm.MAlarmText_CN = row.Cells[6].StringCellValue; |
| | | // alarm.MAlarmText_EN = row.Cells[7].StringCellValue; |
| | | // this.alarmTexts.Add(alarm); |
| | | // } |
| | | // } |
| | | |
| | | // if (sheet.LastRowNum > 0) |
| | | // { |
| | | // XmlHelper.SerializeToXml(textSettingPath, this.alarmTexts); |
| | | // } |
| | | |
| | | // this.Success(this.lang("App.ImportSuccess")); |
| | | // } |
| | | // catch (Exception ex) |
| | | // { |
| | | // log.Error($"Row {index} AlarmText Import Failed.", ex); |
| | | // this.Error($"{this.lang("Alm.RowImportFailed", index)}: {ex.Message}"); |
| | | // } |
| | | //} |
| | | |
| | | /// <summary> |
| | | /// 导入报警代码文本 |
| | | /// </summary> |
| | | /// <param name="excelFile">电子设计表格文件</param> |
| | | private void ImportExcelAlarmText(string excelFile) |
| | | { |
| | | int index = 0; |
| | | |
| | | try |
| | | { |
| | | if (excelFile.IndexOf(".xlsm") < 0) |
| | | { |
| | | this.Warning(this.lang("Alm.OnlySupportXlsm")); |
| | | return; |
| | | } |
| | | |
| | | this.alarmTexts = new ObservableCollection<AlarmText> |
| | | (ExcelHelper.ReadFromExcel<AlarmText>(excelFile)); |
| | | |
| | | if (alarmTexts.Count > 0) |
| | | { |
| | | XmlHelper.SerializeToXml(alarmTextPath, this.alarmTexts); |
| | | } |
| | | LoadAlarmText(); |
| | | this.Success(this.lang("App.ImportSuccess")); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"Row {index} AlarmText Import Failed.", ex); |
| | | this.Error($"{this.lang("Alm.RowImportFailed", index)}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void LoadAlarmText() |
| | | { |
| | | try |
| | | { |
| | | if (string.IsNullOrEmpty(alarmTextPath) || !File.Exists(alarmTextPath)) |
| | | { |
| | | AlarmTexts = new ObservableCollection<AlarmText>(); |
| | | } |
| | | else |
| | | { |
| | | AlarmTexts = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmText>>(alarmTextPath); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("LoadAlarmText Error.", ex); |
| | | AlarmTexts = new ObservableCollection<AlarmText>(); |
| | | } |
| | | } |
| | | |
| | | private void UserControl_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | //if (!string.IsNullOrEmpty(systemSetting.PlcIp)) |
| | | //{ |
| | | // if (this.plc.isConnection) |
| | | // { |
| | | // this.plc.Simens_S7_ConnectPLC(systemSetting.PlcIp, 102, 0, 1, string.Empty); |
| | | // } |
| | | //} |
| | | RefreshPage(1); |
| | | Loaded -= UserControl_Loaded; |
| | | } |
| | | |
| | | public override void DoClose(object sender, EventArgs e) |
| | | { |
| | | // AlarmUtil.OnAlarm -= AlarmUtil_OnAlarm; |
| | | } |
| | | |
| | | //private void MonitorTimer_Elapsed_bak(object sender, ElapsedEventArgs e) |
| | | //{ |
| | | // try |
| | | // { |
| | | // this.MonitorTimer.Enabled = false; |
| | | // isHaveAlarm = false; |
| | | // foreach (var item in signAlarmSetting) |
| | | // { |
| | | // //isHaveAlarm = isHaveAlarm || |
| | | // // this.plc.Simens_S7_ReadBool_DB(item.DBAddress, item.StartAddress, item.Length); |
| | | // } |
| | | |
| | | // ////有报警 |
| | | // if (isHaveAlarm) |
| | | // { |
| | | // this.isNewAlarm = false; |
| | | // this.currentAlarmCachePlc.Clear(); |
| | | // RefreshDBSetting(); |
| | | // for (int i = 0; i < ((int)alarmCodeBytes.Length / 4); i++) |
| | | // { |
| | | // int code = S7.GetDIntAt(alarmCodeBytes, 4 * i); |
| | | // ////有报警 |
| | | // if (code != 0) |
| | | // { |
| | | // string acode = string.Format("{0:d10}", code); |
| | | // alarm = AlarmTexts.FirstOrDefault(item => item.AlarmCode == acode); |
| | | // if (alarm != null) |
| | | // { |
| | | // alarm.Source = "PLC"; |
| | | // alarm.Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); |
| | | // this.currentAlarmCachePlc.Add(alarm); |
| | | // ////有新报警 |
| | | // if (this.preAlarmCachePlc.Find(alarm => alarm.AlarmCode == acode) == null) |
| | | // { |
| | | // this.isNewAlarm = true; |
| | | // this.SendLogMsg(alarm.ModuleName_CN + alarm.MAlarmText_CN); |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | |
| | | // ////有新报警和报警减少时刷新报警表 |
| | | // if (this.isNewAlarm || (this.currentAlarmCachePlc.Count != this.preAlarmCachePlc.Count)) |
| | | // { |
| | | // this.currentAlarmCachePlcToMain = XmlHelper.DeepClone(this.currentAlarmCachePlc); |
| | | // this.RefershAlarm(); |
| | | // } |
| | | |
| | | // this.preAlarmCachePlc.Clear(); |
| | | // this.preAlarmCachePlc = XmlHelper.DeepClone(this.currentAlarmCachePlc); |
| | | // } |
| | | // else |
| | | // { |
| | | // if (this.preAlarmCachePlc.Count != 0 || this.currentAlarmCachePlcToMain.Count != 0 || this.currentAlarmCachePlc.Count != 0) |
| | | // { |
| | | // this.preAlarmCachePlc.Clear(); |
| | | // this.currentAlarmCachePlcToMain.Clear(); |
| | | // this.currentAlarmCachePlc.Clear(); |
| | | // this.RefershAlarm(); |
| | | // } |
| | | // } |
| | | // } |
| | | // catch (Exception ex) |
| | | // { |
| | | // log.Error("sdadsa", ex); |
| | | // } |
| | | // finally |
| | | // { |
| | | // this.MonitorTimer.Enabled = true; |
| | | // } |
| | | //} |
| | | } |
| | | } |
| New file |
| | |
| | | <UserControl x:Class="HMI.AlarmInfoManage.AlarmInfoManage_SettingA" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:local="clr-namespace:HMI.AlarmInfoManage" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | Loaded="UserControl_Loaded" |
| | | mc:Ignorable="d" |
| | | d:DesignHeight="450" d:DesignWidth="800"> |
| | | <Grid> |
| | | <StackPanel> |
| | | <GroupBox Header="PLC设置"> |
| | | <StackPanel> |
| | | <pu:FormGroup Header="PLC IP" GroupName="MyFormGroup"> |
| | | <TextBox Text="{Binding PlcIp}"/> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup Header="启用PLC" GroupName="MyFormGroup"> |
| | | <CheckBox IsChecked="{Binding PlcEnabled,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </pu:FormGroup> |
| | | </StackPanel> |
| | | </GroupBox> |
| | | </StackPanel> |
| | | </Grid> |
| | | </UserControl> |
| New file |
| | |
| | | using HMI.AlarmInfoManage.Entity; |
| | | using HMI.Core.Api; |
| | | using HMI.Core.Util; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.AlarmInfoManage |
| | | { |
| | | /// <summary> |
| | | /// AlarmInfoManage_Setting.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class AlarmInfoManage_SettingA : UserControl, IConfigManager |
| | | { |
| | | public AlarmInfoManage_SettingA() |
| | | { |
| | | InitializeComponent(); |
| | | } |
| | | |
| | | private void UserControl_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | ResetConfig(); |
| | | } |
| | | |
| | | public object GetConfig() |
| | | { |
| | | return AimSetting.Instance; |
| | | } |
| | | |
| | | public void SaveConfig() |
| | | { |
| | | AimSettingModel model = (AimSettingModel)DataContext; |
| | | |
| | | AimSetting newSetting = AimSetting.FromModel(model); |
| | | |
| | | XmlHelper.SerializeToXml(PathHelper.ConfigFolder + "\\AimSetting.xml", model); |
| | | } |
| | | |
| | | public void ResetConfig() |
| | | { |
| | | DataContext = AimSetting.Instance.GetModel(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <pu:WindowX x:Class="HMI.AlarmInfoManage.AlarmSettingList" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:local="clr-namespace:HMI.AlarmInfoManage" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | mc:Ignorable="d" |
| | | Title="{Binding SelectedValue,Mode=OneWay}" |
| | | pu:WindowXModalDialog.Buttons="OK" |
| | | pu:WindowXModalDialog.OKButtonContent="{DynamicResource App.Close}" |
| | | Width="1000" |
| | | Height="600" |
| | | InteropOwnersMask="False" |
| | | Loaded="WindowX_Loaded" |
| | | WindowStartupLocation="CenterOwner" |
| | | Style="{StaticResource ModalWindow}"> |
| | | <pu:WindowX.Resources> |
| | | <ResourceDictionary> |
| | | <ResourceDictionary.MergedDictionaries> |
| | | <ResourceDictionary Source="/HMI.UI;component/Style/PanuonListView.xaml"/> |
| | | </ResourceDictionary.MergedDictionaries> |
| | | |
| | | <Style x:Key="EditLink" TargetType="Button" BasedOn="{StaticResource RowLink}"> |
| | | <Setter Property="Content" Value="{DynamicResource App.Edit}"/> |
| | | <Setter Property="Foreground" Value="{DynamicResource PrimaryColor}"/> |
| | | <Style.Triggers> |
| | | <DataTrigger Binding="{Binding Path=IsMouseOver, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}" |
| | | Value="True"> |
| | | <Setter Property="Foreground" Value="#FFF"/> |
| | | </DataTrigger> |
| | | <DataTrigger Binding="{Binding Path=IsSelected, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}" |
| | | Value="True"> |
| | | <Setter Property="Foreground" Value="#FFF"/> |
| | | </DataTrigger> |
| | | </Style.Triggers> |
| | | </Style> |
| | | </ResourceDictionary> |
| | | </pu:WindowX.Resources> |
| | | <Grid Margin="10"> |
| | | <Grid.RowDefinitions> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition /> |
| | | </Grid.RowDefinitions> |
| | | |
| | | <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" |
| | | Margin="0,0,0,10" |
| | | dp:StackPanelHelper.ChildSpace="10"> |
| | | <Button Style="{StaticResource AddBtn}" x:Name="addBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource RfhBtn}" x:Name="refreshBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource ExportBtn}" x:Name="exportBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource ImportBtn}" x:Name="importBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource DelBtn}" x:Name="delBatchBtn" Click="Button_Click"/> |
| | | </StackPanel> |
| | | |
| | | <Grid Grid.Row="1"> |
| | | |
| | | <TabControl Style="{StaticResource HeaderTabControl}" SelectedIndex="{Binding ItemIndex}" x:Name="tab"> |
| | | <TabItem Header="DB块报警"> |
| | | <!--<ListView Style="{StaticResource DefaultListTable}" ItemsSource="{Binding Datasource}"> |
| | | <ListView.View> |
| | | <GridView AllowsColumnReorder="False"> |
| | | |
| | | <GridViewColumn Width="30"> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <CheckBox VerticalAlignment="Center" |
| | | IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="60" Header="#"> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" Text="{Binding Converter={StaticResource lvRowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="100"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.DbAddress}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding DBAddress}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="150"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.StartAddress}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding StartAddress}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="100"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.DbLength}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding Length}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="150"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="报警代码"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding AlarmCode}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="250"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Description}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding Description}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="120"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource App.Opt}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10"> |
| | | <Button x:Name="editBtn" Style="{StaticResource EditLink}" |
| | | Click="Button_Click" Tag="{Binding }"/> |
| | | |
| | | <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" |
| | | Click="Button_Click" Tag="{Binding }"/> |
| | | </StackPanel> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | </GridView> |
| | | </ListView.View> |
| | | </ListView>--> |
| | | <DataGrid Style="{StaticResource DefaultDataGrid}" |
| | | dp:DataGridHelper.ShowRowNumber="True" |
| | | ItemsSource="{Binding BlockDatasource}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Width="20" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <CheckBox VerticalAlignment="Center" |
| | | HorizontalAlignment="Center" IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | |
| | | <DataGridTextColumn Header="#" |
| | | Binding="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}, Mode=FindAncestor}}" |
| | | Width="40"/> |
| | | |
| | | <DataGridTextColumn Width="100" Binding="{Binding DBAddress}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.DbAddress}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="150" Binding="{Binding StartAddress}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.StartAddress}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="100" Binding="{Binding Length}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.DbLength}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="150" Binding="{Binding AlarmCode}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="报警代码"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="*" Binding="{Binding Level}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="级别"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="*" Binding="{Binding Description}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Description}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTemplateColumn Width="120"> |
| | | <DataGridTemplateColumn.Header> |
| | | <TextBlock Text="{DynamicResource App.Opt}"/> |
| | | </DataGridTemplateColumn.Header> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10"> |
| | | <Button x:Name="editBtn" Style="{StaticResource EditLink}" |
| | | Click="Button_Click" Tag="{Binding }"/> |
| | | |
| | | <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" |
| | | Click="Button_Click" Tag="{Binding }"/> |
| | | </StackPanel> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | </TabItem> |
| | | <TabItem Header="标志位报警"> |
| | | <DataGrid Style="{StaticResource DefaultDataGrid}" |
| | | dp:DataGridHelper.ShowRowNumber="True" |
| | | ItemsSource="{Binding MarkDatasource}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Width="Auto" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <CheckBox VerticalAlignment="Center" |
| | | HorizontalAlignment="Center" IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | |
| | | <DataGridTextColumn Header="#" |
| | | Binding="{Binding Header, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}, Mode=FindAncestor}}" Width="40"/> |
| | | |
| | | <DataGridTextColumn Width="100" Binding="{Binding DBAddress}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.DbAddress}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="150" Binding="{Binding StartAddress}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.StartAddress}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="100" Binding="{Binding Length}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.DbLength}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Width="*" Binding="{Binding Description}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Description}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTemplateColumn Width="120"> |
| | | <DataGridTemplateColumn.Header> |
| | | <TextBlock Text="{DynamicResource App.Opt}"/> |
| | | </DataGridTemplateColumn.Header> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10"> |
| | | <Button x:Name="editBtn" Style="{StaticResource EditLink}" |
| | | Click="Button_Click" Tag="{Binding }"/> |
| | | |
| | | <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" |
| | | Click="Button_Click" Tag="{Binding }"/> |
| | | </StackPanel> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | |
| | | <!--<ListView Style="{StaticResource DefaultListTable}" ItemsSource="{Binding MarkDatasource}"> |
| | | <ListView.View> |
| | | <GridView AllowsColumnReorder="False"> |
| | | |
| | | <GridViewColumn Width="30"> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <CheckBox VerticalAlignment="Center" |
| | | IsChecked="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="60" Header="#"> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" Text="{Binding Converter={StaticResource lvRowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="220"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.DbAddress}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding DBAddress}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="180"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.StartAddress}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding StartAddress}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="150"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.DbLength}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding Length}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="200"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Description}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding Description}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="120"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource App.Opt}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10"> |
| | | <Button x:Name="editBtn" Style="{StaticResource EditLink}" |
| | | Click="Button_Click" Tag="{Binding }"/> |
| | | |
| | | <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" |
| | | Click="Button_Click" Tag="{Binding }"/> |
| | | </StackPanel> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | </GridView> |
| | | </ListView.View> |
| | | </ListView>--> |
| | | </TabItem> |
| | | </TabControl> |
| | | <!--<Grid> |
| | | <Grid.RowDefinitions> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition /> |
| | | </Grid.RowDefinitions> |
| | | |
| | | <ListBox ItemsSource="{Binding Items}" SelectedIndex="{Binding ItemIndex}" BorderThickness="0"> |
| | | <ListBox.ItemsPanel> |
| | | <ItemsPanelTemplate> |
| | | <StackPanel Orientation="Horizontal"/> |
| | | </ItemsPanelTemplate> |
| | | </ListBox.ItemsPanel> |
| | | <ListBox.ItemContainerStyle> |
| | | <Style TargetType="ListBoxItem"> |
| | | <Setter Property="Margin" Value="0"/> |
| | | <Setter Property="Padding" Value="0"/> |
| | | <Setter Property="Template"> |
| | | <Setter.Value> |
| | | <ControlTemplate TargetType="ListBoxItem"> |
| | | <Border x:Name="border" Background="LightGray" |
| | | Padding="10" Margin="0" BorderThickness="0"> |
| | | --> |
| | | <!-- 内容呈现器 --> |
| | | <!-- |
| | | <StackPanel> |
| | | <ContentPresenter Content="{TemplateBinding Content}" |
| | | ContentStringFormat="{TemplateBinding ContentStringFormat}" |
| | | ContentTemplate="{TemplateBinding ContentTemplate}" /> |
| | | </StackPanel> |
| | | </Border> |
| | | |
| | | <ControlTemplate.Triggers> |
| | | <Trigger Property="IsSelected" Value="True"> |
| | | <Setter TargetName="border" Property="Background" Value="White"/> |
| | | </Trigger> |
| | | </ControlTemplate.Triggers> |
| | | </ControlTemplate> |
| | | </Setter.Value> |
| | | </Setter> |
| | | </Style> |
| | | </ListBox.ItemContainerStyle> |
| | | </ListBox> |
| | | |
| | | <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" |
| | | Margin="0,0,0,10" |
| | | dp:StackPanelHelper.ChildSpace="10"> |
| | | <Button Style="{StaticResource AddBtn}" x:Name="addBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource RfhBtn}" x:Name="refreshBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource DelBtn}" x:Name="delBatchBtn" Click="Button_Click"/> |
| | | </StackPanel> |
| | | |
| | | |
| | | </Grid>--> |
| | | </Grid> |
| | | </Grid> |
| | | </pu:WindowX> |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using HMI.UI.Util; |
| | | using HMI.Util; |
| | | using HMI.Util.Alarm; |
| | | using log4net; |
| | | using Microsoft.Win32; |
| | | using Panuon.WPF.UI; |
| | | using System; |
| | | using System.Collections.ObjectModel; |
| | | using System.Linq; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.AlarmInfoManage |
| | | { |
| | | /// <summary> |
| | | /// AlarmSettingList.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class AlarmSettingList : WindowX |
| | | { |
| | | private ILog log; |
| | | public AlarmSettingList(ILog log) |
| | | { |
| | | BlockDatasource = AlarmUtil.BlockSettings; |
| | | MarkDatasource = AlarmUtil.MarkSettings; |
| | | this.log = log; |
| | | DataContext = this; |
| | | InitializeComponent(); |
| | | } |
| | | |
| | | private ObservableCollection<AlarmSetting> blockDatasource; |
| | | |
| | | public ObservableCollection<AlarmSetting> BlockDatasource |
| | | { |
| | | get => blockDatasource; |
| | | set { Set(ref blockDatasource, value); } |
| | | } |
| | | |
| | | private ObservableCollection<AlarmSetting> markDatasource; |
| | | |
| | | public ObservableCollection<AlarmSetting> MarkDatasource |
| | | { |
| | | get => markDatasource; |
| | | set { Set(ref markDatasource, value); } |
| | | } |
| | | |
| | | private ObservableCollection<string> items = |
| | | new ObservableCollection<string> { LangUtil.Get("Alm.DbBlockAlarm"), LangUtil.Get("Alm.TagAlarm") }; |
| | | |
| | | public ObservableCollection<string> Items |
| | | { |
| | | get => items; |
| | | set { Set(ref items, value); } |
| | | } |
| | | |
| | | private int itemIndex; |
| | | public int ItemIndex |
| | | { |
| | | get => itemIndex; |
| | | set |
| | | { |
| | | Set(ref itemIndex, value); |
| | | LoadData(); |
| | | } |
| | | } |
| | | |
| | | private void Button_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | Button btn = sender as Button; |
| | | AlarmSetting data = btn?.Tag as AlarmSetting; |
| | | switch (btn.Name) |
| | | { |
| | | case "refreshBtn": |
| | | LoadData(); |
| | | break; |
| | | case "addBtn": |
| | | AddData(new AlarmSetting()); |
| | | break; |
| | | case "delBatchBtn": |
| | | DeleteBatchData(); |
| | | break; |
| | | case "editBtn": |
| | | EditData(ItemIndex == 0 ? BlockDatasource.IndexOf(data) : MarkDatasource.IndexOf(data), data); |
| | | break; |
| | | case "exportBtn": |
| | | ExportData(); |
| | | break; |
| | | case "importBtn": |
| | | ImportData(); |
| | | break; |
| | | case "delBtn": |
| | | DeleteData(ItemIndex == 0 ? BlockDatasource.IndexOf(data) : MarkDatasource.IndexOf(data)); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | private void LoadData() |
| | | { |
| | | BlockDatasource = AlarmUtil.BlockSettings; |
| | | MarkDatasource = AlarmUtil.MarkSettings; |
| | | } |
| | | |
| | | private void AddData(AlarmSetting data) |
| | | { |
| | | try |
| | | { |
| | | var temp = ItemIndex == 0 ? this.lang("Alm.DbBlockAlarm") : this.lang("Alm.TagAlarm"); |
| | | var modal = new AlarmSettingModal(log) |
| | | { |
| | | Title = $"{this.lang("App.Add")}{temp}", |
| | | Owner = Window.GetWindow(this), |
| | | }; |
| | | if (modal.Open(-1, data, ItemIndex == 0 ? BlockDatasource : MarkDatasource, ItemIndex == 0) == true) |
| | | { |
| | | LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Add AlarmSetting Failed.", ex); |
| | | ToastHelper.Error(this, $"{this.lang("App.AddFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void EditData(int index, AlarmSetting data) |
| | | { |
| | | try |
| | | { |
| | | var temp = ItemIndex == 0 ? this.lang("Alm.DbBlockAlarm") : this.lang("Alm.TagAlarm"); |
| | | var modal = new AlarmSettingModal(log) |
| | | { |
| | | Title = $"{this.lang("App.Edit")}{temp}", |
| | | Owner = Window.GetWindow(this), |
| | | }; |
| | | if (modal.Open(index, data, ItemIndex == 0 ? BlockDatasource : MarkDatasource, ItemIndex == 0) == true) |
| | | { |
| | | LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Edit AlarmSetting Failed.", ex); |
| | | ToastHelper.Error(this, $"{this.lang("App.EditFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void ExportData() |
| | | { |
| | | try |
| | | { |
| | | SaveFileDialog sfd = new SaveFileDialog |
| | | { |
| | | FileName = ItemIndex == 0 ? "DB块报警.xls" : "标志位报警.xls", |
| | | Filter = "Excel|*.xls" |
| | | }; |
| | | if (sfd.ShowDialog() == true) |
| | | { |
| | | ExcelHelper.ExportExcel(sfd.FileName, ItemIndex == 0 ? BlockDatasource : MarkDatasource); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Export Failed.", ex); |
| | | ToastHelper.Error(this, $"{this.lang("App.ExportFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void ImportData() |
| | | { |
| | | try |
| | | { |
| | | OpenFileDialog ofd = new OpenFileDialog |
| | | { |
| | | Filter = "Excel|*.xls" |
| | | }; |
| | | if (ofd.ShowDialog() == true) |
| | | { |
| | | var datasource = new ObservableCollection<AlarmSetting>( |
| | | ExcelHelper.ReadFromExcel<AlarmSetting>(ofd.FileName)); |
| | | if (ItemIndex == 0) |
| | | { |
| | | AlarmUtil.UpdateBlockSettings(datasource); |
| | | BlockDatasource = AlarmUtil.BlockSettings; |
| | | } |
| | | else |
| | | { |
| | | AlarmUtil.UpdateMarkSettings(datasource); |
| | | MarkDatasource = AlarmUtil.MarkSettings; |
| | | } |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Import Failed.", ex); |
| | | ToastHelper.Error(this, $"{this.lang("App.ImportFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void DeleteData(int index) |
| | | { |
| | | try |
| | | { |
| | | if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), this.lang("HMI.MakeSureDeleteThis")) == MessageBoxResult.OK) |
| | | { |
| | | bool isBlock = ItemIndex == 0; |
| | | var temp = XmlHelper.DeepClone(isBlock ? BlockDatasource : MarkDatasource); |
| | | |
| | | temp.RemoveAt(index); |
| | | if (isBlock) |
| | | { |
| | | AlarmUtil.UpdateBlockSettings(temp); |
| | | } |
| | | else |
| | | { |
| | | AlarmUtil.UpdateMarkSettings(temp); |
| | | } |
| | | LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Delete AlarmSetting Failed.", ex); |
| | | ToastHelper.Error(this, $"{this.lang("App.DeleteFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | private void DeleteBatchData() |
| | | { |
| | | try |
| | | { |
| | | var temp = (ItemIndex == 0 ? BlockDatasource : MarkDatasource).Where(e => e.IsSelected).ToList(); |
| | | if (temp == null || temp.Count <= 0) |
| | | { |
| | | ToastHelper.Warning(Window.GetWindow(this), this.lang("HMI.NoDeleteData")); |
| | | return; |
| | | } |
| | | |
| | | if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), this.lang("HMI.MakeSureDeletes", temp.Count)) |
| | | == MessageBoxResult.OK) |
| | | { |
| | | bool isBlock = ItemIndex == 0; |
| | | temp = (isBlock ? BlockDatasource : MarkDatasource).Where(e => !e.IsSelected).ToList(); |
| | | if (isBlock) |
| | | { |
| | | AlarmUtil.UpdateBlockSettings(temp); |
| | | } |
| | | else |
| | | { |
| | | AlarmUtil.UpdateMarkSettings(temp); |
| | | } |
| | | LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Delete Batch AlarmSetting Failed.", ex); |
| | | ToastHelper.Error(this, $"{this.lang("App.DeleteFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void WindowX_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | ItemIndex = 0; |
| | | Loaded -= WindowX_Loaded; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | |
| | | namespace HMI.AlarmInfoManage.Entity |
| | | { |
| | | public class AimSetting |
| | | { |
| | | public static AimSetting Instance { get; private set; } |
| | | |
| | | public string PlcIp { get; private set; } |
| | | public bool PlcEnabled { get; private set; } |
| | | |
| | | private AimSetting() { } |
| | | |
| | | static AimSetting() |
| | | { |
| | | LoadFromXml(); |
| | | } |
| | | |
| | | public static void LoadFromXml() |
| | | { |
| | | try |
| | | { |
| | | string path = PathHelper.ConfigFolder + "\\AimSetting.xml"; |
| | | AimSettingModel model = XmlHelper.DeserializeFromXml<AimSettingModel>(path); |
| | | Instance = FromModel(model); |
| | | } |
| | | catch |
| | | { |
| | | Instance = new AimSetting(); |
| | | } |
| | | } |
| | | |
| | | public AimSettingModel GetModel() |
| | | { |
| | | return new AimSettingModel() { PlcIp = PlcIp, PlcEnabled = PlcEnabled }; |
| | | } |
| | | |
| | | public static AimSetting FromModel(AimSettingModel model) |
| | | { |
| | | return new AimSetting() { PlcIp = model.PlcIp, PlcEnabled = model.PlcEnabled }; |
| | | } |
| | | |
| | | } |
| | | |
| | | public class AimSettingModel : NotifyBase |
| | | { |
| | | private string plcIp; |
| | | public string PlcIp |
| | | { |
| | | get => plcIp; |
| | | set |
| | | { |
| | | Set(ref plcIp, value); |
| | | } |
| | | } |
| | | |
| | | private bool plcEnabled; |
| | | public bool PlcEnabled |
| | | { |
| | | get { return plcEnabled; } |
| | | set { Set(ref plcEnabled, value); } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| | | <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
| | | <PropertyGroup> |
| | | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
| | | <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
| | | <ProjectGuid>{945C2577-4558-45E3-891A-096CB3047304}</ProjectGuid> |
| | | <OutputType>library</OutputType> |
| | | <RootNamespace>HMI.AlarmInfoManage</RootNamespace> |
| | | <AssemblyName>HMI.AlarmInfoManage</AssemblyName> |
| | | <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> |
| | | <FileAlignment>512</FileAlignment> |
| | | <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> |
| | | <WarningLevel>4</WarningLevel> |
| | | <Deterministic>true</Deterministic> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <DebugType>full</DebugType> |
| | | <Optimize>false</Optimize> |
| | | <OutputPath>bin\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
| | | <DebugType>pdbonly</DebugType> |
| | | <Optimize>true</Optimize> |
| | | <OutputPath>bin\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <OutputPath>bin\x64\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <DebugType>full</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> |
| | | <OutputPath>bin\x64\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <Optimize>true</Optimize> |
| | | <DebugType>pdbonly</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <ItemGroup> |
| | | <Reference Include="ICSharpCode.SharpZipLib, Version=1.0.0.999, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\SharpZipLib.1.0.0\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="NPOI, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="NPOI.OOXML, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OOXML.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="NPOI.OpenXml4Net, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OpenXml4Net.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="NPOI.OpenXmlFormats, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OpenXmlFormats.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Panuon.WPF, Version=1.1.2.2, Culture=neutral, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Panuon.WPF.1.1.3\lib\net472\Panuon.WPF.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Panuon.WPF.UI, Version=1.3.0.2, Culture=neutral, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Panuon.WPF.UI.1.3.0.2\lib\net472\Panuon.WPF.UI.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="System" /> |
| | | <Reference Include="System.Configuration" /> |
| | | <Reference Include="System.Data" /> |
| | | <Reference Include="System.Web" /> |
| | | <Reference Include="System.Xml" /> |
| | | <Reference Include="Microsoft.CSharp" /> |
| | | <Reference Include="System.Core" /> |
| | | <Reference Include="System.Xml.Linq" /> |
| | | <Reference Include="System.Data.DataSetExtensions" /> |
| | | <Reference Include="System.Net.Http" /> |
| | | <Reference Include="System.Xaml"> |
| | | <RequiredTargetFramework>4.0</RequiredTargetFramework> |
| | | </Reference> |
| | | <Reference Include="WindowsBase" /> |
| | | <Reference Include="PresentationCore" /> |
| | | <Reference Include="PresentationFramework" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Page Include="SimpleAlarm.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | <Page Include="AlarmInfoManage.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | <Compile Include="SimpleAlarm.xaml.cs"> |
| | | <DependentUpon>SimpleAlarm.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="AlarmInfoManage.xaml.cs"> |
| | | <DependentUpon>AlarmInfoManage.xaml</DependentUpon> |
| | | <SubType>Code</SubType> |
| | | </Compile> |
| | | <Page Include="AlarmInfoManage_SettingA.xaml"> |
| | | <SubType>Designer</SubType> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | </Page> |
| | | <Page Include="AlarmSettingList.xaml"> |
| | | <SubType>Designer</SubType> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | </Page> |
| | | <Page Include="Modal\AlarmSettingModal.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="AlarmInfoManage_SettingA.xaml.cs"> |
| | | <DependentUpon>AlarmInfoManage_SettingA.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="AlarmSettingList.xaml.cs"> |
| | | <DependentUpon>AlarmSettingList.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Entity\AimSetting.cs" /> |
| | | <Compile Include="Modal\AlarmSettingModal.xaml.cs"> |
| | | <DependentUpon>AlarmSettingModal.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Model\XmlModel.cs" /> |
| | | <Compile Include="Model\UIModel.cs" /> |
| | | <Compile Include="Properties\AssemblyInfo.cs"> |
| | | <SubType>Code</SubType> |
| | | </Compile> |
| | | <Compile Include="Properties\Resources.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DesignTime>True</DesignTime> |
| | | <DependentUpon>Resources.resx</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Properties\Settings.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DependentUpon>Settings.settings</DependentUpon> |
| | | <DesignTimeSharedInput>True</DesignTimeSharedInput> |
| | | </Compile> |
| | | <EmbeddedResource Include="Properties\Resources.resx"> |
| | | <Generator>ResXFileCodeGenerator</Generator> |
| | | <LastGenOutput>Resources.Designer.cs</LastGenOutput> |
| | | </EmbeddedResource> |
| | | <None Include="packages.config" /> |
| | | <None Include="Properties\Settings.settings"> |
| | | <Generator>SettingsSingleFileGenerator</Generator> |
| | | <LastGenOutput>Settings.Designer.cs</LastGenOutput> |
| | | </None> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <ProjectReference Include="..\S7Driver\S7Driver.csproj"> |
| | | <Project>{628ef12b-9ed2-4b20-950f-dc3ecc3fd15f}</Project> |
| | | <Name>S7Driver</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\ConnectionUtil\ConnectionUtil.csproj"> |
| | | <Project>{d0806fa6-3f6d-4304-81b8-ead9da7cb5b2}</Project> |
| | | <Name>ConnectionUtil</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.Base\HMI.Base.csproj"> |
| | | <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project> |
| | | <Name>HMI.Base</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.Core\HMI.Core.csproj"> |
| | | <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project> |
| | | <Name>HMI.Core</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.UI\HMI.UI.csproj"> |
| | | <Project>{f17de6b5-af1d-48c6-94a0-6b763a6546a6}</Project> |
| | | <Name>HMI.UI</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.Util\HMI.Util.csproj"> |
| | | <Project>{AFEA100A-9223-448D-AB10-3015D93DD98C}</Project> |
| | | <Name>HMI.Util</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\OPCClient\OPCClient.csproj"> |
| | | <Project>{E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}</Project> |
| | | <Name>OPCClient</Name> |
| | | </ProjectReference> |
| | | </ItemGroup> |
| | | <ItemGroup /> |
| | | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| | | <PropertyGroup> |
| | | <PostBuildEvent> |
| | | </PostBuildEvent> |
| | | </PropertyGroup> |
| | | </Project> |
| New file |
| | |
| | | <pu:WindowX x:Class="HMI.AlarmInfoManage.AlarmSettingModal" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:local="clr-namespace:HMI.AlarmInfoManage" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI" |
| | | xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | mc:Ignorable="d" |
| | | Title="Form" |
| | | SizeToContent="Height" |
| | | Width="600" |
| | | WindowStartupLocation="CenterOwner" |
| | | BorderBrush="#3E3E45" |
| | | BorderThickness="1" |
| | | Foreground="Black" |
| | | Style="{StaticResource ModalWindow}"> |
| | | <Grid Margin="10"> |
| | | <StackPanel x:Name="formPanel"> |
| | | <pu:FormGroup Header="{DynamicResource Alm.DbAddress}" |
| | | dp:FormGroupHelper.IsRequired="True" |
| | | dp:FormGroupHelper.ValidateMethod="ValidateDBAddress" |
| | | GroupName="MyFormGroup"> |
| | | <TextBox Style="{StaticResource EditorTextBoxStyle}" |
| | | x:Name="dbAddress"/> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup Header="{DynamicResource Alm.StartAddress}" dp:FormGroupHelper.IsRequired="True" |
| | | GroupName="MyFormGroup"> |
| | | <TextBox Style="{StaticResource EditorTextBoxStyle}" |
| | | x:Name="startAddress"/> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup Header="{DynamicResource Alm.DbLength}" dp:FormGroupHelper.IsRequired="True" |
| | | GroupName="MyFormGroup"> |
| | | <pu:NumberInput Style="{StaticResource FormNumberInput}" x:Name="length" Interval="1"/> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup Header="报警代码" dp:FormGroupHelper.IsRequired="True" x:Name="FgCode" |
| | | GroupName="MyFormGroup"> |
| | | <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="TbCode"/> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup Header="级别" dp:FormGroupHelper.IsRequired="True" x:Name="FgLevel" |
| | | GroupName="MyFormGroup"> |
| | | <pu:NumberInput Style="{StaticResource FormNumberInput}" x:Name="NiLevel"/> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgDesc" Header="{DynamicResource Alm.Description}" dp:FormGroupHelper.IsRequired="True" |
| | | GroupName="MyFormGroup"> |
| | | <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="TbDesc"/> |
| | | </pu:FormGroup> |
| | | </StackPanel> |
| | | </Grid> |
| | | </pu:WindowX> |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using HMI.UI.DpUtil; |
| | | using HMI.UI.Util; |
| | | using HMI.Util.Alarm; |
| | | using log4net; |
| | | using Panuon.WPF.UI; |
| | | using System; |
| | | using System.Collections.ObjectModel; |
| | | using System.ComponentModel; |
| | | using System.Linq; |
| | | using System.Windows; |
| | | |
| | | namespace HMI.AlarmInfoManage |
| | | { |
| | | /// <summary> |
| | | /// ModalWindow.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class AlarmSettingModal : WindowX |
| | | { |
| | | public AlarmSettingModal(ILog log) |
| | | { |
| | | this.log = log; |
| | | this.DataContext = this; |
| | | InitializeComponent(); |
| | | } |
| | | private ILog log; |
| | | private bool isBlock; |
| | | public int DataIndex { get; private set; } |
| | | public AlarmSetting Data { get; private set; } |
| | | public ObservableCollection<AlarmSetting> Datasource { get; private set; } |
| | | |
| | | private bool _isProcessing; |
| | | |
| | | protected override void OnClosing(CancelEventArgs e) |
| | | { |
| | | //Press Continue Button |
| | | if (DialogResult == true) |
| | | { |
| | | if (_isProcessing) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | var okButton = WindowXModalDialog.GetOKButton(this); |
| | | try |
| | | { |
| | | _isProcessing = true; |
| | | ButtonHelper.SetIsPending(okButton, true); |
| | | if (!ValidateForm()) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | |
| | | SaveOrUpdateData(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("AlarmSetting Form Submit Failed.", ex); |
| | | ToastHelper.Error(this, $"Error: {ex.Message}"); |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | finally |
| | | { |
| | | _isProcessing = false; |
| | | ButtonHelper.SetIsPending(okButton, false); |
| | | } |
| | | } |
| | | base.OnClosing(e); |
| | | } |
| | | |
| | | public bool ValidateDBAddress(object value, out string err) |
| | | { |
| | | err = null; |
| | | var target = Datasource?.FirstOrDefault(e => e.DBAddress == value); |
| | | if (target != null && target != Data) |
| | | { |
| | | err = this.lang("App.MustBeUnique", this.lang("Alm.DbAddress")); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private bool ValidateForm() |
| | | { |
| | | bool flag = FormGroupHelper.ValidateForm(formPanel); |
| | | if (flag) |
| | | { |
| | | Data.DBAddress = dbAddress.Text; |
| | | Data.StartAddress = startAddress.Text; |
| | | Data.Length = Convert.ToInt32(length.Value); |
| | | Data.AlarmCode = TbCode.Text; |
| | | Data.Level = NiLevel.Value ?? 0d; |
| | | Data.Description = TbDesc.Text; |
| | | } |
| | | return flag; |
| | | } |
| | | |
| | | public bool? Open(int index, AlarmSetting data, |
| | | ObservableCollection<AlarmSetting> dataSource, bool isBlock) |
| | | { |
| | | this.isBlock = isBlock; |
| | | FgCode.Visibility = isBlock ? Visibility.Visible : Visibility.Collapsed; |
| | | DataIndex = index; |
| | | Data = data; |
| | | Datasource = dataSource; |
| | | dbAddress.Text = Data.DBAddress; |
| | | startAddress.Text = Data.StartAddress; |
| | | length.Value = Data.Length; |
| | | TbCode.Text = Data.AlarmCode; |
| | | NiLevel.Value = Data.Level; |
| | | TbDesc.Text = Data.Description; |
| | | return this.ShowDialog(); |
| | | } |
| | | |
| | | private void SaveOrUpdateData() |
| | | { |
| | | if (DataIndex == -1) |
| | | { |
| | | InsertData(Data); |
| | | } |
| | | else |
| | | { |
| | | UpdateData(DataIndex, Data); |
| | | } |
| | | } |
| | | |
| | | public void InsertData(AlarmSetting data) |
| | | { |
| | | Datasource.Add(data); |
| | | if (this.isBlock) |
| | | { |
| | | AlarmUtil.UpdateBlockSettings(Datasource); |
| | | } |
| | | else |
| | | { |
| | | AlarmUtil.UpdateMarkSettings(Datasource); |
| | | } |
| | | } |
| | | |
| | | public void UpdateData(int index, AlarmSetting data) |
| | | { |
| | | Datasource[index] = data; |
| | | if (this.isBlock) |
| | | { |
| | | AlarmUtil.UpdateBlockSettings(Datasource); |
| | | } |
| | | else |
| | | { |
| | | AlarmUtil.UpdateMarkSettings(Datasource); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.AlarmInfoManage.Entity; |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using System.Collections.ObjectModel; |
| | | using System.Data; |
| | | using System.IO; |
| | | |
| | | namespace HMI.AlarmInfoManage.Model |
| | | { |
| | | public class UIModel : NotifyBase |
| | | { |
| | | public SqliteHelper sqliteHelper; |
| | | |
| | | private ObservableCollection<AlarmSetting> datasource; |
| | | |
| | | public ObservableCollection<AlarmSetting> Datasource |
| | | { |
| | | get { return datasource; } |
| | | set { Set(ref datasource, value); } |
| | | } |
| | | |
| | | public UIModel(string filePath) |
| | | { |
| | | if (!File.Exists(filePath)) |
| | | { |
| | | SqliteHelper.CreateDatabase(filePath); |
| | | } |
| | | sqliteHelper = new SqliteHelper(filePath); |
| | | } |
| | | |
| | | public void LoadData() { |
| | | ObservableCollection<AlarmSetting> list = new ObservableCollection<AlarmSetting>(); |
| | | DataTable dt = sqliteHelper.GetDataTable("SELECT * FROM alerm_setting;"); |
| | | |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using System.IO; |
| | | |
| | | namespace HMI.AlarmInfoManage.Model |
| | | { |
| | | public class XmlUIModel : NotifyBase |
| | | { |
| | | public string FileFolder { get; private set; } |
| | | public string AlermTextPath { get; private set; } |
| | | public string AlermSettingPath { get; private set; } |
| | | |
| | | |
| | | |
| | | public XmlUIModel(string FileFolder) |
| | | { |
| | | this.FileFolder = FileFolder; |
| | | this.AlermTextPath = Path.Combine(FileFolder, "AlermText.xml"); |
| | | this.AlermSettingPath = Path.Combine(FileFolder, "AlermSetting.xml"); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Reflection; |
| | | using System.Resources; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Runtime.InteropServices; |
| | | using System.Windows; |
| | | using HMI.Core.Annotation; |
| | | |
| | | // 有关程序集的一般信息由以下 |
| | | // 控制。更改这些特性值可修改 |
| | | // 与程序集关联的信息。 |
| | | [assembly: AssemblyTitle("HMI.AlarmInfoManage")] |
| | | [assembly: AssemblyDescription("")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("Organization")] |
| | | [assembly: AssemblyProduct("HMI.AlarmInfoManage")] |
| | | [assembly: AssemblyCopyright("Copyright © Organization 2025")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // 将 ComVisible 设置为 false 会使此程序集中的类型 |
| | | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 |
| | | //请将此类型的 ComVisible 特性设置为 true。 |
| | | [assembly: ComVisible(false)] |
| | | |
| | | //若要开始生成可本地化的应用程序,请设置 |
| | | //.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture> |
| | | //在 <PropertyGroup> 中。例如,如果你使用的是美国英语。 |
| | | //使用的是美国英语,请将 <UICulture> 设置为 en-US。 然后取消 |
| | | //对以下 NeutralResourceLanguage 特性的注释。 更新 |
| | | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 |
| | | |
| | | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] |
| | | |
| | | |
| | | [assembly:ThemeInfo( |
| | | ResourceDictionaryLocation.None, //主题特定资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //或应用程序资源字典中找到时使用) |
| | | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //、应用程序或任何主题专用资源字典中找到时使用) |
| | | )] |
| | | |
| | | |
| | | // 程序集的版本信息由下列四个值组成: |
| | | // |
| | | // 主版本 |
| | | // 次版本 |
| | | // 生成号 |
| | | // 修订号 |
| | | // |
| | | [assembly: AssemblyVersion("1.0.0.0")] |
| | | [assembly: AssemblyFileVersion("1.0.0.0")] |
| | | [assembly: PluginClassName("HMI.AlarmInfoManage.AlarmInfoManage")] |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // 此代码由工具生成。 |
| | | // 运行时版本:4.0.30319.42000 |
| | | // |
| | | // 对此文件的更改可能会导致不正确的行为,并且如果 |
| | | // 重新生成代码,这些更改将会丢失。 |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.AlarmInfoManage.Properties { |
| | | using System; |
| | | |
| | | |
| | | /// <summary> |
| | | /// 一个强类型的资源类,用于查找本地化的字符串等。 |
| | | /// </summary> |
| | | // 此类是由 StronglyTypedResourceBuilder |
| | | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 |
| | | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen |
| | | // (以 /str 作为命令选项),或重新生成 VS 项目。 |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] |
| | | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | internal class Resources { |
| | | |
| | | private static global::System.Resources.ResourceManager resourceMan; |
| | | |
| | | private static global::System.Globalization.CultureInfo resourceCulture; |
| | | |
| | | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
| | | internal Resources() { |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 返回此类使用的缓存的 ResourceManager 实例。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Resources.ResourceManager ResourceManager { |
| | | get { |
| | | if (object.ReferenceEquals(resourceMan, null)) { |
| | | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HMI.AlarmInfoManage.Properties.Resources", typeof(Resources).Assembly); |
| | | resourceMan = temp; |
| | | } |
| | | return resourceMan; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 重写当前线程的 CurrentUICulture 属性,对 |
| | | /// 使用此强类型资源类的所有资源查找执行重写。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Globalization.CultureInfo Culture { |
| | | get { |
| | | return resourceCulture; |
| | | } |
| | | set { |
| | | resourceCulture = value; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <root> |
| | | <!-- |
| | | Microsoft ResX Schema |
| | | |
| | | Version 2.0 |
| | | |
| | | The primary goals of this format is to allow a simple XML format |
| | | that is mostly human readable. The generation and parsing of the |
| | | various data types are done through the TypeConverter classes |
| | | associated with the data types. |
| | | |
| | | Example: |
| | | |
| | | ... ado.net/XML headers & schema ... |
| | | <resheader name="resmimetype">text/microsoft-resx</resheader> |
| | | <resheader name="version">2.0</resheader> |
| | | <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
| | | <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
| | | <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
| | | <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
| | | <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
| | | <value>[base64 mime encoded serialized .NET Framework object]</value> |
| | | </data> |
| | | <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
| | | <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
| | | <comment>This is a comment</comment> |
| | | </data> |
| | | |
| | | There are any number of "resheader" rows that contain simple |
| | | name/value pairs. |
| | | |
| | | Each data row contains a name, and value. The row also contains a |
| | | type or mimetype. Type corresponds to a .NET class that support |
| | | text/value conversion through the TypeConverter architecture. |
| | | Classes that don't support this are serialized and stored with the |
| | | mimetype set. |
| | | |
| | | The mimetype is used for serialized objects, and tells the |
| | | ResXResourceReader how to depersist the object. This is currently not |
| | | extensible. For a given mimetype the value must be set accordingly: |
| | | |
| | | Note - application/x-microsoft.net.object.binary.base64 is the format |
| | | that the ResXResourceWriter will generate, however the reader can |
| | | read any of the formats listed below. |
| | | |
| | | mimetype: application/x-microsoft.net.object.binary.base64 |
| | | value : The object must be serialized with |
| | | : System.Serialization.Formatters.Binary.BinaryFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.soap.base64 |
| | | value : The object must be serialized with |
| | | : System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.bytearray.base64 |
| | | value : The object must be serialized into a byte array |
| | | : using a System.ComponentModel.TypeConverter |
| | | : and then encoded with base64 encoding. |
| | | --> |
| | | <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
| | | <xsd:element name="root" msdata:IsDataSet="true"> |
| | | <xsd:complexType> |
| | | <xsd:choice maxOccurs="unbounded"> |
| | | <xsd:element name="metadata"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | <xsd:attribute name="type" type="xsd:string" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="assembly"> |
| | | <xsd:complexType> |
| | | <xsd:attribute name="alias" type="xsd:string" /> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="data"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> |
| | | <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="resheader"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" use="required" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:choice> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:schema> |
| | | <resheader name="resmimetype"> |
| | | <value>text/microsoft-resx</value> |
| | | </resheader> |
| | | <resheader name="version"> |
| | | <value>2.0</value> |
| | | </resheader> |
| | | <resheader name="reader"> |
| | | <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | <resheader name="writer"> |
| | | <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | </root> |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // 此代码由工具生成。 |
| | | // 运行时版本:4.0.30319.42000 |
| | | // |
| | | // 对此文件的更改可能会导致不正确的行为,并且如果 |
| | | // 重新生成代码,这些更改将会丢失。 |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.AlarmInfoManage.Properties { |
| | | |
| | | |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.13.0.0")] |
| | | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { |
| | | |
| | | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); |
| | | |
| | | public static Settings Default { |
| | | get { |
| | | return defaultInstance; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version='1.0' encoding='utf-8'?> |
| | | <SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> |
| | | <Profiles> |
| | | <Profile Name="(Default)" /> |
| | | </Profiles> |
| | | <Settings /> |
| | | </SettingsFile> |
| New file |
| | |
| | | <hb:PluginControl x:Class="HMI.AlarmInfoManage.SimpleAlarm" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | xmlns:local="clr-namespace:HMI.AlarmInfoManage" |
| | | xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | mc:Ignorable="d" |
| | | Background="{DynamicResource WorkspaceBackground}" |
| | | Loaded="UserControl_Loaded" |
| | | d:DesignHeight="450" d:DesignWidth="800"> |
| | | |
| | | <Border Style="{StaticResource CardBoarder}"> |
| | | <DockPanel> |
| | | <DockPanel Margin="0,0,0,10" DockPanel.Dock="Top"> |
| | | <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}"> |
| | | <Button Style="{StaticResource RfhBtn}" x:Name="refreshBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource ExportBtn}" x:Name="exportBtn" Click="Button_Click"/> |
| | | </StackPanel> |
| | | |
| | | <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" |
| | | VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource Alm.Title}"/> |
| | | </DockPanel> |
| | | |
| | | <pu:Pagination DockPanel.Dock="Bottom" Margin="0,10,0,0" Height="30" |
| | | FontSize="14" |
| | | HorizontalAlignment="Left" |
| | | Foreground="Black" |
| | | ItemsBackground="Transparent" |
| | | ItemsCornerRadius="12" |
| | | ItemsSelectedBackground="#36AAF7" CurrentPage="1" MaxPage="10"/> |
| | | <DataGrid x:Name="table" Style="{StaticResource DefaultDataGrid}" ItemsSource="{Binding Datasource}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | <DataGridTemplateColumn Header="#" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding Source}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Source}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding Code}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Code}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding Info}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Info}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding Time}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Alm.Time}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <!--<DataGridTemplateColumn Width="100" IsReadOnly="True" CanUserResize="False"> |
| | | <DataGridTemplateColumn.Header> |
| | | <TextBlock Text="{DynamicResource App.Opt}"/> |
| | | </DataGridTemplateColumn.Header> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <StackPanel Orientation="Horizontal"> |
| | | <Button x:Name="editBtn" Style="{StaticResource EditLink}" |
| | | Margin="0,0,10,0" |
| | | Click="Button_Click" |
| | | DataContext="{Binding }"/> |
| | | <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" |
| | | DataContext="{Binding }" |
| | | Click="Button_Click"/> |
| | | </StackPanel> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn>--> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | |
| | | </DockPanel> |
| | | </Border> |
| | | </hb:PluginControl> |
| New file |
| | |
| | | using HMI.Base; |
| | | using HMI.Core.Util; |
| | | using System; |
| | | using System.IO; |
| | | using System.Timers; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.AlarmInfoManage |
| | | { |
| | | /// <summary> |
| | | /// UserControl1.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class SimpleAlarm : PluginControl |
| | | { |
| | | private string dbPath; |
| | | private SqliteHelper sqliteHelper; |
| | | private Timer MonitorTimer; |
| | | |
| | | public SimpleAlarm() |
| | | { |
| | | DataContext = this; |
| | | InitializeComponent(); |
| | | } |
| | | |
| | | public override void InitFinish() |
| | | { |
| | | dbPath = Path.Combine(BaseFolder, "alarm.db"); |
| | | if (!File.Exists(dbPath)) |
| | | { |
| | | SqliteHelper.CreateDatabase(dbPath); |
| | | sqliteHelper = new SqliteHelper(dbPath); |
| | | sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS Alarm ( |
| | | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| | | source TEXT NOT NULL, |
| | | alarmcode TEXT, |
| | | alarminfo TEXT, |
| | | time DATETIME);"); |
| | | } |
| | | } |
| | | |
| | | private void Button_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | Button btn = sender as Button; |
| | | switch (btn.Name) |
| | | { |
| | | case "refreshBtn": |
| | | //Model. |
| | | break; |
| | | case "exportBtn": |
| | | //ImportText(); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | private void UserControl_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | MonitorTimer = new Timer |
| | | { |
| | | Interval = 100, |
| | | }; |
| | | MonitorTimer.Elapsed += MonitorTimer_Elapsed; |
| | | |
| | | Loaded -= UserControl_Loaded; |
| | | } |
| | | |
| | | |
| | | private void MonitorTimer_Elapsed(object sender, ElapsedEventArgs e) |
| | | { |
| | | try |
| | | { |
| | | this.MonitorTimer.Enabled = false; |
| | | this.RefershAlarm(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("sdadsa", ex); |
| | | } |
| | | finally |
| | | { |
| | | this.MonitorTimer.Enabled = true; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 刷新报警信息 |
| | | /// </summary> |
| | | private void RefershAlarm() |
| | | { |
| | | |
| | | } |
| | | |
| | | public override void SetBaseFolder(string folder) |
| | | { |
| | | base.SetBaseFolder(folder); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <packages> |
| | | <package id="log4net" version="3.0.4" targetFramework="net472" /> |
| | | <package id="NPOI" version="2.4.1" targetFramework="net472" /> |
| | | <package id="Panuon.WPF" version="1.1.3" targetFramework="net472" /> |
| | | <package id="Panuon.WPF.UI" version="1.3.0.2" targetFramework="net472" /> |
| | | <package id="SharpZipLib" version="1.0.0" targetFramework="net472" /> |
| | | </packages> |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Data; |
| | | using System.Reflection; |
| | | |
| | | namespace HMI.Base |
| | | { |
| | | public class DtConverter |
| | | { |
| | | public static void DoConvert<T>(DataTable dataTable, ICollection<T> target) where T : new() |
| | | { |
| | | var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); |
| | | |
| | | foreach (DataRow row in dataTable.Rows) |
| | | { |
| | | var item = new T(); |
| | | foreach (var property in properties) |
| | | { |
| | | if (dataTable.Columns.Contains(property.Name) && row[property.Name] != DBNull.Value) |
| | | { |
| | | property.SetValue(item, Convert.ChangeType(row[property.Name], property.PropertyType)); |
| | | } |
| | | } |
| | | target.Add(item); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| | | <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
| | | <PropertyGroup> |
| | | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
| | | <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
| | | <ProjectGuid>{2A6F9720-A890-4036-9D73-41A0DAE28341}</ProjectGuid> |
| | | <OutputType>library</OutputType> |
| | | <RootNamespace>HMI.Base</RootNamespace> |
| | | <AssemblyName>HMI.Base</AssemblyName> |
| | | <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> |
| | | <FileAlignment>512</FileAlignment> |
| | | <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> |
| | | <WarningLevel>4</WarningLevel> |
| | | <Deterministic>true</Deterministic> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <DebugType>full</DebugType> |
| | | <Optimize>false</Optimize> |
| | | <OutputPath>bin\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
| | | <DebugType>pdbonly</DebugType> |
| | | <Optimize>true</Optimize> |
| | | <OutputPath>bin\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <OutputPath>bin\x64\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <DebugType>full</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> |
| | | <OutputPath>bin\x64\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <Optimize>true</Optimize> |
| | | <DebugType>pdbonly</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <ItemGroup> |
| | | <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="System" /> |
| | | <Reference Include="System.Configuration" /> |
| | | <Reference Include="System.Data" /> |
| | | <Reference Include="System.Web" /> |
| | | <Reference Include="System.Xml" /> |
| | | <Reference Include="Microsoft.CSharp" /> |
| | | <Reference Include="System.Core" /> |
| | | <Reference Include="System.Xml.Linq" /> |
| | | <Reference Include="System.Data.DataSetExtensions" /> |
| | | <Reference Include="System.Net.Http" /> |
| | | <Reference Include="System.Xaml"> |
| | | <RequiredTargetFramework>4.0</RequiredTargetFramework> |
| | | </Reference> |
| | | <Reference Include="WindowsBase" /> |
| | | <Reference Include="PresentationCore" /> |
| | | <Reference Include="PresentationFramework" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="DtConverter.cs" /> |
| | | <Compile Include="PluginMaskControl.cs" /> |
| | | <Compile Include="NotifyControl.cs" /> |
| | | <Compile Include="SimpleControl.cs" /> |
| | | <Compile Include="PluginControl.cs" /> |
| | | <Compile Include="PluginInfo.cs" /> |
| | | <Compile Include="PluginLoader.cs" /> |
| | | <Compile Include="Properties\AssemblyInfo.cs"> |
| | | <SubType>Code</SubType> |
| | | </Compile> |
| | | <Compile Include="Properties\Resources.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DesignTime>True</DesignTime> |
| | | <DependentUpon>Resources.resx</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Properties\Settings.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DependentUpon>Settings.settings</DependentUpon> |
| | | <DesignTimeSharedInput>True</DesignTimeSharedInput> |
| | | </Compile> |
| | | <Compile Include="SimpleMaskControl.cs" /> |
| | | <EmbeddedResource Include="Properties\Resources.resx"> |
| | | <Generator>ResXFileCodeGenerator</Generator> |
| | | <LastGenOutput>Resources.Designer.cs</LastGenOutput> |
| | | </EmbeddedResource> |
| | | <None Include="packages.config" /> |
| | | <None Include="Properties\Settings.settings"> |
| | | <Generator>SettingsSingleFileGenerator</Generator> |
| | | <LastGenOutput>Settings.Designer.cs</LastGenOutput> |
| | | </None> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <ProjectReference Include="..\HMI.Core\HMI.Core.csproj"> |
| | | <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project> |
| | | <Name>HMI.Core</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.UI\HMI.UI.csproj"> |
| | | <Project>{f17de6b5-af1d-48c6-94a0-6b763a6546a6}</Project> |
| | | <Name>HMI.UI</Name> |
| | | </ProjectReference> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Page Include="MaskControl.xaml"> |
| | | <SubType>Designer</SubType> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | </Page> |
| | | <Page Include="Themes\Generic.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | </ItemGroup> |
| | | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| | | </Project> |
| New file |
| | |
| | | <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI" |
| | | xmlns:local="clr-namespace:HMI.Base"> |
| | | <Style x:Key="simpleMaskControl" TargetType="local:SimpleMaskControl"> |
| | | <Setter Property="Template"> |
| | | <Setter.Value> |
| | | <ControlTemplate TargetType="local:SimpleMaskControl"> |
| | | <Grid> |
| | | <ContentPresenter Content="{TemplateBinding Content}"/> |
| | | |
| | | <ui:MaskControl x:Name="mask"/> |
| | | </Grid> |
| | | </ControlTemplate> |
| | | </Setter.Value> |
| | | </Setter> |
| | | </Style> |
| | | |
| | | <Style x:Key="pluginMaskControl" TargetType="local:PluginMaskControl"> |
| | | <Setter Property="Template"> |
| | | <Setter.Value> |
| | | <ControlTemplate TargetType="local:PluginMaskControl"> |
| | | <Grid> |
| | | <ContentPresenter Content="{TemplateBinding Content}"/> |
| | | |
| | | <ui:MaskControl x:Name="mask"/> |
| | | </Grid> |
| | | </ControlTemplate> |
| | | </Setter.Value> |
| | | </Setter> |
| | | </Style> |
| | | </ResourceDictionary> |
| New file |
| | |
| | | using System.ComponentModel; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.Base |
| | | { |
| | | public class NotifyControl : UserControl, INotifyPropertyChanged |
| | | { |
| | | #region ... |
| | | public event PropertyChangedEventHandler PropertyChanged; |
| | | |
| | | protected void Set([CallerMemberName] string name = null) |
| | | { |
| | | |
| | | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); |
| | | } |
| | | |
| | | protected void Set<T>(ref T old, T @new, [CallerMemberName] string propertyName = null) |
| | | { |
| | | old = @new; |
| | | if (propertyName != null) |
| | | { |
| | | Set(propertyName); |
| | | } |
| | | } |
| | | #endregion |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.ComponentModel; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Windows.Controls; |
| | | using HMI.Core.Api; |
| | | using log4net; |
| | | |
| | | namespace HMI.Base |
| | | { |
| | | public class PluginControl : UserControl, IPlugin, INotifyPropertyChanged |
| | | { |
| | | #region ... |
| | | public event PropertyChangedEventHandler PropertyChanged; |
| | | |
| | | protected void Set([CallerMemberName] string name = null) |
| | | { |
| | | |
| | | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); |
| | | } |
| | | |
| | | protected void Set<T>(ref T old, T @new, [CallerMemberName] string propertyName = null) |
| | | { |
| | | old = @new; |
| | | if (propertyName != null) |
| | | { |
| | | Set(propertyName); |
| | | } |
| | | } |
| | | #endregion |
| | | |
| | | protected ILog log; |
| | | protected string BaseFolder; |
| | | |
| | | public virtual void Active() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void DeActive() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void Init() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void OnTabChange(object sender, object args) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void SetBaseFolder(string folder) |
| | | { |
| | | BaseFolder = folder; |
| | | } |
| | | |
| | | public virtual void SetLogger(ILog logger) |
| | | { |
| | | log = logger; |
| | | } |
| | | |
| | | public virtual void InitFinish() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void OnClose(object sender, CancelEventArgs e) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void DoClose(object sender, EventArgs e) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual bool CanActive() |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | public virtual bool CanDeActive() |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Api; |
| | | using System.Collections.ObjectModel; |
| | | using System.Windows.Media.Imaging; |
| | | |
| | | namespace HMI.Base |
| | | { |
| | | public class PluginInfo |
| | | { |
| | | public PluginInfo() |
| | | { |
| | | |
| | | } |
| | | public bool IsFont { get; set; } |
| | | public string Code { get; set; } |
| | | public string Name { get; set; } |
| | | public string Type { get; set; } |
| | | public string ParentCode { get; set; } |
| | | public string DllPath { get; set; } |
| | | public string ClassName { get; set; } |
| | | public ObservableCollection<PluginInfo> Children { get; set; } = new ObservableCollection<PluginInfo>(); |
| | | public string IconFont { get; set; } |
| | | public BitmapImage _Icon { get; set; } |
| | | |
| | | public object Content; |
| | | |
| | | public object SettingControl; |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Api; |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using log4net; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using System.Reflection; |
| | | using System.Threading; |
| | | using System.Windows; |
| | | using System.Windows.Threading; |
| | | |
| | | namespace HMI.Base |
| | | { |
| | | public class PluginLoader |
| | | { |
| | | private static ILog log = LogManager.GetLogger("PluginLoader"); |
| | | private static string PluginFolder = PathHelper.PluginFolder(); |
| | | |
| | | public SqliteHelper sqliteHelper; |
| | | public List<PluginInfo> Menus { get; private set; } |
| | | public List<PluginInfo> Plugins { get; private set; } |
| | | public static PluginLoader Instance { get; private set; } |
| | | |
| | | public event EventHandler<LoadMsg> LoadMessageAppend; |
| | | |
| | | private PluginLoader() |
| | | { |
| | | sqliteHelper = new SqliteHelper(PathHelper.DatabasePath); |
| | | } |
| | | |
| | | public static void Reset() |
| | | { |
| | | if (Instance != null) |
| | | { |
| | | Instance = null; |
| | | } |
| | | GC.Collect(); |
| | | } |
| | | |
| | | public static PluginLoader GetLoader() |
| | | { |
| | | if (Instance == null) |
| | | { |
| | | Instance = new PluginLoader(); |
| | | } |
| | | return Instance; |
| | | } |
| | | |
| | | private IPluginContainer pluginContainer; |
| | | |
| | | public void LoadMenuAndPlugin(IPluginContainer container, Dispatcher dispatcher) |
| | | { |
| | | pluginContainer = container; |
| | | if (Menus == null) |
| | | { |
| | | Plugins = new List<PluginInfo>(); |
| | | Menus = LoadTreeMenu(dispatcher); |
| | | sqliteHelper.Close(); |
| | | LoadMessageAppend?.Invoke(this, new LoadMsg { Type = LoadMsgType.FINISHED }); |
| | | } |
| | | pluginContainer.DoCloseEvent += PluginContainer_DoCloseEvent; |
| | | } |
| | | |
| | | private void PluginContainer_DoCloseEvent(object sender, EventArgs e) |
| | | { |
| | | Reset(); |
| | | } |
| | | |
| | | private void LoadChildrenMenu() |
| | | { |
| | | foreach (var menu in Plugins) |
| | | { |
| | | CountdownEvent c = new CountdownEvent(1); |
| | | Thread.Sleep(50); |
| | | Application.Current.Dispatcher.Invoke(new Action(() => |
| | | { |
| | | try |
| | | { |
| | | var start = DateTime.Now; |
| | | log.Info(this.lang("App.IsLoadingMenu", menu.Name)); |
| | | LoadMessageAppend?.Invoke(this, |
| | | new LoadMsg |
| | | { |
| | | Type = LoadMsgType.LOADING, |
| | | Message = this.lang("App.IsLoadingMenu", menu.Name) |
| | | } |
| | | ); |
| | | |
| | | if (!string.IsNullOrEmpty(menu.DllPath)) |
| | | { |
| | | string dllPath = Path.Combine(PluginFolder, menu.DllPath); |
| | | Assembly ass = Assembly.LoadFrom(dllPath); |
| | | var tss = ass.GetTypes(); |
| | | Type ClassType = ass.GetType(menu.ClassName); |
| | | object Instance = Activator.CreateInstance(ClassType); |
| | | menu.Content = Instance; |
| | | if (Instance is IPlugin plugin) |
| | | { |
| | | } |
| | | else |
| | | { |
| | | plugin = new PluginPackage(Instance); |
| | | } |
| | | plugin.SetLogger(LogManager.GetLogger(menu.Code)); |
| | | string configFolder = Path.Combine(PathHelper.ConfigFolder, menu.Code); |
| | | if (!Directory.Exists(configFolder)) |
| | | { |
| | | Directory.CreateDirectory(configFolder); |
| | | } |
| | | plugin.SetBaseFolder(configFolder); |
| | | plugin.InitFinish(); |
| | | pluginContainer.OnCloseEvent += plugin.OnClose; |
| | | pluginContainer.DoCloseEvent += plugin.DoClose; |
| | | Type SettingType = ass.GetType($"{menu.ClassName}_Setting"); |
| | | if (SettingType != null) |
| | | { |
| | | object SettingInstance = Activator.CreateInstance(SettingType); |
| | | if (SettingInstance is IFolderOwner fo) |
| | | { |
| | | fo.SetBaseFolder(configFolder); |
| | | } |
| | | menu.SettingControl = SettingInstance; |
| | | } |
| | | } |
| | | log.Info($"{this.lang("App.MenuLoadSuccess", menu.Name)} Cost: {(DateTime.Now - start).TotalMilliseconds}ms"); |
| | | LoadMessageAppend?.Invoke(this, |
| | | new LoadMsg |
| | | { |
| | | Type = LoadMsgType.LOADING, |
| | | Result = true, |
| | | Message = this.lang("App.MenuLoadSuccess", menu.Name), |
| | | }); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"Load Plugin [{menu.Name}] Failed.", ex); |
| | | LoadMessageAppend?.Invoke(this, new LoadMsg |
| | | { |
| | | Type = LoadMsgType.LOADING, |
| | | Result = false, |
| | | Message = this.lang("App.MenuLoadFailed", menu.Name, ex.Message, ex.StackTrace) |
| | | }); |
| | | } |
| | | finally |
| | | { |
| | | c.Signal(); |
| | | } |
| | | })); |
| | | c.Wait(); |
| | | } |
| | | } |
| | | |
| | | private PluginInfo ConvertPermission2PluginInfo(PermissionData data) |
| | | { |
| | | if (data.Type == "权限") return null; |
| | | PluginInfo info = new PluginInfo |
| | | { |
| | | Code = data.Code, |
| | | Name = data.Name, |
| | | Type = data.Type, |
| | | ParentCode = data.ParentCode, |
| | | DllPath = data.DllPath, |
| | | ClassName = data.ClassName, |
| | | IconFont = data.IconFont |
| | | //Icon = new BitmapImage(new Uri(Path.Combine(PathHelper.IconFolder, dr["iconPath"].ToString()))) |
| | | }; |
| | | if (data.Children != null && data.Children.Count > 0) |
| | | { |
| | | foreach (var child in data.Children) |
| | | { |
| | | var childInfo = ConvertPermission2PluginInfo(child); |
| | | if (childInfo != null) info.Children.Add(childInfo); |
| | | } |
| | | } |
| | | return info; |
| | | } |
| | | |
| | | public List<PluginInfo> LoadTreeMenu(Dispatcher dispatcher) |
| | | { |
| | | List<PluginInfo> menus = new List<PluginInfo>(); |
| | | Dictionary<string, PluginInfo> cache = new Dictionary<string, PluginInfo>(); |
| | | try |
| | | { |
| | | List<PermissionData> allPermissions = UserInfo.User?.AllPermissions; |
| | | if (allPermissions != null) |
| | | { |
| | | Application.Current.Dispatcher.Invoke(new Action(() => |
| | | { |
| | | log.Info(this.lang("App.MenuCount", allPermissions.Count)); |
| | | LoadMessageAppend?.Invoke(this, |
| | | new LoadMsg |
| | | { |
| | | Type = LoadMsgType.INIT, |
| | | NumData = allPermissions.Count, |
| | | Message = this.lang("App.MenuCount", allPermissions.Count), |
| | | }); |
| | | |
| | | foreach (PermissionData data in allPermissions) |
| | | { |
| | | if (data.Type == "Permission") continue; |
| | | var menu = new PluginInfo |
| | | { |
| | | Code = data.Code, |
| | | Name = data.Name, |
| | | Type = data.Type, |
| | | ParentCode = data.ParentCode, |
| | | DllPath = data.DllPath, |
| | | ClassName = data.ClassName, |
| | | IconFont = data.IconFont |
| | | }; |
| | | cache[menu.Code] = menu; |
| | | if (!cache.ContainsKey(menu.ParentCode)) |
| | | { |
| | | menus.Add(menu); |
| | | } |
| | | else |
| | | { |
| | | cache[menu.ParentCode].Children.Add(menu); |
| | | } |
| | | Plugins.Add(menu); |
| | | } |
| | | })); |
| | | LoadChildrenMenu(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | //LoadMessageAppend?.Invoke(this, ex.Message); |
| | | } |
| | | return menus; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.ComponentModel; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | using HMI.Core.Api; |
| | | using HMI.UI; |
| | | using log4net; |
| | | |
| | | namespace HMI.Base |
| | | { |
| | | |
| | | /// <summary> |
| | | /// PluginMaskControl.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class PluginMaskControl : UserControl, IPlugin, INotifyPropertyChanged |
| | | { |
| | | #region ... |
| | | public event PropertyChangedEventHandler PropertyChanged; |
| | | |
| | | protected void Set([CallerMemberName] string name = null) |
| | | { |
| | | |
| | | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); |
| | | } |
| | | |
| | | protected void Set<T>(ref T old, T @new, [CallerMemberName] string propertyName = null) |
| | | { |
| | | old = @new; |
| | | if (propertyName != null) |
| | | { |
| | | Set(propertyName); |
| | | } |
| | | } |
| | | #endregion |
| | | |
| | | static PluginMaskControl() |
| | | { |
| | | DefaultStyleKeyProperty.OverrideMetadata(typeof(PluginMaskControl), |
| | | new FrameworkPropertyMetadata(typeof(PluginMaskControl))); |
| | | } |
| | | |
| | | protected ILog log; |
| | | protected string BaseFolder; |
| | | |
| | | public virtual void Active() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void DeActive() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void Init() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void OnTabChange(object sender, object args) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void SetBaseFolder(string folder) |
| | | { |
| | | BaseFolder = folder; |
| | | } |
| | | |
| | | public virtual void SetLogger(ILog logger) |
| | | { |
| | | log = logger; |
| | | } |
| | | |
| | | public virtual void InitFinish() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void OnClose(object sender, CancelEventArgs e) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void DoClose(object sender, EventArgs e) |
| | | { |
| | | |
| | | } |
| | | |
| | | public void ShowMask(Action action, Action<Exception> reject = null, string title = null, string content = null) |
| | | { |
| | | MaskControl mask = this.Template.FindName("mask", this) as MaskControl; |
| | | if (mask != null) |
| | | { |
| | | mask.ShowMask(this, action, reject, title, content); |
| | | } |
| | | } |
| | | |
| | | public virtual bool CanActive() |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | public virtual bool CanDeActive() |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Reflection; |
| | | using System.Resources; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Runtime.InteropServices; |
| | | using System.Windows; |
| | | |
| | | // 有关程序集的一般信息由以下 |
| | | // 控制。更改这些特性值可修改 |
| | | // 与程序集关联的信息。 |
| | | [assembly: AssemblyTitle("HMI.Base")] |
| | | [assembly: AssemblyDescription("")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("Organization")] |
| | | [assembly: AssemblyProduct("HMI.Base")] |
| | | [assembly: AssemblyCopyright("Copyright © Organization 2025")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // 将 ComVisible 设置为 false 会使此程序集中的类型 |
| | | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 |
| | | //请将此类型的 ComVisible 特性设置为 true。 |
| | | [assembly: ComVisible(false)] |
| | | |
| | | //若要开始生成可本地化的应用程序,请设置 |
| | | //.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture> |
| | | //在 <PropertyGroup> 中。例如,如果你使用的是美国英语。 |
| | | //使用的是美国英语,请将 <UICulture> 设置为 en-US。 然后取消 |
| | | //对以下 NeutralResourceLanguage 特性的注释。 更新 |
| | | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 |
| | | |
| | | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] |
| | | |
| | | |
| | | [assembly:ThemeInfo( |
| | | ResourceDictionaryLocation.None, //主题特定资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //或应用程序资源字典中找到时使用) |
| | | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //、应用程序或任何主题专用资源字典中找到时使用) |
| | | )] |
| | | |
| | | |
| | | // 程序集的版本信息由下列四个值组成: |
| | | // |
| | | // 主版本 |
| | | // 次版本 |
| | | // 生成号 |
| | | // 修订号 |
| | | // |
| | | [assembly: AssemblyVersion("1.0.0.0")] |
| | | [assembly: AssemblyFileVersion("1.0.0.0")] |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // 此代码由工具生成。 |
| | | // 运行时版本: 4.0.30319.42000 |
| | | // |
| | | // 对此文件的更改可能导致不正确的行为,如果 |
| | | // 重新生成代码,则所做更改将丢失。 |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.Base.Properties { |
| | | |
| | | |
| | | /// <summary> |
| | | /// 强类型资源类,用于查找本地化字符串等。 |
| | | /// </summary> |
| | | // 此类是由 StronglyTypedResourceBuilder |
| | | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 |
| | | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen |
| | | // (以 /str 作为命令选项),或重新生成 VS 项目。 |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] |
| | | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | internal class Resources { |
| | | |
| | | private static global::System.Resources.ResourceManager resourceMan; |
| | | |
| | | private static global::System.Globalization.CultureInfo resourceCulture; |
| | | |
| | | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
| | | internal Resources() { |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 返回此类使用的缓存 ResourceManager 实例。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Resources.ResourceManager ResourceManager { |
| | | get { |
| | | if ((resourceMan == null)) { |
| | | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HMI.Base.Properties.Resources", typeof(Resources).Assembly); |
| | | resourceMan = temp; |
| | | } |
| | | return resourceMan; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 重写当前线程的 CurrentUICulture 属性,对 |
| | | /// 使用此强类型资源类的所有资源查找执行重写。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Globalization.CultureInfo Culture { |
| | | get { |
| | | return resourceCulture; |
| | | } |
| | | set { |
| | | resourceCulture = value; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <root> |
| | | <!-- |
| | | Microsoft ResX Schema |
| | | |
| | | Version 2.0 |
| | | |
| | | The primary goals of this format is to allow a simple XML format |
| | | that is mostly human readable. The generation and parsing of the |
| | | various data types are done through the TypeConverter classes |
| | | associated with the data types. |
| | | |
| | | Example: |
| | | |
| | | ... ado.net/XML headers & schema ... |
| | | <resheader name="resmimetype">text/microsoft-resx</resheader> |
| | | <resheader name="version">2.0</resheader> |
| | | <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
| | | <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
| | | <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
| | | <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
| | | <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
| | | <value>[base64 mime encoded serialized .NET Framework object]</value> |
| | | </data> |
| | | <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
| | | <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
| | | <comment>This is a comment</comment> |
| | | </data> |
| | | |
| | | There are any number of "resheader" rows that contain simple |
| | | name/value pairs. |
| | | |
| | | Each data row contains a name, and value. The row also contains a |
| | | type or mimetype. Type corresponds to a .NET class that support |
| | | text/value conversion through the TypeConverter architecture. |
| | | Classes that don't support this are serialized and stored with the |
| | | mimetype set. |
| | | |
| | | The mimetype is used for serialized objects, and tells the |
| | | ResXResourceReader how to depersist the object. This is currently not |
| | | extensible. For a given mimetype the value must be set accordingly: |
| | | |
| | | Note - application/x-microsoft.net.object.binary.base64 is the format |
| | | that the ResXResourceWriter will generate, however the reader can |
| | | read any of the formats listed below. |
| | | |
| | | mimetype: application/x-microsoft.net.object.binary.base64 |
| | | value : The object must be serialized with |
| | | : System.Serialization.Formatters.Binary.BinaryFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.soap.base64 |
| | | value : The object must be serialized with |
| | | : System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.bytearray.base64 |
| | | value : The object must be serialized into a byte array |
| | | : using a System.ComponentModel.TypeConverter |
| | | : and then encoded with base64 encoding. |
| | | --> |
| | | <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
| | | <xsd:element name="root" msdata:IsDataSet="true"> |
| | | <xsd:complexType> |
| | | <xsd:choice maxOccurs="unbounded"> |
| | | <xsd:element name="metadata"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | <xsd:attribute name="type" type="xsd:string" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="assembly"> |
| | | <xsd:complexType> |
| | | <xsd:attribute name="alias" type="xsd:string" /> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="data"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> |
| | | <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="resheader"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" use="required" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:choice> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:schema> |
| | | <resheader name="resmimetype"> |
| | | <value>text/microsoft-resx</value> |
| | | </resheader> |
| | | <resheader name="version"> |
| | | <value>2.0</value> |
| | | </resheader> |
| | | <resheader name="reader"> |
| | | <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | <resheader name="writer"> |
| | | <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | </root> |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // This code was generated by a tool. |
| | | // Runtime Version:4.0.30319.42000 |
| | | // |
| | | // Changes to this file may cause incorrect behavior and will be lost if |
| | | // the code is regenerated. |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.Base.Properties |
| | | { |
| | | |
| | | |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] |
| | | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase |
| | | { |
| | | |
| | | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); |
| | | |
| | | public static Settings Default |
| | | { |
| | | get |
| | | { |
| | | return defaultInstance; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version='1.0' encoding='utf-8'?> |
| | | <SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> |
| | | <Profiles> |
| | | <Profile Name="(Default)" /> |
| | | </Profiles> |
| | | <Settings /> |
| | | </SettingsFile> |
| New file |
| | |
| | | using System; |
| | | using System.ComponentModel; |
| | | using System.Windows.Controls; |
| | | using HMI.Core.Api; |
| | | using log4net; |
| | | |
| | | namespace HMI.Base |
| | | { |
| | | public class SimpleControl : UserControl, IPlugin |
| | | { |
| | | protected ILog log; |
| | | protected string BaseFolder; |
| | | |
| | | public virtual void Active() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void DeActive() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void Init() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void OnTabChange(object sender, object args) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void SetBaseFolder(string folder) |
| | | { |
| | | BaseFolder = folder; |
| | | } |
| | | |
| | | public virtual void SetLogger(ILog logger) |
| | | { |
| | | log = logger; |
| | | } |
| | | |
| | | public virtual void InitFinish() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void OnClose(object sender, CancelEventArgs e) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void DoClose(object sender, EventArgs e) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual bool CanActive() |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | public virtual bool CanDeActive() |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Api; |
| | | using HMI.UI; |
| | | using log4net; |
| | | using System; |
| | | using System.ComponentModel; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.Base |
| | | { |
| | | /// <summary> |
| | | /// PluginMaskControl.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class SimpleMaskControl : UserControl, IPlugin |
| | | { |
| | | static SimpleMaskControl() |
| | | { |
| | | DefaultStyleKeyProperty.OverrideMetadata(typeof(SimpleMaskControl), |
| | | new FrameworkPropertyMetadata(typeof(SimpleMaskControl))); |
| | | } |
| | | |
| | | protected ILog log; |
| | | protected string BaseFolder; |
| | | |
| | | public virtual void Active() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void DeActive() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void Init() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void OnTabChange(object sender, object args) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void SetBaseFolder(string folder) |
| | | { |
| | | BaseFolder = folder; |
| | | } |
| | | |
| | | public virtual void SetLogger(ILog logger) |
| | | { |
| | | log = logger; |
| | | } |
| | | |
| | | public virtual void InitFinish() |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void OnClose(object sender, CancelEventArgs e) |
| | | { |
| | | |
| | | } |
| | | |
| | | public virtual void DoClose(object sender, EventArgs e) |
| | | { |
| | | |
| | | } |
| | | |
| | | public void ShowMask(Action action, Action<Exception> reject = null, string title = null, string content = null) |
| | | { |
| | | MaskControl mask = this.Template.FindName("mask", this) as MaskControl; |
| | | if (mask != null) |
| | | { |
| | | mask.ShowMask(this, action, reject, title, content); |
| | | } |
| | | } |
| | | |
| | | public virtual bool CanActive() |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | public virtual bool CanDeActive() |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:local="clr-namespace:HMI.Base" |
| | | xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"> |
| | | <Style TargetType="local:SimpleMaskControl"> |
| | | <Setter Property="Template"> |
| | | <Setter.Value> |
| | | <ControlTemplate TargetType="local:SimpleMaskControl"> |
| | | <Grid Background="{TemplateBinding Background}"> |
| | | <ContentPresenter Content="{TemplateBinding Content}"/> |
| | | |
| | | <ui:MaskControl x:Name="mask"/> |
| | | </Grid> |
| | | </ControlTemplate> |
| | | </Setter.Value> |
| | | </Setter> |
| | | </Style> |
| | | |
| | | <Style TargetType="local:PluginMaskControl"> |
| | | <Setter Property="Template"> |
| | | <Setter.Value> |
| | | <ControlTemplate TargetType="local:PluginMaskControl"> |
| | | <Grid> |
| | | <ContentPresenter Content="{TemplateBinding Content}" Margin="{TemplateBinding Padding}"/> |
| | | <ui:MaskControl x:Name="mask"/> |
| | | </Grid> |
| | | </ControlTemplate> |
| | | </Setter.Value> |
| | | </Setter> |
| | | </Style> |
| | | </ResourceDictionary> |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <packages> |
| | | <package id="log4net" version="3.0.4" targetFramework="net472" /> |
| | | </packages> |
| New file |
| | |
| | | using System; |
| | | |
| | | namespace HMI.Core |
| | | { |
| | | [AttributeUsage(AttributeTargets.Property)] |
| | | public class ExcelColumnAttribute : Attribute |
| | | { |
| | | public string Name { get; private set; } |
| | | public bool IgnoreExport { get; private set; } |
| | | public bool IgnoreImport { get; private set; } |
| | | |
| | | public ExcelColumnAttribute(string name, bool ignoreExport = false, bool ignoreImport = false) |
| | | { |
| | | this.Name = name; |
| | | this.IgnoreExport = ignoreExport; |
| | | this.IgnoreImport = ignoreImport; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Runtime.InteropServices; |
| | | |
| | | namespace HMI.Core.Annotation |
| | | { |
| | | |
| | | [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] |
| | | [ComVisible(true)] |
| | | public class PluginClassNameAttribute : Attribute |
| | | { |
| | | public string ClassName { get; private set; } |
| | | |
| | | public PluginClassNameAttribute(string ClassName) |
| | | { |
| | | this.ClassName = ClassName; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public class AlarmShowHelper |
| | | { |
| | | private static List<IAlarmHandler> handlers; |
| | | |
| | | private static IAlarmShow shower; |
| | | |
| | | public static void Register(IAlarmShow shower) |
| | | { |
| | | if (AlarmShowHelper.shower == null) |
| | | { |
| | | AlarmShowHelper.shower = shower; |
| | | } |
| | | } |
| | | |
| | | public static void UnRegister(IAlarmShow shower) |
| | | { |
| | | if (AlarmShowHelper.shower == shower) |
| | | { |
| | | AlarmShowHelper.shower = null; |
| | | } |
| | | } |
| | | |
| | | public static void AddHandler(IAlarmHandler handler) |
| | | { |
| | | if (handlers == null) |
| | | { |
| | | handlers = new List<IAlarmHandler>(); |
| | | } |
| | | if (!handlers.Contains(handler)) |
| | | { |
| | | handlers.Add(handler); |
| | | } |
| | | } |
| | | |
| | | public static bool ShowAlarm(AlarmText alarm) |
| | | { |
| | | if (handlers != null) |
| | | { |
| | | foreach (var handler in handlers) |
| | | { |
| | | try |
| | | { |
| | | handler.HandleAlarm(alarm); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (shower != null) |
| | | { |
| | | return shower.ShowAlarm(alarm); |
| | | } |
| | | return false; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public interface IAlarmShow |
| | | { |
| | | bool ShowAlarm(AlarmText alarm); |
| | | } |
| | | |
| | | public interface IAlarmHandler |
| | | { |
| | | void HandleAlarm(AlarmText alarm); |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public interface IConfigManager |
| | | { |
| | | object GetConfig(); |
| | | |
| | | void SaveConfig(); |
| | | |
| | | void ResetConfig(); |
| | | } |
| | | } |
| New file |
| | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public interface IConfigRefresh |
| | | { |
| | | void OnConfigRefrsh(object sender, object setting); |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public interface IFolderOwner |
| | | { |
| | | /// <summary> |
| | | /// 插件加载完之后会分配一个路径,可以存放一些独立的文件 |
| | | /// </summary> |
| | | /// <param name="folder"></param> |
| | | void SetBaseFolder(string folder); |
| | | } |
| | | } |
| New file |
| | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public interface ILogWriter |
| | | { |
| | | void Debug(string source, string content); |
| | | void Info(string source, string content); |
| | | void Warn(string source, string content); |
| | | void Error(string source, string content); |
| | | } |
| | | } |
| New file |
| | |
| | | using log4net; |
| | | using System; |
| | | using System.ComponentModel; |
| | | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public interface IPlugin : IFolderOwner |
| | | { |
| | | /// <summary> |
| | | /// 初始化 |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | void Init(); |
| | | |
| | | /// <summary> |
| | | /// 初始化完成(执行完构造器,并且设置好logger folder等资源之后执行) |
| | | /// </summary> |
| | | void InitFinish(); |
| | | |
| | | /// <summary> |
| | | /// 插件被激活时的操作 |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | void Active(); |
| | | |
| | | /// <summary> |
| | | /// 插件被激活时的操作 |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | bool CanActive(); |
| | | |
| | | /// <summary> |
| | | /// 失活时的操作 |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | void DeActive(); |
| | | |
| | | bool CanDeActive(); |
| | | |
| | | /// <summary> |
| | | /// 分配日志logger(log4net) |
| | | /// </summary> |
| | | /// <param name="logger"></param> |
| | | void SetLogger(ILog logger); |
| | | |
| | | /// <summary> |
| | | /// 将要关闭程序事件 |
| | | /// </summary> |
| | | void OnClose(object sender, CancelEventArgs e); |
| | | |
| | | /// <summary> |
| | | /// 关闭程序事件 |
| | | /// </summary> |
| | | /// <param name="sender"></param> |
| | | /// <param name="e"></param> |
| | | void DoClose(object sender, EventArgs e); |
| | | |
| | | /// <summary> |
| | | /// 面板切换事件(预留) |
| | | /// </summary> |
| | | /// <param name="sender"></param> |
| | | /// <param name="args"></param> |
| | | void OnTabChange(object sender, object args); |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.ComponentModel; |
| | | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public interface IPluginContainer |
| | | { |
| | | event CancelEventHandler OnCloseEvent; |
| | | event EventHandler DoCloseEvent; |
| | | event EventHandler LoadComplateEvent; |
| | | } |
| | | } |
| New file |
| | |
| | | using log4net; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.ComponentModel; |
| | | using System.Reflection; |
| | | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public class PluginPackage : IPlugin |
| | | { |
| | | public static Dictionary<object, IPlugin> Cache = new Dictionary<object, IPlugin>(); |
| | | public static IPlugin Get(object obj) |
| | | { |
| | | if (obj is null) return null; |
| | | if (obj is IPlugin plugin) |
| | | { |
| | | return plugin; |
| | | } |
| | | if (Cache.ContainsKey(obj)) return Cache[obj]; |
| | | PluginPackage pluginPackage = new PluginPackage(obj); |
| | | Cache.Add(obj, pluginPackage); |
| | | return pluginPackage; |
| | | } |
| | | |
| | | private object Content; |
| | | |
| | | private MethodInfo ActiveMethod; |
| | | private MethodInfo DeActiveMethod; |
| | | private MethodInfo DoCloseMethod; |
| | | private MethodInfo InitMethod; |
| | | private MethodInfo InitFinishMethod; |
| | | private MethodInfo OnCloseMethod; |
| | | private MethodInfo OnTabChangeMethod; |
| | | private MethodInfo SetBaseFolderMethod; |
| | | private MethodInfo SetLoggerMethod; |
| | | private MethodInfo CanDeActiveMethod; |
| | | private MethodInfo CanActiveMethod; |
| | | |
| | | public PluginPackage(object content) |
| | | { |
| | | this.Content = content; |
| | | ActiveMethod = content.GetType().GetMethod("Active"); |
| | | DeActiveMethod = content.GetType().GetMethod("DeActive"); |
| | | DoCloseMethod = content.GetType().GetMethod("DoClose", new Type[] { typeof(object), typeof(EventArgs) }); |
| | | InitMethod = content.GetType().GetMethod("Init"); |
| | | InitFinishMethod = content.GetType().GetMethod("InitFinish"); |
| | | OnCloseMethod = content.GetType().GetMethod("OnClose", new Type[] { typeof(object), typeof(CancelEventArgs) }); |
| | | OnTabChangeMethod = content.GetType().GetMethod("OnTabChange", new Type[] { typeof(object), typeof(object) }); |
| | | SetBaseFolderMethod = content.GetType().GetMethod("SetBaseFolder", new Type[] { typeof(string) }); |
| | | SetLoggerMethod = content.GetType().GetMethod("SetLogger", new Type[] { typeof(ILog) }); |
| | | CanDeActiveMethod = content.GetType().GetMethod("CanDeActive"); |
| | | CanActiveMethod = content.GetType().GetMethod("CanActive"); |
| | | } |
| | | |
| | | public void Active() |
| | | { |
| | | ActiveMethod?.Invoke(Content, null); |
| | | } |
| | | |
| | | public void DeActive() |
| | | { |
| | | DeActiveMethod?.Invoke(Content, null); |
| | | } |
| | | |
| | | public void DoClose(object sender, EventArgs e) |
| | | { |
| | | DoCloseMethod?.Invoke(Content, new object[] { sender, e }); |
| | | } |
| | | |
| | | public void Init() |
| | | { |
| | | InitMethod?.Invoke(Content, null); |
| | | } |
| | | |
| | | public void InitFinish() |
| | | { |
| | | InitFinishMethod?.Invoke(Content, null); |
| | | } |
| | | |
| | | public void OnClose(object sender, CancelEventArgs e) |
| | | { |
| | | OnCloseMethod?.Invoke(Content, new object[] { sender, e }); |
| | | } |
| | | |
| | | public void OnTabChange(object sender, object args) |
| | | { |
| | | OnTabChangeMethod?.Invoke(Content, new object[] { sender, args }); |
| | | } |
| | | |
| | | public void SetBaseFolder(string folder) |
| | | { |
| | | SetBaseFolderMethod?.Invoke(Content, new object[] { folder }); |
| | | } |
| | | |
| | | public void SetLogger(ILog logger) |
| | | { |
| | | SetLoggerMethod?.Invoke(Content, new object[] { logger }); |
| | | } |
| | | |
| | | public bool CanActive() |
| | | { |
| | | return (bool)CanActiveMethod?.Invoke(Content, null); |
| | | } |
| | | |
| | | public bool CanDeActive() |
| | | { |
| | | return (bool)CanDeActiveMethod?.Invoke(Content, null); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using System.Linq; |
| | | using System.Windows; |
| | | |
| | | namespace HMI.Core.DpUtil |
| | | { |
| | | public class PermissionHelper |
| | | { |
| | | public static string GetRequiredPermission(UIElement element) |
| | | { |
| | | return (string)element.GetValue(RequiredPermissionProperty); |
| | | } |
| | | |
| | | public static void SetRequiredPermission(UIElement element, string requiredPermission) |
| | | { |
| | | element.SetValue(RequiredPermissionProperty, requiredPermission); |
| | | } |
| | | |
| | | public static readonly DependencyProperty RequiredPermissionProperty = |
| | | DependencyProperty.RegisterAttached("RequiredPermission", typeof(string), typeof(UIElement), |
| | | new PropertyMetadata(string.Empty, OnRequiredPermissionChanged)); |
| | | |
| | | private static void OnRequiredPermissionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) |
| | | { |
| | | if (d is UIElement element) |
| | | { |
| | | if (e.Property == RequiredPermissionProperty) |
| | | { |
| | | var requiredPermission = (string)e.NewValue; |
| | | if (string.IsNullOrEmpty(requiredPermission)) return; |
| | | if (UserInfo.User == null) |
| | | { |
| | | element.Visibility = Visibility.Collapsed; |
| | | return; |
| | | } |
| | | if (UserInfo.User?.AllPermissions == null) |
| | | { |
| | | element.Visibility = Visibility.Collapsed; |
| | | return; |
| | | } |
| | | if (UserInfo.User.AllPermissions.FirstOrDefault(p => p.Code == requiredPermission) != null) return; |
| | | element.Visibility = Visibility.Collapsed; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class AlarmSetting : SelectBase |
| | | { |
| | | public static readonly string BLOCK_TYPE = "BLOCK"; |
| | | public static readonly string MARK_TYPE = "MARK"; |
| | | /// <summary> |
| | | /// 开始DB地址 |
| | | /// </summary> |
| | | [ExcelColumn("DB地址")] |
| | | public string DBAddress { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 起始Byte位 |
| | | /// </summary> |
| | | [ExcelColumn("起始位")] |
| | | public string StartAddress { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 长度 |
| | | /// </summary> |
| | | [ExcelColumn("长度")] |
| | | public int Length { get; set; } |
| | | |
| | | [ExcelColumn("报警代码")] |
| | | public string AlarmCode { get; set; } |
| | | |
| | | [ExcelColumn("级别")] |
| | | public double Level { get; set; } |
| | | |
| | | [ExcelColumn("报警描述")] |
| | | public string Description { get; set; } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Xml.Serialization; |
| | | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class AlarmText |
| | | { |
| | | /// <summary> |
| | | /// 报警代码 |
| | | /// </summary> |
| | | [ExcelColumn("组合报警代码")] |
| | | public string AlarmCode { get; set; } |
| | | |
| | | public string VirtualId { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 工位编号 |
| | | /// </summary> |
| | | [ExcelColumn("设备号")] |
| | | public string OpNo { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 模块编号 |
| | | /// </summary> |
| | | [ExcelColumn("模块号")] |
| | | public string ModuleNo { get; set; } |
| | | |
| | | ///// <summary> |
| | | ///// 模块名称(CN) |
| | | ///// </summary> |
| | | //[ExcelColumn("模块名称-CN")] |
| | | //public string ModuleName_CN { get; set; } |
| | | |
| | | ///// <summary> |
| | | ///// 模块名称(EN) |
| | | ///// </summary> |
| | | //[ExcelColumn("模块名称-US")] |
| | | //public string ModuleName_EN { get; set; } |
| | | |
| | | ///// <summary> |
| | | ///// 模块报警文本(CN) |
| | | ///// </summary> |
| | | //[ExcelColumn("报警信息文本-CN")] |
| | | //public string MAlarmText_CN { get; set; } |
| | | |
| | | ///// <summary> |
| | | ///// 模块报警文本(EN) |
| | | ///// </summary> |
| | | //[ExcelColumn("报警信息文本-US")] |
| | | //public string MAlarmText_EN { get; set; } |
| | | |
| | | |
| | | /// <summary> |
| | | /// 模块报警代码 |
| | | /// </summary> |
| | | [ExcelColumn("报警代码")] |
| | | public string MAlarmCode { get; set; } |
| | | |
| | | |
| | | |
| | | /// <summary> |
| | | /// 报警时间 |
| | | /// </summary> |
| | | public string Time { get; set; } |
| | | |
| | | [XmlIgnore] |
| | | public string AlarmInfo { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 报警来源 |
| | | /// </summary> |
| | | [XmlIgnore] |
| | | public string Source { get; set; } |
| | | |
| | | [XmlIgnore] |
| | | public string PLC_DB_BLOCK { get; set; } |
| | | |
| | | [XmlIgnore] |
| | | public double Level { get; set; } |
| | | |
| | | [XmlIgnore] |
| | | public bool IsPLCAlarm { get; set; } |
| | | } |
| | | } |
| New file |
| | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class LangInfo |
| | | { |
| | | public LangInfo(string code, string name) |
| | | { |
| | | Code = code; |
| | | Name = name; |
| | | } |
| | | |
| | | public string Code { get; private set; } |
| | | public string Name { get; private set; } |
| | | } |
| | | } |
| New file |
| | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public enum LoadMsgType |
| | | { |
| | | INIT, LOADING, FINISHED |
| | | } |
| | | |
| | | public class LoadMsg |
| | | { |
| | | public string Message { get; set; } |
| | | public bool? Result { get; set; } |
| | | public LoadMsgType Type { get; set; } |
| | | public int NumData { get; set; } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.ComponentModel; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Windows.Input; |
| | | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class NotifyBase : INotifyPropertyChanged |
| | | { |
| | | #region ... |
| | | public virtual event PropertyChangedEventHandler PropertyChanged; |
| | | |
| | | protected virtual void Set([CallerMemberName] string name = null) |
| | | { |
| | | |
| | | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); |
| | | } |
| | | |
| | | protected void Set<T>(ref T old, T @new, [CallerMemberName] string propertyName = null) |
| | | { |
| | | old = @new; |
| | | if (propertyName != null) |
| | | { |
| | | Set(propertyName); |
| | | } |
| | | } |
| | | #endregion |
| | | } |
| | | |
| | | public class RelayCommand<T> : ICommand |
| | | { |
| | | /// <summary> |
| | | /// 命令能否执行 |
| | | /// </summary> |
| | | readonly Func<bool> _canExecute; |
| | | /// <summary> |
| | | /// 命令执行的方法 |
| | | /// </summary> |
| | | readonly Action<T> _execute; |
| | | |
| | | /// <summary> |
| | | /// 命令的构造函数 |
| | | /// </summary> |
| | | /// <param name="action">命令需执行的方法</param> |
| | | /// <param name="canExecute">命令是否可以执行的方法</param> |
| | | public RelayCommand(Action<T> action, Func<bool> canExecute) |
| | | { |
| | | _execute = action; |
| | | _canExecute = canExecute; |
| | | } |
| | | |
| | | public RelayCommand(Action<T> action) |
| | | { |
| | | _execute = action; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 判断命令是否可以执行 |
| | | /// </summary> |
| | | /// <param name="parameter"></param> |
| | | /// <returns></returns> |
| | | public bool CanExecute(Object parameter) |
| | | { |
| | | if (_canExecute == null) |
| | | return true; |
| | | return _canExecute(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 执行命令 |
| | | /// </summary> |
| | | /// <param name="parameter"></param> |
| | | public void Execute(Object parameter) |
| | | { |
| | | _execute((T)parameter); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 事件追加、移除 |
| | | /// </summary> |
| | | public event EventHandler CanExecuteChanged |
| | | { |
| | | add |
| | | { |
| | | if (_canExecute != null) |
| | | CommandManager.RequerySuggested += value; |
| | | } |
| | | remove |
| | | { |
| | | if (_canExecute != null) |
| | | CommandManager.RequerySuggested -= value; |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Xml.Serialization; |
| | | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class ParameterData : SelectBase |
| | | { |
| | | public string Id { get; set; } |
| | | [ExcelColumn("设置项名称")] |
| | | public string Name { get; set; } |
| | | [ExcelColumn("设置项类型")] |
| | | public string Type { get; set; } |
| | | |
| | | [ExcelColumn("是否为数组")] |
| | | public bool IsArray { get; set; } = false; |
| | | |
| | | [ExcelColumn("数组长度")] |
| | | public int Length { get; set; } = 1; |
| | | |
| | | [ExcelColumn("值")] |
| | | public string Value { get; set; } |
| | | [ExcelColumn("单位")] |
| | | public string Unit { get; set; } |
| | | [ExcelColumn("描述")] |
| | | public string Description { get; set; } |
| | | [ExcelColumn("分组")] |
| | | public string PGroup { get; set; } |
| | | [ExcelColumn("来源")] |
| | | public string Source { get; set; } |
| | | |
| | | public string ProductName { get; set; } |
| | | |
| | | [XmlIgnore] |
| | | public string ShowGroup |
| | | { |
| | | get => string.IsNullOrEmpty(PGroup) ? "暂无" : PGroup; |
| | | set |
| | | { |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class PermissionData |
| | | { |
| | | public string Id { get; set; } |
| | | public string Code { get; set; } |
| | | public string Name { get; set; } |
| | | public string IconPath { get; set; } |
| | | public string Icon { get; set; } |
| | | public string Level { get; set; } |
| | | public string Type { get; set; } |
| | | public string ParentCode { get; set; } |
| | | public string ParentName { get; set; } |
| | | public string DllPath { get; set; } |
| | | public string ClassName { get; set; } |
| | | public string IconFont { get; set; } |
| | | |
| | | public double? Sort { get; set; } |
| | | |
| | | public List<PermissionData> Children { get; set; } = new List<PermissionData>(); |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Xml.Serialization; |
| | | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class ProductData:SelectBase |
| | | { |
| | | [XmlIgnore] |
| | | public string Id { get; set; } |
| | | public string Name { get; set; } |
| | | public bool IsMaster { get; set; } |
| | | public string ParentId { get; set; } |
| | | public string dir; |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Xml.Serialization; |
| | | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class SelectBase : NotifyBase |
| | | { |
| | | private bool isSelected; |
| | | [XmlIgnore] |
| | | public virtual bool IsSelected |
| | | { |
| | | get => isSelected; |
| | | set { Set(ref isSelected, value); } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.ComponentModel; |
| | | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class SortParam |
| | | { |
| | | public string field { get; set; } |
| | | public ListSortDirection direction { get; set; } |
| | | public string sort { get; set; } |
| | | } |
| | | |
| | | public class PageParam |
| | | { |
| | | public int limit { get; set; } |
| | | public int offset { get; set; } |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class TestItemData : SelectBase |
| | | { |
| | | /// <summary> |
| | | /// 参数ID |
| | | /// </summary> |
| | | public string ID { get; set; } = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 测试项名称 |
| | | /// </summary> |
| | | [ExcelColumn("测试项名称")] |
| | | public string Name { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 测试项类型 |
| | | /// </summary> |
| | | [ExcelColumn("测试项类型")] |
| | | public string Type { get; set; } = "NumberTest"; |
| | | |
| | | /// <summary> |
| | | /// 字符串型数据 |
| | | /// </summary> |
| | | [ExcelColumn("字符串")] |
| | | public string StrValue { get; set; } |
| | | |
| | | |
| | | /// <summary> |
| | | /// 测试项下限 |
| | | /// </summary> |
| | | [ExcelColumn("下限")] |
| | | public double LimitDown { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 测试项上限 |
| | | /// </summary> |
| | | [ExcelColumn("上限")] |
| | | public double LimitUp { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 使能 |
| | | /// </summary> |
| | | [ExcelColumn("使能")] |
| | | public bool Enable { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 补偿值 |
| | | /// </summary> |
| | | [ExcelColumn("补偿值")] |
| | | public double Offset { get; set; } |
| | | |
| | | /// <summary> |
| | | /// 来源 |
| | | /// </summary> |
| | | [ExcelColumn("来源")] |
| | | public string Source { get; set; } = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 分组 |
| | | /// </summary> |
| | | public string Group { get; set; } = string.Empty; |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | |
| | | namespace HMI.Core.Entity |
| | | { |
| | | public class UserInfo |
| | | { |
| | | public static UserInfo User { get; private set; } |
| | | public UserInfo(string id, string code, string username, string name, |
| | | List<PermissionData> permissions, |
| | | List<PermissionData> allPermissions, |
| | | string password = null) |
| | | { |
| | | Id = id; |
| | | Code = code; |
| | | Username = username; |
| | | Name = name; |
| | | Password = password; |
| | | Permissions = permissions; |
| | | AllPermissions = allPermissions; |
| | | } |
| | | |
| | | public string Id { get; private set; } |
| | | public string Code { get; private set; } |
| | | public string Username { get; private set; } |
| | | public string Name { get; private set; } |
| | | public string Password { get; private set; } |
| | | public List<PermissionData> Permissions { get; private set; } |
| | | public List<PermissionData> AllPermissions { get; private set; } |
| | | |
| | | public static void Login(UserInfo info) |
| | | { |
| | | if (User == null) User = info; |
| | | } |
| | | |
| | | public static void Logout() |
| | | { |
| | | User = null; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | using System.Windows; |
| | | |
| | | namespace HMI.Core.Api |
| | | { |
| | | public class TabChangeEventArgs |
| | | { |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace HMI.Core.Exceptions |
| | | { |
| | | public class FormValidateException : Exception |
| | | { |
| | | public readonly List<FormGroupValidateResult> Result; |
| | | public bool Success |
| | | { |
| | | get => Result == null || Result.Count == 0; |
| | | } |
| | | |
| | | public FormValidateException() |
| | | { |
| | | Result = new List<FormGroupValidateResult>(); |
| | | } |
| | | } |
| | | |
| | | public class FormGroupValidateResult |
| | | { |
| | | public readonly string Message; |
| | | public readonly object Sender; |
| | | |
| | | public FormGroupValidateResult(string message, object sender) |
| | | { |
| | | Message = message; |
| | | Sender = sender; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Globalization; |
| | | using System.Windows.Data; |
| | | |
| | | namespace HMI.Core |
| | | { |
| | | public class WidthSizeConverter : IValueConverter |
| | | { |
| | | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
| | | { |
| | | /// "{size},{基准值},{最小值},{最大值}" |
| | | if (parameter == null) return 15; |
| | | |
| | | string[] ps = parameter.ToString().Split(','); |
| | | double size = double.Parse(ps[0]); |
| | | double baseSize = double.Parse(ps[1]); |
| | | double min = -1, max = -1; |
| | | |
| | | if (ps.Length >= 3) |
| | | { |
| | | min = double.Parse(ps[2]); |
| | | } |
| | | if (ps.Length >= 4) |
| | | { |
| | | max = double.Parse(ps[3]); |
| | | } |
| | | |
| | | if (value is double width) |
| | | { |
| | | double res = CountFontSize(size, width, baseSize); |
| | | res = min > 0 && res < min ? min : res; |
| | | res = max > 0 && res > max ? max : res; |
| | | return res; |
| | | } |
| | | return 15; |
| | | } |
| | | |
| | | public static double CountFontSize(double size, double limit, double baseSize) |
| | | { |
| | | return size * limit / baseSize; |
| | | } |
| | | |
| | | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
| | | { |
| | | throw new NotImplementedException(); |
| | | } |
| | | } |
| | | |
| | | public class HeightSizeConverter : IValueConverter |
| | | { |
| | | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
| | | { |
| | | if (parameter == null) return 15; |
| | | |
| | | string[] ps = parameter.ToString().Split(','); |
| | | int size = int.Parse(ps[0]); |
| | | int baseSize = int.Parse(ps[1]); |
| | | double min = -1, max = -1; |
| | | |
| | | if (ps.Length >= 3) |
| | | { |
| | | min = double.Parse(ps[2]); |
| | | } |
| | | if (ps.Length >= 4) |
| | | { |
| | | max = double.Parse(ps[3]); |
| | | } |
| | | if (value is double height) |
| | | { |
| | | double res = CountFontSize(size, height, baseSize); |
| | | res = min > 0 && res < min ? min : res; |
| | | res = max > 0 && res > max ? max : res; |
| | | return res; |
| | | } |
| | | return 15; |
| | | } |
| | | |
| | | public static double CountFontSize(double size, double limit, double baseSize) |
| | | { |
| | | return size * limit / baseSize; |
| | | } |
| | | |
| | | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
| | | { |
| | | throw new NotImplementedException(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| | | <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
| | | <PropertyGroup> |
| | | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
| | | <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
| | | <ProjectGuid>{3E3BF44A-0B69-45C5-9512-528CB48A7106}</ProjectGuid> |
| | | <OutputType>Library</OutputType> |
| | | <AppDesignerFolder>Properties</AppDesignerFolder> |
| | | <RootNamespace>HMI.Core</RootNamespace> |
| | | <AssemblyName>HMI.Core</AssemblyName> |
| | | <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> |
| | | <FileAlignment>512</FileAlignment> |
| | | <Deterministic>true</Deterministic> |
| | | <NuGetPackageImportStamp> |
| | | </NuGetPackageImportStamp> |
| | | <TargetFrameworkProfile /> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <DebugType>full</DebugType> |
| | | <Optimize>false</Optimize> |
| | | <OutputPath>bin\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
| | | <DebugType>pdbonly</DebugType> |
| | | <Optimize>true</Optimize> |
| | | <OutputPath>bin\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <OutputPath>bin\x64\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <DebugType>full</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> |
| | | <OutputPath>bin\x64\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <Optimize>true</Optimize> |
| | | <DebugType>pdbonly</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <OutputPath>bin\x86\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <DebugType>full</DebugType> |
| | | <PlatformTarget>x86</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> |
| | | <OutputPath>bin\x86\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <Optimize>true</Optimize> |
| | | <DebugType>pdbonly</DebugType> |
| | | <PlatformTarget>x86</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <ItemGroup> |
| | | <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="PresentationCore" /> |
| | | <Reference Include="PresentationFramework" /> |
| | | <Reference Include="System" /> |
| | | <Reference Include="System.ComponentModel.DataAnnotations" /> |
| | | <Reference Include="System.Configuration" /> |
| | | <Reference Include="System.Core" /> |
| | | <Reference Include="System.Data.SQLite"> |
| | | <HintPath>..\Libs\System.Data.SQLite.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="System.Messaging" /> |
| | | <Reference Include="System.Numerics" /> |
| | | <Reference Include="System.Web" /> |
| | | <Reference Include="System.Xaml" /> |
| | | <Reference Include="System.Xml.Linq" /> |
| | | <Reference Include="Microsoft.CSharp" /> |
| | | <Reference Include="System.Data" /> |
| | | <Reference Include="System.Net.Http" /> |
| | | <Reference Include="System.Xml" /> |
| | | <Reference Include="WindowsBase" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="Annotation\ExcelColumn.cs" /> |
| | | <Compile Include="Api\AlarmShowHelper.cs" /> |
| | | <Compile Include="Api\IAlarmShow.cs" /> |
| | | <Compile Include="Api\IConfigManager.cs" /> |
| | | <Compile Include="Api\IConfigRefresh.cs" /> |
| | | <Compile Include="Api\IFolderOwner.cs" /> |
| | | <Compile Include="Api\ILogWriter.cs" /> |
| | | <Compile Include="Api\IPlugin.cs" /> |
| | | <Compile Include="Api\IPluginContainer.cs" /> |
| | | <Compile Include="Annotation\InitAttribute.cs" /> |
| | | <Compile Include="Api\PluginPackage.cs" /> |
| | | <Compile Include="DpUtil\PermissionHelper.cs" /> |
| | | <Compile Include="Entity\AlarmSetting.cs" /> |
| | | <Compile Include="Entity\AlarmText.cs" /> |
| | | <Compile Include="Entity\LangInfo.cs" /> |
| | | <Compile Include="Entity\LoadMsg.cs" /> |
| | | <Compile Include="Entity\NotifyBase.cs" /> |
| | | <Compile Include="Entity\ParameterData.cs" /> |
| | | <Compile Include="Entity\PermissionData.cs" /> |
| | | <Compile Include="Entity\ProductData.cs" /> |
| | | <Compile Include="Entity\SelectBase.cs" /> |
| | | <Compile Include="Entity\SortParam.cs" /> |
| | | <Compile Include="Entity\TestItemData.cs" /> |
| | | <Compile Include="Entity\UserInfo.cs" /> |
| | | <Compile Include="Event\TabChange.cs" /> |
| | | <Compile Include="Exceptions\FormValidateException.cs" /> |
| | | <Compile Include="FontSizeConverter.cs" /> |
| | | <Compile Include="LangConverter.cs" /> |
| | | <Compile Include="Manager\TestItemManager.cs" /> |
| | | <Compile Include="Properties\AssemblyInfo.cs" /> |
| | | <Compile Include="Setting\ProductSetting.cs" /> |
| | | <Compile Include="Setting\ParamSetting.cs" /> |
| | | <Compile Include="Setting\SystemSetting.cs" /> |
| | | <Compile Include="Util\BeanUtil.cs" /> |
| | | <Compile Include="Util\BindingProxy.cs" /> |
| | | <Compile Include="Util\DllHelper.cs" /> |
| | | <Compile Include="Util\HexUtil.cs" /> |
| | | <Compile Include="Util\JsonUtil.cs" /> |
| | | <Compile Include="Util\LangUtil.cs" /> |
| | | <Compile Include="Util\LoggerHolder.cs" /> |
| | | <Compile Include="Util\MSMQHelper.cs" /> |
| | | <Compile Include="Manager\ParameterManager.cs" /> |
| | | <Compile Include="Util\PathHelper.cs" /> |
| | | <Compile Include="Manager\ProductManager.cs" /> |
| | | <Compile Include="Util\Sqlite3Helper.cs" /> |
| | | <Compile Include="Util\SqliteHelper.cs" /> |
| | | <Compile Include="Util\UtilContainer.cs" /> |
| | | <Compile Include="Util\XmlHelper.cs" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <None Include="packages.config" /> |
| | | </ItemGroup> |
| | | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| | | </Project> |
| New file |
| | |
| | | using HMI.Core.Util; |
| | | using System; |
| | | using System.Globalization; |
| | | using System.Windows.Data; |
| | | |
| | | namespace HMI.Core |
| | | { |
| | | public class LangConverter : IValueConverter |
| | | { |
| | | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
| | | { |
| | | return LangUtil.Get(parameter.ToString()); |
| | | } |
| | | |
| | | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
| | | { |
| | | return null; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using System.Linq; |
| | | |
| | | namespace HMI.Core.Manager |
| | | { |
| | | /// <summary> |
| | | /// TODO 优化数据源,需要支持sqlite和xml |
| | | /// </summary> |
| | | public class ParameterManager |
| | | { |
| | | |
| | | /// <summary> |
| | | /// 获取通用参数 |
| | | /// </summary> |
| | | /// <typeparam name="T"></typeparam> |
| | | /// <returns></returns> |
| | | public static T GetCommonParameter<T>() where T : IList<ParameterData> |
| | | { |
| | | string path = PathHelper.ParameterDataPath; |
| | | if (!File.Exists(path)) return default(T); |
| | | T datas = XmlHelper.DeserializeFromXml<T>(path); |
| | | return datas; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取通用参数 |
| | | /// </summary> |
| | | /// <typeparam name="T"></typeparam> |
| | | /// <returns></returns> |
| | | public static ParameterData GetCommonParameterByName(string name) |
| | | { |
| | | string path = PathHelper.ParameterDataPath; |
| | | if (!File.Exists(path)) return null; |
| | | List<ParameterData> datas = XmlHelper.DeserializeFromXml<List<ParameterData>>(path); |
| | | return datas.FirstOrDefault(d => d.Name == name); |
| | | } |
| | | |
| | | public static T GetCommonParameterByName<T>(string name, T defaultValue) |
| | | { |
| | | try |
| | | { |
| | | string path = PathHelper.ParameterDataPath; |
| | | if (!File.Exists(path)) return defaultValue; |
| | | List<ParameterData> datas = XmlHelper.DeserializeFromXml<List<ParameterData>>(path); |
| | | var data = datas.FirstOrDefault(d => d.Name == name); |
| | | if (data == null) |
| | | { |
| | | return defaultValue; |
| | | } |
| | | return (T)Convert.ChangeType(data.Value, typeof(T)); |
| | | } |
| | | catch |
| | | { |
| | | return defaultValue; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 保存通用参数 |
| | | /// </summary> |
| | | /// <param name="datas"></param> |
| | | /// <exception cref="Exception"></exception> |
| | | public static void SaveCommonParameter(IList<ParameterData> datas) |
| | | { |
| | | if (datas == null) |
| | | { |
| | | throw new Exception("不能为null"); |
| | | } |
| | | string path = PathHelper.ParameterDataPath; |
| | | XmlHelper.SerializeToXml(path, datas); |
| | | } |
| | | |
| | | public static string GetCommonParameterValue(string path, string name) |
| | | { |
| | | if (!File.Exists(path)) return null; |
| | | List<ParameterData> datas = XmlHelper.DeserializeFromXml<List<ParameterData>>(path); |
| | | ParameterData data = datas.FirstOrDefault(d => d.Name == name); |
| | | if (data == null) return null; |
| | | return data.Value; |
| | | } |
| | | |
| | | public static bool UpdateCommonParameter(string name, object value, out string errMsg) |
| | | { |
| | | errMsg = string.Empty; |
| | | try |
| | | { |
| | | string path = PathHelper.ParameterDataPath; |
| | | if (!File.Exists(path)) |
| | | { |
| | | errMsg = "配置文件不存在"; |
| | | return false; |
| | | } |
| | | List<ParameterData> datas = XmlHelper.DeserializeFromXml<List<ParameterData>>(path); |
| | | ParameterData data = datas.FirstOrDefault(d => d.Name == name); |
| | | if (data != null) |
| | | { |
| | | data.Value = value.ToString(); |
| | | XmlHelper.SerializeToXml(path, datas); |
| | | return true; |
| | | } |
| | | string type = "String"; |
| | | if (value is bool) |
| | | { |
| | | type = "Bool"; |
| | | } |
| | | else if (value is int) |
| | | { |
| | | type = "Int"; |
| | | } |
| | | else if (value is double) |
| | | { |
| | | type = "Double"; |
| | | } |
| | | |
| | | data = new ParameterData |
| | | { |
| | | Name = name, |
| | | Type = type, |
| | | Value = value.ToString() |
| | | }; |
| | | datas.Add(data); |
| | | XmlHelper.SerializeToXml(path, datas); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | errMsg = ex.Message; |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Setting; |
| | | using HMI.Core.Util; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using System.Linq; |
| | | |
| | | namespace HMI.Core.Manager |
| | | { |
| | | /// <summary> |
| | | /// TODO 优化数据源,需要支持sqlite和xml |
| | | /// </summary> |
| | | public class ProductManager |
| | | { |
| | | public static string GetFolder() |
| | | { |
| | | if (ParamSetting.ShareConfig != null) |
| | | { |
| | | return Path.Combine(ParamSetting.ShareConfig, "Product"); |
| | | } |
| | | return Path.Combine(PathHelper.ConfigFolder, "Product"); |
| | | } |
| | | |
| | | public static string GetDataFilePath() |
| | | { |
| | | return Path.Combine(GetFolder(), "Product.data"); |
| | | } |
| | | |
| | | public static string GetParamFolderByProductName(string productName) |
| | | { |
| | | return Path.Combine(GetFolder(), "Parameter", $"Parameter_{productName}.data"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取保存的产品 |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | public static T GetProducts<T>() where T : IList<ProductData> |
| | | { |
| | | string path = GetDataFilePath(); |
| | | if (!File.Exists(path)) return default(T); |
| | | T datas = XmlHelper.DeserializeFromXml<T>(path); |
| | | return datas; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取保存的产品 |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | public static T GetParametersByProductName<T>(string productName) where T : IList<ParameterData> |
| | | { |
| | | string path = GetParamFolderByProductName(productName); |
| | | if (!File.Exists(path)) return default(T); |
| | | T datas = XmlHelper.DeserializeFromXml<T>(path); |
| | | return datas; |
| | | } |
| | | |
| | | |
| | | public static List<ProductData> GetProducts() |
| | | { |
| | | List<ProductData> datas = GetProducts<List<ProductData>>(); |
| | | return datas ?? new List<ProductData>(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据产品名称获取产品参数 |
| | | /// </summary> |
| | | /// <param name="productName"></param> |
| | | /// <returns></returns> |
| | | public static List<ParameterData> GetParametersByProductName(string productName) |
| | | { |
| | | List<ParameterData> datas = GetParametersByProductName<List<ParameterData>>(productName); |
| | | return datas ?? new List<ParameterData>(); |
| | | } |
| | | |
| | | public static ParameterData GetParameterByProductAndName(string product, string name) |
| | | { |
| | | List<ParameterData> datas = GetParametersByProductName<List<ParameterData>>(product); |
| | | return datas?.FirstOrDefault(e => e.Name == name); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | |
| | | namespace HMI.Core.Manager |
| | | { |
| | | /// <summary> |
| | | /// TODO 优化数据源,需要支持sqlite和xml |
| | | /// </summary> |
| | | public class TestItemManager |
| | | { |
| | | /// <summary> |
| | | /// 根据产品获取测试项 |
| | | /// </summary> |
| | | /// <typeparam name="T"></typeparam> |
| | | /// <returns></returns> |
| | | public static T GetTestItemByProduct<T>(string product) where T : IList<TestItemData> |
| | | { |
| | | string path = Path.Combine(PathHelper.TestItemDataFolder, $"TestItem_{product}.setting"); |
| | | if (!File.Exists(path)) return default(T); |
| | | T datas = XmlHelper.DeserializeFromXml<T>(path); |
| | | return datas; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 根据产品保存测试项 |
| | | /// </summary> |
| | | /// <param name="datas"></param> |
| | | /// <exception cref="Exception"></exception> |
| | | public static void SaveTestItemByProduct(string product, IList<TestItemData> datas) |
| | | { |
| | | if (datas == null) |
| | | { |
| | | throw new Exception("不能为null"); |
| | | } |
| | | string path = Path.Combine(PathHelper.TestItemDataFolder, $"TestItem_{product}.setting"); |
| | | XmlHelper.SerializeToXml(path, datas); |
| | | } |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Reflection; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Runtime.InteropServices; |
| | | |
| | | // 有关程序集的一般信息由以下 |
| | | // 控制。更改这些特性值可修改 |
| | | // 与程序集关联的信息。 |
| | | [assembly: AssemblyTitle("HMI.Core")] |
| | | [assembly: AssemblyDescription("")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("HP Inc.")] |
| | | [assembly: AssemblyProduct("HMI.Core")] |
| | | [assembly: AssemblyCopyright("Copyright © HP Inc. 2024")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // 将 ComVisible 设置为 false 会使此程序集中的类型 |
| | | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 |
| | | //请将此类型的 ComVisible 特性设置为 true。 |
| | | [assembly: ComVisible(false)] |
| | | |
| | | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID |
| | | [assembly: Guid("3e3bf44a-0b69-45c5-9512-528cb48a7106")] |
| | | |
| | | // 程序集的版本信息由下列四个值组成: |
| | | // |
| | | // 主版本 |
| | | // 次版本 |
| | | // 生成号 |
| | | // 修订号 |
| | | // |
| | | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 |
| | | //通过使用 "*",如下所示: |
| | | // [assembly: AssemblyVersion("1.0.*")] |
| | | [assembly: AssemblyVersion("1.0.0.1")] |
| | | [assembly: AssemblyFileVersion("1.0.0.1")] |
| New file |
| | |
| | | using HMI.Core.Manager; |
| | | using HMI.Core.Util; |
| | | using System.Collections; |
| | | using System.Configuration; |
| | | using System.IO; |
| | | using System.Runtime.CompilerServices; |
| | | |
| | | namespace HMI.Core.Setting |
| | | { |
| | | public class ParamSetting |
| | | { |
| | | private static Hashtable customConfig = (Hashtable)ConfigurationManager.GetSection("TestResultConfig"); |
| | | |
| | | private static string GetValue([CallerMemberName] string propertyName = null) |
| | | { |
| | | return (customConfig != null && customConfig.ContainsKey(propertyName)) ? customConfig[propertyName].ToString() : null; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// MesAddress |
| | | /// </summary> |
| | | public static string MesAddress { get; private set; } = "127.0.0.1:8080"; |
| | | /// <summary> |
| | | /// 老化导出行转列 |
| | | /// </summary> |
| | | public static bool ReverseCSV { get; private set; } = false; |
| | | |
| | | /// <summary> |
| | | /// 客户编码 |
| | | /// </summary> |
| | | public static string CustomerCode { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 生产数据DB存放目录 |
| | | /// </summary> |
| | | |
| | | public static string DataLogFolder { get; private set; } = null; |
| | | |
| | | /// <summary> |
| | | /// 是否开启水冷机设置 |
| | | /// </summary> |
| | | public static bool ShowCoolerSetting { get; private set; } = false; |
| | | |
| | | public static bool ShowWaterChart { get; private set; } = false; |
| | | |
| | | /// <summary> |
| | | /// 按型号计算探针 |
| | | /// </summary> |
| | | public static bool ProbeByModel { get; private set; } = false; |
| | | public static int ReverseCSVSkipRow { get; private set; } = 3; |
| | | |
| | | /// <summary> |
| | | /// 操作是否需要密码 |
| | | /// </summary> |
| | | |
| | | public static bool IsOptRequirePassword { get; private set; } = false; |
| | | |
| | | /// <summary> |
| | | /// 启用扫码换型 |
| | | /// </summary> |
| | | public static bool EnableAutoModel { get; private set; } = false; |
| | | |
| | | public const string ShareConfigKey = "ShareConfig"; |
| | | public static string ShareConfig { get; private set; } = null; |
| | | public static bool ShowBurnInAbort { get; private set; } = true; |
| | | public static string SocketNames { get; private set; } = string.Empty; |
| | | public static string StationName { get; private set; } = string.Empty; |
| | | public static string MainSeqPath { get; private set; } = string.Empty; |
| | | |
| | | |
| | | public static string EngineType |
| | | { |
| | | get |
| | | { |
| | | var data = GetValue(); |
| | | return data == null ? "TestStand" : data.ToString(); |
| | | } |
| | | } |
| | | |
| | | static ParamSetting() |
| | | { |
| | | try |
| | | { |
| | | var value = ParameterManager.GetCommonParameterByName("MesAddress"); |
| | | if (value != null) |
| | | { |
| | | MesAddress = value.Value; |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | } |
| | | |
| | | try |
| | | { |
| | | var value = ParameterManager.GetCommonParameterByName("ReverseCSV"); |
| | | if (value != null) |
| | | { |
| | | ReverseCSV = bool.Parse(value.Value); |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | } |
| | | |
| | | try |
| | | { |
| | | var value = ParameterManager.GetCommonParameterByName("TS_DataLogPath"); |
| | | DataLogFolder = value?.Value ?? Path.Combine(PathHelper.ConfigFolder, "DataBoard"); |
| | | } |
| | | catch |
| | | { |
| | | DataLogFolder = Path.Combine(PathHelper.ConfigFolder, "DataBoard"); |
| | | } |
| | | |
| | | try |
| | | { |
| | | var value = ParameterManager.GetCommonParameterByName("ShowWaterChart"); |
| | | if (value != null) |
| | | { |
| | | ShowWaterChart = bool.Parse(value.Value); |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | } |
| | | |
| | | try |
| | | { |
| | | var value = ParameterManager.GetCommonParameterByName("ShowCoolerSetting"); |
| | | if (value != null) |
| | | { |
| | | ShowCoolerSetting = bool.Parse(value.Value); |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | } |
| | | |
| | | try |
| | | { |
| | | var value = ParameterManager.GetCommonParameterByName("CustomerCode"); |
| | | if (value != null) |
| | | { |
| | | CustomerCode = value.Value; |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | } |
| | | |
| | | try |
| | | { |
| | | var value = ParameterManager.GetCommonParameterByName("ProbeByModel"); |
| | | if (value != null) |
| | | { |
| | | ProbeByModel = bool.Parse(value.Value); |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | } |
| | | |
| | | try |
| | | { |
| | | var value = ParameterManager.GetCommonParameterByName("ReverseCSVSkipRow"); |
| | | if (value != null) |
| | | { |
| | | ReverseCSVSkipRow = int.Parse(value.Value); |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | } |
| | | |
| | | try |
| | | { |
| | | IsOptRequirePassword = ParameterManager.GetCommonParameterByName<bool>("IsOptRequirePassword", false); |
| | | } |
| | | catch |
| | | { |
| | | |
| | | } |
| | | |
| | | try |
| | | { |
| | | EnableAutoModel = ParameterManager.GetCommonParameterByName<bool>("EnableAutoModel", false); |
| | | } |
| | | catch |
| | | { |
| | | |
| | | } |
| | | |
| | | try |
| | | { |
| | | ShareConfig = ParameterManager.GetCommonParameterByName<string>(ShareConfigKey, null); |
| | | } |
| | | catch |
| | | { |
| | | |
| | | } |
| | | |
| | | try |
| | | { |
| | | ShowBurnInAbort = ParameterManager.GetCommonParameterByName<bool>("ShowBurnInAbort", true); |
| | | } |
| | | catch |
| | | { |
| | | |
| | | } |
| | | |
| | | try |
| | | { |
| | | SocketNames = ParameterManager.GetCommonParameterByName<string>("SocketNames", string.Empty); |
| | | } |
| | | catch |
| | | { |
| | | |
| | | } |
| | | |
| | | try |
| | | { |
| | | StationName = ParameterManager.GetCommonParameterByName<string>("StationName", string.Empty); |
| | | } |
| | | catch |
| | | { |
| | | |
| | | } |
| | | |
| | | try |
| | | { |
| | | |
| | | MainSeqPath = ParameterManager.GetCommonParameterByName<string>("BurnInMainSeq", string.Empty); |
| | | } |
| | | catch |
| | | { |
| | | |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Manager; |
| | | using HMI.Core.Util; |
| | | using System.IO; |
| | | |
| | | namespace HMI.Core.Setting |
| | | { |
| | | public class ProductSetting |
| | | { |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.IO; |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | |
| | | namespace HMI.Core.Setting |
| | | { |
| | | public class SystemSetting |
| | | { |
| | | public static SystemSetting Instance { get; private set; } |
| | | /// <summary> |
| | | /// 系统设置改变事件 |
| | | /// </summary> |
| | | public static event EventHandler<SystemSetting> SystemSettingChanged; |
| | | |
| | | private static string FilePath = Path.Combine(PathHelper.SettingFolder, "SystemConfig.xml"); |
| | | |
| | | static SystemSetting() |
| | | { |
| | | LoadFromXml(); |
| | | } |
| | | |
| | | private SystemSetting() |
| | | { |
| | | |
| | | } |
| | | |
| | | public string LogoPath { get; private set; } = ".\\Logo.png"; |
| | | public string LoginLogoPath { get; private set; } = ".\\LoginLogo.png"; |
| | | public string WindowTitle { get; private set; } = "产品测试平台"; |
| | | public string PlcIp { get; private set; } = "127.0.0.1"; |
| | | public int PlcPort { get; private set; } = 102; |
| | | public bool PlcEnabled { get; private set; } |
| | | public string CurrentLanguage { get; private set; } = "zh-CN"; |
| | | public int LogExpiredDays { get; private set; } = 7; |
| | | |
| | | private static void LoadFromXml() |
| | | { |
| | | if (Instance != null) return; |
| | | if (!File.Exists(FilePath)) |
| | | { |
| | | Instance = new SystemSetting(); |
| | | } |
| | | else |
| | | { |
| | | SystemSettingModel model = XmlHelper.DeserializeFromXml<SystemSettingModel>(FilePath); |
| | | SystemSetting setting = new SystemSetting(); |
| | | BeanUtil.Copy(model, setting); |
| | | Instance = setting; |
| | | } |
| | | } |
| | | |
| | | public static SystemSettingModel GetModel() |
| | | { |
| | | return XmlHelper.DeserializeFromXml<SystemSettingModel>(FilePath); |
| | | } |
| | | |
| | | public static void ReLoad() |
| | | { |
| | | if (!File.Exists(FilePath)) |
| | | { |
| | | return; |
| | | } |
| | | else |
| | | { |
| | | SystemSettingModel model = XmlHelper.DeserializeFromXml<SystemSettingModel>(FilePath); |
| | | SystemSetting setting = new SystemSetting(); |
| | | BeanUtil.Copy(model, setting); |
| | | Instance = setting; |
| | | } |
| | | SystemSettingChanged?.Invoke(null, Instance); |
| | | } |
| | | } |
| | | |
| | | |
| | | public class SystemSettingModel : NotifyBase |
| | | { |
| | | private string logoPath; |
| | | public string LogoPath |
| | | { |
| | | get => logoPath; |
| | | set { Set(ref logoPath, value); } |
| | | } |
| | | |
| | | private string loginLogoPath; |
| | | public string LoginLogoPath |
| | | { |
| | | get => loginLogoPath; |
| | | set { Set(ref loginLogoPath, value); } |
| | | } |
| | | |
| | | private string windowTitle; |
| | | public string WindowTitle |
| | | { |
| | | get => windowTitle; |
| | | set { Set(ref windowTitle, value); } |
| | | } |
| | | |
| | | private string plcIp; |
| | | public string PlcIp |
| | | { |
| | | get => plcIp; |
| | | set { Set(ref plcIp, value); } |
| | | } |
| | | |
| | | private int plcPort; |
| | | public int PlcPort |
| | | { |
| | | get => plcPort; |
| | | set { Set(ref plcPort, value); } |
| | | } |
| | | |
| | | private bool plcEnabled; |
| | | public bool PlcEnabled |
| | | { |
| | | get => plcEnabled; |
| | | set { Set(ref plcEnabled, value); } |
| | | } |
| | | |
| | | private string currentLanguage; |
| | | public string CurrentLanguage |
| | | { |
| | | get => currentLanguage; |
| | | set { Set(ref currentLanguage, value); } |
| | | } |
| | | |
| | | private int logExpiredDays; |
| | | public int LogExpiredDays |
| | | { |
| | | get => logExpiredDays; |
| | | set { Set(ref logExpiredDays, value); } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Data; |
| | | using System.Reflection; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public class BeanUtil |
| | | { |
| | | public static void Copy<S, T>(S source, T target) |
| | | { |
| | | Type sourceType = typeof(S); |
| | | Type targetType = typeof(T); |
| | | PropertyInfo[] fieldArray = targetType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); |
| | | foreach (PropertyInfo field in fieldArray) |
| | | { |
| | | string fileName = field.Name; |
| | | PropertyInfo sourceField = sourceType.GetProperty(fileName); |
| | | if (sourceField == null) continue; |
| | | field.SetValue(target, sourceField.GetValue(source)); |
| | | } |
| | | |
| | | } |
| | | |
| | | public static List<T> ReadBeanFromDataTable<T>(DataTable table) |
| | | { |
| | | if (table == null || table.Rows.Count <= 0) return new List<T>(); |
| | | |
| | | List<T> list = new List<T>(); |
| | | foreach (DataRow row in table.Rows) |
| | | { |
| | | T bean = Activator.CreateInstance<T>(); |
| | | foreach (var prop in typeof(T).GetProperties()) |
| | | { |
| | | if (table.Columns.Contains(prop.Name)) |
| | | { |
| | | var value = row[prop.Name]; |
| | | if (value != DBNull.Value) |
| | | { |
| | | prop.SetValue(bean, Convert.ChangeType(value, prop.PropertyType)); |
| | | } |
| | | } |
| | | } |
| | | list.Add(bean); |
| | | } |
| | | return list; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Windows; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public class BindingProxy : Freezable |
| | | { |
| | | protected override Freezable CreateInstanceCore() |
| | | { |
| | | return new BindingProxy(); |
| | | } |
| | | |
| | | public object Data |
| | | { |
| | | get { return (object)GetValue(DataProperty); } |
| | | set { SetValue(DataProperty, value); } |
| | | } |
| | | |
| | | public static readonly DependencyProperty DataProperty = |
| | | DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), |
| | | new UIPropertyMetadata(null)); |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.CodeDom.Compiler; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Reflection; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | using Microsoft.CSharp; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | /// <summary> |
| | | /// DLL文件操作类 |
| | | /// </summary> |
| | | public class DllHelper |
| | | { |
| | | /// <summary> |
| | | /// 获取当前DLL所处的路径 |
| | | /// </summary> |
| | | /// <returns>Dll路径</returns> |
| | | public static string GetAssemblyPath() |
| | | { |
| | | string CodeBase = System.Reflection.Assembly.GetExecutingAssembly().CodeBase; |
| | | CodeBase = CodeBase.Substring(8, CodeBase.Length - 8); // 8是 file:// 的长度 |
| | | string[] arrSection = CodeBase.Split(new char[] { '/' }); |
| | | string FolderPath = string.Empty; |
| | | for (int i = 0; i < arrSection.Length - 1; i++) |
| | | { |
| | | FolderPath += arrSection[i] + "\\"; |
| | | } |
| | | |
| | | return FolderPath; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 导入DLL(要导入的DLL要放在程序的根目录中) |
| | | /// </summary> |
| | | /// <param name="DLLName">要导入的DLL名称</param> |
| | | public static void ImportDLL(string DllPath) |
| | | { |
| | | try |
| | | { |
| | | //string DllPath = GetAssemblyPath() + DLLName; |
| | | Assembly[] assemblyArray = AppDomain.CurrentDomain.GetAssemblies(); |
| | | foreach (Assembly dll in assemblyArray) |
| | | { |
| | | if (dll.Location == DllPath) |
| | | { |
| | | return; |
| | | } |
| | | } |
| | | |
| | | Assembly.LoadFrom(DllPath); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception(ex.Message); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 导入DLL |
| | | /// </summary> |
| | | /// <param name="DllPath">要导入的DLL文件路径</param> |
| | | public static void ImportDLL2(string DllPath) |
| | | { |
| | | try |
| | | { |
| | | Assembly[] assemblyArray = AppDomain.CurrentDomain.GetAssemblies(); |
| | | foreach (Assembly dll in assemblyArray) |
| | | { |
| | | if (!dll.FullName.Contains("Microsoft.GeneratedCode")) |
| | | { |
| | | if (dll.Location == DllPath) |
| | | { |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | | Assembly.LoadFile(DllPath); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception(ex.Message); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 生成无参数的构造函数的实例 |
| | | /// </summary> |
| | | /// <param name="NameSpaceStr">命名空间</param> |
| | | /// <param name="ClassStr">包含命名控件的类名</param> |
| | | /// <returns>生成的对应类的实例</returns> |
| | | public static object CreateInstance(string NameSpaceStr, string ClassStr) |
| | | { |
| | | try |
| | | { |
| | | Type DllType = Assembly.Load(NameSpaceStr).GetType(ClassStr); // 生成类型 |
| | | object Instance = Activator.CreateInstance(DllType); // 生成实例 |
| | | return Instance; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception(ex.Message); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 动态编译和执行语句 |
| | | /// </summary> |
| | | /// <param name="Expression">要编译执行的语句</param> |
| | | /// <returns>执行的结果</returns> |
| | | public static object GetValue(string Expression) |
| | | { |
| | | object Rtn = null; |
| | | // 1.CSharpCodePrivoder |
| | | CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider(); |
| | | // 2.ICodeComplier |
| | | ICodeCompiler objICodeCompiler = objCSharpCodePrivoder.CreateCompiler(); |
| | | // 3.CompilerParameters |
| | | CompilerParameters objCompilerParameters = new CompilerParameters(); |
| | | objCompilerParameters.ReferencedAssemblies.Add("System.dll"); |
| | | objCompilerParameters.GenerateExecutable = false; |
| | | objCompilerParameters.GenerateInMemory = true; |
| | | // 4.CompilerResults |
| | | CompilerResults cr = objICodeCompiler.CompileAssemblyFromSource(objCompilerParameters, GenerateCode(Expression)); |
| | | if (cr.Errors.HasErrors) |
| | | { |
| | | throw new Exception("编译错误:" + cr.Errors[0].ErrorText); |
| | | } |
| | | else |
| | | { |
| | | // 通过反射,调用HelloWorld的实例 |
| | | Assembly objAssembly = cr.CompiledAssembly; |
| | | object Obj = objAssembly.CreateInstance("CommonLibs.Eval"); |
| | | MethodInfo objMI = Obj.GetType().GetMethod("GetValue"); |
| | | Rtn = objMI.Invoke(Obj, null); |
| | | } |
| | | return Rtn; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 生成编译语句 |
| | | /// </summary> |
| | | /// <param name="Expression">需要编译的语句</param> |
| | | /// <returns>编译执行后的结果</returns> |
| | | private static string GenerateCode(string Expression) |
| | | { |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.Append("using System;"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append("namespace CommonLibs"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append("{"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append(" public class Eval"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append(" {"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append(" public object GetValue()"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append(" {"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append(" return " + Expression + " ;"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append(" }"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append(" }"); |
| | | sb.Append(Environment.NewLine); |
| | | sb.Append("}"); |
| | | string code = sb.ToString(); |
| | | return code; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// |
| | | /// </summary> |
| | | /// <typeparam name="T"></typeparam> |
| | | /// <param name="obj"></param> |
| | | /// <returns></returns> |
| | | public static T DeepCopy<T>(T obj) |
| | | { |
| | | //如果是字符串或值类型则直接返回 |
| | | if (obj is string || obj.GetType().IsValueType) return obj; |
| | | |
| | | object retval = Activator.CreateInstance(obj.GetType()); |
| | | FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); |
| | | foreach (FieldInfo field in fields) |
| | | { |
| | | try { field.SetValue(retval, DeepCopy(field.GetValue(obj))); } |
| | | catch { } |
| | | } |
| | | return (T)retval; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | //public class HexUtil |
| | | //{ |
| | | // public static byte[] ToBytesFromHexString(string hexString) |
| | | // { |
| | | // //以 ' ' 分割字符串,并去掉空字符 |
| | | // string[] chars = hexString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); |
| | | // byte[] returnBytes; |
| | | // returnBytes = BitConverter.GetBytes(Convert.ToInt32(chars[0])); |
| | | // return returnBytes; |
| | | // } |
| | | //} |
| | | } |
| New file |
| | |
| | | using Newtonsoft.Json; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public class JsonUtil |
| | | { |
| | | static readonly JsonSerializerSettings JsSetting = new JsonSerializerSettings() |
| | | { |
| | | NullValueHandling = NullValueHandling.Ignore |
| | | }; |
| | | public static T Deserialize<T>(string jsonStr) |
| | | { |
| | | return JsonConvert.DeserializeObject<T>(jsonStr); |
| | | } |
| | | |
| | | public static string Serialize(object obj) |
| | | { |
| | | return JsonConvert.SerializeObject(obj); |
| | | } |
| | | |
| | | public static string Stringfiy(object obj) |
| | | { |
| | | return JsonConvert.SerializeObject(obj, Formatting.Indented, JsSetting); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Setting; |
| | | using Newtonsoft.Json; |
| | | using Newtonsoft.Json.Linq; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Globalization; |
| | | using System.IO; |
| | | using System.Windows; |
| | | using System.Windows.Markup; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public class LangUtil |
| | | { |
| | | public const string DefaultLang = "zh-CN"; |
| | | public static List<LangInfo> langs = new List<LangInfo>() { |
| | | new LangInfo(DefaultLang,"中文"), |
| | | new LangInfo("en-US","English") |
| | | }; |
| | | |
| | | private static ResourceDictionary currentResource; |
| | | |
| | | private static Dictionary<string, ResourceDictionary> ResourceMap |
| | | = new Dictionary<string, ResourceDictionary>(); |
| | | private static Dictionary<string, JObject> JsonMap = new Dictionary<string, JObject>(); |
| | | |
| | | static LangUtil() |
| | | { |
| | | |
| | | } |
| | | |
| | | private static string GetLang() |
| | | { |
| | | var lang = SystemSetting.Instance.CurrentLanguage; |
| | | if (string.IsNullOrEmpty(lang)) lang = "zh-CN"; |
| | | return lang; |
| | | } |
| | | |
| | | public static void LoadLang() |
| | | { |
| | | var lang = GetLang(); |
| | | if (!ResourceMap.ContainsKey(lang)) return; |
| | | var newLang = ResourceMap[lang]; |
| | | if (currentResource == newLang) |
| | | { |
| | | return; |
| | | } |
| | | if (currentResource != null) |
| | | { |
| | | Application.Current.Resources.MergedDictionaries.Remove(currentResource); |
| | | } |
| | | currentResource = newLang; |
| | | Application.Current.Resources.MergedDictionaries.Add(currentResource); |
| | | } |
| | | |
| | | public static void Load() |
| | | { |
| | | try |
| | | { |
| | | LoadAllData(); |
| | | LoadLang(); |
| | | } |
| | | finally |
| | | { |
| | | |
| | | } |
| | | } |
| | | |
| | | public static string Get(string key) |
| | | { |
| | | var lang = GetLang(); |
| | | if (!JsonMap.ContainsKey(lang)) return key; |
| | | if (!JsonMap[lang].ContainsKey(key)) return key; |
| | | return JsonMap[lang][key].ToString(); |
| | | } |
| | | |
| | | private static void LoadDataByJson(string lang) |
| | | { |
| | | string langFolder = PathHelper.LangFolder; |
| | | string folder = Path.Combine(langFolder, lang); |
| | | if (!Directory.Exists(folder)) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | string[] files = Directory.GetFiles(folder, "*.json", SearchOption.AllDirectories); |
| | | |
| | | foreach (var path in files) |
| | | { |
| | | try |
| | | { |
| | | using (FileStream stream = File.Open(path, FileMode.Open)) |
| | | { |
| | | using (StreamReader streamReader = new StreamReader(stream)) |
| | | { |
| | | JsonTextReader jsonTextReader = new JsonTextReader(streamReader); |
| | | JObject jsonObject = (JObject)JToken.ReadFrom(jsonTextReader); |
| | | |
| | | if (!JsonMap.ContainsKey(lang)) |
| | | { |
| | | JsonMap[lang] = jsonObject; |
| | | } |
| | | else |
| | | { |
| | | foreach (var item in jsonObject) |
| | | { |
| | | JsonMap[lang][item.Key] = item.Value; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static void LoadXamlFromJson(string lang) |
| | | { |
| | | LoadDataByJson(lang); |
| | | if (!JsonMap.ContainsKey(lang)) return; |
| | | JObject json = JsonMap[lang]; |
| | | var resource = new ResourceDictionary(); |
| | | foreach (var item in json) |
| | | { |
| | | resource.Add(item.Key, item.Value.ToString()); |
| | | } |
| | | ResourceMap[lang] = resource; |
| | | } |
| | | |
| | | private static void LoadXamlFromJson2(string lang) |
| | | { |
| | | LoadDataByJson(lang); |
| | | |
| | | if (!JsonMap.ContainsKey(lang)) return; |
| | | JObject json = JsonMap[lang]; |
| | | var resource = new ResourceDictionary(); |
| | | foreach (var item in json) |
| | | { |
| | | resource.Add(item.Key, item.Value.ToString()); |
| | | } |
| | | ResourceMap[lang] = resource; |
| | | if (lang != DefaultLang) |
| | | { |
| | | var defaultMap = new ResourceDictionary(); |
| | | foreach (var item in json) |
| | | { |
| | | defaultMap.Add(item.Key, item.Key); |
| | | } |
| | | ResourceMap[DefaultLang] = defaultMap; |
| | | } |
| | | } |
| | | |
| | | private static void LoadAllData() |
| | | { |
| | | foreach (var lang in langs) |
| | | { |
| | | LoadXamlFromJson(lang.Code); |
| | | } |
| | | } |
| | | |
| | | public static string Format(string key, params object[] datas) |
| | | { |
| | | return string.Format(Get(key), datas); |
| | | } |
| | | } |
| | | |
| | | public static class LangReader |
| | | { |
| | | public static string lang(this object dp, string key, params object[] datas) |
| | | { |
| | | return string.Format(LangUtil.Get(key), datas); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Api; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public class LoggerHelper |
| | | { |
| | | public static ILogWriter Logger { get; private set; } |
| | | |
| | | public static void Register(ILogWriter logger) |
| | | { |
| | | if (Logger == null) |
| | | { |
| | | Logger = logger; |
| | | } |
| | | } |
| | | |
| | | public static void Debug(string source, string content) |
| | | { |
| | | Logger?.Debug(source, content); |
| | | } |
| | | |
| | | public static void Info(string source, string content) |
| | | { |
| | | Logger?.Info(source, content); |
| | | } |
| | | |
| | | public static void Warn(string source, string content) |
| | | { |
| | | Logger?.Warn(source, content); |
| | | } |
| | | |
| | | public static void Error(string source, string content) |
| | | { |
| | | Logger?.Error(source, content); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | //----------------------------------------------------------------------- |
| | | // <copyright file="MSMQHelper.cs" company="iPilotWhale"> |
| | | // Suzhou Linktron Systems Co.,Ltd . All rights reserved. |
| | | // </copyright> |
| | | // <author>温向超</author> |
| | | // <description>消息队列操作辅助类</description> |
| | | //----------------------------------------------------------------------- |
| | | using System; |
| | | using System.Messaging; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | /// <summary> |
| | | /// 消息队列操作类 |
| | | /// </summary> |
| | | public class MSMQHelperCCC |
| | | { |
| | | /// <summary> |
| | | /// 消息队列名称 |
| | | /// </summary> |
| | | private string msmqName = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 消息队列路径 |
| | | /// </summary> |
| | | private string msmqPath = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 要操作的消息队列 |
| | | /// </summary> |
| | | private MessageQueue msgQueue = null; |
| | | |
| | | /// <summary> |
| | | /// 构造函数 |
| | | /// </summary> |
| | | /// <param name="Name">消息队列名称</param> |
| | | public MSMQHelperCCC(string Name) |
| | | { |
| | | try |
| | | { |
| | | this.msmqName = Name; |
| | | this.msmqPath = ".\\private$\\" + this.msmqName; |
| | | if (MessageQueue.Exists(this.msmqPath)) |
| | | { |
| | | // 如果当前消息队列已经存在 |
| | | this.msgQueue = new MessageQueue(this.msmqPath); |
| | | } |
| | | else |
| | | { |
| | | // 如果消息队列不存在,则创建消息对垒 |
| | | this.msgQueue = MessageQueue.Create(this.msmqPath); |
| | | } |
| | | this.msgQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) }); |
| | | } |
| | | catch(Exception ex) |
| | | { |
| | | throw new Exception(ex.Message); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 发送消息 |
| | | /// </summary> |
| | | /// <param name="MessageID">消息ID</param> |
| | | /// <param name="MsgString">要发送的字符串</param> |
| | | public void Send(string MessageID,string MsgString) |
| | | { |
| | | try |
| | | { |
| | | this.msgQueue.Send(MsgString, MessageID); |
| | | } |
| | | catch(Exception ex) |
| | | { |
| | | throw new Exception(ex.Message); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 接收消息队列中的消息 |
| | | /// </summary> |
| | | /// <param name="TimeOut">超时时间(ms)</param> |
| | | /// <param name="MessageID">消息ID</param> |
| | | /// <param name="MsgString">消息内容</param> |
| | | public void Receive(long TimeOut,out string MessageID,out string MsgString) |
| | | { |
| | | try |
| | | { |
| | | MessageID = string.Empty; |
| | | MsgString = string.Empty; |
| | | TimeSpan Time = new TimeSpan(TimeOut); |
| | | Message MsgReceived = null; |
| | | try |
| | | { |
| | | MsgReceived = this.msgQueue.Receive(Time); |
| | | } |
| | | catch |
| | | { |
| | | // do nothing |
| | | } |
| | | |
| | | if(MsgReceived!=null) |
| | | { |
| | | MessageID = MsgReceived.Label; |
| | | MsgString = MsgReceived.Body.ToString(); |
| | | } |
| | | } |
| | | catch(Exception ex) |
| | | { |
| | | throw new Exception("接收消息失败:" + ex.Message); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 清除消息队列中的消息 |
| | | /// </summary> |
| | | public void Clear() |
| | | { |
| | | try |
| | | { |
| | | this.msgQueue.Purge(); |
| | | } |
| | | catch(Exception ex) |
| | | { |
| | | throw new Exception("清除消息队列失败:" + ex.Message); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 关闭当前队列 |
| | | /// </summary> |
| | | public void Close() |
| | | { |
| | | this.msgQueue.Close(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 删除当前队列 |
| | | /// </summary> |
| | | public void Delete() |
| | | { |
| | | MessageQueue.Delete(this.msmqPath); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.IO; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public class PathHelper |
| | | { |
| | | /// <summary> |
| | | /// 程序目录 |
| | | /// </summary> |
| | | public static string AppFolder { get; private set; } = RootPath(); |
| | | |
| | | /// <summary> |
| | | /// 图标目录 |
| | | /// </summary> |
| | | public static string IconFolder { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// Config目录 |
| | | /// </summary> |
| | | public static string ConfigFolder { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// Sqlite数据库目录 |
| | | /// </summary> |
| | | public static string DatabaseFolder { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// Sqlite数据库Path |
| | | /// </summary> |
| | | public static string DatabasePath { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 多语言目录 |
| | | /// </summary> |
| | | public static string LangFolder { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 系统设置目录 |
| | | /// </summary> |
| | | public static string SettingFolder { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 参数目录 |
| | | /// </summary> |
| | | public static string ParameterFolder { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 参数数据源文件 |
| | | /// </summary> |
| | | public static string ParameterDataPath { get; private set; } |
| | | |
| | | |
| | | |
| | | |
| | | /// <summary> |
| | | /// 测试项数据目录 |
| | | /// </summary> |
| | | public static string TestItemDataFolder { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// 测试项数据源文件 |
| | | /// </summary> |
| | | public static string TestItemDataFile { get; private set; } |
| | | |
| | | /// <summary> |
| | | /// sqlite日志数据库文件夹 |
| | | /// </summary> |
| | | public static string LogViewerDir { get; private set; } |
| | | |
| | | |
| | | /// <summary> |
| | | /// 应用程序的根目录 |
| | | /// </summary> |
| | | /// <returns></returns> |
| | | private static string RootPath() |
| | | { |
| | | string DllPath = AppDomain.CurrentDomain.BaseDirectory; |
| | | string path = Path.GetFullPath("."); |
| | | IconFolder = InitFolder(path + "\\Icon"); |
| | | ConfigFolder = InitFolder(path + "\\Config"); |
| | | DatabaseFolder = InitFolder(ConfigFolder + "\\Database"); |
| | | DatabasePath = DatabaseFolder + "\\database.db"; |
| | | LangFolder = InitFolder(path + "\\Lang"); |
| | | SettingFolder = InitFolder(ConfigFolder + "\\SystemSetting"); |
| | | ParameterFolder = InitFolder(ConfigFolder + "\\Parameter"); |
| | | ParameterDataPath = ParameterFolder + "\\CommonParameter.data"; |
| | | |
| | | //ProductDataFolder = InitFolder(ConfigFolder + "\\Product"); |
| | | //ProductParameterFolder = InitFolder(ProductDataFolder + "\\Parameter"); |
| | | //ProductDataFile = ProductDataFolder + "\\Product.data"; |
| | | |
| | | // TestItemDataFolder = InitFolder(ProductDataFolder + "\\TestItem"); |
| | | TestItemDataFile = TestItemDataFolder + "\\TestItem.data"; |
| | | |
| | | LogViewerDir = InitFolder(ConfigFolder + "\\LogViewer"); |
| | | return path; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 系统配置信息存储文件夹路径 |
| | | /// </summary> |
| | | /// <returns>系统配置信息文件夹</returns> |
| | | public static string SystemSettingFolder() |
| | | { |
| | | string Path = RootPath() + "\\Config\\SystemSetting"; |
| | | return Path; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 系统配置文件 |
| | | /// </summary> |
| | | /// <returns>系统配置文件路径</returns> |
| | | public static string SystemSettingFile() |
| | | { |
| | | string Path = RootPath() + "\\Config\\SystemSetting\\system.setting"; |
| | | return Path; |
| | | } |
| | | |
| | | // <summary> |
| | | /// 菜单配置文件 |
| | | /// </summary> |
| | | /// <returns>菜单配置文件</returns> |
| | | public static string MenuFile() |
| | | { |
| | | string Path = RootPath() + "\\Config\\SystemSetting\\menu.setting"; |
| | | return Path; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 生产配置文件夹 |
| | | /// </summary> |
| | | /// <returns>生产配置文件夹</returns> |
| | | public static string ProductionFolder() |
| | | { |
| | | string Path = RootPath() + "\\Config\\ProductionSetting"; |
| | | return Path; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 测试项信息存储文件夹 |
| | | /// </summary> |
| | | /// <returns>测试项存储文件夹</returns> |
| | | public static string TestItemFolder() |
| | | { |
| | | string Path = ProductionFolder() + "\\TestItem"; |
| | | return Path; |
| | | } |
| | | |
| | | public static string BlockAlarmSettingPath() |
| | | { |
| | | string Path = RootPath() + "\\Config\\AlarmInfo\\BlockSetting.xml"; |
| | | return Path; |
| | | } |
| | | |
| | | public static string MarkAlarmSettingPath() |
| | | { |
| | | string Path = RootPath() + "\\Config\\AlarmInfo\\MarkSetting.xml"; |
| | | return Path; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 插件存储目录 |
| | | /// </summary> |
| | | /// <returns>插件存储目录</returns> |
| | | public static string PluginFolder() |
| | | { |
| | | string Path = RootPath() + "\\Plugin"; |
| | | return Path; |
| | | } |
| | | |
| | | public static string InitFolder(string path) |
| | | { |
| | | try |
| | | { |
| | | if (!Directory.Exists(path)) |
| | | { |
| | | Directory.CreateDirectory(path); |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | |
| | | } |
| | | return path; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Data; |
| | | using System.Data.SQLite; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public class Sqlite3Helper : IDisposable |
| | | { |
| | | public string DataBaseFile { get; private set; } = string.Empty; |
| | | |
| | | private string ConnectString = string.Empty; |
| | | |
| | | public const int MAX_ATTACHED = 30; |
| | | |
| | | private SQLiteConnection conn = null; |
| | | |
| | | public Sqlite3Helper(string DBFile) |
| | | { |
| | | DataBaseFile = DBFile; |
| | | ConnectString = "DataSource=" + DataBaseFile + ";Version=3;Pooling=False;Max Pool Size=100;"; |
| | | conn = new SQLiteConnection(ConnectString); |
| | | Connect(); |
| | | } |
| | | |
| | | public static bool CreateDatabase(string DataBaseFile) |
| | | { |
| | | try |
| | | { |
| | | SQLiteConnection.CreateFile(DataBaseFile); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception($"Create Database [${DataBaseFile}] Failed.", ex); |
| | | } |
| | | } |
| | | |
| | | public int Execute(string Sql) |
| | | { |
| | | try |
| | | { |
| | | using (var cmd = new SQLiteCommand(conn)) |
| | | { |
| | | cmd.CommandType = CommandType.Text; |
| | | cmd.CommandText = Sql; |
| | | return cmd.ExecuteNonQuery(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | string message = "ExecuteSql Error:" + ex.Message; |
| | | throw new Exception(message); |
| | | } |
| | | } |
| | | |
| | | public long InsertWithId(string Sql) |
| | | { |
| | | try |
| | | { |
| | | if (Sql.EndsWith(";")) |
| | | { |
| | | Sql += "SELECT last_insert_rowid();"; |
| | | } |
| | | else |
| | | { |
| | | Sql += ";SELECT last_insert_rowid();"; |
| | | } |
| | | using (var cmd = new SQLiteCommand(Sql, conn)) |
| | | { |
| | | return (long)cmd.ExecuteScalar(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | string message = "ExecuteSql Error:" + ex.Message; |
| | | throw new Exception(message); |
| | | } |
| | | } |
| | | |
| | | public object SelectOne(string Sql) |
| | | { |
| | | try |
| | | { |
| | | using (var cmd = new SQLiteCommand(Sql, conn)) |
| | | { |
| | | return cmd.ExecuteScalar(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | string message = "ExecuteSql Error:" + ex.Message; |
| | | throw new Exception(message); |
| | | } |
| | | } |
| | | |
| | | public int ExecuteBatch(IEnumerable<string> sqls) |
| | | { |
| | | SQLiteTransaction tr = null; |
| | | int res = 0; |
| | | try |
| | | { |
| | | Connect(); |
| | | tr = conn.BeginTransaction(); |
| | | using (var cmd = new SQLiteCommand(conn)) |
| | | { |
| | | cmd.CommandType = CommandType.Text; |
| | | foreach (string sql in sqls) |
| | | { |
| | | if (string.IsNullOrEmpty(sql) || string.IsNullOrWhiteSpace(sql)) |
| | | continue; |
| | | cmd.CommandText = sql; |
| | | res += cmd.ExecuteNonQuery(); |
| | | } |
| | | } |
| | | tr.Commit(); |
| | | return res; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | tr?.Rollback(); |
| | | throw ex; |
| | | } |
| | | } |
| | | |
| | | public DataTable GetDataTable(string Sql) |
| | | { |
| | | DataTable dataTable = new DataTable(); |
| | | try |
| | | { |
| | | Connect(); |
| | | using (var cmd = new SQLiteCommand(conn) |
| | | { |
| | | CommandType = CommandType.Text, |
| | | CommandText = Sql |
| | | }) |
| | | using (SQLiteDataAdapter sQLiteDataAdapter = new SQLiteDataAdapter() |
| | | { |
| | | SelectCommand = cmd |
| | | }) |
| | | { |
| | | sQLiteDataAdapter.Fill(dataTable); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | string message = "ExecuteSql Error:" + ex.Message; |
| | | throw new Exception(message); |
| | | } |
| | | return dataTable; |
| | | } |
| | | |
| | | public string[,] GetDataTableToArray(string Sql) |
| | | { |
| | | try |
| | | { |
| | | DataTable dataTable = GetDataTable(Sql); |
| | | string[,] array = new string[dataTable.Rows.Count, dataTable.Columns.Count]; |
| | | int num = 0; |
| | | int num2 = 0; |
| | | foreach (DataRow row in dataTable.Rows) |
| | | { |
| | | num2 = 0; |
| | | object[] itemArray = row.ItemArray; |
| | | foreach (object obj in itemArray) |
| | | { |
| | | array[num, num2] = obj.ToString(); |
| | | num2++; |
| | | } |
| | | num++; |
| | | } |
| | | return array; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception(ex.Message); |
| | | } |
| | | } |
| | | |
| | | public void Close() |
| | | { |
| | | try |
| | | { |
| | | if (conn.State == ConnectionState.Open) |
| | | { |
| | | conn.Close(); |
| | | } |
| | | if (conn != null) |
| | | { |
| | | conn.Dispose(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception(ex.Message); |
| | | } |
| | | } |
| | | |
| | | public bool HasData(string sql) |
| | | { |
| | | DataTable dataTable = GetDataTable(sql); |
| | | return dataTable.Rows.Count > 0; |
| | | } |
| | | |
| | | public string[] GetColumnNameFromTable(string TableName) |
| | | { |
| | | try |
| | | { |
| | | string sql = $"PRAGMA table_info('{TableName}')"; |
| | | DataTable dataTable = GetDataTable(sql); |
| | | string[] array = new string[dataTable.Rows.Count]; |
| | | int num = 0; |
| | | foreach (DataRow row in dataTable.Rows) |
| | | { |
| | | array[num] = row.ItemArray[1].ToString(); |
| | | num++; |
| | | } |
| | | return array; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception("获取表字段名称失败:" + ex.Message); |
| | | } |
| | | } |
| | | |
| | | public string[] GetAllTableInFile() |
| | | { |
| | | try |
| | | { |
| | | string sql = "select name from sqlite_master where type = 'table' order by name"; |
| | | DataTable dataTable = GetDataTable(sql); |
| | | string[] array = new string[dataTable.Rows.Count]; |
| | | int num = 0; |
| | | foreach (DataRow row in dataTable.Rows) |
| | | { |
| | | array[num] = row.ItemArray[0].ToString(); |
| | | num++; |
| | | } |
| | | |
| | | return array; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception("获取表名失败:" + ex.Message); |
| | | } |
| | | } |
| | | |
| | | private void Connect() |
| | | { |
| | | if (conn.State == ConnectionState.Closed) |
| | | { |
| | | conn.ConnectionString = ConnectString; |
| | | conn.Open(); |
| | | } |
| | | } |
| | | |
| | | private void DisConnect() |
| | | { |
| | | if (conn.State == ConnectionState.Open) |
| | | { |
| | | conn.Close(); |
| | | } |
| | | } |
| | | |
| | | public void Dispose() |
| | | { |
| | | this.Close(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Data; |
| | | using System.Data.SQLite; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public class SqliteHelper:IDisposable |
| | | { |
| | | public string DataBaseFile { get; private set; } = string.Empty; |
| | | |
| | | private string ConnectString = string.Empty; |
| | | |
| | | private SQLiteConnection conn = null; |
| | | |
| | | private SQLiteCommand cmd = null; |
| | | |
| | | public SqliteHelper(string DBFile) |
| | | { |
| | | DataBaseFile = DBFile; |
| | | ConnectString = "DataSource=" + DataBaseFile + ";Version=3;Pooling=False;Max Pool Size=100;"; |
| | | conn = new SQLiteConnection(ConnectString); |
| | | cmd = new SQLiteCommand(conn); |
| | | } |
| | | |
| | | public static bool CreateDatabase(string DataBaseFile) |
| | | { |
| | | try |
| | | { |
| | | SQLiteConnection.CreateFile(DataBaseFile); |
| | | return true; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception($"Create Database [${DataBaseFile}] Failed.", ex); |
| | | } |
| | | } |
| | | |
| | | public int ExecuteSql(string Sql) |
| | | { |
| | | try |
| | | { |
| | | Connect(); |
| | | cmd.CommandType = CommandType.Text; |
| | | cmd.CommandText = Sql; |
| | | return cmd.ExecuteNonQuery(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | string message = "ExecuteSql Error:" + ex.Message; |
| | | throw new Exception(message); |
| | | } |
| | | finally |
| | | { |
| | | DisConnect(); |
| | | } |
| | | } |
| | | |
| | | public void ExecuteBatchSql(IEnumerable<string> sqls) |
| | | { |
| | | SQLiteTransaction tr = null; |
| | | try |
| | | { |
| | | Connect(); |
| | | tr = conn.BeginTransaction(); |
| | | cmd.CommandType = CommandType.Text; |
| | | foreach (string sql in sqls) |
| | | { |
| | | if (string.IsNullOrEmpty(sql) || string.IsNullOrWhiteSpace(sql)) continue; |
| | | cmd.CommandText = sql; |
| | | cmd.ExecuteNonQuery(); |
| | | } |
| | | tr.Commit(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | tr?.Rollback(); |
| | | throw ex; |
| | | } |
| | | finally |
| | | { |
| | | DisConnect(); |
| | | } |
| | | } |
| | | |
| | | public DataTable GetDataTable(string Sql) |
| | | { |
| | | DataTable dataTable = new DataTable(); |
| | | SQLiteDataAdapter sQLiteDataAdapter = new SQLiteDataAdapter(); |
| | | try |
| | | { |
| | | Connect(); |
| | | cmd.CommandType = CommandType.Text; |
| | | cmd.CommandText = Sql; |
| | | sQLiteDataAdapter.SelectCommand = cmd; |
| | | sQLiteDataAdapter.Fill(dataTable); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | string message = "ExecuteSql Error:" + ex.Message; |
| | | throw new Exception(message); |
| | | } |
| | | finally |
| | | { |
| | | DisConnect(); |
| | | } |
| | | |
| | | return dataTable; |
| | | } |
| | | |
| | | public string[,] GetDataTableToArray(string Sql) |
| | | { |
| | | try |
| | | { |
| | | DataTable dataTable = GetDataTable(Sql); |
| | | string[,] array = new string[dataTable.Rows.Count, dataTable.Columns.Count]; |
| | | int num = 0; |
| | | int num2 = 0; |
| | | foreach (DataRow row in dataTable.Rows) |
| | | { |
| | | num2 = 0; |
| | | object[] itemArray = row.ItemArray; |
| | | foreach (object obj in itemArray) |
| | | { |
| | | array[num, num2] = obj.ToString(); |
| | | num2++; |
| | | } |
| | | |
| | | num++; |
| | | } |
| | | |
| | | return array; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception(ex.Message); |
| | | } |
| | | } |
| | | |
| | | public void Close() |
| | | { |
| | | try |
| | | { |
| | | if (conn.State == ConnectionState.Open) |
| | | { |
| | | conn.Close(); |
| | | } |
| | | |
| | | if (cmd != null) |
| | | { |
| | | cmd.Dispose(); |
| | | } |
| | | |
| | | if (conn != null) |
| | | { |
| | | conn.Dispose(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception(ex.Message); |
| | | } |
| | | } |
| | | |
| | | public bool HasData(string sql) |
| | | { |
| | | DataTable dataTable = GetDataTable(sql); |
| | | return dataTable.Rows.Count > 0; |
| | | } |
| | | |
| | | public string[] GetColumnNameFromTable(string TableName) |
| | | { |
| | | try |
| | | { |
| | | string sql = $"PRAGMA table_info('{TableName}')"; |
| | | DataTable dataTable = GetDataTable(sql); |
| | | string[] array = new string[dataTable.Rows.Count]; |
| | | int num = 0; |
| | | foreach (DataRow row in dataTable.Rows) |
| | | { |
| | | array[num] = row.ItemArray[1].ToString(); |
| | | num++; |
| | | } |
| | | |
| | | return array; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception("获取表字段名称失败:" + ex.Message); |
| | | } |
| | | } |
| | | |
| | | public string[] GetAllTableInFile() |
| | | { |
| | | try |
| | | { |
| | | string sql = "select name from sqlite_master where type = 'table' order by name"; |
| | | DataTable dataTable = GetDataTable(sql); |
| | | string[] array = new string[dataTable.Rows.Count]; |
| | | int num = 0; |
| | | foreach (DataRow row in dataTable.Rows) |
| | | { |
| | | array[num] = row.ItemArray[0].ToString(); |
| | | num++; |
| | | } |
| | | |
| | | return array; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | throw new Exception("获取表名失败:" + ex.Message); |
| | | } |
| | | } |
| | | |
| | | private void Connect() |
| | | { |
| | | if (conn.State == ConnectionState.Closed) |
| | | { |
| | | conn.ConnectionString = ConnectString; |
| | | cmd.Connection = conn; |
| | | conn.Open(); |
| | | } |
| | | } |
| | | |
| | | private void DisConnect() |
| | | { |
| | | if (conn.State == ConnectionState.Open) |
| | | { |
| | | conn.Close(); |
| | | } |
| | | } |
| | | |
| | | public void Dispose() |
| | | { |
| | | this.Close(); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Collections.Generic; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public class UtilContainer |
| | | { |
| | | private static Dictionary<string, object> Map = new Dictionary<string, object>(); |
| | | |
| | | public static bool RegisterUtil(string name, object value) |
| | | { |
| | | if (Map.ContainsKey(name)) return false; |
| | | Map[name] = value; |
| | | return true; |
| | | } |
| | | |
| | | public T GetUtil<T>(string name) |
| | | { |
| | | if (!Map.ContainsKey(name)) return default(T); |
| | | return (T)Map[name]; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.IO; |
| | | using System.Reflection; |
| | | using System.Xml; |
| | | using System.Xml.Serialization; |
| | | |
| | | namespace HMI.Core.Util |
| | | { |
| | | public static class XmlHelper |
| | | { |
| | | |
| | | static readonly XmlSerializerNamespaces DEFAULT_NS = new XmlSerializerNamespaces(); |
| | | |
| | | static XmlHelper() |
| | | { |
| | | DEFAULT_NS.Add("", ""); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// serialize object to xml file. |
| | | /// </summary> |
| | | /// <param name="path">the path to save the xml file</param> |
| | | /// <param name="obj">the object you want to serialize</param> |
| | | public static void SerializeToXml(string path, object obj) |
| | | { |
| | | XmlSerializer serializer = new XmlSerializer(obj.GetType()); |
| | | string content = string.Empty; |
| | | //serialize |
| | | using (StringWriter writer = new StringWriter()) |
| | | { |
| | | serializer.Serialize(writer, obj, DEFAULT_NS); |
| | | content = writer.ToString(); |
| | | } |
| | | //save to file |
| | | using (StreamWriter stream_writer = new StreamWriter(path)) |
| | | { |
| | | stream_writer.Write(content); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// xml 文件反序列化为对象 |
| | | /// </summary> |
| | | /// <typeparam name="T">序列化类型</typeparam> |
| | | /// <param name="path">the path of the xml file</param> |
| | | /// <returns>对象</returns> |
| | | public static T DeserializeFromXml<T>(string path) |
| | | { |
| | | XmlSerializer serializer = new XmlSerializer(typeof(T)); |
| | | using (StreamReader reader = new StreamReader(path)) |
| | | { |
| | | return (T)serializer.Deserialize(reader); |
| | | } |
| | | } |
| | | public static bool ReadFromXml<T>(T obj, string path) |
| | | { |
| | | XmlDocument xd = new XmlDocument(); |
| | | xd.Load(path); |
| | | |
| | | Type type = typeof(T); |
| | | XmlNode root = xd.SelectSingleNode(type.Name); |
| | | if (root == null) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | //获取 RefHero 中所有的实例私有字段 |
| | | FieldInfo[] fieldArray = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); |
| | | foreach (FieldInfo filed in fieldArray) |
| | | { |
| | | string fileName = filed.Name; |
| | | XmlNode node = root.SelectSingleNode(fileName); |
| | | if (node == null) |
| | | { |
| | | continue; |
| | | } |
| | | filed.SetValue(obj, node.InnerText); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | public static T DeepClone<T>(T data) |
| | | { |
| | | using (MemoryStream stream = new MemoryStream()) |
| | | { |
| | | XmlSerializer serializer = new XmlSerializer(typeof(T)); |
| | | serializer.Serialize(stream, data); |
| | | stream.Seek(0, SeekOrigin.Begin); |
| | | return (T)serializer.Deserialize(stream); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <packages> |
| | | <package id="log4net" version="3.0.4" targetFramework="net472" /> |
| | | <package id="Newtonsoft.Json" version="13.0.3" targetFramework="net472" /> |
| | | </packages> |
| New file |
| | |
| | | <hb:PluginControl x:Class="HMI.DataViewer.DataViewer" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" |
| | | xmlns:local="clr-namespace:HMI.DataViewer" |
| | | mc:Ignorable="d" |
| | | xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | Background="{DynamicResource WorkspaceBackground}" |
| | | Loaded="PluginControl_Loaded" |
| | | d:DesignHeight="450" d:DesignWidth="800"> |
| | | <UserControl.Resources> |
| | | <ResourceDictionary> |
| | | <ResourceDictionary.MergedDictionaries> |
| | | </ResourceDictionary.MergedDictionaries> |
| | | </ResourceDictionary> |
| | | </UserControl.Resources> |
| | | <Border Style="{StaticResource CardBoarder}"> |
| | | <Grid dp:GridHelper.Space="10"> |
| | | <Grid.RowDefinitions> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition/> |
| | | </Grid.RowDefinitions> |
| | | |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition Width="7*"/> |
| | | <ColumnDefinition Width="3*"/> |
| | | </Grid.ColumnDefinitions> |
| | | |
| | | <DockPanel Grid.ColumnSpan="2"> |
| | | <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}"> |
| | | <Button Style="{StaticResource AddBtn}" x:Name="addBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource EditBtn}" x:Name="editBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource RfhBtn}" x:Name="rfhBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource DelBtn}" x:Name="delBtn" Click="Button_Click"/> |
| | | </StackPanel> |
| | | <TextBlock Style="{StaticResource TitleBox}" Text="{DynamicResource Dv.Title}"/> |
| | | </DockPanel> |
| | | |
| | | <!--<Grid> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition/> |
| | | <ColumnDefinition/> |
| | | </Grid.ColumnDefinitions> |
| | | </Grid>--> |
| | | <StackPanel Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Left" Style="{StaticResource ButtonGroup}"> |
| | | |
| | | <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" |
| | | Text="{DynamicResource Dv.SelectItem}"/> |
| | | <ComboBox x:Name="programInput" |
| | | MinWidth="180" |
| | | SelectionChanged="programInput_SelectionChanged"/> |
| | | |
| | | <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" |
| | | Text="{DynamicResource App.StartTime}"/> |
| | | <pu:DateTimePicker Style="{StaticResource TimePicker}" |
| | | SelectedDateTime="{x:Null}" x:Name="startTimeInput"/> |
| | | <TextBlock Style="{StaticResource TitleBox}" FontSize="{StaticResource ContentFontSize2}" |
| | | Text="{DynamicResource App.EndTime}"/> |
| | | <pu:DateTimePicker Style="{StaticResource TimePicker}" |
| | | SelectedDateTime="{x:Null}" x:Name="endTimeInput"/> |
| | | |
| | | |
| | | <Button Style="{StaticResource QueryBtn}" |
| | | x:Name="srhBtn" Click="Button_Click"/> |
| | | |
| | | <Button Style="{StaticResource ResetBtn}" |
| | | x:Name="resetBtn" Click="Button_Click"/> |
| | | |
| | | <Button Style="{StaticResource ExportBtn}" |
| | | x:Name="exportBtn" Click="Button_Click"/> |
| | | |
| | | <!--<Menu x:Name="anyBtn" Style="{StaticResource CommonMenu}"> |
| | | <MenuItem Header="分析"> |
| | | <MenuItem Header="CPK" Click="MenuItem_Click"/> |
| | | <MenuItem Header="RandR" /> |
| | | </MenuItem> |
| | | </Menu>--> |
| | | |
| | | </StackPanel> |
| | | |
| | | <DockPanel Grid.Row="2" Grid.ColumnSpan="2" Margin="0,10,0,0"> |
| | | <pu:Drawer DockPanel.Dock="Left" x:Name="DrwMenu" MinWidth="35" MaxWidth="400" |
| | | Padding="0" |
| | | ShadowColor="{x:Null}" |
| | | AnimationDuration="0" BorderThickness="2,0,0,2" BorderBrush="{DynamicResource CommonBoarder}"> |
| | | <Grid> |
| | | <Grid.RowDefinitions> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition/> |
| | | </Grid.RowDefinitions> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition/> |
| | | <ColumnDefinition Width="Auto"/> |
| | | </Grid.ColumnDefinitions> |
| | | |
| | | <Border Grid.ColumnSpan="2" BorderThickness="2" BorderBrush="{DynamicResource CommonBoarder}" Background="{DynamicResource VerBar}"> |
| | | <DockPanel Height="35"> |
| | | <ToggleButton x:Name="drawerBtn" BorderThickness="0" |
| | | DockPanel.Dock="Right" |
| | | Background="Transparent" |
| | | Width="35" Height="35" HorizontalAlignment="Right" |
| | | RenderTransformOrigin="0.5,0.5" |
| | | Foreground="{DynamicResource PrimaryColorContract}" |
| | | FontFamily="{StaticResource iconfont}" |
| | | Content="" IsChecked="{Binding IsOpen, ElementName=DrwMenu, Mode=TwoWay}"> |
| | | <ToggleButton.Style> |
| | | <Style TargetType="ToggleButton" BasedOn="{StaticResource CommonToggle}"> |
| | | <Style.Triggers> |
| | | <DataTrigger Binding="{Binding IsOpen, ElementName=DrwMenu}" Value="False"> |
| | | <Setter Property="RenderTransform"> |
| | | <Setter.Value> |
| | | <RotateTransform Angle="-90"/> |
| | | </Setter.Value> |
| | | </Setter> |
| | | </DataTrigger> |
| | | <DataTrigger Binding="{Binding IsOpen, ElementName=DrwMenu}" Value="True"> |
| | | <Setter Property="Background" Value="Red"/> |
| | | <Setter Property="RenderTransform"> |
| | | <Setter.Value> |
| | | <RotateTransform Angle="90"/> |
| | | </Setter.Value> |
| | | </Setter> |
| | | </DataTrigger> |
| | | <Trigger Property="IsChecked" Value="True"> |
| | | <Setter Property="Background" Value="Red"/> |
| | | </Trigger> |
| | | </Style.Triggers> |
| | | </Style> |
| | | </ToggleButton.Style> |
| | | </ToggleButton> |
| | | |
| | | <TextBlock Text="{DynamicResource Dv.Advanced}" FontSize="16" |
| | | Foreground="{DynamicResource PrimaryColorContract}" |
| | | VerticalAlignment="Center" HorizontalAlignment="Center"/> |
| | | </DockPanel> |
| | | </Border> |
| | | |
| | | |
| | | <Grid Grid.Row="1"> |
| | | <Grid.RowDefinitions> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition/> |
| | | </Grid.RowDefinitions> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition Width="Auto"/> |
| | | <ColumnDefinition/> |
| | | </Grid.ColumnDefinitions> |
| | | |
| | | <!--<DockPanel Margin="10,10,10,0" Grid.ColumnSpan="2"> |
| | | <Label Content="{DynamicResource Dv.SelectItem}"/> |
| | | <ComboBox Grid.Column="1" x:Name="programInput" SelectionChanged="programInput_SelectionChanged"/> |
| | | </DockPanel>--> |
| | | |
| | | <DataGrid Grid.Row="1" Grid.ColumnSpan="2" Style="{StaticResource DefaultDataGrid}" |
| | | IsReadOnly="False" |
| | | SelectionMode="Single" |
| | | BorderThickness="0" |
| | | ItemsSource="{Binding Datas}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="#" IsReadOnly="True" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | |
| | | <DataGridTextColumn IsReadOnly="True" Width="80" Binding="{Binding Name}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Dv.Field}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTemplateColumn Width="60"> |
| | | <DataGridTemplateColumn.Header> |
| | | <TextBlock Text="{DynamicResource Dv.Condition}"/> |
| | | </DataGridTemplateColumn.Header> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Condition}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | <DataGridTemplateColumn.CellEditingTemplate> |
| | | <DataTemplate> |
| | | <ComboBox SelectedValue="{Binding Condition}" |
| | | SelectedValuePath="Content" > |
| | | <ComboBoxItem Content="="/> |
| | | <ComboBoxItem Content="<"/> |
| | | <ComboBoxItem Content=">"/> |
| | | <ComboBoxItem Content="<="/> |
| | | <ComboBoxItem Content=">="/> |
| | | </ComboBox> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellEditingTemplate> |
| | | </DataGridTemplateColumn> |
| | | |
| | | <DataGridTextColumn Width="*" Binding="{Binding Value}" > |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Dv.Value}"/> |
| | | </DataGridTextColumn.Header> |
| | | <DataGridTextColumn.HeaderTemplate> |
| | | <DataTemplate> |
| | | <StackPanel Orientation="Horizontal"> |
| | | <TextBlock Text="*" Foreground="Red" /> |
| | | <ContentControl Content="{Binding }"/> |
| | | </StackPanel> |
| | | </DataTemplate> |
| | | </DataGridTextColumn.HeaderTemplate> |
| | | </DataGridTextColumn> |
| | | </DataGrid.Columns> |
| | | |
| | | </DataGrid> |
| | | </Grid> |
| | | |
| | | <Label Grid.Row="1" Grid.Column="1" Width="100" Height="34" |
| | | VerticalContentAlignment="Center" |
| | | HorizontalContentAlignment="Center" |
| | | Content="{DynamicResource Dv.Advanced}" |
| | | FontSize="16"> |
| | | <Label.LayoutTransform> |
| | | <RotateTransform CenterX="25" CenterY="25" Angle="270"/> |
| | | </Label.LayoutTransform> |
| | | |
| | | <Label.Style> |
| | | <Style TargetType="Label"> |
| | | <Style.Triggers> |
| | | <DataTrigger Binding="{Binding IsOpen, ElementName=DrwMenu,Mode=OneWay}" Value="True"> |
| | | <Setter Property="Visibility" Value="Collapsed"/> |
| | | </DataTrigger> |
| | | <DataTrigger Binding="{Binding IsOpen, ElementName=DrwMenu}" Value="False"> |
| | | <Setter Property="Visibility" Value="Visible"/> |
| | | </DataTrigger> |
| | | </Style.Triggers> |
| | | </Style> |
| | | </Label.Style> |
| | | </Label> |
| | | </Grid> |
| | | </pu:Drawer> |
| | | |
| | | <!-- SelectionUnit="CellOrRowHeader" |
| | | SelectionMode="Extended" --> |
| | | <DataGrid BorderThickness="2" BorderBrush="{DynamicResource CommonBoarder}" x:Name="dataTable" |
| | | Style="{StaticResource DefaultDataGrid}" |
| | | EnableRowVirtualization="True" |
| | | PreviewMouseLeftButtonDown="dataTable_PreviewMouseLeftButtonDown"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="#" IsReadOnly="True" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | </DockPanel> |
| | | </Grid> |
| | | </Border> |
| | | </hb:PluginControl> |
| New file |
| | |
| | | using HMI.Base; |
| | | using HMI.Core.Util; |
| | | using HMI.DataViewer.Entity; |
| | | using HMI.UI.Converter; |
| | | using HMI.UI.Util; |
| | | using NPOI.SS.Formula.Functions; |
| | | using NPOI.SS.UserModel; |
| | | using NPOI.XSSF.UserModel; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.Data; |
| | | using System.Globalization; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | using System.Windows.Data; |
| | | using System.Windows.Forms; |
| | | |
| | | namespace HMI.DataViewer |
| | | { |
| | | |
| | | |
| | | /// <summary> |
| | | /// UserControl1.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class DataViewer : PluginControl |
| | | { |
| | | public DataViewer() |
| | | { |
| | | DataContext = this; |
| | | InitializeComponent(); |
| | | DefaultDate(); |
| | | } |
| | | |
| | | private string SourceFilePath; |
| | | |
| | | private ObservableCollection<DataBase_Setting> settings = new ObservableCollection<DataBase_Setting>(); |
| | | public ObservableCollection<DataBase_Setting> Settings |
| | | { |
| | | get => settings; |
| | | set { Set(ref settings, value); } |
| | | } |
| | | |
| | | private ObservableCollection<QueryData> datas = new ObservableCollection<QueryData>(); |
| | | public ObservableCollection<QueryData> Datas |
| | | { |
| | | get => datas; |
| | | set { Set(ref datas, value); } |
| | | } |
| | | |
| | | private Dictionary<string, DataBase_Setting> Map = new Dictionary<string, DataBase_Setting>(); |
| | | |
| | | private void Button_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | System.Windows.Controls.Button btn = sender as System.Windows.Controls.Button; |
| | | switch (btn.Name) |
| | | { |
| | | case "addBtn": |
| | | OpenSettingModal(-1, new DataBase_Setting()); |
| | | break; |
| | | case "editBtn": |
| | | EditSettingModal(); |
| | | break; |
| | | case "rfhBtn": |
| | | LoadData(); |
| | | break; |
| | | case "delBtn": |
| | | DeleteDatasource(); |
| | | break; |
| | | case "srhBtn": |
| | | SearchData(); |
| | | break; |
| | | case "resetBtn": |
| | | ResetDataTable(); |
| | | break; |
| | | case "exportBtn": |
| | | ExportData(); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | private void DeleteDatasource() |
| | | { |
| | | try |
| | | { |
| | | var key = programInput.Text; |
| | | if (string.IsNullOrEmpty(key)) |
| | | { |
| | | ToastHelper.Warning(Window.GetWindow(this), this.lang("Dv.SelectDatasourceFirst")); |
| | | return; |
| | | } |
| | | if (!Map.ContainsKey(key)) |
| | | { |
| | | ToastHelper.Error(Window.GetWindow(this), this.lang("App.ParameterException")); |
| | | return; |
| | | } |
| | | DataBase_Setting setting = Map[key]; |
| | | if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), this.lang("HMI.MakeSureDeleteSingle", setting.Program)) |
| | | == MessageBoxResult.OK) |
| | | { |
| | | var temp = XmlHelper.DeepClone(Settings); |
| | | temp.RemoveAt(Settings.IndexOf(setting)); |
| | | XmlHelper.SerializeToXml(SourceFilePath, temp); |
| | | ToastHelper.Success(Window.GetWindow(this), this.lang("App.DeleteSuccess")); |
| | | LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Delete Datasource Failed.", ex); |
| | | ToastHelper.Success(Window.GetWindow(this), $"{this.lang("App.DeleteFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void EditSettingModal() |
| | | { |
| | | var key = programInput.Text; |
| | | if (string.IsNullOrEmpty(key)) |
| | | { |
| | | ToastHelper.Warning(Window.GetWindow(this), this.lang("Dv.SelectDatasourceFirst")); |
| | | return; |
| | | } |
| | | if (!Map.ContainsKey(key)) |
| | | { |
| | | ToastHelper.Error(Window.GetWindow(this), this.lang("App.ParameterException")); |
| | | return; |
| | | } |
| | | DataBase_Setting setting = Map[key]; |
| | | OpenSettingModal(Settings.IndexOf(setting), setting); |
| | | } |
| | | |
| | | private void OpenSettingModal(int index, DataBase_Setting data) |
| | | { |
| | | DbSettingModal modal = new DbSettingModal(log) |
| | | { |
| | | Title = this.lang("Dv.AddDatasource"), |
| | | Owner = Window.GetWindow(this) |
| | | }; |
| | | if (modal.Open(index, data, Settings, SourceFilePath) == true) |
| | | { |
| | | LoadData(); |
| | | } |
| | | } |
| | | |
| | | private void DefaultDate() |
| | | { |
| | | var now = DateTime.Now; |
| | | startTimeInput.SelectedDateTime = DateTime.Parse(now.ToString("yyyy-MM-dd")); |
| | | endTimeInput.SelectedDateTime = DateTime.Parse(now.AddDays(1).ToString("yyyy-MM-dd")); |
| | | } |
| | | |
| | | private void ResetDataTable() |
| | | { |
| | | DefaultDate(); |
| | | dataTable.ItemsSource = null; |
| | | foreach (var data in Datas) |
| | | { |
| | | data.Value = null; |
| | | } |
| | | } |
| | | |
| | | public override void SetBaseFolder(string folder) |
| | | { |
| | | base.SetBaseFolder(folder); |
| | | SourceFilePath = Path.Combine(BaseFolder, "SourceData.setting"); |
| | | } |
| | | |
| | | private void PluginControl_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | LoadData(); |
| | | Loaded -= PluginControl_Loaded; |
| | | } |
| | | |
| | | private void LoadData() |
| | | { |
| | | ObservableCollection<DataBase_Setting> settings = new ObservableCollection<DataBase_Setting>(); |
| | | if (File.Exists(SourceFilePath)) |
| | | { |
| | | settings = XmlHelper.DeserializeFromXml<ObservableCollection<DataBase_Setting>>(SourceFilePath); |
| | | } |
| | | List<string> names = new List<string>(); |
| | | |
| | | Map.Clear(); |
| | | foreach (var data in settings) |
| | | { |
| | | Map[data.Program] = data; |
| | | names.Add(data.Program); |
| | | } |
| | | programInput.ItemsSource = names; |
| | | //ResetInput(); |
| | | UpdateAdvanced(); |
| | | Settings = settings; |
| | | } |
| | | |
| | | private void ResetInput() |
| | | { |
| | | programInput.Text = string.Empty; |
| | | Datas.Clear(); |
| | | |
| | | var temp = dataTable.Columns[0]; |
| | | HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null); |
| | | dataTable.Columns.Clear(); |
| | | dataTable.Columns.Add(temp); |
| | | currentColumns?.Clear(); |
| | | } |
| | | |
| | | private void UpdateAdvanced() |
| | | { |
| | | var key = programInput.Text; |
| | | if (!Map.ContainsKey(key)) |
| | | { |
| | | ResetInput(); |
| | | return; |
| | | } |
| | | DataBase_Setting setting = Map[key]; |
| | | Datas = new ObservableCollection<QueryData>( |
| | | setting?.Condition?.Select(d => new QueryData { Key = d.Key, Name = d.Desc, Condition = "=" }).ToList()); |
| | | |
| | | var temp = dataTable.Columns[0]; |
| | | HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null); |
| | | dataTable.Columns.Clear(); |
| | | dataTable.Columns.Add(temp); |
| | | |
| | | List<DataGridTextColumn> columns = new List<DataGridTextColumn>(); |
| | | foreach (var data in Datas) |
| | | { |
| | | columns.Add(AddDataGridTextColumn(data.Name, data.Key)); |
| | | } |
| | | currentColumns = columns; |
| | | } |
| | | |
| | | private void programInput_SelectionChanged(object sender, SelectionChangedEventArgs e) |
| | | { |
| | | if (e.AddedItems == null || e.AddedItems.Count <= 0) return; |
| | | var key = e.AddedItems[0].ToString(); |
| | | if (!Map.ContainsKey(key)) |
| | | { |
| | | ToastHelper.Error(Window.GetWindow(this), this.lang("App.ParameterException")); |
| | | programInput.Text = string.Empty; |
| | | return; |
| | | } |
| | | DataBase_Setting setting = Map[key]; |
| | | Datas = new ObservableCollection<QueryData>( |
| | | setting?.Condition?.Select(d => new QueryData { Key = d.Key, Name = d.Desc, Condition = "=" }).ToList()); |
| | | |
| | | var temp = dataTable.Columns[0]; |
| | | HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null); |
| | | dataTable.Columns.Clear(); |
| | | dataTable.Columns.Add(temp); |
| | | |
| | | List<DataGridTextColumn> columns = new List<DataGridTextColumn>(); |
| | | foreach (var data in Datas) |
| | | { |
| | | columns.Add(AddDataGridTextColumn(data.Name, data.Key)); |
| | | } |
| | | currentColumns = columns; |
| | | } |
| | | |
| | | private List<DataGridTextColumn> currentColumns; |
| | | |
| | | private DataGridTextColumn AddDataGridTextColumn(string header, string bindingPath) |
| | | { |
| | | DataGridTextColumn textColumn = new DataGridTextColumn |
| | | { |
| | | Header = header, |
| | | Binding = new System.Windows.Data.Binding(bindingPath) |
| | | { |
| | | Converter = DateStrConverter.Instance |
| | | }, |
| | | Width = new DataGridLength(1, DataGridLengthUnitType.Star) |
| | | }; |
| | | dataTable.Columns.Add(textColumn); |
| | | return textColumn; |
| | | } |
| | | |
| | | private void dataTable_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) |
| | | { |
| | | //// 获取点击的元素 |
| | | //DependencyObject dep = (DependencyObject)e.OriginalSource; |
| | | |
| | | //// 遍历找到 DataGridColumnHeader |
| | | //while (dep != null && !(dep is DataGridColumnHeader) && !(dep is DataGridRow)) |
| | | //{ |
| | | // dep = VisualTreeHelper.GetParent(dep); |
| | | //} |
| | | //HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null); |
| | | |
| | | //if (dep is DataGridColumnHeader columnHeader) |
| | | //{ |
| | | // // 获取列索引 |
| | | // int columnIndex = columnHeader.Column.DisplayIndex; |
| | | |
| | | // // 清除已有选择 |
| | | // dataTable.SelectedCells.Clear(); |
| | | |
| | | // // 遍历所有行,选中该列的所有单元格 |
| | | // foreach (var item in dataTable.Items) |
| | | // { |
| | | // DataGridCellInfo cellInfo = new DataGridCellInfo(item, columnHeader.Column); |
| | | // dataTable.SelectedCells.Add(cellInfo); |
| | | // } |
| | | // HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, columnHeader.Column); |
| | | |
| | | // e.Handled = true; // 防止默认行为 |
| | | //} |
| | | //else if (dep is DataGridRow row) |
| | | //{ |
| | | // // 清除已有选择 |
| | | // dataTable.SelectedCells.Clear(); |
| | | // dataTable.SelectedItem = row.Item; |
| | | // e.Handled = true; |
| | | //} |
| | | } |
| | | |
| | | private DataTable DoSearchData(DataBase_Setting setting, List<QueryData> datas) |
| | | { |
| | | string conditon = "WHERE 1=1 "; |
| | | string fields = string.Join(",", Datas?.Select(d => $"[{d.Key}]").ToList()); |
| | | if (datas != null && datas.Count > 0) |
| | | { |
| | | conditon = "WHERE " + string.Join(" AND ", datas.Select(d => $"[{d.Key}] {d.Condition} '{d.Value}'").ToList()); |
| | | } |
| | | |
| | | string timeCol = setting.TimeCol; |
| | | if (string.IsNullOrEmpty(timeCol)) |
| | | { |
| | | timeCol = "time"; |
| | | } |
| | | |
| | | if (!string.IsNullOrEmpty(startTimeInput.Text)) |
| | | { |
| | | conditon += $" AND {timeCol} >= '{startTimeInput.Text}'"; |
| | | } |
| | | if (!string.IsNullOrEmpty(endTimeInput.Text)) |
| | | { |
| | | conditon += $" AND {timeCol} < '{endTimeInput.Text}'"; |
| | | } |
| | | |
| | | string sql = $"SELECT {fields} FROM {setting.TableName} {conditon}"; |
| | | |
| | | DataTable dt = new DataTable(); |
| | | |
| | | if (string.IsNullOrEmpty(setting.Pattern)) |
| | | { |
| | | try |
| | | { |
| | | using (var helper = new Sqlite3Helper(setting.DBFilePath)) |
| | | { |
| | | dt = helper.GetDataTable(sql); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"Select Data Failed. SQL: {sql}", ex); |
| | | throw ex; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | // 通配符 |
| | | try |
| | | { |
| | | string dir = Path.GetDirectoryName(setting.DBFilePath); |
| | | string startName = $"Data_{startTimeInput.SelectedDateTime?.ToString("yyyyMMdd")}"; |
| | | string endName = $"Data_{endTimeInput.SelectedDateTime?.ToString("yyyyMMdd")}"; |
| | | var oldFiles = Directory.GetFiles(dir, "Data_*.db"); |
| | | oldFiles = oldFiles.Where(file => |
| | | { |
| | | var fileName = Path.GetFileNameWithoutExtension(file); |
| | | bool flag = true; |
| | | flag &= (string.Compare(fileName, startName) == 1 || fileName == startName); |
| | | flag &= (string.Compare(endName, fileName) == 1 || fileName == endName); |
| | | return flag; |
| | | }).ToArray(); |
| | | if (oldFiles == null || oldFiles.Length <= 0) |
| | | { |
| | | return dt; |
| | | } |
| | | |
| | | foreach (string file in oldFiles) |
| | | { |
| | | try |
| | | { |
| | | using (var helper = new SqliteHelper(file)) |
| | | { |
| | | var temp = helper.GetDataTable(sql); |
| | | dt.Merge(temp); |
| | | } |
| | | } |
| | | catch |
| | | { |
| | | |
| | | } |
| | | } |
| | | return dt; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error($"Select Data Failed. SQL: {sql}", ex); |
| | | throw ex; |
| | | } |
| | | } |
| | | //HMI.UI.DpUtil.DataGridHelper.SetSelectedColumn(dataTable, null); |
| | | //dt. |
| | | return dt; |
| | | } |
| | | |
| | | private void SearchData() |
| | | { |
| | | string program = programInput.Text; |
| | | if (!Map.ContainsKey(program)) |
| | | { |
| | | ToastHelper.Error(Window.GetWindow(this), this.lang("App.ParameterException")); |
| | | return; |
| | | } |
| | | |
| | | var datas = Datas?.Where(d => !string.IsNullOrEmpty(d.Value)).ToList(); |
| | | |
| | | if ((datas == null || datas.Count <= 0) && |
| | | (string.IsNullOrEmpty(startTimeInput.Text) || string.IsNullOrEmpty(endTimeInput.Text))) |
| | | { |
| | | ToastHelper.Warning(Window.GetWindow(this), this.lang("Dv.QueryCantBeEmpty")); |
| | | return; |
| | | } |
| | | |
| | | DataBase_Setting setting = Map[program]; |
| | | |
| | | try |
| | | { |
| | | dataTable.ItemsSource = DoSearchData(setting, datas).DefaultView; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | ToastHelper.Error(Window.GetWindow(this), $"{this.lang("App.QueryFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void ExportData() |
| | | { |
| | | int maxRow = 1048575; |
| | | try |
| | | { |
| | | string program = programInput.Text; |
| | | if (string.IsNullOrEmpty(program)) |
| | | { |
| | | ToastHelper.Warning(Window.GetWindow(this), this.lang("Dv.SelectDatasourceFirst")); |
| | | return; |
| | | } |
| | | |
| | | if (!Map.ContainsKey(program)) |
| | | { |
| | | ToastHelper.Error(Window.GetWindow(this), this.lang("App.ParameterException")); |
| | | return; |
| | | } |
| | | |
| | | var datas = Datas?.Where(d => !string.IsNullOrEmpty(d.Value)).ToList(); |
| | | if ((datas == null || datas.Count <= 0) && |
| | | (string.IsNullOrEmpty(startTimeInput.Text) || string.IsNullOrEmpty(endTimeInput.Text))) |
| | | { |
| | | ToastHelper.Warning(Window.GetWindow(this), this.lang("Dv.QueryCantBeEmpty")); |
| | | return; |
| | | } |
| | | |
| | | DataBase_Setting setting = Map[program]; |
| | | DataTable dt; |
| | | try |
| | | { |
| | | dt = DoSearchData(setting, datas); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | ToastHelper.Error(Window.GetWindow(this), $"{this.lang("App.QueryFailed")}: {ex.Message}"); |
| | | return; |
| | | } |
| | | |
| | | if (currentColumns == null) return; |
| | | var dataView = dt?.DefaultView; |
| | | if (dataView == null) return; |
| | | |
| | | string fname = string.Empty; |
| | | SaveFileDialog sfd = new SaveFileDialog(); |
| | | |
| | | sfd.Filter = ""; |
| | | sfd.DefaultExt = "xlsx"; |
| | | sfd.FileName = setting.Program; |
| | | if (sfd.ShowDialog() == DialogResult.OK) |
| | | { |
| | | fname = sfd.FileName; |
| | | } |
| | | |
| | | /*导出当前dataGridView中的所有数据到xls文件 |
| | | 1.引入库文件,新建lib文件夹,复制相关文件 |
| | | 2.在项目中添加对这几个dll的引用 |
| | | 3.在内存中建立 excel表文件 */ |
| | | using (FileStream fs = new FileStream(fname, FileMode.Create)) |
| | | { |
| | | |
| | | XSSFWorkbook workbook = new XSSFWorkbook(); |
| | | |
| | | int row = 1; |
| | | for (int i = 0; i < dataView.Count; i++) |
| | | { |
| | | int sheetIndex = sheetIndex = i / maxRow; |
| | | string sheetName = $"sheet{sheetIndex + 1}"; |
| | | var sheet = (XSSFSheet)workbook.GetSheet(sheetName); |
| | | if (sheet == null) |
| | | { |
| | | sheet = (XSSFSheet)workbook.CreateSheet(sheetName); |
| | | IRow row1 = sheet.CreateRow(0); |
| | | for (int j = 0; j < currentColumns.Count; j++) |
| | | { |
| | | row1.CreateCell(j).SetCellValue(currentColumns[j].Header.ToString()); |
| | | } |
| | | row = 1; |
| | | } |
| | | |
| | | |
| | | IRow rowTemp = sheet.CreateRow(row++); |
| | | for (int j = 0; j < currentColumns.Count; j++) |
| | | { |
| | | string name = ((System.Windows.Data.Binding)currentColumns[j].Binding).Path.Path; |
| | | var value = dataView[i][name].ToString(); |
| | | rowTemp.CreateCell(j).SetCellValue(value); |
| | | } |
| | | } |
| | | |
| | | workbook.Write(fs); |
| | | } |
| | | ToastHelper.Success(Window.GetWindow(this), this.lang("App.ExportSuccess")); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Export Data Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{this.lang("App.ExportFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void MenuItem_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | DataGridColumn column = HMI.UI.DpUtil.DataGridHelper.GetSelectedColumn(dataTable); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <pu:WindowX x:Class="HMI.DataViewer.DbSettingModal" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:local="clr-namespace:HMI.DataViewer" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI" |
| | | xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | mc:Ignorable="d" |
| | | Title="Form" |
| | | SizeToContent="Height" |
| | | Width="800" |
| | | WindowStartupLocation="CenterOwner" |
| | | BorderBrush="#3E3E45" |
| | | BorderThickness="1" |
| | | Foreground="Black" |
| | | Style="{StaticResource ModalWindow}"> |
| | | <Grid Margin="10"> |
| | | <StackPanel x:Name="formPanel"> |
| | | <pu:FormGroup Header="{DynamicResource Dv.Datasource.Name}" |
| | | dp:FormGroupHelper.IsRequired="True" |
| | | dp:FormGroupHelper.ValidateMethod="ValidateName" |
| | | Style="{StaticResource InputFormGroupStyle}" |
| | | GroupName="MyFormGroup"> |
| | | <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="programInput"/> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup Header="{DynamicResource Dv.Datasource.File}" |
| | | dp:FormGroupHelper.IsRequired="True" |
| | | Style="{StaticResource InputFormGroupStyle}" |
| | | GroupName="MyFormGroup"> |
| | | <Grid> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition/> |
| | | <ColumnDefinition Width="Auto"/> |
| | | </Grid.ColumnDefinitions> |
| | | <TextBox Style="{StaticResource EditorTextBoxStyle}" IsReadOnly="False" x:Name="dbFilePathInput"/> |
| | | <Button Style="{StaticResource SearchButton}" x:Name="searchBtn" Click="Button_Click" Grid.Column="1"/> |
| | | </Grid> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup Header="{DynamicResource Dv.Datasource.Table}" |
| | | dp:FormGroupHelper.IsRequired="True" |
| | | Style="{StaticResource InputFormGroupStyle}" |
| | | GroupName="MyFormGroup"> |
| | | <ComboBox Style="{StaticResource FormComboxStyle}" SelectedValuePath="Content" |
| | | x:Name="tableNameInput" SelectionChanged="ComboxSelectionChanged"/> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup Header="通配符" Style="{StaticResource InputFormGroupStyle}" GroupName="MyFormGroup"> |
| | | <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="TbPattern"/> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup Header="{DynamicResource Dv.Datasource.Fields}" Orientation="Vertical" GroupName="MyFormGroup"> |
| | | <Grid dp:GridHelper.Space="10"> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition/> |
| | | <ColumnDefinition Width="Auto"/> |
| | | <ColumnDefinition/> |
| | | </Grid.ColumnDefinitions> |
| | | |
| | | <DataGrid Style="{StaticResource DefaultDataGrid}" |
| | | BorderThickness="1" |
| | | SelectionMode="Extended" |
| | | ItemsSource="{Binding Columns}" x:Name="columnTable"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="#" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding }"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Dv.Datasource.FieldName}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | |
| | | <StackPanel Grid.Column="1" VerticalAlignment="Stretch" dp:StackPanelHelper.ChildSpace="10"> |
| | | <Button Style="{StaticResource PrimaryButton}" x:Name="selectAllBtn" Click="Button_Click" |
| | | Content="{DynamicResource App.SelectAll}" |
| | | /> |
| | | <Button Style="{StaticResource PrimaryButton}" x:Name="addBtn" Click="Button_Click" |
| | | Content="{DynamicResource App.AddTo}"/> |
| | | <Button Style="{StaticResource PrimaryButton}" x:Name="delBtn" Click="Button_Click" |
| | | Content="{DynamicResource App.Delete}" /> |
| | | </StackPanel> |
| | | |
| | | <DataGrid Grid.Column="2" Style="{StaticResource DefaultDataGrid}" |
| | | BorderThickness="1" |
| | | IsReadOnly="False" |
| | | SelectionMode="Extended" |
| | | ItemsSource="{Binding Conditions}" x:Name="conditionTable"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="#" CanUserResize="False" IsReadOnly="True"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | <DataGridTextColumn Width="*" IsReadOnly="True" Binding="{Binding Key}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Dv.Datasource.FieldName}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding Desc}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource Dv.Datasource.Desc}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTemplateColumn Width="*" IsReadOnly="True"> |
| | | <DataGridTemplateColumn.Header> |
| | | <TextBlock Text="{DynamicResource Dv.Datasource.TimeField}"/> |
| | | </DataGridTemplateColumn.Header> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <RadioButton IsChecked="{Binding IsTimeCol,Mode=OneWay}" |
| | | Click="RadioButton_Click"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | </Grid> |
| | | </pu:FormGroup> |
| | | </StackPanel> |
| | | </Grid> |
| | | </pu:WindowX> |
| New file |
| | |
| | | using HMI.Core.Util; |
| | | using HMI.DataViewer.Entity; |
| | | using HMI.UI.DpUtil; |
| | | using HMI.UI.Util; |
| | | using log4net; |
| | | using Microsoft.Win32; |
| | | using Panuon.WPF.UI; |
| | | using System; |
| | | using System.Collections.ObjectModel; |
| | | using System.ComponentModel; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.DataViewer |
| | | { |
| | | /// <summary> |
| | | /// ModalWindow.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class DbSettingModal : WindowX |
| | | { |
| | | public DbSettingModal(ILog log) |
| | | { |
| | | this.log = log; |
| | | this.DataContext = this; |
| | | InitializeComponent(); |
| | | } |
| | | private ILog log; |
| | | public string FilePath { get; private set; } |
| | | public int DataIndex { get; private set; } |
| | | public DataBase_Setting Data { get; private set; } |
| | | public ObservableCollection<DataBase_Setting> Datasource { get; private set; } |
| | | |
| | | private SqliteHelper helper; |
| | | public SqliteHelper Helper |
| | | { |
| | | get => helper; |
| | | set |
| | | { |
| | | if (helper == value) return; |
| | | helper?.Close(); |
| | | helper = value; |
| | | } |
| | | } |
| | | |
| | | private ObservableCollection<string> columns = new ObservableCollection<string>(); |
| | | public ObservableCollection<string> Columns |
| | | { |
| | | get => columns; |
| | | set { Set(ref columns, value); } |
| | | } |
| | | |
| | | private ObservableCollection<QueryCondition> conditions = |
| | | new ObservableCollection<QueryCondition>(); |
| | | public ObservableCollection<QueryCondition> Conditions |
| | | { |
| | | get => conditions; |
| | | set { Set(ref conditions, value); } |
| | | } |
| | | |
| | | |
| | | private bool _isProcessing; |
| | | |
| | | protected override void OnClosing(CancelEventArgs e) |
| | | { |
| | | //Press Continue Button |
| | | if (DialogResult == true) |
| | | { |
| | | if (_isProcessing) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | var okButton = WindowXModalDialog.GetOKButton(this); |
| | | try |
| | | { |
| | | _isProcessing = true; |
| | | ButtonHelper.SetIsPending(okButton, true); |
| | | if (!ValidateForm()) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | |
| | | SaveOrUpdateData(); |
| | | Helper?.Close(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("IPManage Form Submit Failed.", ex); |
| | | ToastHelper.Error(this, $"{this.lang("App.App.UnhandledException")}: {ex.Message}"); |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | finally |
| | | { |
| | | _isProcessing = false; |
| | | ButtonHelper.SetIsPending(okButton, false); |
| | | } |
| | | } |
| | | base.OnClosing(e); |
| | | } |
| | | |
| | | public bool ValidateName(object value, out string err) |
| | | { |
| | | err = null; |
| | | var target = Datasource.FirstOrDefault(e => e.Program == value.ToString()); |
| | | if (target != null && target != Data) |
| | | { |
| | | err = this.lang("App.MustBeUnique", LangUtil.Get("Dv.Datasource.Name")); ; |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private bool ValidateForm() |
| | | { |
| | | bool flag = FormGroupHelper.ValidateForm(formPanel); |
| | | if (flag) |
| | | { |
| | | Data.Program = programInput.Text; |
| | | Data.DBFilePath = dbFilePathInput.Text; |
| | | Data.TableName = tableNameInput.Text; |
| | | Data.Pattern = TbPattern.Text; |
| | | Data.TimeCol = Conditions.FirstOrDefault(c => c.IsTimeCol)?.Key; |
| | | Data.Condition = Conditions; |
| | | } |
| | | return flag; |
| | | } |
| | | |
| | | public bool? Open(int index, DataBase_Setting data, |
| | | ObservableCollection<DataBase_Setting> dataSource, |
| | | string filePath) |
| | | { |
| | | FilePath = filePath; |
| | | DataIndex = index; |
| | | Data = data; |
| | | Datasource = dataSource; |
| | | |
| | | programInput.Text = Data.Program; |
| | | dbFilePathInput.Text = Data.DBFilePath; |
| | | tableNameInput.Text = Data.TableName; |
| | | TbPattern.Text = Data.Pattern; |
| | | |
| | | if (!string.IsNullOrEmpty(Data.DBFilePath) && File.Exists(Data.DBFilePath)) |
| | | { |
| | | UpdateDbHelper(Data.DBFilePath); |
| | | UpdateTableSource(); |
| | | tableNameInput.SelectedItem = Data.TableName; |
| | | UpdateColumnSource(Data.TableName); |
| | | } |
| | | Conditions = data?.Condition ?? new ObservableCollection<QueryCondition>(); |
| | | |
| | | return this.ShowDialog(); |
| | | } |
| | | |
| | | private void SaveOrUpdateData() |
| | | { |
| | | if (DataIndex == -1) |
| | | { |
| | | InsertData(Data); |
| | | } |
| | | else |
| | | { |
| | | UpdateData(DataIndex, Data); |
| | | } |
| | | } |
| | | |
| | | public void InsertData(DataBase_Setting data) |
| | | { |
| | | Datasource.Add(data); |
| | | XmlHelper.SerializeToXml(FilePath, Datasource); |
| | | } |
| | | |
| | | public void UpdateData(int index, DataBase_Setting data) |
| | | { |
| | | Datasource[index] = data; |
| | | XmlHelper.SerializeToXml(FilePath, Datasource); |
| | | } |
| | | |
| | | private void Button_Click(object sender, System.Windows.RoutedEventArgs e) |
| | | { |
| | | Button btn = sender as Button; |
| | | switch (btn.Name) |
| | | { |
| | | case "searchBtn": |
| | | OpenSelectDbDialog(); |
| | | break; |
| | | case "selectAllBtn": |
| | | SelectAll(); |
| | | break; |
| | | case "addBtn": |
| | | AddCondition(); |
| | | break; |
| | | case "delBtn": |
| | | RemoveCondition(); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | private void UpdateDbHelper(string filePath) |
| | | { |
| | | if (Helper == null) |
| | | { |
| | | Helper = new SqliteHelper(filePath); |
| | | } |
| | | else if (Helper.DataBaseFile != filePath) |
| | | { |
| | | //Helper.Close(); |
| | | Helper = new SqliteHelper(filePath); |
| | | } |
| | | } |
| | | |
| | | private void UpdateTableSource() |
| | | { |
| | | string[] tableNames = Helper.GetAllTableInFile(); |
| | | tableNameInput.ItemsSource = tableNames; |
| | | } |
| | | |
| | | private void UpdateColumnSource(string tableName) |
| | | { |
| | | string[] cols = Helper.GetColumnNameFromTable(tableName); |
| | | Columns = new ObservableCollection<string>(cols); |
| | | } |
| | | |
| | | private void OpenSelectDbDialog() |
| | | { |
| | | string BasePath = PathHelper.ConfigFolder; |
| | | |
| | | OpenFileDialog openFileDialog = new OpenFileDialog(); |
| | | openFileDialog.Filter = "(*.db)|*.db"; |
| | | openFileDialog.InitialDirectory = BasePath; |
| | | openFileDialog.Title = this.lang("Dv.SelectDbFile"); |
| | | openFileDialog.Multiselect = false; |
| | | |
| | | if (openFileDialog.ShowDialog() == true) |
| | | { |
| | | string FileName = openFileDialog.FileName; |
| | | dbFilePathInput.Text = FileName; |
| | | UpdateDbHelper(FileName); |
| | | UpdateTableSource(); |
| | | } |
| | | } |
| | | |
| | | private void SelectAll() |
| | | { |
| | | columnTable.SelectAll(); |
| | | AddCondition(); |
| | | } |
| | | |
| | | private void AddCondition() |
| | | { |
| | | if (columnTable.SelectedItems == null || columnTable.SelectedItems.Count <= 0) return; |
| | | var temp = new ObservableCollection<QueryCondition>(); |
| | | foreach (var column in columnTable.SelectedItems) |
| | | { |
| | | temp.Add(new QueryCondition { Key = column.ToString(), Desc = column.ToString() }); |
| | | } |
| | | Conditions = temp; |
| | | } |
| | | |
| | | private void RemoveCondition() |
| | | { |
| | | if (conditionTable.SelectedItems == null || conditionTable.SelectedItems.Count <= 0) return; |
| | | Conditions = new ObservableCollection<QueryCondition>( |
| | | Conditions.Where(d => !conditionTable.SelectedItems.Contains(d)).ToList()); |
| | | } |
| | | |
| | | private void ComboxSelectionChanged(object sender, SelectionChangedEventArgs e) |
| | | { |
| | | UpdateColumnSource(e.AddedItems[0].ToString()); |
| | | //Conditions = new ObservableCollection<QueryCondition>(); |
| | | } |
| | | |
| | | private void RadioButton_Click(object sender, System.Windows.RoutedEventArgs e) |
| | | { |
| | | RadioButton btn = sender as RadioButton; |
| | | var condition = btn.DataContext as QueryCondition; |
| | | if (condition.IsTimeCol == true) |
| | | { |
| | | condition.IsTimeCol = false; |
| | | } |
| | | else |
| | | { |
| | | condition.IsTimeCol = true; |
| | | foreach (var c in Conditions) |
| | | { |
| | | if (c != condition) c.IsTimeCol = false; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | using System.Xml.Serialization; |
| | | |
| | | namespace HMI.DataViewer.Entity |
| | | { |
| | | /// <summary> |
| | | /// 数据库配置类 |
| | | /// </summary> |
| | | [Serializable] |
| | | public class DataBase_Setting : NotifyBase |
| | | { |
| | | /// <summary> |
| | | /// 数据库文件路径 |
| | | /// </summary> |
| | | private string dBFilePath = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 数据库操作类 |
| | | /// </summary> |
| | | private SqliteHelper sql = null; |
| | | |
| | | /// <summary> |
| | | /// 数据表名称 |
| | | /// </summary> |
| | | private string tableName = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 设定程序名 |
| | | /// </summary> |
| | | private string program = string.Empty; |
| | | |
| | | private string timeCol = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 高级查询条件 |
| | | /// </summary> |
| | | private ObservableCollection<QueryCondition> condition = new ObservableCollection<QueryCondition>(); |
| | | |
| | | /// <summary> |
| | | /// 构造函数 |
| | | /// </summary> |
| | | public DataBase_Setting() |
| | | { |
| | | // do nothing |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 数据库文件的存储路径 |
| | | /// </summary> |
| | | public string DBFilePath |
| | | { |
| | | get => dBFilePath; |
| | | set { Set(ref dBFilePath, value); } |
| | | } |
| | | |
| | | private string pattern; |
| | | public string Pattern |
| | | { |
| | | get => pattern; |
| | | set |
| | | { |
| | | Set(ref pattern, value); |
| | | } |
| | | } |
| | | |
| | | public string TimeCol |
| | | { |
| | | get => timeCol; |
| | | set { Set(ref timeCol, value); } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 数据库操作句柄 |
| | | /// </summary> |
| | | [XmlIgnore] |
| | | public SqliteHelper SQL |
| | | { |
| | | get { return this.sql; } |
| | | set { this.sql = value; } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 高级查询条件 |
| | | /// </summary> |
| | | public ObservableCollection<QueryCondition> Condition |
| | | { |
| | | get { return this.condition; } |
| | | set { Set(ref condition, value); } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 数据表名称 |
| | | /// </summary> |
| | | public string TableName |
| | | { |
| | | get { return this.tableName; } |
| | | set { Set(ref tableName, value); } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 设定程序名 |
| | | /// </summary> |
| | | public string Program |
| | | { |
| | | get { return this.program; } |
| | | set { Set(ref program, value); } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace HMI.DataViewer.Entity |
| | | { |
| | | /// <summary> |
| | | /// 筛选条件类 |
| | | /// </summary> |
| | | public class QueryCondition : NotifyBase |
| | | { |
| | | /// <summary> |
| | | /// 查询的键值 |
| | | /// </summary> |
| | | private string key = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 键值描述 |
| | | /// </summary> |
| | | private string desc = string.Empty; |
| | | |
| | | /// <summary> |
| | | /// 查询的键值 |
| | | /// </summary> |
| | | public string Key |
| | | { |
| | | get { return this.key; } |
| | | set { Set(ref key, value); } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 键值描述 |
| | | /// </summary> |
| | | public string Desc |
| | | { |
| | | get { return this.desc; } |
| | | set { Set(ref desc, value); } |
| | | } |
| | | |
| | | private bool isTimeCol; |
| | | public bool IsTimeCol |
| | | { |
| | | get => isTimeCol; |
| | | set { Set(ref isTimeCol, value); } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | |
| | | namespace HMI.DataViewer.Entity |
| | | { |
| | | public class QueryData : NotifyBase |
| | | { |
| | | private string key; |
| | | private string name; |
| | | private string condition; |
| | | private string _value; |
| | | |
| | | public string Key |
| | | { |
| | | get => key; |
| | | set => Set(ref key, value); |
| | | } |
| | | |
| | | public string Name |
| | | { |
| | | get => name; |
| | | set => Set(ref name, value); |
| | | } |
| | | |
| | | public string Condition |
| | | { |
| | | get => condition; |
| | | set => Set(ref condition, value); |
| | | } |
| | | |
| | | public string Value |
| | | { |
| | | get => _value; |
| | | set => Set(ref _value, value); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| | | <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
| | | <PropertyGroup> |
| | | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
| | | <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
| | | <ProjectGuid>{A580DE4A-34E7-4EFE-AD2C-0871396000E9}</ProjectGuid> |
| | | <OutputType>library</OutputType> |
| | | <RootNamespace>HMI.DataViewer</RootNamespace> |
| | | <AssemblyName>HMI.DataViewer</AssemblyName> |
| | | <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> |
| | | <FileAlignment>512</FileAlignment> |
| | | <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> |
| | | <WarningLevel>4</WarningLevel> |
| | | <Deterministic>true</Deterministic> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <DebugType>full</DebugType> |
| | | <Optimize>false</Optimize> |
| | | <OutputPath>bin\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
| | | <DebugType>pdbonly</DebugType> |
| | | <Optimize>true</Optimize> |
| | | <OutputPath>bin\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <OutputPath>bin\x64\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <DebugType>full</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> |
| | | <OutputPath>bin\x64\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <Optimize>true</Optimize> |
| | | <DebugType>pdbonly</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <ItemGroup> |
| | | <Reference Include="ICSharpCode.SharpZipLib, Version=1.0.0.999, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\SharpZipLib.1.0.0\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="NPOI, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="NPOI.OOXML, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OOXML.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="NPOI.OpenXml4Net, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OpenXml4Net.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="NPOI.OpenXmlFormats, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\NPOI.2.4.1\lib\net45\NPOI.OpenXmlFormats.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Panuon.WPF, Version=1.1.2.2, Culture=neutral, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Panuon.WPF.1.1.3\lib\net472\Panuon.WPF.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Panuon.WPF.UI, Version=1.3.0.2, Culture=neutral, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Panuon.WPF.UI.1.3.0.2\lib\net472\Panuon.WPF.UI.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="System" /> |
| | | <Reference Include="System.Configuration" /> |
| | | <Reference Include="System.Data" /> |
| | | <Reference Include="System.Web" /> |
| | | <Reference Include="System.Windows.Forms" /> |
| | | <Reference Include="System.Xml" /> |
| | | <Reference Include="Microsoft.CSharp" /> |
| | | <Reference Include="System.Core" /> |
| | | <Reference Include="System.Xml.Linq" /> |
| | | <Reference Include="System.Data.DataSetExtensions" /> |
| | | <Reference Include="System.Net.Http" /> |
| | | <Reference Include="System.Xaml"> |
| | | <RequiredTargetFramework>4.0</RequiredTargetFramework> |
| | | </Reference> |
| | | <Reference Include="WindowsBase" /> |
| | | <Reference Include="PresentationCore"> |
| | | <Private>False</Private> |
| | | </Reference> |
| | | <Reference Include="PresentationFramework" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Page Include="DataViewer.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | <Compile Include="DataViewer.xaml.cs"> |
| | | <DependentUpon>DataViewer.xaml</DependentUpon> |
| | | <SubType>Code</SubType> |
| | | </Compile> |
| | | <Page Include="DbSettingModal.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="Entity\QueryCondition.cs" /> |
| | | <Compile Include="Entity\DataBase_Setting.cs" /> |
| | | <Compile Include="DbSettingModal.xaml.cs"> |
| | | <DependentUpon>DbSettingModal.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Entity\QueryData.cs" /> |
| | | <Compile Include="Properties\AssemblyInfo.cs"> |
| | | <SubType>Code</SubType> |
| | | </Compile> |
| | | <Compile Include="Properties\Resources.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DesignTime>True</DesignTime> |
| | | <DependentUpon>Resources.resx</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Properties\Settings.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DependentUpon>Settings.settings</DependentUpon> |
| | | <DesignTimeSharedInput>True</DesignTimeSharedInput> |
| | | </Compile> |
| | | <EmbeddedResource Include="Properties\Resources.resx"> |
| | | <Generator>ResXFileCodeGenerator</Generator> |
| | | <LastGenOutput>Resources.Designer.cs</LastGenOutput> |
| | | </EmbeddedResource> |
| | | <None Include="packages.config" /> |
| | | <None Include="Properties\Settings.settings"> |
| | | <Generator>SettingsSingleFileGenerator</Generator> |
| | | <LastGenOutput>Settings.Designer.cs</LastGenOutput> |
| | | </None> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <ProjectReference Include="..\HMI.Base\HMI.Base.csproj"> |
| | | <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project> |
| | | <Name>HMI.Base</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.Core\HMI.Core.csproj"> |
| | | <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project> |
| | | <Name>HMI.Core</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.UI\HMI.UI.csproj"> |
| | | <Project>{f17de6b5-af1d-48c6-94a0-6b763a6546a6}</Project> |
| | | <Name>HMI.UI</Name> |
| | | </ProjectReference> |
| | | </ItemGroup> |
| | | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| | | <PropertyGroup> |
| | | <PostBuildEvent> |
| | | </PostBuildEvent> |
| | | </PropertyGroup> |
| | | </Project> |
| New file |
| | |
| | | using System.Reflection; |
| | | using System.Resources; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Runtime.InteropServices; |
| | | using System.Windows; |
| | | |
| | | // 有关程序集的一般信息由以下 |
| | | // 控制。更改这些特性值可修改 |
| | | // 与程序集关联的信息。 |
| | | [assembly: AssemblyTitle("HMI.DataViewer")] |
| | | [assembly: AssemblyDescription("")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("Organization")] |
| | | [assembly: AssemblyProduct("HMI.DataViewer")] |
| | | [assembly: AssemblyCopyright("Copyright © Organization 2025")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // 将 ComVisible 设置为 false 会使此程序集中的类型 |
| | | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 |
| | | //请将此类型的 ComVisible 特性设置为 true。 |
| | | [assembly: ComVisible(false)] |
| | | |
| | | //若要开始生成可本地化的应用程序,请设置 |
| | | //.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture> |
| | | //在 <PropertyGroup> 中。例如,如果你使用的是美国英语。 |
| | | //使用的是美国英语,请将 <UICulture> 设置为 en-US。 然后取消 |
| | | //对以下 NeutralResourceLanguage 特性的注释。 更新 |
| | | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 |
| | | |
| | | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] |
| | | |
| | | |
| | | [assembly:ThemeInfo( |
| | | ResourceDictionaryLocation.None, //主题特定资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //或应用程序资源字典中找到时使用) |
| | | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //、应用程序或任何主题专用资源字典中找到时使用) |
| | | )] |
| | | |
| | | |
| | | // 程序集的版本信息由下列四个值组成: |
| | | // |
| | | // 主版本 |
| | | // 次版本 |
| | | // 生成号 |
| | | // 修订号 |
| | | // |
| | | [assembly: AssemblyVersion("1.0.0.0")] |
| | | [assembly: AssemblyFileVersion("1.0.0.0")] |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // 此代码由工具生成。 |
| | | // 运行时版本: 4.0.30319.42000 |
| | | // |
| | | // 对此文件的更改可能导致不正确的行为,如果 |
| | | // 重新生成代码,则所做更改将丢失。 |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.DataViewer.Properties { |
| | | |
| | | |
| | | /// <summary> |
| | | /// 强类型资源类,用于查找本地化字符串等。 |
| | | /// </summary> |
| | | // 此类是由 StronglyTypedResourceBuilder |
| | | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 |
| | | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen |
| | | // (以 /str 作为命令选项),或重新生成 VS 项目。 |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] |
| | | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | internal class Resources { |
| | | |
| | | private static global::System.Resources.ResourceManager resourceMan; |
| | | |
| | | private static global::System.Globalization.CultureInfo resourceCulture; |
| | | |
| | | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
| | | internal Resources() { |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 返回此类使用的缓存 ResourceManager 实例。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Resources.ResourceManager ResourceManager { |
| | | get { |
| | | if ((resourceMan == null)) { |
| | | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HMI.DataViewer.Properties.Resources", typeof(Resources).Assembly); |
| | | resourceMan = temp; |
| | | } |
| | | return resourceMan; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 重写当前线程的 CurrentUICulture 属性,对 |
| | | /// 使用此强类型资源类的所有资源查找执行重写。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Globalization.CultureInfo Culture { |
| | | get { |
| | | return resourceCulture; |
| | | } |
| | | set { |
| | | resourceCulture = value; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <root> |
| | | <!-- |
| | | Microsoft ResX Schema |
| | | |
| | | Version 2.0 |
| | | |
| | | The primary goals of this format is to allow a simple XML format |
| | | that is mostly human readable. The generation and parsing of the |
| | | various data types are done through the TypeConverter classes |
| | | associated with the data types. |
| | | |
| | | Example: |
| | | |
| | | ... ado.net/XML headers & schema ... |
| | | <resheader name="resmimetype">text/microsoft-resx</resheader> |
| | | <resheader name="version">2.0</resheader> |
| | | <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
| | | <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
| | | <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
| | | <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
| | | <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
| | | <value>[base64 mime encoded serialized .NET Framework object]</value> |
| | | </data> |
| | | <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
| | | <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
| | | <comment>This is a comment</comment> |
| | | </data> |
| | | |
| | | There are any number of "resheader" rows that contain simple |
| | | name/value pairs. |
| | | |
| | | Each data row contains a name, and value. The row also contains a |
| | | type or mimetype. Type corresponds to a .NET class that support |
| | | text/value conversion through the TypeConverter architecture. |
| | | Classes that don't support this are serialized and stored with the |
| | | mimetype set. |
| | | |
| | | The mimetype is used for serialized objects, and tells the |
| | | ResXResourceReader how to depersist the object. This is currently not |
| | | extensible. For a given mimetype the value must be set accordingly: |
| | | |
| | | Note - application/x-microsoft.net.object.binary.base64 is the format |
| | | that the ResXResourceWriter will generate, however the reader can |
| | | read any of the formats listed below. |
| | | |
| | | mimetype: application/x-microsoft.net.object.binary.base64 |
| | | value : The object must be serialized with |
| | | : System.Serialization.Formatters.Binary.BinaryFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.soap.base64 |
| | | value : The object must be serialized with |
| | | : System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.bytearray.base64 |
| | | value : The object must be serialized into a byte array |
| | | : using a System.ComponentModel.TypeConverter |
| | | : and then encoded with base64 encoding. |
| | | --> |
| | | <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
| | | <xsd:element name="root" msdata:IsDataSet="true"> |
| | | <xsd:complexType> |
| | | <xsd:choice maxOccurs="unbounded"> |
| | | <xsd:element name="metadata"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | <xsd:attribute name="type" type="xsd:string" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="assembly"> |
| | | <xsd:complexType> |
| | | <xsd:attribute name="alias" type="xsd:string" /> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="data"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> |
| | | <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="resheader"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" use="required" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:choice> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:schema> |
| | | <resheader name="resmimetype"> |
| | | <value>text/microsoft-resx</value> |
| | | </resheader> |
| | | <resheader name="version"> |
| | | <value>2.0</value> |
| | | </resheader> |
| | | <resheader name="reader"> |
| | | <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | <resheader name="writer"> |
| | | <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | </root> |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // This code was generated by a tool. |
| | | // Runtime Version:4.0.30319.42000 |
| | | // |
| | | // Changes to this file may cause incorrect behavior and will be lost if |
| | | // the code is regenerated. |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.DataViewer.Properties |
| | | { |
| | | |
| | | |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] |
| | | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase |
| | | { |
| | | |
| | | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); |
| | | |
| | | public static Settings Default |
| | | { |
| | | get |
| | | { |
| | | return defaultInstance; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version='1.0' encoding='utf-8'?> |
| | | <SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> |
| | | <Profiles> |
| | | <Profile Name="(Default)" /> |
| | | </Profiles> |
| | | <Settings /> |
| | | </SettingsFile> |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <packages> |
| | | <package id="log4net" version="3.0.4" targetFramework="net472" /> |
| | | <package id="NPOI" version="2.4.1" targetFramework="net472" /> |
| | | <package id="Panuon.WPF" version="1.1.3" targetFramework="net472" /> |
| | | <package id="Panuon.WPF.UI" version="1.3.0.2" targetFramework="net472" /> |
| | | <package id="SharpZipLib" version="1.0.0" targetFramework="net472" /> |
| | | </packages> |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| | | <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
| | | <PropertyGroup> |
| | | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
| | | <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
| | | <ProjectGuid>{20C23602-B029-4E21-8C11-B9E5F2223237}</ProjectGuid> |
| | | <OutputType>library</OutputType> |
| | | <RootNamespace>HMI.LogViewer</RootNamespace> |
| | | <AssemblyName>HMI.LogViewer</AssemblyName> |
| | | <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> |
| | | <FileAlignment>512</FileAlignment> |
| | | <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> |
| | | <WarningLevel>4</WarningLevel> |
| | | <Deterministic>true</Deterministic> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <DebugType>full</DebugType> |
| | | <Optimize>false</Optimize> |
| | | <OutputPath>bin\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
| | | <DebugType>pdbonly</DebugType> |
| | | <Optimize>true</Optimize> |
| | | <OutputPath>bin\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <OutputPath>bin\x64\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <DebugType>full</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> |
| | | <OutputPath>bin\x64\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <Optimize>true</Optimize> |
| | | <DebugType>pdbonly</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <ItemGroup> |
| | | <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Panuon.WPF, Version=1.1.2.2, Culture=neutral, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Panuon.WPF.1.1.3\lib\net472\Panuon.WPF.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Panuon.WPF.UI, Version=1.3.0.2, Culture=neutral, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Panuon.WPF.UI.1.3.0.2\lib\net472\Panuon.WPF.UI.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="System" /> |
| | | <Reference Include="System.Configuration" /> |
| | | <Reference Include="System.Data" /> |
| | | <Reference Include="System.Web" /> |
| | | <Reference Include="System.Xml" /> |
| | | <Reference Include="Microsoft.CSharp" /> |
| | | <Reference Include="System.Core" /> |
| | | <Reference Include="System.Xml.Linq" /> |
| | | <Reference Include="System.Data.DataSetExtensions" /> |
| | | <Reference Include="System.Net.Http" /> |
| | | <Reference Include="System.Xaml"> |
| | | <RequiredTargetFramework>4.0</RequiredTargetFramework> |
| | | </Reference> |
| | | <Reference Include="WindowsBase" /> |
| | | <Reference Include="PresentationCore" /> |
| | | <Reference Include="PresentationFramework" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Page Include="LogViewer.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | <Compile Include="LogData.cs" /> |
| | | <Compile Include="LogViewer.xaml.cs"> |
| | | <DependentUpon>LogViewer.xaml</DependentUpon> |
| | | <SubType>Code</SubType> |
| | | </Compile> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="Properties\AssemblyInfo.cs"> |
| | | <SubType>Code</SubType> |
| | | </Compile> |
| | | <Compile Include="Properties\Resources.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DesignTime>True</DesignTime> |
| | | <DependentUpon>Resources.resx</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Properties\Settings.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DependentUpon>Settings.settings</DependentUpon> |
| | | <DesignTimeSharedInput>True</DesignTimeSharedInput> |
| | | </Compile> |
| | | <EmbeddedResource Include="Properties\Resources.resx"> |
| | | <Generator>ResXFileCodeGenerator</Generator> |
| | | <LastGenOutput>Resources.Designer.cs</LastGenOutput> |
| | | </EmbeddedResource> |
| | | <None Include="packages.config" /> |
| | | <None Include="Properties\Settings.settings"> |
| | | <Generator>SettingsSingleFileGenerator</Generator> |
| | | <LastGenOutput>Settings.Designer.cs</LastGenOutput> |
| | | </None> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <ProjectReference Include="..\HMI.Base\HMI.Base.csproj"> |
| | | <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project> |
| | | <Name>HMI.Base</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.Core\HMI.Core.csproj"> |
| | | <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project> |
| | | <Name>HMI.Core</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.UI\HMI.UI.csproj"> |
| | | <Project>{f17de6b5-af1d-48c6-94a0-6b763a6546a6}</Project> |
| | | <Name>HMI.UI</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.Util\HMI.Util.csproj"> |
| | | <Project>{afea100a-9223-448d-ab10-3015d93dd98c}</Project> |
| | | <Name>HMI.Util</Name> |
| | | </ProjectReference> |
| | | </ItemGroup> |
| | | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| | | <PropertyGroup> |
| | | <PostBuildEvent> |
| | | </PostBuildEvent> |
| | | </PropertyGroup> |
| | | </Project> |
| New file |
| | |
| | | using HMI.Core; |
| | | |
| | | namespace HMI.LogViewer |
| | | { |
| | | public class LogData |
| | | { |
| | | [ExcelColumn("级别")] |
| | | public string level { get; set; } |
| | | |
| | | [ExcelColumn("时间")] |
| | | public string time { get; set; } |
| | | |
| | | [ExcelColumn("来源")] |
| | | public string source { get; set; } |
| | | |
| | | [ExcelColumn("内容")] |
| | | public string text { get; set; } |
| | | } |
| | | } |
| New file |
| | |
| | | <hb:PluginControl x:Class="HMI.LogViewer.LogViewer" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:local="clr-namespace:HMI.LogViewer" |
| | | mc:Ignorable="d" |
| | | xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" |
| | | xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | Loaded="UserControl_Loaded" |
| | | Background="{DynamicResource WorkspaceBackground}" |
| | | d:DesignHeight="450" d:DesignWidth="800"> |
| | | <UserControl.Resources> |
| | | <ResourceDictionary> |
| | | <ResourceDictionary.MergedDictionaries> |
| | | <ResourceDictionary Source="/HMI.UI;component/Style/PanuonListView.xaml"/> |
| | | </ResourceDictionary.MergedDictionaries> |
| | | |
| | | <Style x:Key="TableBlock" TargetType="TextBlock"> |
| | | <Setter Property="Padding" Value="0"/> |
| | | <Setter Property="Margin" Value="0"/> |
| | | <Setter Property="VerticalAlignment" Value="Center"/> |
| | | <Setter Property="TextWrapping" Value="Wrap"/> |
| | | </Style> |
| | | |
| | | <Style x:Key="TableBox" TargetType="TextBox"> |
| | | <Setter Property="Padding" Value="0"/> |
| | | <Setter Property="Margin" Value="0"/> |
| | | <Setter Property="VerticalAlignment" Value="Center"/> |
| | | <Setter Property="VerticalContentAlignment" Value="Center"/> |
| | | <Setter Property="TextWrapping" Value="Wrap"/> |
| | | <Setter Property="BorderThickness" Value="0"/> |
| | | <Setter Property="BorderBrush" Value="Transparent"/> |
| | | <Setter Property="Background" Value="Transparent"/> |
| | | <Setter Property="Foreground" Value="{Binding Foreground,RelativeSource={RelativeSource Mode=FindAncestor, |
| | | AncestorLevel=1,AncestorType=ListViewItem}}"/> |
| | | <Style.Triggers> |
| | | <DataTrigger Binding="{Binding IsSelected,RelativeSource={RelativeSource Mode=FindAncestor, |
| | | AncestorLevel=1,AncestorType=ListViewItem}}" Value="True"> |
| | | <Setter Property="Foreground" Value="#FFF"/> |
| | | </DataTrigger> |
| | | <DataTrigger Binding="{Binding IsMouseOver,RelativeSource={RelativeSource Mode=FindAncestor, |
| | | AncestorLevel=1,AncestorType=ListViewItem}}" Value="True"> |
| | | <Setter Property="Foreground" Value="#FFF"/> |
| | | </DataTrigger> |
| | | </Style.Triggers> |
| | | </Style> |
| | | |
| | | <Style x:Key="sbCell" TargetType="DataGridCell" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridCellStyle}}"> |
| | | <Setter Property="Padding" Value="0"/> |
| | | <Setter Property="Margin" Value="0"/> |
| | | </Style> |
| | | </ResourceDictionary> |
| | | </UserControl.Resources> |
| | | <Border Style="{StaticResource CardBoarder}"> |
| | | <Grid dp:GridHelper.Space="10" Cursor=""> |
| | | <Grid.RowDefinitions> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition Height="Auto"/> |
| | | <RowDefinition/> |
| | | <RowDefinition Height="Auto"/> |
| | | </Grid.RowDefinitions> |
| | | |
| | | <TextBlock FontSize="{StaticResource TitleFontSize}" |
| | | VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource log.title}"/> |
| | | |
| | | <DockPanel Grid.Row="1" Cursor=""> |
| | | <StackPanel DockPanel.Dock="Right" Style="{StaticResource ButtonGroup}"> |
| | | <Button Style="{StaticResource QueryBtn}" |
| | | x:Name="queryBtn" Click="Button_Click"/> |
| | | <Button Style="{StaticResource ResetBtn}" |
| | | x:Name="resetBtn" Click="Button_Click"/> |
| | | <Button x:Name="exportBtn" |
| | | Style="{StaticResource ExportBtn}" Click="Button_Click"/> |
| | | </StackPanel> |
| | | |
| | | <StackPanel Style="{StaticResource ButtonGroup}" HorizontalAlignment="Left"> |
| | | <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.level}"/> |
| | | <ComboBox Style="{StaticResource FormComboxStyle}" x:Name="levelInput" |
| | | Width="180" |
| | | SelectedValuePath="Content"> |
| | | <ComboBoxItem Content="Debug"/> |
| | | <ComboBoxItem Content="Info"/> |
| | | <ComboBoxItem Content="Warn"/> |
| | | <ComboBoxItem Content="Error"/> |
| | | </ComboBox> |
| | | |
| | | <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.source}"/> |
| | | <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="sourceInput" Width="180"/> |
| | | |
| | | <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource App.StartTime}"/> |
| | | <pu:DateTimePicker Style="{StaticResource TimePicker}" IsEnabled="{Binding AutoRfh,Converter={StaticResource reverseBool}}" |
| | | x:Name="startTimeInput"/> |
| | | |
| | | <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource App.EndTime}"/> |
| | | <pu:DateTimePicker Style="{StaticResource TimePicker}" IsEnabled="{Binding AutoRfh,Converter={StaticResource reverseBool}}" |
| | | x:Name="endTimeInput"/> |
| | | |
| | | <TextBlock Style="{StaticResource TitleBox}" FontSize="14" Text="{DynamicResource log.autoRefresh}"/> |
| | | <CheckBox x:Name="autoRfhInput" HorizontalAlignment="Center" VerticalAlignment="Center" |
| | | IsChecked="{Binding AutoRfh,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </StackPanel> |
| | | </DockPanel> |
| | | |
| | | <ListView Grid.Row="2" Style="{StaticResource DefaultListTable}" x:Name="table" |
| | | VirtualizingPanel.VirtualizationMode="Standard" > |
| | | <ListView.ItemContainerStyle> |
| | | <Style TargetType="ListViewItem" BasedOn="{StaticResource {x:Static pu:StyleKeys.ListViewItemStyle}}"> |
| | | <Setter Property="Height" Value="{StaticResource nanD}" /> |
| | | <Style.Triggers> |
| | | <DataTrigger Binding="{Binding level}" Value="Warn"> |
| | | <Setter Property="Foreground" Value="{DynamicResource WarningColor}"/> |
| | | </DataTrigger> |
| | | <DataTrigger Binding="{Binding level}" Value="Error"> |
| | | <Setter Property="Foreground" Value="{DynamicResource DangerColor}"/> |
| | | </DataTrigger> |
| | | <Trigger Property="IsSelected" Value="True"> |
| | | <Setter Property="Background" Value="Transparent"/> |
| | | </Trigger> |
| | | <Trigger Property="IsMouseOver" Value="True"> |
| | | <Setter Property="Background" Value="Transparent"/> |
| | | </Trigger> |
| | | </Style.Triggers> |
| | | </Style> |
| | | </ListView.ItemContainerStyle> |
| | | |
| | | <ListView.View> |
| | | <GridView> |
| | | <GridViewColumn Width="50"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="#"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource lvRowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="80"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.level}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding level}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="150"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.time}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding time,Converter={x:Static uic:DateStrConverter.Instance},ConverterParameter='yyyy-MM-dd HH:mm:ss.fff'}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="150"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.source}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding source}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="1420"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.text}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBox IsReadOnly="True" Style="{StaticResource TableBox}" |
| | | Text="{Binding text}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | </GridView> |
| | | </ListView.View> |
| | | </ListView> |
| | | |
| | | <StackPanel Grid.Row="3" HorizontalAlignment="Right"> |
| | | <pu:Pagination Style="{StaticResource DefaultPage}" |
| | | CurrentPage="{Binding CurrentPage}" |
| | | MaxPage="{Binding MaxPage}"/> |
| | | </StackPanel> |
| | | |
| | | <!--<DataGrid Grid.Row="2" x:Name="table" |
| | | CellStyle="{StaticResource sbCell}" |
| | | RowHeight="{StaticResource nanD}" Style="{StaticResource DefaultDataGrid}"> |
| | | <DataGrid.RowStyle> |
| | | <Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}"> |
| | | <Style.Triggers> |
| | | <DataTrigger Binding="{Binding level}" Value="Warn"> |
| | | <Setter Property="Background" Value="{DynamicResource WarningColor}"/> |
| | | <Setter Property="Foreground" Value="Black"/> |
| | | </DataTrigger> |
| | | <DataTrigger Binding="{Binding level}" Value="Error"> |
| | | <Setter Property="Background" Value="{DynamicResource DangerColor}"/> |
| | | <Setter Property="Foreground" Value="Black"/> |
| | | </DataTrigger> |
| | | <Trigger Property="IsSelected" Value="True"> |
| | | <Setter Property="Background" Value="Transparent"/> |
| | | </Trigger> |
| | | <Trigger Property="IsMouseOver" Value="True"> |
| | | <Setter Property="Background" Value="Transparent"/> |
| | | </Trigger> |
| | | </Style.Triggers> |
| | | </Style> |
| | | </DataGrid.RowStyle> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="#" Width="60" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Style="{StaticResource TableBlock}" Text="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | <DataGridTextColumn Width="80" Binding="{Binding level}" |
| | | ElementStyle="{StaticResource TableBlock}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.level}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="150" Binding="{Binding time}" |
| | | ElementStyle="{StaticResource TableBlock}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.time}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="150" Binding="{Binding source}" |
| | | ElementStyle="{StaticResource TableBlock}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.source}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTemplateColumn Width="*"> |
| | | <DataGridTemplateColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.text}"/> |
| | | </DataGridTemplateColumn.Header> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBox IsReadOnly="True" Style="{StaticResource TableBox}" Text="{Binding text}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid>--> |
| | | |
| | | <!--<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right"> |
| | | <pu:Pagination |
| | | Height="36" Margin="10" |
| | | HorizontalAlignment="Left" |
| | | Foreground="Black" |
| | | ItemsBackground="Transparent" |
| | | ItemsCornerRadius="12" |
| | | ItemsSelectedBackground="{DynamicResource SecondPrimaryColor}" |
| | | x:Name="page" |
| | | CurrentPage="{Binding CurrentPage}" |
| | | MaxPage="{Binding MaxPage}"/> |
| | | </StackPanel>--> |
| | | |
| | | <!--<ListView Grid.Row="2" Style="{StaticResource DefaultListTable}" x:Name="table" VirtualizingPanel.IsContainerVirtualizable="True" |
| | | VirtualizingPanel.IsVirtualizing="True" |
| | | VirtualizingPanel.VirtualizationMode="Standard"> |
| | | <ListView.ItemContainerStyle> |
| | | <Style TargetType="ListViewItem" BasedOn="{StaticResource {x:Static pu:StyleKeys.ListViewItemStyle}}"> |
| | | <Style.Triggers> |
| | | <DataTrigger Binding="{Binding level}" Value="Warn"> |
| | | <Setter Property="Background" Value="{DynamicResource WarningColor}"/> |
| | | <Setter Property="Foreground" Value="Black"/> |
| | | </DataTrigger> |
| | | <DataTrigger Binding="{Binding level}" Value="Error"> |
| | | <Setter Property="Background" Value="{DynamicResource DangerColor}"/> |
| | | <Setter Property="Foreground" Value="Black"/> |
| | | </DataTrigger> |
| | | <Trigger Property="IsSelected" Value="True"> |
| | | <Setter Property="Background" Value="Transparent"/> |
| | | </Trigger> |
| | | <Trigger Property="IsMouseOver" Value="True"> |
| | | <Setter Property="Background" Value="Transparent"/> |
| | | </Trigger> |
| | | </Style.Triggers> |
| | | </Style> |
| | | </ListView.ItemContainerStyle> |
| | | <ListView.View> |
| | | <GridView AllowsColumnReorder="False"> |
| | | <GridViewColumn Width="60" Header="#"> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource lvRowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListViewItem,AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | |
| | | <GridViewColumn Width="80"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.level}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding level}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="150"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.time}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding time}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="150"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.source}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding source}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | <GridViewColumn Width="1410"> |
| | | <GridViewColumn.Header> |
| | | <TextBlock Text="{DynamicResource log.text}"/> |
| | | </GridViewColumn.Header> |
| | | <GridViewColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <Border Style="{StaticResource CellBorder}"> |
| | | <TextBlock Style="{StaticResource ListTableTextBlock}" |
| | | Text="{Binding text}"/> |
| | | </Border> |
| | | </DataTemplate> |
| | | </GridViewColumn.CellTemplate> |
| | | </GridViewColumn> |
| | | </GridView> |
| | | </ListView.View> |
| | | </ListView>--> |
| | | <!--<pu:Pagination HorizontalContentAlignment="Right" Grid.Row="3" Margin="0,10,0,0" Height="30" |
| | | FontSize="14" |
| | | HorizontalAlignment="Left" |
| | | Foreground="Black" |
| | | ItemsBackground="Transparent" |
| | | ItemsCornerRadius="12" |
| | | ItemsSelectedBackground="#36AAF7" CurrentPage="1" MaxPage="10"/>--> |
| | | </Grid> |
| | | </Border> |
| | | </hb:PluginControl> |
| New file |
| | |
| | | using HMI.Base; |
| | | using HMI.Core.Util; |
| | | using HMI.UI.Util; |
| | | using HMI.Util; |
| | | using Microsoft.Win32; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Data; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System.Threading.Tasks; |
| | | using System.Timers; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.LogViewer |
| | | { |
| | | /// <summary> |
| | | /// UserControl1.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class LogViewer : PluginControl |
| | | { |
| | | |
| | | private bool autoRfh; |
| | | |
| | | public bool AutoRfh |
| | | { |
| | | get { return autoRfh; } |
| | | set |
| | | { |
| | | Set(ref autoRfh, value); |
| | | if (autoRfhTimer != null) autoRfhTimer.Enabled = value; |
| | | } |
| | | } |
| | | |
| | | private Timer autoRfhTimer; |
| | | |
| | | private long pageSize = 50; |
| | | |
| | | private long currentPage = 1; |
| | | public long CurrentPage |
| | | { |
| | | get => currentPage; |
| | | set |
| | | { |
| | | Set(ref currentPage, value); |
| | | Filtrate(value, pageSize); |
| | | } |
| | | } |
| | | |
| | | private long maxPage = 1; |
| | | public long MaxPage |
| | | { |
| | | get => maxPage; |
| | | set |
| | | { |
| | | Set(ref maxPage, value); |
| | | } |
| | | } |
| | | |
| | | |
| | | public LogViewer() |
| | | { |
| | | DataContext = this; |
| | | autoRfhTimer = new Timer |
| | | { |
| | | Enabled = false, |
| | | Interval = 1500, |
| | | AutoReset = false |
| | | }; |
| | | autoRfhTimer.Elapsed += AutoRfhTimer_Elapsed; |
| | | InitializeComponent(); |
| | | QueryReset(); |
| | | } |
| | | |
| | | public override void InitFinish() |
| | | { |
| | | //base.InitFinish(); |
| | | //string baseFile = Path.Combine(logDir, "log.db"); |
| | | |
| | | //try |
| | | //{ |
| | | // if (!File.Exists(baseFile)) |
| | | // { |
| | | // SqliteHelper.CreateDatabase(baseFile); |
| | | // } |
| | | // SqliteHelper sqliteHelper = new SqliteHelper(baseFile); |
| | | // sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS log ( |
| | | // id INTEGER PRIMARY KEY AUTOINCREMENT, |
| | | // level TEXT NOT NULL, |
| | | // text TEXT NOT NULL, |
| | | // source TEXT, |
| | | // time TIME |
| | | // );"); |
| | | //} |
| | | //catch (Exception ex) |
| | | //{ |
| | | // log.Error("Create Log File Failed.", ex); |
| | | //} |
| | | } |
| | | |
| | | private void UserControl_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | Loaded -= UserControl_Loaded; |
| | | } |
| | | |
| | | private void AutoRfhTimer_Elapsed(object sender, ElapsedEventArgs e) |
| | | { |
| | | Refresh100items(); |
| | | } |
| | | |
| | | //private void RefreshTable() { |
| | | // if(CurrentPage == ) |
| | | //} |
| | | |
| | | private void Button_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | Button btn = sender as Button; |
| | | switch (btn.Name) |
| | | { |
| | | case "queryBtn": |
| | | QueryLog(); |
| | | break; |
| | | case "resetBtn": |
| | | QueryReset(); |
| | | CurrentPage = 1; |
| | | break; |
| | | case "exportBtn": |
| | | DoExport(); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | private void DoExport() |
| | | { |
| | | try |
| | | { |
| | | SaveFileDialog sfd = new SaveFileDialog |
| | | { |
| | | Filter = "Excel Files (*.xls)|*.xls", |
| | | }; |
| | | if (sfd.ShowDialog() == true) |
| | | { |
| | | var dt = GetDataTable(false, 1, 1, out long totalPage); |
| | | ExcelHelper.ExportExcel<LogData>(sfd.FileName, dt); |
| | | ToastHelper.Success(this.lang("App.ExportSuccess")); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Export Log Failed.", ex); |
| | | MsgBoxHelper2.Error($"导出失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | private void QueryLog() |
| | | { |
| | | try |
| | | { |
| | | if (startTimeInput.SelectedDateTime == null && endTimeInput.SelectedDateTime == null) |
| | | { |
| | | ToastHelper.Error(this.lang("log.timeCantBeNull")); |
| | | return; |
| | | } |
| | | |
| | | if (endTimeInput.SelectedDateTime != null && startTimeInput.SelectedDateTime != null && |
| | | endTimeInput.SelectedDateTime < startTimeInput.SelectedDateTime) |
| | | { |
| | | ToastHelper.Error(this.lang("log.timeRangeError")); |
| | | return; |
| | | } |
| | | |
| | | AutoRfh = false; |
| | | //queryBtn.IsEnabled = false; |
| | | //autoRfhInput.IsEnabled = false; |
| | | CurrentPage = 1; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Log Query Failed.", ex); |
| | | this.Error($"查询失败!{ex.Message}"); |
| | | } |
| | | finally |
| | | { |
| | | queryBtn.IsEnabled = true; |
| | | autoRfhInput.IsEnabled = true; |
| | | } |
| | | } |
| | | |
| | | private void QueryReset() |
| | | { |
| | | var today = DateTime.Now; |
| | | var start = DateTime.Parse(today.ToString("yyyy-MM-dd 00:00:00")).AddDays(-7); |
| | | var end = DateTime.Parse(today.ToString("yyyy-MM-dd 00:00:00")).AddDays(1); |
| | | |
| | | levelInput.Text = null; |
| | | startTimeInput.SelectedDateTime = start; |
| | | endTimeInput.SelectedDateTime = end; |
| | | } |
| | | |
| | | public DataTable GetDataTable(bool usePage, long page, long size, out long totalPage) |
| | | { |
| | | totalPage = 0; |
| | | var files = GetOldDbFiles(PathHelper.LogViewerDir, new DateTime?[] { startTimeInput.SelectedDateTime, |
| | | endTimeInput.SelectedDateTime }); |
| | | if (files.Length <= 0) |
| | | { |
| | | return new DataTable(); |
| | | } |
| | | |
| | | using (Sqlite3Helper helper = new Sqlite3Helper(files[0])) |
| | | { |
| | | for (int i = 0; i < files.Length; i++) |
| | | { |
| | | var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'"; |
| | | helper.Execute(attach); |
| | | } |
| | | |
| | | string sql = string.Join(" UNION ALL ", GetSqls(files)); |
| | | |
| | | string sortName = "time", sortDirection = "DESC"; |
| | | |
| | | string baseSql = $"SELECT level,time,source,text FROM ({sql}) as _temp"; |
| | | |
| | | string countSql = $"SELECT COUNT(1) FROM ({sql}) as __temp"; |
| | | |
| | | long total = (long)helper.SelectOne(countSql); |
| | | totalPage = (total / pageSize) + ((total % pageSize) > 0 ? 1 : 0); |
| | | |
| | | if (usePage) |
| | | { |
| | | sql = $"{baseSql} ORDER BY {sortName} {sortDirection} LIMIT {size} OFFSET {(page - 1) * size} "; |
| | | } |
| | | else |
| | | { |
| | | sql = $"{baseSql} ORDER BY {sortName} {sortDirection}"; |
| | | } |
| | | |
| | | DataTable dt = helper.GetDataTable(sql); |
| | | return dt; |
| | | } |
| | | } |
| | | |
| | | private void Filtrate(long page, long size) |
| | | { |
| | | try |
| | | { |
| | | //var files = GetOldDbFiles(PathHelper.LogViewerDir, new DateTime?[] { startTimeInput.SelectedDateTime, |
| | | //endTimeInput.SelectedDateTime }); |
| | | //if (files.Length <= 0) |
| | | //{ |
| | | // table.ItemsSource = new DataTable().DefaultView; |
| | | // return; |
| | | //} |
| | | |
| | | //using (Sqlite3Helper helper = new Sqlite3Helper(files[0])) |
| | | //{ |
| | | // for (int i = 0; i < files.Length; i++) |
| | | // { |
| | | // var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'"; |
| | | // helper.Execute(attach); |
| | | // } |
| | | |
| | | // string sql = string.Join(" UNION ALL ", GetSqls(files)); |
| | | |
| | | // string sortName = "time", sortDirection = "DESC"; |
| | | |
| | | // string baseSql = $"SELECT level,time,source,text FROM ({sql}) as _temp"; |
| | | |
| | | // string countSql = $"SELECT COUNT(1) FROM ({sql}) as __temp"; |
| | | |
| | | // long total = (long)helper.SelectOne(countSql); |
| | | // long pages = (total/pageSize)+ ((total % pageSize) > 0 ? 1 : 0); |
| | | |
| | | // sql = $"{baseSql} ORDER BY {sortName} {sortDirection} LIMIT {size} OFFSET {(page - 1) * size} "; |
| | | |
| | | // DataTable dt = helper.GetDataTable(sql); |
| | | // table.ItemsSource = dt.DefaultView; |
| | | |
| | | // MaxPage = pages; |
| | | //} |
| | | table.ItemsSource = GetDataTable(true, page, size, out long totalPage).DefaultView; |
| | | MaxPage = totalPage; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Select Log Error.", ex); |
| | | MsgBoxHelper2.Error($"查询失败: {ex.Message}"); |
| | | } |
| | | } |
| | | |
| | | public static string[] GetOldDbFiles(string folderPath, DateTime?[] range) |
| | | { |
| | | if (!Directory.Exists(folderPath)) |
| | | { |
| | | return new string[0]; |
| | | } |
| | | // 获取所有.db文件并筛选出7天前更新的文件 |
| | | var oldFiles = Directory.GetFiles(folderPath, "*.db"); |
| | | try |
| | | { |
| | | oldFiles = oldFiles.Where(file => |
| | | { |
| | | var lastWriteTime = File.GetLastWriteTime(file); |
| | | bool flag = true; |
| | | if (range[0] != null) flag &= lastWriteTime >= range[0]; |
| | | //if (range[1] != null) flag &= lastWriteTime <= range[1]; |
| | | return flag; |
| | | }).OrderByDescending(f => f).ToArray(); |
| | | |
| | | return oldFiles; |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | return new string[0]; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// |
| | | /// </summary> |
| | | /// <param name="files"></param> |
| | | /// <returns></returns> |
| | | private string[] GetSqls(string[] files) |
| | | { |
| | | string[] sqls = new string[files.Length]; |
| | | for (int i = 0; i < files.Length; i++) |
| | | { |
| | | string sql = $"SELECT level,time,source,text FROM db{i}.log"; |
| | | List<string> datas = new List<string>(); |
| | | if (!string.IsNullOrEmpty(levelInput.Text)) |
| | | { |
| | | datas.Add($"level='{levelInput.Text}'"); |
| | | } |
| | | if (!string.IsNullOrEmpty(startTimeInput.Text)) |
| | | { |
| | | datas.Add($"time>='{startTimeInput.Text}'"); |
| | | } |
| | | if (!string.IsNullOrEmpty(endTimeInput.Text)) |
| | | { |
| | | datas.Add($"time<'{endTimeInput.Text}'"); |
| | | } |
| | | if (!string.IsNullOrEmpty(sourceInput.Text)) |
| | | { |
| | | datas.Add($"source='{sourceInput.Text}'"); |
| | | } |
| | | |
| | | string condition = string.Join(" AND ", datas); |
| | | condition = string.IsNullOrEmpty(condition) ? string.Empty : $" WHERE {condition}"; |
| | | sql = $"{sql}{condition}"; |
| | | sqls[i] = sql; |
| | | } |
| | | return sqls; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 刷新100条数据 |
| | | /// </summary> |
| | | private void Refresh100items() |
| | | { |
| | | autoRfhTimer.Enabled = false; |
| | | Task.Run(() => |
| | | { |
| | | try |
| | | { |
| | | string todayLogFile = Path.Combine(PathHelper.LogViewerDir, $"log_{DateTime.Today:yyyy-MM-dd}.db"); |
| | | if (!File.Exists(todayLogFile)) return; |
| | | string str = "select level,time,source,text from log order by time desc limit 100"; |
| | | using (SqliteHelper sql = new SqliteHelper(todayLogFile)) |
| | | { |
| | | var dt = sql.GetDataTable(str); |
| | | this.Dispatcher.BeginInvoke((Action)delegate () |
| | | { |
| | | table.ItemsSource = dt.DefaultView; |
| | | MaxPage = 1; |
| | | }); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Log Auto Refresh Failed.", ex); |
| | | this.Error($"自动刷新失败!{ex.Message}"); |
| | | } |
| | | finally |
| | | { |
| | | this.Dispatcher.BeginInvoke((Action)delegate () |
| | | { |
| | | autoRfhTimer.Enabled = AutoRfh; |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System.Reflection; |
| | | using System.Resources; |
| | | using System.Runtime.CompilerServices; |
| | | using System.Runtime.InteropServices; |
| | | using System.Windows; |
| | | |
| | | // 有关程序集的一般信息由以下 |
| | | // 控制。更改这些特性值可修改 |
| | | // 与程序集关联的信息。 |
| | | [assembly: AssemblyTitle("HMI.LogViewer")] |
| | | [assembly: AssemblyDescription("")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("")] |
| | | [assembly: AssemblyProduct("HMI.LogViewer")] |
| | | [assembly: AssemblyCopyright("Copyright © 2025")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // 将 ComVisible 设置为 false 会使此程序集中的类型 |
| | | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 |
| | | //请将此类型的 ComVisible 特性设置为 true。 |
| | | [assembly: ComVisible(false)] |
| | | |
| | | //若要开始生成可本地化的应用程序,请设置 |
| | | //.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture> |
| | | //在 <PropertyGroup> 中。例如,如果你使用的是美国英语。 |
| | | //使用的是美国英语,请将 <UICulture> 设置为 en-US。 然后取消 |
| | | //对以下 NeutralResourceLanguage 特性的注释。 更新 |
| | | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 |
| | | |
| | | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] |
| | | |
| | | |
| | | [assembly:ThemeInfo( |
| | | ResourceDictionaryLocation.None, //主题特定资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //或应用程序资源字典中找到时使用) |
| | | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //、应用程序或任何主题专用资源字典中找到时使用) |
| | | )] |
| | | |
| | | |
| | | // 程序集的版本信息由下列四个值组成: |
| | | // |
| | | // 主版本 |
| | | // 次版本 |
| | | // 生成号 |
| | | // 修订号 |
| | | // |
| | | [assembly: AssemblyVersion("1.0.0.0")] |
| | | [assembly: AssemblyFileVersion("1.0.0.5")] |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // 此代码由工具生成。 |
| | | // 运行时版本: 4.0.30319.42000 |
| | | // |
| | | // 对此文件的更改可能导致不正确的行为,如果 |
| | | // 重新生成代码,则所做更改将丢失。 |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.LogViewer.Properties { |
| | | |
| | | |
| | | /// <summary> |
| | | /// 强类型资源类,用于查找本地化字符串等。 |
| | | /// </summary> |
| | | // 此类是由 StronglyTypedResourceBuilder |
| | | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 |
| | | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen |
| | | // (以 /str 作为命令选项),或重新生成 VS 项目。 |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] |
| | | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | internal class Resources { |
| | | |
| | | private static global::System.Resources.ResourceManager resourceMan; |
| | | |
| | | private static global::System.Globalization.CultureInfo resourceCulture; |
| | | |
| | | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
| | | internal Resources() { |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 返回此类使用的缓存 ResourceManager 实例。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Resources.ResourceManager ResourceManager { |
| | | get { |
| | | if ((resourceMan == null)) { |
| | | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HMI.LogViewer.Properties.Resources", typeof(Resources).Assembly); |
| | | resourceMan = temp; |
| | | } |
| | | return resourceMan; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 重写当前线程的 CurrentUICulture 属性,对 |
| | | /// 使用此强类型资源类的所有资源查找执行重写。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Globalization.CultureInfo Culture { |
| | | get { |
| | | return resourceCulture; |
| | | } |
| | | set { |
| | | resourceCulture = value; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <root> |
| | | <!-- |
| | | Microsoft ResX Schema |
| | | |
| | | Version 2.0 |
| | | |
| | | The primary goals of this format is to allow a simple XML format |
| | | that is mostly human readable. The generation and parsing of the |
| | | various data types are done through the TypeConverter classes |
| | | associated with the data types. |
| | | |
| | | Example: |
| | | |
| | | ... ado.net/XML headers & schema ... |
| | | <resheader name="resmimetype">text/microsoft-resx</resheader> |
| | | <resheader name="version">2.0</resheader> |
| | | <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
| | | <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
| | | <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
| | | <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
| | | <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
| | | <value>[base64 mime encoded serialized .NET Framework object]</value> |
| | | </data> |
| | | <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
| | | <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
| | | <comment>This is a comment</comment> |
| | | </data> |
| | | |
| | | There are any number of "resheader" rows that contain simple |
| | | name/value pairs. |
| | | |
| | | Each data row contains a name, and value. The row also contains a |
| | | type or mimetype. Type corresponds to a .NET class that support |
| | | text/value conversion through the TypeConverter architecture. |
| | | Classes that don't support this are serialized and stored with the |
| | | mimetype set. |
| | | |
| | | The mimetype is used for serialized objects, and tells the |
| | | ResXResourceReader how to depersist the object. This is currently not |
| | | extensible. For a given mimetype the value must be set accordingly: |
| | | |
| | | Note - application/x-microsoft.net.object.binary.base64 is the format |
| | | that the ResXResourceWriter will generate, however the reader can |
| | | read any of the formats listed below. |
| | | |
| | | mimetype: application/x-microsoft.net.object.binary.base64 |
| | | value : The object must be serialized with |
| | | : System.Serialization.Formatters.Binary.BinaryFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.soap.base64 |
| | | value : The object must be serialized with |
| | | : System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.bytearray.base64 |
| | | value : The object must be serialized into a byte array |
| | | : using a System.ComponentModel.TypeConverter |
| | | : and then encoded with base64 encoding. |
| | | --> |
| | | <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
| | | <xsd:element name="root" msdata:IsDataSet="true"> |
| | | <xsd:complexType> |
| | | <xsd:choice maxOccurs="unbounded"> |
| | | <xsd:element name="metadata"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | <xsd:attribute name="type" type="xsd:string" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="assembly"> |
| | | <xsd:complexType> |
| | | <xsd:attribute name="alias" type="xsd:string" /> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="data"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> |
| | | <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="resheader"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" use="required" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:choice> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:schema> |
| | | <resheader name="resmimetype"> |
| | | <value>text/microsoft-resx</value> |
| | | </resheader> |
| | | <resheader name="version"> |
| | | <value>2.0</value> |
| | | </resheader> |
| | | <resheader name="reader"> |
| | | <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | <resheader name="writer"> |
| | | <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | </root> |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // This code was generated by a tool. |
| | | // Runtime Version:4.0.30319.42000 |
| | | // |
| | | // Changes to this file may cause incorrect behavior and will be lost if |
| | | // the code is regenerated. |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.LogViewer.Properties |
| | | { |
| | | |
| | | |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] |
| | | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase |
| | | { |
| | | |
| | | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); |
| | | |
| | | public static Settings Default |
| | | { |
| | | get |
| | | { |
| | | return defaultInstance; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version='1.0' encoding='utf-8'?> |
| | | <SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> |
| | | <Profiles> |
| | | <Profile Name="(Default)" /> |
| | | </Profiles> |
| | | <Settings /> |
| | | </SettingsFile> |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <packages> |
| | | <package id="log4net" version="3.0.4" targetFramework="net472" /> |
| | | <package id="Panuon.WPF" version="1.1.3" targetFramework="net472" /> |
| | | <package id="Panuon.WPF.UI" version="1.3.0.2" targetFramework="net472" /> |
| | | </packages> |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | |
| | | namespace HMI.MenuManage.Entity |
| | | { |
| | | public class IconHolder |
| | | { |
| | | public List<IconMeta> glyphs { get; set; } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | |
| | | namespace HMI.MenuManage.Entity |
| | | { |
| | | public class IconMeta |
| | | { |
| | | public string unicode { get; set; } |
| | | |
| | | public string Code |
| | | { |
| | | get |
| | | { |
| | | int temp = Convert.ToInt32(unicode, 16); |
| | | return char.ConvertFromUtf32(temp); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | using HMI.Core.Entity; |
| | | |
| | | namespace HMI.MenuManage.Entity |
| | | { |
| | | public class MenuData : NotifyBase |
| | | { |
| | | public string Id { get; set; } |
| | | public string Code { get; set; } |
| | | public string Name { get; set; } |
| | | public string IconPath { get; set; } |
| | | public string Icon { get; set; } |
| | | public string Level { get; set; } |
| | | public string Type { get; set; } |
| | | public string ParentCode { get; set; } |
| | | public string ParentName { get; set; } |
| | | public string DllPath { get; set; } |
| | | public string ClassName { get; set; } |
| | | |
| | | public double? Sort { get; set; } |
| | | |
| | | private ObservableCollection<MenuData> _Children = new ObservableCollection<MenuData>(); |
| | | |
| | | public ObservableCollection<MenuData> Children |
| | | { |
| | | get => _Children; |
| | | set |
| | | { |
| | | Set(ref _Children, value); |
| | | } |
| | | } |
| | | |
| | | private bool _Selected; |
| | | |
| | | public bool Selected |
| | | { |
| | | get => _Selected; |
| | | set |
| | | { |
| | | Set(ref _Selected, value); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Text; |
| | | using System.Threading.Tasks; |
| | | using HMI.Core.Entity; |
| | | |
| | | namespace HMI.MenuManage.Entity |
| | | { |
| | | public class RoleData : NotifyBase |
| | | { |
| | | public string Id { get; set; } |
| | | public string Code { get; set; } |
| | | public string Name { get; set; } |
| | | |
| | | private bool _Selected; |
| | | public bool Selected |
| | | { |
| | | get => _Selected; |
| | | set |
| | | { |
| | | if (_Selected != value) |
| | | { |
| | | Set(ref _Selected, value); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.ObjectModel; |
| | | using System.Windows.Input; |
| | | using HMI.Core.Entity; |
| | | |
| | | namespace HMI.MenuManage.Entity |
| | | { |
| | | public class RoleMenuRel : NotifyBase |
| | | { |
| | | private event EventHandler<bool> SelectedChanged; |
| | | |
| | | public string Id { get; set; } |
| | | public string RoleCode { get; set; } |
| | | public string RoleName { get; set; } |
| | | public string MenuCode { get; set; } |
| | | public string ParentCode { get; set; } |
| | | public string MenuName { get; set; } |
| | | public string MenuIcon { get; set; } |
| | | |
| | | private bool _Selected; |
| | | public bool Selected |
| | | { |
| | | get => _Selected; |
| | | set |
| | | { |
| | | if (_Selected != value) |
| | | { |
| | | Set(ref _Selected, value); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public ICommand CheckCommand |
| | | { |
| | | get => new RelayCommand<bool>(ExcuteCheckCommand); |
| | | } |
| | | |
| | | private void ExcuteCheckCommand(bool flag) |
| | | { |
| | | if (flag && Parent != null) Parent.Selected = flag; |
| | | foreach (var item in Children) |
| | | { |
| | | item.Selected = flag; |
| | | } |
| | | } |
| | | |
| | | |
| | | public RoleMenuRel Parent; |
| | | |
| | | private ObservableCollection<RoleMenuRel> _Children = new ObservableCollection<RoleMenuRel>(); |
| | | |
| | | public ObservableCollection<RoleMenuRel> Children |
| | | { |
| | | get { return _Children; } |
| | | set { Set(ref _Children, value); } |
| | | } |
| | | |
| | | public void AddChild(RoleMenuRel child) |
| | | { |
| | | child.Parent = this; |
| | | Children.Add(child); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core; |
| | | using HMI.Core.Entity; |
| | | |
| | | namespace HMI.MenuManage.Entity |
| | | { |
| | | public class UserData : NotifyBase |
| | | { |
| | | public string Id { get; set; } |
| | | |
| | | [ExcelColumn(name: "工号")] |
| | | public string Code { get; set; } |
| | | |
| | | [ExcelColumn(name: "账号")] |
| | | public string Username { get; set; } |
| | | |
| | | [ExcelColumn(name: "名称")] |
| | | public string Name { get; set; } |
| | | |
| | | [ExcelColumn(name: "角色编码")] |
| | | public string RoleCodes { get; set; } |
| | | |
| | | [ExcelColumn(name: "密码", ignoreExport: true)] |
| | | public string Password { get; set; } |
| | | |
| | | public string RoleCode { get; set; } |
| | | |
| | | private bool _Selected; |
| | | public bool Selected |
| | | { |
| | | get => _Selected; |
| | | set |
| | | { |
| | | if (_Selected != value) |
| | | { |
| | | Set(ref _Selected, value); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.ObjectModel; |
| | | using System.Windows.Input; |
| | | using HMI.Core.Entity; |
| | | |
| | | namespace HMI.MenuManage.Entity |
| | | { |
| | | public class UserRoleRel : NotifyBase |
| | | { |
| | | public string Id { get; set; } |
| | | public string Username { get; set; } |
| | | public string RealName { get; set; } |
| | | public string RoleCode { get; set; } |
| | | public string RoleName { get; set; } |
| | | |
| | | private bool _Selected; |
| | | public bool Selected |
| | | { |
| | | get => _Selected; |
| | | set |
| | | { |
| | | Set(ref _Selected, value); |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
| | | <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
| | | <PropertyGroup> |
| | | <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
| | | <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
| | | <ProjectGuid>{DE4205C6-5923-46FD-A4B9-65169BF61C75}</ProjectGuid> |
| | | <OutputType>library</OutputType> |
| | | <RootNamespace>HMI.MenuManage</RootNamespace> |
| | | <AssemblyName>HMI.MenuManage</AssemblyName> |
| | | <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> |
| | | <FileAlignment>512</FileAlignment> |
| | | <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> |
| | | <WarningLevel>4</WarningLevel> |
| | | <Deterministic>true</Deterministic> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <DebugType>full</DebugType> |
| | | <Optimize>false</Optimize> |
| | | <OutputPath>bin\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
| | | <DebugType>pdbonly</DebugType> |
| | | <Optimize>true</Optimize> |
| | | <OutputPath>bin\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | <WarningLevel>4</WarningLevel> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> |
| | | <DebugSymbols>true</DebugSymbols> |
| | | <OutputPath>bin\x64\Debug\</OutputPath> |
| | | <DefineConstants>DEBUG;TRACE</DefineConstants> |
| | | <DebugType>full</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> |
| | | <OutputPath>bin\x64\Release\</OutputPath> |
| | | <DefineConstants>TRACE</DefineConstants> |
| | | <Optimize>true</Optimize> |
| | | <DebugType>pdbonly</DebugType> |
| | | <PlatformTarget>x64</PlatformTarget> |
| | | <LangVersion>7.3</LangVersion> |
| | | <ErrorReport>prompt</ErrorReport> |
| | | </PropertyGroup> |
| | | <ItemGroup> |
| | | <Reference Include="DatabaseHelper"> |
| | | <HintPath>..\Libs\DatabaseHelper.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Panuon.WPF, Version=1.1.2.2, Culture=neutral, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Panuon.WPF.1.1.3\lib\net472\Panuon.WPF.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="Panuon.WPF.UI, Version=1.3.0.2, Culture=neutral, processorArchitecture=MSIL"> |
| | | <HintPath>..\WPFProject\packages\Panuon.WPF.UI.1.3.0.2\lib\net472\Panuon.WPF.UI.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="System" /> |
| | | <Reference Include="System.Configuration" /> |
| | | <Reference Include="System.Data" /> |
| | | <Reference Include="System.Data.SQLite"> |
| | | <HintPath>..\Libs\System.Data.SQLite.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="System.Data.SQLite.EF6"> |
| | | <HintPath>..\Libs\System.Data.SQLite.EF6.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="System.Data.SQLite.Linq"> |
| | | <HintPath>..\Libs\System.Data.SQLite.Linq.dll</HintPath> |
| | | </Reference> |
| | | <Reference Include="System.Web" /> |
| | | <Reference Include="System.Windows.Forms" /> |
| | | <Reference Include="System.Xml" /> |
| | | <Reference Include="Microsoft.CSharp" /> |
| | | <Reference Include="System.Core" /> |
| | | <Reference Include="System.Xml.Linq" /> |
| | | <Reference Include="System.Data.DataSetExtensions" /> |
| | | <Reference Include="System.Net.Http" /> |
| | | <Reference Include="System.Xaml"> |
| | | <RequiredTargetFramework>4.0</RequiredTargetFramework> |
| | | </Reference> |
| | | <Reference Include="WindowsBase" /> |
| | | <Reference Include="PresentationCore" /> |
| | | <Reference Include="PresentationFramework" /> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="Entity\IconHolder.cs" /> |
| | | <Compile Include="Entity\IconMeta.cs" /> |
| | | <Compile Include="Entity\MenuData.cs" /> |
| | | <Compile Include="Entity\RoleData.cs" /> |
| | | <Compile Include="Entity\UserRoleRel.cs" /> |
| | | <Compile Include="Entity\RoleMenuRel.cs" /> |
| | | <Compile Include="Entity\UserData.cs" /> |
| | | <Compile Include="Modal\IconSelector.xaml.cs"> |
| | | <DependentUpon>IconSelector.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Modal\RoleModal.xaml.cs"> |
| | | <DependentUpon>RoleModal.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Model\RoleManageModel.cs" /> |
| | | <Compile Include="RoleManage.xaml.cs"> |
| | | <DependentUpon>RoleManage.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Modal\UserModal.xaml.cs"> |
| | | <DependentUpon>UserModal.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Model\UserManageModel.cs" /> |
| | | <Compile Include="UserManage.xaml.cs"> |
| | | <DependentUpon>UserManage.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Util\UserRoleRelHelper.cs" /> |
| | | <Compile Include="Util\RoleHelper.cs" /> |
| | | <Compile Include="Util\RoleMenuHelper.cs" /> |
| | | <Page Include="Modal\IconSelector.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | <Page Include="Modal\RoleModal.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | <Page Include="RoleManage.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | <Page Include="MenuManage.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | <Compile Include="MenuManage.xaml.cs"> |
| | | <DependentUpon>MenuManage.xaml</DependentUpon> |
| | | <SubType>Code</SubType> |
| | | </Compile> |
| | | <Page Include="Modal\UserModal.xaml"> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | <SubType>Designer</SubType> |
| | | </Page> |
| | | <Page Include="Modal\MenuModal.xaml"> |
| | | <SubType>Designer</SubType> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | </Page> |
| | | <Page Include="UserManage.xaml"> |
| | | <SubType>Designer</SubType> |
| | | <Generator>MSBuild:Compile</Generator> |
| | | </Page> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <Compile Include="Modal\MenuModal.xaml.cs"> |
| | | <DependentUpon>MenuModal.xaml</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Model\MenuManageModel.cs" /> |
| | | <Compile Include="Properties\AssemblyInfo.cs"> |
| | | <SubType>Code</SubType> |
| | | </Compile> |
| | | <Compile Include="Properties\Resources.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DesignTime>True</DesignTime> |
| | | <DependentUpon>Resources.resx</DependentUpon> |
| | | </Compile> |
| | | <Compile Include="Properties\Settings.Designer.cs"> |
| | | <AutoGen>True</AutoGen> |
| | | <DependentUpon>Settings.settings</DependentUpon> |
| | | <DesignTimeSharedInput>True</DesignTimeSharedInput> |
| | | </Compile> |
| | | <EmbeddedResource Include="Properties\Resources.resx"> |
| | | <Generator>ResXFileCodeGenerator</Generator> |
| | | <LastGenOutput>Resources.Designer.cs</LastGenOutput> |
| | | </EmbeddedResource> |
| | | <None Include="packages.config" /> |
| | | <None Include="Properties\Settings.settings"> |
| | | <Generator>SettingsSingleFileGenerator</Generator> |
| | | <LastGenOutput>Settings.Designer.cs</LastGenOutput> |
| | | </None> |
| | | </ItemGroup> |
| | | <ItemGroup> |
| | | <ProjectReference Include="..\HMI.Base\HMI.Base.csproj"> |
| | | <Project>{2a6f9720-a890-4036-9d73-41a0dae28341}</Project> |
| | | <Name>HMI.Base</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.Core\HMI.Core.csproj"> |
| | | <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project> |
| | | <Name>HMI.Core</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.UI\HMI.UI.csproj"> |
| | | <Project>{f17de6b5-af1d-48c6-94a0-6b763a6546a6}</Project> |
| | | <Name>HMI.UI</Name> |
| | | </ProjectReference> |
| | | <ProjectReference Include="..\HMI.Util\HMI.Util.csproj"> |
| | | <Project>{afea100a-9223-448d-ab10-3015d93dd98c}</Project> |
| | | <Name>HMI.Util</Name> |
| | | </ProjectReference> |
| | | </ItemGroup> |
| | | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| | | <PropertyGroup> |
| | | <PostBuildEvent> |
| | | </PostBuildEvent> |
| | | </PropertyGroup> |
| | | </Project> |
| New file |
| | |
| | | <hb:PluginControl x:Class="HMI.MenuManage.MenuManage" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:local="clr-namespace:HMI.MenuManage" |
| | | xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" |
| | | xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | xmlns:dp="clr-namespace:HMI.UI;assembly=HMI.UI" |
| | | xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core" |
| | | mc:Ignorable="d" |
| | | Padding="10" |
| | | Loaded="DataGrid_Loaded" |
| | | Background="{DynamicResource WorkspaceBackground}" |
| | | d:DesignHeight="450" d:DesignWidth="800"> |
| | | <UserControl.Resources> |
| | | |
| | | </UserControl.Resources> |
| | | <Grid> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition Width="8*"/> |
| | | <ColumnDefinition Width="2*"/> |
| | | </Grid.ColumnDefinitions> |
| | | |
| | | <Border Style="{StaticResource CardBoarder}" Margin="0,0,2,0"> |
| | | <StackPanel> |
| | | <DockPanel Margin="0,0,0,10"> |
| | | <Button DockPanel.Dock="Right" Style="{StaticResource DelBtn}" |
| | | x:Name="deleteBatchBtn" Click="Button_Click"/> |
| | | <Button DockPanel.Dock="Right" Style="{StaticResource RfhBtn}" |
| | | Margin="0,0,10,0" x:Name="refreshBtn" Click="Button_Click"/> |
| | | <Button DockPanel.Dock="Right" Style="{StaticResource AddBtn}" |
| | | Margin="0,0,10,0" x:Name="addBtn" Click="Button_Click"/> |
| | | <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" |
| | | VerticalAlignment="Center" HorizontalAlignment="Left" |
| | | Text="{DynamicResource HMI.Menu.Title}"/> |
| | | </DockPanel> |
| | | |
| | | <DataGrid Grid.Column="1" x:Name="table" Style="{StaticResource DefaultDataGrid}" |
| | | ItemsSource="{Binding DataSource}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | <DataGridTemplateColumn Header="#" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <TextBlock Text="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | <DataGridTextColumn Binding="{Binding Code}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Menu.Code}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | |
| | | <DataGridTextColumn Binding="{Binding Name}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Menu.Name}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Binding="{Binding Level}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Menu.Level}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Binding="{Binding Type}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Menu.Type}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <!--<DataGridTextColumn Binding="{Binding ParentCode}" Header="{DynamicResource HMI.Menu.ParentCode}"/>--> |
| | | |
| | | <DataGridTextColumn Binding="{Binding ParentName}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Menu.ParentName}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding DllPath}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Menu.DllPath}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding ClassName}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Menu.ClassName}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="50" Binding="{Binding Sort}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Menu.Sort}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTemplateColumn Width="110" IsReadOnly="True" CanUserResize="False"> |
| | | <DataGridTemplateColumn.Header> |
| | | <TextBlock Text="{DynamicResource App.Opt}"/> |
| | | </DataGridTemplateColumn.Header> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <StackPanel Orientation="Horizontal"> |
| | | <Button x:Name="editBtn" Style="{StaticResource EditLink}" |
| | | Click="Button_Click" Margin="0,0,10,0" |
| | | DataContext="{Binding }"/> |
| | | <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" |
| | | DataContext="{Binding }" |
| | | Click="Button_Click"/> |
| | | </StackPanel> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | </StackPanel> |
| | | </Border> |
| | | |
| | | <Border Grid.Column="1" Style="{StaticResource CardBoarder}" Margin="2,0,0,0"> |
| | | <StackPanel> |
| | | <DockPanel Margin="0,0,0,10" > |
| | | <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" |
| | | VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource HMI.Menu.TreeTitle}"/> |
| | | </DockPanel> |
| | | |
| | | <TreeView x:Name="treeViewer" Style="{StaticResource DefaultTreeView}" ItemsSource="{Binding TreeDataSource}" |
| | | SelectedItemChanged="treeViewer_SelectedItemChanged"> |
| | | <TreeView.ItemTemplate> |
| | | <HierarchicalDataTemplate ItemsSource="{Binding Children}"> |
| | | <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> |
| | | <Image Width="{StaticResource ContentFontSize}" Height="{StaticResource ContentFontSize}" Margin="5" |
| | | Source="{Binding IconPath,Converter={StaticResource path2Image},Mode=OneWay}"/> |
| | | <TextBlock VerticalAlignment="Center" FontSize="{StaticResource ContentFontSize}" Text="{Binding Name}"/> |
| | | </StackPanel> |
| | | </HierarchicalDataTemplate> |
| | | </TreeView.ItemTemplate> |
| | | </TreeView> |
| | | </StackPanel> |
| | | </Border> |
| | | </Grid> |
| | | </hb:PluginControl> |
| New file |
| | |
| | | using HMI.Base; |
| | | using HMI.Core.Util; |
| | | using HMI.MenuManage.Entity; |
| | | using HMI.MenuManage.Model; |
| | | using HMI.UI.Util; |
| | | using log4net; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.ComponentModel; |
| | | using System.Data; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.MenuManage |
| | | { |
| | | /// <summary> |
| | | /// UserControl1.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class MenuManage : PluginControl |
| | | { |
| | | #region 属性 |
| | | public SqliteHelper sqliteHelper; |
| | | |
| | | public int DataSourceCount { get => DataSource?.Count ?? 0; } |
| | | |
| | | private ObservableCollection<MenuData> _DataSource; |
| | | |
| | | public ObservableCollection<MenuData> DataSource |
| | | { |
| | | get => _DataSource; |
| | | set |
| | | { |
| | | Set(ref _DataSource, value); |
| | | Set(nameof(DataSourceCount)); |
| | | } |
| | | } |
| | | |
| | | private ObservableCollection<MenuData> _TreeDataSource; |
| | | |
| | | public ObservableCollection<MenuData> TreeDataSource |
| | | { |
| | | get => _TreeDataSource; |
| | | set { Set(ref _TreeDataSource, value); } |
| | | } |
| | | #endregion |
| | | |
| | | public static ILog PluginLog { get; private set; } |
| | | |
| | | public MenuManage() |
| | | { |
| | | this.DataContext = this; |
| | | InitializeComponent(); |
| | | if (!File.Exists(PathHelper.DatabasePath)) |
| | | { |
| | | SqliteHelper.CreateDatabase(PathHelper.DatabasePath); |
| | | } |
| | | sqliteHelper = new SqliteHelper(PathHelper.DatabasePath); |
| | | sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS menu ( |
| | | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| | | code TEXT NOT NULL, |
| | | name TEXT NOT NULL, |
| | | icon TEXT, |
| | | iconPath TEXT, |
| | | level TEXT, |
| | | type TEXT, |
| | | parentCode TEXT, |
| | | dllPath TEXT, |
| | | className TEXT, |
| | | sort REAL, |
| | | UNIQUE(code) |
| | | );"); |
| | | } |
| | | |
| | | public override void SetLogger(ILog logger) |
| | | { |
| | | base.SetLogger(logger); |
| | | PluginLog = logger; |
| | | } |
| | | |
| | | public void LoadData() |
| | | { |
| | | ObservableCollection<MenuData> data = new ObservableCollection<MenuData>(); |
| | | ObservableCollection<MenuData> treeData = new ObservableCollection<MenuData>(); |
| | | DataTable dt = sqliteHelper.GetDataTable("SELECT m1.*,m2.name AS parentName FROM menu AS m1 LEFT JOIN menu AS m2 ON m1.parentCode = m2.code Order By IIF(m1.level=1,m1.sort,m2.sort),IIF(m1.level=1,0,m1.sort),m1.id;"); |
| | | Dictionary<string, MenuData> cache = new Dictionary<string, MenuData>(); |
| | | if (dt != null) |
| | | { |
| | | foreach (DataRow dr in dt.Rows) |
| | | { |
| | | var menu = new MenuData |
| | | { |
| | | Id = dr["id"].ToString(), |
| | | Code = dr["code"].ToString(), |
| | | Name = dr["name"].ToString(), |
| | | Icon = dr["icon"].ToString(), |
| | | Level = dr["level"].ToString(), |
| | | Type = dr["type"].ToString(), |
| | | ParentCode = dr["parentCode"].ToString(), |
| | | ParentName = dr["parentName"].ToString(), |
| | | DllPath = dr["dllPath"].ToString(), |
| | | ClassName = dr["className"].ToString(), |
| | | Sort = dr["sort"] == DBNull.Value ? 0 : double.Parse(dr["sort"].ToString()), |
| | | }; |
| | | data.Add(menu); |
| | | if (menu.Level == "1") |
| | | { |
| | | treeData.Add(menu); |
| | | } |
| | | cache.Add(menu.Code, menu); |
| | | } |
| | | |
| | | foreach (var menu in data) |
| | | { |
| | | if (!string.IsNullOrEmpty(menu.ParentCode) && menu.ParentCode != menu.Code) |
| | | { |
| | | cache[menu.ParentCode].Children.Add(menu); |
| | | } |
| | | } |
| | | } |
| | | |
| | | TreeDataSource = treeData; |
| | | DataSource = data; |
| | | } |
| | | |
| | | public void DeleteMenuById(string id) |
| | | { |
| | | string sql = $"DELETE FROM menu WHERE id = {id};"; |
| | | sqliteHelper.ExecuteSql(sql); |
| | | } |
| | | |
| | | public void DeleteMenuByIds(IEnumerable<string> ids) |
| | | { |
| | | List<string> sqls = ids.Select(id => $"DELETE FROM menu WHERE id = {id};") |
| | | .ToList(); |
| | | sqliteHelper.ExecuteBatchSql(sqls); |
| | | } |
| | | |
| | | private void Button_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | var btn = ((Button)sender); |
| | | if (btn.Name == "addBtn") |
| | | { |
| | | try |
| | | { |
| | | MenuModal window = new MenuModal |
| | | { |
| | | Title = $"{LangUtil.Get("HMI.Menu.Add")}", |
| | | sqliteHelper = sqliteHelper, |
| | | Owner = Window.GetWindow(this) |
| | | }; |
| | | if (true == window.Open(new MenuData())) |
| | | { |
| | | MenuData data = window.Data; |
| | | LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Add Menu Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("App.AddFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | else if (btn.Name == "refreshBtn") |
| | | { |
| | | try |
| | | { |
| | | LoadData(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Refresh Menu List Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("App.RefreshFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | else if (btn.Name == "deleteBatchBtn") |
| | | { |
| | | try |
| | | { |
| | | List<string> ids = DataSource.Where(x => x.Selected).Select(x => x.Id).ToList(); |
| | | if (ids == null || ids.Count <= 0) |
| | | { |
| | | ToastHelper.Warning(Window.GetWindow(this), LangUtil.Get("HMI.NoDeleteData")); |
| | | return; |
| | | } |
| | | |
| | | var res = MsgBoxHelper.WarnMessage(Window.GetWindow(this), |
| | | string.Format(LangUtil.Get("HMI.MakeSureDeletes"), ids.Count)); |
| | | if (res == MessageBoxResult.OK) |
| | | { |
| | | DeleteMenuByIds(ids); |
| | | LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Delete Menu Batch Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("App.DeleteFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | else if (btn.Name == "editBtn") |
| | | { |
| | | try |
| | | { |
| | | var data = (MenuData)btn.DataContext; |
| | | MenuModal window = new MenuModal |
| | | { |
| | | Title = $"{LangUtil.Get("HMI.Menu.Edit")}", |
| | | sqliteHelper = sqliteHelper, |
| | | Owner = Window.GetWindow(this) |
| | | }; |
| | | if (true == window.Open(data)) |
| | | { |
| | | var newData = window.Data; |
| | | LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Edit Menu Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("HMI.EditFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | else if (btn.Name == "delBtn") |
| | | { |
| | | try |
| | | { |
| | | var data = (MenuData)((Button)sender).DataContext; |
| | | var res = MsgBoxHelper.WarnMessage(Window.GetWindow(this), |
| | | string.Format(LangUtil.Get("HMI.MakeSureDeleteSingle"), data.Name)); |
| | | if (res == MessageBoxResult.OK) |
| | | { |
| | | DeleteMenuById(data.Id); |
| | | LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Delete Menu Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("HMI.DeleteFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | public override void DoClose(object sender, EventArgs e) |
| | | { |
| | | try |
| | | { |
| | | sqliteHelper?.Close(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Warn("Do Close Failed.", ex); |
| | | } |
| | | } |
| | | |
| | | private void DataGrid_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | LoadData(); |
| | | Loaded -= DataGrid_Loaded; |
| | | } |
| | | |
| | | private void treeViewer_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) |
| | | { |
| | | table.SelectedItem = e.NewValue; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <pu:WindowX x:Class="HMI.MenuManage.IconSelector" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:local="clr-namespace:HMI.MenuManage" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | mc:Ignorable="d" |
| | | Title="Form" |
| | | SizeToContent="Height" |
| | | Width="600" |
| | | Style="{StaticResource ModalWindow}" |
| | | WindowStartupLocation="CenterOwner" |
| | | Foreground="Black"> |
| | | <Window.Resources> |
| | | <ResourceDictionary> |
| | | <ResourceDictionary.MergedDictionaries> |
| | | <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/> |
| | | </ResourceDictionary.MergedDictionaries> |
| | | </ResourceDictionary> |
| | | </Window.Resources> |
| | | |
| | | <Grid> |
| | | <ListBox Margin="10" x:Name="list" |
| | | FontSize="50" |
| | | SelectedValue="{Binding Data}" |
| | | ScrollViewer.HorizontalScrollBarVisibility="Disabled"> |
| | | <ListBox.ItemsPanel> |
| | | <ItemsPanelTemplate> |
| | | <WrapPanel HorizontalAlignment="Center" Orientation="Horizontal" IsItemsHost="True"> |
| | | </WrapPanel> |
| | | </ItemsPanelTemplate> |
| | | </ListBox.ItemsPanel> |
| | | |
| | | <ListBox.ItemTemplate> |
| | | <DataTemplate> |
| | | <Label FontFamily="{StaticResource iconfont}" Content="{Binding }"/> |
| | | </DataTemplate> |
| | | </ListBox.ItemTemplate> |
| | | </ListBox> |
| | | </Grid> |
| | | |
| | | |
| | | </pu:WindowX> |
| New file |
| | |
| | | using HMI.MenuManage.Entity; |
| | | using Newtonsoft.Json; |
| | | using Panuon.WPF.UI; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.ComponentModel; |
| | | using System.Data; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using System.Reflection; |
| | | |
| | | namespace HMI.MenuManage |
| | | { |
| | | /// <summary> |
| | | /// ModalWindow.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class IconSelector : WindowX |
| | | { |
| | | |
| | | private static readonly List<IconMeta> icons; |
| | | |
| | | private string data; |
| | | public string Data |
| | | { |
| | | get => data; |
| | | set { Set(ref data, value); } |
| | | } |
| | | |
| | | static IconSelector() |
| | | { |
| | | try |
| | | { |
| | | Assembly assembly = Assembly.Load("HMI.UI"); |
| | | var serializer = new JsonSerializer(); |
| | | var sb = assembly.GetManifestResourceNames(); |
| | | using (Stream stream = assembly.GetManifestResourceStream("HMI.UI.Resources.Font.iconfont.json")) |
| | | using (StreamReader streamReader = new StreamReader(stream)) |
| | | using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) |
| | | { |
| | | IconHolder holder = serializer.Deserialize<IconHolder>(jsonTextReader); |
| | | icons = holder?.glyphs; |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | |
| | | } |
| | | } |
| | | |
| | | public IconSelector() |
| | | { |
| | | DataContext = this; |
| | | InitializeComponent(); |
| | | } |
| | | private bool _isProcessing; |
| | | |
| | | |
| | | protected override void OnClosing(CancelEventArgs e) |
| | | { |
| | | base.OnClosing(e); |
| | | } |
| | | |
| | | public bool? Open(string currentValue) |
| | | { |
| | | list.ItemsSource = icons?.Select(i => i.Code).ToList(); |
| | | Data = currentValue; |
| | | return this.ShowDialog(); |
| | | } |
| | | |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | <pu:WindowX x:Class="HMI.MenuManage.MenuModal" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:local="clr-namespace:HMI.MenuManage" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | mc:Ignorable="d" |
| | | Title="Form" |
| | | SizeToContent="Height" |
| | | Width="600" |
| | | Style="{StaticResource ModalWindow}" |
| | | WindowStartupLocation="CenterOwner" |
| | | Foreground="Black" |
| | | > |
| | | <Grid Margin="10"> |
| | | <StackPanel x:Name="formPanel"> |
| | | <pu:FormGroup x:Name="FgCode" Style="{StaticResource InputFormGroupStyle}" |
| | | dp:FormGroupHelper.ValidateMethod="ValidateCode" |
| | | Header="{DynamicResource HMI.Menu.Code}" |
| | | dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup"> |
| | | <TextBox x:Name="TbCode" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgName" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Menu.Name}" GroupName="MyFormGroup" |
| | | dp:FormGroupHelper.IsRequired="True" |
| | | dp:FormGroupHelper.ValidateMethod="ValidateName"> |
| | | <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgIcon" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Menu.Icon}" GroupName="MyFormGroup"> |
| | | <Grid> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition /> |
| | | <ColumnDefinition Width="30" /> |
| | | </Grid.ColumnDefinitions> |
| | | <TextBox x:Name="TbIcon" IsReadOnly="True" FontFamily="{StaticResource iconfont}" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | <Button Grid.Column="1" BorderThickness="0,1,1,1" Style="{StaticResource SearchButton}" |
| | | Click="IconSelect_Click"/> |
| | | </Grid> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgLevel" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Menu.Level}" |
| | | dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup"> |
| | | <ComboBox x:Name="TbLevel" Style="{StaticResource FormComboxStyle}" |
| | | SelectionChanged="TbLevel_SelectionChanged"> |
| | | <ComboBoxItem>1</ComboBoxItem> |
| | | <ComboBoxItem>2</ComboBoxItem> |
| | | </ComboBox> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgType" |
| | | Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource HMI.Menu.Type}" GroupName="MyFormGroup"> |
| | | <ComboBox x:Name="TbType" Style="{StaticResource FormComboxStyle}"> |
| | | <ComboBoxItem>Permission</ComboBoxItem> |
| | | <ComboBoxItem>WPF</ComboBoxItem> |
| | | <ComboBoxItem>WinForm</ComboBoxItem> |
| | | <ComboBoxItem>LV</ComboBoxItem> |
| | | </ComboBox> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgParentCode" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Menu.ParentName}" GroupName="MyFormGroup"> |
| | | <!--<Grid> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition /> |
| | | <ColumnDefinition Width="30" /> |
| | | </Grid.ColumnDefinitions> |
| | | <TextBox x:Name="TbParentCode" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | <Button Grid.Column="1" Style="{StaticResource SearchButton}"/> |
| | | </Grid>--> |
| | | <ComboBox x:Name="TbParentCode" Style="{StaticResource FormComboxStyle}" |
| | | DisplayMemberPath="Name" |
| | | SelectedValuePath="Code" |
| | | ItemsSource="{Binding ParentSource}" |
| | | ></ComboBox> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgDllPath" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Menu.DllPath}" GroupName="MyFormGroup"> |
| | | <Grid> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition /> |
| | | <ColumnDefinition Width="30" /> |
| | | </Grid.ColumnDefinitions> |
| | | <TextBox x:Name="TbDllPath" IsReadOnly="True" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | <Button Grid.Column="1" Click="DllSelect_Click" Style="{StaticResource SearchButton}"/> |
| | | </Grid> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgClassName" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Menu.ClassName}" GroupName="MyFormGroup"> |
| | | <TextBox x:Name="TbClassName" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgSort" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Menu.Sort}" GroupName="MyFormGroup"> |
| | | <pu:NumberInput x:Name="NiSort" Style="{StaticResource FormNumberInput}" /> |
| | | </pu:FormGroup> |
| | | </StackPanel> |
| | | </Grid> |
| | | </pu:WindowX> |
| New file |
| | |
| | | using HMI.Core.Annotation; |
| | | using HMI.Core.Util; |
| | | using HMI.MenuManage.Entity; |
| | | using HMI.UI.DpUtil; |
| | | using HMI.UI.Util; |
| | | using Microsoft.Win32; |
| | | using Panuon.WPF.UI; |
| | | using System; |
| | | using System.Collections.ObjectModel; |
| | | using System.ComponentModel; |
| | | using System.Data; |
| | | using System.Reflection; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.MenuManage |
| | | { |
| | | /// <summary> |
| | | /// ModalWindow.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class MenuModal : WindowX |
| | | { |
| | | public MenuModal() |
| | | { |
| | | DataContext = this; |
| | | InitializeComponent(); |
| | | } |
| | | |
| | | public MenuData Data { get; set; } |
| | | public SqliteHelper sqliteHelper { get; set; } |
| | | |
| | | private bool _isProcessing; |
| | | |
| | | private ObservableCollection<MenuData> parentSource; |
| | | |
| | | public ObservableCollection<MenuData> ParentSource |
| | | { |
| | | get { return parentSource; } |
| | | set { Set(ref parentSource, value); } |
| | | } |
| | | |
| | | protected override void OnClosing(CancelEventArgs e) |
| | | { |
| | | //Press Continue Button |
| | | if (DialogResult == true) |
| | | { |
| | | if (_isProcessing) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | var okButton = WindowXModalDialog.GetOKButton(this); |
| | | try |
| | | { |
| | | _isProcessing = true; |
| | | ButtonHelper.SetIsPending(okButton, true); |
| | | if (!ValidateForm()) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | |
| | | SaveOrUpdateData(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | MenuManage.PluginLog.Error("Modal Commit Error.", ex); |
| | | ToastHelper.Error(this, ex.Message); |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | finally |
| | | { |
| | | _isProcessing = false; |
| | | ButtonHelper.SetIsPending(okButton, false); |
| | | } |
| | | } |
| | | base.OnClosing(e); |
| | | } |
| | | |
| | | public bool ValidateCode(object value, out string err) |
| | | { |
| | | err = null; |
| | | string segment = string.IsNullOrEmpty(Data.Id) ? "" : $" AND id!='{Data.Id}'"; |
| | | string sql = $"SELECT 1 FROM menu WHERE code='{TbCode.Text}'{segment};"; |
| | | if (sqliteHelper.HasData(sql)) |
| | | { |
| | | err = this.lang("App.MustBeUnique", LangUtil.Get("HMI.Menu.Code")); ; |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | public bool ValidateName(object value, out string err) |
| | | { |
| | | err = null; |
| | | string segment = string.IsNullOrEmpty(Data.Id) ? "" : $" AND id!='{Data.Id}'"; |
| | | string sql = $"SELECT 1 FROM menu WHERE name='{TbName.Text}'{segment};"; |
| | | if (sqliteHelper.HasData(sql)) |
| | | { |
| | | err = string.Format(LangUtil.Get("App.MustBeUnique"), LangUtil.Get("HMI.Menu.Name")); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private bool ValidateForm() |
| | | { |
| | | //PreValidate(); |
| | | bool flag = FormGroupHelper.ValidateForm(formPanel); |
| | | if (flag) |
| | | { |
| | | Data.Code = TbCode.Text; |
| | | Data.Name = TbName.Text; |
| | | Data.Icon = TbIcon.Text; |
| | | Data.Level = TbLevel.Text; |
| | | Data.Type = TbType.Text; |
| | | Data.ParentCode = TbParentCode.SelectedValue?.ToString(); |
| | | Data.DllPath = TbDllPath.Text; |
| | | Data.ClassName = TbClassName.Text; |
| | | Data.Sort = NiSort.Value; |
| | | } |
| | | return flag; |
| | | } |
| | | |
| | | private void RefreshParentCode() |
| | | { |
| | | ObservableCollection<MenuData> datas = new ObservableCollection<MenuData>(); |
| | | string sql = $"SELECT code,name FROM menu WHERE level = 1 ORDER By id;"; |
| | | DataTable dt = sqliteHelper.GetDataTable(sql); |
| | | if (dt != null) |
| | | { |
| | | foreach (DataRow dr in dt.Rows) |
| | | { |
| | | var menu = new MenuData |
| | | { |
| | | Code = dr["code"].ToString(), |
| | | Name = dr["name"].ToString(), |
| | | }; |
| | | datas.Add(menu); |
| | | } |
| | | } |
| | | ParentSource = datas; |
| | | } |
| | | |
| | | public bool? Open(MenuData data) |
| | | { |
| | | RefreshParentCode(); |
| | | Data = data; |
| | | TbCode.Text = Data.Code; |
| | | TbName.Text = Data.Name; |
| | | TbIcon.Text = Data.Icon; |
| | | TbLevel.SelectedValue = Data.Level; |
| | | TbType.SelectedValue = Data.Type; |
| | | TbParentCode.SelectedValue = Data.ParentCode; |
| | | TbDllPath.Text = Data.DllPath; |
| | | TbClassName.Text = Data.ClassName; |
| | | NiSort.Value = Data.Sort; |
| | | return this.ShowDialog(); |
| | | } |
| | | |
| | | private void SaveOrUpdateData() |
| | | { |
| | | if (string.IsNullOrEmpty(Data.Id)) |
| | | { |
| | | InsertMenu(Data); |
| | | } |
| | | else |
| | | { |
| | | UpdateMenu(Data); |
| | | } |
| | | } |
| | | |
| | | public void InsertMenu(MenuData data) |
| | | { |
| | | string sql = $@"INSERT INTO menu (code,name,icon,level,type,parentCode,dllPath,className,sort) |
| | | VALUES |
| | | ('{data.Code}','{data.Name}','{data.Icon}','{data.Level}','{data.Type}','{data.ParentCode}', |
| | | '{data.DllPath}','{data.ClassName}',{data.Sort});"; |
| | | sqliteHelper.ExecuteSql(sql); |
| | | } |
| | | |
| | | public void UpdateMenu(MenuData data) |
| | | { |
| | | string sql = $@"UPDATE menu SET code='{data.Code}',name='{data.Name}', |
| | | icon='{data.Icon}',level='{data.Level}',type='{data.Type}',parentCode='{data.ParentCode}', |
| | | dllPath='{data.DllPath}',className='{data.ClassName}',sort={data.Sort} WHERE id='{data.Id}';"; |
| | | sqliteHelper.ExecuteSql(sql); |
| | | } |
| | | |
| | | |
| | | private void IconSelect_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | //string BasePath = PathHelper.IconFolder; |
| | | |
| | | //OpenFileDialog openFileDialog = new OpenFileDialog(); |
| | | //openFileDialog.Filter = "(*.*)|*.*"; |
| | | //openFileDialog.InitialDirectory = BasePath; |
| | | //openFileDialog.Title = "选择图片"; |
| | | //openFileDialog.Multiselect = false; |
| | | |
| | | //if (openFileDialog.ShowDialog() == true) |
| | | //{ |
| | | // string FileName = openFileDialog.FileName; |
| | | // if (!FileName.StartsWith(BasePath)) |
| | | // { |
| | | // (this, "请将图标移动至程序Icon目录下!", ToastPosition.Top); |
| | | // } |
| | | // else |
| | | // { |
| | | // TbIcon.Text = FileName.Replace(BasePath, "."); |
| | | // } |
| | | //} |
| | | IconSelector selector = new IconSelector |
| | | { |
| | | Title = LangUtil.Get("App.SelectIcon"), |
| | | Owner = this |
| | | }; |
| | | if (selector.Open(Data.Icon) == true) |
| | | { |
| | | TbIcon.Text = selector.Data; |
| | | } |
| | | } |
| | | |
| | | private void DllSelect_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | string pluginPath = PathHelper.PluginFolder(); |
| | | |
| | | OpenFileDialog openFileDialog = new OpenFileDialog(); |
| | | openFileDialog.Filter = "(*.dll)|*.dll"; |
| | | openFileDialog.InitialDirectory = pluginPath; |
| | | openFileDialog.Title = LangUtil.Get("App.SelectFile"); |
| | | openFileDialog.Multiselect = false; |
| | | |
| | | if (openFileDialog.ShowDialog() == true) |
| | | { |
| | | string FileName = openFileDialog.FileName; |
| | | if (!FileName.StartsWith(pluginPath)) |
| | | { |
| | | ToastHelper.Warning(this, LangUtil.Get("App.Msg.PluginMustInPluginFolder")); |
| | | } |
| | | else |
| | | { |
| | | TbDllPath.Text = FileName.Replace(pluginPath, "."); |
| | | try |
| | | { |
| | | object[] infos = Assembly.LoadFrom(FileName).GetCustomAttributes(typeof(PluginClassNameAttribute), false); |
| | | if (infos != null && infos.Length > 0) |
| | | { |
| | | PluginClassNameAttribute attr = (PluginClassNameAttribute)infos[0]; |
| | | TbClassName.Text = attr.ClassName; |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void TbLevel_SelectionChanged(object sender, SelectionChangedEventArgs e) |
| | | { |
| | | bool flag = (((ComboBoxItem)e.AddedItems[0])?.Content?.ToString() == "2"); |
| | | FormGroupHelper.SetIsRequired(FgType, flag); |
| | | FormGroupHelper.SetIsRequired(FgParentCode, flag); |
| | | FormGroupHelper.SetIsRequired(FgDllPath, flag); |
| | | FormGroupHelper.SetIsRequired(FgClassName, flag); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <pu:WindowX x:Class="HMI.MenuManage.RoleModal" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:local="clr-namespace:HMI.MenuManage" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | mc:Ignorable="d" |
| | | Title="Form" |
| | | Width="600" |
| | | Height="800" |
| | | WindowStartupLocation="CenterOwner" |
| | | Style="{StaticResource ModalWindow}"> |
| | | <Grid Margin="10"> |
| | | <StackPanel x:Name="formPanel"> |
| | | <pu:FormGroup x:Name="FgCode" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Role.Code}" |
| | | dp:FormGroupHelper.IsRequired="True" |
| | | dp:FormGroupHelper.ValidateMethod="ValidateCode" |
| | | GroupName="MyFormGroup"> |
| | | <TextBox x:Name="TbCode" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgName" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Role.Name}" |
| | | dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup"> |
| | | <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgMenu" Orientation="Vertical" Style="{StaticResource RequiredInputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Role.Permission}" GroupName="MyFormGroup"> |
| | | <TreeView x:Name="menuTree" Grid.Column="1" Style="{StaticResource DefaultTreeView}" |
| | | |
| | | MaxHeight="550" |
| | | ItemsSource="{Binding TreeDataSource}"> |
| | | <TreeView.ItemTemplate> |
| | | <HierarchicalDataTemplate ItemsSource="{Binding Children}"> |
| | | <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> |
| | | <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected}" |
| | | Command="{Binding CheckCommand}" |
| | | CommandParameter="{Binding Path=IsChecked,RelativeSource={RelativeSource Mode=Self}}"/> |
| | | <Image Width="{StaticResource ContentFontSize}" |
| | | Height="{StaticResource ContentFontSize}" Margin="5" Source="{Binding MenuIcon,Converter={StaticResource path2Image},Mode=OneWay}"/> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding MenuName}"/> |
| | | </StackPanel> |
| | | </HierarchicalDataTemplate> |
| | | </TreeView.ItemTemplate> |
| | | </TreeView> |
| | | </pu:FormGroup> |
| | | </StackPanel> |
| | | </Grid> |
| | | </pu:WindowX> |
| New file |
| | |
| | | using HMI.Core.Util; |
| | | using HMI.MenuManage.Entity; |
| | | using HMI.MenuManage.Util; |
| | | using HMI.UI.DpUtil; |
| | | using HMI.UI.Util; |
| | | using Panuon.WPF.UI; |
| | | using System; |
| | | using System.Collections.ObjectModel; |
| | | using System.ComponentModel; |
| | | |
| | | namespace HMI.MenuManage |
| | | { |
| | | /// <summary> |
| | | /// ModalWindow.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class RoleModal : WindowX |
| | | { |
| | | public RoleModal() |
| | | { |
| | | this.DataContext = this; |
| | | InitializeComponent(); |
| | | } |
| | | |
| | | public RoleData Data { get; set; } |
| | | |
| | | private ObservableCollection<RoleMenuRel> _TreeDataSource; |
| | | |
| | | public ObservableCollection<RoleMenuRel> TreeDataSource |
| | | { |
| | | get { return _TreeDataSource; } |
| | | set { Set(ref _TreeDataSource, value); } |
| | | } |
| | | |
| | | public SqliteHelper sqliteHelper { get; set; } |
| | | |
| | | private bool _isProcessing; |
| | | |
| | | protected override void OnClosing(CancelEventArgs e) |
| | | { |
| | | //Press Continue Button |
| | | if (DialogResult == true) |
| | | { |
| | | if (_isProcessing) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | var okButton = WindowXModalDialog.GetOKButton(this); |
| | | try |
| | | { |
| | | _isProcessing = true; |
| | | ButtonHelper.SetIsPending(okButton, true); |
| | | if (!ValidateForm()) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | |
| | | SaveOrUpdateData(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | ToastHelper.Error(this, $"Error!{ex.Message}"); |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | finally |
| | | { |
| | | _isProcessing = false; |
| | | ButtonHelper.SetIsPending(okButton, false); |
| | | } |
| | | } |
| | | base.OnClosing(e); |
| | | } |
| | | |
| | | private bool ValidateCode(object value, out string err) |
| | | { |
| | | err = null; |
| | | string segment = string.IsNullOrEmpty(Data.Id) ? "" : $" AND id!='{Data.Id}'"; |
| | | string sql = $"SELECT 1 FROM user WHERE username='{TbCode.Text}'{segment};"; |
| | | if (sqliteHelper.HasData(sql)) |
| | | { |
| | | err = this.lang("App.MustUnique", this.lang("HMI.Role.Code")); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private bool ValidateForm() |
| | | { |
| | | bool flag = FormGroupHelper.ValidateForm(formPanel); |
| | | if (flag) |
| | | { |
| | | Data.Code = TbCode.Text; |
| | | Data.Name = TbName.Text; |
| | | } |
| | | return flag; |
| | | } |
| | | |
| | | public bool? Open(RoleData data) |
| | | { |
| | | Data = data; |
| | | TbCode.Text = Data.Code; |
| | | TbName.Text = Data.Name; |
| | | TreeDataSource = RoleMenuHelper.SelectRoleMenuTreeData(sqliteHelper, Data.Code); |
| | | return this.ShowDialog(); |
| | | } |
| | | |
| | | private void SaveOrUpdateData() |
| | | { |
| | | if (string.IsNullOrEmpty(Data.Id)) |
| | | { |
| | | InsertData(Data); |
| | | } |
| | | else |
| | | { |
| | | UpdateData(Data); |
| | | } |
| | | } |
| | | |
| | | public void InsertData(RoleData data) |
| | | { |
| | | string sql = $@"INSERT INTO role (code,name) |
| | | VALUES |
| | | ('{data.Code}','{data.Name}');"; |
| | | RoleMenuHelper.SaveRelData(sqliteHelper, TreeDataSource, data.Code, sql, null); |
| | | } |
| | | |
| | | public void UpdateData(RoleData data) |
| | | { |
| | | string sql = $@"UPDATE role SET code='{data.Code}',name='{data.Name}' WHERE id='{data.Id}';"; |
| | | RoleMenuHelper.SaveRelData(sqliteHelper, TreeDataSource, data.Code, sql, null); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <pu:WindowX x:Class="HMI.MenuManage.UserModal" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:local="clr-namespace:HMI.MenuManage" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI" |
| | | xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" |
| | | mc:Ignorable="d" |
| | | Title="Form" |
| | | SizeToContent="Height" |
| | | Width="600" |
| | | WindowStartupLocation="CenterOwner" |
| | | Style="{StaticResource ModalWindow}"> |
| | | <Grid Margin="10"> |
| | | <StackPanel x:Name="formPanel"> |
| | | <pu:FormGroup x:Name="FgCode" Style="{StaticResource InputFormGroupStyle}" |
| | | dp:FormGroupHelper.ValidateMethod="ValidateCode" |
| | | Header="{DynamicResource HMI.User.Code}" |
| | | dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup"> |
| | | <TextBox x:Name="TbCode" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgName" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.User.RealName}" |
| | | dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup"> |
| | | <TextBox x:Name="TbName" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgUsername" Style="{StaticResource InputFormGroupStyle}" |
| | | dp:FormGroupHelper.ValidateMethod="ValidateUsername" |
| | | Header="{DynamicResource HMI.User.Account}" |
| | | dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup"> |
| | | <TextBox x:Name="TbUsername" Style="{StaticResource EditorTextBoxStyle}" /> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgPassword" Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.User.Password}" |
| | | dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup"> |
| | | <PasswordBox x:Name="TbPassword" pu:PasswordBoxHelper.PlainButtonVisibility="VisibleOnHover" |
| | | Style="{StaticResource SignInPasswordBoxStyle}" /> |
| | | </pu:FormGroup> |
| | | |
| | | <pu:FormGroup x:Name="FgRoleCode" Orientation="Vertical" |
| | | Style="{StaticResource InputFormGroupStyle}" |
| | | Header="{DynamicResource HMI.Role}" GroupName="MyFormGroup"> |
| | | <DataGrid x:Name="table" Style="{StaticResource DefaultDataGrid}" SelectionMode="Single" |
| | | ItemsSource="{Binding Roles}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Width="Auto" CanUserResize="False"> |
| | | <DataGridTemplateColumn.Header> |
| | | <TextBlock Text="{DynamicResource App.Granted}"/> |
| | | </DataGridTemplateColumn.Header> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <CheckBox VerticalAlignment="Center" |
| | | HorizontalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | |
| | | <DataGridTextColumn Header="#" |
| | | Binding="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}" |
| | | CanUserResize="False"/> |
| | | <DataGridTextColumn Width="*" Binding="{Binding RoleCode}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Role.Code}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding RoleName}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Role.Name}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | </pu:FormGroup> |
| | | </StackPanel> |
| | | </Grid> |
| | | </pu:WindowX> |
| New file |
| | |
| | | using HMI.Core.Util; |
| | | using HMI.MenuManage.Entity; |
| | | using HMI.MenuManage.Util; |
| | | using HMI.UI.DpUtil; |
| | | using HMI.UI.Util; |
| | | using Panuon.WPF.UI; |
| | | using System; |
| | | using System.Collections.ObjectModel; |
| | | using System.ComponentModel; |
| | | |
| | | namespace HMI.MenuManage |
| | | { |
| | | /// <summary> |
| | | /// ModalWindow.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class UserModal : WindowX |
| | | { |
| | | public UserModal() |
| | | { |
| | | this.DataContext = this; |
| | | InitializeComponent(); |
| | | } |
| | | |
| | | public UserData Data { get; set; } |
| | | private ObservableCollection<UserRoleRel> _Roles; |
| | | public ObservableCollection<UserRoleRel> Roles |
| | | { |
| | | get => _Roles; |
| | | set { Set(ref _Roles, value); } |
| | | } |
| | | public SqliteHelper sqliteHelper { get; set; } |
| | | |
| | | private bool _isProcessing; |
| | | |
| | | protected override void OnClosing(CancelEventArgs e) |
| | | { |
| | | //Press Continue Button |
| | | if (DialogResult == true) |
| | | { |
| | | if (_isProcessing) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | var okButton = WindowXModalDialog.GetOKButton(this); |
| | | try |
| | | { |
| | | _isProcessing = true; |
| | | ButtonHelper.SetIsPending(okButton, true); |
| | | if (!ValidateForm()) |
| | | { |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | |
| | | SaveOrUpdateData(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | ToastHelper.Error(this, $"Error!{ex.Message}"); |
| | | e.Cancel = true; |
| | | return; |
| | | } |
| | | finally |
| | | { |
| | | _isProcessing = false; |
| | | ButtonHelper.SetIsPending(okButton, false); |
| | | } |
| | | } |
| | | base.OnClosing(e); |
| | | } |
| | | |
| | | private bool ValidateUsername(object value, out string err) |
| | | { |
| | | err = null; |
| | | string segment = string.IsNullOrEmpty(Data.Id) ? "" : $" AND id!='{Data.Id}'"; |
| | | string sql = $"SELECT 1 FROM user WHERE username='{TbUsername.Text}'{segment};"; |
| | | if (sqliteHelper.HasData(sql)) |
| | | { |
| | | err = string.Format(LangUtil.Get("App.MustBeUnique"), LangUtil.Get("HMI.User.Account")); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private bool ValidateCode(object value, out string err) |
| | | { |
| | | err = null; |
| | | string segment = string.IsNullOrEmpty(Data.Id) ? "" : $" AND id!='{Data.Id}'"; |
| | | string sql = $"SELECT 1 FROM user WHERE code='{TbCode.Text}'{segment};"; |
| | | if (sqliteHelper.HasData(sql)) |
| | | { |
| | | err = string.Format(LangUtil.Get("App.MustBeUnique"), LangUtil.Get("HMI.User.Code")); |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private bool ValidateForm() |
| | | { |
| | | bool flag = FormGroupHelper.ValidateForm(formPanel); |
| | | if (flag) |
| | | { |
| | | Data.Code = TbCode.Text; |
| | | Data.Username = TbUsername.Text; |
| | | Data.Name = TbName.Text; |
| | | Data.Password = TbPassword.Password; |
| | | } |
| | | |
| | | return flag; |
| | | } |
| | | |
| | | public bool? Open(UserData data) |
| | | { |
| | | Data = data; |
| | | TbCode.Text = Data.Code; |
| | | TbUsername.Text = Data.Username; |
| | | TbName.Text = Data.Name; |
| | | TbPassword.Password = Data.Password; |
| | | |
| | | Roles = UserRoleRelHelper.SelectUserRoleData(sqliteHelper, Data.Username); |
| | | |
| | | return this.ShowDialog(); |
| | | } |
| | | |
| | | private void SaveOrUpdateData() |
| | | { |
| | | if (string.IsNullOrEmpty(Data.Id)) |
| | | { |
| | | InsertData(Data); |
| | | } |
| | | else |
| | | { |
| | | UpdateData(Data); |
| | | } |
| | | } |
| | | |
| | | public void InsertData(UserData data) |
| | | { |
| | | string sql = $@"INSERT INTO user (code, username,password,name) |
| | | VALUES |
| | | ('{data.Code}', '{data.Username}','{data.Password}','{data.Name}');"; |
| | | UserRoleRelHelper.SaveUserRoleRel(sqliteHelper, Roles, data, sql, null); |
| | | } |
| | | |
| | | public void UpdateData(UserData data) |
| | | { |
| | | string sql = $@"UPDATE user SET code='{data.Code}', username='{data.Username}',password='{data.Password}',name='{data.Name}' |
| | | WHERE id='{data.Id}';"; |
| | | UserRoleRelHelper.SaveUserRoleRel(sqliteHelper, Roles, data, sql, null); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.Data; |
| | | using System.IO; |
| | | using System.Linq; |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using HMI.MenuManage.Entity; |
| | | |
| | | namespace HMI.MenuManage.Model |
| | | { |
| | | public class MenuManageModel : NotifyBase |
| | | { |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Entity; |
| | | using HMI.Core.Util; |
| | | using HMI.MenuManage.Entity; |
| | | using HMI.MenuManage.Util; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.Data; |
| | | using System.IO; |
| | | |
| | | namespace HMI.MenuManage.Model |
| | | { |
| | | public class RoleManageModel : NotifyBase |
| | | { |
| | | public SqliteHelper sqliteHelper; |
| | | |
| | | private ObservableCollection<RoleData> _DataSource; |
| | | |
| | | public ObservableCollection<RoleData> DataSource |
| | | { |
| | | get { return _DataSource; } |
| | | set { Set(ref _DataSource, value); } |
| | | } |
| | | |
| | | private RoleData _SelectedValue; |
| | | public RoleData SelectedValue |
| | | { |
| | | get => _SelectedValue; |
| | | set |
| | | { |
| | | Set(ref _SelectedValue, value); |
| | | LoadRelData(value); |
| | | } |
| | | } |
| | | |
| | | private ObservableCollection<RoleMenuRel> _TreeDataSource; |
| | | |
| | | public ObservableCollection<RoleMenuRel> TreeDataSource |
| | | { |
| | | get { return _TreeDataSource; } |
| | | set { Set(ref _TreeDataSource, value); } |
| | | } |
| | | |
| | | public RoleManageModel() |
| | | { |
| | | if (!File.Exists(PathHelper.DatabasePath)) |
| | | { |
| | | SqliteHelper.CreateDatabase(PathHelper.DatabasePath); |
| | | } |
| | | sqliteHelper = new SqliteHelper(PathHelper.DatabasePath); |
| | | sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS role ( |
| | | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| | | code TEXT NOT NULL, |
| | | name TEXT, |
| | | UNIQUE(code) |
| | | ); |
| | | CREATE TABLE IF NOT EXISTS role_menu_rel ( |
| | | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| | | roleCode TEXT NOT NULL, |
| | | menuCode TEXT NOT NULL, |
| | | UNIQUE(roleCode,menuCode) |
| | | ); |
| | | "); |
| | | } |
| | | |
| | | public void LoadData() |
| | | { |
| | | ObservableCollection<RoleData> data = new ObservableCollection<RoleData>(); |
| | | DataTable dt = sqliteHelper.GetDataTable("SELECT * FROM role Order By id;"); |
| | | if (dt != null) |
| | | { |
| | | foreach (DataRow dr in dt.Rows) |
| | | { |
| | | var menu = new RoleData |
| | | { |
| | | Id = dr["id"].ToString(), |
| | | Code = dr["code"].ToString(), |
| | | Name = dr["name"].ToString() |
| | | }; |
| | | data.Add(menu); |
| | | } |
| | | } |
| | | DataSource = data; |
| | | } |
| | | |
| | | public void LoadRelData(RoleData data) |
| | | { |
| | | if (data == null) |
| | | { |
| | | TreeDataSource.Clear(); |
| | | return; |
| | | } |
| | | TreeDataSource = RoleMenuHelper.SelectRoleMenuTreeData(sqliteHelper, data.Code); |
| | | } |
| | | |
| | | public void DelSingleData(RoleData data) |
| | | { |
| | | sqliteHelper.ExecuteBatchSql(new List<string>{ |
| | | $"DELETE FROM role WHERE id = '{data.Id}'", |
| | | $"DELETE FROM role_menu_rel WHERE roleCode = '{data.Code}'", |
| | | $"DELETE FROM user_role_rel WHERE roleCode = '{data.Code}'" |
| | | }); |
| | | } |
| | | |
| | | public void DeleteRoleByCodes(IEnumerable<string> codes) |
| | | { |
| | | List<string> sqls = new List<string>(); |
| | | foreach (string code in codes) |
| | | { |
| | | sqls.Add($"DELETE FROM role WHERE code = '{code}';"); |
| | | sqls.Add($"DELETE FROM role_menu_rel WHERE roleCode = '{code}';"); |
| | | sqls.Add($"DELETE FROM user_role_rel WHERE roleCode = '{code}';"); |
| | | } |
| | | sqliteHelper.ExecuteBatchSql(sqls); |
| | | } |
| | | |
| | | public void SaveRelData(RoleData role) |
| | | { |
| | | if (role == null) return; |
| | | RoleMenuHelper.SaveRelData(sqliteHelper, TreeDataSource, role.Code, null, null); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Collections.ObjectModel; |
| | | using System.Data; |
| | | using System.IO; |
| | | using HMI.Core.Util; |
| | | using HMI.MenuManage.Entity; |
| | | using HMI.MenuManage.Util; |
| | | using HMI.Core.Entity; |
| | | |
| | | namespace HMI.MenuManage.Model |
| | | { |
| | | public class UserManageModel : NotifyBase |
| | | { |
| | | public SqliteHelper sqliteHelper; |
| | | |
| | | private ObservableCollection<UserData> _DataSource; |
| | | |
| | | public ObservableCollection<UserData> DataSource |
| | | { |
| | | get { return _DataSource; } |
| | | set { Set(ref _DataSource, value); } |
| | | } |
| | | private UserData _SelectedUser; |
| | | |
| | | public UserData SelectedUser |
| | | { |
| | | get { return _SelectedUser; } |
| | | set |
| | | { |
| | | Set(ref _SelectedUser, value); |
| | | LoadUserRole(); |
| | | } |
| | | } |
| | | |
| | | private ObservableCollection<UserRoleRel> _Roles; |
| | | public ObservableCollection<UserRoleRel> Roles |
| | | { |
| | | get => _Roles; |
| | | set { Set(ref _Roles, value); } |
| | | } |
| | | |
| | | public UserManageModel() |
| | | { |
| | | if (!File.Exists(PathHelper.DatabasePath)) |
| | | { |
| | | SqliteHelper.CreateDatabase(PathHelper.DatabasePath); |
| | | } |
| | | sqliteHelper = new SqliteHelper(PathHelper.DatabasePath); |
| | | sqliteHelper.ExecuteSql(@"CREATE TABLE IF NOT EXISTS user ( |
| | | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| | | code TEXT NOT NULL, |
| | | username TEXT NOT NULL, |
| | | password TEXT NOT NULL, |
| | | name TEXT, |
| | | UNIQUE(username) |
| | | ); |
| | | CREATE TABLE IF NOT EXISTS user_role_rel ( |
| | | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| | | username TEXT NOT NULL, |
| | | roleCode TEXT NOT NULL, |
| | | UNIQUE(username,roleCode) |
| | | ); |
| | | "); |
| | | } |
| | | |
| | | public void ImportUser(List<UserData> users) |
| | | { |
| | | List<string> batch = new List<string>(); |
| | | foreach (var user in users) |
| | | { |
| | | string sql = $"SELECT 1 FROM user WHERE username='{user.Username}' OR code='{user.Code}' OR name='{user.Name}'"; |
| | | if (sqliteHelper.HasData(sql)) |
| | | { |
| | | throw new Exception($"User {user.Username} already exists."); |
| | | } |
| | | string codes = "''"; |
| | | string roleCodes = user.RoleCodes?.Trim(); |
| | | if (!string.IsNullOrEmpty(roleCodes)) |
| | | { |
| | | codes = $"'{roleCodes.Replace(",", "','")}'"; |
| | | } |
| | | batch.Add($"INSERT INTO user (code,username,name,password) VALUES ('{user.Code}', '{user.Username}', '{user.Name}', '{user.Password}');"); |
| | | batch.Add($"INSERT INTO user_role_rel (username, roleCode) SELECT '{user.Username}', code FROM role WHERE code IN ({codes});"); |
| | | } |
| | | sqliteHelper.ExecuteBatchSql(batch); |
| | | } |
| | | |
| | | public void LoadData() |
| | | { |
| | | ObservableCollection<UserData> data = new ObservableCollection<UserData>(); |
| | | DataTable dt = sqliteHelper.GetDataTable("SELECT * FROM user Order By id;"); |
| | | if (dt != null) |
| | | { |
| | | foreach (DataRow dr in dt.Rows) |
| | | { |
| | | var menu = new UserData |
| | | { |
| | | Id = dr["id"].ToString(), |
| | | Code = dr["code"].ToString(), |
| | | Username = dr["username"].ToString(), |
| | | Password = dr["password"].ToString(), |
| | | Name = dr["name"].ToString() |
| | | }; |
| | | DataTable roles = sqliteHelper.GetDataTable($@"SELECT r.code AS code FROM user_role_rel AS rel |
| | | INNER JOIN role AS r ON rel.roleCode = r.code AND rel.username = '{menu.Username}' Order By r.code;"); |
| | | List<string> codes = new List<string>(); |
| | | foreach (DataRow row in roles.Rows) |
| | | { |
| | | if (row["code"] != DBNull.Value) |
| | | { |
| | | codes.Add(row["code"].ToString()); |
| | | } |
| | | } |
| | | menu.RoleCodes = string.Join(",", codes); |
| | | data.Add(menu); |
| | | } |
| | | } |
| | | DataSource = data; |
| | | } |
| | | |
| | | public void LoadUserRole() |
| | | { |
| | | Roles = SelectedUser == null ? new ObservableCollection<UserRoleRel>() |
| | | : UserRoleRelHelper.SelectUserRoleData(sqliteHelper, SelectedUser.Username); |
| | | } |
| | | |
| | | public void SaveUserRole() |
| | | { |
| | | UserRoleRelHelper.SaveUserRoleRel(sqliteHelper, Roles, SelectedUser, null, null); |
| | | } |
| | | |
| | | public void DeleteUserByUsername(string username) |
| | | { |
| | | sqliteHelper.ExecuteBatchSql(new List<string> { |
| | | $"DELETE FROM user WHERE username = '{username}';", |
| | | $"DELETE FROM user_role_rel WHERE username = '{username}';" |
| | | }); |
| | | } |
| | | |
| | | public void DeleteUserByUsernames(IEnumerable<string> usernames) |
| | | { |
| | | List<string> sqls = new List<string>(); |
| | | foreach (string username in usernames) |
| | | { |
| | | sqls.Add($"DELETE FROM user WHERE username = '{username}';"); |
| | | sqls.Add($"DELETE FROM user_role_rel WHERE username = '{username}';"); |
| | | } |
| | | sqliteHelper.ExecuteBatchSql(sqls); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | using HMI.Core.Annotation; |
| | | using System.Reflection; |
| | | using System.Runtime.InteropServices; |
| | | using System.Windows; |
| | | |
| | | // 有关程序集的一般信息由以下 |
| | | // 控制。更改这些特性值可修改 |
| | | // 与程序集关联的信息。 |
| | | [assembly: AssemblyTitle("HMI.MenuManage")] |
| | | [assembly: AssemblyDescription("")] |
| | | [assembly: AssemblyConfiguration("")] |
| | | [assembly: AssemblyCompany("Organization")] |
| | | [assembly: AssemblyProduct("HMI.MenuManage")] |
| | | [assembly: AssemblyCopyright("Copyright © Organization 2025")] |
| | | [assembly: AssemblyTrademark("")] |
| | | [assembly: AssemblyCulture("")] |
| | | |
| | | // 将 ComVisible 设置为 false 会使此程序集中的类型 |
| | | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 |
| | | //请将此类型的 ComVisible 特性设置为 true。 |
| | | [assembly: ComVisible(false)] |
| | | |
| | | //若要开始生成可本地化的应用程序,请设置 |
| | | //.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture> |
| | | //在 <PropertyGroup> 中。例如,如果你使用的是美国英语。 |
| | | //使用的是美国英语,请将 <UICulture> 设置为 en-US。 然后取消 |
| | | //对以下 NeutralResourceLanguage 特性的注释。 更新 |
| | | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 |
| | | |
| | | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] |
| | | |
| | | |
| | | [assembly:ThemeInfo( |
| | | ResourceDictionaryLocation.None, //主题特定资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //或应用程序资源字典中找到时使用) |
| | | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 |
| | | //(未在页面中找到资源时使用, |
| | | //、应用程序或任何主题专用资源字典中找到时使用) |
| | | )] |
| | | |
| | | |
| | | // 程序集的版本信息由下列四个值组成: |
| | | // |
| | | // 主版本 |
| | | // 次版本 |
| | | // 生成号 |
| | | // 修订号 |
| | | // |
| | | [assembly: AssemblyVersion("1.0.0.0")] |
| | | [assembly: AssemblyFileVersion("1.0.0.0")] |
| | | [assembly: PluginClassName("HMI.MenuManage.MenuManage")] |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // 此代码由工具生成。 |
| | | // 运行时版本:4.0.30319.42000 |
| | | // |
| | | // 对此文件的更改可能会导致不正确的行为,并且如果 |
| | | // 重新生成代码,这些更改将会丢失。 |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.MenuManage.Properties { |
| | | using System; |
| | | |
| | | |
| | | /// <summary> |
| | | /// 一个强类型的资源类,用于查找本地化的字符串等。 |
| | | /// </summary> |
| | | // 此类是由 StronglyTypedResourceBuilder |
| | | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 |
| | | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen |
| | | // (以 /str 作为命令选项),或重新生成 VS 项目。 |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] |
| | | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | internal class Resources { |
| | | |
| | | private static global::System.Resources.ResourceManager resourceMan; |
| | | |
| | | private static global::System.Globalization.CultureInfo resourceCulture; |
| | | |
| | | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] |
| | | internal Resources() { |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 返回此类使用的缓存的 ResourceManager 实例。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Resources.ResourceManager ResourceManager { |
| | | get { |
| | | if (object.ReferenceEquals(resourceMan, null)) { |
| | | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HMI.MenuManage.Properties.Resources", typeof(Resources).Assembly); |
| | | resourceMan = temp; |
| | | } |
| | | return resourceMan; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 重写当前线程的 CurrentUICulture 属性,对 |
| | | /// 使用此强类型资源类的所有资源查找执行重写。 |
| | | /// </summary> |
| | | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] |
| | | internal static global::System.Globalization.CultureInfo Culture { |
| | | get { |
| | | return resourceCulture; |
| | | } |
| | | set { |
| | | resourceCulture = value; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <root> |
| | | <!-- |
| | | Microsoft ResX Schema |
| | | |
| | | Version 2.0 |
| | | |
| | | The primary goals of this format is to allow a simple XML format |
| | | that is mostly human readable. The generation and parsing of the |
| | | various data types are done through the TypeConverter classes |
| | | associated with the data types. |
| | | |
| | | Example: |
| | | |
| | | ... ado.net/XML headers & schema ... |
| | | <resheader name="resmimetype">text/microsoft-resx</resheader> |
| | | <resheader name="version">2.0</resheader> |
| | | <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> |
| | | <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> |
| | | <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> |
| | | <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> |
| | | <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> |
| | | <value>[base64 mime encoded serialized .NET Framework object]</value> |
| | | </data> |
| | | <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> |
| | | <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> |
| | | <comment>This is a comment</comment> |
| | | </data> |
| | | |
| | | There are any number of "resheader" rows that contain simple |
| | | name/value pairs. |
| | | |
| | | Each data row contains a name, and value. The row also contains a |
| | | type or mimetype. Type corresponds to a .NET class that support |
| | | text/value conversion through the TypeConverter architecture. |
| | | Classes that don't support this are serialized and stored with the |
| | | mimetype set. |
| | | |
| | | The mimetype is used for serialized objects, and tells the |
| | | ResXResourceReader how to depersist the object. This is currently not |
| | | extensible. For a given mimetype the value must be set accordingly: |
| | | |
| | | Note - application/x-microsoft.net.object.binary.base64 is the format |
| | | that the ResXResourceWriter will generate, however the reader can |
| | | read any of the formats listed below. |
| | | |
| | | mimetype: application/x-microsoft.net.object.binary.base64 |
| | | value : The object must be serialized with |
| | | : System.Serialization.Formatters.Binary.BinaryFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.soap.base64 |
| | | value : The object must be serialized with |
| | | : System.Runtime.Serialization.Formatters.Soap.SoapFormatter |
| | | : and then encoded with base64 encoding. |
| | | |
| | | mimetype: application/x-microsoft.net.object.bytearray.base64 |
| | | value : The object must be serialized into a byte array |
| | | : using a System.ComponentModel.TypeConverter |
| | | : and then encoded with base64 encoding. |
| | | --> |
| | | <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> |
| | | <xsd:element name="root" msdata:IsDataSet="true"> |
| | | <xsd:complexType> |
| | | <xsd:choice maxOccurs="unbounded"> |
| | | <xsd:element name="metadata"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | <xsd:attribute name="type" type="xsd:string" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="assembly"> |
| | | <xsd:complexType> |
| | | <xsd:attribute name="alias" type="xsd:string" /> |
| | | <xsd:attribute name="name" type="xsd:string" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="data"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> |
| | | <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> |
| | | <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | <xsd:element name="resheader"> |
| | | <xsd:complexType> |
| | | <xsd:sequence> |
| | | <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> |
| | | </xsd:sequence> |
| | | <xsd:attribute name="name" type="xsd:string" use="required" /> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:choice> |
| | | </xsd:complexType> |
| | | </xsd:element> |
| | | </xsd:schema> |
| | | <resheader name="resmimetype"> |
| | | <value>text/microsoft-resx</value> |
| | | </resheader> |
| | | <resheader name="version"> |
| | | <value>2.0</value> |
| | | </resheader> |
| | | <resheader name="reader"> |
| | | <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | <resheader name="writer"> |
| | | <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
| | | </resheader> |
| | | </root> |
| New file |
| | |
| | | //------------------------------------------------------------------------------ |
| | | // <auto-generated> |
| | | // 此代码由工具生成。 |
| | | // 运行时版本:4.0.30319.42000 |
| | | // |
| | | // 对此文件的更改可能会导致不正确的行为,并且如果 |
| | | // 重新生成代码,这些更改将会丢失。 |
| | | // </auto-generated> |
| | | //------------------------------------------------------------------------------ |
| | | |
| | | namespace HMI.MenuManage.Properties { |
| | | |
| | | |
| | | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
| | | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.12.0.0")] |
| | | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { |
| | | |
| | | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); |
| | | |
| | | public static Settings Default { |
| | | get { |
| | | return defaultInstance; |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <?xml version='1.0' encoding='utf-8'?> |
| | | <SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> |
| | | <Profiles> |
| | | <Profile Name="(Default)" /> |
| | | </Profiles> |
| | | <Settings /> |
| | | </SettingsFile> |
| New file |
| | |
| | | <hb:PluginControl x:Class="HMI.MenuManage.RoleManage" |
| | | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
| | | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
| | | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
| | | xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
| | | xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" |
| | | xmlns:local="clr-namespace:HMI.MenuManage" |
| | | xmlns:dp="clr-namespace:HMI.UI;assembly=HMI.UI" |
| | | xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI" |
| | | xmlns:pw="https://opensource.panuon.com/wpf" |
| | | xmlns:pu="https://opensource.panuon.com/wpf-ui" |
| | | mc:Ignorable="d" |
| | | Padding="10" |
| | | Background="#f0f2f5" |
| | | d:DesignHeight="450" d:DesignWidth="800"> |
| | | <UserControl.Resources> |
| | | </UserControl.Resources> |
| | | <Grid> |
| | | <Grid.ColumnDefinitions> |
| | | <ColumnDefinition Width="6*"/> |
| | | <ColumnDefinition Width="4*"/> |
| | | </Grid.ColumnDefinitions> |
| | | |
| | | <Border Style="{StaticResource CardBoarder}" Margin="0,0,2,0"> |
| | | <StackPanel> |
| | | <DockPanel Margin="0,0,0,10"> |
| | | <Button DockPanel.Dock="Right" Style="{StaticResource DelBtn}" |
| | | x:Name="deleteBatchBtn" Click="Button_Click"/> |
| | | <Button DockPanel.Dock="Right" Style="{StaticResource RfhBtn}" |
| | | Margin="0,0,10,0" x:Name="refreshBtn" Click="Button_Click"/> |
| | | <Button DockPanel.Dock="Right" Style="{StaticResource AddBtn}" |
| | | Margin="0,0,10,0" x:Name="addBtn" Click="Button_Click"/> |
| | | <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" |
| | | VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource HMI.Role.Title}"/> |
| | | </DockPanel> |
| | | |
| | | <DataGrid x:Name="table" Style="{StaticResource DefaultDataGrid}" |
| | | SelectedValue="{Binding SelectedValue}" |
| | | ItemsSource="{Binding DataSource}"> |
| | | <DataGrid.Columns> |
| | | <DataGridTemplateColumn Header="" Width="Auto" CanUserResize="False"> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <CheckBox VerticalAlignment="Center" |
| | | HorizontalAlignment="Center" IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | |
| | | <DataGridTextColumn Header="#" |
| | | Binding="{Binding Converter={StaticResource rowNumber}, |
| | | RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}" |
| | | CanUserResize="False"/> |
| | | <DataGridTextColumn Width="*" Binding="{Binding Code}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Role.Code}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTextColumn Width="*" Binding="{Binding Name}"> |
| | | <DataGridTextColumn.Header> |
| | | <TextBlock Text="{DynamicResource HMI.Role.Name}"/> |
| | | </DataGridTextColumn.Header> |
| | | </DataGridTextColumn> |
| | | <DataGridTemplateColumn Width="110" IsReadOnly="True" CanUserResize="False"> |
| | | <DataGridTemplateColumn.Header> |
| | | <TextBlock Text="{DynamicResource App.Opt}"/> |
| | | </DataGridTemplateColumn.Header> |
| | | <DataGridTemplateColumn.CellTemplate> |
| | | <DataTemplate> |
| | | <StackPanel Orientation="Horizontal"> |
| | | <Button x:Name="editBtn" Style="{StaticResource EditLink}" |
| | | Margin="0,0,10,0" |
| | | Click="Button_Click" |
| | | DataContext="{Binding }"/> |
| | | <Button x:Name="delBtn" Style="{StaticResource DeleteLink}" |
| | | DataContext="{Binding }" |
| | | Click="Button_Click"/> |
| | | </StackPanel> |
| | | </DataTemplate> |
| | | </DataGridTemplateColumn.CellTemplate> |
| | | </DataGridTemplateColumn> |
| | | </DataGrid.Columns> |
| | | </DataGrid> |
| | | </StackPanel> |
| | | </Border> |
| | | |
| | | <Border Grid.Column="1" Style="{StaticResource CardBoarder}" Margin="2,0,0,0"> |
| | | <StackPanel> |
| | | <DockPanel Margin="0,0,0,10" > |
| | | <Button DockPanel.Dock="Right" Style="{StaticResource SaveBtn}" |
| | | x:Name="saveMenuBtn" Click="Button_Click"/> |
| | | <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource TitleFontSize}" |
| | | VerticalAlignment="Center" HorizontalAlignment="Left" Text="{DynamicResource HMI.Role.Permission}"/> |
| | | </DockPanel> |
| | | <TreeView x:Name="treeViewer" Grid.Column="1" |
| | | Style="{StaticResource DefaultTreeView}" ItemsSource="{Binding TreeDataSource}"> |
| | | <TreeView.ItemTemplate> |
| | | <HierarchicalDataTemplate ItemsSource="{Binding Children}"> |
| | | <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> |
| | | <CheckBox VerticalAlignment="Center" IsChecked="{Binding Selected}" |
| | | Command="{Binding CheckCommand}" |
| | | CommandParameter="{Binding Path=IsChecked,RelativeSource={RelativeSource Mode=Self}}" |
| | | /> |
| | | <!--<Image Width="{StaticResource ContentFontSize}" |
| | | Height="{StaticResource ContentFontSize}" Margin="5" Source="{Binding MenuIcon,Converter={StaticResource path2Image},Mode=OneWay}"/>--> |
| | | <TextBlock VerticalAlignment="Center" Text="{Binding MenuName}"/> |
| | | </StackPanel> |
| | | </HierarchicalDataTemplate> |
| | | </TreeView.ItemTemplate> |
| | | </TreeView> |
| | | </StackPanel> |
| | | </Border> |
| | | </Grid> |
| | | </hb:PluginControl> |
| New file |
| | |
| | | using HMI.Base; |
| | | using HMI.Core.Util; |
| | | using HMI.MenuManage.Entity; |
| | | using HMI.MenuManage.Model; |
| | | using HMI.UI.Util; |
| | | using Panuon.WPF.UI; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.Linq; |
| | | using System.Windows; |
| | | using System.Windows.Controls; |
| | | |
| | | namespace HMI.MenuManage |
| | | { |
| | | /// <summary> |
| | | /// UserControl1.xaml 的交互逻辑 |
| | | /// </summary> |
| | | public partial class RoleManage : PluginControl |
| | | { |
| | | #region 属性 |
| | | private RoleManageModel Model; |
| | | #endregion |
| | | |
| | | public RoleManage() |
| | | { |
| | | Model = new RoleManageModel(); |
| | | this.DataContext = Model; |
| | | this.Loaded += RoleManage_Loaded; |
| | | InitializeComponent(); |
| | | } |
| | | |
| | | private void RoleManage_Loaded(object sender, RoutedEventArgs e) |
| | | { |
| | | Model.LoadData(); |
| | | this.Loaded -= RoleManage_Loaded; |
| | | } |
| | | |
| | | public override void DoClose(object sender, EventArgs e) |
| | | { |
| | | try |
| | | { |
| | | Model?.sqliteHelper?.Close(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Warn("Do Close Failed.", ex); |
| | | } |
| | | } |
| | | private void Button_Click(object sender, RoutedEventArgs e) |
| | | { |
| | | try |
| | | { |
| | | Button btn = (Button)sender; |
| | | if (btn.Name == "addBtn") |
| | | { |
| | | try |
| | | { |
| | | RoleModal modal = new RoleModal |
| | | { |
| | | Title = this.lang("HMI.Role.Add"), |
| | | Owner = Window.GetWindow(this), |
| | | sqliteHelper = Model.sqliteHelper |
| | | }; |
| | | if (modal.Open(new RoleData()) == true) |
| | | { |
| | | Model.LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Add Role Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("App.AddFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | else if (btn.Name == "refreshBtn") |
| | | { |
| | | try |
| | | { |
| | | Model.LoadData(); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Refresh Role Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("App.RefreshFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | else if (btn.Name == "menuBtn") |
| | | { |
| | | try |
| | | { |
| | | Model.LoadRelData((RoleData)btn.DataContext); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Load Role-Menu Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("App.RefreshFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | else if (btn.Name == "editBtn") |
| | | { |
| | | try |
| | | { |
| | | RoleModal modal = new RoleModal |
| | | { |
| | | Title = this.lang("HMI.Role.Edit"), |
| | | Owner = Window.GetWindow(this), |
| | | sqliteHelper = Model.sqliteHelper |
| | | }; |
| | | if (modal.Open((RoleData)btn.DataContext) == true) |
| | | { |
| | | Model.LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Edit Role Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("App.EditFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | else if (btn.Name == "deleteBatchBtn") |
| | | { |
| | | try |
| | | { |
| | | List<string> codes = Model.DataSource.Where(x => x.Selected).Select(x => x.Code).ToList(); |
| | | if (codes == null || codes.Count <= 0) |
| | | { |
| | | ToastHelper.Warning(Window.GetWindow(this), this.lang("HMI.NoDeleteData")); |
| | | return; |
| | | } |
| | | |
| | | var res = MsgBoxHelper.WarnMessage(Window.GetWindow(this), |
| | | this.lang("HMI.MakeSureDeletes", codes.Count)); |
| | | if (res == MessageBoxResult.OK) |
| | | { |
| | | Model.DeleteRoleByCodes(codes); |
| | | Model.LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Delete Role Batch Failed.", ex); |
| | | ToastHelper.Error(Window.GetWindow(this), $"{LangUtil.Get("App.DeleteFailed")}: {ex.Message}"); |
| | | } |
| | | } |
| | | else if (btn.Name == "delBtn") |
| | | { |
| | | try |
| | | { |
| | | var data = (RoleData)((Button)sender).DataContext; |
| | | var res = MsgBoxHelper.WarnMessage(Window.GetWindow(this), |
| | | this.lang("HMI.MakeSureDeleteSingle", data.Name)); |
| | | if (res == MessageBoxResult.OK) |
| | | { |
| | | Model.DelSingleData(data); |
| | | Model.LoadData(); |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Delete Role Failed.", ex); |
| | | ToastHelper.Error((WindowX)Window.GetWindow(this), LangUtil.Get("App.DeleteFailed")); |
| | | } |
| | | } |
| | | else if (btn.Name == "saveMenuBtn") |
| | | { |
| | | try |
| | | { |
| | | Model.SaveRelData(Model.SelectedValue); |
| | | ToastHelper.Success((WindowX)Window.GetWindow(this), LangUtil.Get("App.SaveSuccess")); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Save Role-Menu Failed.", ex); |
| | | ToastHelper.Error((WindowX)Window.GetWindow(this), LangUtil.Get("App.SaveFailed")); |
| | | } |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | log.Error("Something Error...", ex); |
| | | } |
| | | } |
| | | } |
| | | } |
| HMI.MenuManage/UserManage.xaml
HMI.MenuManage/UserManage.xaml.cs
HMI.MenuManage/Util/RoleHelper.cs
HMI.MenuManage/Util/RoleMenuHelper.cs
HMI.MenuManage/Util/UserRoleRelHelper.cs
HMI.MenuManage/packages.config
HMI.ParameterManage/HMI.ParameterManage.csproj
HMI.ParameterManage/Model/SqlModel.cs
HMI.ParameterManage/Model/XmlModel.cs
HMI.ParameterManage/ParameterManage.xaml
HMI.ParameterManage/ParameterManage.xaml.cs
HMI.ParameterManage/ParameterModal.xaml
HMI.ParameterManage/ParameterModal.xaml.cs
HMI.ParameterManage/ParameterSetting.xaml
HMI.ParameterManage/ParameterSetting.xaml.cs
HMI.ParameterManage/ProductModal.xaml
HMI.ParameterManage/ProductModal.xaml.cs
HMI.ParameterManage/Properties/AssemblyInfo.cs
HMI.ParameterManage/Properties/Resources.Designer.cs
HMI.ParameterManage/Properties/Resources.resx
HMI.ParameterManage/Properties/Settings.Designer.cs
HMI.ParameterManage/Properties/Settings.settings
HMI.ParameterManage/SettingModal.xaml
HMI.ParameterManage/SettingModal.xaml.cs
HMI.ParameterManage/TestItem.xaml
HMI.ParameterManage/TestItem.xaml.cs
HMI.ParameterManage/TestItemModal.xaml
HMI.ParameterManage/TestItemModal.xaml.cs
HMI.ParameterManage/packages.config
HMI.SettingControl/HMI.SettingControl.csproj
HMI.SettingControl/Properties/AssemblyInfo.cs
HMI.SettingControl/Properties/Resources.Designer.cs
HMI.SettingControl/Properties/Resources.resx
HMI.SettingControl/Properties/Settings.Designer.cs
HMI.SettingControl/Properties/Settings.settings
HMI.SettingControl/SettingControl.xaml
HMI.SettingControl/SettingControl.xaml.cs
HMI.SettingControl/SystemConfig.xaml
HMI.SettingControl/SystemConfig.xaml.cs
HMI.SettingControl/packages.config
HMI.StdControl/HMI.StdControl.csproj
HMI.StdControl/Properties/AssemblyInfo.cs
HMI.StdControl/StdEnableMes.xaml
HMI.StdControl/StdEnableMes.xaml.cs
HMI.StdControl/StdSettingCheckBox.xaml
HMI.StdControl/StdSettingCheckBox.xaml.cs
HMI.StdControl/StdUIEventArg.cs
HMI.UI/Controls/KeyboradWindow.xaml
HMI.UI/Controls/KeyboradWindow.xaml.cs
HMI.UI/Controls/LightBall.cs
HMI.UI/Controls/MultiCombox.xaml
HMI.UI/Controls/MultiCombox.xaml.cs
HMI.UI/Controls/NumberInput.cs
HMI.UI/Controls/NumberPanel.xaml
HMI.UI/Controls/NumberPanel.xaml.cs
HMI.UI/Controls/VisualHost.cs
HMI.UI/Controls/treegrid/Controls/TreeDataGrid.cs
HMI.UI/Controls/treegrid/Controls/TreeGrid.cs
HMI.UI/Controls/treegrid/Controls/TreeItemContentControl.cs
HMI.UI/Controls/treegrid/Controls/TreeItemData.cs
HMI.UI/Controls/treegrid/Controls/TreeListView.cs
HMI.UI/Controls/treegrid/Themes/TreeGrid.xaml
HMI.UI/Controls/treegrid/Themes/TreeItemContentControl.xaml
HMI.UI/Controls/treegrid/Themes/TreeListView.xaml
HMI.UI/Controls/treegrid/TreeItemConverter.cs
HMI.UI/Controls/treegrid/TreeNodeExtend.cs
HMI.UI/Converter/CollectionHasItem.cs
HMI.UI/Converter/DataGridRowFlagConverter.cs
HMI.UI/Converter/DataGridRowNumberConverter.cs
HMI.UI/Converter/DateStrConverter.cs
HMI.UI/Converter/IndentConverter.cs
HMI.UI/Converter/PathImageConverter.cs
HMI.UI/Converter/RadioEnumConverter.cs
HMI.UI/Converter/ResultConverter.cs
HMI.UI/Converter/ReverseBoolConverter.cs
HMI.UI/Converter/VisibilityBoolConverter.cs
HMI.UI/DpUtil/CanvasHelper.cs
HMI.UI/DpUtil/DataGridHelper.cs
HMI.UI/DpUtil/DataGridTextColumnHelper.cs
HMI.UI/DpUtil/DpPlugin.cs
HMI.UI/DpUtil/FormGroupHelper.cs
HMI.UI/DpUtil/GridHelper.cs
HMI.UI/DpUtil/Resize.cs
HMI.UI/DpUtil/StackPanelHelper.cs
HMI.UI/DpUtil/TreeViewHelper.cs
HMI.UI/DpUtil/UniformGridHelper.cs
HMI.UI/EditBox.cs
HMI.UI/Entity/ScannerConfig.cs
HMI.UI/HMI.UI.csproj
HMI.UI/MDI/BottomBar.xaml
HMI.UI/MDI/BottomBar.xaml.cs
HMI.UI/MDI/InnerWindow.xaml
HMI.UI/MDI/InnerWindow.xaml.cs
HMI.UI/MDI/MinInnerWindow.xaml
HMI.UI/MDI/MinInnerWindow.xaml.cs
HMI.UI/MDI/Workspace.xaml
HMI.UI/MDI/Workspace.xaml.cs
HMI.UI/MaskControl.xaml
HMI.UI/MaskControl.xaml.cs
HMI.UI/MaskWindow.xaml
HMI.UI/MaskWindow.xaml.cs
HMI.UI/Properties/AssemblyInfo.cs
HMI.UI/Properties/Resources.Designer.cs
HMI.UI/Properties/Resources.resx
HMI.UI/Properties/Settings.Designer.cs
HMI.UI/Properties/Settings.settings
HMI.UI/RequirePasswordByUser.xaml
HMI.UI/RequirePasswordByUser.xaml.cs
HMI.UI/RequirePasswordWindow.xaml
HMI.UI/RequirePasswordWindow.xaml.cs
HMI.UI/Resources/Font/Font.xaml
HMI.UI/Resources/Font/iconfont.json
HMI.UI/Resources/Font/iconfont.ttf
HMI.UI/Resources/Icon/dot.png
HMI.UI/Resources/Icon/dot.svg
HMI.UI/Resources/Icon/dot2.png
HMI.UI/Resources/Icon/xinzeng_1.png
HMI.UI/ScannerSetting.xaml
HMI.UI/ScannerSetting.xaml.cs
HMI.UI/ScrollLabel.cs
HMI.UI/Style/BaseWindow.xaml
HMI.UI/Style/BaseWindow.xaml.cs
HMI.UI/Style/Colors.xaml
HMI.UI/Style/FormStyle.xaml
HMI.UI/Style/MsgBoxStyle.xaml
HMI.UI/Style/PanuonDataGrid.xaml
HMI.UI/Style/PanuonListView.xaml
HMI.UI/Style/PanuonResource.xaml
HMI.UI/Style/ScrollBarResource.xaml
HMI.UI/Style/WindowResources.xaml
HMI.UI/TableComboBox.cs
HMI.UI/Themes/Generic.xaml
HMI.UI/Util/IValueValidator.cs
HMI.UI/Util/MaskHolder.cs
HMI.UI/Util/MsgBoxHelper.cs
HMI.UI/Util/MsgBoxHelper2.cs
HMI.UI/Util/ToastHelper.cs
HMI.UI/packages.config
HMI.UnitTest/App.config
HMI.UnitTest/App.xaml
HMI.UnitTest/App.xaml.cs
HMI.UnitTest/HMI.UnitTest.csproj
HMI.UnitTest/MainWindow.xaml
HMI.UnitTest/MainWindow.xaml.cs
HMI.UnitTest/Properties/AssemblyInfo.cs
HMI.UnitTest/Properties/Resources.Designer.cs
HMI.UnitTest/Properties/Resources.resx
HMI.UnitTest/Properties/Settings.Designer.cs
HMI.UnitTest/Properties/Settings.settings
HMI.UnitTest/log4net.config
HMI.UnitTest/packages.config
HMI.Util/Alarm/AlarmUtil.cs
HMI.Util/DateUtil.cs
HMI.Util/DbFileHelper.cs
HMI.Util/ExcelHelper.cs
HMI.Util/HMI.Util.csproj
HMI.Util/IniFileHelper.cs
HMI.Util/Properties/AssemblyInfo.cs
HMI.Util/PropertyHelper.cs
HMI.Util/packages.config
Libs/25/NationalInstruments.TestStand.Interop.API.dll
Libs/25/NationalInstruments.TestStand.Interop.API.xml
Libs/25/NationalInstruments.TestStand.Interop.UI.AxControls.dll
Libs/25/NationalInstruments.TestStand.Interop.UI.Support.dll
Libs/25/NationalInstruments.TestStand.Interop.UI.Support.xml
Libs/25/NationalInstruments.TestStand.Interop.UI.dll
Libs/25/NationalInstruments.TestStand.Interop.UI.xml
Libs/25/NationalInstruments.TestStand.Utility.dll
Libs/25/NationalInstruments.TestStand.Utility.xml
Libs/DatabaseHelper.dll
Libs/HslCommunication.dll
Libs/Microsoft.DwayneNeed.Win32.dll
Libs/Microsoft.DwayneNeed.dll
Libs/NationalInstruments.TestStand.Interop.API.dll
Libs/NationalInstruments.TestStand.Interop.API.xml
Libs/NationalInstruments.TestStand.Interop.AdapterAPI.dll
Libs/NationalInstruments.TestStand.Interop.AdapterAPI.xml
Libs/NationalInstruments.TestStand.Interop.SequenceAnalyzer.dll
Libs/NationalInstruments.TestStand.Interop.SequenceAnalyzer.xml
Libs/NationalInstruments.TestStand.Interop.UI.AxControls.dll
Libs/NationalInstruments.TestStand.Interop.UI.Support.dll
Libs/NationalInstruments.TestStand.Interop.UI.Support.xml
Libs/NationalInstruments.TestStand.Interop.UI.dll
Libs/NationalInstruments.TestStand.Interop.UI.xml
Libs/NationalInstruments.TestStand.Utility.dll
Libs/NationalInstruments.TestStand.Utility.xml
Libs/SQLite.Interop.dll
Libs/SimensDriver.dll
Libs/SupersocketCommon.dll
Libs/System.Data.SQLite.EF6.dll
Libs/System.Data.SQLite.Linq.dll
Libs/System.Data.SQLite.dll
Libs/TSSync.Interop.dll
Libs/lmes.dll
ModBusTcpController/ModBusTcpController.cs
ModBusTcpController/ModBusTcpController.csproj
ModBusTcpController/Properties/AssemblyInfo.cs
OPCClient/Commond.cs
OPCClient/OPCClient.cs
OPCClient/OPCClient.csproj
OPCClient/Properties/AssemblyInfo.cs
OPCClient/packages.config
PLCHelper/BoolReader.cs
PLCHelper/Entity/PLCTagData.cs
PLCHelper/IntReader.cs
PLCHelper/ModbusHelper.cs
PLCHelper/NXI_5010Helper.cs
PLCHelper/PLCHelper.csproj
PLCHelper/Properties/AssemblyInfo.cs
PLCHelper/RestTcpClient.cs
PLCHelper/S7Context.cs
Plugin.BurnIn/AssemblyInfo.cs
Plugin.BurnIn/BurnInUI.xaml
Plugin.BurnIn/BurnInUI.xaml.cs
Plugin.BurnIn/ChartWindow.xaml
Plugin.BurnIn/ChartWindow.xaml.cs
Plugin.BurnIn/Constant/CommonConstant.cs
Plugin.BurnIn/Cooler.xaml
Plugin.BurnIn/Cooler.xaml.cs
Plugin.BurnIn/CoolerStatus.xaml
Plugin.BurnIn/CoolerStatus.xaml.cs
Plugin.BurnIn/DataTable.xaml
Plugin.BurnIn/DataTable.xaml.cs
Plugin.BurnIn/InfoTable.xaml
Plugin.BurnIn/InfoTable.xaml.cs
Plugin.BurnIn/InfoWindow.xaml
Plugin.BurnIn/InfoWindow.xaml.cs
Plugin.BurnIn/ManualDebug/ManualCell.xaml
Plugin.BurnIn/ManualDebug/ManualCell.xaml.cs
Plugin.BurnIn/ManualDebug/ManualDebug.xaml
Plugin.BurnIn/ManualDebug/ManualDebug.xaml.cs
Plugin.BurnIn/ManualDebug/ManualDebugStyle.xaml
Plugin.BurnIn/ManualDebug/ManualDebugWindow.xaml
Plugin.BurnIn/ManualDebug/ManualDebugWindow.xaml.cs
Plugin.BurnIn/ManualDebug/StatusBtn.xaml
Plugin.BurnIn/ManualDebug/StatusBtn.xaml.cs
Plugin.BurnIn/ManualDebug/StatusCell.xaml
Plugin.BurnIn/ManualDebug/StatusCell.xaml.cs
Plugin.BurnIn/Model/BaseConfig.cs
Plugin.BurnIn/Model/BaseModel.cs
Plugin.BurnIn/Model/CellInfo.cs
Plugin.BurnIn/Model/DataTableModel.cs
Plugin.BurnIn/Model/Info.cs
Plugin.BurnIn/Model/ScannerConfig.cs
Plugin.BurnIn/Model/TcpServer.cs
Plugin.BurnIn/Plugin.BurnIn.csproj
Plugin.BurnIn/Resource/abort.png
Plugin.BurnIn/Resource/alert.png
Plugin.BurnIn/Resource/barcode.png
Plugin.BurnIn/Resource/checkIn.png
Plugin.BurnIn/Resource/checkInFailed2.png
Plugin.BurnIn/Resource/ng2.png
Plugin.BurnIn/Resource/ok2.png
Plugin.BurnIn/Resource/ready.png
Plugin.BurnIn/Resource/testing2.png
Plugin.BurnIn/Resource/waiting2.png
Plugin.BurnIn/Scanner.xaml
Plugin.BurnIn/Scanner.xaml.cs
Plugin.BurnIn/ScannerSetting.xaml
Plugin.BurnIn/ScannerSetting.xaml.cs
Plugin.BurnIn/SingleCell.xaml
Plugin.BurnIn/SingleCell.xaml.cs
Plugin.BurnIn/TestResultView.xaml
Plugin.BurnIn/TestResultView.xaml.cs
Plugin.BurnIn/UI/LiveChart.xaml
Plugin.BurnIn/UI/LiveChart.xaml.cs
Plugin.BurnIn/Util/CommonCodeParser.cs
Plugin.BurnIn/Util/DpUtil.cs
Plugin.BurnIn/Util/FontSizeConverter.cs
Plugin.BurnIn/Util/ICodeParser.cs
Plugin.BurnIn/Util/JKCodeParser.cs
Plugin.BurnIn/Util/ModelCodeParser.cs
Plugin.BurnIn/Util/PNUtil.cs
Plugin.BurnIn/Util/RunTimeVariablePool.cs
Plugin.BurnIn/Util/SNValidator.cs
Plugin.BurnIn/Util/ScannerParser.cs
Plugin.CurrentSensorControl/AssemblyInfo.cs
Plugin.CurrentSensorControl/BurnInCell.xaml
Plugin.CurrentSensorControl/BurnInCell.xaml.cs
Plugin.CurrentSensorControl/BurnInOven.xaml
Plugin.CurrentSensorControl/BurnInOven.xaml.cs
Plugin.CurrentSensorControl/BurnInOvenCollection.xaml
Plugin.CurrentSensorControl/BurnInOvenCollection.xaml.cs
Plugin.CurrentSensorControl/BurnInSettingModal.xaml
Plugin.CurrentSensorControl/BurnInSettingModal.xaml.cs
Plugin.CurrentSensorControl/ControlPage - 复制.xaml
Plugin.CurrentSensorControl/ControlPage - 复制.xaml.cs
Plugin.CurrentSensorControl/ControlPage.xaml
Plugin.CurrentSensorControl/ControlPage.xaml.cs
Plugin.CurrentSensorControl/DataModal.xaml
Plugin.CurrentSensorControl/DataModal.xaml.cs
Plugin.CurrentSensorControl/Entity/BaseConfig.cs
Plugin.CurrentSensorControl/Entity/OPCContext.cs
Plugin.CurrentSensorControl/Entity/SensorData.cs
Plugin.CurrentSensorControl/Plugin.CurrentSensorControl.csproj
Plugin.CurrentSensorControl/SingleSensor.xaml
Plugin.CurrentSensorControl/SingleSensor.xaml.cs
Plugin.DOControl/ControlList.xaml
Plugin.DOControl/ControlList.xaml.cs
Plugin.DOControl/DOControl.xaml
Plugin.DOControl/DOControl.xaml.cs
Plugin.DOControl/DataModal.xaml
Plugin.DOControl/DataModal.xaml.cs
Plugin.DOControl/Entity/DOControlData.cs
Plugin.DOControl/Plugin.DOControl.csproj
Plugin.DOControl/Properties/AssemblyInfo.cs
Plugin.DOControl/Properties/Resources.Designer.cs
Plugin.DOControl/Properties/Resources.resx
Plugin.DOControl/Properties/Settings.Designer.cs
Plugin.DOControl/Properties/Settings.settings
Plugin.DOControl/packages.config
Plugin.DODriverControl/Api/Dask.cs
Plugin.DODriverControl/Api/DaskHelper.cs
Plugin.DODriverControl/Api/IDIOController.cs
Plugin.DODriverControl/Api/ModBusTcpController.cs
Plugin.DODriverControl/Api/PCIController.cs
Plugin.DODriverControl/AssemblyInfo.cs
Plugin.DODriverControl/ConfigModal.xaml
Plugin.DODriverControl/ConfigModal.xaml.cs
Plugin.DODriverControl/DOControl.xaml
Plugin.DODriverControl/DOControl.xaml.cs
Plugin.DODriverControl/DataModal.xaml
Plugin.DODriverControl/DataModal.xaml.cs
Plugin.DODriverControl/Entity/ConfigData.cs
Plugin.DODriverControl/Entity/DIOData.cs
Plugin.DODriverControl/Plugin.DODriverControl.csproj
Plugin.DODriverControl/SingleControl.xaml
Plugin.DODriverControl/SingleControl.xaml.cs
Plugin.DataBoard/DataBoard.xaml
Plugin.DataBoard/DataBoard.xaml.cs
Plugin.DataBoard/Libs/Static/echarts.min.js
Plugin.DataBoard/Libs/Static/test.html
Plugin.DataBoard/Libs/echarts.min.js
Plugin.DataBoard/OptionData.cs
Plugin.DataBoard/PassRateData.cs
Plugin.DataBoard/Plugin.DataBoard.csproj
Plugin.DataBoard/Properties/AssemblyInfo.cs
Plugin.DataBoard/Properties/Resources.Designer.cs
Plugin.DataBoard/Properties/Resources.resx
Plugin.DataBoard/Properties/Settings.Designer.cs
Plugin.DataBoard/Properties/Settings.settings
Plugin.DataBoard/SerialData.cs
Plugin.DataBoard/SimpleData.cs
Plugin.DataBoard/app.config
Plugin.DataBoard/packages.config
Plugin.DataViewer/AssemblyInfo.cs
Plugin.DataViewer/CustomSortColumn.cs
Plugin.DataViewer/DataViewerControl.xaml
Plugin.DataViewer/DataViewerControl.xaml.cs
Plugin.DataViewer/Plugin.DataViewer.csproj
Plugin.DeviceConfig/BatchGenerateModal.xaml
Plugin.DeviceConfig/BatchGenerateModal.xaml.cs
Plugin.DeviceConfig/DeviceConfig.xaml
Plugin.DeviceConfig/DeviceConfig.xaml.cs
Plugin.DeviceConfig/DeviceEditModal.xaml
Plugin.DeviceConfig/DeviceEditModal.xaml.cs
Plugin.DeviceConfig/Entity/DeviceChannel.cs
Plugin.DeviceConfig/Entity/DeviceConfigVersion.cs
Plugin.DeviceConfig/Entity/DeviceDefinition.cs
Plugin.DeviceConfig/Entity/DeviceGroupTemplate.cs
Plugin.DeviceConfig/Entity/SlotDeviceMapping.cs
Plugin.DeviceConfig/Model/DeviceConfigModel.cs
Plugin.DeviceConfig/Plugin.DeviceConfig.csproj
Plugin.DeviceConfig/VersionManageModal.xaml
Plugin.DeviceConfig/VersionManageModal.xaml.cs
Plugin.DeviceDebug/DeviceDebug.xaml
Plugin.DeviceDebug/DeviceDebug.xaml.cs
Plugin.DeviceDebug/Entity/DebugCommand.cs
Plugin.DeviceDebug/Entity/DebugOperation.cs
Plugin.DeviceDebug/Entity/DebugSession.cs
Plugin.DeviceDebug/Entity/DeviceRuntimeStatus.cs
Plugin.DeviceDebug/Model/DebugSessionManager.cs
Plugin.DeviceDebug/Model/DeviceDebugModel.cs
Plugin.DeviceDebug/Panels/CanDebugPanel.xaml
Plugin.DeviceDebug/Panels/CanDebugPanel.xaml.cs
Plugin.DeviceDebug/Panels/PlcDebugPanel.xaml
Plugin.DeviceDebug/Panels/PlcDebugPanel.xaml.cs
Plugin.DeviceDebug/Panels/PowerSupplyDebugPanel.xaml
Plugin.DeviceDebug/Panels/PowerSupplyDebugPanel.xaml.cs
Plugin.DeviceDebug/Plugin.DeviceDebug.csproj
Plugin.IntegrationTest/App.xaml
Plugin.IntegrationTest/App.xaml.cs
Plugin.IntegrationTest/MainWindow.xaml
Plugin.IntegrationTest/MainWindow.xaml.cs
Plugin.IntegrationTest/Plugin.IntegrationTest.csproj
Plugin.JarUI/JarCell.xaml
Plugin.JarUI/JarCell.xaml.cs
Plugin.JarUI/JarUI.xaml
Plugin.JarUI/JarUI.xaml.cs
Plugin.JarUI/Plugin.JarUI.csproj
Plugin.JarUI/Properties/AssemblyInfo.cs
Plugin.JarUI/Properties/Resources.Designer.cs
Plugin.JarUI/Properties/Resources.resx
Plugin.JarUI/Properties/Settings.Designer.cs
Plugin.JarUI/Properties/Settings.settings
Plugin.JarUI/packages.config
Plugin.ModelManage/AssemblyInfo.cs
Plugin.ModelManage/DataModal.xaml
Plugin.ModelManage/DataModal.xaml.cs
Plugin.ModelManage/Entity/ModelData.cs
Plugin.ModelManage/ModelManage.xaml
Plugin.ModelManage/ModelManage.xaml.cs
Plugin.ModelManage/Plugin.ModelManage.csproj
Plugin.QualityReport/AssemblyInfo.cs
Plugin.QualityReport/Libs/Static/CMK模板.xls
Plugin.QualityReport/Libs/Static/MSA模板.xlsx
Plugin.QualityReport/Libs/Static/重复性模板.xls
Plugin.QualityReport/Plugin.QualityReport.csproj
Plugin.QualityReport/QualityReport.xaml
Plugin.QualityReport/QualityReport.xaml.cs
Plugin.SeqEditor/AssemblyInfo.cs
Plugin.SeqEditor/Constant/DefaultStepName.cs
Plugin.SeqEditor/Constant/StepTypeStr.cs
Plugin.SeqEditor/Entity/EditablePO/IEPO.cs
Plugin.SeqEditor/Entity/EditablePO/NumValTestEPO.cs
Plugin.SeqEditor/Entity/EditablePO/StringValTestEPO.cs
Plugin.SeqEditor/Entity/EditablePO/ViCallEPO.cs
Plugin.SeqEditor/Entity/EditablePO/WaitEPO.cs
Plugin.SeqEditor/Entity/SeqUI.cs
Plugin.SeqEditor/Entity/StepUI.cs
Plugin.SeqEditor/Entity/VarUI.cs
Plugin.SeqEditor/Plugin.SeqEditor.csproj
Plugin.SeqEditor/RowNumberConverter.cs
Plugin.SeqEditor/SeqEditor.xaml
Plugin.SeqEditor/SeqEditor.xaml.cs
Plugin.SeqEditor/SettingPanel/ActionPanel.xaml
Plugin.SeqEditor/SettingPanel/ActionPanel.xaml.cs
Plugin.SeqEditor/SettingPanel/CallSeqPanel.xaml
Plugin.SeqEditor/SettingPanel/CallSeqPanel.xaml.cs
Plugin.SeqEditor/SettingPanel/CommonProperty.xaml
Plugin.SeqEditor/SettingPanel/CommonProperty.xaml.cs
Plugin.SeqEditor/SettingPanel/LoopPanel.xaml
Plugin.SeqEditor/SettingPanel/LoopPanel.xaml.cs
Plugin.SeqEditor/SettingPanel/NumValTestPanel.xaml
Plugin.SeqEditor/SettingPanel/NumValTestPanel.xaml.cs
Plugin.SeqEditor/SettingPanel/StringValTestPanel.xaml
Plugin.SeqEditor/SettingPanel/StringValTestPanel.xaml.cs
Plugin.SeqEditor/SettingPanel/ViPanel.xaml
Plugin.SeqEditor/SettingPanel/ViPanel.xaml.cs
Plugin.SeqEditor/SettingPanel/WaitPanel.xaml
Plugin.SeqEditor/SettingPanel/WaitPanel.xaml.cs
Plugin.SeqEditor/Util/ItemData.cs
Plugin.SeqEditor/Util/PoExplain.cs
Plugin.SeqEditor/Util/Step2EPO.cs
Plugin.SeqEditor/ViewModel/SeqEditorViewModel.cs
Plugin.SeqEditor/ViewModel/StepDropHandler.cs
Plugin.SeqEditor/ViewModel/TreeListViewHandler.cs
Plugin.StandardTest/AssemblyInfo.cs
Plugin.StandardTest/Entity/TestStep.cs
Plugin.StandardTest/Plugin.StandardTest.csproj
Plugin.StandardTest/Scanner.xaml
Plugin.StandardTest/Scanner.xaml.cs
Plugin.StandardTest/ScannerManager.cs
Plugin.StandardTest/ScreenGrid.xaml
Plugin.StandardTest/ScreenGrid.xaml.cs
Plugin.StandardTest/ScreenTouch.xaml
Plugin.StandardTest/ScreenTouch.xaml.cs
Plugin.StandardTest/ScreenTouchWin.xaml
Plugin.StandardTest/ScreenTouchWin.xaml.cs
Plugin.StandardTest/Setting/TcpServer.cs
Plugin.StandardTest/Setting/TsSetting.cs
Plugin.StandardTest/SingleControl.xaml
Plugin.StandardTest/SingleControl.xaml.cs
Plugin.StandardTest/StandardTestUI.xaml
Plugin.StandardTest/StandardTestUI.xaml.cs
Plugin.StandardTest/WorkOrderWin.xaml
Plugin.StandardTest/WorkOrderWin.xaml.cs
Plugin.TestPlanEditor/AssemblyInfo.cs
Plugin.TestPlanEditor/EditorUI.xaml
Plugin.TestPlanEditor/EditorUI.xaml.cs
Plugin.TestPlanEditor/Plugin.TestPlanEditor.csproj
Plugin.TestResult/CSVPrinter.cs
Plugin.TestResult/Chart.xaml
Plugin.TestResult/Chart.xaml.cs
Plugin.TestResult/ChartWindow.xaml
Plugin.TestResult/ChartWindow.xaml.cs
Plugin.TestResult/Config/log4net.config
Plugin.TestResult/Config/log4net.unix.config
Plugin.TestResult/Constant/CommonConstant.cs
Plugin.TestResult/Cooler.xaml
Plugin.TestResult/Cooler.xaml.cs
Plugin.TestResult/CoolerStatus.xaml
Plugin.TestResult/CoolerStatus.xaml.cs
Plugin.TestResult/DataTable.xaml
Plugin.TestResult/DataTable.xaml.cs
Plugin.TestResult/InfoTable.xaml
Plugin.TestResult/InfoTable.xaml.cs
Plugin.TestResult/InfoWindow.xaml
Plugin.TestResult/InfoWindow.xaml.cs
Plugin.TestResult/LiveChart.xaml
Plugin.TestResult/LiveChart.xaml.cs
Plugin.TestResult/Lixun/SNValidator.cs
Plugin.TestResult/ManualDebugWindow.xaml
Plugin.TestResult/ManualDebugWindow.xaml.cs
Plugin.TestResult/Model/BaseConfig.cs
Plugin.TestResult/Model/BaseModel.cs
Plugin.TestResult/Model/CellInfo.cs
Plugin.TestResult/Model/DataTableModel.cs
Plugin.TestResult/Model/Info.cs
Plugin.TestResult/Model/ScannerConfig.cs
Plugin.TestResult/Model/TcpServer.cs
Plugin.TestResult/Plugin.TestResult.csproj
Plugin.TestResult/Properties/AssemblyInfo.cs
Plugin.TestResult/Properties/Resources.Designer.cs
Plugin.TestResult/Properties/Resources.resx
Plugin.TestResult/Properties/Settings.Designer.cs
Plugin.TestResult/Properties/Settings.settings
Plugin.TestResult/Resource/abort.png
Plugin.TestResult/Resource/alert.png
Plugin.TestResult/Resource/barcode.png
Plugin.TestResult/Resource/checkIn.png
Plugin.TestResult/Resource/checkInFailed2.png
Plugin.TestResult/Resource/ng2.png
Plugin.TestResult/Resource/ok2.png
Plugin.TestResult/Resource/ready.png
Plugin.TestResult/Resource/testing2.png
Plugin.TestResult/Resource/waiting2.png
Plugin.TestResult/Scanner.xaml
Plugin.TestResult/Scanner.xaml.cs
Plugin.TestResult/ScannerSetting.xaml
Plugin.TestResult/ScannerSetting.xaml.cs
Plugin.TestResult/SingleCell.xaml
Plugin.TestResult/SingleCell.xaml.cs
Plugin.TestResult/TestResultView.xaml
Plugin.TestResult/TestResultView.xaml.cs
Plugin.TestResult/Util/CommonCodeParser.cs
Plugin.TestResult/Util/DpUtil.cs
Plugin.TestResult/Util/FontSizeConverter.cs
Plugin.TestResult/Util/ICodeParser.cs
Plugin.TestResult/Util/JKCodeParser.cs
Plugin.TestResult/Util/ModelCodeParser.cs
Plugin.TestResult/Util/PNUtil.cs
Plugin.TestResult/Util/RunTimeVariablePool.cs
Plugin.TestResult/Util/ScannerParser.cs
Plugin.TestResult/ViewControl.xaml
Plugin.TestResult/ViewControl.xaml.cs
Plugin.TestResult/packages.config
Plugin.TsExecution/Entity/TestStep.cs
Plugin.TsExecution/OpenTK.dll.config
Plugin.TsExecution/Plugin.TsExecution.csproj
Plugin.TsExecution/Properties/AssemblyInfo.cs
Plugin.TsExecution/Properties/Resources.Designer.cs
Plugin.TsExecution/Properties/Resources.resx
Plugin.TsExecution/Properties/Settings.Designer.cs
Plugin.TsExecution/Properties/Settings.settings
Plugin.TsExecution/Scanner.xaml
Plugin.TsExecution/Scanner.xaml.cs
Plugin.TsExecution/ScannerManager.cs
Plugin.TsExecution/ScreenGrid.xaml
Plugin.TsExecution/ScreenGrid.xaml.cs
Plugin.TsExecution/ScreenTouch.xaml
Plugin.TsExecution/ScreenTouch.xaml.cs
Plugin.TsExecution/ScreenTouchWin.xaml
Plugin.TsExecution/ScreenTouchWin.xaml.cs
Plugin.TsExecution/Setting/TsSetting.cs
Plugin.TsExecution/SingleControl.xaml
Plugin.TsExecution/SingleControl.xaml.cs
Plugin.TsExecution/TsExecution.xaml
Plugin.TsExecution/TsExecution.xaml.cs
Plugin.TsExecution/WorkOrderWin.xaml
Plugin.TsExecution/WorkOrderWin.xaml.cs
Plugin.TsExecution/packages.config
Plugin.TsSupport/ITestExecutionWrapper.cs
Plugin.TsSupport/Plugin.TsSupport.csproj
Plugin.TsSupport/Properties/AssemblyInfo.cs
Plugin.TsSupport/TapWrapper.cs
Plugin.TsSupport/TestAppManager.cs
Plugin.TsSupport/TeststandWrapper.cs
Plugin.UICollection/AssemblyInfo.cs
Plugin.UICollection/Plugin.UICollection.csproj
Plugin.UICollection/TcpDebugger.xaml
Plugin.UICollection/TcpDebugger.xaml.cs
S7Driver/PLCS7Driver.cs
S7Driver/Properties/AssemblyInfo.cs
S7Driver/S7Driver.csproj
SimpleUtil/DIOController/DIOController.csproj
SimpleUtil/DIOController/DIOControllerCreator.cs
SimpleUtil/DIOController/Dask.cs
SimpleUtil/DIOController/DaskHelper.cs
SimpleUtil/DIOController/IDIOController.cs
SimpleUtil/DIOController/IniFileHelper.cs
SimpleUtil/DIOController/ModBusTcpController.cs
SimpleUtil/DIOController/PCI-Dask64.dll
SimpleUtil/DIOController/PCIController.cs
SimpleUtil/DIOController/Properties/AssemblyInfo.cs
SimpleUtil/DIOController/template.ini
SimpleUtil/HMI.LogUtil/HMI.LogUtil.csproj
SimpleUtil/HMI.LogUtil/LogHelper.cs
SimpleUtil/HMI.LogUtil/Properties/AssemblyInfo.cs
SimpleUtil/HMI.MQUtil/HMI.MQUtil.csproj
SimpleUtil/HMI.MQUtil/MQConnector.cs
SimpleUtil/HMI.MQUtil/Properties/AssemblyInfo.cs
SimpleUtil/HMI.SerialData.Util/HMI.SerialData.Util.csproj
SimpleUtil/HMI.SerialData.Util/Properties/AssemblyInfo.cs
SimpleUtil/HMI.SerialData.Util/SerialDataHelper.cs
SimpleUtil/HMI.Simple.Util/AlarmUtil.cs
SimpleUtil/HMI.Simple.Util/DataSender.cs
SimpleUtil/HMI.Simple.Util/HMI.Simple.Util.csproj
SimpleUtil/HMI.Simple.Util/MSMQHelper.cs
SimpleUtil/HMI.Simple.Util/Parameter.cs
SimpleUtil/HMI.Simple.Util/ParameterUtil.cs
SimpleUtil/HMI.Simple.Util/ProductModelHelper.cs
SimpleUtil/HMI.Simple.Util/ProductUtil.cs
SimpleUtil/HMI.Simple.Util/Properties/AssemblyInfo.cs
SimpleUtil/HMI.Simple.Util/SerialDataHelper.cs
SimpleUtil/HMI.Simple.Util/SqliteHelper.cs
SimpleUtil/HMI.Simple.Util/XmlHelper.cs
SimpleUtil/HMI.TapApi/HMI.TapApi.csproj
SimpleUtil/HMI.TapApi/ProductUtil.cs
SimpleUtil/HMI.TapApi/Properties/AssemblyInfo.cs
SimpleUtil/HMI.Util.Base/HMI.Util.Base.csproj
SimpleUtil/HMI.Util.Base/Properties/AssemblyInfo.cs
SimpleUtil/HMI.Util.Base/RegUtil.cs
StationGlobalsReader/App.xaml
StationGlobalsReader/App.xaml.cs
StationGlobalsReader/AssemblyInfo.cs
StationGlobalsReader/MainWindow.xaml
StationGlobalsReader/MainWindow.xaml.cs
StationGlobalsReader/StationGlobalsReader.csproj
Util.ManualDebug/AssemblyInfo.cs
Util.ManualDebug/ManualCell.xaml
Util.ManualDebug/ManualCell.xaml.cs
Util.ManualDebug/ManualDebug.xaml
Util.ManualDebug/ManualDebug.xaml.cs
Util.ManualDebug/ManualDebugStyle.xaml
Util.ManualDebug/ManualDebugWindow.xaml
Util.ManualDebug/ManualDebugWindow.xaml.cs
Util.ManualDebug/Model/CellConfig.cs
Util.ManualDebug/Model/TcpServer.cs
Util.ManualDebug/OldManualDebug.xaml
Util.ManualDebug/OldManualDebug.xaml.cs
Util.ManualDebug/StatusBtn.xaml
Util.ManualDebug/StatusBtn.xaml.cs
Util.ManualDebug/StatusCell.xaml
Util.ManualDebug/StatusCell.xaml.cs
Util.ManualDebug/Util.ManualDebug.csproj
WPFProject/Api/SQLite.Interop.dll
WPFProject/App.config
WPFProject/App.xaml
WPFProject/App.xaml.cs
WPFProject/BuildCmd.bat
WPFProject/Config/Database/database.db
WPFProject/Config/Put Your Config Here.txt
WPFProject/Config/log4net.config
WPFProject/Controls/DpPlugin.cs
WPFProject/Controls/HolderForm.Designer.cs
WPFProject/Controls/HolderForm.cs
WPFProject/Controls/HolderForm.resx
WPFProject/Controls/IconTabItem.cs
WPFProject/Controls/MainTabControl.xaml
WPFProject/Controls/MainTabControl.xaml.cs
WPFProject/Controls/MaskControl.Designer.cs
WPFProject/Controls/MaskControl.cs
WPFProject/Controls/MenuTabControl.xaml
WPFProject/Controls/MenuTabControl.xaml.cs
WPFProject/Controls/WinFormHolder.xaml
WPFProject/Controls/WinFormHolder.xaml.cs
WPFProject/Entity/PluginLoadMessage.cs
WPFProject/Icon/JingCeLogo.png
WPFProject/Icon/JingCeLogo2.png
WPFProject/Icon/LoginLogo.png
WPFProject/Icon/b1.jpg
WPFProject/Icon/b2.jpg
WPFProject/Icon/b3.jpg
WPFProject/Icon/b4.jpg
WPFProject/Icon/pic1.png
WPFProject/Icon/t1.png
WPFProject/Icon/t2.png
WPFProject/Icon/t3.png
WPFProject/Icon/t4.png
WPFProject/Icon/xinzeng_1.png
WPFProject/JingCeLogo2.ico
WPFProject/Lang/en-US/AlarmInfoManage.json
WPFProject/Lang/en-US/ChangeModel.json
WPFProject/Lang/en-US/DOControl.json
WPFProject/Lang/en-US/DataBoard.json
WPFProject/Lang/en-US/DataViewer.json
WPFProject/Lang/en-US/IOMonitor.json
WPFProject/Lang/en-US/LogViewer.json
WPFProject/Lang/en-US/ParamManage.json
WPFProject/Lang/en-US/SeqEditor.json
WPFProject/Lang/en-US/ServoManage.json
WPFProject/Lang/en-US/TestExecution.json
WPFProject/Lang/en-US/TestResult.json
WPFProject/Lang/en-US/app.json
WPFProject/Lang/en-US/setting.json
WPFProject/Lang/zh-CN/AlarmInfoManage.json
WPFProject/Lang/zh-CN/ChangeModel.json
WPFProject/Lang/zh-CN/DOControl.json
WPFProject/Lang/zh-CN/DataBoard.json
WPFProject/Lang/zh-CN/DataViewer.json
WPFProject/Lang/zh-CN/IOMonitor.json
WPFProject/Lang/zh-CN/LogViewer.json
WPFProject/Lang/zh-CN/ParamManage.json
WPFProject/Lang/zh-CN/SeqEditor.json
WPFProject/Lang/zh-CN/ServoManage.json
WPFProject/Lang/zh-CN/TestExecution.json
WPFProject/Lang/zh-CN/TestResult.json
WPFProject/Lang/zh-CN/app.json
WPFProject/Lang/zh-CN/setting.json
WPFProject/LoadingWindow.xaml
WPFProject/LoadingWindow.xaml.cs
WPFProject/Login.xaml
WPFProject/Login.xaml.cs
WPFProject/MigrationBackup/fde93b81/VisionBuilder.Core/NuGetUpgradeLog.html
WPFProject/MigrationBackup/fde93b81/VisionBuilder.Core/VisionBuilder.Core.csproj
WPFProject/MigrationBackup/fde93b81/VisionBuilder.Core/packages.config
WPFProject/PanuonWindow.xaml
WPFProject/PanuonWindow.xaml.cs
WPFProject/Properties/AssemblyInfo.cs
WPFProject/Properties/Resources.Designer.cs
WPFProject/Properties/Resources.resx
WPFProject/Properties/Settings.Designer.cs
WPFProject/Properties/Settings.settings
WPFProject/Properties/app.manifest
WPFProject/ResourceListener.cs
WPFProject/Resources/Background.png
WPFProject/Resources/JingCeLogo.png
WPFProject/SQLite.Interop.dll
WPFProject/Settings.ini
WPFProject/Style/CommonTree.xaml
WPFProject/Style/ListViewStyle.xaml
WPFProject/Style/MainTab.xaml
WPFProject/SuperSocket.SocketEngine.dll
WPFProject/TapProject.csproj
WPFProject/TapProject.sln
WPFProject/UI/ResourceUI.xaml
WPFProject/UI/ResourceUI.xaml.cs
WPFProject/UI/TestWindow.xaml
WPFProject/UI/TestWindow.xaml.cs
WPFProject/UI/WorkWindow.xaml
WPFProject/UI/WorkWindow.xaml.cs
WPFProject/Utils/BinUtil.cs
WPFProject/Utils/CommonLogger.cs
WPFProject/Utils/Encryptor.cs
WPFProject/Utils/LogCleanner.cs
WPFProject/Utils/MainToastHandler.cs
WPFProject/Utils/Win32Helper.cs
WPFProject/packages.config
WPFProject/x86/SQLite.Interop.dll
WPFProject/变更记录/2025-12-24.txt
WPFProject/安装MSMQ.bat
check_parent.py
debug_check.py
docs/TapProject_代码结构.drawio
docs/TapProject_代码结构_01.drawio.png
docs/TapProject_代码结构_01_预览.png
docs/TapProject_代码结构_02.drawio.png
docs/TapProject_代码结构_02_预览.png
docs/TapProject_代码结构分析.md
fix_menu.py |