Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Anda dapat menggunakan API Komposisi Windows Runtime (juga disebut lapisan Visual) di aplikasi Windows Presentation Foundation (WPF) Anda untuk menciptakan pengalaman modern yang menyala bagi pengguna Windows.
Kode lengkap untuk tutorial ini tersedia di GitHub: WPF sampel HelloComposition.
Prasyarat
API hosting UWP XAML memiliki prasyarat ini.
- Kami berasumsi bahwa Anda memiliki keakraban dengan pengembangan aplikasi menggunakan WPF dan UWP. Untuk informasi selengkapnya, lihat:
- .NET Framework 4.7.2 atau yang lebih baru
- Windows 10 versi 1803 atau yang terbaru
- Windows 10 SDK 17134 atau yang lebih baru
Cara menggunakan API Komposisi di WPF
Dalam tutorial ini, Anda membuat antarmuka pengguna aplikasi WPF sederhana dan menambahkan elemen Komposisi animasi ke dalamnya. Komponen WPF dan Komposisi tetap sederhana, tetapi kode interop yang ditampilkan sama terlepas dari kompleksitas komponen. Aplikasi yang sudah selesai terlihat seperti ini.
Membuat proyek WPF
Langkah pertama adalah membuat proyek aplikasi WPF, yang mencakup definisi aplikasi dan halaman XAML untuk UI.
Untuk membuat proyek Aplikasi WPF baru di Visual C# bernama HelloComposition:
Buka Visual Studio dan pilih File>Proyek Baru.>
Dialog Proyek Baru terbuka.
Di bawah kategori Installed, perluas node Visual C#, lalu pilih Windows Desktop.
Pilih templat WPF App (.NET Framework).
Masukkan nama HelloComposition, pilih Framework .NET Framework 4.7.2, lalu klik OK.
Visual Studio membuat proyek dan membuka perancang untuk jendela aplikasi default bernama MainWindow.xaml.
Mengonfigurasi proyek untuk menggunakan WINDOWS Runtime API
Untuk menggunakan API Windows Runtime (WinRT) di aplikasi WPF, Anda perlu mengonfigurasi proyek Visual Studio untuk mengakses Windows Runtime. Selain itu, vektor digunakan secara ekstensif oleh API Komposisi, jadi Anda perlu menambahkan referensi yang diperlukan untuk menggunakan vektor.
Paket NuGet tersedia untuk mengatasi kedua kebutuhan ini. Instal versi terbaru paket ini untuk menambahkan referensi yang diperlukan ke proyek Anda.
- Microsoft.Windows.SDK.Contracts (Memerlukan format manajemen paket default dengan diatur ke PackageReference.)
- System.Numerics.Vectors
Note
Meskipun sebaiknya gunakan paket NuGet untuk mengonfigurasi proyek Anda, Anda dapat menambahkan referensi yang diperlukan secara manual. Untuk informasi selengkapnya, lihat Tingkatkan aplikasi desktop Anda untuk Windows. Tabel berikut ini memperlihatkan file yang perlu Anda tambahkan referensinya.
| File | Location |
|---|---|
| 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:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.7.2 |
Konfigurasi proyek agar dapat mengenali DPI per-monitor
Konten lapisan visual yang Anda tambahkan ke aplikasi tidak secara otomatis diskalakan agar sesuai dengan pengaturan DPI layar tempat konten ditampilkan. Anda perlu mengaktifkan kesadaran DPI per monitor untuk aplikasi Anda, lalu memastikan bahwa kode yang Anda gunakan untuk membuat konten lapisan visual memperhitungkan skala DPI saat ini saat aplikasi berjalan. Di sini, kami mengonfigurasi proyek agar peka terhadap DPI. Di bagian selanjutnya, kami menunjukkan cara menggunakan informasi DPI untuk menskalakan konten lapisan visual.
aplikasi WPF mengenali DPI Sistem secara default, tetapi perlu mendeklarasikan diri untuk mengenali DPI per-monitor dalam file app.manifest. Untuk mengaktifkan kesadaran DPI per monitor tingkat Windows di dalam file manifes aplikasi:
Di Penjelajah Solusi, klik kanan pada proyek HelloComposition.
Di menu konteks, pilih Tambahkan>Item Baru....
Dalam dialog Tambahkan Item Baru , pilih 'File Manifes Aplikasi', lalu klik Tambahkan. (Anda dapat meninggalkan nama default.)
Dalam file app.manifest, cari xml ini dan hapus tanda komentar pada:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> </windowsSettings> </application>Tambahkan pengaturan ini setelah tag pembuka
<windowsSettings>:<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>Anda juga perlu mengatur pengaturan DoNotScaleForDpiChanges dalam file App.config.
Buka App.Config dan tambahkan xml ini di
<configuration>dalam elemen:<runtime> <AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/> </runtime>
Note
AppContextSwitchOverrides hanya dapat diatur sekali. Jika aplikasi Anda sudah memiliki satu set, Anda harus menggunakan titik koma untuk memisahkan sakelar ini di dalam nilai atribut.
(Untuk informasi selengkapnya, lihat Panduan dan sampel pengembang Per Monitor DPI di GitHub.)
Membuat kelas turunan HwndHost untuk menghosting elemen komposisi
Untuk menghosting konten yang Anda buat dengan lapisan visual, Anda perlu membuat kelas yang berasal dari HwndHost. Di sinilah Anda melakukan sebagian besar konfigurasi untuk menghosting API Komposisi. Di kelas ini, Anda menggunakan Platform Invocation Services (PInvoke) dan COM Interop untuk membawa API Komposisi ke aplikasi WPF Anda. Untuk informasi selengkapnya tentang PInvoke dan COM Interop, lihat Mengoperasikan dengan kode yang tidak dikelola.
Petunjuk / Saran
Jika perlu, periksa kode lengkap di akhir tutorial untuk memastikan semua kode berada di tempat yang tepat saat Anda bekerja melalui tutorial.
Tambahkan file kelas baru ke proyek Anda yang berasal dari HwndHost.
- Di Penjelajah Solusi, klik kanan pada proyek HelloComposition.
- Di menu konteks, pilih Tambahkan>Kelas....
- Dalam dialog Tambahkan Item Baru , beri nama kelas CompositionHost.cs, lalu klik Tambahkan.
Dalam CompositionHost.cs, edit definisi kelas untuk berasal dari HwndHost.
// Add // using System.Windows.Interop; namespace HelloComposition { class CompositionHost : HwndHost { } }Tambahkan kode dan konstruktor berikut ke kelas .
// 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; }Timpa metode BuildWindowCore dan DestroyWindowCore.
Note
Di BuildWindowCore, Anda memanggil metode InitializeCoreDispatcher dan InitComposition . Anda membuat metode ini di langkah berikutnya.
// 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 dan DestroyWindow memerlukan deklarasi PInvoke. Tempatkan deklarasi ini di akhir kode untuk kelas .
#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 declarationsMenginisialisasi thread dengan CoreDispatcher. Dispatcher inti bertanggung jawab untuk memproses pesan jendela dan mengirimkan peristiwa untuk API WinRT. Instans baru CoreDispatcher harus dibuat pada utas yang memiliki CoreDispatcher.
- Buat metode bernama InitializeCoreDispatcher dan tambahkan kode untuk menyiapkan antrean 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; }- Antrian dispatcher juga memerlukan deklarasi PInvoke. Tempatkan deklarasi ini di dalam wilayah deklarasi PInvoke yang Anda buat di langkah sebelumnya.
//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);Anda sekarang memiliki antrean dispatcher yang siap dan dapat mulai menginisialisasi dan membuat konten Komposisi.
Inisialisasi Compositor. Compositor adalah pembuat yang menghasilkan berbagai jenis objek dalam namespace Windows.UI.Composition yang meliputi visual, sistem efek, dan sistem animasi. Kelas Compositor juga mengelola masa pakai objek yang dibuat dari pabrik.
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 dan ICompositionTarget memerlukan impor COM. Tempatkan kode ini setelah kelas CompositionHost, tetapi di dalam deklarasi 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
Membuat UserControl untuk menambahkan konten Anda ke pohon visual WPF
Langkah terakhir untuk menyiapkan infrastruktur yang diperlukan untuk menghosting konten Komposisi adalah menambahkan HwndHost ke pohon visual WPF.
Membuat UserControl
UserControl adalah cara mudah untuk mengemas kode Anda yang membuat dan mengelola konten Komposisi, dan dengan mudah menambahkan konten ke XAML Anda.
Tambahkan file kontrol pengguna baru ke proyek Anda.
- Di Penjelajah Solusi, klik kanan pada proyek HelloComposition.
- Di menu konteks, pilih Tambahkan>Kontrol Pengguna....
- Dalam dialog Tambahkan Item Baru , beri nama kontrol pengguna CompositionHostControl.xaml, lalu klik Tambahkan.
File CompositionHostControl.xaml dan CompositionHostControl.xaml.cs dibuat dan ditambahkan ke proyek Anda.
Dalam CompositionHostControl.xaml, ganti
<Grid> </Grid>tag dengan elemen Border ini, yang merupakan kontainer XAML yang akan digunakan HwndHost Anda.<Border Name="CompositionHostElement"/>
Dalam kode untuk kontrol pengguna, Anda membuat instans kelas CompositionHost yang Anda buat di langkah sebelumnya dan menambahkannya sebagai elemen turunan dari CompositionHostElement, Batas yang Anda buat di halaman XAML.
Di CompositionHostControl.xaml.cs, tambahkan variabel privat untuk objek yang akan Anda gunakan dalam kode Komposisi Anda. Tambahkan ini setelah definisi kelas.
CompositionHost compositionHost; Compositor compositor; Windows.UI.Composition.ContainerVisual containerVisual; DpiScale currentDpi;Tambahkan handler untuk peristiwa Loaded kontrol pengguna. Di sinilah Anda menyiapkan instans CompositionHost Anda.
- Di konstruktor, hubungkan penanganan aktivitas seperti yang ditunjukkan di sini (
Loaded += CompositionHostControl_Loaded;).
public CompositionHostControl() { InitializeComponent(); Loaded += CompositionHostControl_Loaded; }- Tambahkan metode penanganan aktivitas dengan nama 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; } }Dalam metode ini, Anda menyiapkan objek yang akan Anda gunakan dalam kode Komposisi Anda. Berikut adalah sekilas apa yang terjadi.
- Pertama, pastikan pengaturan hanya dilakukan sekali dengan memeriksa apakah instans CompositionHost sudah ada.
// 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) { }- Dapatkan DPI saat ini. Ini digunakan untuk menskalakan elemen Komposisi Anda dengan benar.
currentDpi = VisualTreeHelper.GetDpi(this);- Buat instance dari CompositionHost dan tetapkan sebagai child dari Border, CompositionHostElement.
compositionHost = new CompositionHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth); ControlHostElement.Child = compositionHost;- Dapatkan Compositor dari CompositionHost.
compositor = compositionHost.Compositor;- Gunakan Compositor untuk membuat visual kontainer. Ini adalah kontainer Komposisi tempat Anda menambahkan elemen Komposisi.
containerVisual = compositor.CreateContainerVisual(); compositionHost.Child = containerVisual;- Di konstruktor, hubungkan penanganan aktivitas seperti yang ditunjukkan di sini (
Menambahkan elemen komposisi
Dengan infrastruktur di tempat, Anda sekarang dapat menghasilkan konten Komposisi yang ingin Anda tampilkan.
Untuk contoh ini, Anda menambahkan kode yang membuat dan menganimasikan SpriteVisual persegi sederhana.
Tambahkan elemen komposisi. Dalam CompositionHostControl.xaml.cs, tambahkan metode ini ke kelas 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); }
Menangani perubahan DPI
Kode untuk menambahkan dan menganimasikan elemen memperhitungkan skala DPI saat ini saat elemen dibuat, tetapi Anda juga perlu memperhitungkan perubahan DPI saat aplikasi sedang berjalan. Anda dapat menangani peristiwa HwndHost.DpiChanged untuk diberi tahu tentang perubahan dan menyesuaikan perhitungan Anda berdasarkan DPI baru.
Dalam metode CompositionHostControl_Loaded, setelah baris terakhir, tambahkan ini untuk menghubungkan penanganan aktivitas DpiChanged.
compositionHost.DpiChanged += CompositionHost_DpiChanged;Tambahkan metode penanganan aktivitas dengan nama CompositionHostDpiChanged. Kode ini menyesuaikan skala dan offset setiap elemen, dan menghitung ulang animasi apa pun yang tidak selesai.
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); } }
Menambahkan kontrol pengguna ke halaman XAML Anda
Sekarang, Anda dapat menambahkan kontrol pengguna ke UI XAML Anda.
Di MainWindow.xaml, atur Tinggi Jendela ke 600 dan Lebar ke 840.
Tambahkan XAML untuk UI. Di MainWindow.xaml, tambahkan XAML ini di antara tag akar
<Grid> </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"/>Tangani penekanan tombol untuk membuat elemen baru. (Peristiwa Klik sudah dikaitkan di XAML.)
Di MainWindow.xaml.cs, tambahkan metode penanganan aktivitas Button_Click ini. Kode ini memanggil CompositionHost.AddElement untuk membuat elemen baru dengan ukuran dan offset yang dihasilkan secara acak.
// 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); }
Sekarang Anda dapat membuat dan menjalankan aplikasi WPF Anda. Jika perlu, periksa kode lengkap di akhir tutorial untuk memastikan semua kode berada di tempat yang tepat.
Saat Anda menjalankan aplikasi dan mengklik tombol , Anda akan melihat kotak animasi ditambahkan ke UI.
Langkah berikutnya
Untuk contoh yang lebih lengkap yang dibangun pada infrastruktur yang sama, lihat sampel integrasi lapisan WPF Visual pada GitHub.
Sumber daya tambahan
- Panduan Memulai (WPF) (.NET)
- Interoperating dengan kode yang tidak dikelola (.NET)
- Mulai menggunakan aplikasi Windows (UWP)
- Tingkatkan aplikasi desktop Anda untuk Windows (UWP)
- Windows.UI.Composition namespace (UWP)
Kode lengkap
Berikut adalah kode lengkap untuk tutorial ini.
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);
}
}
}
KomposisiHostControl.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
}