WPF Adapter
WPF uses the MVVM path only. Zeus.Presentation.Wpf owns window lifecycle integration and Dispatcher marshaling; UI state is exposed to view models through ChannelBindingSource, PointBindingSource, PointTableBindingSource, and PointAlarmBindingSource, then rendered by normal XAML bindings.
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}" />
Assembly: Zeus.Presentation.Wpf with target framework net8.0-windows.
APIs
| API | Purpose |
|---|---|
Window.AttachZeus(...) | Starts on window Loaded, disposes on Closed by default |
host.AttachManually() | Creates the attachment without binding a window; useful after login or for tray apps |
WpfUiDispatcher.Current() | Captures the current WPF Dispatcher for binding sources in the ViewModel |
host.Bind(dispatcher) | Pass the dispatcher once, then use Channel / Point / Table / Alarms |
channel.AsBindingSource(dispatcher) | Single-channel projection; prefer host.Bind when you have several sources |
points.AsBindingSource("temperature", dispatcher) | Single-point projection; prefer _ui.Point(...) for several points |
points.AsTableBindingSource(dispatcher) | Whole point-table projection refreshed by BatchChanged |
alarms.AsAlarmBindingSource(dispatcher) | Active alarm-list projection |
The WPF package keeps the MVVM surface only. Control state should come from ViewModel properties or commands.
ViewModel Binding Sources
Call host.Bind(dispatcher) once. Channels and points then come from that context, so you do not pass the dispatcher again. Dispose the context to release every projection.
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();
}
Common ChannelBindingSource properties: Name, State, StateText, LastText, LastHex, and ReceivedCount.
Common PointBindingSource properties: Name, QualifiedName, Value, Text, Error, HasError, AlarmState, IsAlarmed, UpdatedAt, and Writable.
Commands And Writes
Channel sends and point writes belong in ViewModel commands. Install CommunityToolkit.Mvvm and prefer [RelayCommand] methods to generate XAML-bindable commands.
[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="Send" Command="{Binding SendCommand}" />
Tables, Trends, And Alarms
Keep AsBindingSource("temperature") when the screen only shows a few values such as temperature and setpoint. Use the table binding source for a DataGrid, overview, or many points on one screen: it only projects the core BatchChanged event into a bindable list and enters the UI thread once per poll. Do not create one projection per point, and do not clear and rebuild rows on every Changed.
Bind DataGrid to PointTableBindingSource.All. That source copies the whole table each poll; with only a few points this is heavier than updating a few TextBlocks. Keep write-back boxes on a single-point source so a table refresh cannot overwrite an in-progress edit. The point-table events themselves are UI-agnostic; see Point Tables and Acquisition.
Points = host.Points.AsTableBindingSource(dispatcher);
Alarms = host.Alarms.AsAlarmBindingSource(dispatcher);
<DataGrid ItemsSource="{Binding Points.All}" />
<ItemsControl ItemsSource="{Binding Alarms.Active}" />
For chart controls, let the ViewModel subscribe to host.Points.BatchChanged and maintain its own series. Subscribe to Changed only when debugging a single point. Zeus does not keep point-history buffers or include a WPF charting library.
Existing Prism / DI Apps
If you already use Prism, DryIoc, Unity, or your own container, keep that container in charge of windows, navigation, and view models. Register Zeus as a singleton service and inject IZeusHost, IChannelRegistry, or IPointTable into view models.
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>();
Start the host when the app is ready and dispose it on exit. ViewModels should receive IZeusHost, IPointTable, or IUiDispatcher through constructors; do not manually new view models in window code.
Troubleshooting
- Properties do not update: pass
WpfUiDispatcher.Current()to binding sources from the ViewModel on the UI thread. - Button state does not change: configure
CanExecuteon[RelayCommand]and callNotifyCanExecuteChanged()on the generated command when channel state changes, or expose a ViewModel boolean property. - DataGrid flickers: use
AsTableBindingSourceinstead of clearing and rebuilding rows on everyChangedevent. - Existing container integration: register
IZeusHost,IChannelRegistry, orIPointTableas singletons; windows should only setDataContext.