Skip to content

Commit 4d1f233

Browse files
authored
Add WinFormsWithIslandApp, a simple WinForms app that hosts Xaml in an Island (#386)
* Initial app from template * Change configurations to platforms x86, x64, arm64 * Add reference to WindowsAppSDK * Use WindowsPackageType=None * Basics are working * Some cleanup * Adding .csproj.user file * Add README.md files * Fix README.md * Fix up readmes * Update WinAppSDK, add ComboBox with x:Bind * Various updates from PR feedback
1 parent d21cc80 commit 4d1f233

17 files changed

+852
-0
lines changed

Samples/Islands/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,38 @@ DispatcherQueueController on a dedicated rendering thread. (main.cpp)
9292
* Press Ctrl+Shift+B, or select **Build** \> **Build Solution**.
9393
* Press Ctrl+F5 to launch the app without attaching a debugger.
9494
* Press F5 to launch the app under a debugger.
95+
96+
# WinForms Island App (cs-winforms-unpackaged)
97+
98+
The cs-winforms-unpackaged directory has a sample shows how to add a WinAppSDK island with Xaml content to a WinForms app.
99+
It was first created with the C# "Windows Forms App" template in Visual Studio, which yields a boilerplate WinForms app.
100+
101+
This sample is an "unpackaged" app, so it will run like a WinForms/Win32 app does when built from the default templates.
102+
103+
This sample uses Windows App SDK as a "framework package". This means that the Windows App SDK runtime must be installed for it to run.
104+
105+
## Prerequisites
106+
107+
* See [System requirements for Windows app development](https://docs.microsoft.com/windows/apps/windows-app-sdk/system-requirements).
108+
* Make sure that your development environment is set up correctly—see [Install tools for developing apps for Windows 10 and Windows 11](https://docs.microsoft.com/windows/apps/windows-app-sdk/set-up-your-development-environment).
109+
110+
## Building and running the WinForms Island sample
111+
112+
* Open the solution file (`.sln`) in Visual Studio.
113+
* Press Ctrl+Shift+B, or select **Build** \> **Build Solution**.
114+
* Press Ctrl+F5 to launch the app (without attaching a debugger)
115+
> Note: If the Windows App SDK runtime isn't installed on the machine, the user will see a message box directing them to a download link.
116+
* Press F5 to launch the app under a debugger.
117+
* To run from the command line or File Explorer, navigate to `bin/<arch>/<config>/net6.0-windows10.0.17763.0` directory and run WinFormsWithIslandApp.exe.
118+
* To deploy to another machine, copy the `bin/<arch>/<config>/net6.0-windows10.0.17763.0` directory to that machine and run WinFormsWithIslandApp.exe. The sample
119+
runs on Windows version 17763 and later.
120+
121+
In Visual Studio, you should also be able to see the DesktopWindowXamlSourceControl in the designer:
122+
123+
![alt text](img/designer.png)
124+
125+
When you launch the app, it should look like this:
126+
127+
![alt text](img/screenshot.png)
128+
129+

Samples/Islands/cs-winforms-unpackaged/DesktopWindowXamlSourceControl.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
using Microsoft.UI.Xaml;
2+
using Microsoft.UI.Xaml.Controls;
3+
using Microsoft.UI.Xaml.Hosting;
4+
using Microsoft.UI.Xaml.Media;
5+
using Microsoft.UI;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.ComponentModel;
9+
using System.Data;
10+
using System.Drawing;
11+
using System.Linq;
12+
using System.Text;
13+
using System.Threading.Tasks;
14+
using System.Windows.Forms;
15+
using Windows.Foundation;
16+
17+
namespace WinFormsWithIsland
18+
{
19+
/// <summary>
20+
/// This control hosts a DesktopWindowXamlSource in a WinForms application.
21+
/// The DesktopWindowXamlSource is backed by a DesktopChildSiteBridge HWND that is a child of the WinForms Control.
22+
/// The HWND tree looks like this:
23+
/// * Top-level WinForms HWND
24+
/// * DesktopWindowXamlSourceControl HWND
25+
/// * DesktopChildSiteBridge HWND
26+
/// * Other WinForms control HWNDs
27+
/// * Other WinForms control HWNDs
28+
/// </summary>
29+
public partial class DesktopWindowXamlSourceControl : System.Windows.Forms.Control
30+
{
31+
public DesktopWindowXamlSourceControl()
32+
{
33+
InitializeComponent();
34+
}
35+
36+
protected override void OnGotFocus(EventArgs e)
37+
{
38+
if (_desktopWindowXamlSource != null)
39+
{
40+
// The DesktopWindowXamlSourceControl is getting focus, let's redirect focus to the Xaml content by
41+
// calling DesktopWindowXamlSource.NavigateFocus.
42+
bool isShiftPressed = ((Form.ModifierKeys & Keys.Shift) != 0);
43+
var reason = isShiftPressed ? Microsoft.UI.Xaml.Hosting.XamlSourceFocusNavigationReason.Last : Microsoft.UI.Xaml.Hosting.XamlSourceFocusNavigationReason.First;
44+
var request = new XamlSourceFocusNavigationRequest(reason);
45+
_desktopWindowXamlSource.NavigateFocus(request);
46+
}
47+
}
48+
49+
private void OnDesktopWindowXamlSourceTakeFocusRequested(object sender, DesktopWindowXamlSourceTakeFocusRequestedEventArgs e)
50+
{
51+
// The DesktopWindowXamlSource is requesting that the host take focus.
52+
// This typically happens when the user is tabbing through the controls in the Xaml content and reaches the first or last control.
53+
if (e.Request.Reason == XamlSourceFocusNavigationReason.First)
54+
{
55+
FocusNextFocusableWinFormsControl(this.Parent!, this, true /*forward*/);
56+
}
57+
else if (e.Request.Reason == XamlSourceFocusNavigationReason.Last)
58+
{
59+
FocusNextFocusableWinFormsControl(this.Parent!, this, false /*forward*/);
60+
}
61+
}
62+
63+
private void InitializeDesktopWindowXamlSource()
64+
{
65+
_desktopWindowXamlSource = new Microsoft.UI.Xaml.Hosting.DesktopWindowXamlSource();
66+
67+
_desktopWindowXamlSource.Initialize(new WindowId((ulong)this.Handle));
68+
69+
_desktopWindowXamlSource.TakeFocusRequested +=
70+
new TypedEventHandler<DesktopWindowXamlSource, DesktopWindowXamlSourceTakeFocusRequestedEventArgs>(
71+
OnDesktopWindowXamlSourceTakeFocusRequested);
72+
73+
_desktopWindowXamlSource.SiteBridge.MoveAndResize(new Windows.Graphics.RectInt32(0, 0, this.Width, this.Height));
74+
75+
_frame = new Frame();
76+
_desktopWindowXamlSource.Content = _frame;
77+
78+
_desktopWindowXamlSource.SystemBackdrop = new DesktopAcrylicBackdrop();
79+
80+
if (_content != null)
81+
{
82+
_frame.Content = _content;
83+
}
84+
}
85+
86+
protected override void OnResize(EventArgs e)
87+
{
88+
base.OnResize(e);
89+
if (_desktopWindowXamlSource != null)
90+
{
91+
// Resize the DesktopChildSiteBridge HWND to match the size of the WinForms control, it's parent.
92+
_desktopWindowXamlSource.SiteBridge.MoveAndResize(new Windows.Graphics.RectInt32(0, 0, this.Width, this.Height));
93+
}
94+
}
95+
96+
protected override void OnPaint(PaintEventArgs pe)
97+
{
98+
if (this.DesignMode)
99+
{
100+
base.OnPaint(pe);
101+
pe.Graphics.FillRectangle(Brushes.LightGray, this.ClientRectangle);
102+
pe.Graphics.DrawString("DesktopWindowXamlSourceControl", this.Font, Brushes.Black, 10, 10);
103+
}
104+
else if (_desktopWindowXamlSource == null)
105+
{
106+
InitializeDesktopWindowXamlSource();
107+
}
108+
}
109+
110+
/// <summary>
111+
/// Sets the content of the DesktopWindowXamlSource.
112+
/// </summary>
113+
public FrameworkElement? Content
114+
{
115+
get
116+
{
117+
return _content;
118+
}
119+
set
120+
{
121+
_content = value;
122+
if (_frame != null)
123+
{
124+
_frame.Content = _content;
125+
}
126+
}
127+
}
128+
129+
/// <summary>
130+
/// Call to move focus to the next focusable control in the parent.
131+
/// </summary>
132+
/// <param name="parent">Control or Form that contains the controls.</param>
133+
/// <param name="start">Control we're starting with.</param>
134+
/// <param name="forward">If true, focus is moving forward. If not, backward.</param>
135+
private static void FocusNextFocusableWinFormsControl(System.Windows.Forms.Control parent, System.Windows.Forms.Control start, bool forward)
136+
{
137+
// GetNextControl can return controls that aren't tab stops, so keep going until we find one that is.
138+
System.Windows.Forms.Control? next = start;
139+
do
140+
{
141+
next = parent.GetNextControl(next, forward);
142+
}
143+
while (next != null && next.TabStop == false);
144+
145+
if (next == null)
146+
{
147+
// Oops, we ran out of controls. Get the first control in the parent.
148+
next = parent.GetNextControl(null, forward);
149+
}
150+
151+
if (next != null)
152+
{
153+
next.Focus();
154+
}
155+
}
156+
157+
private FrameworkElement? _content;
158+
private Frame? _frame;
159+
private DesktopWindowXamlSource? _desktopWindowXamlSource;
160+
}
161+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<root>
3+
<!--
4+
Microsoft ResX Schema
5+
6+
Version 2.0
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
11+
associated with the data types.
12+
13+
Example:
14+
15+
... ado.net/XML headers & schema ...
16+
<resheader name="resmimetype">text/microsoft-resx</resheader>
17+
<resheader name="version">2.0</resheader>
18+
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19+
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20+
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21+
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22+
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23+
<value>[base64 mime encoded serialized .NET Framework object]</value>
24+
</data>
25+
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26+
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27+
<comment>This is a comment</comment>
28+
</data>
29+
30+
There are any number of "resheader" rows that contain simple
31+
name/value pairs.
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
37+
mimetype set.
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
41+
extensible. For a given mimetype the value must be set accordingly:
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
45+
read any of the formats listed below.
46+
47+
mimetype: application/x-microsoft.net.object.binary.base64
48+
value : The object must be serialized with
49+
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50+
: and then encoded with base64 encoding.
51+
52+
mimetype: application/x-microsoft.net.object.soap.base64
53+
value : The object must be serialized with
54+
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55+
: and then encoded with base64 encoding.
56+
57+
mimetype: application/x-microsoft.net.object.bytearray.base64
58+
value : The object must be serialized into a byte array
59+
: using a System.ComponentModel.TypeConverter
60+
: and then encoded with base64 encoding.
61+
-->
62+
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63+
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64+
<xsd:element name="root" msdata:IsDataSet="true">
65+
<xsd:complexType>
66+
<xsd:choice maxOccurs="unbounded">
67+
<xsd:element name="metadata">
68+
<xsd:complexType>
69+
<xsd:sequence>
70+
<xsd:element name="value" type="xsd:string" minOccurs="0" />
71+
</xsd:sequence>
72+
<xsd:attribute name="name" use="required" type="xsd:string" />
73+
<xsd:attribute name="type" type="xsd:string" />
74+
<xsd:attribute name="mimetype" type="xsd:string" />
75+
<xsd:attribute ref="xml:space" />
76+
</xsd:complexType>
77+
</xsd:element>
78+
<xsd:element name="assembly">
79+
<xsd:complexType>
80+
<xsd:attribute name="alias" type="xsd:string" />
81+
<xsd:attribute name="name" type="xsd:string" />
82+
</xsd:complexType>
83+
</xsd:element>
84+
<xsd:element name="data">
85+
<xsd:complexType>
86+
<xsd:sequence>
87+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88+
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89+
</xsd:sequence>
90+
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91+
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92+
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93+
<xsd:attribute ref="xml:space" />
94+
</xsd:complexType>
95+
</xsd:element>
96+
<xsd:element name="resheader">
97+
<xsd:complexType>
98+
<xsd:sequence>
99+
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100+
</xsd:sequence>
101+
<xsd:attribute name="name" type="xsd:string" use="required" />
102+
</xsd:complexType>
103+
</xsd:element>
104+
</xsd:choice>
105+
</xsd:complexType>
106+
</xsd:element>
107+
</xsd:schema>
108+
<resheader name="resmimetype">
109+
<value>text/microsoft-resx</value>
110+
</resheader>
111+
<resheader name="version">
112+
<value>2.0</value>
113+
</resheader>
114+
<resheader name="reader">
115+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116+
</resheader>
117+
<resheader name="writer">
118+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119+
</resheader>
120+
</root>

0 commit comments

Comments
 (0)