WPF seriál 21. díl – Data Binding – 1. část

Data binding patří mezi mé oblíbené funkcionality, které WPF nabízí a věřím, že jak se tuto funkcionalitu naučíte, rovněž si jí zamilujete.

Klíčovou třídou je třída Binding, která se nachází ve jmeném prostoru System.Windows.Data. Pomocí této třídy můžeme jednoduše navázat spojení mezi zdrojovou a cílovou vlastností. Jak to myslím? Pokusím se Vám to vysvětlit na následujícím příkladu:

Př. Máme-li slider a budeme chtít v nějakém labelu zobrazovat aktuální hodnotu slideru, tak musíme obsloužit událost, kdy dojde ke změně hodnoty slideru a poté aktualizovat text v labelu. Pomocí bindingu můžeme říct labelu, že se má připojit na konkrétní vlastnost slideru, v našem případě Value a dosáhneme stejné funkcionality, ale BEZ použití nějakého kódu na pozadí.

Binding lze uplatnit na jednotlivé .NET vlastnosti, kolekce. Lze pomocí bindingu velice jednoduše zobrazovat data načtená z databáze, xml souboru, apod.

Mezi základní vlastnosti třídy Binding patří:

Vlastnost

Popis

ElementName

Při aplikaci bindingu na WPF element

FallbackValue

Lze nastavit hodnotu, která se vrátí v případě, že binding nebude schopen vrátit správnou hodnotu

Mode

Lze nastavit mód bindingu – o módu později

NotifyOnSourceUpdated

Lze nastavit, zda se má vyvolat událost SourceUpdated při přesunu dat ze zdroje do cíle

NotifyOnTargetUpdated

Lze nastavit, zda se má vyvolat událost TargetUpdated při přesunu dat z cíle do zdroje

Path

Cesta k cílové vlastnosti, na kterou chci uplatnit binding

RelativeSource

Binding lze uplatnit i na elementy např. o n úrovní výše, atd. – o relativeSource více později

Source

Pokud provádím binding na něco jiného než WPF element použiju právě source

XPath

XPath dotaz

Converter

Kovertor pro převod hodnoty na jiný objekt (např. zápornou hodnotu, na červenou šipku atd.)

Definice bindingu se zapisuje do složených závorek, kde za klíčovým slovíčkem binding následuje definice zdroje, zdrojové vlastnosti, mód, atd.

Základní Binding

Nejdříve si vytvoříme aplikace, kde si ukážeme základní použití bindingu, poté si tento binding vytvoříme v kódu na pozadí a na závěr si ukážeme jak příslušný binding odebrat. V naší aplikaci budeme mít slider a label ve kterém nastavíme pomocí bindingu hodnotu Text na hodnotu vlastnosti value. První tlačítko bude sloužit pro vymazání bindingu a druhé tlačítko pro jeho nastavení pomocí kódu na pozadí.

XAML

<Window x:Class="WpfApplication1.BasicBinding"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="BasicBinding" Height="300" Width="300">
    <StackPanel>
        <Slider x:Name="mySlider" Minimum="0" Maximum="100" Value="10"/>
        <Label x:Name="myLabel" Content="{Binding ElementName=mySlider, Path=Value}"/>
        <Button x:Name="CreateButton" Content="Create" Click="CreateButton_Click" />
        <Button x:Name="ClearButton" Content="Clear" Click="ClearButton_Click" />
    </StackPanel>
</Window>

Kód na pozadí

public partial class BasicBinding : Window
{
    public BasicBinding()
    {
        InitializeComponent();
    }

    private void CreateButton_Click(object sender, RoutedEventArgs e)
    {
        // vytvorim objekt binding
        Binding binding = new Binding();
        // nastavim nazev elementu, ktery je zdrojem pro muj binding
        binding.ElementName = "mySlider";
        // dale nastavim vlastnost path, na vlastnost ze ktere chci ziskavat informace
        binding.Path = new PropertyPath("Value");
        // vytvoreny binding nastavim memu labelu
        // pomoci metody SetBinding, kde prvnim parametrem je vlastnost labelu ve ktere budu chtit
        // zobrazovat informace ze zdrojoveho elementu a druhym parametrem je nas vytvoreny binding
        myLabel.SetBinding(Label.ContentProperty, binding);
    }

    private void ClearButton_Click(object sender, RoutedEventArgs e)
    {
        // binding lze smazat dvemi metodami
        // ClearAllBinding prijima jeden parametr a to cilovy element na kterem smaze vsechny nastavene bindingy
        BindingOperations.ClearAllBindings(myLabel);
        // ClearBinding prijima dva parametry, prvnim je cilovy element a druhym vlastnost, na ktere se vymaze binding
        //BindingOperations.ClearBinding(myLabel, Label.ContentProperty);
    }

}

Po spuštění aplikace dojde při každé změně k aktualizaci hodnoty v labelu. Ovšem po kliknutí na tlačítko Clear dojde k odebrání bindingu a label, ve kterém byla původně hodnota slideru najednou zmizí. I když budeme sliderem sebevíce hýbat, tak již žádnou hodnotu neuvidíme. Po stisknutí tlačítka Create se naopak binding vytvoří a vše bude fungovat tak jako na začátku.

Ukázku jak svázat 2 elementy máme za sebou a nyní si ukážeme jak pomocí bindingu svážeme více vlastností s více elementy. Pro tyto účely slouží vlastnost DataContext. Pokud totiž u elementu neuvedeme Source, RelativeSource, či ElementName, tak se jako datový zdroj bere DataContext nadřazeného kontejneru. Ve zmiňované ukázce si vytvoříme třídu Person s vlastnostmi FirstName, LastName a Age a poté tyto hodnoty svážeme s UI elementy našeho okna

Kód na pozadí

public partial class PersonBinding : Window
{
    public PersonBinding()
    {
        InitializeComponent();

        Person person = new Person
        {
            FirstName = "Lukas",
            LastName = "Kubis",
            Age = 23
        };
        MainGrid.DataContext = person;
    }
}

XAML

<Grid x:Name="MainGrid">
        <Grid.Resources>
            <Style x:Key="myStyle">
                <Setter Property="Control.Margin" Value="5"/>
                <Setter Property="Control.HorizontalAlignment" Value="Left"/>
                <Setter Property="Control.Width" Value="200"/>
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.3*" />
            <ColumnDefinition Width="0.7*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="0.25*" />
            <RowDefinition Height="0.25*" />
            <RowDefinition Height="0.25*" />
            <RowDefinition Height="0.25*" />
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Style="{StaticResource myStyle}">First Name</Label>
        <Label Grid.Row="1" Style="{StaticResource myStyle}">Last Name</Label>
        <Label Grid.Row="2" Style="{StaticResource myStyle}">Age</Label>
        <TextBox Grid.Row="0" Grid.Column="1" Style="{StaticResource myStyle}" Text="{Binding Path=FirstName}"/>
        <TextBox Grid.Row="1" Grid.Column="1" Style="{StaticResource myStyle}" Text="{Binding Path=LastName}"/>
        <TextBox Grid.Row="2" Grid.Column="1" Style="{StaticResource myStyle}" Text="{Binding Path=Age}"/>
    </Grid>

Nejdříve jsme si pomocí řádku xmlns:clr="clr-namespace:DataBindingPart1 naimportovali namespace DataBindingPart1 ve kterém se nachází třída Person. Poté jsme si ve Window.Resources vytvořili objekt Person a nastavili potřebné vlastnosti. Na závěr jsme nastavili gridu DataContext na námi vytvořený objeckt Person pomocí StaticResource.

Poznámka: Umístíme-li PersonResource do Grid.Resources, budeme muset místo StaticResource využít DynamicResource, jinak by došlo k chybě, protože se nemůžeme z DataContextu odkazovat na resources definovaný v XAMLu níže něž samotný DataContext

Na náš formulář přidáme ještě tlačítko pro inkrementování věku. Vždy, když klikneme na tlačítko update, věk se zvýší o jeden rok a zobrazí se textová informace o aktuálních hodnotách objektu person. Vrátíme se tedy k původnímu kódu kde jsme nastavovali DataContext v kódu na pozadí.

A po stisku tlačítka Update se vykoná následující kód:

private void Button_Click(object sender, RoutedEventArgs e)
{
    person.Age++;
    MessageBox.Show(person.ToString());
}

 Po spuštění aplikace a klikáním na příslušné tlačítko dochází k inkrementálnímu zvyšování hodnoty Age, bohužel se tato změna neprojevuje v UI.

image

Toto chování je pochopitelně správné, protože náš cíl (textbox) se nemá šanci dozvědět o tom, že ve zdroji (person) došlo k nějaké aktualizaci. Řešení je velice jednoduché, stačí když bude naše třída implementovat rozhraní INotifyPropertyChanged. Toto rozhraní má pouze jednu událost PropertyChanged. Upravíme tedy naší třídu Person tak, aby implementovala toto rozhraní a vždy když změníme hodnotu nějaké vlastnosti vyvoláme i událost, kde do parametru předám název vlastnosti u které došlo ke změně.

public class Person : INotifyPropertyChanged
{
    private string _firstName;
    private string _lastName;
    private int _age;

    public event PropertyChangedEventHandler PropertyChanged;

    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            PropertyOnChanged("FirstName");
        }
    }
    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            PropertyOnChanged("LastName");
        }
    }
    public int Age
    {
        get { return _age; }
        set
        {
            _age = value;
            PropertyOnChanged("Age");
        }
    }

    public override string ToString()
    {
        return string.Format("{0} {1}, {2}", FirstName, LastName, Age);
    }

    private void PropertyOnChanged(string propertyName)
    {
        if ( PropertyChanged != null )
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Nyní při stisku tlačítka Update dojde i k automatické synchronizaci našeho textboxu s aktuální hodnotou objektu person.

Závěr

Dnes jsme si stihli představit pár vlastností ze třídy Binding. Naučili jsme se provázat dva objekty jak pomocí XAMLu tak pomocí kódu na pozadí. Víme kdy použít vlastnost ElementName, Path či k čemu se hodí implementovat rozhraní INotifyPropertyChanged. V příštím díle se můžeme těšit na CollectionBinding (jak nabindovat nějaké kolekce)

Materiály ke stažení

Demo

Ohodnoťte článek: starstarstarstarstar

Komentáře

Přidat komentář

jméno

text komentáře

opište text z obrázku


O autorovi

Lukáš Kubis

Lukáš Kubis

Působí jako Microsoft Student Partner na VŠB-TU Ostrava a zároveň pracuje jako software developer.

Mezi jeho portfolio patří platforma Microsoft .NET především WPF, WCF, Silverlight, ASP.NET MVC, ale i oblast týmového vývoje a Design Patterns.

Rovněž publikuje na MSTV.cz a je držitelem ocenění Microsoft Most Valuable Professional (MVP)

Máte-li zájem o školení, konzultaci či zakázkový vývoj pak mě neváhejte kontaktovat!

web: http://web.lukaskubis.net

blog: http://lukaskubis.net

©2010 | David Beinhauer | Lukáš Kubis