Skip to main content

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​

APIPurpose
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}" />

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 CanExecute on [RelayCommand] and call NotifyCanExecuteChanged() on the generated command when channel state changes, or expose a ViewModel boolean property.
  • DataGrid flickers: use AsTableBindingSource instead of clearing and rebuilding rows on every Changed event.
  • Existing container integration: register IZeusHost, IChannelRegistry, or IPointTable as singletons; windows should only set DataContext.