Configuration Pages - AngryCarrot789/MemoryEngine360 GitHub Wiki
Configuration pages are presented in a configuration so that a user may modify settings. It also provides support for live changes until Cancel is clicked (which reverts them) or Apply/Save is clicked (when they're actually saved).
Page models are implemented via the ConfigurationPage
class. This base class contains the active ConfigurationContext
which is an object created whenever the settings dialog is opened and is used to track modified pages and the active page (currently being viewed).
The methods available are:
-
ValueTask OnContextCreated(ConfigurationContext)
: This is invoked recursively for every page in a configuration manager when a settings dialog is opened. This is where you can load data from the application or project into the state of the page, and also register event handlers for data changes, if you need to. -
ValueTask OnContextDestroyed(ConfigurationContext)
: This is invoked recursively for every page in a configuration manager when a settings dialog is closed. Here you can remove event handlers previous added inOnContextCreated
. -
ValueTask Apply(List<ApplyChangesFailureEntry>)
: Apply changes back into the application or project. The provided list is not fully implemented yet, however, it should be used instead of showing message dialogs, since it might annoy the user if there's 100s of errors that occur. So instead, all errors will be shown at once in a custom dialog using theApplyChangesFailureEntry
objects as the models. -
void OnActiveContextChanged(ConfigurationContext, ConfigurationContext)
: This is invoked when the viewed configuration page changes.newContext
is null when this page is no longer visible, and is non-null when this page is now being viewed. You may wish to implement the loading data behaviour in this method instead ofOnContextCreated
to help with performance. But beware, this method isn't async, since it is invoked during a UI input event (the tree node being clicked), so don't do anything too slow here
There's a singleton configuration page for the entire application stored in ApplicationConfigurationManager.Instance
. You can add your own configuration entries and pages in your plugin's OnApplicationLoading
method
The simplest way to create a configuration page would be to derive from PropertyEditorConfigurationPage
and use its property editor
But if you wish to implement a completely custom configuration page control (either XAML or declarative if you so please), you can register a mapping via the ConfigurationPageRegistry.Registry
, like so:
Registry.RegisterType<MyConfigurationPage>(
() => new MyConfigurationPageControl()
);
By doing this, you allow the UI to create your control when it tries to present your page. Page controls may be recycled, so it's important to override the OnConnected
and OnDisconnected
methods and add/remove event handlers or bindings accordingly to and from the page model.
MemEngine360 has a built-in system for loading and saving configurations, saving you from having to manage a file path and file IO yourself.
You create a type that derives from PersistentConfiguration
. Then you can register persistent properties via the PersistentProperty
class.
For example, say you want to save the location of the editor window:
public sealed class MainWindowConfigurationOptions : PersistentConfiguration {
// This lets us get/set the instance of this configuration, so that we can update
// the PosX/PosY properties when the window moves.
public static MainWindowConfigurationOptions Instance => Application.Instance.PersistentStorageManager.GetConfiguration<MainWindowConfigurationOptions>();
// Register the persistent properties.
public static readonly PersistentProperty<int> PosXProperty = PersistentProperty.RegisterParsable<int, MainWindowConfigurationOptions>(nameof(PosX), 0, o => o.posX, (o, val) => o.posX = val, false);
public static readonly PersistentProperty<int> PosYProperty = PersistentProperty.RegisterParsable<int, MainWindowConfigurationOptions>(nameof(PosY), 0, o => o.posY, (o, val) => o.posY = val, false);
// Value backing fields
private int posX, posY;
// Get/Set helpers
public int PosX {
get => PosXProperty.GetValue(this);
set => PosXProperty.SetValue(this, value);
}
public int PosY {
get => PosYProperty.GetValue(this);
set => PosYProperty.SetValue(this, value);
}
// Value change helpers. There are other ways of adding value change handlers too
public event PersistentPropertyInstanceValueChangeEventHandler<int>? PosXChanged {
add => PosXProperty.AddValueChangeHandler(this, value);
remove => PosXProperty.RemoveValueChangeHandler(this, value);
}
public event PersistentPropertyInstanceValueChangeEventHandler<int>? PosYChanged {
add => PosYProperty.AddValueChangeHandler(this, value);
remove => PosYProperty.RemoveValueChangeHandler(this, value);
}
public MainWindowConfigurationOptions() {
IMainWindowService.Instance.MainWindowCreatedOrShown += OnMainWindowCreatedOrShown;
}
private void OnMainWindowCreatedOrShown(IMainWindow window, bool isbeforeshow) {
if (!isbeforeshow) { // when false, the window is actually visible
window.WindowPosition = new SKPointI(this.PosX, this.PosY);
}
}
}
Then to register the config, override RegisterConfigurations
in your plugin class.
// The manager is the application's PSM,
// accessible directly via Application.Instance.PersistentStorageManager
public override void RegisterConfigurations(PersistentStorageManager manager) {
// Register our config.
// 'windowinfo' is the area. Areas are just files. There can be multiple configs per area.
// 'main' is the config name in the area.
manager.Register(new MainWindowConfigurationOptions(), "windowinfo", "main");
}