Studi kasus Windows Phone Silverlight ke UWP: Bookstore2
Studi kasus ini—yang dibangun berdasarkan info yang diberikan di Bookstore1—dimulai dengan aplikasi Windows Phone Silverlight yang menampilkan data yang dikelompokkan dalam LongListSelector. Dalam model tampilan, setiap instans penulis kelas mewakili grup buku yang ditulis oleh penulis tersebut, dan di LongListSelector, kita dapat melihat daftar buku yang dikelompokkan menurut penulis atau kita dapat memperkecil untuk melihat daftar lompat penulis. Daftar lompat membeli navigasi yang jauh lebih cepat daripada menggulir daftar buku. Kami menelusuri langkah-langkah porting aplikasi ke aplikasi Windows 10 Platform Windows Universal (UWP).
Catatan Saat membuka Bookstore2Universal_10 di Visual Studio, jika Anda melihat pesan "Pembaruan Visual Studio diperlukan", maka ikuti langkah-langkah untuk mengatur Versi Platform Target di TargetPlatformVersion.
Unduhan
Unduh aplikasi Bookstore2WPSL8 Windows Phone Silverlight.
Unduh aplikasi Windows 10 Bookstore2Universal_10.
Aplikasi Windows Phone Silverlight
Ilustrasi di bawah ini menunjukkan seperti apa Bookstore2WPSL8—aplikasi yang akan kita port—. Ini adalah LongListSelector yang menggulir vertikal buku yang dikelompokkan menurut penulis. Anda dapat memperkecil ke jump list, dan dari sana, Anda dapat menavigasi kembali ke grup mana pun. Ada dua bagian utama untuk aplikasi ini: model tampilan yang menyediakan sumber data yang dikelompokkan, dan antarmuka pengguna yang mengikat model tampilan tersebut. Seperti yang akan kita lihat, kedua bagian ini port dengan mudah dari teknologi Windows Phone Silverlight ke Platform Windows Universal (UWP).
Porting ke proyek Windows 10
Ini adalah tugas cepat untuk membuat proyek baru di Visual Studio, menyalin file ke dalamnya dari Bookstore2WPSL8, dan menyertakan file yang disalin dalam proyek baru. Mulailah dengan membuat proyek Aplikasi Kosong (Windows Universal) baru. Beri nama Bookstore2Universal_10. Ini adalah file yang akan disalin dari Bookstore2WPSL8 ke Bookstore2Universal_10.
- Salin folder yang berisi file PNG gambar sampul buku (foldernya adalah \Assets\CoverImages). Setelah menyalin folder, di Penjelajah Solusi, pastikan Tampilkan Semua File diaktifkan. Klik kanan folder yang Anda salin dan klik Sertakan Dalam Proyek. Perintah itu adalah apa yang kita maksud dengan "menyertakan" file atau folder dalam proyek. Setiap kali Anda menyalin file atau folder, klik Refresh di Penjelajah Solusi lalu sertakan file atau folder dalam proyek. Tidak perlu melakukan ini untuk file yang Anda ganti di tujuan.
- Salin folder yang berisi file sumber model tampilan (foldernya adalah \ViewModel).
- Salin MainPage.xaml dan ganti file di tujuan.
Kita dapat menyimpan App.xaml, dan App.xaml.cs yang dihasilkan Visual Studio untuk kita dalam proyek Windows 10.
Edit kode sumber dan file markup yang baru saja Anda salin dan ubah referensi apa pun ke namespace Bookstore2WPSL8 ke Bookstore2Universal_10. Cara cepat untuk melakukannya adalah dengan menggunakan fitur Replace In Files . Dalam kode imperatif dalam file sumber model tampilan, perubahan port ini diperlukan.
- Ubah
System.ComponentModel.DesignerProperties
keDesignMode
lalu gunakan perintah Atasi di atasnya.IsInDesignTool
Hapus properti dan gunakan IntelliSense untuk menambahkan nama properti yang benar:DesignModeEnabled
. - Gunakan perintah Atasi pada
ImageSource
. - Gunakan perintah Atasi pada
BitmapImage
. - Hapus
using System.Windows.Media;
danusing System.Windows.Media.Imaging;
. - Ubah nilai yang dikembalikan oleh properti Bookstore2Universal_10.BookstoreViewModel.AppName dari "BOOKSTORE2WPSL8" menjadi "BOOKSTORE2UNIVERSAL".
- Sama seperti yang kami lakukan untuk Bookstore1, perbarui implementasi properti BookSku.CoverImage (lihat Mengikat Gambar ke model tampilan).
Di MainPage.xaml, perubahan port awal ini diperlukan.
- Ubah
phone:PhoneApplicationPage
kePage
(termasuk kemunculan dalam sintaks elemen properti). phone
Hapus deklarasi awalan namespace danshell
.- Ubah "clr-namespace" menjadi "using" dalam deklarasi awalan namespace yang tersisa.
- Hapus
SupportedOrientations="Portrait"
, danOrientation="Portrait"
, dan konfigurasikan Potret dalam manifes paket aplikasi di proyek baru. - Hapus
shell:SystemTray.IsVisible="True"
. - Jenis pengonversi item jump list (yang ada dalam markup sebagai sumber daya) telah dipindahkan ke namespace Windows.UI.Xaml.Controls.Primitives. Jadi, tambahkan deklarasi awalan namespace Windows_UI_Xaml_Controls_Primitives dan petakan ke Windows.UI.Xaml.Controls.Primitives. Pada sumber daya konverter item jump list, ubah awalan dari
phone:
keWindows_UI_Xaml_Controls_Primitives:
. - Seperti yang kami lakukan untuk Bookstore1, ganti semua referensi ke
PhoneTextExtraLargeStyle
gaya TextBlock dengan referensi keSubtitleTextBlockStyle
, gantiPhoneTextSubtleStyle
dengan , gantiPhoneTextNormalStyle
denganSubtitleTextBlockStyle
CaptionTextBlockStyle
, dan gantiPhoneTextTitle1Style
denganHeaderTextBlockStyle
. - Ada satu pengecualian di
BookTemplate
. Gaya TextBlock kedua harus mereferensikanCaptionTextBlockStyle
. - Hapus atribut FontFamily dari TextBlock di dalam
AuthorGroupHeaderTemplate
dan atur Latar Belakang Batas ke referensiSystemControlBackgroundAccentBrush
, bukanPhoneAccentBrush
. - Karena perubahan yang terkait dengan tampilan piksel, telusuri markup dan kalikan dimensi ukuran tetap (margin, lebar, tinggi, dll) sebesar 0,8.
Mengganti LongListSelector
Mengganti LongListSelector dengan kontrol SemanticZoom akan mengambil beberapa langkah, jadi mari kita mulai. LongListSelector mengikat langsung ke sumber data yang dikelompokkan, tetapi SemanticZoom berisi kontrol ListView atau GridView, yang mengikat secara tidak langsung ke data melalui adaptor CollectionViewSource. CollectionViewSource perlu ada di markup sebagai sumber daya, jadi mari kita mulai dengan menambahkannya ke markup di MainPage.xaml di dalam <Page.Resources>
.
<CollectionViewSource
x:Name="AuthorHasACollectionOfBookSku"
Source="{Binding Authors}"
IsSourceGrouped="true"/>
Perhatikan bahwa pengikatan pada LongListSelector.ItemsSource menjadi nilai CollectionViewSource.Source, dan LongListSelector.IsGroupingEnabled menjadi CollectionViewSource.IsSourceGrouped. CollectionViewSource memiliki nama (catatan: bukan kunci, seperti yang Anda harapkan) sehingga kami dapat mengikatnya.
Selanjutnya, ganti phone:LongListSelector
dengan markup ini, yang akan memberi kita SemanticZoom awal untuk bekerja dengan.
<SemanticZoom>
<SemanticZoom.ZoomedInView>
<ListView
ItemsSource="{Binding Source={StaticResource AuthorHasACollectionOfBookSku}}"
ItemTemplate="{StaticResource BookTemplate}">
<ListView.GroupStyle>
<GroupStyle
HeaderTemplate="{StaticResource AuthorGroupHeaderTemplate}"
HidesIfEmpty="True"/>
</ListView.GroupStyle>
</ListView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<ListView
ItemsSource="{Binding CollectionGroups, Source={StaticResource AuthorHasACollectionOfBookSku}}"
ItemTemplate="{StaticResource ZoomedOutAuthorTemplate}"/>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
Gagasan LongListSelector tentang mode daftar datar dan jump list dijawab dalam gagasan SemanticZoom dari tampilan zoomed-in dan zoomed-out, masing-masing. Tampilan yang diperbesar adalah properti, dan Anda mengatur properti tersebut ke instans ListView. Dalam hal ini, tampilan yang diperkecil juga diatur ke ListView, dan kedua kontrol ListView terikat ke CollectionViewSource kami. Tampilan yang diperbesar menggunakan templat item, templat header grup, dan pengaturan HideEmptyGroups yang sama (sekarang bernama HidesIfEmpty) seperti yang dilakukan daftar datar LongListSelector. Dan tampilan yang diperbesar menggunakan templat item sangat mirip dengan yang ada di dalam gaya daftar lompat LongListSelector (AuthorNameJumpListStyle
). Perhatikan juga bahwa tampilan yang diperkecil mengikat ke properti khusus CollectionViewSource bernama CollectionGroups, yang merupakan koleksi yang berisi grup daripada item.
Kita tidak lagi membutuhkan AuthorNameJumpListStyle
, setidaknya tidak semua itu. Kita hanya memerlukan templat data untuk grup (yang merupakan penulis dalam aplikasi ini) dalam tampilan yang diperkecil. Jadi, kami menghapus AuthorNameJumpListStyle
gaya dan menggantinya dengan templat data ini.
<DataTemplate x:Key="ZoomedOutAuthorTemplate">
<Border Margin="9.6,0.8" Background="{Binding Converter={StaticResource JumpListItemBackgroundConverter}}">
<TextBlock Margin="9.6,0,9.6,4.8" Text="{Binding Group.Name}" Style="{StaticResource SubtitleTextBlockStyle}"
Foreground="{Binding Converter={StaticResource JumpListItemForegroundConverter}}" VerticalAlignment="Bottom"/>
</Border>
</DataTemplate>
Perhatikan bahwa, karena konteks data templat data ini adalah grup daripada item, kami mengikat ke properti khusus bernama Grup.
Anda dapat membuat dan menjalankan aplikasi sekarang. Berikut tampilannya di emulator seluler.
Model tampilan dan tampilan yang diperbesar dan diperbesar bekerja sama dengan benar, meskipun satu masalahnya adalah kita perlu melakukan sedikit lebih banyak gaya dan pekerjaan templat. Misalnya, gaya dan kuas yang benar belum digunakan, sehingga teks tidak terlihat pada header grup yang dapat Anda klik untuk memperkecil tampilan. Jika Anda menjalankan aplikasi di perangkat desktop, maka Anda akan melihat masalah kedua, yaitu aplikasi belum menyesuaikan antarmuka penggunanya untuk memberikan pengalaman dan penggunaan ruang terbaik pada perangkat yang lebih besar di mana windows dapat berpotensi jauh lebih besar daripada layar perangkat seluler. Jadi, di beberapa bagian berikutnya (Gaya dan templat awal, Antarmuka Pengguna Adaptif, dan Gaya akhir), kami akan memperbaiki masalah tersebut.
Gaya dan templat awal
Untuk memberi spasi header grup dengan baik, edit AuthorGroupHeaderTemplate
dan atur Margin "0,0,0,9.6"
di Batas.
Untuk meluaskan ruang item buku dengan baik, Edit BookTemplate
dan atur Margin ke "9.6,0"
pada kedua TextBlocks.
Untuk menata nama aplikasi dan judul halaman sedikit lebih baik, di dalam TitlePanel
, hapus Margin atas pada TextBlock kedua dengan mengatur nilai ke "7.2,0,0,0"
. Dan pada TitlePanel
dirinya sendiri, atur margin ke 0
(atau nilai apa pun yang terlihat baik untuk Anda)
Ubah LayoutRoot
Latar Belakang menjadi "{ThemeResource ApplicationPageBackgroundThemeBrush}"
.
Antarmuka pengguna adaptif
Karena kami memulai dengan aplikasi telepon, tidak mengherankan bahwa tata letak UI aplikasi port kami benar-benar hanya masuk akal untuk perangkat kecil dan jendela sempit pada tahap ini dalam prosesnya. Tapi, kami benar-benar ingin tata letak UI beradaptasi sendiri dan memanfaatkan ruang dengan lebih baik ketika aplikasi berjalan di jendela lebar (yang hanya mungkin pada perangkat dengan layar besar), dan untuk itu hanya menggunakan UI yang saat ini kita miliki ketika jendela aplikasi sempit (yang terjadi pada perangkat kecil, dan juga dapat terjadi pada perangkat besar).
Kita dapat menggunakan fitur Visual State Manager adaptif untuk mencapai hal ini. Kita akan mengatur properti pada elemen visual sehingga, secara default, UI ditata dalam status sempit menggunakan templat yang kita gunakan saat ini. Kemudian, kita akan mendeteksi kapan jendela aplikasi lebih lebar dari atau sama dengan ukuran tertentu (diukur dalam satuan piksel efektif), dan sebagai respons, kita akan mengubah properti elemen visual sehingga kita mendapatkan tata letak yang lebih besar, dan lebih luas. Kami akan menempatkan perubahan properti tersebut dalam status visual, dan kami akan menggunakan pemicu adaptif untuk terus memantau dan menentukan apakah akan menerapkan status visual tersebut, atau tidak, tergantung pada lebar jendela dalam piksel yang efektif. Kami memicu lebar jendela dalam kasus ini, tetapi dimungkinkan untuk memicu pada tinggi jendela juga.
Lebar jendela minimum 548 epx sesuai untuk kasus penggunaan ini karena itulah ukuran perangkat terkecil yang ingin kita tampilkan tata letak lebarnya. Ponsel biasanya lebih kecil dari 548 epx, jadi pada perangkat kecil seperti itu, kita akan tetap berada di tata letak sempit default. Pada PC, jendela akan diluncurkan secara default cukup lebar untuk memicu sakelar ke keadaan lebar, yang akan menampilkan item berukuran 250x250. Dari sana, Anda akan dapat menyeret jendela yang cukup sempit untuk menampilkan minimal dua kolom item 250x250. Setiap yang lebih sempit dari itu dan pemicu akan dinonaktifkan, status visual lebar akan dihapus, dan tata letak sempit default akan berlaku.
Sebelum menangani bagian Visual State Manager adaptif, pertama-tama kita perlu merancang status lebar dan itu berarti menambahkan beberapa elemen visual dan templat baru ke markup kita. Langkah-langkah ini menjelaskan cara melakukannya. Dengan cara penamaan konvensi untuk elemen visual dan templat, kita akan menyertakan kata "lebar" dalam nama elemen atau templat apa pun yang untuk status luas. Jika elemen atau templat tidak berisi kata "lebar", maka Anda dapat berasumsi bahwa itu untuk status sempit, yang merupakan status default dan yang nilai propertinya ditetapkan sebagai nilai lokal pada elemen visual di halaman. Hanya nilai properti untuk status lebar yang diatur melalui Status Visual aktual dalam markup.
- Buat salinan kontrol SemanticZoom di markup dan atur
x:Name="narrowSeZo"
pada salinan. Pada aslinya, aturx:Name="wideSeZo"
dan aturVisibility="Collapsed"
juga agar lebar tidak terlihat secara default. - Di
wideSeZo
, ubah ListViews ke GridViewdi tampilan yang diperbesar dan tampilan yang diperkecil. - Buat salinan ketiga sumber daya
AuthorGroupHeaderTemplate
ini , ,ZoomedOutAuthorTemplate
danBookTemplate
tambahkan kataWide
ke kunci salinan. Selain itu, perbaruiwideSeZo
sehingga mereferensikan kunci sumber daya baru ini. - Ganti konten
AuthorGroupHeaderTemplateWide
dengan<TextBlock Style="{StaticResource SubheaderTextBlockStyle}" Text="{Binding Name}"/>
. - Ganti isi
ZoomedOutAuthorTemplateWide
dengan:
<Grid HorizontalAlignment="Left" Width="250" Height="250" >
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}"/>
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"
Style="{StaticResource SubtitleTextBlockStyle}"
Height="80" Margin="15,0" Text="{Binding Group.Name}"/>
</StackPanel>
</Grid>
- Ganti isi
BookTemplateWide
dengan:
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}"/>
<Image Source="{Binding CoverImage}" Stretch="UniformToFill"/>
<StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}"
Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}"
TextWrapping="NoWrap" TextTrimming="CharacterEllipsis"
Margin="12,0,24,0" Text="{Binding Title}"/>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" Text="{Binding Author.Name}"
Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis" Margin="12,0,12,12"/>
</StackPanel>
</Grid>
- Untuk keadaan lebar, grup dalam tampilan yang diperbesar akan membutuhkan lebih banyak ruang bernapas vertikal di sekitarnya. Membuat dan mereferensikan templat panel item akan memberi kita hasil yang kita inginkan. Berikut tampilan markupnya.
<ItemsPanelTemplate x:Key="ZoomedInItemsPanelTemplate">
<ItemsWrapGrid Orientation="Horizontal" GroupPadding="0,0,0,20"/>
</ItemsPanelTemplate>
...
<SemanticZoom x:Name="wideSeZo" ... >
<SemanticZoom.ZoomedInView>
<GridView
...
ItemsPanel="{StaticResource ZoomedInItemsPanelTemplate}">
...
- Terakhir, tambahkan markup Visual State Manager yang sesuai sebagai anak pertama dari
LayoutRoot
.
<Grid x:Name="LayoutRoot" ... >
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="WideState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="548"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="wideSeZo.Visibility" Value="Visible"/>
<Setter Target="narrowSeZo.Visibility" Value="Collapsed"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
...
Gaya akhir
Semua yang tersisa adalah beberapa tweak gaya akhir.
- Di
AuthorGroupHeaderTemplate
, aturForeground="White"
pada TextBlock sehingga terlihat benar saat berjalan pada keluarga perangkat seluler. - Tambahkan
FontWeight="SemiBold"
ke TextBlock di danAuthorGroupHeaderTemplate
ZoomedOutAuthorTemplate
. - Di
narrowSeZo
, header grup dan penulis dalam tampilan yang diperkecil diratakan kiri alih-alih direntangkan, jadi mari kita kerjakan. Kita akan membuat HeaderContainerStyle untuk tampilan yang diperbesar tampilan dengan HorizontalContentAlignment diatur keStretch
. Dan kita akan membuat ItemContainerStyle untuk tampilan zoomed-out yang berisi Setter yang sama. Inilah yang terlihat seperti.
<Style x:Key="AuthorGroupHeaderContainerStyle" TargetType="ListViewHeaderItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
<Style x:Key="ZoomedOutAuthorItemContainerStyle" TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
...
<SemanticZoom x:Name="narrowSeZo" ... >
<SemanticZoom.ZoomedInView>
<ListView
...
<ListView.GroupStyle>
<GroupStyle
...
HeaderContainerStyle="{StaticResource AuthorGroupHeaderContainerStyle}"
...
<SemanticZoom.ZoomedOutView>
<ListView
...
ItemContainerStyle="{StaticResource ZoomedOutAuthorItemContainerStyle}"
...
Urutan operasi gaya terakhir itu membuat aplikasi terlihat seperti ini.
Aplikasi Windows 10 port yang berjalan pada perangkat Desktop, tampilan yang diperbesar tampilan, dua ukuran jendela
Aplikasi Windows 10 port yang berjalan pada perangkat Desktop, tampilan yang diperkecil, dua ukuran jendela
Aplikasi Windows 10 port yang berjalan di perangkat Seluler, tampilan yang diperbesar tampilan
Aplikasi Windows 10 port yang berjalan pada perangkat Seluler, tampilan yang diperkecil
Membuat model tampilan lebih fleksibel
Bagian ini berisi contoh fasilitas yang terbuka bagi kami karena telah memindahkan aplikasi kami untuk menggunakan UWP. Di sini, kami menjelaskan langkah-langkah opsional yang dapat Anda ikuti untuk membuat model tampilan Anda lebih fleksibel saat diakses melalui CollectionViewSource. Model tampilan (file sumber berada di ViewModel\BookstoreViewModel.cs) yang kami port dari aplikasi Windows Phone Silverlight Bookstore2WPSL8 berisi kelas bernama Penulis, yang berasal dari Daftar<T>, di mana T adalah BookSku. Itu berarti bahwa kelas Penulis adalah sekelompok BookSku.
Ketika kami mengikat CollectionViewSource.Source ke Penulis, satu-satunya hal yang kami komunikasikan adalah bahwa setiap Penulis dalam Penulis adalah sekelompok sesuatu. Kami menyerahkannya ke CollectionViewSource untuk menentukan bahwa Penulis, dalam hal ini, sekelompok BookSku. Itu berfungsi: tetapi tidak fleksibel. Bagaimana jika kita ingin Penulis menjadi sekelompok BookSku dan sekelompok alamat tempat penulis tinggal? Penulis tidak boleh menjadi kedua grup tersebut. Tetapi, Penulis dapat memiliki sejumlah grup. Dan itulah solusinya: gunakan pola has-a-group alih-alih, atau selain itu, pola is-a-group yang kita gunakan saat ini. Berikut caranya:
- Ubah Penulis agar tidak lagi berasal dari Daftar<T>.
- Tambahkan bidang ini ke
- Tambahkan properti ini ke
- Dan tentu saja kita dapat mengulangi dua langkah di atas untuk menambahkan grup sebanyak mungkin ke Penulis sesuai kebutuhan.
- Ubah implementasi metode AddBookSku menjadi
this.BookSkus.Add(bookSku);
. - Sekarang penulis memiliki setidaknya satu grup, kita perlu berkomunikasi dengan CollectionViewSource yang mana dari grup yang harus digunakannya. Untuk melakukannya, tambahkan properti ini ke CollectionViewSource:
ItemsPath="BookSkus"
Perubahan tersebut membuat aplikasi ini secara fungsional tidak berubah, tetapi Anda sekarang tahu bagaimana Anda dapat memperluas Penulis, dan CollectionViewSource, jika Perlu. Mari kita buat satu perubahan terakhir pada Penulis sehingga, jika kita menggunakannya tanpa menentukan CollectionViewSource.ItemsPath, grup default yang kita pilih akan digunakan:
public class Author : IEnumerable<BookSku>
{
...
public IEnumerator<BookSku> GetEnumerator()
{
return this.BookSkus.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.BookSkus.GetEnumerator();
}
}
Dan sekarang kita dapat memilih untuk menghapus ItemsPath="BookSkus"
jika kita suka dan aplikasi masih akan berulah dengan cara yang sama.
Kesimpulan
Studi kasus ini melibatkan antarmuka pengguna yang lebih ambisius daripada yang sebelumnya. Semua fasilitas dan konsep Windows Phone Silverlight LongListSelector—dan banyak lagi—ditemukan tersedia untuk aplikasi UWP dalam bentuk SemanticZoom, ListView, GridView, dan CollectionViewSource. Kami menunjukkan cara menggunakan kembali, atau menyalin dan mengedit, baik kode imperatif maupun markup dalam aplikasi UWP untuk mencapai fungsionalitas, UI, dan interaksi yang disesuaikan agar sesuai dengan faktor bentuk perangkat Windows tersempit dan terluas dan semua ukuran di antaranya.