Pokazywanie postów oznaczonych etykietą WPF. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą WPF. Pokaż wszystkie posty

niedziela, 10 lutego 2013

Zapis danych z RadGridView do CSV

Bardzo często chcemy zapisać dane do pliku w formacie CSV. Format ten jest na tyle prosty, że wiele aplikacji umożliwia jego import. Nadaje się więc idealnie do przenoszenia danych pomiędzy aplikacjami.
Telerik w kontrolce RadGridView oferuje dwie metody zapisu danych do formatu CSV:


Code:
            string content = radGridView.ToCsv(true);
            using (var sw = new StreamWriter("data.csv"))
            {
                sw.Write(content);
            }

Sposób ten wykorzystuje metodę rozszerzającą ToCsv. Parametr przekazany do tej metody wskazuje czy nagłówek (nazwy kolumn) mają także zostać dodane do zawartości pliku.

Drugi sposób, umożliwiający między innymi ustawienie:
  • kultury
  • kodowania znaków
  • zapis nagłówków kolumn
  • stopki

Code:
            var gridViewExportOptions = new GridViewExportOptions();
            gridViewExportOptions.Format = ExportFormat.Csv;
            radGridView.Export(new FileStream("data.csv", FileMode.Create), gridViewExportOptions);

Kod przykładu do pobrania: http://sdrv.ms/WXAs5I

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

ś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

poniedziałek, 24 grudnia 2012

70-511 Rozdział 7 - Binding - Sortowanie, Grupowanie oraz Filtrowanie

Sortowanie
Sortowanie zbindowanych danych z kontrolką umożliwia interfejs ICollectionView. Po pobraniu do niego referencji mamy dostęp do kolekcji SortDescriptions, która zawiera obiekty typu SortDescription. Obiekty te definiują po której właściwości dotyczą oraz kierunek sortowania. Umieszczając więcej niż jedną definicję sposobu sortowania, będzie ono miało takie samo znaczenie jak w przypadku zapytań SQL: kolekcja najpierw zostanie posortowana według pierwszego parametru a następnie według drugiego.

Code:
            var collectionView = CollectionViewSource.GetDefaultView(DataContext);
            collectionView.SortDescriptions.Add(new SortDescription("Price", ListSortDirection.Ascending));
            collectionView.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));

Efekt:



Jeżeli bindujemy do źródła, które nie implementuje interfejsu IBindingList, możemy napisać własne porównywanie obiektów a następnie przypisać je do właściwości CustomSort.

Własny mechanizm porównywania opiera się stworzenie klasy która implementuje interfejs IComparer. następnie rzutujemy ICollectionView na ListCollectionView i przypisujemy do właściwości CustomSort obiekt naszej klasy:

Code:
            var collectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(DataContext);
            collectionView.CustomSort = new CustomComparer();



Grupowanie
Kolejną operacją którą oferuje ICollectionView jest grupowanie. Grupy tworzymy poprzez dodanie do kolekcji GroupDescriptions obiektu klasy PropertyGroupDescription. Aby zobaczyć efekt należy jeszcze stworzyć styl wyświetlania dla zgrupowanych obiektów GroupStyle:
Obiekt GroupStyle zawiera następujące właściwości:
  • ContainerStyle - styl dla grupy
  • ContainerStyleSelector - logika wyboru stylu dla grupy
  • HeaderTemplate - styl nagłówka dla grupy
  • HeaderTemplateSelector - logika wybierania stylu dla nagłówka grupy
  • Panel - styl dla panelu, na którym są prezentowane grupowane elementy

Prosty przykład grupowania książek na podstawie ich ceny:

Code:
        <ListBox ItemsSource="{Binding}" Name="lbBooks" ItemTemplate="{StaticResource BooksTemplate}">
            <ListBox.GroupStyle>
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <Label Content="{Binding Path=Name}" Foreground="Purple" Background="Red" Padding="4" />
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </ListBox.GroupStyle>
        </ListBox>

Code:
collectionView.GroupDescriptions.Add(new PropertyGroupDescription("Price"));


Efekt:


W HeaderTemplate Label bindowany jest do właściwości Name. Właściwość ta jest pobierana z obiektu klasy PropertyGroupDescription.



Filtrowanie
ICollectionView posiada wbudowany mechanizm filtrowania poprzez właściwość Filter. Jest to delegata ktra przyjmuje jako parametr obiekt i zwraca wartość logiczną.
Przykładowa implementacja, odfiltrowująca książki które kosztują ponad 5 zł:

Code:
            collectionView.Filter += o =>
                                         {
                                             var book = (Book) o;
                                             return book.Price < 5;
                                         };

Efekt:




Filtrowanie obiektów ADO.NET
Metoda wyżej opisana nie zadziała na obiekty związane z ADO.NET (DataSet, Table). Jest to spowodowane tym, iż tak naprawdę bindujemy się do obiektu typu DataView. DataView posiada własny mechanizm filtrowania. Możemy z niego skorzystać poprzez właściwość BindingListCollectionView.CustomFilter. Właściwość ta przyjmuje parametr jako łańcuch w formacie:
<ColumnName> <Operator> <Value>
Przykładowo jeżeli chcemy odfiltrować osoby których imię to Lucy, wystarczy napisać:
FirstName = 'Lucy'
W przypadku wartości łańcuchowych zamykamy je w apostrofach.
Przykład w kodzie:

Code:
            var customersCollectionView =
                (BindingListCollectionView)CollectionViewSource.GetDefaultView(((DataContextContent) DataContext).Customers);
            customersCollectionView.CustomFilter = "FirstName = 'Lucy'";


Kod prezentowanych przykładów: http://sdrv.ms/Wl1Yb7

niedziela, 23 grudnia 2012

70-511 Rozdział 7 - Binding - Hierarchical Data Templates

W tej części o szablonach dla danych hierarchicznych.

Hierarchical Data Templates
Wyświetlając hierarchiczne dane w kontrolkach takich jak TreeView czy Menu możemy użyć HierarchicalDataTemplate. Klasa ta posiada wszystkie właściwości znane z klasy DataTemplate oraz kilka dodatkowych właściwości:
  • ItemBindingGroup - wartość tej właściwości jest kopiowana do wszystkich podrzędnych elementów danego elementu 
  • ItemContainerStyle - ustawia styl dla każdego z elementów podrzędnych
  • ItemContainerStyleSelector - logika wybierania stylu dla elementów
  • ItemsSource - ścieżka do następnego poziomu elementów względem obecnego
  • ItemStringFormat - formatowanie tekstu
  • ItemTemplate - szablon elementu
  • ItemTemplateSelector - logika wybierania szablonu prezentacji
Dwa przykłady obrazujące w jaki sposób poradzić sobie z szablonami dla danych hierarchicznych (pełny kod źródłowy do pobrania z linka podanego poniżej):

Definicje klas dla przykładu z firmami:

Code:
    [Serializable]
    public class Company
    {
        public string Name { get; set;}

        public List<CompanyState> CompanyStates { get; set; }
    }

    [Serializable]
    public class CompanyState
    {
        public string Name { get; set; }

        public List<Unit> Units { get; set; }
    }

    public class Unit
    {
        public string Name { get; set; }

        public List<Department> Departments { get; set; }
    }

    public class Department
    {
        public string Name { get; set; }

        public List<Employee> Employees { get; set; }
    }

    public class Employee
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }


Definicje hierarchii:

Code:
<Window x:Class="HierarchicalDataTemplates.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:bindingDataTemplates="clr-namespace:Binding_DataTemplates;assembly=SampleDataProvider" Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <HierarchicalDataTemplate x:Key="TreeViewDataTemplate" ItemsSource="{Binding Friends}">
            <TextBlock Text="{Binding Name}"></TextBlock>
            <HierarchicalDataTemplate.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding FirstName}"/>
                            <TextBlock Text=" "></TextBlock>
                            <TextBlock Text="{Binding LastName}"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </HierarchicalDataTemplate.ItemTemplate>
        </HierarchicalDataTemplate>

        <HierarchicalDataTemplate x:Key="CompanyDataTemplate" ItemsSource="{Binding CompanyStates}" DataType="{x:Type bindingDataTemplates:Company}">
            <TextBlock Text="{Binding Name}" />
            <HierarchicalDataTemplate.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Units}">
                    <TextBlock Text="{Binding Name}" />
                    <HierarchicalDataTemplate.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding Departments}">
                            <TextBlock Text="{Binding Name}" />
                            <HierarchicalDataTemplate.ItemTemplate>
                                <HierarchicalDataTemplate ItemsSource="{Binding Employees}">
                                    <TextBlock Text="{Binding Name}" />
                                    <HierarchicalDataTemplate.ItemTemplate>
                                        <DataTemplate>
                                            <StackPanel Orientation="Horizontal">
                                                <TextBlock Text="{Binding Id}" />
                                                <TextBlock Text=" "></TextBlock>
                                                <TextBlock Text="{Binding FirstName}" />
                                                <TextBlock Text=" " />
                                                <TextBlock Text="{Binding LastName}" />
                                            </StackPanel>
                                        </DataTemplate>
                                    </HierarchicalDataTemplate.ItemTemplate>
                                </HierarchicalDataTemplate>
                            </HierarchicalDataTemplate.ItemTemplate>
                        </HierarchicalDataTemplate>
                    </HierarchicalDataTemplate.ItemTemplate>
                </HierarchicalDataTemplate>
            </HierarchicalDataTemplate.ItemTemplate>
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <TreeView ItemTemplate="{StaticResource TreeViewDataTemplate}" ItemsSource="{Binding StatesList}" Grid.Column="0" />
        <TreeView ItemTemplate="{StaticResource CompanyDataTemplate}" ItemsSource="{Binding Company}" Grid.Column="1" />
    </Grid>
</Window>

Efekt:



Cały kod aplikacji można pobrać z linku: http://sdrv.ms/ZCHejE