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

wtorek, 23 lipca 2013

[WPF] Grafika trójwymiarowa

Jedną z ciekawszych funkcjonalności WPF jest wsparcie dla grafiki trójwymiarowej. Funkcjonalności 3D są dostępne zarówno z poziomu XAML jak i kodu proceduralnego. Poniższy kod przedstawia prosty sposób opisu sceny 3D w sposób deklaratywny:


<Window x:Class="_3dGraphics.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>
        <Viewport3D>
            <Viewport3D.Camera>
                <OrthographicCamera Position="5,5,5" LookDirection="-1,-1,-1" Width="5"/>
            </Viewport3D.Camera>
            <Viewport3D.Children>
                <ModelVisual3D x:Name="Light">
                    <ModelVisual3D.Content>
                        <AmbientLight/>
                    </ModelVisual3D.Content>
                </ModelVisual3D>
                <ModelVisual3D>
                    <ModelVisual3D.Content>
                        <Model3DGroup x:Name="House">
                            <GeometryModel3D x:Name="Roof">
                                <GeometryModel3D.Material>
                                    <DiffuseMaterial Brush="Blue"/>
                                </GeometryModel3D.Material>
                                <GeometryModel3D.Geometry>
                                    <MeshGeometry3D Positions="-1,1,1 0,2,1 0,2,-1 -1,1,-1 0,2,1 1,1,1
      1,1,-1 0,2,-1"
      TriangleIndices="0 1 2 0 2 3 4 5 6 4 6 7"/>
                                </GeometryModel3D.Geometry>
                            </GeometryModel3D>
                            <GeometryModel3D x:Name="Sides">
                                <GeometryModel3D.Material>
                                    <DiffuseMaterial Brush="Green"/>
                                </GeometryModel3D.Material>
                                <GeometryModel3D.Geometry>
                                    <MeshGeometry3D Positions="-1,1,1 -1,1,-1 -1,-1,-1 -1,-1,1 1,1,-1
       1,1,1 1,-1,1 1,-1,-1"
       TriangleIndices="0 1 2 0 2 3 4 5 6 4 6 7"/>
                                </GeometryModel3D.Geometry>
                            </GeometryModel3D>
                            <GeometryModel3D x:Name="Ends">
                                <GeometryModel3D.Material>
                                    <DiffuseMaterial Brush="Red"/>
                                </GeometryModel3D.Material>
                                <GeometryModel3D.Geometry>
                                    <MeshGeometry3D
       Positions="-0.25,0,1 -1,1,1 -1,-1,1 -0.25,-1,1 -0.25,0,1
       -1,-1,1 0.25,0,1 1,-1,1 1,1,1 0.25,0,1 0.25,-1,1 1,-1,1
       1,1,1 0,2,1 -1,1,1 -1,1,1 -0.25,0,1 0.25,0,1 1,1,1 1,1,-1
       1,-1,-1 -1,-1,-1 -1,1,-1 1,1,-1 -1,1,-1 0,2,-1"
       TriangleIndices="0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 15
       17 18 19 20 21 19 21 22 23 24 25"/>
                                </GeometryModel3D.Geometry>
                            </GeometryModel3D>
                        </Model3DGroup>
                    </ModelVisual3D.Content>
                </ModelVisual3D>
            </Viewport3D.Children>
        </Viewport3D>
    </Grid>
</Window>

Zastosowane powyżej elementy XAML służą do:

  • GeometryModel3D - reprezentuje kształt i materiał obiektu 3D, kształt w powyższym przypadku jest siatką, w której podaje się współrzędne trójkątów oraz ich indeksy.
  • ModelVisual3D - odpowiada za renderowanie contentu 3D
Położenie kamery oraz obiektów na scenie podaje się, pamiętając o układzie osi w kartezjańskim układzie współrzędnych.
Układ współrzędnych jest prawoskrętny, a pozycję podaje się ustawiając wartości na atrybucie Position.
Pozostałe ważne atrybuty to LookDirection (kierunek, w którym ma być obrócona kamera), oraz UpDirection decydujący o obrocie kamery wokół osi z. 

Rodzaje kamer:
  • PerspectiveCamera - odpowiada postrzeganiu przestrzeni przez ludzkie oko, obiekty znajdujące się daleko od kamery wydają się mniejsze, niż te bliższe
  • OrthographicCamera - obiekty pozostają tej samej wielkości niezależnie od odległości
Szerokość pola widzenia kontrolowana jest przez atrybut Width (dla drugiej kamery) i przez atrybut FieldOfView (dla pierwszej kamery).

Transformacje 3D:

Poniżej przedstawiono, jak wykonać trzy podstawowe transformacje z poziomu kodu proceduralnego


private void KeyDownHandler(object sender, KeyEventArgs e)
{
    Transform3DGroup group = new Transform3DGroup();
    switch (e.Key)
    {
        case Key.T:
            TranslateTransform3D transform3D = new TranslateTransform3D(0.1,0,0);
            group.Children.Add(transform3D);
            House.Transform = group;
            break;
        case Key.R:
            //45 - stopnie, nie radiany, new Vector3D(0, 1, 0) - wokol jakiej osi obrot
            RotateTransform3D rotate3d = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), 45));
            group.Children.Add(rotate3d);
            House.Transform = group;
            break;
        case Key.S:
            ScaleTransform3D scale3d = new ScaleTransform3D(0.5,0.5,1);
            group.Children.Add(scale3d);
            House.Transform = group;
            break;
        default:
            break;
    }
}

Rodzaje świateł:

  • DirectionalLight - wysyła równoległe wiązki światła ze źródła umieszczonego nieskończenie daleko, odpowiada słońcu
  • PointLight - odpowiada żarówce, wysyła światło we wszystkich kierunkach, intensywność spada wraz ze wzrostem odległości od źródła
  • FlashLight - odpowiada działaniu latarki, emituje stożek światła, którego intensywność spada wraz ze wzrostem odległości od źródła
  • AmbientLight - światło rozproszone

wtorek, 26 lutego 2013

[WPF] ExpressionBlend: SampleData

Tworząc w WPF interfejs użytkownika, często potrzebujemy przykładowych kolekcji danych, np. wyświetlanych w ListBoxie. Korzystając z Blenda, można w prosty sposób wygenerować kolekcje przykładowych danych, składających się z propercji różnych typów.Rozpocząć należy od zakładki Data, widocznej na przykład w prawym górnym rogu okna designera.


W drugim kroku należy dodać kolekcję i zbudować odpowiedni model danych, a więc dodać properties interesującego nas typu.






Jedną z fajniejszych opcji jest możliwość dodawania Stringów różnego typu, w zależności od potrzeb aplikacji.


Dane należy następnie podłączyć jako ItemSource...


a w ListBoxTemplate  zbindować odpowiednie kontrolki od odpowiednich propercji, wybierając z menu dostępnego pod prawym przyciskiem myszy opcję Data bind Content to Data..

Jeżeli to nie wystarcza, w każdej chwili można utworzyć przykładowe dane w oparciu o własny plik XML z danymi.


Po dodaniu przykładowych danych warto także spojrzeć do folderu SampleData, tworzonego przez Blenda. Tworzona jest automatycznie klasa wspierająca INotifyPropertyChanged, dzięki czemu wszystkie zmiany modelu danych będą odświeżały UI i na odwrót.

czwartek, 31 stycznia 2013

[WPF] Caliburn.Micro: Window Manager

Window Manager, jak nazwa wskazuje, służy do zarządzania trzema rodzajami okienek. Serwis ten dodawany jest do kontenera IoC w bootstrapperze przy zainstalowaniu Caliburna w naszym projekcie. Dostępne rodzaje okienek to:
  • Window - niezależne osobne okno z paskiem do zamykania, minimalizowania itd.
  • Popup - nie blokuje okna - rodzica, brak paska do zamykania
  • Dialog - blokuje okno - rodzic, dostępny pasek do zamykania
Każde osobne okienko powinno być osobnym widokiem z własnym ViewModelem. W ViewModelu można obsłużyć zdarzenie zamknięcia okienka.

public class PopupPersonViewModel : Screen
{
    public Person Entity { get; set; }

    public PopupPersonViewModel(Person person)
    {
        Entity = person;
    }

    public override void TryClose(bool? dialogResult)
    {
        IoC.Get<IEventAggregator>().Publish(new PersonUpdatedMessage(){Entity = Entity});
        base.TryClose(dialogResult);
    }
}

Samo otwarcie okna jest bardzo proste:

public void OpenWindow()
{
    dynamic settings = new ExpandoObject();
    settings.WindowStartupLocation = WindowStartupLocation.Manual;
    _windowManager.ShowWindow(new PopupPersonViewModel(Entity), null, settings);
}

środa, 30 stycznia 2013

[WPF] Caliburn.Micro: Akcje

W tym poście nieco na temat sposobów, jak powiązać akcje użytkownika z widoku z handlerami z ViewModelu.

Najprostszy sposób to binding przez konwencje nazw, ale sposób ten nie zawsze wystarcza. Caliburn.Micro, dla niektórych wybranych kontrolek, wiąże atrybut x:Name (z Xaml) z funkcją o identycznej nazwie z ViewModelu. W ten sposób nie jesteśmy jednak w stanie sprecyzować, które zdarzenie z kontrolki nas interesuje. Twórcy uznali, że np. dla przycisku będzie to zdarzenie Click, które zdecydowanie najczęściej ma nieść za sobą określone akcje użytkownika. Zainteresowani, mogą przejrzeć domyślne bindingi w klasie ConventionManager.cs ze źródeł projektu Caliburn.Micro.

Inny bardziej złożony, ale dający więcej możliwości sposób wykorzystuje Triggery.

<Button Content="Print Name">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <cal:ActionMessage MethodName="PrintName" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

Dzięki konwencjom możemy sterować dostępnością przycisku. Wystarczy wprowadzić property o nazwie takiej samej jak akcja, lecz zawierającej z przodu człon Can. Property takie musi zwracać typ bool i być notyfikowane przy odpowiednich zmianach w modelu danych. Na przykład:

public class PersonDetailsViewModel : Screen, IHandle<NewPersonMessage>
{
    public bool CanPrintName
    {
        get { return Entity != null; }
    }

    public void Handle(NewPersonMessage message)
    {
        Entity = message.Entity;
        NotifyOfPropertyChange(() => CanPrintName);
    }

    public void PrintName()
    {
        MessageBox.Show(Entity.Name);
    }
}

Ostatnim sposobem zapisu, krótszym i umożliwiającym przekazywanie parametrów w prostszy sposób jest Message.Attach. Tutaj w pełnym zapisie możemy wybrać, które zdarzenie nas interesuje, oraz co chcemy przekazać przez parametr.

<Slider x:Name="Entity_Age" Maximum="100" VerticalAlignment="Stretch"
    cal:Message.Attach="[Event MouseEnter] = [Action IncrementAge($eventArgs)]" />

Poza eventArgs można przekazać kilka innych obiektów, między innymi związanych z data bindingiem. Opisane są w dokumentacji.

wtorek, 29 stycznia 2013

[WPF] Caliburn.Micro: EventAggregator

Do komunikacji pomiędzy ViewModelami, Caliburn Micro wykorzystuje EventAggregatora. Tworzenie pojedynczej instancji ma miejsce w klasie AppBootstrapper (dodawanej do naszego projektu podczas instalacji Caliburna w projekcie), a konkretnie w funkcji Configure.

protected override void Configure() {
    var catalog = new AggregateCatalog(
        AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
        );

    container = new CompositionContainer(catalog);

    var batch = new CompositionBatch();

    batch.AddExportedValue<IWindowManager>(new WindowManager());
    batch.AddExportedValue<IEventAggregator>(new EventAggregator());
    batch.AddExportedValue(container);
    batch.AddExportedValue(catalog);

    container.Compose(batch);
}

W ViewModelach EventAggregator dostępny jest dzięki kontenerowi IoC. Dobrą praktyką jest, aby wiadomości nadawane przez EventAggregatora były osobnych, dedykowanych w tym celu typów. Aby nadać taką wiadomość wystarczy w danym ViewModelu:

var person = ea.AddedItems[0] as Person;
var msg = new NewPersonMessage() {Entity = person};
var evag = IoC.Get<IEventAggregator>();
evag.Publish(msg);

Po stronie subskrybenta należy implementować interfejs IHandle<T> z konkretnym typem wiadomości obsługiwanym w funkcji Handle(T).

public class PersonDetailsViewModel : Screen, IHandle<NewPersonMessage>
{
    public PersonDetailsViewModel()
    {
        var ea = IoC.Get<IEventAggregator>();
        ea.Subscribe(this);
    }

    public void Handle(NewPersonMessage message)
    {
        Entity = message.Entity;
    }
}

niedziela, 27 stycznia 2013

[WPF] Caliburn.Micro: Kolekcje

Podczas bindowania kolekcji najpierw należy zdefiniować model danych. Przykładowa klasa reprezentująca dane osoby.

public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
    public bool IsMale { get; set; }
}

Następnie w ViewModelu definiujemy obserwowalną kolekcję, która zapewni nam synchronizację widoku z modelem danych.

public class PersonListViewModel : Screen
{
    public ObservableCollection<Person> Items { get; set; }

    private Person _selectedItem;
    public Person SelectedItem
    {
        get { return _selectedItem; }
        set { _selectedItem = value; }
    }

    public PersonListViewModel()
    {
        Items = new ObservableCollection<Person>
        {
            new Person{ Age = 15, IsMale = true, Name= "Michael"},
            new Person{ Age = 80, IsMale = true, Name= "Steven"},
            new Person{ Age = 25, IsMale = false, Name= "Kate"},
            new Person{ Age = 64, IsMale = true, Name= "Matthew"}

        };
    }
}

Dodatkowo, dzięki konwencjom, property SelectedItem będzie synchronizowane z wybranym elementem z kolekcji Items.
Ostatni krok to binding w ViewModelu. Kolekcje można wyświetlać w kilku elementach WPFa. Poniżej pokazano, jak to zrobić w ListBoxie.

<ListBox x:Name="Items" 
         cal:Message.Attach="[Event SelectionChanged] = [Action NewSelectedItem($eventargs)]"
         SelectionMode="Single">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Width="200" Height="100" cal:Bind.Model="{Binding}" >
                <TextBlock x:Name="Name" VerticalAlignment="Top" />
                <Label x:Name="Age" VerticalAlignment="Top" />
                <CheckBox x:Name="IsMale" VerticalAlignment="Top" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Podobnie jak w przypadku skalarnych propercji, zaczynamy od ustawienia atrybutu x:Name. Dzięki cal:Message.Attach możemy przypisywać do zdarzeń widoku funkcje z ViewModelu. Następnie dla całego ListBoxa definiuje się ItemTemplate, w którym na kontenerze zaznaczamy binding do propercji poprzez atrybut cal:Bind.Model. Poziom niżej w drzewie wizualnym możemy już bindować się do pojedynczych propercji poprzez standardowe x:Name.

sobota, 26 stycznia 2013

[WPF] Caliburn.Micro: Wprowadzenie

Caliburn.Micro to prosty a zarazem dający wiele możliwości framework dla aplikacji tworzonych w oparciu o XAML i wzorzec MVVM. Aby skorzystać z Caliburna należy doinstalować go do naszego projektu przy pomocy managera pakietów NuGet z VisualStudio.



Jedną z najlepszych funkcjonalności jest data binding rozwiązany poprzez konwencje. Aby powiązać ze sobą View oraz ViewModel, programista nie musi dopisywać żadnych specjalnych instrukcji. Wszystko zapewnia nam zachowanie odpowiedniej konwencji nazw dla naszych klas. Przykładowo, domyślny widok startowy po zainstalowaniu Caliburna to ShellView. Aby bindować do niego dane jako propercje ViewModelu wystarczy nazwać go zamieniając View na ViewModel.

<Window x:Class="CaliburnMicroSampleWPF.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cal="http://www.caliburnproject.org"
        Width="400" Height="400"
        >
 <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        
        <ContentControl x:Name="Item" Grid.Column="0">            
        </ContentControl>
        <ContentControl x:Name="Details" Grid.Column="1">            
        </ContentControl>
    </Grid>
</Window>


[Export(typeof(IShell))]
    public class ShellViewModel :  Screen, IShell 
    {
        private object _item;
        public object Item 
        {
            get { return _item; }
            set
            {
                _item = value;
                NotifyOfPropertyChange("Item");
            }
        }
        public object Details { get; set; }

        public ShellViewModel()
        {
            Item = new PersonListViewModel();
            Details = new PersonDetailsViewModel();
        }
    }

Elementowi z XAML, który ma ustawiony atrybut x:Name, odpowiada property z ViewModelu o takiej samej nazwie. Tak działa data binding w Caliburnie. Powyższy kod pokazuje dodatkowo, jak wstawiać podwidoki do widoku okna. Wystarczy po stronie widoku skorzystać z ContentControl, a po stronie ViewModelu stworzyć properties typu object. To wystarczy do zbindowania danych, natomiast wstawieniem konkretnego podwidoku można sterować poprzez przypisywanie do propercji odpowiednich ViewModeli.

O tym, który widok będzie wyświetlany przy starcie aplikacji decyduje klasa AppBootstrapper.cs, a konretnie jej nagłówek:

public class AppBootstrapper : Bootstrapper<IShell>

W tym przypadku uruchamiany będzie widok, którego ViewModel dziedziczy z interfejsu IShell.

niedziela, 2 września 2012

WPF

Spis postów na temat Windows Presentation Foundation

Podstawy WPF



Expression Blend


Caliburn.Micro


Inne

[WPF] Storyboardy wywoływane rekurencyjnie

Czasami może zdarzyć się taka sytuacja, że mamy nagrany jeden storyboard i chcemy go kilkukrotnie wywołać, zmieniając "w locie" jakiś parametr. Może skorzystać z callbacku, po zakończeniu każdego wywołania.


int storyBoardsCounter = 0;
var sBoard = Resources["LogoPassage"] as Storyboard;
sBoard.Completed += (a, b) =>
            {
                Thread.Sleep(200);
                storyBoardsCounter++;
                if (storyBoardsCounter > 19)
                {
     //akcje po zakonczeniu serii
                }
                else
                {
                    //operacje miedzy storyboardami
                    sBoard.Stop();
                    sBoard.Begin();
                }
            };
sBoard.Begin();

[WPF] Dispatcher : opóźnione wykonanie metody

W przypadku, gdy chcemy, aby dispatcher odczekał określony przez nas czas, a następnie wykonał określoną metodę, możemy skorzystać z klasy ThreadPool.

Poniższa ExtensionMethod rozwiązuje problem opóźnionego wywołania dispatchera.


internal static class Extensions
{
    public static void DelayedInvoke(this Dispatcher dispatcher, int delayMs, Action action)
    {
        ThreadPool.QueueUserWorkItem((o) =>
                                            {
                                                Thread.Sleep(delayMs);
                                                dispatcher.Invoke(action);
                                            });
    }
}


czwartek, 30 sierpnia 2012

[WPF] ControlTemplate

Choć jesteśmy przyzwyczajeni do WPF - owych kontrolek w takiej postaci, w jakiej występują one w naszym systemie operacyjnym, nie jesteśmy w żaden sposób ograniczeni do takiego ich wyglądu. Aby w pełni zreorganizować wygląd kontrolek, należy zdefiniować dla nich ControlTemplate. Dzięki temu nie stracimy dostępu do DependencyProperties czy Eventów charakterystycznych dla danej kontrolki, a nadamy jej unikalny wygląd. Mechanizm TemplateBinding umożliwia zbindowanie się do property z oryginalnej kontrolki.


<Window.Resources>
    <ControlTemplate x:Key="TriangleButton">
        <Grid Width="100" Height="100">
            <StackPanel Orientation="Vertical">
            <Path Data="M 30 10 L 50 40 10 40 Z"
                StrokeThickness="48"
                StrokeLineJoin="Round"
                Stroke="Blue"
                Fill="Blue" />
                <TextBlock FontSize="15" 
                            HorizontalAlignment="Left"
                            Text="{TemplateBinding Property=Button.Content}" />
            </StackPanel>
        </Grid>
            
    </ControlTemplate>
</Window.Resources>
<Grid>
    <Button Content="Exit" 
            Template="{StaticResource TriangleButton}" 
            Click="Button_Click" />
</Grid>

Efekt :


wtorek, 28 sierpnia 2012

[WPF] Triggers

Triggery stanowią kolejny bardzo użyteczny mechanizm WPF. Pojedynczy Trigger składa się z zestawy setterów, podobnie jak styl, oraz z warunku, jaki musi zostać spełniony, aby te settery zostały zastosowane.Zatem dzięki zastosowaniu triggerów, mamy okazję przenieść sporo kodu dotyczącego wyglądu z code behind do xaml. Wprowadzono trzy rodzaje triggerów :

  • Property Trigger

Warunek wyzwolenia takiego triggera zależy od tego, czy któreś dependency property osiągnęło pewną wartość. W momencie, gdy property ponownie zmieni wartość, zmiany zostaną cofnięte. Przykład :

<Style TargetType="{x:Type Button}">
    <Setter Property="Background" Value="Red" />
    <Setter Property="Foreground" Value="White" />
    <Setter Property="BorderThickness" Value="2" />
    <Setter Property="BorderBrush" Value="Blue" />
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="Green" />
        </Trigger>
    </Style.Triggers>
</Style>

  • Data Trigger

Działa podobnie jak Property Trigger, ale może być wyzwolony przez dowolne .NETowe property. Przez to obowiązuje nieco trudniejsza składa przy definiowaniu tego Triggera. Przykładowo tworząc w CodeBehind property IsHidden, możemy sterować widocznością TextBlocka.


  public bool IsHidden
        {
            get { return _isHidden; }
            set
            {
                _isHidden = value;
                Debug.WriteLine(value);
                if(PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("IsHidden"));
            }
        }

Teraz można zdefiniować DataTrigger.


<Style TargetType="{x:Type TextBlock}" >
    <Style.Triggers>
        <DataTrigger Value="True"
            Binding="{Binding ElementName=Window,Path=IsHidden}">
            <Setter Property="Visibility" Value="Collapsed" />
        </DataTrigger>
    </Style.Triggers>
</Style>

  • EventTrigger

Głównym ich zastosowaniem jest wyzwalanie animacji zapisanych w storyboardach w momencie, gdy zajdzie jakieś zdarzenie. Mając nagrany w Resources storyboard, możemy się posłużyć następującą składnią:


<Window.Triggers>
    <EventTrigger RoutedEvent="FrameworkElement.Loaded">
        <BeginStoryboard Storyboard="{StaticResource MyStoryboard}"/>
    </EventTrigger>
</Window.Triggers>

Dodatkowo często chcemy stworzyć bardziej złożoną logikę, niż zajście pojedynczego warunku. Mamy do dyspozycji konstrukcje będące ekwiwalentem logicznych funkcji AND i OR.

  • AND

Narzędziem, służącym do zbudowania tej funkcji logicznej jest MultiTrigger.


<Style.Triggers>    
    <MultiTrigger>
        <MultiTrigger.Conditions>
            <Condition Property="IsMouseOver" Value="True" />
            <Condition Property="IsVisible" Value="True" />
        </MultiTrigger.Conditions>
        <MultiTrigger.Setters>
            <Setter Property="Foreground" Value="Black"/>
        </MultiTrigger.Setters>
    </MultiTrigger>
</Style.Triggers>

  • OR

Aby stworzyć funkcję OR, można dodać kilka Triggerów obok siebie z tymi samymi setterami. Taka konstrukcja będzie odpowiadała alternatywie.

poniedziałek, 27 sierpnia 2012

[WPF] Implicit Styles

Podczas stylowania kontrolek czasami ciężko jest uniknąć ważnej zasady DRY - Don't Repeat Yourself. WPF daje sporo sposobów, na które możemy ostylować nasze kontrolki. Jednym z nich, bardzo wygodnym i zapewniającym reużywalność naszego stylu jest stylowanie domniemane. Idea polega na tym, że definiujemy styl dla danego typu kontrolki. Jeśli nie nadpiszemy określonych property to będą one ustawiane tak, jak w implicit style.

Przykładowo gdy chcemy, aby wszystkie przyciski w obrębie aplikacji były o określonej kolorystyce, dopisujemy styl do Application.Resources.


<Application.Resources>
    <Style TargetType="{x:Type Button}">
        <Setter Property="Background" Value="Purple" />
        <Setter Property="Foreground" Value="White" />
        <Setter Property="BorderThickness" Value="2" />
        <Setter Property="BorderBrush" Value="White" />
    </Style>
</Application.Resources>

Dzięki temu, każdy przycisk będzie domyślnie wyglądał jak powyżej.

[WPF] ValidationRule

Bindując się do kontrolek interfejsu użytkownika, powinno się z góry założyć, że wprowadzone mogą zostać niepoprawne dane. W niektórych przypadkach możemy takie dane przyjąć, a w innych możemy wymusić na użytkowniku wpisanie poprawnych danych. Pomocne w tym przypadku stają się reguły walidacyjne. Reguła walidacyjna jest warstwą pomiędzy źródłem i celem bindingu, która zawiera logikę sprawdzającą poprawność danych.

Aby zbudować swoją regułą, wystarczy stworzyć klasę dziedziczącą po ValidationRule.

 public class EmailValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            Debug.WriteLine("Validation Rule Active : " + value.ToString());
            var str = value.ToString();
            Regex regex = new Regex(@"^([\w.-]+)@([\w-]+)((.(\w){2,3})+)$",RegexOptions.IgnoreCase);
            var match = regex.Match(str);
            return match.Success ? new ValidationResult(true, null) : 
                new ValidationResult(false, "Email Address is not valid");
        }
    }

A następnie dodać instancję, do databindingu.

<TextBox Name="textBox8" >
    <TextBox.Text>
        <Binding>
            <Binding.ElementName>
                main
            </Binding.ElementName>
            <Binding.Path>
                Email
            </Binding.Path>
            <Binding.ValidationRules>
                <local:EmailValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Metoda Validate wywoła się w momencie próby aktualizacji źródła danych w przypadku Bindingu OneWay.

niedziela, 26 sierpnia 2012

[WPF] DataBinding : Mode, UpdateSourceTrigger

Data binding może być stosowany na wiele sposobów. WPF umożliwia określenie trybu, oraz sposobu uaktualniania danych. Przez tryb rozumiany jest głównie kierunek przepływu danych. Ustawia się go przez property Mode.

 <TextBox  Text="{Binding Mode=OneWay, ElementName=main, Path=MyText1}" />
 <TextBox  Text="{Binding Mode=TwoWay, ElementName=main, Path=MyText2}" />
 <TextBox  Text="{Binding Mode=OneWayToSource,ElementName=main, Path=MyText3}" />
 <TextBox  Text="{Binding Mode=OneTime,ElementName=main, Path=MyText4}" />


Tryby data bindingu :
  • OneWay
Cel jest aktualizowany przy każdej zmianie źródła, zmiany celu (np. zmiana teksu w TextBoxie) pozostaje bez wpływu na stan źródła.
  • TwoWay
Zarówno zmiana źródła danych, jak i celu spowoduje zmiany po drugiej stronie.
  • OneWayToSource
Odwroność OneWay. Zmiana celu powoduje zmianę źródła danych, w drugą stronę zmiany nie zachodzą.
  • OneTime
Target pobiera dane ze źródła w momencie bindowania, następnie połączenie zostaje niejako zerwane: zmiany źródła nie aktualizują celu, podobnie zmiany celu nie powodują zmian w źródle.

Jeśli chodzi o domyślny tryb, to dla większości DependencyProperties jest to OneWay. Wyjątkiem jest np. TextBox.Text, gdzie domyślnie ustawiony jest tryb TwoWay.

Drugą użyteczną opcją, jest UpdateSourceTrigger. Można w ten sposób ustawić, kiedy ma dojść do aktualizacji danych.


<TextBox Text="{Binding Mode=TwoWay, ElementName=main, Path=MyText1, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Mode=TwoWay, ElementName=main, Path=MyText1, UpdateSourceTrigger=LostFocus}" />
<TextBox Text="{Binding Mode=TwoWay, ElementName=main, Path=MyText1, UpdateSourceTrigger=Explicit}" />

  • PropertyChanged
Aktualizacja następuje przy każdej zmianie property. Oznacza to, że np. wpisując do pustego TextBoxa słowo kot, nastąpią trzy zmiany źródła : przy dopisaniu każdej litery.
  • LostFocus
Domyślny tryb dla TextBox.Text. Zmiana zachodzi, gdy kontrolka będąca targetem straci focus. Oszczędza się wtedy niepotrzebnych zmian źródła.
  • Explicit
W tym przypadku, musimy jawnie aktualizować źródło w następujący sposób:


var bindingExpr = textBox7.GetBindingExpression(TextBox.TextProperty);
bindingExpr.UpdateSource();

[WPF] HierarchicalDataTemplate

Podczas stosowanie data bindingu, dane mogą być nam dostarczone w różnej postaci. Mogą to być pojedyncze wartości, kolekcje przeróżnych typów, ale często zdarzy się, że są dostarczone w postaci hierarchicznej. WPF ułatwia bindowanie się do plików XML, za pomocą specjalnych mechanizmów.Pierwszy z nich do XmlDataProvider zapewniający binding i notyfikację każdej zmiany, jaka zajdzie po obu stronach. Kolejnym jest HierarchicalDataTemplate, który umożliwia binding do hierarchicznej struktury.

Mając przykładowe dane w pliku Countries.xml:


<?xml version="1.0" encoding="utf-8" ?>
<Countries>
  <Country Name="Poland">
    <CapitalCity>Warsaw</CapitalCity>
    <Voivodeships>
      <Voivodeship Name="Masovian">
        <Cities>
          <City>Plock</City>
          <City>Radom</City>
          <City>Warsaw</City>
        </Cities>
      </Voivodeship>
      <Voivodeship Name="Lesser Poland">
        <Cities>
          <City>Cracow</City>
          <City>Tarnow</City>
        </Cities>
      </Voivodeship>
      <Voivodeship Name="Greater Poland">
        <Cities>
          <City>Kalisz</City>
          <City>Poznan</City>
        </Cities>
      </Voivodeship>      
    </Voivodeships>  
</Country>
</Countries>

Możemy do resourców dodać XmlProvider z ścieżką do pliku.

<XmlDataProvider Source="Countries.xml" XPath="Countries" x:Key="MyProvider" />

A następnie, również w resourcach dodać hierarchiczny szablon danych.


<HierarchicalDataTemplate DataType="Countries" ItemsSource="{Binding XPath=*}">
    <TextBlock Text="Countries" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Country" ItemsSource="{Binding XPath=*}">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding XPath=@Name}" />
        <TextBlock Text=", Capital City : " />                
        <TextBlock Text="{Binding XPath=CapitalCity}" />
    </StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Voivodeships" ItemsSource="{Binding XPath=*}">
    <TextBlock Text="Voivodeships :" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Voivodeship" ItemsSource="{Binding XPath=*}">
    <TextBlock Text="{Binding XPath=@Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Cities" ItemsSource="{Binding XPath=*}">
    <TextBlock Text="Cities" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="City" >
    <TextBlock Text="{Binding XPath=.}" />
</HierarchicalDataTemplate>

Ostatecznie możemy zbindować dane z providera do kontrolki TreeView.


 <TreeView ItemsSource="{Binding Source={StaticResource ResourceKey=MyProvider}, XPath=.}"  />

I tym sposobem możemy przeglądać drzewo z naszymi danymi zapisanymi w takiej hierarchii, jak w pliku XML

sobota, 25 sierpnia 2012

[WPF] ICollectionView

WPF oferuje mechanizm wspierający podstawowe standardy dotyczące wyświetlania kolekcji, takie jak filtrowanie, sortowanie czy grupowanie. Mechanizm jest następujący : w momencie, gdy następuje data binding do obiektu typu IEnumerable, tworzony jest pomiędzy źródłem a celem tworzony jest niejawnie widok. Jest nim obiekt implementujący interfejs ICollectionView. Wspiera on wspomniane wcześniej operacje.
  • Sortowanie
Tworzymy obiekt klasy SortDescription, gdzie jako parametry konstruktora podawane jest property, po którym ma się odbyć sortowanie, oraz kierunek (rosnąco lub malejąco).


var sort1 = new SortDescription("Surname", ListSortDirection.Descending);   
c1.Items.SortDescriptions.Add(sort1);

  • Grupowanie
Aby móc w pełni wykorzystać grupowanie, należy nieco rozszerzyć wygląd ListBoxa o to, jak prezentowany będzie nagłówek grupowania.

<ListBox.GroupStyle>
    <GroupStyle>
        <GroupStyle.HeaderTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Label Content="Age = " />
                    <Label Content="{Binding}"/>
                </StackPanel>                                
            </DataTemplate>
        </GroupStyle.HeaderTemplate>
    </GroupStyle>
</ListBox.GroupStyle>

Po dodaniu wyglądu nagłówka, można przejść do dodania mechanizmu grupującego. Najbardziej przydatne wydaje się być grupowanie po property.


var group1 = new PropertyGroupDescription("Age");
c2.Items.GroupDescriptions.Add(group1);

  • Filtrowaanie
 Aby móc filtrować kolekcję należy stworzyć delegata zwracającego wartość logiczną.


c3.Items.Filter = o =>
                        {
                            var p = o as Person;
                            if (p == null)
                                return false;
                            return p.Age > 27;
                        };



piątek, 24 sierpnia 2012

[WPF] Data Binding : Value Converter

Używając data bindingu może zajść potrzeba zbindowania dwóch różnych typów. Innym razem możemy chcieć dodać pewne reguły walidacji bindowanych danych, np. saturację wartości liczbowych. Te zadania wymagają stworzenia własnego Value Convertera - specjalnej klasy, zawierającej logikę konwertującą, czy walidującą.

W pierwszym kroku należy zdefiniować klasę implementującą interfejs IValueConverter.

 public class DigitToBrushConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            float res = 0;
            bool valid = float.TryParse(value.ToString(), out res);
            if(valid)
            {
                var props = typeof(Colors).GetProperties();
                try
                {
                    return new SolidColorBrush(Color.FromRgb((byte)res, 0, 0));
                }
                catch (Exception)
                {
                    return new SolidColorBrush(Color.FromRgb(0, 0, 0));
                }
            }
            return Brushes.Wheat;

        }

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

Jeżeli interesuje nas binding w jedną stronę, metody ConvertBack nie trzeba implementować.

Następnie należy dodać do zasobów okna nasz konwerter.

<Window.Resources>
        <local:DigitToBrushConverter x:Key="myConverter" />
</Window.Resources>

Utworzona zostanie instancja konwertera, którą można przywoływać, jako StaticResource.

W końcu można przejść w xaml do stworzenia bindingu.


<Slider x:Name="colorSlider"  />
<Button Background="{Binding ElementName=colorSlider, Converter={StaticResource myConverter}, Path=Value}" 
Content="BoundBackground" />
   

Dodatkową opcją jest ConverterParameter - obiekt, który będzie przekazany do metody konwertera.jako parameter.

[WPF] Data Binding : Relative Source

Ustawiając w xaml data binding, możemy określić źródło korzystając z RelativeSource. Istnieje kilka sytuacji, w których warto użyć tej opcji.

  • Gdy chcemy, aby źródło i cel bindingu były tą samą kontrolką, np. zmiana wartości slidera powoduje zmianę jego nieprzezroczystości.
<Slider Minimum="0.1" Maximum="1" TickFrequency="0.1"
   Value="{Binding Path=Opacity, RelativeSource={RelativeSource Self}}" />

  • Podczas tworzenia CustomControl w WPF, dodajemy do niej styl w pliku Generic.xaml. Custom control może składać się z wielu mniejszych kontrolek i gdy chcemy aby taka kontrolka bindowała się do property z CustomControl, używamy RelativeSource TemplatedParent.

<Style TargetType="{x:Type local:MySimpleCustomControl}">
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MySimpleCustomControl}">
                    <Grid Margin="3">                     
                        <Border BorderThickness="1" BorderBrush="Gray" 
                            Margin="2" Grid.RowSpan="2" 
                            VerticalAlignment="Center" HorizontalAlignment="Stretch">
                            <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Margin}" 
                                Width="60" TextAlignment="Right" Padding="5"/>
                        </Border>                      

                    </Grid>

                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

...możemy stworzyć instancję takiej kontrolki, ustawić jej marginesy i zostaną one zbindowane do textblocka.
  • Czasami zachodzi potrzeba zbindowania się do property z kontrolki, która znajduje się wyżej w drzewie wizualnym. Możemy znaleźć najbliższego przodka danego typu.

<TextBlock Text="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" />

Istnieje także możliwość określenie, który z kolei przodek nas interesuje, dopisując AncestorLevel=...