WPF 上位机开发模板
WPF上位机开发模板,集成了基础操作菜单、海康视觉实时图像界面、串口通讯、网口通讯、主流PLC通讯、数据存储、图片存储、参数配置、权限管理、第三方webapi接口接入、数据追溯与查询等功能。
一、项目结构
WpfSupervisor/
├── Models/ # 数据模型
│ ├── DeviceModels.cs
│ ├── ImageModel.cs
│ ├── LogModel.cs
│ ├── ParameterModel.cs
│ └── UserModel.cs
├── Services/ # 服务层
│ ├── Communication/
│ │ ├── ComService.cs
│ │ ├── EthernetService.cs
│ │ ├── PlcService.cs
│ │ └── WebApiService.cs
│ ├── Database/
│ │ ├── DatabaseService.cs
│ │ └── ImageStorage.cs
│ ├── HikVision/
│ │ └── HikVisionService.cs
│ ├── Security/
│ │ ├── AuthService.cs
│ │ └── PermissionService.cs
│ └── Utility/
│ ├── ConfigManager.cs
│ └── Logger.cs
├── ViewModels/ # 视图模型
│ ├── MainViewModel.cs
│ ├── CommunicationViewModel.cs
│ ├── ImageViewModel.cs
│ ├── ParameterViewModel.cs
│ └── UserViewModel.cs
├── Views/ # 视图
│ ├── MainWindow.xaml
│ ├── CommunicationView.xaml
│ ├── ImageView.xaml
│ ├── ParameterView.xaml
│ └── LoginView.xaml
├── Helpers/ # 辅助类
│ ├── RelayCommand.cs
│ └── EnumExtensions.cs
└── App.xaml.cs # 应用程序入口
二、核心代码实现
1. 数据模型 (Models/)
// DeviceModels.cs
public class PlcDevice
{
public string Id { get; set; }
public string Name { get; set; }
public string IpAddress { get; set; }
public int Port { get; set; }
public string Protocol { get; set; } // Modbus, S7, etc.
}
public class SerialDevice
{
public string Id { get; set; }
public string Name { get; set; }
public string PortName { get; set; }
public int BaudRate { get; set; }
public Parity Parity { get; set; }
public int DataBits { get; set; }
public StopBits StopBits { get; set; }
}
// ImageModel.cs
public class CapturedImage
{
public string Id { get; set; }
public byte[] ImageData { get; set; }
public DateTime CaptureTime { get; set; }
public string DeviceId { get; set; }
public string FilePath { get; set; }
}
// LogModel.cs
public class SystemLog
{
public string Id { get; set; }
public DateTime Timestamp { get; set; }
public string Level { get; set; } // Info, Warning, Error
public string Message { get; set; }
public string UserId { get; set; }
}
// ParameterModel.cs
public class SystemParameter
{
public string Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public string Description { get; set; }
public string Category { get; set; }
}
// UserModel.cs
public class User
{
public string Id { get; set; }
public string Username { get; set; }
public string PasswordHash { get; set; }
public string FullName { get; set; }
public string Role { get; set; } // Admin, Operator, etc.
public DateTime LastLogin { get; set; }
}
2. 服务层 (Services/)
2.1 通信服务
// ComService.cs
public class ComService : IDisposable
{
private SerialPort _serialPort;
public event Action<string> DataReceived;
public bool IsOpen => _serialPort?.IsOpen ?? false;
public void Open(SerialDevice device)
{
_serialPort = new SerialPort(device.PortName, device.BaudRate, device.Parity, device.DataBits, device.StopBits);
_serialPort.DataReceived += (s, e) =>
{
try
{
var data = _serialPort.ReadExisting();
DataReceived?.Invoke(data);
}
catch (Exception ex)
{
Logger.LogError($"串口数据接收错误: {ex.Message}");
}
};
_serialPort.Open();
}
public void Close() => _serialPort?.Close();
public void Send(string data) => _serialPort?.Write(data);
public void Dispose() => Close();
}
// EthernetService.cs
public class EthernetService : IDisposable
{
private TcpClient _tcpClient;
private NetworkStream _stream;
public event Action<string> DataReceived;
public bool IsConnected => _tcpClient?.Connected ?? false;
public async Task ConnectAsync(string ipAddress, int port)
{
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(ipAddress, port);
_stream = _tcpClient.GetStream();
_ = ReceiveDataAsync();
}
private async Task ReceiveDataAsync()
{
try
{
var buffer = new byte[1024];
while (_stream != null && _stream.CanRead)
{
int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);
var data = Encoding.ASCII.GetString(buffer, 0, bytesRead);
DataReceived?.Invoke(data);
}
}
catch (Exception ex)
{
Logger.LogError($"以太网数据接收错误: {ex.Message}");
}
}
public void Send(string data)
{
if (_stream == null || !_stream.CanWrite) return;
var bytes = Encoding.ASCII.GetBytes(data);
_stream.Write(bytes, 0, bytes.Length);
}
public void Disconnect() => _tcpClient?.Close();
public void Dispose()
{
Disconnect();
_stream?.Close();
}
}
// PlcService.cs (使用S7.Net库示例)
public class PlcService : IDisposable
{
private S7.Net.PLC _plc;
public event Action<string> DataReceived;
public bool IsConnected => _plc?.IsConnected ?? false;
public async Task ConnectAsync(PlcDevice device)
{
_plc = new S7.Net.PLC(device.Protocol == "S7"
? S7.Net.CpuType.S71200
: S7.Net.CpuType.S7300,
device.IpAddress, device.Port);
await Task.Run(() => _plc.Open());
}
public async Task<T> ReadAsync<T>(string address)
{
if (!IsConnected) throw new InvalidOperationException("PLC未连接");
return await Task.Run(() => (T)_plc.Read(address));
}
public async Task WriteAsync<T>(string address, T value)
{
if (!IsConnected) throw new InvalidOperationException("PLC未连接");
await Task.Run(() => _plc.Write(address, value));
}
public void Dispose() => _plc?.Close();
}
// WebApiService.cs
public class WebApiService
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
public WebApiService(string baseUrl)
{
_baseUrl = baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/";
_httpClient = new HttpClient();
}
public async Task<T> GetAsync<T>(string endpoint)
{
var response = await _httpClient.GetAsync(_baseUrl + endpoint);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<T>();
}
public async Task PostAsync<T>(string endpoint, object data)
{
var content = new StringContent(JsonConvert.SerializeObject(data),
Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(_baseUrl + endpoint, content);
response.EnsureSuccessStatusCode();
}
}
2.2 数据库服务
// DatabaseService.cs
public class DatabaseService
{
private readonly string _connectionString;
private SQLiteConnection _connection;
public DatabaseService(string dbPath)
{
_connectionString = $"Data Source={dbPath};Version=3;";
InitializeDatabase();
}
private void InitializeDatabase()
{
_connection = new SQLiteConnection(_connectionString);
_connection.Open();
// 创建表
ExecuteNonQuery(@"
CREATE TABLE IF NOT EXISTS SystemLogs (
Id TEXT PRIMARY KEY,
Timestamp TEXT,
Level TEXT,
Message TEXT,
UserId TEXT
);
CREATE TABLE IF NOT EXISTS SystemParameters (
Id TEXT PRIMARY KEY,
Key TEXT,
Value TEXT,
Description TEXT,
Category TEXT
);
CREATE TABLE IF NOT EXISTS Users (
Id TEXT PRIMARY KEY,
Username TEXT,
PasswordHash TEXT,
FullName TEXT,
Role TEXT,
LastLogin TEXT
);
");
}
public void ExecuteNonQuery(string sql, params object[] parameters)
{
using (var cmd = new SQLiteCommand(sql, _connection))
{
for (int i = 0; i < parameters.Length; i++)
{
cmd.Parameters.AddWithValue($"@p{i}", parameters[i]);
}
cmd.ExecuteNonQuery();
}
}
public T ExecuteScalar<T>(string sql, params object[] parameters)
{
using (var cmd = new SQLiteCommand(sql, _connection))
{
for (int i = 0; i < parameters.Length; i++)
{
cmd.Parameters.AddWithValue($"@p{i}", parameters[i]);
}
return (T)cmd.ExecuteScalar();
}
}
public DataTable ExecuteQuery(string sql, params object[] parameters)
{
using (var cmd = new SQLiteCommand(sql, _connection))
{
for (int i = 0; i < parameters.Length; i++)
{
cmd.Parameters.AddWithValue($"@p{i}", parameters[i]);
}
var adapter = new SQLiteDataAdapter(cmd);
var table = new DataTable();
adapter.Fill(table);
return table;
}
}
public void Dispose()
{
_connection?.Close();
}
}
// ImageStorage.cs
public class ImageStorage
{
private readonly string _imageFolderPath;
private readonly DatabaseService _dbService;
public ImageStorage(string folderPath, DatabaseService dbService)
{
_imageFolderPath = folderPath;
Directory.CreateDirectory(_imageFolderPath);
_dbService = dbService;
}
public async Task SaveImageAsync(CapturedImage image)
{
// 保存到数据库
var imageId = Guid.NewGuid().ToString();
var parameters = new object[]
{
imageId,
image.ImageData != null ? Convert.ToBase64String(image.ImageData) : null,
image.CaptureTime.ToString("o"),
image.DeviceId,
image.FilePath
};
_dbService.ExecuteNonQuery(@"
INSERT INTO Images (Id, Data, CaptureTime, DeviceId, FilePath)
VALUES (@p0, @p1, @p2, @p3, @p4);
", parameters);
// 保存文件
if (image.ImageData != null)
{
var filePath = Path.Combine(_imageFolderPath, $"{imageId}.jpg");
await File.WriteAllBytesAsync(filePath, image.ImageData);
// 更新数据库中的文件路径
_dbService.ExecuteNonQuery(@"
UPDATE Images SET FilePath = @p0 WHERE Id = @p1;
", filePath, imageId);
}
}
public async Task<CapturedImage> GetImageAsync(string id)
{
var row = _dbService.ExecuteQuery(
"SELECT * FROM Images WHERE Id = @p0;", id).Rows[0];
return new CapturedImage
{
Id = row["Id"].ToString(),
ImageData = row["Data"] != DBNull.Value
? Convert.FromBase64String(row["Data"].ToString())
: null,
CaptureTime = DateTime.Parse(row["CaptureTime"].ToString()),
DeviceId = row["DeviceId"].ToString(),
FilePath = row["FilePath"]?.ToString()
};
}
}
2.3 海康视觉服务
// HikVisionService.cs
public class HikVisionService
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
private readonly string _username;
private readonly string _password;
public event Action<byte[]> ImageReceived;
public HikVisionService(string baseUrl, string username, string password)
{
_baseUrl = baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/";
_username = username;
_password = password;
_httpClient = new HttpClient();
// 登录
Login();
}
private void Login()
{
var loginData = new Dictionary<string, string>
{
{"action", "login"},
{"user", _username},
{"password", _password}
};
var content = new FormUrlEncodedContent(loginData);
var response = _httpClient.PostAsync(_baseUrl + "login", content).Result;
response.EnsureSuccessStatusCode();
}
public async Task StartRealTimeImage()
{
// 启动实时图像流
var streamResponse = await _httpClient.GetAsync($"{_baseUrl}stream");
streamResponse.EnsureSuccessStatusCode();
var stream = await streamResponse.Content.ReadAsStreamAsync();
// 处理图像流数据
using (var reader = new BinaryReader(stream))
{
while (true)
{
// 实际实现需要解析海康威视的流协议
// 这里简化处理
var buffer = reader.ReadBytes(1024);
if (buffer.Length > 0)
{
// 解码图像数据
var imageData = DecodeHikVisionImage(buffer);
ImageReceived?.Invoke(imageData);
}
}
}
}
private byte[] DecodeHikVisionImage(byte[] buffer)
{
// 实际实现需要根据海康威视的图像编码格式解码
// 这里简化处理,直接返回原始数据
return buffer;
}
}
2.4 安全服务
// AuthService.cs
public class AuthService
{
private readonly DatabaseService _dbService;
public AuthService(DatabaseService dbService)
{
_dbService = dbService;
}
public async Task<User> LoginAsync(string username, string password)
{
var userRow = _dbService.ExecuteQuery(
"SELECT * FROM Users WHERE Username = @p0;", username).Rows[0];
var user = MapUserFromRow(userRow);
// 验证密码
if (VerifyPassword(password, user.PasswordHash))
{
user.LastLogin = DateTime.UtcNow.ToString("o");
await UpdateUserAsync(user);
return user;
}
return null;
}
private bool VerifyPassword(string inputPassword, string storedHash)
{
// 实际实现应使用安全的密码哈希验证
// 这里简化处理
return inputPassword == storedHash;
}
public async Task<User> GetUserAsync(string userId)
{
var row = _dbService.ExecuteQuery(
"SELECT * FROM Users WHERE Id = @p0;", userId).Rows[0];
return MapUserFromRow(row);
}
private User MapUserFromRow(DataRow row)
{
return new User
{
Id = row["Id"].ToString(),
Username = row["Username"].ToString(),
PasswordHash = row["PasswordHash"].ToString(),
FullName = row["FullName"].ToString(),
Role = row["Role"].ToString(),
LastLogin = row["LastLogin"]?.ToString()
};
}
private async Task UpdateUserAsync(User user)
{
_dbService.ExecuteNonQuery(@"
UPDATE Users SET
LastLogin = @p0
WHERE Id = @p1;",
user.LastLogin, user.Id);
}
}
// PermissionService.cs
public class PermissionService
{
private readonly DatabaseService _dbService;
public PermissionService(DatabaseService dbService)
{
_dbService = dbService;
}
public async Task<bool> HasPermissionAsync(string userId, string permission)
{
// 从数据库查询用户权限
var hasPermission = await _dbService.ExecuteScalarAsync<bool>(
"SELECT COUNT(*) > 0 FROM UserPermissions WHERE UserId = @p0 AND Permission = @p1;",
userId, permission);
return hasPermission;
}
public async Task<IEnumerable<string>> GetUserPermissionsAsync(string userId)
{
var permissions = await _dbService.ExecuteQueryAsync(
"SELECT Permission FROM UserPermissions WHERE UserId = @p0;", userId);
return permissions.Select(r => r["Permission"].ToString());
}
}
3. 视图模型 (ViewModels/)
// MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
private readonly IEventAggregator _eventAggregator;
private readonly AuthService _authService;
private readonly PermissionService _permissionService;
private object _currentView;
private User _currentUser;
public object CurrentView
{
get => _currentView;
set { _currentView = value; OnPropertyChanged(); }
}
public User CurrentUser
{
get => _currentUser;
private set { _currentUser = value; OnPropertyChanged(); }
}
public ICommand LoginCommand { get; }
public ICommand LogoutCommand { get; }
public MainViewModel(
IEventAggregator eventAggregator,
AuthService authService,
PermissionService permissionService)
{
_eventAggregator = eventAggregator;
_authService = authService;
_permissionService = permissionService;
LoginCommand = new RelayCommand(Login);
LogoutCommand = new RelayCommand(Logout, CanLogout);
}
private async void Login()
{
// 实际实现应显示登录对话框
var loginView = new LoginView();
if (loginView.ShowDialog() == true)
{
var user = await _authService.LoginAsync(
loginView.Username,
loginView.Password);
if (user != null)
{
CurrentUser = user;
CurrentView = new ShellViewModel(_eventAggregator, user).View;
_eventAggregator.Publish(new UserLoggedInEvent(user));
}
}
}
private void Logout()
{
CurrentUser = null;
CurrentView = new LoginView();
_eventAggregator.Publish(new UserLoggedOutEvent());
}
private bool CanLogout() => CurrentUser != null;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// CommunicationViewModel.cs
public class CommunicationViewModel : INotifyPropertyChanged
{
private readonly ComService _comService;
private readonly EthernetService _ethernetService;
private readonly PlcService _plcService;
private readonly WebApiService _webApiService;
private SerialDevice _selectedSerialDevice;
private PlcDevice _selectedPlcDevice;
public ObservableCollection<SerialDevice> SerialDevices { get; } = new();
public ObservableCollection<PlcDevice> PlcDevices { get; } = new();
public SerialDevice SelectedSerialDevice
{
get => _selectedSerialDevice;
set
{
_selectedSerialDevice = value;
OnPropertyChanged();
OpenSerialPort();
}
}
public PlcDevice SelectedPlcDevice
{
get => _selectedPlcDevice;
set
{
_selectedPlcDevice = value;
OnPropertyChanged();
ConnectPlc();
}
}
public ICommand RefreshDevicesCommand { get; }
public ICommand SendSerialCommand { get; }
public ICommand ReadPlcCommand { get; }
public ICommand WritePlcCommand { get; }
public CommunicationViewModel(
ComService comService,
EthernetService ethernetService,
PlcService plcService,
WebApiService webApiService)
{
_comService = comService;
_ethernetService = ethernetService;
_plcService = plcService;
_webApiService = webApiService;
RefreshDevicesCommand = new RelayCommand(RefreshDevices);
SendSerialCommand = new RelayCommand(SendSerialData, CanSendSerial);
ReadPlcCommand = new RelayCommand(ReadPlcData, CanReadPlc);
WritePlcCommand = new RelayCommand(WritePlcData, CanWritePlc);
LoadDevices();
}