使用視覺層搭配 Windows Forms
您可在 Windows Forms 應用程式中使用 Windows 執行階段 Composition API (又稱為視覺層),以建立適用於 Windows 使用者的現代化體驗。
您可在 GitHub 取得本教學課程的完整程式碼: Windows Forms HelloComposition 範本。
必要條件
UWP 主控 API 具備下列先決條件。
- 假設您對使用 Windows Forms 和 UWP 進行應用程式開發已有一些熟悉。 如需詳細資訊,請參閱:
- .NET Framework 4.7.2 或更新版本
- Windows 10 版本 1803 或更新版本
- Windows 10 SDK 17134 或更新版本
如何在 Windows Forms 中使用 Composition API
在本教學課程中,您會建立簡單的 Windows Forms UI,並在其中加入動畫 Composition 元素。 Windows Forms 和 Composition 元件都保持簡單,但不論元件的複雜度為何,顯示的 Interop 程式碼都相同。 完成的應用程式看起來像這樣。
建立 Windows Forms 專案
第一步是建立 Windows Forms 應用程式專案,其包含應用程式定義和 UI 的主要表單。
若要使用 Visual C# 建立名稱為 HelloComposition 的 Windows Forms 應用程式專案,請執行下列動作:
- 開啟 Visual Studio,然後選取 [檔案]>[新增]>[專案]。
[新增專案] 對話方塊隨即開啟。 - 在 [已安裝] 類別下,展開 [Visual C#] 節點,然後選取 [Windows Desktop]。
- 選取 [Windows Forms 應用程式 (.NET Framework)] 範本。
- 輸入名稱 HelloComposition,選取 [Framework .NET Framework 4.7.2],然後按一下 [確定]。
Visual Studio 會建立專案,並針對名為 Form1.cs 的預設應用程式視窗,開啟設計工具。
將專案設定為使用 Windows 執行階段 API
若要在您的 Windows Forms 應用程式中使用 Windows 執行階段 (WinRT) API,您需要設定 Visual Studio 專案,以存取 Windows 執行階段。 此外,Composition API 會廣泛使用向量,因此,您需要新增使用向量所需的參考。
NuGet 封裝可用於解決這兩項需求。 請安裝最新版本的封裝,將必要的參考新增至您的專案。
- Microsoft.Windows.SDK.Contracts (需要將預設的封裝管理格式設為 PackageReference。)
- System.Numerics.Vectors
注意
雖然建議使用 NuGet 封裝來設定您的專案,但您可以手動新增需要的參考。 如需詳細資訊,請參閱增強您的 Windows 傳統型應用程式。 下表所列的是需要新增參考的檔案。
檔案 | 地點 |
---|---|
System.Runtime.WindowsRuntime | C:\Windows\Microsoft.NET\Framework\v4.0.30319 |
Windows.Foundation.UniversalApiContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<sdk 版本>\Windows.Foundation.UniversalApiContract< 版本> |
Windows.Foundation.FoundationContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<sdk 版本>\Windows.Foundation.FoundationContract< 版本> |
System.Numerics.Vectors.dll | C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Numerics.Vectors\v4.0_4.0.0.0__b03f5f7f11d50a3a |
System.Numerics.dll | C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.7.2 |
建立自訂控制項來管理 Interop
若要裝載使用視覺層所建立的內容,可以建立衍生自 Control 的自訂控制項。 此控制項可讓您存取視窗 Handle,以便建立視覺層內容的容器。
您可以在這裡進行用於託管 Composition API 的大部分組態。 在此控制項中,您會使用 平台叫用服務 (PInvoke) 和 COM Interop,將 Composition API 帶入您的 Windows Forms 應用程式。 如需 PInvoke 和 COM Interop 的詳細資訊,請參閱與非受控程式碼互通。
提示
如有需要,請在本教學課程最後檢查完整的程式碼,以確保當您逐步進行教學課程時,所有程式碼皆位於正確的位置。
將新的自訂控制項檔案新增到衍生自 Control 的專案。
- 在 [方案總管] 中,以滑鼠右鍵按一下 [HelloComposition] 專案。
- 在操作功能表中,選取 [新增]Add>[新增項目...]。
- 在 [新增項目] 對話方塊中,選取 [報表控制項]。
- 將控制項命名為 CompositionHost.cs,然後按一下 [新增]。 CompositionHost.cs 會在 [設計] 檢視中開啟。
切換至 CompositionHost.cs 的程式碼檢視,並將下列程式碼新增至此類別。
// Add // using Windows.UI.Composition; IntPtr hwndHost; object dispatcherQueue; protected ContainerVisual containerVisual; protected Compositor compositor; private ICompositionTarget compositionTarget; public Visual Child { set { if (compositor == null) { InitComposition(hwndHost); } compositionTarget.Root = value; } }
將程式碼新增至建構函式。
在建構函式中,您可以呼叫 InitializeCoreDispatcher 和 InitComposition 方法。 在後續步驟中會建立上述方法。
public CompositionHost() { InitializeComponent(); // Get the window handle. hwndHost = Handle; // Create dispatcher queue. dispatcherQueue = InitializeCoreDispatcher(); // Build Composition tree of content. InitComposition(hwndHost); }
使用 CoreDispatcher 初始化執行緒。 核心發送器負責處理 WinRT API 的視窗訊息和分派事件。 Compositor 的新執行個體必須建立在具有 CoreDispatcher 的執行緒上。
- 建立名為 InitializeCoreDispatcher 的方法,並新增程式碼以設定發送器佇列。
// Add // using System.Runtime.InteropServices; private object InitializeCoreDispatcher() { DispatcherQueueOptions options = new DispatcherQueueOptions(); options.apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_STA; options.threadType = DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT; options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions)); object queue = null; CreateDispatcherQueueController(options, out queue); return queue; }
- 發送器佇列需要 PInvoke 宣告。 將此宣告放置在該類別的程式碼結尾。 (我們會將此程式碼放在區域內,讓類別程式碼保持整齊。)
#region PInvoke declarations //typedef enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE //{ // DQTAT_COM_NONE, // DQTAT_COM_ASTA, // DQTAT_COM_STA //}; internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE { DQTAT_COM_NONE = 0, DQTAT_COM_ASTA = 1, DQTAT_COM_STA = 2 }; //typedef enum DISPATCHERQUEUE_THREAD_TYPE //{ // DQTYPE_THREAD_DEDICATED, // DQTYPE_THREAD_CURRENT //}; internal enum DISPATCHERQUEUE_THREAD_TYPE { DQTYPE_THREAD_DEDICATED = 1, DQTYPE_THREAD_CURRENT = 2, }; //struct DispatcherQueueOptions //{ // DWORD dwSize; // DISPATCHERQUEUE_THREAD_TYPE threadType; // DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType; //}; [StructLayout(LayoutKind.Sequential)] internal struct DispatcherQueueOptions { public int dwSize; [MarshalAs(UnmanagedType.I4)] public DISPATCHERQUEUE_THREAD_TYPE threadType; [MarshalAs(UnmanagedType.I4)] public DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType; }; //HRESULT CreateDispatcherQueueController( // DispatcherQueueOptions options, // ABI::Windows::System::IDispatcherQueueController** dispatcherQueueController //); [DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)] internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options, [MarshalAs(UnmanagedType.IUnknown)] out object dispatcherQueueController); #endregion PInvoke declarations
現在已準備好發送器佇列,可以開始初始化並建立 Composition 內容。
初始化 Compositor。 Compositor 是一個處理站,可以在跨越視覺層、效果系統和動畫系統的 Windows.UI.Composition 命名空間中建立各種類型。 Compositor 類別也會管理從處理站建立的物件存留期。
private void InitComposition(IntPtr hwndHost) { ICompositorDesktopInterop interop; compositor = new Compositor(); object iunknown = compositor as object; interop = (ICompositorDesktopInterop)iunknown; IntPtr raw; interop.CreateDesktopWindowTarget(hwndHost, true, out raw); object rawObject = Marshal.GetObjectForIUnknown(raw); compositionTarget = (ICompositionTarget)rawObject; if (raw == null) { throw new Exception("QI Failed"); } containerVisual = compositor.CreateContainerVisual(); Child = containerVisual; }
- ICompositorDesktopInterop 和 ICompositionTarget 需要 COM 匯入項目。 將此程式碼放置在 CompositionHost 類別後面,但在命名空間宣告中。
#region COM Interop /* #undef INTERFACE #define INTERFACE ICompositorDesktopInterop DECLARE_INTERFACE_IID_(ICompositorDesktopInterop, IUnknown, "29E691FA-4567-4DCA-B319-D0F207EB6807") { IFACEMETHOD(CreateDesktopWindowTarget)( _In_ HWND hwndTarget, _In_ BOOL isTopmost, _COM_Outptr_ IDesktopWindowTarget * *result ) PURE; }; */ [ComImport] [Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface ICompositorDesktopInterop { void CreateDesktopWindowTarget(IntPtr hwndTarget, bool isTopmost, out IntPtr test); } //[contract(Windows.Foundation.UniversalApiContract, 2.0)] //[exclusiveto(Windows.UI.Composition.CompositionTarget)] //[uuid(A1BEA8BA - D726 - 4663 - 8129 - 6B5E7927FFA6)] //interface ICompositionTarget : IInspectable //{ // [propget] HRESULT Root([out] [retval] Windows.UI.Composition.Visual** value); // [propput] HRESULT Root([in] Windows.UI.Composition.Visual* value); //} [ComImport] [Guid("A1BEA8BA-D726-4663-8129-6B5E7927FFA6")] [InterfaceType(ComInterfaceType.InterfaceIsIInspectable)] public interface ICompositionTarget { Windows.UI.Composition.Visual Root { get; set; } } #endregion COM Interop
建立自訂控制項來裝載組合元素
建議您將可產生和管理組合元素的程式碼放在衍生自 CompositionHost 的個別控制項中。 這會讓您在 CompositionHost 類別中建立的 Interop 程式碼得以重複使用。
在此,您會建立衍生自 CompositionHost 的自訂控制項。 此控制項會新增至 Visual Studio 工具箱,讓您可將其新增至表單。
將新的自訂控制項檔案新增到衍生自 CompositionHost 的專案。
- 在 [方案總管] 中,以滑鼠右鍵按一下 [HelloComposition] 專案。
- 在操作功能表中,選取 [新增]Add>[新增項目...]。
- 在 [新增項目] 對話方塊中,選取 [報表控制項]。
- 將控制項命名為 CompositionHostControl.cs,然後按一下 [新增]。 CompositionHostControl.cs 會在 [設計] 檢視中開啟。
在 CompositionHostControl.cs 設計檢視的 [屬性] 窗格中,將 BackColor 屬性設定為 ControlLight。
設定背景色彩是選擇性作業。 我們在此執行此作業,讓您可以看到表單背景的自訂控制項。
切換至 CompositionHostControl.cs 的程式碼檢視,並將類別宣告更新為衍生自 CompositionHost。
class CompositionHostControl : CompositionHost
更新建構函式以呼叫基底建構函式。
public CompositionHostControl() : base() { }
新增組合元素
基礎結構已就緒之後,即可將組合內容新增至應用程式 UI。
在此範例中,您將程式碼新增至 CompositionHostControl 類別,以建立簡單的 SpriteVisual並產生其動畫。
新增 Composition 元素。
在 CompositionHostControl.cs 中,將這些方法新增至 CompositionHostControl 類別。
// Add // using Windows.UI.Composition; public void AddElement(float size, float offsetX, float offsetY) { var visual = compositor.CreateSpriteVisual(); visual.Size = new Vector2(size, size); // Requires references visual.Brush = compositor.CreateColorBrush(GetRandomColor()); visual.Offset = new Vector3(offsetX, offsetY, 0); containerVisual.Children.InsertAtTop(visual); AnimateSquare(visual, 3); } private void AnimateSquare(SpriteVisual visual, int delay) { float offsetX = (float)(visual.Offset.X); Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation(); float bottom = Height - visual.Size.Y; animation.InsertKeyFrame(1f, new Vector3(offsetX, bottom, 0f)); animation.Duration = TimeSpan.FromSeconds(2); animation.DelayTime = TimeSpan.FromSeconds(delay); visual.StartAnimation("Offset", animation); } private Windows.UI.Color GetRandomColor() { Random random = new Random(); byte r = (byte)random.Next(0, 255); byte g = (byte)random.Next(0, 255); byte b = (byte)random.Next(0, 255); return Windows.UI.Color.FromArgb(255, r, g, b); }
將控制項新增至您的表單
您現在已有可裝載組合內容的自訂控制項,請將其新增至應用程式 UI。 在此,您會新增在上一個步驟中建立的 CompositionHostControl 執行個體。 CompositionHostControl 會自動新增至 Visual Studio 工具箱的 [專案名稱][元件] 之下。
在 Form1.CS 設計檢視中,將 Button 新增至 UI。
- 從工具箱將 Button 拖曳至 Form1。 將其放在表單的左上角。 (請參閱教學課程開頭的影像,以檢查控制項的位置。)
- 在 [屬性] 窗格中,將 [Text] 屬性從 [button1] 變更為 [新增組合元素]。
- 調整 Button 的大小,以便顯示所有文字。
(如需詳細資訊,請參閱作法:將控制項新增至 Windows Forms。)
將 CompositionHostControl 新增至 UI。
- 將 CompositionHostControl 從工具箱拖曳至 Form1。 將其放在 Button 的右邊。
- 調整 CompositionHost 的大小,使其填滿表單的其餘部分。
處理 button click 事件。
- 在 [屬性] 窗格中,按一下閃電以切換至 [事件] 檢視。
- 在事件清單中,選取 [Click] 事件,鍵入 Button_Click,然後按 Enter。
- 此程式碼會在 Form1.cs 中新增:
private void Button_Click(object sender, EventArgs e) { }
將程式碼新增至 button click 處理常式,以建立新的元素。
- 在 Form1.cs 中,將程式碼新增至您先前建立的 Button_Click 事件處理常式。 此程式碼會呼叫 CompositionHostControl1.AddElement,以建立具有隨機產生大小和位移的新元素。 (CompositionHostControl 的執行個體會在您將其拖曳到表單時,自動命名為 compositionHostControl1。)
// Add // using System; private void Button_Click(object sender, RoutedEventArgs e) { Random random = new Random(); float size = random.Next(50, 150); float offsetX = random.Next(0, (int)(compositionHostControl1.Width - size)); float offsetY = random.Next(0, (int)(compositionHostControl1.Height/2 - size)); compositionHostControl1.AddElement(size, offsetX, offsetY); }
您現在可以建置及執行 Windows Forms 應用程式。 當您按一下按鈕時,應該會看見新增到 UI 的動畫方塊。
下一步
如需在相同基礎結構上建置的更完整範例,請參閱 GitHub 上的 Windows Forms 視覺層整合範例。
其他資源
- Windows Forms 使用者入門 (.NET)
- 與非受控程式碼交互操作 (.NET)
- 開始使用 Windows 應用程式 (UWP)
- 增強您的 Windows 傳統型應用程式 (UWP)
- Windows.UI.Composition 命名空間 (UWP) (英文)
完整程式碼
這裡提供本教學課程的完整程式碼。
Form1.cs
using System;
using System.Windows.Forms;
namespace HelloComposition
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Button_Click(object sender, EventArgs e)
{
Random random = new Random();
float size = random.Next(50, 150);
float offsetX = random.Next(0, (int)(compositionHostControl1.Width - size));
float offsetY = random.Next(0, (int)(compositionHostControl1.Height/2 - size));
compositionHostControl1.AddElement(size, offsetX, offsetY);
}
}
}
CompositionHostControl.cs
using System;
using System.Numerics;
using Windows.UI.Composition;
namespace HelloComposition
{
class CompositionHostControl : CompositionHost
{
public CompositionHostControl() : base()
{
}
public void AddElement(float size, float offsetX, float offsetY)
{
var visual = compositor.CreateSpriteVisual();
visual.Size = new Vector2(size, size); // Requires references
visual.Brush = compositor.CreateColorBrush(GetRandomColor());
visual.Offset = new Vector3(offsetX, offsetY, 0);
containerVisual.Children.InsertAtTop(visual);
AnimateSquare(visual, 3);
}
private void AnimateSquare(SpriteVisual visual, int delay)
{
float offsetX = (float)(visual.Offset.X);
Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation();
float bottom = Height - visual.Size.Y;
animation.InsertKeyFrame(1f, new Vector3(offsetX, bottom, 0f));
animation.Duration = TimeSpan.FromSeconds(2);
animation.DelayTime = TimeSpan.FromSeconds(delay);
visual.StartAnimation("Offset", animation);
}
private Windows.UI.Color GetRandomColor()
{
Random random = new Random();
byte r = (byte)random.Next(0, 255);
byte g = (byte)random.Next(0, 255);
byte b = (byte)random.Next(0, 255);
return Windows.UI.Color.FromArgb(255, r, g, b);
}
}
}
CompositionHost.cs
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Windows.UI.Composition;
namespace HelloComposition
{
public partial class CompositionHost : Control
{
IntPtr hwndHost;
object dispatcherQueue;
protected ContainerVisual containerVisual;
protected Compositor compositor;
private ICompositionTarget compositionTarget;
public Visual Child
{
set
{
if (compositor == null)
{
InitComposition(hwndHost);
}
compositionTarget.Root = value;
}
}
public CompositionHost()
{
// Get the window handle.
hwndHost = Handle;
// Create dispatcher queue.
dispatcherQueue = InitializeCoreDispatcher();
// Build Composition tree of content.
InitComposition(hwndHost);
}
private object InitializeCoreDispatcher()
{
DispatcherQueueOptions options = new DispatcherQueueOptions();
options.apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE.DQTAT_COM_STA;
options.threadType = DISPATCHERQUEUE_THREAD_TYPE.DQTYPE_THREAD_CURRENT;
options.dwSize = Marshal.SizeOf(typeof(DispatcherQueueOptions));
object queue = null;
CreateDispatcherQueueController(options, out queue);
return queue;
}
private void InitComposition(IntPtr hwndHost)
{
ICompositorDesktopInterop interop;
compositor = new Compositor();
object iunknown = compositor as object;
interop = (ICompositorDesktopInterop)iunknown;
IntPtr raw;
interop.CreateDesktopWindowTarget(hwndHost, true, out raw);
object rawObject = Marshal.GetObjectForIUnknown(raw);
compositionTarget = (ICompositionTarget)rawObject;
if (raw == null) { throw new Exception("QI Failed"); }
containerVisual = compositor.CreateContainerVisual();
Child = containerVisual;
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
}
#region PInvoke declarations
//typedef enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE
//{
// DQTAT_COM_NONE,
// DQTAT_COM_ASTA,
// DQTAT_COM_STA
//};
internal enum DISPATCHERQUEUE_THREAD_APARTMENTTYPE
{
DQTAT_COM_NONE = 0,
DQTAT_COM_ASTA = 1,
DQTAT_COM_STA = 2
};
//typedef enum DISPATCHERQUEUE_THREAD_TYPE
//{
// DQTYPE_THREAD_DEDICATED,
// DQTYPE_THREAD_CURRENT
//};
internal enum DISPATCHERQUEUE_THREAD_TYPE
{
DQTYPE_THREAD_DEDICATED = 1,
DQTYPE_THREAD_CURRENT = 2,
};
//struct DispatcherQueueOptions
//{
// DWORD dwSize;
// DISPATCHERQUEUE_THREAD_TYPE threadType;
// DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType;
//};
[StructLayout(LayoutKind.Sequential)]
internal struct DispatcherQueueOptions
{
public int dwSize;
[MarshalAs(UnmanagedType.I4)]
public DISPATCHERQUEUE_THREAD_TYPE threadType;
[MarshalAs(UnmanagedType.I4)]
public DISPATCHERQUEUE_THREAD_APARTMENTTYPE apartmentType;
};
//HRESULT CreateDispatcherQueueController(
// DispatcherQueueOptions options,
// ABI::Windows::System::IDispatcherQueueController** dispatcherQueueController
//);
[DllImport("coremessaging.dll", EntryPoint = "CreateDispatcherQueueController", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateDispatcherQueueController(DispatcherQueueOptions options,
[MarshalAs(UnmanagedType.IUnknown)]
out object dispatcherQueueController);
#endregion PInvoke declarations
}
#region COM Interop
/*
#undef INTERFACE
#define INTERFACE ICompositorDesktopInterop
DECLARE_INTERFACE_IID_(ICompositorDesktopInterop, IUnknown, "29E691FA-4567-4DCA-B319-D0F207EB6807")
{
IFACEMETHOD(CreateDesktopWindowTarget)(
_In_ HWND hwndTarget,
_In_ BOOL isTopmost,
_COM_Outptr_ IDesktopWindowTarget * *result
) PURE;
};
*/
[ComImport]
[Guid("29E691FA-4567-4DCA-B319-D0F207EB6807")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICompositorDesktopInterop
{
void CreateDesktopWindowTarget(IntPtr hwndTarget, bool isTopmost, out IntPtr test);
}
//[contract(Windows.Foundation.UniversalApiContract, 2.0)]
//[exclusiveto(Windows.UI.Composition.CompositionTarget)]
//[uuid(A1BEA8BA - D726 - 4663 - 8129 - 6B5E7927FFA6)]
//interface ICompositionTarget : IInspectable
//{
// [propget] HRESULT Root([out] [retval] Windows.UI.Composition.Visual** value);
// [propput] HRESULT Root([in] Windows.UI.Composition.Visual* value);
//}
[ComImport]
[Guid("A1BEA8BA-D726-4663-8129-6B5E7927FFA6")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface ICompositionTarget
{
Windows.UI.Composition.Visual Root
{
get;
set;
}
}
#endregion COM Interop
}