Pokazywanie postów oznaczonych etykietą 70-511. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą 70-511. Pokaż wszystkie posty

środa, 9 stycznia 2013

70 - 511 Podsumowanie i wrażenia po egzaminie

Do egzaminu przygotowałem się już od dłuższego czasu. Artykuły, które w tym czasie tutaj umieszczałem miały na celu zapisanie notatek, aby na parę godzin przed egzaminem powtórzyć sobie najważniejsze rzeczy.
Pytania dotyczyły głównie możliwości które oferuje WPF - bindowanie, walidacja, szablony, style. Nie zabrakło także pytań dotyczących bezpieczeństwa tworzonych aplikacji.
Z mojej perspektywy pytania były ciekawe i pozwalały zweryfikować, czy ktoś ma do czynienia z programowanie w WPF czy może tylko jest oczytany w tematach.
Przystępowanie do egzaminu po przeczytaniu tylko artykułów na temat WPF nie ma sensu. Należy poświęcić wiele czasu na praktyczną część i dużo ćwiczyć i jeszcze raz ćwiczyć praktycznie pisząc kod.
Czasu na egzamin jest wystarczająco dużo, aby można było go dwukrotnie odbyć. Można więc na spokojnie przemyśleć sobie kawałki kodu i nawet jeżeli nie jest się czegoś pewnym, drogą dedukcji dojść do prawidłowego rozwiązania.
Czy taka forma egzaminu w pełni odzwierciedla wiedzę i umiejętności programisty? Nie mam zamiaru tutaj polemizować z kimkolwiek na ten temat. W każdym razie sama wiedza teoretyczna nie jest wystarczająca, a znajomość i wiedza, że takie narzędzie przydaje się do tego a tamto do tamtego - nie zastąpi praktycznych umiejętności posługiwania się nimi.
Egzamin zaliczony :) czas na kolejne wyzwania. W planach mam zdanie 5 egzaminów do maja. W tym momencie mam już pierwszy za sobą.
Kolejny który zamierzam podjąć to 70 -516 Accessing Data with Microsoft .NET Framework 4 - możecie się spodziewać następnych notatek ;)

poniedziałek, 7 stycznia 2013

Bindowanie do zasobów Dynamic vs Static

Tym razem krótko, o czymś co może się przydać nie tylko na egzaminie, ale także w codziennej pracy z zasobami i bindowaniem do nich.

Rozważmy cztery przypadki bindowania do zasobów w kodzie.
Kod wyjściowy dla wszystkich przypadków:

Code:
<Window x:Class="DynamicStatic.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">
    <Window.Resources>
        <SolidColorBrush x:Key="Background" Color="Blue" />
        <SolidColorBrush x:Key="Foreground" Color="Red" />
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="30" />
        </Grid.RowDefinitions>
        <Button Content="Przycisk"></Button>
        <Button Name="btnDoSomething" Content="Wykonaj" Grid.Row="1" Click="BtnDoSomething_OnClick" />
    </Grid>
</Window>



Dwa zasoby dla tła i koloru czcionki, będziemy przypisywać do przycisku, a następnie w kodzie pobierzemy i zmienimy ich wartość. Zmieniać będzie się tylko definicja klawisza i metody podpiętej pod zdarzenie drugiego klawisza.

1. Przypisanie statyczne i zmiana koloru pędzla:

Code:
<Button Background="{StaticResource Background}" Foreground="{StaticResource Foreground}" Content="Przycisk"></Button>

private void BtnDoSomething_OnClick(object sender, RoutedEventArgs e)
        {
            var background = (SolidColorBrush)this.FindResource("Background");
            background.Color = Colors.Yellow;

            var foreground = (SolidColorBrush)this.FindResource("Foreground");
            foreground.Color = Colors.Black;
        }

Efekt? Zmiana kolorów:


2. Przypisanie statyczne i zmiana pędzla:
Różnica polega na zmianie pędzla - czyli podmieniamy cały obiekt zasobów (XAML definiujący przycisk nie zmienia się):

Code:
        private void BtnDoSomething_OnClick(object sender, RoutedEventArgs e)
        {
            this.Resources["Background"] = new SolidColorBrush(Colors.Yellow);

            this.Resources["Foreground"] = new SolidColorBrush(Colors.Black);
        }

Efekt?





Właściwie w tym przypadku możemy mówić o braku jakiegokolwiek efektu. Dzieje się tak dlatego, że statyczny zasób pobierany jest tylko raz. Obiekt pędzla został pobrany i nie zmienia się, jednak jeżeli zmienimy właściwość - zobaczymy efekt (dotyczy to pierwszego przykładu).

3. Przypisanie dynamiczne i zmiana koloru pędzla

Code:
<Button Background="{DynamicResource Background}" Foreground="{DynamicResource Foreground}" Content="Przycisk"></Button>

        private void BtnDoSomething_OnClick(object sender, RoutedEventArgs e)
        {
            ((SolidColorBrush) this.Resources["Background"]).Color = Colors.Yellow;

            ((SolidColorBrush)this.Resources["Foreground"]).Color = Colors.Black;
        }

Efekt:


4. Przypisanie dynamiczne i zmiana pędzla
Kod XAML przycisku taki jak poprzednio

Code:
        private void BtnDoSomething_OnClick(object sender, RoutedEventArgs e)
        {
            this.Resources["Background"] = new SolidColorBrush(Colors.Yellow);

            this.Resources["Foreground"] = new SolidColorBrush(Colors.Black);
        }

Efekt:


Warto zapamiętać te przypadki i wiedzieć w jaki sposób są pobierane zasoby, gdyż możemy dzięki temu uniknąć częstych błędów.

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.

niedziela, 30 grudnia 2012

70-511 Rozdział 9 - globalizacja i lokalizacja

Terminy globalizacja i lokalizacja w przypadku tworzenia oprogramowania oznaczają różne terminy:
  • Globalizacja - wyświetlanie kwot, oraz liczb z odpowiednim formatowaniem
  • Lokalizacja - język programu dostosowany do ustawień systemu operacyjnego
Aktualna kultura aplikacji jest reprezentowana przez obiekt typu CultureInfo. Aktualną kulturę możemy zmienić w prosty sposób:

Code:
Thread.CurrentThread.CurrentCulture = new CultureInfo("en");

Globalizacja obsługiwana jest automatycznie dla dat. W przypadku cen należy korzystać w celu ich wyświetlania z automatycznego formatowania:

Code:
var price = 500.ToString("C");

Jeżeli przyjrzymy się klasie Thread, znajdują się w niej dwie podobne właściwości: CurrentCulture oraz CurrentUICulture. Różnica pomiędzy nimi jest taka, iż pierwsza właściwość determinuje w jaki sposób będą formatowane daty, a druga determinuje tłumaczenie aplikacji.

Lokalizacja w Windows Forms
Aby włączyć lokalizację w Windows Forms, należy w ustawieniach formy zaznaczyć flagę Localizable. Następnie tłumaczymy interfejs na poszczególne języki. Po przetłumaczeniu formatki (np. na angielski) zmieniamy właściwość Language na następny język i ponownie tłumaczymy zawartość interfejsu. 



Lokalizacja w WPF
Podczas uruchamiania aplikacji WPF, szukane są pliki dll zawierające zasoby lokalizacji. Kroki które należy wykonać, aby mechanizm lokalizacji działał poprawnie:
1. Dodajemy do projektu atrybut UICulture
Atrybut UICulture specyfikuje domyślny język aplikacji. Po dodaniu tego atrybutu, zostanie stworzony katalog dla danej kultury, do którego zostanie zapisana biblioteka dll z zasobami językowymi.
Na początek otwieramy za pomocą notatnika plik <nazwa_projektu>.csproj. Wyszukujemy pierwszy element PropertyGroup i dodajemy w nim element: <UICulture>en-US</UICulture>. Zapisujemy plik i przebudowujemy projekt.

2. Oznaczenie lokalizowanych elementów
Lokalizowane elementy możemy oznaczyć za pomocą właściwości x:Uid:

Code:
<Button x:Uid="Button1"></Button>

Powyższą operację można zautomatyzować za pomocą aplikacji Msbuild.exe.

3. Wyodrębnienie lokalizowanej zawartości.
Aby wyodrębnić lokalizowaną zawartość potrzebujemy narzędzia LocBaml. Narzędzie uruchamiamy z linii poleceń: locbaml /parse en-US\myApplication.resources.dll. Na wyjściu otrzymamy plik w formacie .csv, który zawiera wszystkie lokalizowane właściwości z elementów oznaczonych atrybutem x:Uid

4. Tłumaczenie
Tłumaczenie polega na przetłumaczeniu zawartości pliku CSV. Tłumaczymy tylko zawartość kolumny Value.

5. Tworzenie podfolderów dla języków
Podfoldery tworzymy w miejscu, którym znajduje się wykonywalna wersja naszej aplikacji. Foldery muszą mieć nazwy kultur dla jakich są tworzone, np. pl

6. Tworzenie bibliotek dll z przetłumaczoną zawartością
Za pomocą LocBaml możemy stworzyć biblioteki dll z przetłumaczonymi zasobami za pomocą polecenia:

locbaml /generate en-US\myApplication.resources.dll
/trans:myApplication.resources.FrenchCan.csv /cul:fr-CA /out:fr-CA

Elementy tego polecenia:
  • /generate - LocBaml stworzy zasób na podstawie podanej dll
  • /trans - plik csv z tłumaczeniem
  • /cul - kultura dla której tworzymy plik zasobów
  • /out - folder do którego zostanie skopiowany stworzony zasób
Jeżeli chcemy zmienić język, musimy zrobić to przed startem aplikacji, najlepiej zrobić to w zdarzeniu Application.Startup

sobota, 29 grudnia 2012

70-511 Rozdział 9 - programowanie asynchroniczne

Programowanie asynchroniczne ma na celu polepszenie interakcji aplikacji z użytkownikiem. Przenosząc długotrwałe operacje na inny wątek, zapewniamy, że interfejs naszej aplikacji nie zostanie zamrożony.
Komponentem, który umożliwia proste przeniesienie długotrwałych operacji na inny wątek, znany także z Windows Forms jest BackgroundWorker.
W momencie, gdy na obiekcie klasy BackgroundWorker wywołamy metodę RunWorkerAsync zostaje wywołane zdarzenie DoWork. Kod podpięty pod to zdarzenie zostanie wykonany na osobnym wątku. Niektóre z właściwości obiektu BackgroundWorker:
  • CancellationPending - flaga czy anulowano zadanie
  • IsBusy - flaga czy BW wykonuje zadanie
  • WorkerReportsProgress - flaga czy BW może powiadamiać o postępie pracy
  • WorkerSupportsCancellation - flaga czy operacja wykonywana przez BW może zostać anulowana
  • CancelAsync - przerywa aktualnie wykonywaną operację
  • ReportProgress - wywołuje zdarzenie ProgressChanged
  • RunWorkerAsync - uruchamia operacje
  • DoWork - zdarzenie, które jest wywoływane w momencie wywołania metody RunWorkerAsync 
  • ProgressChanged - zdarzenie wywoływane w momencie wywołania metody ReportProgress
  • RunWorkerCompleted - zdarzenie wywoływane w momencie zakończenia operacji przez BW
Podczas pracy z BW możemy mieć następujące zadania do zrealizowania:
  • przesłanie danych do naszego nowego wątku - parametr przekazujemy w wywołaniu metody RunWorkerAsync, a odebranie parametru następuje przez właściwość DoWorkEventArgs.Argument
  • powiadomienie, że proces zakończył się - realizowane poprzez obsłużenie zdarzenia RunWorkerCompleted
  • zwrócenie obliczanej wartości - przypisujemy wartość do właściwości - DoWorkEventArgs.Result, a odbieramy ją w zdarzeniu RunWorkerCompleted.Result
  • anulowanie operacji - ustawiamy flagę WorkerSupportsCancellation; w zdarzeniu DoWork implementujemy logikę, która sprawdza co jakiś czas flagę CancellationPending i jeżeli flaga jest ustawiona, przerywa operację
  • raportowanie postępów - ustawiamy flagę WorkerReportsProgress; w kodzie, metody która wykonuje długotrwałe obliczenia wywołujemy metodę ReportProgress do której przekazujemy informację o procencie wykonanej pracy
Przykładowa realizacja opisanych powyżej punktów:

Code:
public partial class MainWindow : Window
    {
        private BackgroundWorker backgroundWorker;

        public MainWindow()
        {
            InitializeComponent();

            backgroundWorker = new BackgroundWorker {WorkerReportsProgress = true, WorkerSupportsCancellation = true};
            backgroundWorker.DoWork += (o, args) =>
            {
                var number = double.Parse(args.Argument.ToString());
                double tmp = 0;
                for (int i = 1; i < 101; i++)
                {
                    if (backgroundWorker.CancellationPending)
                    {
                        args.Cancel = true;
                        return;
                    }
                    tmp += Math.Sqrt(number*i);
                    Thread.Sleep(50);
                    backgroundWorker.ReportProgress(i);
                }

                args.Result = tmp;
            };

            backgroundWorker.RunWorkerCompleted += (sender, args) =>
                                                       {
                                                           if (!args.Cancelled)
                                                           {
                                                               tbResult.Text = "Zadanie ukończone. Wynik: " +
                                                                               args.Result;
                                                           }
                                                           else
                                                           {
                                                               tbResult.Text = "Zadanie zostało przerwane";
                                                           }
                                                       };

            backgroundWorker.ProgressChanged += (sender, args) =>
                                                    {
                                                        pbProgress.Value = args.ProgressPercentage;
                                                    };
        }

        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            backgroundWorker.RunWorkerAsync(tbNumber.Text);
        }

        private void btnStop_Click(object sender, RoutedEventArgs e)
        {
            backgroundWorker.CancelAsync();
        }
    }






Aplikacja do pobrania z linka: http://sdrv.ms/V0bXkg



Delegaty
Delegaty umożliwiają implementację asynchronicznych metod. Każda delegata posiada metody: BeginInvoke oraz EndInvoke. Wywołanie pierwszej z nich powoduje stworzenie osobnego wątku i wykonania w nim wskazywanej metody. Druga metoda pozwala na pobranie rezultatu wykonania metody.
Delegaty możemy użyć na trzy sposoby w celu tworzenia metod asynchronicznych:

  • wywołujemy BeginInvoke, wykonujemy zadanie a następnie wywołujemy EndInvoke na tym samym wątku
  • wywołujemy BeginInvoke a następnie oczekując na zakończenie wywołania, wykonujemy inne operacje
  • wywołanie metody po zakończeniu pracy wątku w tle
Pierwsze rozwiązanie jest najprostsze w wykonaniu, jednak nie jest idealne. W momencie wywołania metody EndInvoke nastąpi zablokowanie wątku głównego i oczekiwanie aż wątek w tle zakończy swoją pracę:

Code:
            MyDelegate myDelegate = DoSomething;
            myDelegate(null);
            IAsyncResult result = myDelegate.BeginInvoke(5, null, null);
            lblOnTheSameThreadResult.Text = myDelegate.EndInvoke(result).ToString();

Drugi przypadek zakład iż, w czasie oczekiwania na rezultat wykonania się operacji w innym wątku, będziemy wykonywać inne rzeczy w głównym wątku:

Code:
            MyDelegate myDelegate = DoSomething;
            myDelegate(null);
            IAsyncResult result = myDelegate.BeginInvoke(5, null, null);
            while (!result.IsCompleted)
            {
                //Do some operations here
            }
            lblPoolingResult.Text = myDelegate.EndInvoke(result).ToString();

Ostatnia możliwość zakłada, iż rezultat wykonania operacji asynchronicznej nie będzie nam potrzebny w wątku, który go wykonał. Po zakończeniu obliczeń, zostanie wywołana metoda, w której możemy odebrać naszą wartość:

Code:
            MyDelegate myDelegate = DoSomething;
            myDelegate(null);
            IAsyncResult result = myDelegate.BeginInvoke(5,
                            new AsyncCallback(ar =>
                                                {
                                                    var d = (MyDelegate) ar.AsyncState;
                                                    double res = d.EndInvoke(ar);
                                                    Dispatcher.Invoke(() =>
                                                                          {
                                                                              lblCallbackResult.Text = res.ToString();
                                                                          });
                                                }), myDelegate);

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



Wątki
Chcąc mieć 100% kontrolę nad wątkami, możemy skorzystać z klasy Thread. Podstawowe operacje w przypadku pracy z wątkami:
1. Tworzenie wątku:

Code:
            var thread = new Thread(() =>
                                        {
                                            //...Do something
                                        });
            thread.Start();

2. Niszczenie wątku:
Wątek można zniszczyć za pomocą metody Abort. Wywołanie tej metody na działającym wątku spowoduje rzucenie wyjątku ThreadAbortException:

Code:
            try
            {
                thread.Abort();
            }
            catch (ThreadAbortException e)
            {
                Console.WriteLine(e);
            }

3. Synchronizacja wątków
Zakleszczenia (deadlock) - występują gdy procesy czekają nawzajem na siebie - żaden nie jest w stanie ukończyć pracy
Sytuacje wyścigu (race conditions) - występują w momencie gdy więcej niż jeden proces odwołuje się do niezabezpieczonej pamięci współdzielonej
Do synchronizacji możemy wykorzystać słowo kluczowe lock:

Code:
            lock (aobj)
            {
                
            }



Wątki a kontrolki
Kontrolki na formatce obsługuje wątek nazywany UI Thread. Dostęp do kontrolek z innych wątków nie jest bezpieczny. Windows Forms jak i WPF definiują wzorce dostępu do wątku interfejsu:
Windows Forms:
Za pomocą delegaty odwołujemy się do wątku interfejsu:

Code:
        public delegate void SetTextDelegate(string t);
        public void SetText(string text)
        {
            if (textBox1.InvokeRequired)
            {
                var del = new SetTextDelegate(SetText);
                del.Invoke(text);
            }
            else
            {
                textBox1.Text = text;
            }
        }

WPF:
Za pomocą obiektu Dispatcher, wykonujemy zmiany w interfejsie:

Code:
            Dispatcher.Invoke(() => { });
            Dispatcher.BeginInvoke(() => { }, DispatcherPriority.Normal);

Pierwsza metoda jest blokująca. Druga nie zablokuje wątku interfejsu w czasie jego aktualizacji.

piątek, 28 grudnia 2012

70-511 Rozdział 8 - implementacja IDataErrorInfo

Poprzednio zostały przedstawione reguły walidacji - ValidationRules. Mechanizm ten umożliwiał łatwe tworzenie reguł, według których były walidowane dane. Interfejs IDataErrorInfo implementujemy na poziomie obiektu. Dodaje to dodatkowy poziom walidacji - na poziomie poszczególnych obiektów.
Interfejs ten posiada dwie składowe:
  • Error - informacja o błędzie w obiekcie
  • Item - informacja o błędzie dla danego pola w klasie
Właściwość Error w większości przypadków nie jest implementowana (aby dostać jej wartość, należy bezpośrednio odwołać się do danego pola klasy). Składowa Item, integruje się z istniejącym systemem walidacji.

Po zaimplementowaniu interfejsu w obiekcie, należy włączyć powiadamianie o błędach w definicji bindowania (ValidatesOnDataErrors=True). Kolejnym elementem jest ustawienie właściwości UpdateSourceTrigger. Jeżeli będzie ona ustawiona na tryb PropertyChanged (walidacja po każdej zmianie wartości) może spowodować to duże obciążenie dla procesora. Jeżeli podczas walidacji wystąpi błąd, zostanie on dodany do kolekcji Validation.Errors. Aby go wyświetlić, możemy stworzyć odpowiedni styl dla kontrolki, w którym uwzględnimy sposób wyświetlania błędów.

Przykładowa implementacja z zastosowaniem w walidacji obiektu:

Code:
<Window x:Class="IDataErrorInfo_Implementation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:IDataErrorInfo_Implementation" Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:Person x:Key="personObj"></local:Person>
        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},
                                                    Path=(Validation.Errors)[0].ErrorContent}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="80" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        
        <TextBlock>First Name:</TextBlock>
        <TextBox HorizontalAlignment="Left" VerticalAlignment="Center"
            Text="{Binding FirstName, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True}" 
            Grid.Column="1" Width="100"/>

        <TextBlock Grid.Row="1">Last Name:</TextBlock>
        <TextBox HorizontalAlignment="Left" VerticalAlignment="Center"
            Text="{Binding Source={StaticResource personObj}, Path=LastName, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True}" 
            Grid.Column="1" Width="100" Grid.Row="1"/>
    </Grid>
</Window>

Code:
using System.ComponentModel;

namespace IDataErrorInfo_Implementation
{
    public class Person : IDataErrorInfo, INotifyPropertyChanged
    {
        private int id;
        private string firstName;
        private string lastName;
        private string city;

        public int Id
        {
            get { return id; }
            set
            {
                if (value == id) return;
                id = value;
                OnPropertyChanged("Id");
            }
        }

        public string FirstName
        {
            get { return firstName; }
            set
            {
                if (value == firstName) return;
                firstName = value;
                OnPropertyChanged("FirstName");
            }
        }

        public string LastName
        {
            get { return lastName; }
            set
            {
                if (value == lastName) return;
                lastName = value;
                OnPropertyChanged("LastName");
            }
        }

        public string City
        {
            get { return city; }
            set
            {
                if (value == city) return;
                city = value;
                OnPropertyChanged("City");
            }
        }

        public string this[string columnName]
        {
            get
            {
                string result = "";
                switch (columnName)
                {
                    case "FirstName":
                        if (!string.IsNullOrWhiteSpace(FirstName) && FirstName.Length < 3)
                        {
                            result = "First name must be specified";
                        }
                        break;
                    case "LastName":
                        if (string.IsNullOrWhiteSpace(LastName))
                        {
                            result = "Last name must be specified";
                        }
                        break;
                }

                return result;
            }
        }

        public string Error
        {
            get { throw new SystemNotImplementedException(); }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}




Tworzony kod do pobrania: http://sdrv.ms/12GZQ0P

czwartek, 27 grudnia 2012

70-511 Rozdział 8 - Walidacja Windows Forms

Walidacja w WPF została omówiona w poście poświęconym bindowaniu danych. Tym razem przyjrzymy się w jaki sposób można obsłużyć walidację w Windows Forms.

Walidacja na poziomie pojedynczej kontroki
Walidacja pojedynczych pól odbywa się w momencie gdy użytkownik wprowadza dane np. do TextBox-a. Jeżeli chodzi o tę kontrolkę, mamy szereg właściwości ułatwiających ten proces:
  • MaxLength - maksymalna długość tekstu w kontrolce
  • PasswordChar - jaki znak zostanie użyty podczas zasłaniania zawartości kontrolki - przeważnie używane w polach w które wprowadzamy hasło
  • ReadOnly - czy jest tylko do odczytu
  • MultiLine - czy można wprowadzać wiele linii tekstu
Oprócz właściwości, mamy dostępne trzy zdarzenia wywoływane w czasie wprowadzania danych do kontrolek:
  • KeyDow
  • KeyPres
  • KeyUp
KeyDown i KeyUp wywoływane są odpowiednio w momencie naciśnięcia i zwolnienia klawisza. Zdarzenia te są najczęściej wykorzystywane do determinacji czy został wciśnięty jedne z klawiszy: Alt, Shift, Ctrl. Klawiszom tym odpowiadają właściwości logiczne które możemy sprawdzić w metodzie podpiętej pod zdarzenie:

Code:
sender, KeyEventArgs e)
        {
            var alt = e.Alt;
            var ctrl = e.Control;
            var shift = e.Shift;
        }

        private void textBox1_KeyUp(object sender, KeyEventArgs e)
        {
            var alt = e.Alt;
            var ctrl = e.Control;
            var shift = e.Shift;
        }

Za pomocą właściwości KeyCode możemy sprawdzić, który klawisz został naciśnięty:

Code:
            if (e.KeyCode == Keys.A)
            {
                MessageBox.Show("Naciśnięto klawisz");
            }


Zdarzenie KeyPress wywoływane jest, gdy naciśnięty klawisz reprezentuje wartość z tablicy ASCI (są to klawisze liter a - z, liczb, znaków specjalnych. Jeżeli naciśnięte klawisze/klawisz nie tworzą kodu ASCII, zdarzenie nie zostanie wywołane. Przykładem są klawisze Alt, Ctrl. Za pomocą właściwości KeyChar mamy możliwość sprawdzenia jaki klawisz został naciśnięty. Naciśnięty klawisz możemy następnie sprawdzić jawnie (przyrównując go bezpośrednio do żądanego klawisza) bądź skorzystać z metod zawartych w klasie Char:
  • Char.IsDigit(e.KeyChar);
  • Char.IsLetter(e.KeyChar);
  • Char.IsLetterOrDigit(e.KeyChar);
  • Char.IsPunctuation(e.KeyChar);
  • Char.IsLower(e.KeyChar);      
  • Char.IsUpper(e.KeyChar);


Aktywność
Aktywowanie kontrolki, czy też ustawienie fokusa na niej powoduje, że możemy z nią prowadzić interakcje wprowadzając i zmieniając zawartość za pomocą myszki i klawiatury.
Każda kontrolka implementuje metodę Focus, która zwraca wartość logiczną czy udało się ją aktywować. Zdarzenia odpowiedzialne za aktywację kontrolki wywoływane są w następującej kolejności:
  • Enter - wywoływane w momencie gdy kontrolka uzyskała fokus
  • GotFocus - wywoływane w momencie gdy kontrolka po raz pierwszy uzyskała fokus
  • Leave - wywoływane w momencie gdy kontrolka straciła fokus
  • Validating - w momencie walidacji
  • Validated - po zakończeniu walidacji
  • LostFocus - wywoływane w momencie gdy kontrolka po raz pierwszy straciła fokus
Walidację najlepiej przeprowadzić w zdarzeniu Validating. Zdarzenie to jest wywoływane jeżeli zostanie ustawiona flaga CausesValidation kontrolki. Domyślnie ta wartość jest ustawiona, więc nie ma potrzeby tego zmieniać ręcznie. Metoda podpięta pod zdarzenie Validating przyjmuje jako jeden z parametrów obiekt klasy CancelEventArgs. Możemy skorzystać z właściwości Cancel - spowoduje to, iż kontrolka nie straci fokusa w przypadku np. niepowodzenia walidacji.
Przykładowy kod:

Code:
        private void textBox1_Validating(object sender, CancelEventArgs e)
        {
            if (textBox1.Text.Length < 3)
            {
                e.Cancel = true;
            }
        }

Walidację całej formatki można zaimplementować w następujący sposób: klawisz akceptujący wartości czy też przechodzący do następnego kroku przeprowadzi walidację wszystkich pól i w razie niepowodzenia, ustawia fokus na pierwszej kontrolce z nieprawidłową wartością. Przykład:

Code:
        private void button1_Click(object sender, EventArgs e)
        {
            foreach (Control control in Controls)
            {
                if (control is TextBox)
                {
                    if (string.IsNullOrWhiteSpace(control.Text))
                    {
                        control.Focus();
                        return;
                    }
                }
            }
        }

Błędy należy w graficzny sposób przedstawić użytkownikowi. Przydatna do tego jest kontrolka ErrorProvider. Za pomocą metody SetError ustawiamy na danej kontrolce błąd. Aby usunąć powiadomienie o błędzie, należy do metody SetError podać jako błąd pusty ciąg znaków. Przykład:

Code:
            if (textBox1.Text.Length < 3)
            {
                e.Cancel = true;
                errorProvider1.SetError(sender as TextBox, "Błąd walidacji!");
            }
            else
            {
                errorProvider1.SetError(sender as TextBox, "");
            }




Kod do pobrania: http://sdrv.ms/12GZW8A

środa, 26 grudnia 2012

70-511 Rozdział 8 - DataGrid

Kontrolka ta jest analogiczna do DataGridView z WindowsForms, tylko przeznaczona dla WPF. Dane do których chcemy się bindować muszą implementować interfejs IEnumerable. Domyślnie zostaną stworzone kolumny na podstawie nazw pól klasy której obiekty są bindowane do DataGrid. Źródło danych ustawiamy przez właściwość ItemsSource. Możemy ją przypisać zarówno w kodzie jak i XAML-u:

Code:
dgCustomers.ItemsSource = customerRepository.GetAllCustomers();

Code:
        <DataGrid Name="dgCustomers" ItemsSource="{Binding}">
            
        </DataGrid>

Podobnie jak w DGV mamy predefiniowane typy kolumn:
  • DataGridTextBoxColumn - dane tekstowe
  • DataGridComboBoxColumn - dane do wyboru z listy (ComboBox)
  • DataGridCheckBoxColumn - dane logiczne
  • DataGridHyperlinkColumn - linki do danych
Jeżeli dostępne typy kolumn nam nie wystarczają możemy skorzystać z DataGridTemplateColumn. Szablon pozwala zdefiniować wygląd każdego z elementów kolumny. Możemy także skorzystać z właściwości TemplateSelector, wybierając szablon na podstawie zdefiniowanej logiki.
Przykładowa kolumna z własnym stylem:

Code:
<DataGrid Name="dgCustomers" ItemsSource="{Binding}">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="TemplateColumn" >
                    <DataGridTemplateColumn.CellTemplate>
                        <ItemContainerTemplate>
                            <Grid>
                                <Rectangle>
                                    <Rectangle.Fill>
                                        <SolidColorBrush Color="Blue"></SolidColorBrush>
                                    </Rectangle.Fill>
                                </Rectangle>
                                <TextBlock Text="{Binding FirstName}" Foreground="Chartreuse"></TextBlock>
                            </Grid>
                        </ItemContainerTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>



Ciekawą opcją jest możliwość wyświetlenia dodatkowych informacji o zaznaczonym rekordzie (RowDetailsTemplate). Jeżeli dana tabela ma wiele kolumn, możemy wyświetlić najczęściej używane dane, a pozostałe wyświetlać w momencie kiedy tego sobie zażyczy użytkownik.
Przykład:

Code:
            <DataGrid.RowDetailsTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="25" />
                            <RowDefinition Height="25" />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="50" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="Imię: " Grid.Row="0" />
                        <TextBlock Text="{Binding FirstName}" Grid.Column="1" />
                        <TextBlock Text="Imię: " Grid.Row="1" />
                        <TextBlock Text="{Binding LastName}" Grid.Row="1" Grid.Column="1" />
                    </Grid>
                </DataTemplate>
            </DataGrid.RowDetailsTemplate>



Dodatkowe właściwości które możemy ustawić:
  • RowDetailsVisibilityMode - tryb wyświetlania wiersza szczegółów, możliwe tryby:
    • VisibleWhenSelected - domyślny tryb - wiersz widoczny dla zaznaczonego rekordu
    • Visible - widoczny dla wszystkich rekordów
    • Collapsed - brak widoczności wiersza szczegółów
  •  RowDetailsTemplateSelector - logika wyboru szablonu dla wiersza szczegółów
 Kody: http://sdrv.ms/UHcTum

wtorek, 25 grudnia 2012

70-511 Rozdział 8 - Bindowanie w Windows Forms

Windows Forms także umożliwia bindowanie danych do kontrolek. Mechanizm ten nie jest tak bardzo rozwinięty jak w przypadku WPF, jednak umożliwia obsłużenie najczęstszych scenariuszy.
Omówione zostaną także kontrolki DataGridView (Windows Forms) umożliwiająca wyświetlanie danych tabelarycznych.


Bindowanie proste
Bindowanie to polega na przypisaniu pojedynczemu elementowi wartości z danej kolumny tabeli.
Przykładowe przypisanie
Przykładowe przypisanie wartości kolumny FirstName do kontrolki Label:

Code:
            var customers = new DataService().GetSampleDataTable();
            label1.DataBindings.Add("Text", customers, "FirstName");


Bindowanie złożone
Występuje gdy więcej niż jedna kolumna (np. obiektu DataTable) jest bindowana do więcej niż jednej właściwości kontrolki. Przykładami mogą być tutaj DataGridView wyświetlający całą tabelę, bądź kontrolka List wyświetlająca więcej niż jedną kolumnę. Przykład bindowania do DataGridView w dwóch wersjach: za pomocą BindingSource oraz DataSet:

Code:
            var dataSet = new DataService().GetSampleDataSet();
            //1. Bindowanie do BindingSource
            var bindingSource = new BindingSource(dataSet, "Customers");
            dataGridView1.DataSource = bindingSource;

            //2. Bindowanie do DataSet
            dataGridView1.DataSource = dataSet;
            dataGridView1.DataMember = "Customers";



Nawigacja między rekordami
Dzięki kontrolce BindingNavigator można szybko stworzyć prostą nawigację po rekordach. Wystarczy przeciągnąć tę kontrolkę na formatkę i przypisać źródło danych do właściwości BindingSource:

Code:
bindingNavigator1.BindingSource = customersBindingSource;

Efekt:




DataGridView - Windows Forms
Kontrolka służąca do wyświetlania danych tabelarycznych pobranych najczęściej z bazy danych. Wyżej zostało przedstawione w jaki sposób bindować do niej dane.
DataGridView (w skrócie będę pisać DGV) posiada sześć wbudowanych typów kolumn:
  •  DataGridViewTextBoxColumn - typ wykorzystywany do wyświetlania danych łańcuchowych i numerycznych
  • DataGridViewCheckBoxColumn - wyświetlanie danych logicznych
  • DataGridViewImageColumn - wyświetlanie grafiki
  • DataGridViewButtonColumn - komórka, której zawartością jest przycisk
  • DataGridViewComboBoxColumn - zawiera kontrolkę ListBox 
  • DataGridViewLinkColumn - tworzy linki, np. do innych danych
  • Custom - jeżeli wbudowane typy kolumn nam nie wystarczają, możemy stworzyć naszą poprzez dziedziczenie z klasy DataGridViewColumn
 Podstawowymi operacjami wykonywanymi na kontrolce DGV są: dodawanie kolumn, usuwanie kolumn, pobieranie wartości aktualnie zaznaczonej komórki.
Przykłady tych operacji w C#

Code:
            //1. Dodawanie kolumny
            var firstNameDataColumn = new DataGridViewTextBoxColumn();
            firstNameDataColumn.Name = "FirstName";
            firstNameDataColumn.HeaderText = "Imię";
            firstNameDataColumn.ValueType = typeof (string);
            dataGridView2.Columns.Add(firstNameDataColumn);

            //2. Usuwanie kolumny
            dataGridView2.Columns.Remove("FirstName");

            //3. Informacja o aktualnie wybranej komórce
            var currentSelectedCell = dataGridView2.CurrentCell;
            var dataGridViewColumn = currentSelectedCell.OwningColumn; //Kolumna
            var dataGridViewRow = currentSelectedCell.OwningRow; //Wiersz
            var value = currentSelectedCell.Value; //wartość wybranej komórki

Komórki DGV mogą być edytowane przez użytkownika. Ważna jest więc walidacja danych wprowadzanych przez użytkownika. Walidację przeprowadzamy przez podpięcie się do zdarzenia CellValidating:

Code:
            dataGridView2.CellValidating += (sender, e) =>
                                                {
                                                    //Jeżeli kolumna byłaby zbindowana z źródłem danych wtedy zamaist właściwości Name, sprawdzamy DataPropertyName
                                                    if (dataGridView2.Columns[e.ColumnIndex].Name == "FirstName")
                                                    {
                                                        if (string.IsNullOrWhiteSpace(e.FormattedValue.ToString()))
                                                        {
                                                            dataGridView2.Rows[e.RowIndex].ErrorText =
                                                                "Pole imię musi być uzupełnione";
                                                            e.Cancel = true;
                                                        }
                                                        else
                                                        {
                                                            dataGridView2.Rows[e.RowIndex].ErrorText = null;
                                                        }
                                                    }
                                                };

Ważne jest tutaj, że  w przypadku gdy kolumna była tworzona przez nas (ma podaną nazwę) - używamy właściwości Name, a gdy DGV jest bindowany z obiektem typu DataSet należy skorzystać z właściwości DataPropertyName.


Komórki w których użytkownik błędnie wprowadził dane, zostaną oznaczone czerwoną ikoną wykrzyknika, a dodatkowo jeżeli na niego najedziemy otrzymamy zawartość pola ErrorText. 

Ciekawą opcją jest możliwość nadawania wyglądu poszczególnym komórką. Obsługa polega na przypięciu się do zdarzenia CellPainting i napisaniu odpowiedniej logiki. Przykład:

Code:
            dataGridView1.CellPainting += (sender, e) =>
                                              {
                                                  if (e.Value != null && e.Value.ToString() == "Liu")
                                                  {
                                                      e.Graphics.FillRectangle(Brushes.Aqua ,e.CellBounds);
                                                      e.Graphics.DrawString(e.Value.ToString(), e.CellStyle.Font, Brushes.Black, e.CellBounds.X, e.CellBounds.Y);
                                                      e.Handled = true;
                                                  }
                                              };




Kod: http://sdrv.ms/UHd8Wi