
Windows Universal Apps - nasz pierwszy projekt
- Kategoria: Programowanie, autor: Jerzy Piechowiak
- Tagi: Windows 8, .Net, Windows Phone, Universal Apps, C#
- Odsłony: 10358
W ostatnim czasie mocno wchodzę w temat Universal Apps, ponieważ jest to obszar w który w najbliższej przyszłości będę się angażować zawodowo, a jeśli czas pozwoli być może również i prywatnie. Dotychczas popełniłem dwa teksty na ten temat. Jeśli nie czytaliście ich wcześniej, to polecam lekturę rozpocząć od tego pierwszego, który omawia w sposób ogólny koncepcję aplikacji uniwersalnych dla Windows.
Ponieważ dotychczas więcej było teorii, dziś skupimy się na praktyce i zbudujemy naszą pierwszą aplikację. I bynajmniej nie będzie ona wyświetlała tylko i wyłącznie napisu Hello World
;-)
Założenia
Nasz pierwszy projekt będzie prostą galerią wyświetlającą obrazki z folderów systemowych. W przypadku Windowsa będzie to biblioteka obrazków (KnownFolders.PicturesLibrary), natomiast jego mobilny odpowiednik będzie wyświetlać zdjęcia zrobione aparatem (KnownFolders.CameraRoll).
Większość kodu umieścimy w części Shared naszego projektu. Jedynie kod XAML będzie zależny od platformy, choć jakbyśmy się postarali to również i ten dałoby się w pewnym sensie uwspólnić
wydzielając style do osobnych plików.
Czas przystąpić do działania;-)
Przygotowania
Aby móc tworzyć projekty Universal Apps, musimy mieć na komputerze zainstalowany Visual Studio 2013 z aktualizacją numer 2. Oczywiście aplikacje dla Windows można było tworzyć również w wersji Express tego IDE, ale od kilku tygodni można w pełni legalnie korzystać z pełnowartościowego Visual Studio 2013 Community (promocja dotyczy osób indywidualnych oraz małych zespołów developerów - do 5 osób), który możecie pobrać stąd (w linku znajdziecie również szczegółowe informacje o licencjonowaniu tego produktu).
Oprócz tego musimy mieć zainstalowany system Windows 8.1, a żeby korzystać z emulatora Windows Phone, nasz komputer powinien wspierać również wirtualizację.
Jeśli spełniacie powyższe wymagania, to odsyłam Was teraz do działu download, skąd pobierzecie omawiany w tym wpisie projekt;-)
Projekt
Dziś trochę inaczej niż zwykle, ponieważ będziemy omawiać gotowe aplikacje, a nie budować je od zera.
W solucji znajdziecie trzy projekty:
- ImageGallery.Windows
- ImageGallery.WindowsPhone
- ImageGallery.Shared
Pierwszy projekt zawiera wszystkie rzeczy, które mają pojawić się tylko w aplikacji dla Windows, natomiast w drugim te, które są specyficzne dla Windows Phone. Trzeci to oczywiście część współdzielona i naszym celem jest to, by jak najwięcej kodu trafiło właśnie tutaj. W części współdzielonej może również znaleźć się kod specyficzny dla wybranej platformy, o czym więcej napisze w dalszej części tekstu.
Warto w tym miejscu również wspomnieć, że część Shared nie jest normalnym projektem i nie można do niej dodawać bibliotek, czy referencji. Jeśli chcecie skorzystać w tym miejscu z jakiś rozszerzeń (np. z NuGeta), to tak naprawdę musicie dodać je do projektów Windows oraz Windows Phone. Oba te projekty linkują do części współdzielonej.
Część współdzielona
Większość kodu (zgodnie z zaleceniami Microsoftu) umieściłem w części Shared. Z punktu widzenia naszego projektu, najważniejsze są następujące pliki:
- AppImage.cs
- MainPage.xaml.cs
Zaczniemy od tego pierwszego. Zadaniem klasy AppImage, jest przechowywanie informacji na temat obrazów odnalezionych w systemie. Spójrzmy na kod:
public class AppImage : INotifyPropertyChanged { private StorageFile _storageFile = null; private StorageItemThumbnail _storageFileThumb = null; private AppImageSource _appImageSource; private BitmapImage _photo; public AppImage(StorageFile file, AppImageSource appImageSource) { this._storageFile = file; this._appImageSource = appImageSource; this.Name = file.Name; this.Path = file.Path; } public string Name { get; set; } public string Path { get; set; } public BitmapImage Photo { get { if (_photo == null) { var task = FetchPicture(); } return _photo; } } public async Task FetchPicture() { if (this._storageFile == null) { await LoadFile(); } _storageFileThumb = await _storageFile.GetThumbnailAsync(ThumbnailMode.ListView); if (null != _storageFileThumb) { _photo = new BitmapImage(); _photo.SetSource(_storageFileThumb); NotifyPropertyChanged("Photo"); } } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(string sPropertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(sPropertyName)); } } #endregion private async Task LoadFile() { switch(_appImageSource) { case AppImageSource.CameraRoll: _storageFile = await KnownFolders.CameraRoll.GetFileAsync(Name); break; case AppImageSource.PicturesLibrary: _storageFile = await KnownFolders.PicturesLibrary.GetFileAsync(Name); break; } } }
Jak widać, jest to stosunkowo prosta konstrukcja. Najważniejszym jej elementem jest metoda FetchPicture która generuje miniaturę wskazanego obrazu. Warto w tym miejscu zwrócić uwagę, że plik doczytywany jest dopiero wtedy kiedy rzeczywiście korzystamy z właściwości Photo, więc mamy tutaj taki mały Lazy-loading;-)
W części Shared znajdziecie również plik MainPage.xaml.cs. Z pewnością jest to mała niespodzianka, bo ten plik to code-behind naszych stron. Jak to się stało, że pojawia się on zarówno w poszczególnych projektach jak i części współdzielonej? Cóż kluczem do sukcesu jest w tym przypadku słówko Partial. Dzięki temu możemy umieszczać kod klasy w różnych plikach i jest to jedna z najważniejszych funkcjonalności o których warto pamiętać tworząc aplikacje uniwersalne.
Co zatem umieścimy w tym pliku? Znajdziecie tutaj przede wszystkim kod odpowiedzialny za pobieranie obrazków (zdjęcia z aparatu - Windows Phone, obrazy z biblioteki - Windows), które później zostaną podpięte do kolekcji, z której korzystać będzie kontrolka GridView. Spójrzmy na kod:
public sealed partial class MainPage : Page { private ObservableCollection<AppImage> _appImages = null; public ObservableCollection<AppImage> AppImages { get { return _appImages; } set { _appImages = value; } } protected async override void OnNavigatedTo(NavigationEventArgs e) { _appImages = new ObservableCollection<AppImage>(); this.DataContext = this; await this.LoadImages(); } private async Task LoadImages() { StorageFolder storageFolder = null; AppImageSource appImageSource; #if WINDOWS_PHONE_APP appImageSource = AppImageSource.CameraRoll; storageFolder = Windows.Storage.KnownFolders.CameraRoll; #elif WINDOWS_APP appImageSource = AppImageSource.PicturesLibrary; storageFolder = Windows.Storage.KnownFolders.PicturesLibrary; #endif var imageFiles = await storageFolder.GetFilesAsync(); List<BitmapImage> bitmapImages = new List<BitmapImage>(); foreach (var imageFile in imageFiles) { var image = new AppImage(imageFile, appImageSource); _appImages.Add(image); } } }
W tym przypadku dzieje się kilka ważnych rzeczy. W asynchronicznej metodzie OnNavigatedTo ustawiamy kontekst w taki sposób, by wskazywał on na code-behind, a następnie rozpoczynamy asynchroniczny proces wczytywania obrazków.
W tym właśnie miejscu pojawiają się wspomniane wcześniej dyrektywy preprocesora, które określają jaki fragment kodu ma się wykonać dla Windows, a jaki dla Windows Phone. Wykorzystaliśmy je do wskazania folderu, z którego mają zostać pobrane obrazki. (oczywiście cały pozostały kod zadziała na obu platformach). Po wybraniu katalogu, tworzymy obiekty klasy AppImage i dodajemy je do kolekcji, do której później odniesiemy się w XAMLu.
XAML dla Windows
W projekcie dla Windows stworzymy widok charakterystyczny dla tego systemu:
<Page x:Class="ImageGallery.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:ImageGallery" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="100"/> <RowDefinition Height="40"/> <RowDefinition/> </Grid.RowDefinitions> <TextBlock HorizontalAlignment="Left" Margin="120,0,0,0" TextWrapping="Wrap" Text="galeria" VerticalAlignment="Bottom" Style="{StaticResource HeaderTextBlockStyle}"/> <GridView x:Name="gridView" Grid.Row="2" Margin="120,0,0,0" ItemsSource="{Binding AppImages}"> <GridView.ItemTemplate> <DataTemplate> <Grid HorizontalAlignment="Left" Width="250" Height="250"> <Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}"> <Image Source="{Binding Photo}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Name}"/> </Border> <StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}"> <TextBlock Text="{Binding Name}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="60" Margin="15,0,15,0"/> </StackPanel> </Grid> </DataTemplate> </GridView.ItemTemplate> </GridView> </Grid> </Page>
Nie ma tutaj nic nadzwyczajnego, a najważniejszym elementem całego listingu jest kontrolka GridView, w której umieszczać będziemy miniaturki naszych obrazków. Za pomocą bindowania wyświetlimy ich nazwy oraz same obrazki.
XAML dla Windows Phone
Poniżej kod XAML dla projektu Windows Phone:
<Page x:Class="ImageGallery.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:ImageGallery" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="80"/> <RowDefinition Height="20"/> <RowDefinition/> </Grid.RowDefinitions> <TextBlock HorizontalAlignment="Left" Margin="20,0,0,0" TextWrapping="Wrap" Text="galeria" VerticalAlignment="Bottom" Style="{StaticResource HeaderTextBlockStyle}"/> <GridView x:Name="gridView" Grid.Row="2" Margin="10,0,0,0" ItemsSource="{Binding AppImages}"> <GridView.ItemTemplate> <DataTemplate> <Grid HorizontalAlignment="Left" Width="460" Height="300"> <Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}"> <Image Source="{Binding Photo}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Name}"/> </Border> <StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}"> <TextBlock Text="{Binding Name}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="60" Margin="15,0,15,0"/> </StackPanel> </Grid> </DataTemplate> </GridView.ItemTemplate> </GridView> </Grid> </Page>
Jak widać, jest to bardzo zbliżony widok, lecz w tym przypadku zmieniły się odrobinę rozmiary samych kafelków oraz marginesy innych elementów. Poza tym, w gruncie rzeczy jest to ten sam kod. Można by to również załatwić w inny sposób - np. za pomocą rozbudowanych styli, ale nie o to chodzi w tym przykładzie;-)
Podsumowanie
Jak widać, za pomocą stosunkowo krótkiego kodu można stworzyć prostą aplikację, która zadziała na różnych urządzeniach. Nowe aplikacje uniwersalne dają sporo możliwości i jeśli tylko ta tematyka przypadnie Wam do gustu, to postaram się coś jeszcze w tej materii napisać;-)
Kod projektu znajdziecie w dziale download, o czym wspominałem wcześniej w tekście;-)
Kod źródłowy użyty w przykładzie, zawiera kilka rozwiązań z kursu Building Apps for Windows Phone 8.1 - Channel 9.
Data ostatniej modyfikacji: 30.11.2014, 11:31.
Rekomendowane

Cena: 129,00 zł
Komentarze