-
Notifications
You must be signed in to change notification settings - Fork 4
Custom Windows
This page describes how to show custom windows on desktop platforms that therefore have a IWindowManager service.
The window manager, defined by the IWindowManager interface, is a service for tracking opened windows, creating windows, accessing windows from an avalonia UI control, etc.
Currently, there is only a single implementation, and it is only supported on platforms that have native window support (i.e. avalonia's Window class is support).
Caution
Do not use Window directly, this may lead to unexpected problems (for example, app shutdown on main/all windows closed will not take into account windows shown outside of the IWindowManager)
Exposes properties of the window, some of which can be changed (e.g. title bar, title bar icon, etc.).
The window interface is defined in the PFXToolKitUI.Avalonia project, so it still has to be used from UI-based code, however, IWindow extends ITopLevel which is defined in the PFXToolKitUI project and can therefore be used with back-end code (for example, to access the clipboard for a copy command).
// Tries to find a window from (in this order)
// - Current command context (see CommandManager.LocalContextManager)
// - alternateContext parameter
// - Currently activated window or a main window via IWindowManager
IWindow? usefulParentWindow = WindowContextUtils.GetUsefulWindow(alternateContext: null);
if (usefulParentWindow != null) {
MyCoolControl myContent = new MyCoolControl() {
ModelObject = myModel
};
IWindow window = usefulParentWindow.WindowManager.CreateWindow(new WindowBuilder() {
Title = "My cool custom window",
Content = myContent,
TitleBarBrush = BrushManager.Instance.GetDynamicThemeBrush("ABrush.Tone4.Background.Static"),
BorderBrush = BrushManager.Instance.CreateConstant(SKColors.DodgerBlue),
MinWidth = 800, MinHeight = 480,
Width = 1280, Height = 720,
FocusPath = "CustomWindowFocusPath",
Parent = usefulParentWindow
});
window.WindowOpened += static (sender, args) => myContent.OnWindowOpened(sender);
window.WindowClosed += static (sender, args) => myContent.OnWindowClosed();
object? result = await window.ShowDialogAsync();
// check result, maybe result is a bool? DialogResult, so return that
return ...
}The lifespan of IWindow is simple: an "Open State", represented by the OpenState enum, which can only be one of NotOpened, Opening, Open, TryingToClose, Closing or Closed. A window is visible at the Open state, and becomes hidden at the Closed state.
A window can switch between Open and TryingToClose, see Window Closing Procedure for more info
There is a plethora of events that get fired during each state change, some of which are async events.
Closing a window is not as simple as invoking Close(), because asynchronous close handling and cancellation is supported. Instead, you ask the window to begin closing via the RequestCloseAsync() method (which can only be called when the open state is OpenState.Open), which will then change the open state to TryingToClose and, at some point in the future, begin the actual close operation.
The task returned by RequestCloseAsync() becomes completed either once the window actually closes, or the close operation was cancelled. If you wish to wait only until the window actually closes, then await WaitForClosedAsync()
If the window close operation is cancelled, the open state transitions back to Open. If it is not cancelled, then it transitions to Closing and eventually Closed.
Say we have a Notepad application, and the user attempts to close the window, but there are unsaved changes. You would handle the TryCloseAsync event and then show a confirmation dialog (Save? Discard Changes? Cancel?).
If the user clicked Cancel, then you would call the event args' WindowCancelCloseEventArgs.SetCancelled() method to prevent the window closing.
Say the user clicks Save, you don't want to save in TryCloseAsync in case something else cancels the operation (unless you know for certain nothing will). Instead, you may want to handle WindowClosingAsync, which is invoked once the window enters the Closing state. This is where you could save the file contents using something like File.WriteAllLinesAsync() and perhaps show a saving dialog.
Here is a code example:
if (!IWindowManager.TryGetInstance(out IWindowManager? manager)) {
return; // unsupported platform
}
// True = save, False = discard changes.
// DataKeys are singletons, so this should be a static readonly field
DataKey<bool> CloseUserOption = DataKey<bool>.Create("NotepadCloseUserOption");
IWindow window = manager.CreateWindow(new WindowBuilder() {
Title = "Notepad",
Content = new NotepadControl() {
Document = ApplicationDocument // example singleton document, i.e. Notepad.exe clone
},
MinWidth = 300, MinHeight = 200,
Width = 750, Height = 480
});
window.TryCloseAsync += async (TheWindow, args) => {
NotepadControl control = (NotepadControl) TheWindow.Content!;
if (!control.Document.HasUnsavedChanges) {
return; // no unsaved changes, do nothing
}
// We run this code in a command action because we want to set the "Global Context"
// as the context of the window, so that IMessageDialogService works properly
await CommandManager.Instance.RunActionAsync(async cmdArgs => {
// We rawdog the MessageBoxInfo so we can change the button text
MessageBoxInfo info = new MessageBoxInfo("Unsaved Changes", "Do you want to save your changes?") {
Buttons = MessageBoxButton.YesNoCancel,
DefaultButton = MessageBoxResult.Yes,
YesOkText = "Save", NoText = "Don't Save"
};
MessageBoxResult result = await IMessageDialogService.Instance.ShowMessage(info);
if (result == MessageBoxResult.Yes || result == MessageBoxResult.No) {
TheWindow.LocalContextData.Set(CloseUserOption, result == MessageBoxResult.Yes);
}
else {
// Either the dialog closed unexpectedly (perhaps external app force closed the window)
// Or the user clicked cancel or the close button.
args.SetCancelled();
}
}, context: TheWindow.LocalContextData);
};
window.WindowClosingAsync += async (TheWindow, args) => {
NotepadControl control = (NotepadControl) TheWindow.Content!;
if (CloseUserOption.TryGetContext(TheWindow.LocalContextData, out bool saveChanges)) {
if (saveChanges) {
await control.Document.SaveChangesAsync();
}
}
};
// No need to await, especially since we are probably calling from void method
_ = window.ShowAsync();-
Home
- Connect to a console
- Scanning Options
- Scan results & Saved Address Table
- Remote Commands
- Memory Dump
- Tools
- Preferences/App Settings
-
API
- Making a custom connection
- Busy Tokens
- Models, ViewStates, MVP & Binding
- Plugins
- Config Pages
- Brushes and Icons
- Data Manager, Context Data and Data Keys
- Commands and Shortcuts
- Context Menus
- Windows and Dialogs