Models And Binding - AngryCarrot789/MemoryEngine360 GitHub Wiki
MemEngine360 uses a Code-Behind architecture; like MVC, except the views themselves are controllers.
- Models contain the properties and fire events when a property changes. When an event fires, it may mean different similar properties change
- The views add/remove event handlers on the models, and update the appropriate controls.
The IBinder
framework, found in the namespace PFXToolKitUI.Avalonia.Bindings
, aims to massively simplify binding model values to controls, and there's a lot of different binders for different scenarios.
When a binder becomes fully attached (as in, a control and model are attached), the first thing it does is update the control.
These are some of the common binders:
The most common binder used. Listens to a model's event and fires the updateControl callback. Uses the same updateControl/updateModel mechanism
Similar to EventUpdateBinder
but can listen to multiple events
Effectively binds an avalonia property to a CLR property. Uses two Action<IBinder<TModel>>
called updateControl
(required) and updateModel
(optional; maybe you want the control to be read only so it can't change the model). When updateModel is not used, consider EventUpdateBinder
instead
TextBox specific binders use a Func<IBinder<TModel>, string>
to get the text from a model, and an async function Func<IBinder<TModel>, string, Task>
to try and update the model based on the text in the text box, or do something like show the user a dialog containing the problem with the text.
We use this for the Start and Length fields in the Memory Scanning Options panel.
These are special radio button binders used to "assign" an enum value to a radio button so that, when that radio button becomes checked, the model's enum property is set to whatever value was assigned to that radio button.
Similar to AvaloniaPropertyToEventPropertyBinder
but uses getter/setter functions to get/set the model value.
Here are some examples of how to use binders
In this case, since we only want to update the TextBlock from the model, we'd use EventUpdateBinder
.
A simple way to use a binder is to attach/detach the control in the Loaded/Unloaded methods. Attaching/Detaching the model depends on how you want to associate the model with a control. If it's a standalone control, it might be an AvaloniaProperty, in which case, you'd attach/detach the model in the property change handler.
private readonly IBinder<MyDataModel> displayNameBinder =
new EventUpdateBinder<MyDataModel>(
nameof(MyDataModel.DisplayNameChanged),
(b) => ((TextBlock) b.Control).Text = b.Model.DisplayName);`
... define model property here perhaps ...
protected override void OnLoaded(RoutedEventArgs e) {
base.OnLoaded(e);
this.displayNameBinder.AttachControl(this);
}
protected override void OnUnloaded(RoutedEventArgs e) {
base.OnUnloaded(e);
this.displayNameBinder.DetachControl();
}
protected void OnModelChanged(MyDataModel? oldModel, MyDataModel? newModel) {
this.displayNameBinder.SwitchModel(newModel);
}
Or if you don't want to use OnLoaded/OnUnloaded, you can do this instead:
protected void OnModelChanged(MyDataModel? oldModel, MyDataModel? newModel) {
if (oldModel != null)
this.displayNameBinder.Detach();
if (newModel != null)
this.displayNameBinder.Attach(this, newModel);
}