Windows
io.Connect Windows
In order for windows created from external WPF or WinForms apps to become io.Connect Windows, they must first be registered via the .NET Window Management API.
Registering your .NET apps as io.Connect Windows must happen at the correct moment - when the process of creating and rendering your window has completed and you have a reference to its handle. For WPF apps, you should register the window in the Loaded
event, for WinForms apps - using the OnShown()
method. If you are using custom UI frameworks, you should make sure to choose an event where you are certain that all your components have been properly rendered and you have the window handle. If you try to register an io.Connect Window too early, the registration may fail or may interfere with the process of creating and rendering the components of your .NET app.
⚠️ Note that you should make sure you are running io.Connect Desktop and your interop-enabled .NET apps with matching user privileges (e.g., when debugging). Otherwise, window registration will fail - your app window won't be sticky and another transparent sticky window will be visible, looking as if the borders of your app window have been separated from the window itself. If you are running your app as an Elevated administrator (e.g., from Visual Studio, or any IDE, which runs in an elevated state), then you must also run io.Connect Desktop in an elevated state, if possible. Another solution for matching the user privilege states of io.Connect Desktop and your app when debugging is to put
Debugger.Launch()
in the app code and start the app by launching its EXE file from the Windows Explorer. This will cause the app to run with standard user privileges.
Registering Windows
To register the main window (startup window) of your app as an io.Connect Window, use the RegisterStartupWindow()
method. To register other flyer windows (e.g., any helper windows you may need at runtime) as io.Connect Windows, use the RegisterWindow()
method. Registering your app window in the io.Connect framework grants it access to all io.Connect Window Management features - the window becomes sticky (can stick to other io.Connect Windows to form window groups) and you can configure it, control it, and handle events related to it by using the Window Management API.
⚠️ Note that if your app consists of multiple windows, you can register its child windows with the
RegisterAppFactory()
method available in the .NET App Management API. For more details, see the App Management > .NET > Multi Window Apps section.
Main Window
To register the main window of your app as an io.Connect Window, use the RegisterStartupWindow()
method and pass the window object (for WPF windows) or the window handle (for WinForms windows) as a required first argument. As a second and third optional arguments, you can pass a default window title and a window options builder:
IGlueWindow myWindow = await io.GlueWindows.RegisterWindow(this, "My Main Window", options => options.WithChannelSupport(true));
For specifics in registering WPF and WinForms windows, see the following sections.
WPF Windows
⚠️ Note that it's mandatory for an external WPF app to have an
app.manifest
file with the following section:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware> </windowsSettings> </application>
You can register a WPF window right after its creation. You can do this either in the window code or from an external component. The following example demonstrates registering a WPF window and providing options for it:
// Register the window.
IGlueWindow myWindow = await io.GlueWindows.RegisterStartupWindow(this, "My Main Window", options => options.WithChannelSupport(true));
WPF windows are automatically unregistered when they are closed. If you want to manually unregister a window at a different point in time, use:
myWindow.Unregister();
See the .NET WPF window registration example on GitHub.
WinForms Windows
⚠️ Note that WinForms windows using .NET Framework 4.5 and running on Windows 10 (or previous Windows versions) may experience visual issues due to lack of proper high DPI support in .NET Framework versions lower than 4.7. To avoid such issues, it's recommended to upgrade your apps to .NET Framework 4.7 or later and apply the following configuration settings:
- Declare compatibility with Windows 10 by adding the following to the
app.manifest
file:
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <application> <!-- Windows 10 compatibility. --> <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> </application> </compatibility>
- Enable per-monitor DPI awareness in the
app.config
file:
<configuration> <System.Windows.Forms.ApplicationConfigurationSection> <add key="DpiAwareness" value="PerMonitorV2" /> </System.Windows.Forms.ApplicationConfigurationSection> </configuration>
For more details on high DPI support in WinForms windows, see the official Microsoft documentation.
You can register a WinForms window by using its handle:
// Register the window by using its handle as a first argument.
IGlueWindow myWindow = await io.GlueWindows.RegisterStartupWindow(this.Handle, "My Main Window", options => options.WithChannelSupport(true));
WinForms windows aren't automatically unregistered. You should explicitly call Unregister()
when the window is closed:
myWindow.Unregister();
Flyer Windows
To register a flyer window as an io.Connect Window, use the RegisterWindow()
method and pass the window object (for WPF windows) or the window handle (for WinForms windows) as a first required argument. As a second optional argument, you can pass a window options object or a window options builder:
IGlueWindow myWindow = await io.GlueWindows.RegisterWindow(this, options => options.WithChannelSupport(true));
Window Options
You can set several window configuration options during window registration by passing a window options object, or by using a window options builder.
To create a window options object with default settings, invoke the GlueWindowOptions()
constructor:
var options = new GlueWindowOptions();
If the app is started by io.Connect Desktop, you can get any window startup options (regarding the app bounds, Layout, and more) by using the GetStartupOptions()
method:
var options = io.GlueWindows.GetStartupOptions() ?? new GlueWindowOptions();
The created object provides a builder that you can use to specify various window options. The following example demonstrates how to provide window options using the builder:
var options = new GlueWindowOptions();
options
// Create a tab window.
.WithType(GlueWindowType.Tab)
// Provide a title for the window.
.WithTitle("My Window")
// Enable the Channel Selector for the window.
.WithChannelSupport(true)
// Join the window to a Channel.
.WithChannel("Red")
// Hide the taskbar icon.
.WithShowTaskbarIcon(false)
// Provide an ID for the window.
.WithId("my-id");
Window Operations
Once a window is registered, the Window Management API will accept full control over the window positioning, sizing and visibility. The app shouldn't use native methods (e.g., WPF or WinForms calls) to control the window as this will interfere with the io.Connect Window Management.
You can perform operations on the current window and on any other registered window.
Window App Instance
The window object offers access to the host app instance, connecting the Window Management API with the App Management API:
IAppManagerApplication appInstance = myWindow.Instance;
Title
To set the window title, use the Title
property of the IGlueWindow
object:
myWindow.Title = "New title";
Size & Position
To change the bounds of the window, use the Bounds
property of the IGlueWindow
object:
// Set the window bounds - left, top, width, height.
var newWindowBounds = new GlueWindowBounds(10, 10, 200, 200);
myWindow.Bounds = newWindowBounds;
Visibility
To hide the window, use the IsVsible
property of the IGlueWindow
object:
myWindow.IsVisible = false;
Z-Order
Available since io.Connect Desktop 9.3
To set a window on top of the z-order, use the WithOnTopState()
method when supplying options for registering your window in the io.Connect framework:
var options = new GlueWindowOptions();
options.WithOnTopState(GlueWindowOnTopState.Yes);
IGDWindow myWindow = await io.GlueWindows.RegisterWindow(this, options);
⚠️ Note that using the
GlueWindowOnTopState.Yes
option will allow the io.Connect Window to be on top only until the window is visible and not joined to an io.Connect Window group. If the window is hidden programmatically or the user snaps it to another io.Connect Window or window group, it will no longer be on top of the z-order when it becomes visible or when the user tears it off from the group.
To instruct the window to remain permanently on top of the z-order, regardless of changes to its visibility or whether it joins or leaves an io.Connect Window group, use the GlueWindowOnTopState.Always
option:
var options = new GlueWindowOptions();
options.WithOnTopState(GlueWindowOnTopState.Always);
IGDWindow myWindow = await io.GlueWindows.RegisterWindow(this, options);
You can also use the SetOnTop()
and SetAlwaysOnTop()
methods when updating the window:
// The window will be on top of the z-order only until it's visible and not joined to an io.Connect Window group
await myWindow.Update(update => update.SetOnTop(options => options.OnTop = true));
// The window will remain permanently on top of the z-order,
// regardless of changes to its visibility or whether it joins or leaves an io.Connect Window group.
await myWindow.Update(update => update.SetAlwaysOnTop());
Snap
Available since io.Connect Desktop 9.3
To snap the current window to another io.Connect Window, use the Snap()
method when updating the window:
await myWindow.Update(update => update.Snap(options =>
{
// ID of the io.Connect Window to which to snap the current window.
options.TargetWindowId = targetWindow.Id;
// Snap to the bottom edge of the target window.
options.SnapToEdge = SnappingEdge.Bottom;
}));
Ungrouping Windows
Available since io.Connect Desktop 9.3
To extract a window from an io.Connect Window group, use the Ungroup()
method when updating the window. Optionally specify bounds for the ungrouped window and whether it will be on focus when ungrouped:
await myWindow.Update(update => update.Ungroup(options =>
{
// Specify whether the window will be on focus when ungrouped.
options.Focus = true;
// Bounds for the ungrouped window.
options.Bounds = new Bounds
{
Top = 0,
Left = 0,
Width = 500,
Height = 500
};
}));
Handling Other Windows
The .NET Window Management API allows you to find all other io.Connect Windows (as opposed to your current one) and manipulate them. Your current window is described by the IGlueWindow
interface, while all other windows are described by the IGDWindow
interface.
Finding Windows
To find all io.Connect Windows, use the GetGDWindows()
method of the .NET Window Management API:
IGDWindow[] allWindows = await io.GlueWindows.GetGDWindows();
To await your newly registered window to be published to the window collection, use:
IGlueWindow myWindow = await io.GlueWindows.RegisterWindow(this, options);
await io.GlueWindows.AwaitWindow(window => window.Descriptor.Id == myWindow.Id);
// The collection will now contain `myWindow`.
IGDWindow[] allWindows = await io.GlueWindows.GetGDWindows();
To find a window by ID, use:
var targetWindowId = "29476_0";
IGDWindow[] allWindows = await io.GlueWindows.GetGDWindows();
IGDWindow targetWindow = allWindows.FirstOrDefault(window => window.Descriptor.Id == targetWindowId);
To find a window by name, use:
var targetWindowName = "target-window";
IGDWindow[] allWindows = await io.GlueWindows.GetGDWindows();
IGDWindow targetWindow = windows.FirstOrDefault(window => window.Descriptor.Name == targetWindowName);
To find the main window of an app instance by app instance ID, use:
var instanceId = "29476_1";
IGDWindow mainWindow = await io.GlueWindows.AwaitWindow(window => window.Descriptor.Id == instanceId);
To find all windows of any app, use:
var appName = "client-list";
IEnumerable<IGDWindow> appWindows = (await io.GlueWindows.GetGdWindows())
.Where(window => window.Descriptor.Name == appName);
To find all windows of the current app, use:
var appWindows = io.GlueWindows.OwnWindows;
Updating Windows
To manipulate other windows, use the Update()
method of the IGDWindow
instance.
Updating the window title:
IGDWindow[] allWindows = await io.GlueWindows.GetGDWindows();
var newTitle = "New Title";
IGDWindow targetWindow = allWindows.FirstOrDefault((window) =>
{
return window.Descriptor.Name == "My Window";
});
await targetWindow.Update((window) =>
{
window.SetTitle((titleOptions) => titleOptions.Title = newTitle);
});
Hiding a window:
await targetWindow.Update((window) =>
{
window.Hide();
});
Showing a window:
await targetWindow.Update((window) =>
{
window.Show();
});
Closing a window:
await targetWindow.Update((window) =>
{
window.Close();
});
Events
The .NET Window Management API offers methods for listening for io.Connect Window events. To get notified for window events, use the Subscribe()
method directly on an API level or on a window instance.
The example below demonstrates subscribing for an event by using Subscribe()
on an API level:
var subscription = io.GlueWindows.Subscribe(
// Reference to the window for which to receive event notifications.
targetWindow,
// The lambda specifies for which window property to receive event notifications.
p => p.Title,
// Event handler.
(descriptor, eventType, propertyChangedName, newPropertyValue, oldValue) =>
{
var title = descriptor.Title;
}
);
To stop listening for any window event, use the Dispose()
method of the subscription object returned by Subscribe()
:
subscription.Dispose();
Some of the available window events are described below. The examples demonstrate how to subscribe for them through the window instance.
Title
To get notified when the window title changes:
var subscription = window.Subscribe(
EventType.TitleChanged,
(descriptor, eventType) =>
{
var title = descriptor.Title;
}
);
Bounds
To get notified when the window bounds change:
var subscription = window.Subscribe(
EventType.BoundsChanged,
(descriptor, eventType) =>
{
var bounds = descriptor.Bounds;
}
);
Visibility
To get notified when the window visibility changes:
var subscription = window.Subscribe(
EventType.VisibilityChanged,
(descriptor, eventType) =>
{
var isVisible = descriptor.isVisible;
}
);
Frame Color
To get notified when the window frame color changes:
var subscription = window.Subscribe(
EventType.FrameColorChanged,
(descriptor, eventType) =>
{
var frameColor = descriptor.FrameColor;
}
);
Frame Buttons
The Window Management API allows placing custom buttons in the frame area of the window and handling clicks on them.
To add a frame button, use the Update()
method of an io.Connect Window instance. The following example demonstrates how to add a frame button, specify an icon for it and handle clicks on it:
myWindow.Update(windowUpdate => windowUpdate.AddButton(button =>
{
button.ButtonId = "btn" + Guid.NewGuid().ToString("N");
// To set a button icon, you can also use `button.ImageBase64` and supply the respective Base64-encoded string.
button.Image = Image.FromFile("button-image.png");
button.OnClickAction = (@event, buttonInfo) =>
{
// Handle button clicks.
};
}));
WPF Example
This is a minimalistic WPF example that registers its main window as an io.Connect Window.
See the .NET WPF example on GitHub.
In App.xaml
initialize io.Connect:
public partial class App : Application
{
public static Glue42 io;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
io = new Glue42();
io.Initialize("MyDemo");
}
In MainWindow.xaml.cs
register the window:
public partial class MainWindow : Window
{
public async MainWindow()
{
InitializeComponent();
var options = App.io.GlueWindows.GetStartupOptions() ?? new GlueWindowOptions();
options.WithType(GlueWindowType.Flat).WithTitle("Example Window");
// Register the window.
IGlueWindow myWindow = await App.io.GlueWindows.RegisterStartupWindow(this, "My Main Window", options);
}
}