Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Pode usar APIs de Composição do Windows Runtime (também chamadas de Camada Visual) nas suas aplicações Windows Forms para criar experiências modernas que melhorem para os utilizadores do Windows.
O código completo deste tutorial está disponível em GitHub: Windows Forms exemplo HelloComposition.
Pré-requisitos
A API de hospedagem UWP tem esses pré-requisitos.
- Presumimos que tem alguma familiaridade com desenvolvimento de aplicações usando Windows Forms e UWP. Para mais informações, consulte:
- .NET Framework 4.7.2 ou posterior
- Windows 10 versão 1803 ou posterior
- Windows 10 SDK 17134 ou posterior
Como usar APIs de Composições no Windows Forms
Neste tutorial, crias uma interface simples do Windows Forms e adicionas elementos animados de Composição. Tanto os componentes Windows Forms como Composition são mantidos simples, mas o código de interoperabilidade mostrado é o mesmo independentemente da complexidade dos componentes. O aplicativo concluído tem esta aparência.
Criar um projeto Windows Forms
O primeiro passo é criar o projeto de aplicação Windows Forms, que inclui uma definição de aplicação e o formulário principal da interface.
Para criar um novo projeto Windows Forms Application em Visual C# chamado HelloComposition:
- Abra o Visual Studio e selecione Arquivo>Novo Projeto>.
O diálogo do Novo Projeto abre-se. - Na categoria Instalado, expanda o nó Visual C# e depois selecione Ambiente de Trabalho do Windows.
- Selecione o modelo de aplicação Windows Forms (.NET Framework).
- Insira o nome HelloComposition, selecione Framework .NET Framework 4.7.2, depois clique em OK.
Visual Studio cria o projeto e abre o designer da janela de aplicação padrão chamada Form1.cs.
Configurar o projeto para usar APIs do Tempo de Execução do Windows
Para usar APIs do Windows Runtime (WinRT) na sua aplicação Windows Forms, precisa de configurar o seu projeto Visual Studio para aceder ao Windows Runtime. Além disso, os vetores são amplamente usados pelas APIs de Composição, por isso é necessário adicionar as referências necessárias para usar vetores.
Existem pacotes NuGet disponíveis para responder a ambas as necessidades. Instale as versões mais recentes destes pacotes para adicionar as referências necessárias ao seu projeto.
- Microsoft.Windows.SDK.Contracts (Requer o formato de gestão de pacotes predefinido definido como PackageReference.)
- System.Numerics.Vectors
Note
Embora recomendemos usar os pacotes NuGet para configurar o seu projeto, pode adicionar manualmente as referências necessárias. Para mais informações, consulte Melhorar a sua aplicação de ambiente de trabalho para Windows. A tabela seguinte mostra os ficheiros aos quais precisa de adicionar referências.
| File | Local |
|---|---|
| System.Runtime.WindowsRuntime | C:\Windows\Microsoft.NET\Framework\v4.0.30319 |
| Windows.Foundation.UniversalApiContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<sdk versão>\Windows.Foundation.UniversalApiContract<versão> |
| Windows.Foundation.FoundationContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<versão do SDK>\Windows.Foundation.FoundationContract<versão> |
| 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 |
Crie um controlo personalizado para gerir a interoperabilidade
Para alojar o conteúdo que crias com a camada visual, crias um controlo personalizado derivado do Control. Este controlo permite-lhe aceder a um Handle de janela, necessário para criar o contentor para o conteúdo da sua camada visual.
É aqui que fazes a maior parte da configuração para alojar APIs de Composição. Neste controlo, utiliza-se Platform Invocation Services (PInvoke) e COM Interop para integrar APIs de Composições na sua aplicação de Windows Forms. Para mais informações sobre o Invoke e o COM Interop, veja Interoperar com código não gerido.
Sugestão
Se você precisar, verifique o código completo no final do tutorial para se certificar de que todo o código está nos lugares certos enquanto você trabalha no tutorial.
Adicione um novo ficheiro de controlo personalizado ao seu projeto que derive do Control.
- No Explorador de Soluções , clique com o botão direito do mouse no projeto HelloComposition.
- No menu de contexto, selecione Adicionar>Novo Item....
- No diálogo Adicionar Novo Item , selecione Controlo Personalizado.
- Nomeia o CompositionHost.cs de controlo e depois clica em Adicionar. CompositionHost.cs abre na vista Design.
Mude para a vista de código para CompositionHost.cs e adicione o seguinte código à classe.
// 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; } }Adicionar código ao construtor.
No construtor, chama os métodos InitializeCoreDispatcher e InitComposition . Você cria esses métodos nas próximas etapas.
public CompositionHost() { InitializeComponent(); // Get the window handle. hwndHost = Handle; // Create dispatcher queue. dispatcherQueue = InitializeCoreDispatcher(); // Build Composition tree of content. InitComposition(hwndHost); }Inicializar um thread com um CoreDispatcher. O despachante principal é responsável pelo processamento de mensagens de janelas e por despachar eventos para APIs WinRT. Novas instâncias do Compositor devem ser criadas numa thread que tenha um CoreDispatcher.
- Crie um método chamado InitializeCoreDispatcher e adicione código para configurar a fila do despachante.
// 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; }- A fila do despachante requer uma declaração de PInvoke. Coloque esta declaração no final do código da classe. (Colocamos este código dentro de uma região para manter o código da classe organizado.)
#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 declarationsAgora tem a fila do dispatcher pronta e pode começar a inicializar e criar Conteúdo de Composição.
Inicializar o Compositor. O Compositor é uma fábrica que cria uma variedade de tipos no namespace Windows.UI.Composition abrangendo a camada visual, o sistema de efeitos e o sistema de animação. A classe Compositor também gere a vida útil dos objetos criados a partir da fábrica.
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 e ICompositionTarget requerem importações COM. Coloque este código após a classe CompositionHost , mas dentro da declaração do namespace.
#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
Crie um controlo personalizado para alojar elementos de composição
É uma boa ideia colocar o código que gera e gere os elementos da sua composição num controlo separado que deriva do CompositionHost. Isto mantém o código de interoperabilidade que criou na classe CompositionHost reutilizável.
Aqui, cria um controlo personalizado derivado do CompositionHost. Este controlo é adicionado à caixa de ferramentas do Visual Studio para que possa adicioná-lo ao seu formulário.
Adicione um novo ficheiro de controlo personalizado ao seu projeto que derive do CompositionHost.
- No Explorador de Soluções , clique com o botão direito do mouse no projeto HelloComposition.
- No menu de contexto, selecione Adicionar>Novo Item....
- No diálogo Adicionar Novo Item , selecione Controlo Personalizado.
- Dá o nome CompositionHostControl.cs ao controlo, depois clica em Adicionar. CompositionHostControl.cs abre-se na vista de Design.
No painel de Propriedades na vista de design de CompositionHostControl.cs, defina a propriedade BackColor para ControlLight.
Definir a cor de fundo é opcional. Fazemos isso aqui para que possas ver o teu controlo personalizado contra o fundo do formulário.
Mude para o modo de visualização de código para CompositionHostControl.cs e atualize a declaração de classe para derivar do CompositionHost.
class CompositionHostControl : CompositionHostAtualize o construtor para chamar o construtor base.
public CompositionHostControl() : base() { }
Adicionar elementos de composição
Com a infraestrutura implementada, pode agora adicionar conteúdo de Composição à interface da aplicação.
Neste exemplo, adiciona-se código à classe CompositionHostControl que cria e anima um simples SpriteVisual.
Adicione um elemento de composição.
Em CompositionHostControl.cs, adicione estes métodos à classe 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); }
Adicione o controlo ao seu formulário
Agora que tens um controlo personalizado para alojar conteúdo de Composição, podes adicioná-lo à interface da aplicação. Aqui, adiciona uma instância do CompositionHostControl que criou no passo anterior. CompositionHostControl é automaticamente adicionado à caixa de ferramentas Visual Studio sob nome do projeto Components.
Na vista Form1.CS design, adiciona um botão à interface.
- Arrasta um botão da caixa de ferramentas para o Formulário 1. Coloque-o no canto superior esquerdo do formulário. (Veja a imagem no início do tutorial para verificar a colocação dos controlos.)
- No painel de Propriedades, altere a propriedade Texto de button1 para Adicionar elemento de composição.
- Redimensiona o botão para que todo o texto apareça.
(Para mais informações, veja Como: Adicionar Controlos a Windows Forms.)
Adicione um CompositionHostControl à interface.
- Arraste um CompositionHostControl da caixa de ferramentas para o Form1. Coloca-o à direita do Botão.
- Redimensione o CompositionHost para que preencha o restante do formulário.
Trata do evento de clique no botão.
- No painel de Propriedades, clique no ícone de raio para mudar para a vista de Eventos.
- Na lista de eventos, selecione o evento Click , escreva Button_Click e pressione Enter.
- Este código é adicionado em Form1.cs:
private void Button_Click(object sender, EventArgs e) { }Adicione código ao manipulador de clique do botão para criar novos elementos.
- Em Form1.cs, adicione código ao Button_Click gestor de eventos que criou anteriormente. Este código chama CompositionHostControl1.AddElement para criar um novo elemento com tamanho e deslocamento gerados aleatoriamente. (A instância de CompositionHostControl era automaticamente chamada compositionHostControl1 quando a arrastavas para o formulário.)
// 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); }
Agora pode construir e executar a sua aplicação Windows Forms. Quando clicar no botão, deve ver quadrados animados adicionados à interface.
Passos seguintes
Para um exemplo mais completo que se baseia na mesma infraestrutura, veja o exemplo Windows Forms Visual layer integration no GitHub.
Recursos adicionais
- Como Começar com Windows Forms (.NET)
- Interoperar com código não gerenciado (.NET)
- Introdução às aplicações Windows (UWP)
- Aprimore o seu aplicativo de desktop para Windows (UWP)
- Espaço de nomes Windows.UI.Composition (UWP)
Código completo
Aqui está o código completo deste tutorial.
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
}