Programowanie aplikacji internetowych 2015/2016

Instrukcja laboratoryjna cz.4 Aplikacje na Windows 8.x Store (C# i XAML)

Prowadzący: Tomasz Goluch Wersja: 1.1

I.

Wprowadzenie1 Cel: Przekazanie podstawowych informacje o laboratorium.

Laboratorium odbywa się na maszynach fizycznych które posiadają zainstalowany system Windows 8.x i IDE Visual Studio 2012 (tylko aplikacje Windows 8.0) lub wyższe (również aplikacje Windows 8.1).

II.

Tworzenie aplikacji Windows Store (C# i XAML) Cel: Utworzenie aplikacji Windows Store, zamiana wyświetlania domyślnych przykładowych z szablonu rzeczywistymi oraz dostosowanie wyglądu interfejsu użytkownika.

Podczas uruchamiania programu lub próby użycia debuggera może pojawić się monit o odnowienie licencji developera systemu Windows 8.1.

Zgoda wymaga uprawnień administratora (login i hasło podane przez prowadzącego) oraz aktywnego konta email w domenie Windows (hotmail.com albo outlook.com). Nazwa i hasło do konta również zostaną podane przez prowadzącego. Do wyrejestrowania licencji deweloperskiej służy w PowerShell’u polecenie: UnregisterWindowsDeveloperLicense. Proszę uruchomić PowerShell’a w trybie administratora.

1

Instrukcja przygotowana na podstawie laboratorium Hands-On lab: Building your first App for Windows 8.1 and publishing it to the Windows Store.

1

1. Utwórz nowy: Store Apps → Windows Apps → BlankApp(Visual c#) projekt.

2. Do folderu Assets dodaj pliki zasobów. Należy je ściągnąć ze strony przedmiotu (pliki: Logo.scale-100.png, SmallLogo.scale-100.png, SplashScreen.scale100.png, StoreLogo.scale-100.png i YouTube_Channel_Logo.png). 3. W pliku MainPage.xaml wewnątrz elementu Grid umieść kod reprezentujący główne okno naszej aplikacji składające się kontrolki Hub zawierającej nagłówek i trzy sekcje. Pierwsza sekcja zawiera grafikę a kolejne kontrolki ProgressRing i ListView, które będą reprezentować kolejno: posortowane alfabetycznie i najwcześniej opublikowane filmy na naszym kanale. Wklej poniższy kod zaznaczony na szaro.

2



4. Dodaj do projektu folder Classes i dodaj do niego plik BindableBase.cs (do ściągnięcia ze strony przedmiotu). 5. Dodaj do projektu folder DataModel i dodaj do niego nowy plik klasy YouTubeVideo.cs. 6. Dodaj poniższy kod klasy do nowego pliku. Pamiętaj o dodaniu dostępu do wymaganej przestrzeni nazw System.Xml.Linq i folderu Classes. public class YouTubeVideo : BindableBase { private const string YoutubeWatchBaseUrl = "http://www.youtube.com/watch?v="; private string _title; /// /// Gets/Sets the title of the YouTube video. /// public string Title { get { return _title; } set { SetProperty(ref _title, value); } } private string _videoUrl; /// /// Gets/Sets the video URL of the YouTube video. /// public string VideoUrl { get { return _videoUrl; } set { SetProperty(ref _videoUrl, value); } } private string _videoImageUrl; /// /// Gets/Sets the video image URL of the YouTube video. /// public string VideoImageUrl { get { return _videoImageUrl; } set { SetProperty(ref _videoImageUrl, value); } } private string _videoId; /// /// Gets/Sets the identifier of the YouTube video. /// public string VideoId

3

{ get { if (!string.IsNullOrEmpty(VideoUrl)) { var parsed = VideoUrl.Split('/'); _videoId = parsed[parsed.Length - 1]; } return _videoId; } set { SetProperty(ref _videoId, value); } } public string ExternalUrl { get { return YoutubeWatchBaseUrl + VideoId; } } private string _summary; /// /// Gets/Sets the summary of the YouTube video. /// public string Summary { get { return _summary; } set { SetProperty(ref _summary, value); } } public YouTubeVideo() { } public YouTubeVideo(Google.Apis.YouTube.v3.Data.SearchResult ytvideo) { Title = ytvideo.Snippet.Title; VideoId = ytvideo.Id.VideoId; VideoImageUrl = ytvideo.Snippet.Thumbnails.Default__.Url; Summary = ytvideo.Snippet.Description; } }

7. Dodaj do folderu DataModel nowy plik klasy YouTubeChannel.cs. 8. Dodaj poniższy kod klasy do nowego pliku. Pamiętaj o dodaniu dostępu do wymaganej przestrzeni nazw System.Collections.ObjectModel. public class YouTubeChannel { private string _q; private string _orderBy; private const int maxResults = 50; public YouTubeChannel(string query, string orderBy) { _q = query; _orderBy = orderBy; }

public async Task LoadData(int page = 0)

4

{ var youtubeService = new YouTubeService(new BaseClientService.Initializer() { ApiKey = "AIzaSyCZCr7d3QzRnHzzcWf5dAQ6GR7h1bH5zNg", ApplicationName = this.GetType().ToString() }); var searchListRequest = youtubeService.Search.List("snippet"); searchListRequest.Q = _q; // Replace with your search term. searchListRequest.MaxResults = 50; // Call the search.list method to retrieve results matching the specified query term. var searchListResponse = await searchListRequest.ExecuteAsync(); var items = new List(); foreach (var searchResult in searchListResponse.Items) { if (searchResult.Id.Kind == "youtube#video") { items.Add(new YouTubeVideo(searchResult)); } } switch (_orderBy) { case "title": { items.Sort(delegate(YouTubeVideo x, YouTubeVideo y) { if (x.Title == null && y.Title == null) return 0; else if (x.Title == null) return -1; else if (y.Title == null) return 1; else return x.Title.CompareTo(y.Title); }); break; } case "published": { throw new NotImplementedException("zaimplementuj sortowanie malejąco po dacie opublikowania"); break; } default: { } break; }

return items != null ? new ObservableCollection(items) : new ObservableCollection(); } }

9. Aby skorzystać z obiektu reprezentującego serwis YouTube należy dodać referencje do następujących bibliotek: Google.Apis, Google.Apis.Auth, Google.Apis.Auth.PlatformServices, Google.Apis.Core, Google.Apis.PlatformServices i Google.Apis.YouTube.v3. Najlepiej w tym celu wykorzystać managera pakietów: NuGet.

5

10. Następnie należy dodać wymagane przestrzenie nazw: Google.Apis.Auth.OAuth2, Google.Apis.Services, Google.Apis.Upload, Google.Apis.Util.Store, Google.Apis.YouTube.v3, Google.Apis.YouTube.v3.Data 11. Dodaj do folderu DataModel nowy plik klasy Settings.cs. 12. Dodaj poniższy kod klasy do nowego pliku i uzupełnij: słowo kluczowe np.: „board game”, nazwę kanału (ChannelName) oraz jego opis (Description). static class Settings { public const string Query = "..."; public const string ChannelName = "... Channel"; public const string Description = "... Channel on YouTube."; }

13. Dodaj do folderu DataModel nowy plik klasy YouTubeDataSource.cs. 14. Dodaj poniższy kod klasy do nowego pliku. Pamiętaj o dodaniu dostępu do wymaganej przestrzeni nazw System.Collections.ObjectModel i folderu Classes. public class YouTubeDataSource : BindableBase { public ObservableCollection RecentItems { get; set; } public ObservableCollection SortedByTitleItems { get; set; } private string _channelName; public string ChannelName { get { return _channelName; } set { SetProperty(ref _channelName, value); } } private bool _isLoadingData; public bool IsLoadingData { get { return _isLoadingData; } set { SetProperty(ref _isLoadingData, value); } } private bool _isLoadingError; public bool IsLoadingError { get { return _isLoadingError; } set { SetProperty(ref _isLoadingError, value); } } private YouTubeChannel _popularChannel;

6

private YouTubeChannel _recentChannel; public YouTubeDataSource() { IsLoadingError = false; RecentItems = new ObservableCollection(); SortedByTitleItems = new ObservableCollection(); _popularChannel = new YouTubeChannel(Settings.Query, "title"); _recentChannel = new YouTubeChannel(Settings.Query, "published"); } public async Task LoadData() { ChannelName = Settings.ChannelName; IsLoadingData = true; Task taskRecent = LoadRecent(); Task taskPopular = LoadPopular(); await taskRecent; await taskPopular; IsLoadingData = false; } private async Task LoadRecent() { for (int page = 0; page < 10; page++) { var loadedRecentItems = await _recentChannel.LoadData(page); foreach (var item in loadedRecentItems) { RecentItems.Add(item); } } } private async Task LoadPopular() { for (int page = 0; page < 10; page++) { var loadedSortedByTitleItems = await _popularChannel.LoadData(page); foreach (var item in loadedSortedByTitleItems) { SortedByTitleItems.Add(item); } } } }

15. Dodaj do pliku MainPage.xaml.cs poniższy kod. Pamiętaj o dodaniu dostępu do wymaganej przestrzeni nazw Windows.UI.ApplicationSettings i folderu DataModel. public sealed partial class MainPage : Page { private static YouTubeDataSource _dataSource; public MainPage()

7

{ this.InitializeComponent(); if (_dataSource == null) _dataSource = new YouTubeDataSource(); DataContext = _dataSource; } protected override async void OnNavigatedTo(NavigationEventArgs e) { Application.Current.Resuming += Current_Resuming; await LoadData(); } private async void Current_Resuming(object sender, object e) { await LoadData(); } private bool _alreadyLoading = false; private async Task LoadData() { if (_alreadyLoading) return; _alreadyLoading = true; if (_dataSource.SortedByTitleItems.Count == 0 || _dataSource.RecentItems.Count == 0) { try { await _dataSource.LoadData(); } catch { _dataSource.IsLoadingData = false; _dataSource.IsLoadingError = true; _dataSource.SortedByTitleItems.Clear(); _dataSource.RecentItems.Clear(); } } _alreadyLoading = false; } }

16. W pliku MainPage.xaml dodaj wiązanie (binding) atrybutu Text z właściwością (property) ChannelName zadeklarowaną w klasie YouTubeDataSource. Text="{Binding ChannelName}"

17. Podobnie ustaw wiązania atrybutu IsActive kontrolek ProgressRing z właściwością IsLoadingData również zadeklarowaną w klasie YouTubeDataSource. IsActive="{Binding IsLoadingData}

8

18. Do kontrolek ListView dodaj atrybuty ItemsSource i powiąż je z właściwościami RecentItems i SortedByTitleItems również zadeklarowanymi w klasie YouTubeDataSource. ItemsSource="{Binding RecentItems}"

19. Po kompilacji powinniśmy zobaczyć działające kontrolki ProgressRing (podczas ładowania danych) oraz wypełnione kontrolki ListViews, niestety będą one wyświetlały jedynie domyślną reprezentację w postaci stringu.

20. Dodaj sekcję Page.Resources w pliku MainPage.xaml.

9



Dodaj w kontrolkach ListView poniższy atrybut. ItemTemplate="{StaticResource VideoItemTemplate}"

III.

Informacja o braku połączenia z Internetem. Cel: Zapoznanie z konwerterem wartości dla XAML.

1. W przypadku problemu z połączeniem internetowym należy poinformować o tym użytkownika. W tym celu dodaj na końcu pliku MainPage.xaml, za kontrolką Hub poniższy kod.

2. Ponieważ właściwość IsLoadingError przyjmuje wartości ze zbioru {true, false} należy je odpowiednio przekonwertować na napisy zrozumiałe dla atrybutu Visibility czyli Visible albo Collapsed. W tym celu dodaj do folderu Classes nowy plik klasy VisibilityConverter i dodaj do niego poniższy kod. Pamiętaj o dodaniu dostępu do wymaganej przestrzeni nazw Windows.UI.Xaml.Data. public sealed class VisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { if ((bool)value) return "Visible"; else return "Collapsed";

10

} public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } }

3. Dodaj konwerter Converter_Visibility do pliku MainPage.xaml. Pamiętaj o dodaniu przestrzeni nazw: xmlns:local="using:".

4. Rozłącz połączenie sieciowe i sprawdź czy aplikacja odpowiednio zareagowała.

IV.

Dodawanie przycisku odświeżającego zawartości list. Cel: Zapoznanie z paskiem poleceń oraz dodawaniem do niego przycisku wraz z jego obsługą.

1. Aby umożliwić użytkownikowi ręczne odświeżanie wyświetlanych list należy dodać odpowiedni przycisk. W tym celu wystarczy do pliku MainPage.xaml dodać poniższy kod.

11



2. W celu utworzenia pustej funkcji obsługi zdarzenia „Tapped” kliknij na przypisanej jej nazwie AppBarButtonRefresh_Tapped i naciśnij przycisk F12. 3. Dodaj do nowo powstałej funkcji obsługi zdarzenia następujący kod. _dataSource.IsLoadingData = true; _dataSource.IsLoadingError = false; _dataSource.SortedByTitleItems.Clear(); _dataSource.RecentItems.Clear(); LoadData();

4. Funkcja LoadData() jest funkcją nieblokującą należy zatem zaczekać na jej wykonanie wykorzystując słowo kluczowe await. Jego użycie wymaga poinformowania kompilatora że nowo powstała funkcja obsługi zdarzenia „Tapped” będzie zawierać asynchroniczne elementy kodu. Robimy to oznaczając ją słowem kluczowym async. 5. Przycisk odświeżania będzie widoczny na pasku poleceń po kliknięciu gdziekolwiek na ekranie prawym przyciskiem myszy.

V.

Dodanie strony odtwarzacza. Cel: Zapoznanie z procesem dodawania nowej strony do aplikacji nowej strony XAML oraz obsługą odtwarzacza Windows Player.

1. Do kontrolek ListView dodaj atrybuty ItemClick i przy pomocy przycisku F12 utwórz uchwyty do obsługi klinięcia. 12

ItemClick="ListView_ItemClick"

2. Dodaj do nowej metody następujący kod. var _video = (YouTubeVideo)e.ClickedItem; this.Frame.Navigate(typeof(PlaybackPage), _video);

3. Dodaj do projektu nowy element Blank Page o nazwie PlaybackPage.

4. Dodaj layout do pliku XAML nowo dodanej strony.

13



5. Aby umożliwić powrót ze strony odtwarzacza na stronę główną dodaj implementację atrybutu Click="ButtonBack_Click" w elemancie buttonBack. this.Frame.GoBack();

6. Pobierz i zainstaluj odtwarzacz Microsoft Player Framework (wymagane prawa administratora). 7. Dodaj referencję do Microsoft Player Framework SDK.

8. W pliku XAML odtwarzacza umieść, wewnątrz kontrolki Grid kontrolkę odtwarzacza MadiaPlayer.

9. Jego definicja znajduje się w przestrzeni nazw Microsoft.PlayerFramework. xmlns:mpf="using:Microsoft.PlayerFramework"

10. Dodaj do pliku zawierającego kod C# strony odtwarzacza prywatną zmienną typu YouTubeVideo przechowywującą film przekazany z głównej strony aplikacji. Dodaj także przeciążoną metodę OnNavigatedTo ładującą wspomniany film, aktualizującą nazwę strony oraz aktywującą na czas ładowania filmu kontrolkę ProgressRing. private static YouTubeVideo _video; protected override async void OnNavigatedTo(NavigationEventArgs e) { if (e.Parameter == null) _video = new YouTubeVideo(); else _video = ((YouTubeVideo)e.Parameter);

14

textTitle.Text = _video.Title; progressRingLoading.IsActive = true; try { YouTubeUri url = await YouTube.GetVideoUriAsync(_video.VideoId, YouTubeQuality.Quality1080P); mediaPlayer.Source = url.Uri; } catch (Exception) { gridProblem.Visibility = Visibility.Visible; mediaPlayer.Visibility = Visibility.Collapsed; } progressRingLoading.IsActive = false; }

11. W celu obsługi nowo dodanych klas YouTube… wymagane jest dodanie projektu MyToolkit.Extended, najlepiej przy pomocy menedżera pakietów NuGet. Pamiętaj o dodaniu dostępu do wymaganej przestrzeni nazw MyToolkit.Multimedia i folderu DataModel. 12. Dodaj implementację uchwytu MediaPlayer_DoubleTapped z pliku XAML odtwarzacza pozwalającą na przełączanie pomiędzy pełnym ekranem a oknem. mediaPlayer.IsFullScreen = !mediaPlayer.IsFullScreen;

13. Sama zmiana wartości IsFullScreen niestety nie wystarczy, musimy jaszcze zdefiniować i podpiąć odpowiednią metodę do zdarzenia IsFullScreenChanged obiektu mediaPlayer. public PlaybackPage() { this.InitializeComponent(); mediaPlayer.IsFullScreenChanged += mediaPlayer_IsFullScreenChanged; } private void mediaPlayer_IsFullScreenChanged(object sender, RoutedPropertyChangedEventArgs e) { if (e.NewValue) { Grid.SetRow(mediaPlayer, 0); Grid.SetRowSpan(mediaPlayer, 2); gridTop.Visibility = Visibility.Collapsed; } else { Grid.SetRow(mediaPlayer, 1); Grid.SetRowSpan(mediaPlayer, 1); gridTop.Visibility = Visibility.Visible; } }

VI.

Wyświetlanie polityki prywatności. 15

Cel: Zapoznanie ze sposobem automatycznego generowania strony dotyczącej polityki prywatności.

1. Stronę wyświetlającą politykę prywatności można wygenerować automatycznie korzystając z witryny: http://w8privacy.azurewebsites.net/. 2. Podaj Nazwę Wyświetlaną Wydawcy (znajdziesz ją w ustawieniach twojego konta developerskiego), adres email oraz nazwę właśnie tworzonej aplikacji. 3. Skopiuj wygenerowany link z pola adresu przeglądarki

4. Dodaj do projektu nowy element „Settings Flyout”. 5. Dodaj do nowego pliku XAML poniższy kod. This app uses your internet connection only to download and update content. This application does not:

16

- Collect and use any personal information. - Store any personal data. - Share any personal data to third parties.

6. W miejscu „Your Privacy Policy URL here.” Podaj skopiowany wcześniej link URL. Należy zastąpić wszystkie znaki „&” na „&” ponieważ są niezgodne ze specyfikacją XML a w szczególności z XAML. W oknie designera powinna pojawić na się podobna strona.

7. W pliku MainPage.cs należy dodać poniższy kod do konstruktora, uczyni to naszą stronę dostępną. SettingsPane.GetForCurrentView().CommandsRequested += MainPage_CommandsRequested;

8. Oczywiście musimy jeszcze zaimplementować odpowiednią metodę. 17

SettingsCommand privacyCommand = new SettingsCommand("privacy", "Privacy Policy", (handler) => { PrivacyFlyout privacyFlyout = new PrivacyFlyout(); privacyFlyout.Show(); }); args.Request.ApplicationCommands.Add(privacyCommand);

9. Oraz przeciążyć metodę OnNavigatedFrom wyrejestrowującą nasz uchwyt. protected override void OnNavigatedFrom(NavigationEventArgs e) { base.OnNavigatedFrom(e); SettingsPane.GetForCurrentView().CommandsRequested -= MainPage_CommandsRequested; }

10. Strona w akcji:

VII.

Finalizacja aplikacji. Cel: Zapoznanie z ostatnimi czynnościami wymaganymi przed opublikowaniem aplikacji.

1. Należy jeszcze zmienić jeszcze kilka ustawień w pliku manifestu. W sekcji „Application” dodaj wyświetlaną nazwę oraz opis aplikacji.

18

2. W zakładce „Visual Assets” wypełnij pola podobnie jak na poniższym rysunku. Ponownie użyj wyświetlanej nazwy aplikacji.

3. Na koniec w zakładce „Packaging” sprawdź wyświetlaną nazwę oraz wyświetlaną nazwę wydawcy.

VIII.

Utworzenie pliku aplikacji gotowego do umieszczenia w sklepie. Cel: Zapoznanie z kolejnymi krokami wymaganymi do utworzenia i wstępnej walidacji pliku aplikacji.

1. Aby utworzyć plik aplikacji należy zapisać zmiany w projekcie i przebudować projekt (Rebuild). 19

2. Wybrać „PROJECT” → „Store” → „Create App Packages…” → „Yes” → „Next” → (należy się zalogować do konta developerskiego w celu automatycznego podpisania aplikacji naszym certyfikatem developera). Następnie w poniższym oknie należy wybrać opcję „Next”.

3. W kolejnym oknie zostawiamy domyślne wartości i klikamy „Create”

20

4. Teraz możemy dokonać wstępnej walidacji (wymagane uprawnienia administratora).

5. Zostawiamy zaznaczone wszystkie dostępne testy i klikamy „Dalej”. 21

6. Czekamy z nadzieją, że wszystkie testy zostaną zaliczone, i zobaczymy poniższe okno.

IX.

Publikowanie aplikacji w Windows Store. Cel: Zapoznanie z czynnościami wymaganymi do opublikowania aplikacji w Windows Store.

1. Zaloguj się do własnego pulpitu developera. Wybierz aplikację do publikacji. W sekcji „Submissions” wybierz „Pricing and availability”. 2. Ustal „Base price” na „Free” i zatwierdź przyciskiem „Save”. Domyślnie aplikacja będzie dostępna na 242 rynkach, należy uważać jeśli zawiera ona kanał powiązany tematycznie z hazardem, alkoholem, nieodpowiednim humorem lub treściami dla dorosłych może nie przejść certyfikacji na pewnych rynkach zachodnich. 3. Następnie w „App properties” wybierz kategorię do której aplikacja będzie się zaliczać oraz wymagania wiekowe. Dla „normalnych” aplikacji 12+ jest optymalnym wyborem. 22

4. W sekcji „Packages” należy wybrać opcję „browse to files” i dodać pliki utworzonej aplikacji. W tym przypadku pliki z rozszerzeniem *.appxupload z folderu AppPackages. 5. W menu „Descriptions” musimy dodać przynajmniej opis i po jednym zrzucie ekranu działającej aplikacji dla wersji desktopowej i mobilnej. Dla wersji mobilnej należy skorzystać z symulatora. 6. Sekcja „Notes for certification” jest opcjonalna. Jeśli chcemy – możemy poinformować testerów o szczegółach naszej aplikacji. W naszym przypadku nie jest to wymagane. 7. Po wykonaniu wymaganych 4 kroków nasza aplikacja jest gotowa do wysłania do sklepu. Proszę potwierdzić klikając „Submit to the store”. 8. Na koniec przypominam o wyrejestrowaniu konta developerskiego z komputera laboratoryjnego. Informacja jak to zrobić podana została na początku 2 rozdziału – Tworzenie aplikacji Windows Store (C# i XAML).

23