Programowanie aplikacji internetowych 2017/18 Instrukcja laboratoryjna cz.5 Aplikacje na Uniwersalną Platformę Windows (UWP) Prowadzący: Tomasz Goluch Wersja: 1.1
I. Wprowadzenie 1 Cel: Przekazanie podstawowych informacje o zasobach laboratorium. Laboratorium odbywa się na maszynach fizycznych, które posiadają zainstalowany system Windows 10 oraz IDE Visual Studio 2015 + Tools for Universal Windows apps albo na maszynach wirtualnych RAI_64_2016 dostępnych w aplikacji: Oracle VM VirtualBox spełniających powyższe wymagania. Wdrażanie aplikacji UWP wymaga aby w IDE VS zalogować się na swoje konto deweloperskie. Logowanie wymaga uprawnień administratora (login i hasło podane przez prowadzącego) oraz aktywnego konta email w domenie Windows (hotmail.com albo outlook.com). Do wyrejestrowania licencji deweloperskiej służy w PowerShell u polecenie: Unregister-WindowsDeveloperLicense. Proszę uruchomić PowerShell a w trybie administratora. W celu bezproblemowego wdrażania aplikacji UWP w komputerze musi być aktywowany tryb developera. Okno Ustawienia Dla developerów pozwalające na jego aktywację uruchamiane jest podczas pierwszej próby wdrożenia/debugowania. Do zmiany ustawień potrzebne są uprawnienia administratora. II. Tworzenie aplikacji UWP (C# i XAML) Cel: Utworzenie prostej aplikacji UWP. Podczas uruchamiania programu lub próby użycia debuggera może pojawić się monit o odnowienie licencji developerskiej. Zadanie laboratoryjne: Implementacja prostej aplikacji UWP oraz przetestowanie jej w trybie desktop oraz mobile z wykorzystaniem symulatora dostępnego w IDE VS2015. W jej skład powinny wchodzić przynajmniej dwa okna. Powrót do okna otwartego wcześniej powinien być możliwy w trybie desktop przy pomocy przycisku wstecz (obsługa historii przeglądania). Należy wyświetlać rodzinę urządzeń na którym właśnie wykonuje się program oraz aktualny rozmiar ekranu. Aplikacja powinna wyświetlać informację na kafelku o liczbie powiadomień. Powiadomienia powinny być powiązane z jej funkcjonalnością (przykładowo komunikator informuje o liczbie nieprzeczytanych komunikatów). W kolejnym kroku należy wykonać prosty branding aplikacji (logo w kafelkach i tle). W jednym z okien zaimplementuj funkcjonalność pozwalającą na rozpoznawanie pisma odręcznego. Jako element UI pozwalający użytkownikowi na wprowadzenie pisma odręcznego należy wykorzystać kontrolkę InkCanvas oraz kontener InkRecognizerContainer udostępniający metodę RecognizeAsync pozwalającą na rozpoznawanie wprowadzonego tekstu. Poza rozpoznawaniem pisma kontrolka powinna pozwalać na zmianę właściwości pióra (kolor, grubość) oraz możliwość czyszczenia ekranu lub pojedynczych krzywych. 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
Na stronie projektu dostępne są zasoby do brandingu oraz kod obsługi kafelków. 1. Wykrywanie rodziny urządzeń Urządzenia na których może być uruchomiona nasza aplikacja zostały podzielone na rodziny. W celu identyfikacji rodziny z pomocą przychodzi nam klasa AnalyticsInfo. W łatwy sposób możemy zapisać te informacje w zmiennej: public string DeviceFamily = "Device Family " + AnalyticsInfo.VersionInfo.DeviceFamily; i wyświetlić w GUI: <TextBlock Text="x:Bind DeviceFamily" /> Ponadto w celu dostosowania się UI naszej aplikacji do aktualnych rozmiarów okna, możemy na bieżąco śledzić jego wielkość przechowywaną we właściwości: Window.Current.Bounds. Aby na bieżąco wyświetlać zmiany rozmiarów musimy w konstruktorze okna podpiąć się do zdarzenia SizeChanged: this.sizechanged += Page_SizeChanged; oraz wykorzystać interfejs INotifyPropertyChanged: public event PropertyChangedEventHandler PropertyChanged; Kod metody aktualizującej informacje o rozmiarach okna w momencie kiedy ulegają zmianie: private void Page_SizeChanged(object sender, SizeChangedEventArgs e) var currentwidth = Window.Current.Bounds.Width; var currentheight = Window.Current.Bounds.Height; Dimensions = string.format("current Window Size: 0 x 1", (int)currentwidth, (int)currentheight); if (PropertyChanged!= null) PropertyChanged(this, new PropertyChangedEventArgs(nameof(Dimensions))); Wyświetlanie rozmiarów w GUI: <TextBlock Text="x:Bind Dimensions, Mode=OneWay" /> Jeśli przeszkadza nam okno licznika wyświetlanych ramek w górnym prawym rogu możemy łatwo je wyłączyć ustawiając w metodzie OnLaunched pliku App.xaml.cs wartość właściwości EnableFrameRateCounter na false: this.debugsettings.enableframeratecounter = false; Proszę uruchomić aplikację na symulatorze w celu odnotowania zmiany rodziny urządzeń. 2
2. Nawigacja pomiędzy stronami wraz z przekazywaniem parametru Po dodaniu nowej strony Blank Page do projektu, przejście do niej ze strony głównej projektu można wykonać wywołując metodę: Frame.Navigate(typeof(<nazwa_klasy_strony>), <przesylany_parametr>); Pierwszy parametr to typ klasy naszej strony. Przy pomocy drugiego możemy przesłać dodatkowe informacje, może to być dowolny obiekt pod warunkiem, że obsługuje serializację. W celu odebrania tego parametru należy przeciążyć asynchroniczną funkcję OnNavigatedTo: protected async override void OnNavigatedTo(NavigationEventArgs e) await new MessageDialog(e.Parameter).ShowAsync(); base.onnavigatedto(e); Proszę uruchomić aplikację w symulatorze i sprawdzić działanie hardware owego przycisku wstecz. Po naciśnięciu przycisku wstecz wyjdziemy z aplikacji zamiast cofnąć się w historii wyświetlanych okien. Aby przycisk nawigował po historii naszej aplikacji a nie np. po stosie uruchomionych aplikacji należy oprogramować jego zachowanie. Ponadto w trybie desktop klawisz powrotu jest niedostępny. Można go aktywować podpinając się do zdarzenia: rootframe.navigated w pliku App.xaml.cs: rootframe.navigated += RootFrame_Navigated; Kod obsługujący zdarzenie uwidocznienia przycisku powrotu: private void RootFrame_Navigated(object sender, NavigationEventArgs e) Frame rootframe = Window.Current.Content as Frame; SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = rootframe.cangoback? AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed; Powyższy kod powoduje jedynie wyświetlanie przycisku w urządzeniach nie wspierających hardware owego przycisku back. Musimy dodać jeszcze jego zachowanie podpinając się do zdarzenia BackRequested: SystemNavigationManager.GetForCurrentView().BackRequested += App_BackRequested; Kod obsługi zdarzenia: private void App_BackRequested(object sender, BackRequestedEventArgs e) // Check that no one has already handled this if (!e.handled) // Default is to navigate back within the Frame Frame frame = Window.Current.Content as Frame; if (frame.cangoback) 3
frame.goback(); // Signal handled so the system doesn't navigate back // through the app stack e.handled = true; 3. Branding aplikacji W pliku Package.appxmanifest w zakładce Visual Assets można zmienić kolor tła aplikacji oraz splash screen u podając nazwę np.: deepskyblue. Zmiana logo oraz wyświetlanych obrazków wspomaga branding naszej aplikacji. W tym celu można podmienić pliki graficzne w folderze Assets (dostępne ze strony przedmiotu). Po ponownym wdrożeniu aplikacji (wystarczy uruchomienie debbugera) należy odszukać ją w menu start i przypiąć (klikając PPM) do ekranu startowego. Uwaga implementacja klas: TileService i PrimaryTile dostępna jest ze strony przedmiotu. 4. Aktualizacja liczby powiadomień na kafelku oraz tworzenie szablonów adaptacyjnych dla kafelków rozmiarów Wide i Large Archiwum z poprzedniego punktu zawiera implementacje klas: TileService i PrimaryTile proszę je dodać do projektu. W celu przekazania szybkiej informacji, że nasza aplikacja posiada ważne informacje dla użytkownika można wyświetlić na kafelku skrótową informację w postaci liczby. Możemy w tym celu wywalać metodę SetBadgeCountOnTile dołączonej klasy TileService wewnątrz obsługi zdarzenia: static public void SetBadgeCountOnTile(int count) XmlDocument badgexml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber); XmlElement badgeelement = (XmlElement)badgeXml.SelectSingleNode("/badge"); badgeelement.setattribute("value", count.tostring()); BadgeNotification badge = new BadgeNotification(badgeXml); BadgeUpdateManager.CreateBadgeUpdaterForApplication().Update(badge); Ponadto możemy wykorzystywać szablony adaptacyjne pozwalające na wyświetlenie wiadomości na żywym kafelku. Dają one większą swobodę w wyborze sposobu w jaki zawartość zostanie wyświetlona na różnych urządzeniach: private void UpdatePrimaryTile(object sender, Windows.UI.Xaml.RoutedEventArgs e) var xmldoc = TileService.CreateTiles(new PrimaryTile()); var updater = TileUpdateManager.CreateTileUpdaterForApplication(); TileNotification notification = new TileNotification(xmlDoc); updater.update(notification); 4
5. InkCanvas Płótno InkCanvas pozwala na bezpośrednie pisanie przez użytkownika po ekranie. Domyślnie obsługiwany jest jedynie rysik (stylus), jeśli nie dysponujemy ekranem dotykowym można dodać obsługę dodatkowych urządzeń ustawiając właściwość InkCanvas.InkPresenter.InputDeviceTypes. Obsługiwane urządzenia przedstawia typ wyliczeniowy CoreInputDeviceTypes. Obsługę myszy i touchpada uzyskujemy poleceniem: InkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse Windows.UI.Core.CoreInputDeviceTypes.Pen Windows.UI.Core.CoreInputDeviceTypes.Touch; Do zmiany zachowania pióra służy metoda InkCanvas.InkPresenter.UpdateDefaultDrawingAttributes() należy do niej przekazać obiekt klasy InkDrawingAttributes zawierający właściwości odpowiedzialne za kolor, wielkość i inne parametry rysowanego kształtu 2. Listę zdarzeń obiektu InkCanvas możemy uzyskać tutaj: https://msdn.microsoft.com/enus/library/windows/apps/windows.ui.xaml.controls.inkcanvas.aspx W celu rozpoznania tekstu (zbiór elementów typu InkStrokes) IReadOnlyList<InkStroke> currentstrokes = Windows InkCanvas.InkPresenter.StrokeContainer.GetStrokes(); należy go przesłać do metody RecognizeAsync obiektu klasy InkRecognizerContainer: var recognitionresults = await irc.recognizeasync( InkCanvas.InkPresenter.StrokeContainer, InkRecognitionTarget.All); W kolekcji kandydatów rozpoznanego tekstu możemy wyświetlić najbardziej obiecującego:\ string str = "Recognition result:"; foreach (var r in recognitionresults) str += " " + r.gettextcandidates()[0]; Ciało funkcji zapisującej do pliku: var savepicker = new Windows.Storage.Pickers.FileSavePicker(); savepicker.suggestedstartlocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary; savepicker.filetypechoices.add( "Gif with embedded ISF", new System.Collections.Generic.List<string> ".gif" ); Windows.Storage.StorageFile file = await savepicker.picksavefileasync(); if (null!= file) 2 https://msdn.microsoft.com/en-us/library/windows.ui.input.inking.inkdrawingattributes.aspx 5
using (IRandomAccessStream stream = await file.openasync(fileaccessmode.readwrite)) await InkCanvas.InkPresenter.StrokeContainer.SaveAsync( stream); Ciało funkcji wczytującej z pliku: var openpicker = new Windows.Storage.Pickers.FileOpenPicker(); openpicker.suggestedstartlocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary; openpicker.filetypefilter.add(".gif"); openpicker.filetypefilter.add(".isf"); Windows.Storage.StorageFile file = await openpicker.picksinglefileasync(); if (null!= file) using (var stream = await file.opensequentialreadasync()) await InkCanvas.InkPresenter.StrokeContainer.LoadAsync(stream); Poniżej dwie strony przykładowego programu: Rysunek 1- główna strona przykładowego programu 6
Rysunek 2- druga strona przykładowego programu 7