# .NET

Source: https://docs.interop.io/desktop/capabilities/windows/window-management/net/index.html

## 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`](https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.loaded?view=windowsdesktop-6.0) event, for WinForms apps - using the [`OnShown()`](https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.form.onshown?view=windowsdesktop-6.0) 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.*

> ⚠️ *Note that when the windows of your .NET app are registered as io.Connect Windows, they are re-parented. If you are using custom UI frameworks in your apps (e.g., DevExpress, Qt, etc.), the re-parenting mechanism may cause conflicts with these frameworks which may lead to certain issues (e.g., inability to interact with the app title bar or context menus appearing in the wrong offset position). These potential issues can't be foreseen and there isn't a general fix that can be implemented in the io.Connect platform. Possible solutions may include disabling certain features of these UI frameworks based on the specific use case.*

## 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](https://docs.interop.io/desktop/capabilities/app-management/net/index.md#multi_window_apps), 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](https://docs.interop.io/desktop/capabilities/app-management/net/index.md#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](#window_options) builder:

```csharp
IGlueWindow myWindow = await io.GlueWindows.RegisterStartupWindow(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:*
>
> ```xml
> <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:

```csharp
// 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:

```csharp
myWindow.Unregister();
```

> ℹ️ *See the .NET [WPF window registration example](https://github.com/InteropIO/net-examples/tree/master/wpf-sw) 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:
>
> ```xml
> <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
>   <application>
>     
>     <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
>   </application>
> </compatibility>
> ```
>
> - Enable per-monitor DPI awareness in the `app.config` file:
>
> ```xml
> <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](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/high-dpi-support-in-windows-forms?view=netframeworkdesktop-4.8#configuring-your-windows-forms-app-for-high-dpi-support).*

You can register a WinForms window by using its handle:

```csharp
// 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:

```csharp
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](#window_options) object or a window options builder:

```csharp
IGlueWindow myWindow = await io.GlueWindows.RegisterWindow(this, options => options.WithChannelSupport(true));
```

## Window Options

You can set several window configuration options during [window registration](#registering_windows) 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:

```csharp
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:

```csharp
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:

```csharp
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 Windows taskbar icon.
    .WithShowTaskbarIcon(false)
    // Provide an ID for the window.
    .WithId("my-id")
    // Disable the resizing areas of the window.
    .WithAllowResize(false);
```

## 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](https://docs.interop.io/desktop/capabilities/app-management/net/index.md):

```csharp
IAppManagerApplication appInstance = myWindow.Instance;
```

### Title

To set the window title, use the `Title` property of the `IGlueWindow` object:

```csharp
myWindow.Title = "New title";
```

### Size & Position

To change the bounds of the window, use the `Bounds` property of the `IGlueWindow` object:

```csharp
// 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:

```csharp
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:

```csharp
var options = new GlueWindowOptions();

options.WithOnTopState(GlueWindowOnTopState.Yes);

IGlueWindow 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:

```csharp
var options = new GlueWindowOptions();

options.WithOnTopState(GlueWindowOnTopState.Always);

IGlueWindow myWindow = await io.GlueWindows.RegisterWindow(this, options);
```

You can also use the `SetOnTop()` and `SetAlwaysOnTop()` methods when updating the window:

```csharp
// 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:

```csharp
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:

```csharp
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:

```csharp
IGDWindow[] allWindows = await io.GlueWindows.GetGDWindows();
```

To await your newly registered window to be published to the window collection, use:

```csharp
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:

```csharp
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:

```csharp
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:

```csharp
var instanceId = "29476_1";
IGDWindow mainWindow = await io.GlueWindows.AwaitWindow(window => window.Descriptor.Id == instanceId);
```

To find all windows of any app, use:

```csharp
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:

```csharp
var appWindows = io.GlueWindows.OwnWindows;
```

#### Updating Windows

To manipulate other windows, use the `Update()` method of the `IGDWindow` instance.

Updating the window title:

```csharp
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:

```csharp
await targetWindow.Update((window) =>
{
	window.Hide();
});
```

Showing a window:

```csharp
await targetWindow.Update((window) =>
{
	window.Show();
});
```

Closing a window:

```csharp
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 following example demonstrates subscribing for an event by using `Subscribe()` on an API level:

```csharp
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()`:

```csharp
subscription.Dispose();
```

Some of the available window events are described in the following sections. The examples demonstrate how to subscribe for them through the window instance.

### Title

To get notified when the window title changes:

```csharp
var subscription = window.Subscribe(
    EventType.TitleChanged,
    (descriptor, eventType) =>
        {
            var title = descriptor.Title;
        }
);
```

### Bounds

To get notified when the window bounds change:

```csharp
var subscription = window.Subscribe(
    EventType.BoundsChanged,
    (descriptor, eventType) =>
        {
            var bounds = descriptor.Bounds;
        }
);
```

### Visibility

To get notified when the window visibility changes:

```csharp
var subscription = window.Subscribe(
    EventType.VisibilityChanged,
    (descriptor, eventType) =>
        {
            var isVisible = descriptor.isVisible;
        }
);
```

### Frame Color

To get notified when the window frame color changes:

```csharp
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:

```csharp
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](https://github.com/InteropIO/net-examples/tree/master/wpf-sw) on GitHub.*

In `App.xaml` initialize io.Connect:

```csharp
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:

```csharp
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);
    }
}
```
