Artykuł

ign.com ign.com
lis 30 2014
0

Windows Universal Apps - nasz pierwszy projekt

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.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus