Model view Rok temu na prezentacje o Silverlight dla Bialskiej Grupy .NET napisałem prostą aplikację pokazującą powiązania.< Ta aplikacja jest prostym przykładem zastosowania modelu i widoku.

W aplikacji znajdują się dwa kwadraty ,a ich kolor jest zależny od kontrolek “NumericUpDown”. Każda z tych kontrolek odpowiada za odpowiedni kanał ARGB w kolorze. Jak chcesz możesz sam zmienić wartość tych kontrolek w aplikacji powyżej i zobaczyć ,że kolor kwadratów też się zmienia.

Użyłem kontrolki“NumericUpDown” ,aby ułatwić sobie kod. Kontrolka ta ma właściwości, które pozwalają na ograniczenie wartości numerycznych. Kanały ARGB mogą mieć wartości tylko od 0 do 255. 

Wadą zastosowania tej kontrolki jest fakt ,że z powodu dodatkowych bibliotek plik XAP waży 306 kb.

Dlaczego aplikacja wymaga użycia modelu? Faktycznie ,aby powiązać jedną właściwość z drugą nie trzeba żadnego dodatkowego kodu. W tym jednak przypadku musiałbym powiązać składowe klasy “SolidColorBrush” ,a nie jest to możliwe, ponieważ one nie dziedziczą po klasie “DepenedencyProperty”.

Silverlight też nie wspiera wielu powiązań naraz, czyli nie mogę napisać dziwnego konwertera, który obsłuży kontrolki “NumericUpDown” i właściwość Fill równocześnie.

Zobaczymy więc jak wygląda model.

Model

RectangleColorZ czego składa się model. Model zawiera właściwości, które symbolizują oddzielnie kanał kolor A,R,G,B. Właściwości te będą powiązane w obydwu kierunkach z kontrolkami“NumericUpDown”. Istnieje też właściwość Brush, która jest tylko do odczytu. Właściwość ta będzie powiązana z właściwością kwadratu “Fill”.

Klasa SolidColorBrush reprezentuje typ tej właściwości w końcu to ona spełnia cel koloryzowania elementów jednolitym kolorem w WPF i Silverlight.

Aby model działał poprawnie musi on jeszcze dziedziczyć po interfejsie “INotifyPropertyChanged” pewne zdarzenie. Jego celem jest powiadomienie klienta ,że dana wartość uległa zmianie i musi on ją pobrać jeszcze raz.

W tym wypadku za każdym razem, gdy zmienia się jeden z kanałów wysyłam komunikat ,że właściwość Brush powinna być ponownie odczytana. Widać to w blokach kodu SET tych kanałów A,R,G,B.Zaleca się implementacje tego zdarzenie według określonego wzoru, chociaż są lepsze niż ten zastosowany poniżej. Dzięki temu zdarzeniu powiązania pomiędzy właściwościami mają sens.W przeciwny wypadku wartość byłaby pobrana tylko raz i nieodświeżana. Coś mi się wydaje, że przydałby się tutaj oddzielny wpis na temat jak zaimplementować ten interfejs?

public class RectangleColor : INotifyPropertyChanged
{
    private double _a = 255;
    private double _b = 100; 
    private double _g = 20; 
    private double _r = 20;

    public SolidColorBrush Brush
    {
        get
        {
            byte a = (byte)_a; 
            byte r = (byte)_r;
            byte g = (byte)_g; 
            byte b = (byte)_b; 

            return new SolidColorBrush(Color.FromArgb(a, r, g, b));
        }
    }

    public double A
    {
        get { return _a; } 
        set 
        {
            if (value != _a)
            {
                _a = value; 
                RaisePropertyChanged("Brush"); 
                
            }
        }
    }

    public double R
    {
        get
        {
            return _r;
        }
        set
        {
            if (value != _r)
            {
                _r = value; RaisePropertyChanged("Brush");
            }
        }
    }

    public double G
    {
        get
        {
            return _g;
        } 
        set 
        {
            if (value != _g)
            {
                _g = value; RaisePropertyChanged("Brush"); 
                
            } 
        }
    }

    public double B
    {
        get { return _b; } 
        set { if (value != _b) { _b = value; RaisePropertyChanged("Brush"); } }
    } 
        
    public event PropertyChangedEventHandler PropertyChanged;

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

 

Silverlight Model View Binding

W kodzie widać jeszcze pola prywatne, które reprezentują kanały ARGB. Przechowują one domyślne wartości kanału przy uruchomieniu aplikacji.

 

Powiązania są naprawdę fajnie. W ten sposób sprawdziłem czy aplikacja działa, zanim ją w ogóle skompilowałem.

Przykładowo zmieniłem wartość pola “g” na 200 i odświeżyłem aplikacje w Visual Studio to zobaczyłem ,że kolory kwadratów też się zmieniły.

Pola są typu ‘double’ tylko dlatego ,że ten typ jest odwzorowaniem wartości typu w kontrolce “NumericUpDown”. Nie muszę więc pisać żadnego dodatkowego konwertera typu przy powiązaniu.

Właściwość Brush w bloku GET zamienia wszystkie wartości double na byte. W sumie tylko dlatego, że w Silverlight-cie metoda statyczna Color.FromArgb nie ma wersji przeciążonej z liczbami typu double.

Tak wygląda model, teraz czas na widok.

Widok

Aplikacja używa kontrolki z Silverlight toolkit więc musiałem do XAML dodać przestrzeń nazw (xmlns:toolkit) by z niej korzystać.

Ważniejszą rzeczą tutaj jest jednak moja przestrzeń nazw (xmlns:model) bez niej nie mogę się odnieść do mojego modelu.

W zasobach tej kontrolki (UserContol.Resources) muszę utworzyć instancje modelu i nadać mu klucz.

Później w kodzie podzieliłem siatkę (Grid) na 4 części i ustawiłem jej właściwość DataContex na instancje modelu za pomocą klucza. Wewnątrz siatki dodałem StackPanel, który zawiera cztery kontrolki “NumericUpDown” . Wartość tych kontrolek jest powiązana w dwóch kierunkach z odpowiednimi kanałami w modelu. Co to znaczy w “dwóch kierunkach” oznacza to, że wartości właściwości A,R,G,B są zmieniane przez tę kontrolkę ,ale zmiana właściwości też będzie oddziaływać na tę kontrolkę. Co widać przy pierwszym uruchomieniu aplikacji.

<UserControl
        xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit" 
        x:Class="ColorRecSilverlightBinding.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
        xmlns:model="clr-namespace:ColorRecSilverlightBinding.Model"
    d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <model:RectangleColor x:Key="RectangleColor" />
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource RectangleColor}" >
        <Grid.RowDefinitions><RowDefinition /> <RowDefinition /></Grid.RowDefinitions>
        <Grid.ColumnDefinitions> <ColumnDefinition /><ColumnDefinition /></Grid.ColumnDefinitions>
        <!--Kontrolka Numeryczna-->
        <StackPanel Grid.Row="1" Margin="10" VerticalAlignment="Center" HorizontalAlignment="Center">
            <toolkit:NumericUpDown Margin="5" Maximum="255" Value="{Binding Path=A,Mode=TwoWay}" />
            <toolkit:NumericUpDown Margin="5" Maximum="255" Value="{Binding Path=R,Mode=TwoWay}"/>
            <toolkit:NumericUpDown Margin="5" Maximum="255" Value="{Binding Path=G,Mode=TwoWay}"/>
            <toolkit:NumericUpDown Margin="5" Maximum="255" Value="{Binding Path=B,Mode=TwoWay}" />
        </StackPanel>
        <!--Litery-->
        <StackPanel Grid.Row="1" Margin="10,17,110,10" VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBlock TextWrapping="Wrap" Text="A" Margin="5" Height="22"/>
            <TextBlock TextWrapping="Wrap" Text="R" Margin="5" Height="22" Foreground="Red"/>
            <TextBlock TextWrapping="Wrap" Text="G" Margin="5" Height="22" Foreground="Lime"/>
            <TextBlock TextWrapping="Wrap" Text="B" Margin="5" Height="22" Foreground="Blue"/>
        </StackPanel>
        <Rectangle x:Name="Recoto" Fill="{Binding Path=Brush}" />
        <!--Przyklad powiazania wypelnienia z innym kawdratem-->
        <Rectangle Fill="{Binding ElementName=Recoto, Path=Fill}" Grid.Row="1" Grid.Column="1" />
    </Grid>
</UserControl>

Na koniec jeden kwadrat jest powiązany z właściwością modelu Brush,a inny kwadrat jest powiązany z nim. Tak wygląda mój prosty przykład zastosowania Model-View Silverlight.

Pobierz Kod