Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Você pode usar as APIs de Composição do Windows Runtime (também chamadas de camada Visual) em seus aplicativos de Windows Presentation Foundation (WPF) para criar experiências modernas que aprimoram a experiência dos usuários do Windows.
O código completo deste tutorial está disponível no GitHub: WPF exemplo hellocomposition.
Pré-requisitos
A API de hospedagem XAML da UWP tem esses pré-requisitos.
- Presumimos que você tenha alguma familiaridade com o desenvolvimento de aplicativos usando WPF e UWP. Para obter mais informações, consulte:
- .NET Framework 4.7.2 ou posterior
- Windows 10, versão 1803 ou posterior
- SDK do Windows 10 17134 ou posterior
Como usar APIs de composição no WPF
Neste tutorial, você vai criar uma interface de usuário simples de aplicativo WPF e adicionar elementos animados de Composição a ela. Os componentes WPF e 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 WPF
A primeira etapa é criar o projeto de aplicativo WPF, que inclui uma definição de aplicativo e a página XAML para a interface do usuário.
Para criar um novo projeto de aplicativo WPF no Visual C# chamado HelloComposition:
Abra o Visual Studio e selecione Arquivo>Novo>Projeto.
A caixa de diálogo Novo projeto é aberta.
Na categoria Installed, expanda o nó Visual C# e selecione Windows Desktop.
Selecione o modelo WPF App (.NET Framework).
Insira o nome HelloComposition, selecione Framework .NET Framework 4.7.2 e clique em OK.
Visual Studio cria o projeto e abre o designer para a janela de aplicativo padrão chamada MainWindow.xaml.
Configurar o projeto para usar APIs do Windows Runtime
Para usar APIs Windows Runtime (WinRT) em seu aplicativo WPF, você precisa configurar seu projeto Visual Studio para acessar o Windows Runtime. Além disso, os vetores são usados extensivamente pelas APIs de Composição, portanto, você precisa adicionar as referências necessárias para usar vetores.
Os pacotes NuGet estão disponíveis para atender às duas necessidades. Instale as versões mais recentes desses pacotes para adicionar as referências necessárias ao seu projeto.
- Microsoft.Windows.SDK.Contracts (requer o formato de gerenciamento de pacote padrão definido como PackageReference.)
- System.Numerics.Vectors
Note
Embora seja recomendável usar os pacotes NuGet para configurar seu projeto, você pode adicionar as referências necessárias manualmente. Para obter mais informações, consulte Enhance seu aplicativo da área de trabalho para Windows. A tabela a seguir mostra os arquivos aos quais você precisa adicionar referências.
| File | Localização |
|---|---|
| System.Runtime.WindowsRuntime | C:\Windows\Microsoft.NET\Framework\v4.0.30319 |
| Windows.Foundation.UniversalApiContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<sdk version>\Windows.Foundation.UniversalApiContract<version> |
| Windows.Foundation.FoundationContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<sdk version>\Windows. Foundation.FoundationContract<version> |
| 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:\Arquivos de Programas (x86)\Assemblies de Referência\Microsoft\Framework.NETFramework\v4.7.2 |
Configurar o projeto para estar consciente de DPI por monitor
O conteúdo da camada visual que você adiciona ao seu aplicativo não é dimensionado automaticamente para corresponder às configurações de DPI da tela em que ele é mostrado. Você precisa habilitar o reconhecimento de DPI por monitor para seu aplicativo e, em seguida, certificar-se de que o código usado para criar o conteúdo da camada visual leve em conta a escala de DPI atual quando o aplicativo é executado. Aqui, configuramos o projeto para estar ciente do DPI. Em seções posteriores, mostramos como usar as informações de DPI para dimensionar o conteúdo da camada visual.
WPF aplicativos estão cientes do DPI do Sistema por padrão, mas precisam declarar-se cientes de DPI por monitor em um arquivo app.manifest. Para ativar o reconhecimento de DPI de nível Windows por monitor no arquivo de manifesto do aplicativo:
No Gerenciador de Soluções, clique com o botão direito do mouse no projeto HelloComposition.
No menu de contexto, selecione Adicionar>Novo Item....
Na caixa de diálogo Adicionar Novo Item , selecione "Arquivo de Manifesto do Aplicativo" e clique em Adicionar. (Você pode deixar o nome padrão.)
No arquivo app.manifest, localize este xml e cancele o comentário:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application>Adicione essa definição após a marca de abertura
<windowsSettings>:<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>Você também precisa definir a configuração DoNotScaleForDpiChanges no arquivo App.config.
Abra App.Config e adicione este xml dentro do
<configuration>elemento:<runtime> <AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/> </runtime>
Note
AppContextSwitchOverrides só pode ser definido uma vez. Se o aplicativo já tiver um conjunto, você deverá delimitar essa opção dentro do atributo de valor.
(Para obter mais informações, consulte o Per Monitor DPI Developer Guide and samples on GitHub.)
Criar uma classe derivada de HwndHost para hospedar elementos de composição
Para hospedar o conteúdo criado com a camada visual, você precisa criar uma classe derivada do HwndHost. É aqui que você faz a maior parte da configuração para hospedar APIs de Composição. Nesta classe, você usa Serviços de Invocação de Plataforma (PInvoke) e COM Interop para introduzir as APIs de Composição no seu aplicativo WPF. Para obter mais informações sobre pinvoke e interoperabilidade COM, consulte Interoperação com código não gerenciado.
Dica
Se necessário, verifique o código completo no final do tutorial para garantir que todo o código esteja nos locais certos enquanto você trabalha no tutorial.
Adicione um novo arquivo de classe ao projeto que deriva do HwndHost.
- No Gerenciador de Soluções, clique com o botão direito do mouse no projeto HelloComposition.
- No menu de contexto, selecione Adicionar>Classe.... .
- Na caixa de diálogo Adicionar Novo Item , nomeie a classe CompositionHost.cs e clique em Adicionar.
Em CompositionHost.cs, edite a definição de classe para derivar do HwndHost.
// Add // using System.Windows.Interop; namespace HelloComposition { class CompositionHost : HwndHost { } }Adicione o código e o construtor a seguir à classe.
// Add // using Windows.UI.Composition; IntPtr hwndHost; int hostHeight, hostWidth; object dispatcherQueue; ICompositionTarget compositionTarget; public Compositor Compositor { get; private set; } public Visual Child { set { if (Compositor == null) { InitComposition(hwndHost); } compositionTarget.Root = value; } } internal const int WS_CHILD = 0x40000000, WS_VISIBLE = 0x10000000, LBS_NOTIFY = 0x00000001, HOST_ID = 0x00000002, LISTBOX_ID = 0x00000001, WS_VSCROLL = 0x00200000, WS_BORDER = 0x00800000; public CompositionHost(double height, double width) { hostHeight = (int)height; hostWidth = (int)width; }Substitua os métodos BuildWindowCore e DestroyWindowCore .
Note
No BuildWindowCore, você chama os métodos InitializeCoreDispatcher e InitComposition . Você criará esses métodos nas próximas etapas.
// Add // using System.Runtime.InteropServices; protected override HandleRef BuildWindowCore(HandleRef hwndParent) { // Create Window hwndHost = IntPtr.Zero; hwndHost = CreateWindowEx(0, "static", "", WS_CHILD | WS_VISIBLE, 0, 0, hostWidth, hostHeight, hwndParent.Handle, (IntPtr)HOST_ID, IntPtr.Zero, 0); // Create Dispatcher Queue dispatcherQueue = InitializeCoreDispatcher(); // Build Composition tree of content InitComposition(hwndHost); return new HandleRef(this, hwndHost); } protected override void DestroyWindowCore(HandleRef hwnd) { if (compositionTarget.Root != null) { compositionTarget.Root.Dispose(); } DestroyWindow(hwnd.Handle); }- CreateWindowEx e DestroyWindow exigem uma declaração PInvoke. Coloque essa declaração no final do código da classe.
#region PInvoke declarations [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)] internal static extern IntPtr CreateWindowEx(int dwExStyle, string lpszClassName, string lpszWindowName, int style, int x, int y, int width, int height, IntPtr hwndParent, IntPtr hMenu, IntPtr hInst, [MarshalAs(UnmanagedType.AsAny)] object pvParam); [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)] internal static extern bool DestroyWindow(IntPtr hwnd); #endregion PInvoke declarationsInicialize um thread com um CoreDispatcher. O dispatcher principal é responsável por processar mensagens de janela e expedir eventos para APIs do WinRT. Novas instâncias do CoreDispatcher devem ser criadas em um thread que tenha um CoreDispatcher.
- Crie um método chamado InitializeCoreDispatcher e adicione código para configurar a fila do dispatcher.
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 dispatcher também requer uma declaração PInvoke. Coloque essa declaração dentro da região de declarações PInvoke que você criou na etapa anterior.
//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);Agora você tem a fila do dispatcher pronta e pode começar a inicializar e criar conteúdo do Composition.
Inicialize o Compositor. O Compositor é uma fábrica que cria uma variedade de tipos no Windows. UI. Composição namespace abrangendo visuais, o sistema de efeitos e o sistema de animação. A classe Compositor também gerencia o tempo de vida 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); ICompositionTarget target = (ICompositionTarget)rawObject; if (raw == null) { throw new Exception("QI Failed"); } }- ICompositorDesktopInterop e ICompositionTarget exigem importações COM. Coloque esse código após a classe CompositionHost , mas dentro da declaração de 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
Criar um UserControl para adicionar seu conteúdo à árvore visual WPF
A última etapa para configurar a infraestrutura necessária para hospedar o conteúdo da Composição é adicionar o HwndHost à árvore visual WPF.
Criar um Controle de Usuário
Um UserControl é uma maneira conveniente de empacotar seu código que cria e gerencia o conteúdo de composição, facilitando a adição desse conteúdo ao seu XAML.
Adicione um novo arquivo de controle de usuário ao seu projeto.
- No Gerenciador de Soluções, clique com o botão direito do mouse no projeto HelloComposition.
- No menu de contexto, selecione Adicionar>Controle de Usuário....
- Na caixa de diálogo Adicionar Novo Item , nomeie o controle de usuário CompositionHostControl.xaml e clique em Adicionar.
Os arquivos CompositionHostControl.xaml e CompositionHostControl.xaml.cs são criados e adicionados ao seu projeto.
Em CompositionHostControl.xaml, substitua as
<Grid> </Grid>marcas por esse elemento Border , que é o contêiner XAML em que o HwndHost entrará.<Border Name="CompositionHostElement"/>
No código para o controle de usuário, você cria uma instância da classe CompositionHost criada na etapa anterior e a adiciona como um elemento filho de CompositionHostElement, a Borda que você criou na página XAML.
Em CompositionHostControl.xaml.cs, adicione variáveis privadas para os objetos que você usará em seu código de composição. Adicione-os após a definição de classe.
CompositionHost compositionHost; Compositor compositor; Windows.UI.Composition.ContainerVisual containerVisual; DpiScale currentDpi;Adicione um manipulador para o evento Loaded do controle de usuário. É aqui que você configura sua instância compositionHost.
- No construtor, conecte o manipulador de eventos, conforme mostrado aqui (
Loaded += CompositionHostControl_Loaded;).
public CompositionHostControl() { InitializeComponent(); Loaded += CompositionHostControl_Loaded; }- Adicione o método do manipulador de eventos com o nome CompositionHostControl_Loaded.
private void CompositionHostControl_Loaded(object sender, RoutedEventArgs e) { // If the user changes the DPI scale setting for the screen the app is on, // the CompositionHostControl is reloaded. Don't redo this set up if it's // already been done. if (compositionHost is null) { currentDpi = VisualTreeHelper.GetDpi(this); compositionHost = new CompositionHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth); ControlHostElement.Child = compositionHost; compositor = compositionHost.Compositor; containerVisual = compositor.CreateContainerVisual(); compositionHost.Child = containerVisual; } }Nesse método, você configura os objetos que usará no código de composição. Aqui está uma rápida olhada no que está acontecendo.
- Primeiro, verifique se a configuração só é feita uma vez, verificando se uma instância do CompositionHost já existe.
// If the user changes the DPI scale setting for the screen the app is on, // the CompositionHostControl is reloaded. Don't redo this set up if it's // already been done. if (compositionHost is null) { }- Obtenha o DPI atual. Isso é usado para dimensionar corretamente os elementos de Composição.
currentDpi = VisualTreeHelper.GetDpi(this);- Crie uma instância do CompositionHost e atribua-a como o elemento filho do Border, CompositionHostElement.
compositionHost = new CompositionHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth); ControlHostElement.Child = compositionHost;- Obtenha o Compositor do CompositionHost.
compositor = compositionHost.Compositor;- Use o Compositor para criar um visual de contêiner. Esse é o contêiner de composição ao qual você adiciona seus elementos de composição.
containerVisual = compositor.CreateContainerVisual(); compositionHost.Child = containerVisual;- No construtor, conecte o manipulador de eventos, conforme mostrado aqui (
Adicionar elementos de composição
Com a infraestrutura estabelecida, agora você pode gerar o conteúdo de composição que deseja mostrar.
Para este exemplo, você adiciona um código que cria e anima um SpriteVisual quadrado simples.
Adicione um elemento de composição. Em CompositionHostControl.xaml.cs, adicione esses métodos à classe CompositionHostControl.
// Add // using System.Numerics; public void AddElement(float size, float offsetX, float offsetY) { var visual = compositor.CreateSpriteVisual(); visual.Size = new Vector2(size, size); visual.Scale = new Vector3((float)currentDpi.DpiScaleX, (float)currentDpi.DpiScaleY, 1); visual.Brush = compositor.CreateColorBrush(GetRandomColor()); visual.Offset = new Vector3(offsetX * (float)currentDpi.DpiScaleX, offsetY * (float)currentDpi.DpiScaleY, 0); containerVisual.Children.InsertAtTop(visual); AnimateSquare(visual, 3); } private void AnimateSquare(SpriteVisual visual, int delay) { float offsetX = (float)(visual.Offset.X); // Already adjusted for DPI. // Adjust values for DPI scale, then find the Y offset that aligns the bottom of the square // with the bottom of the host container. This is the value to animate to. var hostHeightAdj = CompositionHostElement.ActualHeight * currentDpi.DpiScaleY; var squareSizeAdj = visual.Size.Y * currentDpi.DpiScaleY; float bottom = (float)(hostHeightAdj - squareSizeAdj); // Create the animation only if it's needed. if (visual.Offset.Y != bottom) { Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation(); 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); }
Manipular alterações de DPI
O código para adicionar e animar um elemento leva em conta a escala de DPI atual quando os elementos são criados, mas você também precisa considerar as alterações de DPI enquanto o aplicativo está em execução. Você pode manipular o evento HwndHost.DpiChanged para ser notificado sobre alterações e ajustar seus cálculos com base no novo DPI.
No método CompositionHostControl_Loaded, após a última linha, adicione-o para conectar o manipulador de eventos DpiChanged.
compositionHost.DpiChanged += CompositionHost_DpiChanged;Adicione o método de manipulador de eventos com o nome CompositionHostDpiChanged. Esse código ajusta a escala e o deslocamento de cada elemento e recalcula todas as animações que não estão concluídas.
private void CompositionHost_DpiChanged(object sender, DpiChangedEventArgs e) { currentDpi = e.NewDpi; Vector3 newScale = new Vector3((float)e.NewDpi.DpiScaleX, (float)e.NewDpi.DpiScaleY, 1); foreach (SpriteVisual child in containerVisual.Children) { child.Scale = newScale; var newOffsetX = child.Offset.X * ((float)e.NewDpi.DpiScaleX / (float)e.OldDpi.DpiScaleX); var newOffsetY = child.Offset.Y * ((float)e.NewDpi.DpiScaleY / (float)e.OldDpi.DpiScaleY); child.Offset = new Vector3(newOffsetX, newOffsetY, 1); // Adjust animations for DPI change. AnimateSquare(child, 0); } }
Adicionar o controle de usuário à página XAML
Agora, você pode adicionar o controle de usuário à interface do usuário XAML.
Em MainWindow.xaml, defina a Altura da Janela como 600 e a Largura como 840.
Adicione o XAML para a interface do usuário. Em MainWindow.xaml, adicione este XAML entre as tags
<Grid> </Grid>root.<Grid.ColumnDefinitions> <ColumnDefinition Width="210"/> <ColumnDefinition Width="600"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="46"/> <RowDefinition/> </Grid.RowDefinitions> <Button Content="Add composition element" Click="Button_Click" Grid.Row="1" Margin="12,0" VerticalAlignment="Top" Height="40"/> <TextBlock Text="Composition content" FontSize="20" Grid.Column="1" Margin="0,12,0,4" HorizontalAlignment="Center"/> <local:CompositionHostControl x:Name="CompositionHostControl1" Grid.Row="1" Grid.Column="1" VerticalAlignment="Top" Width="600" Height="500" BorderBrush="LightGray" BorderThickness="3"/>Manipule o clique do botão para criar novos elementos. (O evento Clique já está conectado no XAML.)
Em MainWindow.xaml.cs, adicione esse método de manipulador de eventos Button_Click . Esse código chama CompositionHost.AddElement para criar um novo elemento com um tamanho e deslocamento gerados aleatoriamente.
// 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.ActualWidth - size)); float offsetY = random.Next(0, (int)(CompositionHostControl1.ActualHeight/2 - size)); CompositionHostControl1.AddElement(size, offsetX, offsetY); }
Agora você pode compilar e executar seu aplicativo WPF. Se necessário, verifique o código completo no final do tutorial para garantir que todo o código esteja nos locais certos.
Ao executar o aplicativo e clicar no botão, você deverá ver quadrados animados adicionados à interface do usuário.
Próximas Etapas
Para obter um exemplo mais completo que se baseia na mesma infraestrutura, consulte o WPF exemplo de integração de camada visual em GitHub.
Recursos adicionais
- Getting Started (WPF) (.NET)
- Interoperando com código não gerenciado (.NET)
- Introdução aos aplicativos do Windows (UWP)
- Melhore seu aplicativo de desktop para Windows (UWP)
- Namespace Windows.UI.Composition (UWP)
Código completo
Este é o código completo para este tutorial.
MainWindow.xaml
<Window x:Class="HelloComposition.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HelloComposition"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="840">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="210"/>
<ColumnDefinition Width="600"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="46"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Add composition element" Click="Button_Click"
Grid.Row="1" Margin="12,0"
VerticalAlignment="Top" Height="40"/>
<TextBlock Text="Composition content" FontSize="20"
Grid.Column="1" Margin="0,12,0,4"
HorizontalAlignment="Center"/>
<local:CompositionHostControl x:Name="CompositionHostControl1"
Grid.Row="1" Grid.Column="1"
VerticalAlignment="Top"
Width="600" Height="500"
BorderBrush="LightGray" BorderThickness="3"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
namespace HelloComposition
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
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.ActualWidth - size));
float offsetY = random.Next(0, (int)(CompositionHostControl1.ActualHeight/2 - size));
CompositionHostControl1.AddElement(size, offsetX, offsetY);
}
}
}
CompositionHostControl.xaml
<UserControl x:Class="HelloComposition.CompositionHostControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:HelloComposition"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Border Name="CompositionHostElement"/>
</UserControl>
CompositionHostControl.xaml.cs
using System;
using System.Numerics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Windows.UI.Composition;
namespace HelloComposition
{
/// <summary>
/// Interaction logic for CompositionHostControl.xaml
/// </summary>
public partial class CompositionHostControl : UserControl
{
CompositionHost compositionHost;
Compositor compositor;
Windows.UI.Composition.ContainerVisual containerVisual;
DpiScale currentDpi;
public CompositionHostControl()
{
InitializeComponent();
Loaded += CompositionHostControl_Loaded;
}
private void CompositionHostControl_Loaded(object sender, RoutedEventArgs e)
{
// If the user changes the DPI scale setting for the screen the app is on,
// the CompositionHostControl is reloaded. Don't redo this set up if it's
// already been done.
if (compositionHost is null)
{
currentDpi = VisualTreeHelper.GetDpi(this);
compositionHost = new CompositionHost(CompositionHostElement.ActualHeight, CompositionHostElement.ActualWidth);
CompositionHostElement.Child = compositionHost;
compositor = compositionHost.Compositor;
containerVisual = compositor.CreateContainerVisual();
compositionHost.Child = containerVisual;
}
}
protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi)
{
base.OnDpiChanged(oldDpi, newDpi);
currentDpi = newDpi;
Vector3 newScale = new Vector3((float)newDpi.DpiScaleX, (float)newDpi.DpiScaleY, 1);
foreach (SpriteVisual child in containerVisual.Children)
{
child.Scale = newScale;
var newOffsetX = child.Offset.X * ((float)newDpi.DpiScaleX / (float)oldDpi.DpiScaleX);
var newOffsetY = child.Offset.Y * ((float)newDpi.DpiScaleY / (float)oldDpi.DpiScaleY);
child.Offset = new Vector3(newOffsetX, newOffsetY, 1);
// Adjust animations for DPI change.
AnimateSquare(child, 0);
}
}
public void AddElement(float size, float offsetX, float offsetY)
{
var visual = compositor.CreateSpriteVisual();
visual.Size = new Vector2(size, size);
visual.Scale = new Vector3((float)currentDpi.DpiScaleX, (float)currentDpi.DpiScaleY, 1);
visual.Brush = compositor.CreateColorBrush(GetRandomColor());
visual.Offset = new Vector3(offsetX * (float)currentDpi.DpiScaleX, offsetY * (float)currentDpi.DpiScaleY, 0);
containerVisual.Children.InsertAtTop(visual);
AnimateSquare(visual, 3);
}
private void AnimateSquare(SpriteVisual visual, int delay)
{
float offsetX = (float)(visual.Offset.X); // Already adjusted for DPI.
// Adjust values for DPI scale, then find the Y offset that aligns the bottom of the square
// with the bottom of the host container. This is the value to animate to.
var hostHeightAdj = CompositionHostElement.ActualHeight * currentDpi.DpiScaleY;
var squareSizeAdj = visual.Size.Y * currentDpi.DpiScaleY;
float bottom = (float)(hostHeightAdj - squareSizeAdj);
// Create the animation only if it's needed.
if (visual.Offset.Y != bottom)
{
Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation();
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.Interop;
using Windows.UI.Composition;
namespace HelloComposition
{
class CompositionHost : HwndHost
{
IntPtr hwndHost;
int hostHeight, hostWidth;
object dispatcherQueue;
ICompositionTarget compositionTarget;
public Compositor Compositor { get; private set; }
public Visual Child
{
set
{
if (Compositor == null)
{
InitComposition(hwndHost);
}
compositionTarget.Root = value;
}
}
internal const int
WS_CHILD = 0x40000000,
WS_VISIBLE = 0x10000000,
LBS_NOTIFY = 0x00000001,
HOST_ID = 0x00000002,
LISTBOX_ID = 0x00000001,
WS_VSCROLL = 0x00200000,
WS_BORDER = 0x00800000;
public CompositionHost(double height, double width)
{
hostHeight = (int)height;
hostWidth = (int)width;
}
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
// Create Window
hwndHost = IntPtr.Zero;
hwndHost = CreateWindowEx(0, "static", "",
WS_CHILD | WS_VISIBLE,
0, 0,
hostWidth, hostHeight,
hwndParent.Handle,
(IntPtr)HOST_ID,
IntPtr.Zero,
0);
// Create Dispatcher Queue
dispatcherQueue = InitializeCoreDispatcher();
// Build Composition Tree of content
InitComposition(hwndHost);
return new HandleRef(this, hwndHost);
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
if (compositionTarget.Root != null)
{
compositionTarget.Root.Dispose();
}
DestroyWindow(hwnd.Handle);
}
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"); }
}
#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);
[DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(int dwExStyle,
string lpszClassName,
string lpszWindowName,
int style,
int x, int y,
int width, int height,
IntPtr hwndParent,
IntPtr hMenu,
IntPtr hInst,
[MarshalAs(UnmanagedType.AsAny)] object pvParam);
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);
#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
}