WPF 适配器
WPF 只走 MVVM。Zeus.Presentation.Wpf 负责窗口生命周期和 Dispatcher 封送;界面状态通过 ChannelBindingSource、PointBindingSource、PointTableBindingSource、PointAlarmBindingSource 暴露给 ViewModel,再由 XAML 绑定。
public partial class MainWindow : Window
{
private readonly MainWindowViewModel _viewModel;
public MainWindow(IZeusHost host, MainWindowViewModel viewModel)
{
InitializeComponent();
this.AttachZeus(host);
_viewModel = viewModel;
DataContext = _viewModel;
Closed += (_, _) => _viewModel.Dispose();
}
}
<TextBlock Text="{Binding Meter.StateText}" />
<TextBlock Text="{Binding Meter.LastText}" />
<TextBlock Text="{Binding Temperature.Text}" />
<TextBlock Text="{Binding Temperature.AlarmState}" />
程序集:Zeus.Presentation.Wpf(目标框架 net8.0-windows)。
API
| API | 作用 |
|---|---|
Window.AttachZeus(...) | 默认窗口 Loaded 时启动,Closed 时释放 |
host.AttachManually() | 只创建挂接,不绑窗口;适合登录后进入主界面或托盘常驻 |
WpfUiDispatcher.Current() | 捕获当前 WPF Dispatcher,给 ViewModel 的绑定源使用 |
host.Bind(dispatcher) | 只传一次调度器,后面用 Channel / Point / Table / Alarms |
channel.AsBindingSource(dispatcher) | 单条通道投影;多个源时优先用 host.Bind |
points.AsBindingSource("temperature", dispatcher) | 单点投影;多个点时优先用 _ui.Point(...) |
points.AsTableBindingSource(dispatcher) | 整张点表投影,按 BatchChanged 批量刷新 |
alarms.AsAlarmBindingSource(dispatcher) | 活动报警队列投影 |
WPF 包只保留 MVVM 所需能力。控件状态应从 ViewModel 属性或命令派生。
ViewModel 绑定源
ViewModel 用 host.Bind(dispatcher) 只传一次调度器。后面的通道和点都从上下文取,不必每个点再写 dispatcher;Dispose 上下文即可释放全部投影。
public sealed class MainWindowViewModel : IDisposable
{
private readonly ZeusBindingContext _ui;
public MainWindowViewModel(IZeusHost host, IUiDispatcher dispatcher)
{
_ui = host.Bind(dispatcher);
Meter = _ui.Channel("meter");
Temperature = _ui.Point("temperature", value => $"{value:0.0} C");
}
public ChannelBindingSource Meter { get; }
public PointBindingSource Temperature { get; }
public void Dispose() => _ui.Dispose();
}
ChannelBindingSource 的常用属性:Name、State、StateText、LastText、LastHex、ReceivedCount。
PointBindingSource 的常用属性:Name、QualifiedName、Value、Text、Error、HasError、AlarmState、IsAlarmed、UpdatedAt、Writable。
命令与写回
发送通道数据和点表写回都放在 ViewModel 命令里。推荐安装 CommunityToolkit.Mvvm,用 [RelayCommand] 生成 XAML 可绑定命令。
[RelayCommand]
private async Task SendAsync()
{
await _meter.WriteAsync(Encoding.UTF8.GetBytes(Payload));
}
[RelayCommand]
private async Task WriteSetpointAsync()
{
await Setpoint.WriteAsync(80.0);
}
<TextBox Text="{Binding Payload, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="发送" Command="{Binding SendCommand}" />
表格、趋势和报警
一屏只有温度、设定值这类几个点时,继续用上面的 AsBindingSource("temperature")。DataGrid、总貌或一屏很多点时,改用整表绑定源:它只是把内核的 BatchChanged 投影成可绑定列表,一轮采集只进 UI 线程一次。不要给每个点各建一个投影,也不要在每个 Changed 里清空重加。
整表 DataGrid 绑定 PointTableBindingSource.All。注意它每轮会拷贝整表;点很少时,这比改几个 TextBlock 更重。写回输入框仍用单点投影,避免整表刷新盖掉正在编辑的值。点表事件本身与界面无关,对照见 点表与采集。
Points = host.Points.AsTableBindingSource(dispatcher);
Alarms = host.Alarms.AsAlarmBindingSource(dispatcher);
<DataGrid ItemsSource="{Binding Points.All}" />
<ItemsControl ItemsSource="{Binding Alarms.Active}" />
趋势图或第三方图表由 ViewModel 订阅 host.Points.BatchChanged 后自行维护序列;单点调试曲线才订 Changed。Zeus 不内置点历史缓存,也不绑定 WPF 图表库。
接入已有 Prism / DI 项目
已有 Prism、DryIoc、Unity 或自建容器时,不需要让 Zeus 接管整个应用。推荐把 Zeus 注册成单例服务,ViewModel 依赖 IZeusHost、IChannelRegistry 或 IPointTable。
var zeus = ZeusHost.Create(builder =>
{
builder.AddSerialPort("meter", "COM3", 9600);
builder.AddAcquisition(TimeSpan.FromMilliseconds(500));
});
containerRegistry.RegisterInstance<IZeusHost>(zeus);
containerRegistry.RegisterInstance<IChannelRegistry>(zeus.Channels);
containerRegistry.RegisterInstance<IPointTable>(zeus.Points);
containerRegistry.RegisterInstance<IUiDispatcher>(WpfUiDispatcher.Current());
containerRegistry.Register<MainWindowViewModel>();
应用启动后调用 StartAsync,退出时释放宿主。ViewModel 通过构造函数接收 IZeusHost、IPointTable 或 IUiDispatcher,不要在窗口代码里手动 new ViewModel。
常见问题
- 属性没有刷新:确认 ViewModel 里给绑定源传了
WpfUiDispatcher.Current(),不要在后台线程捕获 Dispatcher。 - 按钮状态不变:给
[RelayCommand]配置CanExecute,通道状态变化时调用生成命令的NotifyCanExecuteChanged(),或暴露 ViewModel 布尔属性。 - DataGrid 频繁闪烁:优先使用
AsTableBindingSource,不要在每个Changed事件里清空重加。 - 已有容器怎么接:把
IZeusHost、IChannelRegistry或IPointTable注册成单例,窗口只负责设置DataContext。