niedziela, 6 stycznia 2013

70-511 Rozdział 12 - publikacja aplikacji

Po stworzeniu aplikacji, możemy ją udostępnić użytkownikowi końcowemu. Można to zrobić na kilka sposobów. Tutaj zostaną przedstawione dwa sposoby: ClickOne oraz klasyczny instalator.

Tworzenie instalatora
Aby dodać standardowy instalator dla naszej aplikacji, dodajemy do naszej solucji projekt typu Setup (niestety w VS 2012 nie ma już dostępnego tego szablonu):


Każdy instalator dysponuje sześcioma edytorami:
  • File System Editor - konfiguracja plików i folderów na maszynie, której będzie instalowane oprogramowanie 
  • Registry Editor - konfiguracja rejestru
  • File Types Editor - tworzenie powiązań pomiędzy plikami i typami
  • User Interface Editor - interfejs podczas instalacji aplikacji
  • Custom Actions Editor - akcje wykonywane podczas instalacji
  • Launch Conditions Editor - pozwala ustawić warunki, konieczne do spełnienia aby uruchomić instalator 
Widok edytora plików:





Ciekawszą, a zarazem bardziej zaawansowaną czynnością jest tworzenie akcji, które są wykonywane podczas instalacji programu. Aby je stworzyć i zaprogramować, należy najpierw dodać klasę Installer do projektu programu. Podkreślić należy tutaj projektu programu nie do projektu instalatora jak by się mogło wydawać.
Klasa Installer udostępnia metody takie jak:
  • Install
  • Rollback
  • Uninstall
  • Commit
Każdą z nich możemy nadpisać i odpowiednio oprogramować. Ważne jest przy tym aby obsłużyć błędy, które mogą wyniknąć z źle oprogramowanej akcji. W takim przypadku należy rzucić wyjątek typu InstallException co spowoduje cofnięcie instalacji i usunięcie dotychczasowych zmian. Jeżeli dopuścimy do rzucenia innego wyjątku, rezultat takie działania nie jest deterministyczny.


ClickOnce
Innym sposobem udostępniania aplikacji jest tzw. ClickOnce. Technologia ta umożliwia łatwe tworzenie aplikacji, które będą często uaktualniane. Instalacja takich aplikacji zabiera także minimalną ilość kroków, którą muszą wykonać instalujący użytkownicy.
Aby otworzyć okno publikacji w tym trybie wystarczy wejść we właściwości projektu i przejść na zakładkę Publish:


Paczka może zostać udostępniona jako plik na lokalnym komputerze, umieszczona na serwerze, jako udział sieciowy.
Aplikacja może zostać udostępniona tylko w trybie online co zapewnia iż zawsze zostanie uruchomiona jej najnowsza wersja. Aplikacja jest uruchamiana na maszynie klienta, jednak zawsze jest wtedy pobierana z serwera.
Ciekawą opcją jest możliwość skonfigurowania opcji aktualizacji aplikacji


Możemy wybrać, czy chcemy aby aplikacja przed uruchomieniem sprawdziła czy nie ma dostępnej nowej wersji. Jeżeli tak zostanie ona pobrana i aplikacja uruchomi się już w nowszej wersji. W oknie tym można także wskazać gdzie ma być szukana najnowsza wersja oprogramowania.

ClickOnce można także umożliwić z poziomu kodu, np. dając w aplikacji przycisk "Sprawdź aktualizację...". Konfigurację przeprowadza się za pomocą klasy ApplicationDeployment. Klasa ta zawiera metodę CheckForUpdate która sprawdza czy jest dostępna nowa wersja do pobrania. Jeżeli jest możemy następnie wywołać metodę Update, która dokona aktualizacji. Aktualizacja widoczna będzie dopiero po restarcie aplikacji.

sobota, 5 stycznia 2013

70-511 Rozdział 11 - Intellitrace i PresentationTraceSources

Intellitrace zostało przedstawione w Visual Studio 2010 Ultimate. Pozwala nagrywać zdarzenia wywoływane w aplikacji i zapisywać je do pliku, z którego następnie mogą być odczytane.
W swojej podstawowej konfiguracji Intellitrace nie ma wielkiego wpływy na szybkość wykonywania aplikacji, jednak nie monitoruje ona wszystkich wywołań metod i zdarzeń. Jeżeli chcemy umożliwić nagrywanie wszystkich zdarzeń i wywołań metod możemy to zrobić w ustawieniach, zaznaczając opcję Intellitrace Events And Call Information


W ustawieniach zaawansowanych możemy zmienić ścieżkę zapisu logów jak i ich maksymalny rozmiar:


Wybór elementów, które mają być nagrywane, dokonujemy na zakładce IntelliTrace Events:


Podgląd zdarzeń nagranych przez IntelliTrace jest możliwe po zatrzymaniu aplikacji w trybie debugowania, lub też wczytania zapisanego plików logów.




PresentationTraceSources
PresentationTraceSources to statyczna klasa umożliwiająca tworzenie logów podczas działania aplikacji. Konfiguracja odbywa się na poziomie pliku App.config lub też w kodzie.
Działanie logowania opiera się na predefiniowanych źródłach, z których możemy logować informacje. Lista dostępnych źródeł:
  • AnimationSource - System.Windows.Media.Animation
  • DataBindingSource - System.Windows.Data
  • DependencyPropertySource - System.Windows.DependencyProperty
  • DocumentsSource - System.Windows.Documents
  • FreezableSource - System.Windows.Freezable
  • HwndHostSource - System.Windows.Interop
  • MarkupSource - System.Windows.Markup
  • NameScopeSource - System.Windows.NameScope
  • ResourceDictionarySource - System.Windows.ResourceDictionary
  • RoutedEventSource - System.Windows.RoutedEvent
  • ShellSource - System.Windows.Shell
Domyślnie PresentationTraceSources jest wyłączone, aktywujemy je poprzez wywołanie metody Refresh().

Konfiguracja w pliku App.config:
1. Do aplikacji dodajemy plik App.config.
2. W pliku, dodajemy gałąź <System.Diagnostics>
3. W dodanej gałęzi definiujemy źródła - Sources:
<source name="przestrzeń_nazw" switchName="nazwa_przełącznika" />
np:
<source name="System.Windows.Media.Animation" switchName="animateSwitch" />
4. Dodajemy przełącznik:
<switches>
     <add name="nazwa_zdefiniowanego_przełącznika" value="poziom_logowania" />
</switches>
np:
<switches>
<add name="animateSwitch" value="All" />
</switches>

Poziomy które możemy użyć:
  • Off - brak logowania
  • Warning - tylko ostrzeżenia
  • Activity - informacje o aktywności
  • Verbose - szczegółowe logowanie
  • All - logowanie wszystkiego
5. Ostatnim krokiem jest dodanie informacji gdzie mają być zapisywane logowane informacje. Możliwe wartości to:
  • konsola - System.Diagnostics.ConsoleTraceListener
  • plik tekstowy - System.Diagnostics.TextWriterTraceListener
  • XML - System.Diagnostics.XmlWriterTraceListener
<sharedListeners>
        <add name="console" type="System.Diagnostics.ConsoleTraceListener" initializeData="false"/>
</sharedListeners>

6. Ustawiamy właściwość autoflush na true:
<trace autoflush="true" ></trace>

7. Przypisujemy listenera do źródła:
<listeners>
      <add name="console" />
</listeners>


Pełna, przykładowa konfiguracja:

Code:
<configuration>
  <system.diagnostics>
    <sources>
      <source name="System.Windows.Media.Animation"
switchName="SourceSwitch" >
        <listeners>
          <add name="textListener" />
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="SourceSwitch" value="All" />
    </switches>
    <sharedListeners>
      <add name="textListener"
           type="System.Diagnostics.TextWriterTraceListener"
           initializeData="Debug.txt" />
    </sharedListeners>
    <trace autoflush="true" indentsize="4"></trace>
  </system.diagnostics>
</configuration>


Konfiguracja w kodzie:
Jeżeli chcemy skonfigurować logowanie w kodzie, musimy ustawić poziom logowania oraz dodać nasłuch:

Code:
PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Verbose;
            Stream myFile = File.Create("trace.txt");
            var listener = new TextWriterTraceListener(myFile);
            PresentationTraceSources.DataBindingSource.Listeners.Add(listener);


piątek, 4 stycznia 2013

70-511 Rozdział 11 - Testowanie interfejsu użytkownika

WPF oferuje automatyczne testowanie interfejsu użytkownika. Nie jest to proste zadanie ze względu na drzewiastą strukturę interfejsu graficznego.
Testowanie automatyczne w WPF-ie zostało umożliwione poprzez zaimplementowanie podstawowych operacji kontrolek w klasach dziedziczących po bazowej klasie AutomationPeer.Klasy te łatwo rozpoznać po nazwie która składa się z nazwy kontrolki + AutomationPeer: <control>AutomationPeer np dla kontrolki przycisku będzie to ButtonAutomationPeer.
Bazowa klasa AutomationPeer posiada cztery przydatne metody:
  • GetChildren - zwraca elementy zawarte w aktualnym jako obiekty typu AutomationPeer
  • GetName - zwraca nazwę kontrolki, powiązanej z aktualnym obiektem AutomationPeer
  • GetParent - zwraca element nadrzędny jako obiekt typu AutomationPeer
  • GetPattern - zwraca interfejsy powiązane z aktualnym testem, umożliwiające testowanie
Lista interfejsów, implementowanych przez klasy dziedziczące z AutomationPeer i zapewniające funkcjonalność podczas testów:
  • IDockProvider - dla kontrolek, które oferują możliwości dokowania w kontenerze
  • IExpandCollapseProvider - dla kontrolek, które mogą być zwijane i rozwijane 
  • IGridItemProvider - dla kontrolek, które mogą się znaleźć w kontrolce typu Grid
  • IGridProvider - dla kontrolek będących kontenerami
  • IInvokeProvider - dla kontrolek, które wykonują tylko jedną akcję
  • IItemContainerProvider - oferuje metody pozwalające odnaleźć element na liście obiektów
  • IMultipleViewProvider - dla kontrolek pozwalających wyświetlać dane w różny sposób 
  • IRangeValueProvider - dla kontrolek pozwalających ustawiać zakres
  • IScrollItemProvider - dostęp do elementów kontrolek implementujących interfejs IScrollProvider
  • IScrollProvider - kontrolki umożliwiające przewijanie
  • ISelectionItemProvider - kontrolki umożliwiające wybór elementu - implementują interfejs ISelectionProvider
  • ISelectionProvider - kontrolki pełniące funkcję kontenera dla elementów możliwych do zaznaczenia
  • ISynchronizedInputProvider - kontrolki wspierające synchroniczne wprowadzanie zawartości
  • ITableItemProvider - element kontrolki implementującej interfejs ITableProvider
  • ITableProvider - dla kontrolek typu zakładkowego
  • ITextProvider - dla kontrolek zawierających tekst
  • ITextRangeProvider - pozwala na dostęp do bloku tekstu
  • IToggleProvider - obsługa kontrolek posiadających stan
  • ITransformProvider - kontrolki mogące być powiększane, obracane i przesuwane w 2D
  • IValueProvider - dostęp do wartości kontrolki, która może być reprezentowana jako tekst
  • IVirtualizedItemProvider - element, który może być użyty na panelu wirtualnym
  • IWindowProvider - reprezentuje operacje oferowane przez okno aplikacji
Konkretny interfejs pobieramy poprzez metodę GetPattern.
Przykład dla kontrolki przycisku:

Code:
<Window x:Class="WPF_AutomationPeer_Testing.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Button Name="Przycisk1" Content="Przyck1" Click="ButtonBase_OnClick" />
        <Button Name="PrzyciskTest" Content="Test" Grid.Row="1" Click="PrzyciskTest_OnClick"></Button>
    </Grid>
</Window>

Code:
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;

namespace WPF_AutomationPeer_Testing
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Kliknięto w przycisk");
        }

        private void PrzyciskTest_OnClick(object sender, RoutedEventArgs e)
        {
            var buttonAutomationPeer = new ButtonAutomationPeer(Przycisk1);
            var pattern = (IInvokeProvider)buttonAutomationPeer.GetPattern(PatternInterface.Invoke);
            pattern.Invoke();
        }
    }
}

Kod do pobrania: http://sdrv.ms/WYcoxC

czwartek, 3 stycznia 2013

70-511 Rozdział 10 - Drag & Drop

Drag & Drop - przeciągnij i upuść pozwala na wykonywanie określonych operacji w graficznym interfejsie za pomocą myszki.
Proces ten, oparty jest na zdarzeniach. Opis dla elementu źródłowego jak i docelowego:
Element źródłowy:
  • MouseDown - w zdarzeniu tym wywołujemy metodę DoDragDrop
  • GiveFeedBack - daje możliwość zmiany wyglądu kursora myszy
  • QueryContinueDrag - możliwość anulowania przeciągania
Element docelowy:
  • DragEnter - zdarzenie wywoływane, w momencie, gdy kontrolka daje możliwość upuszczenia danych
  • DragOver - wywoływane gdy dane znajdują się nad kontrolką docelową
  • DragDrop - w momencie puszczenia przycisku myszy
  • DragLeave - wywoływane gdy myszka opuszcza kontrolkę docelową
Kolejność wywoływanych zdarzeń podczas operacji przeciągnij i upuść (opis dla Windows Forms, wersja dla WPF w nawiasach):
  1. Operacja zaczyna się od wywołania metody DoDragDrop (DragDrop.DoDragDrop) na kontrolce źródłowej. Metoda ta najczęściej jest wywoływana w zdarzeniu MouseDown - kopiowane są dane, oraz ustawiane zachowanie dla skopiowanych danych.
  2. Zostają wywołane zdarzenia GiveFeedBack oraz QueryContinueDrag. Pierwsze zdarzenie umożliwia zmianę wizerunku kursora myszy, drugie mówi o tym czy przenoszenie ma być kontynuowane czy też przerwane. 
  3. Kursor myszy zostaje przesunięty nad kontrolkę docelową. Każda kontrolka, która ma ustawioną flagę AllowDrop, jest potencjalnym elementem do "umieszczenia" danych. Zostaje w tym momencie wywołane zdarzenie DragEnter. Możemy w tym momencie zdecydować czy odebrać dane, czy też odrzucić. 
  4. Użytkownik puszcza przycisk myszy. Zostaje wywołane zdarzenie DragDrop, w którym możemy zdecydować co zrobimy z "przyniesionymi" danymi.
Możliwe tryby przenoszenia (DragDropEffects):
  • All - dane są kopiowane, usuwane z źródła i umieszczane w kontrolce docelowej
  • Copy - dane są kopiowane do celu
  • Link - dane są podlinkowywane do celu
  • Move - dane zostaną przeniesione do celu
  • None - cel nie przyjmie danych
  • Scroll - przewijanie w kontrolce docelowej
Przykładowa implementacja Drag & Drop:

Code:
        private void TbSource_OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            DragDrop.DoDragDrop(tbSource, tbSource.Text, DragDropEffects.Copy);
        }

        private void TvDestination_OnDragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.Text))
            {
                e.Effects = DragDropEffects.Copy;
            }
        }

        private void TvDestination_OnDrop(object sender, DragEventArgs e)
        {
            var position = e.GetPosition(tvDestination);
            var textBlock = tvDestination.InputHitTest(position);
            var data = e.Data.GetData(DataFormats.Text);
            if (textBlock != null && textBlock is TextBlock)
            {
                var node = (TreeViewItem)GetParentByType((Visual)textBlock, typeof(TreeViewItem));
                node.Items.Add(data);
            }
            else
            {
                tvDestination.Items.Add(data);
            }
        }

        public Visual GetParentByType(Visual startPoint, Type searchingType)
        {
            while (startPoint.GetType() != searchingType)
            {
                startPoint = (Visual)VisualTreeHelper.GetParent(startPoint);
            }

            return startPoint;
        }


Efekt:



Drag & Drop pomiędzy aplikacjami .NET jest wspierane przez system. Jedynymi wymogami są:
  • Kontrolka docelowa musi pozwalać na efekt ustawiony w metodzie DoDragDrop
  • Kontrolka docelowa musi akceptować format danych ustawiony w metodzie DoDragDrop


Kod do pobrania: http://sdrv.ms/TjCDz3

środa, 2 stycznia 2013

70-511 Rozdział 10 - Zarządzanie ustawieniami

Ustawienia aplikacji mogą przechowywać takie dane jak adres serwera, domyślny katalog zapisu plików błędów itp.
Ustawienie posiada cztery właściwości:
  • Name - nazwa ustawienia
  • Type - typ przechowywanych danych
  • Value - wartość
  • Scope - zakres (użytkownik, cała aplikacja)
Należy tutaj zaznaczyć ważną różnice odnośnie zakresu obowiązywania ustawień. Ustawienia na użytkownika są zarówno do odczytu jak i zapisu. Ustawienia z zakresem całej aplikacji są tylko do odczytu. Zmieniać je można tylko podczas ich definiowania.
Ustawienia definiujemy we właściwościach projektu, na zakładce Settitngs:


Dostęp do właściwości odbywa się poprzez klasę Settings:

Code:
var serverAddress = Properties.Settings.Default.ServerAddress;

Ustawienia użytkownika możemy w prosty sposób zmienić - przypisujemy nową wartość i wywołujemy metodę Save:

Code:
            Properties.Settings.Default.Setting1 = "new valu";
            Properties.Settings.Default.Save();

wtorek, 1 stycznia 2013

70-511 Rozdział 10 - Bezpieczeństwo

Instalując aplikację na systemie bez pełnych praw administracyjnych, niektóre operacje nie są uznawane za "bezpieczne".
Szczegółowa lista elementów nie w pełni bezpiecznych:
  • aplikacja definiująca okna
  • okno zapisu (SaveFileDialog)
  • obsługa plików
  • dostęp do rejestru systemu
  • Drag & Drop
  • serailizacja XAML (XamlWriter.Save)
  • klienci UIAutomation
  • dostęp do klasy HwndHost
  • pełne wsparcie dla rejestracji dźwięku
  • hostowanie kontrolek Windows Forms w WPF
Lista elementów nie w pełni dostępnych dla specyficznych zadań
  • dostęp do sieci
    • WCF
    • skrypty
    • DOM
  • Efekty wizualne
    • Efekty związane z bitmapami
  • Edycja
    • Pełne wsparcie dla XAML
    • schowek (tekst w formacie rtf)
W przypadku, gdy tworzymy aplikację która będzie uruchamiana w środowisku bez pełnych uprawnień administracyjnych - unikamy powyżej wymienionych operacji. Oczywiście możemy sprawdzić, czy dana operacja jest dozwolona w danym środowisku. W przypadku braku dostępu zostanie rzucony wyjątek, który przechwytujemy i odpowiednio obsługujemy dalsze wykonanie aplikacji:

Code:
            try
            {
                var fileIoPermission = new FileIOPermission(FileIOPermissionAccess.AllAccess, "c:\file.txt");
                fileIoPermission.Demand();
                //operacje na pliku
            }
            catch (SecurityException ex)
            {
                //obsługa wyjątku
            }


Zasady bezpieczeństwa możemy ustawić we właściwościach projektu na zakładce Scurity:


Jeżeli chcemy udostępnić prawa administracyjne dla aplikacji wystarczy w pliku app.manifest zmienić z:
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
na:
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
jeżeli potrzebujemy najwyższych uprawnień używamy poziomu highestAvailable

poniedziałek, 31 grudnia 2012

70-511 Rozdział 9 - kontrolki Windows Forms w WPF

WPF oferuje bardzo dużo kontrolek. Są jednak takie, jak np. MaskedTextBox których w WPF nie ma a Windows Forms są. Dzięki mechanizmowi umożliwiającemu używanie kontrolek Windows Forms w WPF, nie musimy ich implementować od nowa. Przyjrzyjmy się aspektom, wykorzystania okien i kontrolek Windows Forms w aplikacji WPF

Okna dialogowe
Okna OpenFileDialog (Otwóz plik) oraz SaveFileDialog (Zapisz plik) to najczęściej wykorzystywane okna dialogowe w aplikacjach. Aby użyć je w aplikacji WPF należy:
  • Dodać referencję do DLL System.Windows.Forms
  • W kodzie tworzymy obiekt klasy OpenFileDialog, lub SaveFileDialog
Inne kontrolki występujące w Windows Forms, możemy dodawać do aplikacji WPF poprzez komponent WindowsFormsHost. Aby można było używać tego komponentu należy dodać referencję w XAML do DLL System.Windows.Forms.Integration. Następnie przeciągamy element WindowsFormsHost, w którym możemy umieścić kontrolkę Windows Forms. Właściwości i zdarzenia ustawiamy tak samo jak dla kontrolek WPF-owych. Aby pobrać referencję do kontrolki należy skorzystać z właściwości Child kontrolki WindowsFormsHost:

Code:
        <WindowsFormsHost Name="WindowsFormsHost">
            <forms:MaskedTextBox Name="mtbTextBox1"></forms:MaskedTextBox>
        </WindowsFormsHost>

Code:
            var maskedTextBox = (MaskedTextBox) WindowsFormsHost.Child;
            maskedTextBox.Mask = "00000";



Użycie kontrolek WPF w aplikacji Windows Forms
W aplikacji Windows Formst można użyć kontrolek z WPF dzięki kontrolce ElementHost.
Jeżeli kontrolka jest częścią tworzonego projektu, można ją przypisać w designerze. Jeżeli nie należy ją przypisać w kodzie.