Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Można używać interfejsów API kompozycji środowiska uruchomieniowego systemu Windows (nazywanych również Visual Layer) w aplikacjach Windows Presentation Foundation (WPF), aby tworzyć nowoczesne doświadczenia, które zachwycają użytkowników systemu Windows.
Kompletny kod tego samouczka jest dostępny w witrynie GitHub: przykład WPF HelloComposition.
Wymagania wstępne
API hostingu XAML UWP ma następujące wymagania wstępne.
- Zakładamy, że masz pewną znajomość tworzenia aplikacji przy użyciu platform WPF i UWP. Aby uzyskać więcej informacji, zobacz:
- .NET Framework 4.7.2 lub nowszy
- Windows 10 w wersji 1803 lub nowszej
- Windows 10 SDK 17134 lub nowszy
Jak używać API kompozycji w WPF
W tym samouczku utworzysz prosty interfejs użytkownika aplikacji WPF i dodasz do niego animowane elementy kompozycji. Zarówno składniki WPF, jak i Composition są proste, ale pokazany kod międzyoperajności jest taki sam, niezależnie od złożoności składników. Gotowa aplikacja wygląda następująco.
Tworzenie projektu WPF
Pierwszym krokiem jest utworzenie projektu aplikacji WPF, który zawiera definicję aplikacji i stronę XAML interfejsu użytkownika.
Aby utworzyć nowy projekt aplikacji WPF w języku Visual C# o nazwie HelloComposition:
Otwórz program Visual Studio i wybierz pozycję Plik >Nowy>Projekt.
Otwiera się okno dialogowe Nowy projekt.
W kategorii Zainstalowane rozwiń wątek Visual C#, a następnie wybierz pozycję Windows Desktop.
Wybierz szablon Aplikacja WPF (.NET Framework).
Wprowadź nazwę HelloComposition, wybierz pozycję Framework .NET Framework 4.7.2, a następnie kliknij przycisk OK.
Program Visual Studio tworzy projekt i otwiera projektanta domyślnego okna aplikacji o nazwie MainWindow.xaml.
Konfigurowanie projektu do używania interfejsów API środowiska uruchomieniowego systemu Windows
Aby używać interfejsów API środowiska Uruchomieniowego systemu Windows (WinRT) w aplikacji WPF, należy skonfigurować projekt programu Visual Studio w celu uzyskania dostępu do środowiska uruchomieniowego systemu Windows. Ponadto wektory są szeroko używane przez API kompozycji, dlatego należy dodać odwołania wymagane do użycia wektorów.
Pakiety NuGet są dostępne dla obu tych potrzeb. Zainstaluj najnowsze wersje tych pakietów, aby dodać niezbędne odwołania do projektu.
- Microsoft.Windows.SDK.Contracts (Wymaga domyślnego formatu zarządzania pakietami ustawionego na PackageReference).
- System.Numerics.Vectors
Uwaga / Notatka
Zalecamy skonfigurowanie projektu przy użyciu pakietów NuGet, ale można ręcznie dodać wymagane odwołania. Więcej informacji znajdziesz w Ulepszanie aplikacji desktopowej dla systemu Windows. W poniższej tabeli przedstawiono pliki, do których należy dodać odwołania.
| Plik | Lokalizacja |
|---|---|
| System.Runtime.WindowsRuntime | C:\Windows\Microsoft.NET\Framework\v4.0.30319 |
| Windows.Foundation.UniversalApiContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<wersja sdk>\Windows.Foundation.UniversalApiContract<wersja> |
| Windows.FoundationContract.winmd | C:\Program Files (x86)\Windows Kits\10\References<wersja SDK>\Windows.Foundation.FoundationContract<wersja> |
| 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 |
Konfigurowanie projektu pod kątem rozpoznawania dpi na monitor
Zawartość warstwy wizualnej dodawanej do aplikacji nie jest automatycznie skalowana w celu dopasowania do ustawień DPI ekranu, na którym jest wyświetlana. Należy włączyć świadomość DPI na monitor dla swojej aplikacji, a następnie upewnić się, że kod używany do tworzenia warstwy wizualnej uwzględnia bieżącą skalę DPI podczas uruchamiania aplikacji. W tym miejscu skonfigurujemy projekt tak, aby był świadomy dpi. W kolejnych sekcjach pokazano, jak używać informacji DPI do skalowania zawartości warstwy wizualizacji.
Domyślnie aplikacje WPF są świadome rozdzielczości DPI systemu, ale muszą zadeklarować, że są świadome rozdzielczości DPI poszczególnych monitorów w pliku app.manifest. Aby włączyć świadomość DPI na poziomie systemu Windows dla każdego monitora w pliku manifestu aplikacji:
W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy projekt HelloComposition .
W menu kontekstowym wybierz pozycję Dodaj>nowy element....
W oknie dialogowym Dodaj Nowy Element wybierz "Plik manifestu aplikacji", a następnie kliknij Dodaj. (Możesz pozostawić nazwę domyślną).
W pliku app.manifest znajdź ten plik XML i usuń jego komentarz:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application>Dodaj to ustawienie po tagu otwierającego
<windowsSettings><dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>Należy również ustawić ustawienie DoNotScaleForDpiChanges w pliku App.config.
Otwórz plik App.Config i dodaj ten plik XML wewnątrz
<configuration>elementu:<runtime> <AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/> </runtime>
Uwaga / Notatka
Właściwości AppContextSwitchOverrides można ustawić tylko raz. Jeśli aplikacja ma już ustawioną wartość, należy rozdzielić ten przełącznik średnikiem wewnątrz atrybutu wartości.
(Aby uzyskać więcej informacji, zobacz Przewodnik Dewelopera Per Monitor DPI i przykłady w serwisie GitHub.)
Tworzenie klasy pochodnej HwndHost do hostowania elementów kompozycji
Aby hostować zawartość tworzoną za pomocą warstwy wizualnej, należy utworzyć klasę pochodzącą z biblioteki HwndHost. W tym miejscu przeprowadzasz większość konfiguracji potrzebnej do hostowania interfejsów API kompozycji. W tych zajęciach użyjesz Platform Invocation Services (PInvoke) i COM Interop, aby zintegrować interfejsy API Composition z aplikacją WPF. Aby uzyskać więcej informacji na temat PInvoke i współpracy z COM z kodem niezarządzanym, zobacz Interoperating with unmanaged code.
Wskazówka
Jeśli chcesz, sprawdź kompletny kod na końcu samouczka, aby upewnić się, że cały kod znajduje się w odpowiednich miejscach podczas pracy z samouczkiem.
Dodaj nowy plik klasy do projektu, który pochodzi z HwndHost.
- W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy projekt HelloComposition .
- W menu kontekstowym wybierz pozycję Dodaj>klasę....
- W oknie dialogowym Dodaj nowy element
wpisz nazwę klasy CompositionHost.cs , a następnie kliknijDodaj .
W CompositionHost.cs edytuj definicję klasy, aby pochodzić z HwndHost.
// Add // using System.Windows.Interop; namespace HelloComposition { class CompositionHost : HwndHost { } }Dodaj następujący kod i konstruktor do klasy .
// 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; }Zastąpij metody BuildWindowCore i DestroyWindowCore.
Uwaga / Notatka
W pliku BuildWindowCore wywołasz metody InitializeCoreDispatcher i InitComposition metod. Te metody są tworzone w następnych krokach.
// 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 i DestroyWindow wymagają deklaracji PInvoke. Umieść tę deklarację na końcu kodu dla klasy.
#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 declarationsInicjuj wątek używając CoreDispatcher. Główny dyspozytor jest odpowiedzialny za przetwarzanie komunikatów okien i wysyłanie zdarzeń dla interfejsów API WinRT. Nowe wystąpienia CoreDispatcher należy utworzyć w wątku, który ma CoreDispatcher.
- Utwórz metodę o nazwie InitializeCoreDispatcher i dodaj kod w celu skonfigurowania kolejki dyspozytora.
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; }- Kolejka dyspozytora wymaga również deklaracji PInvoke. Umieść tę deklarację wewnątrz regionu PInvoke, który został utworzony w poprzednim kroku.
//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);Masz teraz gotową kolejkę dyspozytora i możesz rozpocząć inicjowanie i tworzenie treści Composition.
Zainicjować Compositor. Kompositor to fabryka, która tworzy różne typy w przestrzeni nazw Windows.UI.Composition obejmująca wizualizacje, system efektów i system animacji. Klasa Compositor zarządza również okresem istnienia obiektów utworzonych z fabryki.
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 i ICompositionTarget wymagają importu modelu COM. Umieść ten kod za klasą CompositionHost, ale wewnątrz deklaracji przestrzeni nazw.
#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
Tworzenie kontrolki UserControl w celu dodania zawartości do drzewa wizualizacji WPF
Ostatnim krokiem do skonfigurowania infrastruktury wymaganej do hostowania zawartości kompozycji jest dodanie HwndHost do drzewa wizualizacji WPF.
Tworzenie elementu UserControl
Kontrolka użytkownika (UserControl) to wygodny sposób na opakowanie kodu, który tworzy i zarządza zawartością kompozycji oraz na łatwe dodawanie tej zawartości do XAML.
Dodaj nowy plik kontrolki użytkownika do projektu.
- W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy projekt HelloComposition .
- W menu kontekstowym wybierz pozycję Dodaj>kontrolkę użytkownika....
- W oknie dialogowym Dodaj nowy element, nazwij kontrolkę użytkownika jako CompositionHostControl.xaml, a następnie kliknij Dodaj.
Pliki CompositionHostControl.xaml i CompositionHostControl.xaml.cs są tworzone i dodawane do projektu.
W pliku CompositionHostControl.xaml zastąp tagi
<Grid> </Grid>tym elementem Border, który jest kontenerem XAML, do którego zostanie umieszczony Twój HwndHost.<Border Name="CompositionHostElement"/>
W kodzie kontrolki użytkownika tworzysz wystąpienie klasy CompositionHost, którą utworzyłeś w poprzednim kroku, i dodajesz ją jako element podrzędny do CompositionHostElement, czyli obramowania utworzonego na stronie XAML.
W CompositionHostControl.xaml.cs dodaj zmienne prywatne dla obiektów, które będą używane w kodzie kompozycji. Dodaj je po definicji klasy.
CompositionHost compositionHost; Compositor compositor; Windows.UI.Composition.ContainerVisual containerVisual; DpiScale currentDpi;Dodaj procedurę obsługi dla kontrolki użytkownika dla zdarzenia Loaded. W tym miejscu skonfigurujesz instancję CompositionHost.
- W konstruktorze podłącz program obsługi zdarzeń, jak pokazano tutaj (
Loaded += CompositionHostControl_Loaded;).
public CompositionHostControl() { InitializeComponent(); Loaded += CompositionHostControl_Loaded; }- Dodaj metodę obsługi zdarzeń o nazwie 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; } }W tej metodzie skonfigurujesz obiekty, których będziesz używać w kodzie Kompozycja. Oto krótkie spojrzenie na to, co się dzieje.
- Najpierw upewnij się, że konfiguracja jest wykonywana tylko raz, sprawdzając, czy wystąpienie obiektu CompositionHost już istnieje.
// 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) { }- Uzyskaj bieżące DPI. Służy do prawidłowego skalowania elementów kompozycji.
currentDpi = VisualTreeHelper.GetDpi(this);- Utwórz wystąpienie obiektu CompositionHost i przypisz je jako element podrzędny obramowania: CompositionHostElement.
compositionHost = new CompositionHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth); ControlHostElement.Child = compositionHost;- Pobierz kompozytor z CompositionHost.
compositor = compositionHost.Compositor;- Użyj kompositora, aby utworzyć wizualizację kontenera. Jest to kontener kompozycji, do którego dodajesz elementy kompozycji.
containerVisual = compositor.CreateContainerVisual(); compositionHost.Child = containerVisual;- W konstruktorze podłącz program obsługi zdarzeń, jak pokazano tutaj (
Dodawanie elementów kompozycji
Po zainstalowaniu infrastruktury możesz teraz wygenerować zawartość kompozycji, którą chcesz wyświetlić.
W tym przykładzie dodasz kod, który tworzy i animuje prosty kwadrat SpriteVisual.
Dodaj element kompozycji. W CompositionHostControl.xaml.cs dodaj te metody do klasy 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); }
Obsługa zmian DPI
Kod do dodawania i animowania elementu uwzględnia bieżącą skalę DPI podczas tworzenia elementów, ale należy również uwzględnić zmiany DPI podczas działania aplikacji. Możesz obsłużyć zdarzenie HwndHost.DpiChanged , aby otrzymywać powiadomienia o zmianach i dostosowywać obliczenia na podstawie nowego dpi.
W metodzie CompositionHostControl_Loaded po ostatnim wierszu dodaj tę metodę, aby podłączyć program obsługi zdarzeń DpiChanged.
compositionHost.DpiChanged += CompositionHost_DpiChanged;Dodaj metodę obsługi zdarzeń o nazwie CompositionHostDpiChanged. Ten kod dostosowuje skalę i przesunięcie każdego elementu i oblicza ponownie wszystkie nieukończone animacje.
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); } }
Dodawanie kontrolki użytkownika do strony XAML
Teraz możesz dodać kontrolkę użytkownika do interfejsu użytkownika XAML.
W pliku MainWindow.xaml ustaw wartość Wysokość okna na 600 i szerokość na 840.
Dodaj kod XAML dla interfejsu użytkownika. W pliku MainWindow.xaml dodaj ten kod XAML między tagami
<Grid> </Grid>katalogu głównego.<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"/>Zajmij się kliknięciem przycisku, aby utworzyć nowe elementy. (Zdarzenie Click zostało już podłączone do kodu XAML).
W pliku MainWindow.xaml.cs dodaj metodę obsługi zdarzenia Button_Click. Ten kod wywołuje CompositionHost.AddElement, aby utworzyć nowy element o losowo wygenerowanym rozmiarze i przesunięciu.
// 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); }
Teraz możesz skompilować i uruchomić aplikację WPF. Jeśli chcesz, sprawdź kompletny kod na końcu samouczka, aby upewnić się, że cały kod znajduje się we właściwych miejscach.
Po uruchomieniu aplikacji i kliknięciu przycisku powinny zostać wyświetlone animowane kwadraty dodane do interfejsu użytkownika.
Dalsze kroki
Aby uzyskać bardziej kompletny przykład oparty na tej samej infrastrukturze, zobacz przykład integracji warstwy wizualnej WPF w witrynie GitHub.
Dodatkowe zasoby
- Wprowadzenie (WPF) ( .NET)
- Interoperacyjność z niezarządzanym kodem (.NET)
- Wprowadzenie do aplikacji systemu Windows (UWP)
- Ulepsz swoją aplikację desktopową dla systemu Windows (UWP)
- przestrzeń nazw Windows.UI.Composition (UWP)
Kompletny kod
Oto kompletny kod tego samouczka.
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
}
Windows developer