dotNed

Welkom bij dotNed Inloggen | Aanmelden | Help
in Zoeken

Dennis' avonturen in .net

  • SharePoint Connections Amsterdam: 22 en 23 november 2011

    Ook dit jaar is er weer een SharePoint Connections. Twee dagen deep dive in alles wat met SharePoint te maken heeft. Veel bekende sprekers uit binnen- en buitenland zullen hier acte de presence geven om jou alles te vertellen wat je moet weten over SharePoint.

    Enkele bekende namen zijn Asif Rehmani, Micheal Noel, Danny Burlage en uiteraard Mirjam van Olst en Wouter van Vugt. De Keynote zal worden verzorgd door Dan Holme. Holmes was de technical consultant voor de NBC Olympics in Beijing en Vancouver. Een rol die hij ook in Londen zal vervullen!

    Deze twee dagen kun je kiezen uit 36 verschillende sessies. En uiteraard: ook SharePint is weer bij.. De avond voor de Connections zal Diwug een speciale avond houden.

    Wil je ook naar SP Connections? De prijs is normaal gesproken ?499 maar indien je de kortingscode MLD5SDA invult op de aanmeldsite betaal je slechts ?359,00 euro.

    Meer info en aanmelden: http://nccomms-events.com/sharepointconnections/

  • CodeCamp 2011

    Pak je agenda en schrijf op (of typ in, wat je maar wilt): zaterdag 26 maart, vanaf 9 uur 's ochtends: de jaarlijkse CodeCamp begint weer!

    Dit jaar wordt groter dan alle jaren tevoren! We zijn gaan samenwerken met Diwug (http://www.diwug.nl) en hebben onze CodeCamp gecombineerd met SharePoint Saturday. Inderdaad: 2 events voor de prijs van 1! Nou ja prijs. zoals altijd is ook dit jaar toegang tot de CodeCamp (en dus ook SPSNL) geheel gratis, mits je je geregistreerd heb  op www.code-camp.nl !

    De locatie is Hotel Mitland te Utrecht, dus mooi centraal (en op dat tijdstip heb je ook geen last van files)

     

    We hebben geprobeerd er een interessant programma van te maken, en ik denk dat dat gelukt is.

    Dit is het programma:

    SESSIE SCHEMA CODE CAMP 2011

    Tijd

    Zaal A

    Zaal B

    Zaal C

    10:00 -11:15 CC1 - Stefan Kamphuis - VS LightSwitch. Een intro voor developers CC5 - Pepijn Sitter - Windows Phone 7 - XNA 4.0 development CC4 - André Boonzaaijer - Google App Engine Python
    11:30 - 12:45 CC2 - Leonard Lobel - So Many Choices, So Little Time: Understanding Your .NET 4.0 Data Access Options CC6 - Dennis Doomen - The Silverlight Cookbook chalk'n'talk CC10 - Ronald Harmsen - EF4.0, basics and beyond
    13:45 - 15:00 CC11 - Dennis Vroegop - Multi-touch; state of the union CC7 - Mark Dirksma / Pieter Joost van de Sande - Reinvent your architecture with CQRS CC8 - Geert van Horrik - MVVM met Catel
    15:15 - 16:30 CC12 - Leonard Lobel - Programming the "Beyond Relational" Features in SQL Server 2008 CC9 - Jonne Kats - Hoe gezond is jouw codebase? CC3 - Pascal Haakmat - UGTOGO - Een Silverlight 4 desktop player voor Uitzending Gemist

    Maar dat is dus niet alles: we hebben ook een aantal SharePoint sessies klaar staan. Ook daar kun je heen als bezoeker van de CodeCamp! Voor meer informatie over de SharePoint sessies verwijs ik je naar http://www.sharepointsaturday.org/nl/Pages/meetings.aspx 

    Oh ja: dit jaar hebben we, in tegenstelling tot vorig jaar, wel koffie en lunch geregeld! Dus wat dat betreft is er geen reden meer om niet te komen!

    Als je komt, let dan even op het volgende. De parkeermogelijkheden zijn beperkt, dus we raden je aan zo veel mogelijk te carpoolen of met het openbaar vervoer te komen. Zie de website van Mitland voor meer details.

    Het wordt een mooie dag! Ontwikkelaars, designers, Information Workers en meer disciplines die de hele dag praten over onderwerpen als LightSwitch, Windows Phone 7, Entity Framework, MVVM, NCQRS, SQL Server en zo voorts..

    Zie we jou ook op 26 maart?

    Tags van Technorati: ,,,,
  • Windows Phone 7 Update 1.0 problemen

    Windows-PhoneIk moet eerlijk zijn: ik heb een Windows Phone 7. En ik ben er uitermate enthousiast over. In het verleden heb ik verschillende telefoons gehad, inclusief een iPhone, maar ik heb ze altijd gezien als telefoons waar je ook wat extra's mee kunt. Tot ik de WP7 kreeg. Dit apparaat is geweldig, ik zou niet meer zonder kunnen.

    Microsoft heeft aangekondigd een update uit te brengen die extra functionaliteit gaat bevatten, waaronder het veel gevraagde Copy&Paste. Om de update mogelijk te maken, bleek het noodzakelijk om het update proces te updaten (klinkt aardig recursief, vind je niet?)

    Maandag 21 februari was het zo ver: de eerste update was beschikbaar. Mijn telefoon meldde keurig dat er een update beschikbaar was en dat ik mijn telefoon aan de PC moest koppelen zodat Zune aan het werk kon gaan. Ik heb eerst gekeken of mijn versie van Zune correct was, en toen dat allemaal in orde bleek te zijn gaf ik hem het signaal dat hij aan het werk kon gaan. De software werd gedownload, en er werd een backup gemaakt van mijn telefoon. Dat duurde even, maar goed, na verloop van tijd ging de backup toch naar 100%. En daar bleef hij.

    Mijn telefoon gaf een mooi beeld waaruit duidelijk bleek dat ik de telefoon niet mocht loskoppelen, maar er gebeurde niets. Backup: 100% stond op het scherm en daar bleef het bij. Na 30 minuten werd ik het zat en sloot ik de Zune af. Ik kreeg een waarschuwing dat dit kon resulteren in een niet-bruikbaar apparaat dus ik twijfelde nog. Maar ja, op deze manier kon ik hem ook niet gebruiken dus veel risico liep ik niet.

    Gelukkig kon ik mijn telefoon resetten en het nog een keer proberen. Met hetzelfde resultaat. Ondertussen zag ik op Twitter allemaal berichten van mensen die hun telefoon succesvol geupdate hadden, dus ik meldde dat het bij mij niet ging. Al snel kreeg ik een berichtje van Microsoft dat ik er maar mee moest ophouden: het ging niet werken.

    Teleurgesteld stopte ik de pogingen tot updaten, rebootte mijn telefoon en ging slapen (het was rond half 1 's nachts).

    De volgende dag hoorde ik berichten van anderen die hetzelfde probleem hadden. Al snel bleek wat de probleemgevallen gemeen hadden: we hebben allemaal een Samsung Omnia 7 met oudere firmware. Microsoft bevestigde het probleem (netjes trouwens, de meeste bedrijven gaan eerst een tijdje ontkennen) en gaf het advies om te wachten met de update tot er een update van de update beschikbaar was. Aangezien deze update geen gebruikersfunctionaliteit bevatte kan ik daar wel mee leven.

    Erger waren de berichten dat er mensen waren die melden dat hun telefoon niet meer te resetten was. Hun telefoon was, zoals dat zo mooi heet, gebrickt, met andere woorden: net zoveel functionaliteit als een baksteen. Microsoft adviseerden deze mensen om terug te gaan naar de winkel en om hun toestel om te laten ruilen zodat ze in ieder geval weerk verder konden. Het exacte aantal van deze getallen is nog niet bekend, maar voor zover ik gehoord heb is dit niet in Nederland gebeurd.

    Tot zover de feiten.

    Het internet liep vol met verhalen over deze gebeurtenissen. Het merendeel van de sites en blogs hierover schreven dat het een schande was. Updates zouden niet getest zijn. Gebruikers over de gehele wereld zouden hun telefoon niet meer kunnen gebruiken. Dit zou een bewijs zijn dat WP7 ten dode was opgeschreven. Het lag in de lijn der verwachtingen: sinds wanneer kan Microsoft goede software uitbrengen? Het platform is niet volwassen, ze kunnen niet eens een simpele update die niets toevoegd aan de telefoon uitbrengen.

    En daar word ik dus zo moe van. Ja er zijn problemen voor sommige Omnia gebruikers. Het aantal is niet bekend, maar het zijn er in ieder geval erg weinig. Het enige wat iedereen zegt is dat ze gehoord hebben dat er mensen zijn die.. enz. Ik geloof onmiddelijk dat dat waar is, maar wat zegt dat nou? Er zijn miljoenen WP7 apparaten verkocht. Als er een tiental devices het niet meer doen, is dat niet meer dan dat er normaal stuk gaan in het dagelijks gebruik. Dus ik kan hier niet zo veel mee.

    En laten we wel zijn: het is voor Microsoft niet te doen om te testen op alle mogelijke hardware. Ok, zoveel leveranciers zijn er niet die een WP7 leveren, maar blijkbaar zijn er bij die leveranciers verschillende versies van de firmware in omloop. En dan wordt het al gauw complex. En in de gevallen waar het fout ging bleef het beperkt tot het niet kunnen uitvoeren van de update.

    Daarnaast: ook andere leveranciers en fabrikanten hebben dit probleem. Ook bij de iPhone hebben updates geleid tot problemen. Hetzelfde geldt voor Androids, ook daar zijn toestellen onbruikbaar geworden door updates. Is dat goed te praten? Niet als je nou net die ene gebruiker bent die zijn dure telefoon gereduceerd ziet tot dure presse-papier. Maar als je alle cijfers bekijkt is er eigenlijk niets aan de hand. Toch is het zo dat zo gauw dit bij een Microsoft product gebeurd de hele wereld moord en brand schreeuwt.

    Het is weer als vanouds: alles wat Microsoft goed doet wordt afgeschilderd als een rip-off van een concurrent, terwijl dingen die niet 100% goed gaan worden opgeblazen (overigens claimt niemand dat de fouten ook gekopieerd zijn van anderen: die zijn echt van MS zelf).

    Een aantal jaar geleden heeft Microsoft een actie gevoerd met de naam "Get the facts", waarin ze opriepen om cijfers te laten zien waaruit zou blijken dat Microsoft spullen echt slechter waren dan anderen. Tot nu toe heb ik die cijfers niet gezien. Het blijft bij het napapegaaien van wat men op andere sites gezien heeft (die op hun beurt ook niet betrouwbaar zijn).

    Ik roep dan ook op om als je kritiek levert op Microsoft producten, of eigenlijk op welk platform dan ook, doe dat dan gefundeerd. Onderbouw je redenering. Kom met cijfers. Geef bewijs. Zoals men ook al zegt: extraordinary claims require extraordinary proof. Het zou fijn zijn als we een eerlijk beeld zouden krijgen van issues op wat voor techniek dan ook, in plaats van onderbuik gevoelens die nergens op gebaseerd zijn.

    Ik wacht ondertussen de nieuwe update rustig af en blijf een enorm tevreden WP7 gebruiker.

    <ranting mode="off" />

    Tags van Technorati: ,,,
  • Reflector niet meer gratis? dotNed to the rescue!

    Het zal je niet ontgaan zijn. Reflector, de onmisbare tool voor iedere zichzelf serieus nemende .net ontwikkelaar is binnenkort niet meer gratis te downloaden.

    Voor mensen die niet weten wat Reflector is: het is een tool die gecompileerde .net code kan decompileren zodat je kunt zien hoe stukken code gemaakt zijn. En dat kan dus ook met het .net framework zelf! Wil je bijvoorbeeld weten hoe de String.IsNullOrWhiteSpace geimplementeerd is? Reflector verteld het je!

    image

    Op deze manier kun je dus veel meer inzicht krijgen in hoe bepaalde stukken code werkt. Het is een uitstekende manier om het .net framework of andere code te leren kennen. En ja, ook in het geval van bugs (ja, ook het .net framework bevat bugs) kun je hier mee omzeilen.

    Reflector heeft mij al enorm veel tijd en dus geld bespaart. Dus de prijs van $35 is niet echt een probleem: dat is heel snel terug verdiend.

    Maar. ik kan me voorstellen dat het voor mensen een behoorlijke drempel is om dat bedrag neer te leggen. Om je nou een beetje tegemoet te komen, hebben Red Gate en dotNed een afspraak gemaakt:

    Mail me (dennis@dotned.nl) waarom jij vind dat je recht hebt op een gratis licentie van Reflector en wij zorgen dat je die krijgt (als je verhaal goed is tenminste). Gratis. Voor niets. Zo maar. Tja, dotNed maakt zich sterk voor de community! (zolang de voorraad strekt, we hebben er wel veel maar niet eindeloos veel)

    Met dank aan Red Gate voor de licenties!

    We zullen de licenties versturen als ze officieel te krijgen zijn, dat wordt pas in begin maart. Dan hoor je of je verhaal goed genoeg was om in aanmerking te komen

  • Sevensteps Lan Party op 10 maart 2011: Ontwikkelen van een Windows Phone 7 applicatie!

    Vorig jaar organiseerde Sevensteps een Surface Lan Party. Het idee erachter was om in 1 dag met de community een applicatie voor Microsoft Surface te ontwikkelen. De dag was enorm geslaagd, zie voor een verslag deze blogpost.

    Het was zelfs zo enorm geslaagd dat Sevensteps besloten heeft om dat dit jaar weer te organiseren, maar dan met als onderwerp een Windows Phone 7 applicatie!

    Het idee is als volgt: we gaan met een groep samen een applicatie ontwikkelen. De groep zal bestaan uit mensen met ervaring op het gebied van WP7 en mensen zonder ervaring. Er zullen ontwikkelaars en designers zijn. Uiteraard is er ook een projectleider, in de vorm van Bart Roozendaal van Sevensteps die de dag in goede banen zal gaan leiden.

    We zullen de hele dag bezig zijn. We beginnen rond 9 uur (de exacte aanvangstijd komt nog maar zal niet veel later zijn, zeker niet eerder!), er zal gezorgd worden voor eten en drinken (met dank aan Microsoft hiervoor) en we zullen genoeg fysieke telefoons hebben om op te testen en te deployen.

    Wat we gaan bouwen houden we nog even geheim maar je kunt er van op aan dat het de moeite waard zal zijn. Eventuele opbrengsten vanuit de Marketplace gaan we nog bespreken :-)

    Het leuke van deze dag is dat je de hele dag bezig bent met Windows Phone 7. Je bent omringd door mensen die dezelfde passie hebben als jij. Er zijn experts waar je wat van kunt leren. Er zijn iets mindere experts waar jij wat aan kunt leren (en daar leer je zelf ook altijd van). Het is gezellig. Heb je nog meer argumenten nodig?

    Dus: schrijf in je agenda: 10 maart 2011: LanParty WP7 bij Sevensteps!

    Tags van Technorati:
  • Bekijk de PDC keynote samen met ons in Veenendaal op 28 oktober!

    PDC Event

    Op donderdag 28 oktober organiseert dotNed samen met Info Support het lokale PDC event. Op deze avond kun je samen met ongeveer 250 andere ontwikkelaars kijken naar de live stream van de keynote op de PDC die dit jaar gehouden wordt.

    Even voor de mensen die niet weten wat de PDC is: de PDC (Professional Developers Conference) is van oudsher de plek waar Microsoft de nieuwste technieken aankondigt voor ontwikkelaars. De PDC’s waren de events waar we voor het eerst .net zagen, Linq, Windows Vista, de laatste Office versies, Azure enzovoorts. Dit alles gedaan door de bril van de developer, dus voor jou als ontwikkelaar absoluut de moeite waard. Wat dit jaar aangekondigd gaat worden is uiteraard nog niet bekend (geen verassingen bederven) maar het zal absoluut de moeite waard zijn.

    Natuurlijk kun je dit ook vanuit je luie stoel bekijken, maar het is natuurlijk veel leuker om dat samen met vakgenoten te doen! Op die manier kun je er nog over napraten. Mocht dit je niet overtuigen van de noodzaak om te komen, dan vertel ik je ook nog even dat veel mensen van Microsoft zelf ook aanwezig zijn. Dus bij hun kun je terecht met vragen, mochten de mensen van Info Support of wij van dotNed je niet kunnen helpen.

    Aansluitend op de keynote hebben we Matthijs Hoekstra van Microsoft bereid gevonden om het een en ander te vertellen over Windows Phone 7, die net gelanceerd is. Dit is dus de kans om dit nieuwe apparaat van dichtbij te bekijken en er meer over te weten te komen!

    Nog niet overtuigd? Ok, we beginnen om 5 uur ‘s middags met een lichte snack. Op die manier kun je de stream volgen zonder hongergevoel. Na het event hebben we een italiaanse maaltijd waarbij we ook nog eens prijzen gaan uitdelen. Deze prijzen zijn voor de mensen die het meeste en meest interessante twitteren over dit event met de hashtag #PDCNL.

    We hopen dat je er bij bent in Veenendaal bij Info Support. Inschrijven kan via deze website, maar doe dat wel snel want de inschrijvingen lopen snel vol!

    Tot 28 oktober in Veenendaal!

    Info Support Logo

    Technorati Tags: ,,
  • MVVM voor beginners, een overzicht

    Hier even een overzicht van mijn posts over MVVM:

    1. Introductie
    2. Uitbreiding basis
    3. Commands
    4. RelayCommand
    5. Praktijk: webcam snapshot met MVVM

    MVVM is een enorm sterk pattern, met hele goede scheiding tussen UI en logica. Hierna ga ik verder met WPF voorbeelden, laat ik gelijk zien hoe MVVM unit testing helpt!

  • MVVM voor beginners: Webcam snapshot app in Silverlight

    Het wordt tijd om de voorgaande posts samen te voegen in een demo applicatie. (zie voor andere posts hier, hier, hier en hier).

    We beginnen met een kleine uitbreiding van onze RelayCommand class. We moeten op de een of andere manier kunnen aangeven wanneer de CanExecute een ander resultaat geeft zodat de controls die van de command gebruik maken daarop kunnen reageren. Voeg in RelayCommand het volgende toe:

    /// <summary>
    /// Raises the can execute changed event.
    /// </summary>
    public void RaiseCanExecuteChanged()
    {
        var eventCopy = CanExecuteChanged;
        if (eventCopy == null)
            return;

        CanExecuteChanged(this, new EventArgs());
    }

    Door dit aan te roepen weten de controls dat ze acties moeten gaan ondernemen.

    Goed, in deze post laat ik zien hoe we in Silverlight een applicatie maken die met behulp van je webcam foto's maakt. Begin met een nieuw Silverlight 4 project. We gaan eerst de userinterface maken:

    Maak een nieuwe SilverlightUserControl aan in je project. Deze ziet er als volgt uit:

    <UserControl x:Class="SilverlightApplication5.SnapshotView"
       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"
       d:DesignHeight="300" d:DesignWidth="400">

        <Grid x:Name="LayoutRoot" Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="3*"/>
                <RowDefinition  Height="1*"/>
            </Grid.RowDefinitions>

            <!-- Placeholder voor de video -->
            <Border 
               Margin="10"
               BorderBrush="Black" 
               BorderThickness="2" 
               CornerRadius="4" 
               Grid.Row="0" Grid.Column="0">
                <Rectangle  Margin="10" />
            </Border>

            <!-- Placeholder voor de snapshot -->
            <Border 
               Margin="10"
               BorderBrush="Black" 
               BorderThickness="2" 
               CornerRadius="4" 
               Grid.Row="0" Grid.Column="1">
                <Rectangle  Margin="10" />
            </Border>

            <!-- Controls -->
            <Grid Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="1*"/>
                    <ColumnDefinition Width="4*"/>
                    <ColumnDefinition Width="4*"/>
                    <ColumnDefinition Width="1*"/>
                </Grid.ColumnDefinitions>

                <!-- Het aan en uitzetten van de video -->
                <CheckBox Grid.Column="1" Content="Capture video" VerticalAlignment="Center" />
                <!-- De knop voor het maken van een foto-->
                <Button Width="100" Height="24" Content="Snapshot!" Grid.Column="2" VerticalAlignment="Center"/>
               
            </Grid>
        </Grid>
    </UserControl>

    En in de MainPage gebruiken we deze nieuwe View:

    <UserControl x:Class="SilverlightApplication5.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"
       xmlns:views="clr-namespace:SilverlightApplication5"            
       mc:Ignorable="d"
       d:DesignHeight="300" d:DesignWidth="400">

        <Grid x:Name="LayoutRoot" Background="White">
            <views:SnapshotView x:Name="snapShotView"/>
        </Grid>
    </UserControl>

    We hebben nu dus een view gemaakt. Deze view bestaat uit 4 onderdelen. Een plek  waar de live video komt, een plek waar de genomen foto verschijnt, een checkbox om de video aan en uit te zetten en als laatste de knop om de foto mee te maken. Maak de UI gerust mooier, voor nu concentreer ik me voornamelijk op de code.

    Goed. Dat is (voorlopig de view). Het wordt tijd voor de ViewModel (in dit voorbeeld hebben we niet echt een Model, of je moet de video capture als dusdanig zien).

    Je hebt wel de ObservableObject en RelayCommand nodig die we vorige keer gemaakt hebben, dus voeg die toe aan dit project (of beter nog: plaats die in een aparte classlibrary zodat je ze altijd kunt gebruiken.. je zult ze vaker nodig hebben!)

    Als je dat gedaan hebt, maken we de ViewModel en voegen een paar properties toe.

    public class SnapShotViewModel : ObservableObject
    {
        // De video bron
        private CaptureSource _Source;

        // Hier wordt de video naartoe gerenderd.
        private VideoBrush _VideoBrush;
        public VideoBrush VideoBrush
        {
            get { return _VideoBrush; }
            set
            {
                _VideoBrush = value;
                RaisePropertyChanged("VideoBrush");
            }
        }

        // De genomen foto wordt hier in geplaatst
        private ImageBrush _ImageBrush;
        public ImageBrush ImageBrush
        {
            get { return _ImageBrush; }
            set
            {
                _ImageBrush = value;
                RaisePropertyChanged("ImageBrush");
            }
        }

        // Zijn we aan het opnemen of niet?
        private bool? _IsRecording = false;
        public bool? IsRecording
        {
            get { return _IsRecording; }
            set
            {
                _IsRecording = value;
                RaisePropertyChanged("IsRecording");
            }
        }

    }

    We hebben 3 properties. De VideoBrush is de brush die de output van de video camera zal gaan bevatten, de ImageBrush wordt het resultaat van de foto en de IsRecording geeft aan of de video stream loopt of niet.

    Laten we wat commands aanmaken:

    private RelayCommand _ToggleRecordingCommand;
    public ICommand ToggleRecordingCommand
    {
        get
        {
            if (_ToggleRecordingCommand == null)
                _ToggleRecordingCommand = new RelayCommand(ToggleRecordingExecute);
            return _ToggleRecordingCommand;
        }
    }

    private void ToggleRecordingExecute(object paramNotUsed)
    {
        // Implement
    }

    private RelayCommand _SnapCommand;
    public ICommand SnapCommand
    {
        get
        {
            if (_SnapCommand == null)
                _SnapCommand = new RelayCommand(SnapExecute, SnapCanExecute);
            return _SnapCommand;
        }
    }

    private void SnapExecute(object paramNotUsed)
    {
        // Implement
    }

    private bool SnapCanExecute(object paramNotUsed)
    {
        return _IsRecording.GetValueOrDefault(false);
    }

    We hebben twee commands aangemaakt. Een ToggleRecording command, welke de video aan of uit zet. Daarnaast hebben we ook een SnapCommand, wat de uiteindelijke foto moet gaan maken. Uiteraard kunnen we alleen maar een foto maken als we een video source hebben, dus SnapCommand implementeert ook de SnapCanExecute.

    Laten we de implementatie van de ToggleRecordingExecute eens bekijken:

    private void ToggleRecordingExecute(object paramNotUsed)
    {
        var currentState = _IsRecording;
        if (_SnapCommand != null)
            _SnapCommand.RaiseCanExecuteChanged();

        switch (currentState.GetValueOrDefault(false))
        {
            case true:
                StartVideoCapture();
                break;

            case false:
            default:
                StopVideoCapture();
                break;
        }
    }

    De _IsRecording variabele die we eerder aangemaakt hadden, geeft aan of we een videostream hebben. Als de waarde daarvan wijzigt en de ToggleRecordingCommand aangeroepen wordt, is dat van invloed op de SnapCommand. We moeten het systeem dus informeren dat alle controls die SnapCommand gebruiken even opnieuw de CanExecute van SnapCommand moeten controleren.

    Daarna zetten we de video aan of uit, al naar gelang de waarde van _IsRecording (dit is een Nullable<bool>, aangezien we deze gaan binden aan een CheckBox.IsChecked welke 3 waardes kan hebben: True, False en Null).

    De implementatie van de StartVideoCapture en StopVideoCapture:

    private void StartVideoCapture()
    {
        // Kijken of de video mogen gebruiken
        if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
        {
            //  Ja, dus maak een CaptureSource aan.
            _Source = new CaptureSource();
            // Zet de video brush op de output van de webcam
            VideoBrush = new VideoBrush();
            _Source.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
            VideoBrush.SetSource(_Source);
            // Start de camera
            _Source.Start();
        }
    }

    private void StopVideoCapture()
    {
        // Stop....
        if (_Source != null)
        {
            _Source.Stop();
        }
        _Source = null;
    }

    We maken gebruik van de nieuwe mogelijkheid in SL4 om de Webcam aan te sturen. Voor we dit doen, moeten we eerst vragen of dit wel mag. Bovenstaande code zal uiteindelijk de volgende dialog geven:

    Dialog Silverlight

    We maken een CaptureSource aan, maken een VideoBrush aan (een brush met de mogelijkheid om video's weer te geven) en starten de capture. Het stoppen van de video is simpeler; we roepen simpelweg Stop() aan.

    Laten we nu de SnapExecute eens bekijken:

    private void SnapExecute(object paramNotUsed)
    {
        ImageBrush = new ImageBrush() { Stretch = Stretch.Uniform };
        _Source.CaptureImageCompleted += (sender, e) => ImageBrush.ImageSource = e.Result;
        _Source.CaptureImageAsync();
    }

    We hebben een videobron, anders kunnen we de command niet uitvoeren (weet je nog? SnapCanExecute?)

    We maken een nieuwe ImageBrush aan. Daarna geven we een lambda mee als Completed event van CaptureImageCompleted. Deze set de ImageSource op het resultaat van de CaptureImageAsync. Daarna roepen we de CaptureImageAsync aan. Ja, ook het maken van images vanuit de webcam is ansynchroon in Silverlight.

    Het wordt tijd om onze ViewModel eens te gebruiken. In de MainWindow.xaml.cs maken we een instance aan van de ViewModel en koppelen deze aan de datacontext.

    public MainPage()
    {
        InitializeComponent();

        SnapShotViewModel snapshotVM = new SnapShotViewModel();
        snapShotView.DataContext = snapshotVM;
    }

    Nu nog even de bindings goed zetten in de SnapShotView.xaml:

    We beginnen met de twee rectangles:

    <!-- Placeholder voor de video -->
    <Border 
       Margin="10"
       BorderBrush="Black" 
       BorderThickness="2" 
       CornerRadius="4" 
       Grid.Row="0" Grid.Column="0">
        <Rectangle  Margin="10" Fill="{Binding VideoBrush}"/>
    </Border>

    <!-- Placeholder voor de snapshot -->
    <Border 
       Margin="10"
       BorderBrush="Black" 
       BorderThickness="2" 
       CornerRadius="4" 
       Grid.Row="0" Grid.Column="1">
        <Rectangle  Margin="10" Fill="{Binding ImageBrush}"/>
    </Border>

    In de eerste rectangle gebruiken we de VideoBrush uit onze ViewModel als Fill, in de tweede de ImageBrush (welke de foto gaat bevatten).

    Op naar de controls:

    <!-- Het aan en uitzetten van de video -->
    <CheckBox 
       Grid.Column="1" 
       Content="Capture video" 
       VerticalAlignment="Center" 
       IsChecked="{Binding IsRecording, Mode=TwoWay}" 
       Command="{Binding ToggleRecordingCommand}"
       />
    <!-- De knop voor het maken van een foto-->
    <Button 
       Width="100" Height="24" 
       Content="Snapshot!" 
       Grid.Column="2" 
       VerticalAlignment="Center" 
       Command="{Binding SnapCommand}"/>

    De checkbox krijgt 2 bindings. Als eerste de IsChecked, deze is gekoppeld aan de IsRecording property van ons ViewModel (vergeet niet om de Mode=TwoWay op te nemen in je binding, anders werkt het niet!). Daarnaast hebben we de Command gekoppeld aan de ToggleRecordingCommand.

    Bij de Button hebben we aangegeven dat we de SnapCommand willen gebruiken. Let op: we doen niets met enablen of disablen van de button: dat doet de Command! (of om precies te zijn: de CanExecute method van ons command).

    Run het, geeft SL toestemming om je webcam te gebruiken (die moet je wel hebben uiteraard) en maak geweldige foto's van jezelf!

  • MVVM voor beginners: RelayCommand

    Voordat ik laat zien hoe je in Silverlight een webcam capture programma maakt met MVVM, wil ik eerst dieper in gaan op de commands die ik vorige keer introduceerde.

    De SayHelloCommand class die ik liet zien, werkt prima. Maar het is natuurlijk niet echt handig om een class te schrijven voor ieder stukje functionaliteit. Om dit wat generieker op te zetten, kwam Josh Smith met de RelayCommand. In deze post laat ik zien hoe dat werkt en hoe je het gebruikt.

    Eerst nog even onze vorige implementatie:

    public class SayHelloCommand : ICommand
    {

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            MessageBox.Show("Hello Silverlight");
        }

    }

    Zoals je ziet is de logica vast geprogrammeerd in deze class. Laten we dit even generieker maken.

    We beginnen met een base class voor onze ViewModels. Immers, alle ViewModels hebben dezelfde implementatie van INotifyPropertyChanged, dus die kunnen we mooi in een base class plaatsen:

    public abstract class ObservableObject : INotifyPropertyChanged
    {

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        protected virtual void RaisePropertyChanged(string propertyName)
        {
            var eventCopy = PropertyChanged;
            if (eventCopy == null)
                return;

            eventCopy(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    En uiteraard passen we onze ViewModel even aan:

    public class MyViewModel : ObservableObject
    {
        private SayHelloCommand _SayHelloCmd = new SayHelloCommand();
        public ICommand SayHelloCmd
        {
            get
            {
                return _SayHelloCmd;
            }
        }
    }

    We hebben in onze ViewModel geen properties, dus we gebruiken de base class niet echt, maar het staat er maar vast voor later.

    We maken nu een nieuwe class aan, met de naam RelayCommand. Deze leiden we af van ObservableObject. Ook implementeerd deze de ICommand interface:

    public class RelayCommand : ObservableObject, ICommand
    {
        public bool CanExecute(object parameter)
        {
            throw new NotImplementedException();
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            throw new NotImplementedException();
        }
    }

    Dit moet nu allemaal bekend zijn.

    Voeg een paar private members toe:

    #region Private members
    readonly Action<object> _Execute;
    readonly Predicate<object> _CanExecute;
    #endregion

    Deze gaan dadelijk de implementatie van de logica bevatten en worden aangeroepen in de Execute en de CanExecute. We moeten deze uiteraard meegegeven, dus we maken ook een constructor:

    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute", "execute is null.");

        _Execute = execute;
        _CanExecute = canExecute;
    }

    public RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }
    #endregion

    We hebben twee constructors gemaakt. In de eerste geven we de execute en de canexecute mee, in de tweede alleen de execute waardoor de canExecute vanzelf null wordt.

    De implementatie van de Execute en CanExecute zien er als volgt uit:

    public bool CanExecute(object parameter)
    {
        return _CanExecute == null ? true : _CanExecute(parameter);
    }


    public void Execute(object parameter)
    {
        if (CanExecute(parameter))
            _Execute(parameter);
    }

    In de CanExecute kijken we eerst of er een predicate aanwezig is om uit te voeren. Als deze er niet is, geven we standaard true terug. Anders geven we het resultaat van de predicate terug.

    In de Execute roepen we eerst de CanExecute aan (gewoon, om aan de veilige kant te zitten) en daarna roepen we de _Execute action aan. Deze is nooit null, immers deze wordt gezet in de constructor.

    Dit is alles wat we nodig hebben. Laten we de ViewModel even aanpassen. De SayHelloCommand class kunnen we weggooien, die hebben we niet meer nodig. In de ViewModel passen we de code aan. De _SayHelloCmd private member en de SayHelloCmd property veranderen we in de volgende code:

    public class MyViewModel : ObservableObject
    {
        private RelayCommand _SayHelloCmd;

        public ICommand SayHelloCmd
        {
            get
            {
                if (_SayHelloCmd == null)
                    _SayHelloCmd = new RelayCommand(SayHelloCmdExecuted);
                return _SayHelloCmd;
            }
        }

        private void SayHelloCmdExecuted(object paramNotUsed)
        {
            MessageBox.Show("Hello Silverlight and MVVM");
        }
    }

    We maken pas een instance van de RelayCommand aan op het moment dat deze nodig is. Daarnaast hebben we een private method gemaakt die de logica bevat welke uitgevoerd moet worden bij het aanroepen van de Command. Dus: die logica zit niet meer in een aparte SayHelloCommand class maar gewoon in de ViewModel. En daar hoort hij ook!

    De XAML blijft hetzelfde, de binding van de Command in de Button is nog steeds {Binding SayHelloCmd} dus dat is gelijk.

    Uiteraard kun je ook de logica voor de CanExecute toevoegen, maar dat laat ik even aan jou over (hint: de signature is private bool SayHelloCmdCanExecute(object paramNotUsed).

    Nog even over die paramNotUsed: bij de binding kun je ook een commandparameter opgeven (dus in je XAML), deze wordt meegegeven in de Execute en CanExecute. In dit voorbeeld heb ik het niet nodig, dus ik heb hem maar even paramNotUsed genoemd. Uiteraard hoort hij wel in de signature thuis, anders klopt de Action<object> niet.

    Je kunt deze class natuurlijk uitbreiden: in plaats van RelayCommand die Action<object> heeft, kun je er een RelayCommand<T> van maken met een Action<T> waardoor je stronly typed parameters krijgt. Ook dat kun je nu zelf wel uitvogelen.

    De volgende keer: de webcam snapshot applicatie in Silverlight 4 met MVVM met onze ObservableObject en RelayCommand implementaties!

  • MVVM voor beginners. Commands

    Een van de nieuwe features in Silverlight 4 is de ondersteuning van de ICommand interface. Met deze toevoeging is het gebruik van MVVM binnen Silverlight nog beter te verdedigen, immers commands maken het mogelijk om de scheiding tussen UI en logica nog groter te maken.

    Wat is nu een Command? Een command is een manier om methods aan te roepen binnen je viewmodel. In de praktijk betekend dat dat je eigenlijk geen code-behind meer schrijft maar al je event afhandeling kunt verplaatsen naar je viewmodel. En dat willen we ook: je code behind achter je XAML moet zo leeg mogelijk blijven.

    Laten we eens een voorbeeld nemen. Maak een nieuwe SL4 project (met of zonder website, voor dit voorbeeld heb je de website niet echt nodig). In de xaml plaats je een button, als volgt:

    <UserControl x:Class="SilverlightApplication6.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"
       d:DesignHeight="300" d:DesignWidth="400">

        <Grid x:Name="LayoutRoot" Background="White">
            <Button Content="Klik!" Width="100" Height="24" Click="Button_Click"/>
        </Grid>
    </UserControl>

    En in de CS file erachter:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show("Hello Silverlight");
    }
     

    Dit is standaard zoals je het kent. Om nu gebruik te maken van de commands, moeten we wel wat werk verzetten (maar geloof me, het is het waard!)

    We beginnen met het maken van een ViewModel. Hoe dat werk heb ik al laten zien in vorige posts, dus daar ga ik niet te diep op in:

    public class MyViewModel : INotifyPropertyChanged
    {
        public MyViewModel()
        {
               
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            var eventCopy = PropertyChanged;
            if (eventCopy == null)
                return;

            eventCopy(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    En, uiteraard moeten we in de CS achter de XAML deze instantieren en kopppelen als DataContext:

    public MainPage()
    {
        InitializeComponent();
        MyViewModel myVM = new MyViewModel();
        DataContext = myVM;
    }

    Je kunt de Button_Click handler wel weghalen, zowel in de XAML als in de CS file. Deze gaan we immers vervangen door een command!

    Voeg een nieuwe class toe aan je project:

    public class SayHelloCommand : ICommand
    {

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            MessageBox.Show("Hello Silverlight");
        }

    }

    Deze nieuwe class, SayHelloCommand implementeert de ICommand interface. De ICommand heeft twee methods en een event.Als een command wordt aangeroepen, roep je eigenlijk de execute method van die class aan. Daarin staat dus je code die je uitgevoerd wilt hebben. Er is nog een method, genaamd CanExecute. Deze bepaalt of een command uitgevoerd mag worden. Als deze true teruggeeft, dan kun je de CanExecute aanroepen, anders niet. Het leuke is dat dit automatisch gebeurd: als je een button aan een command koppelt, is de enabled state van die button vanzelf gekoppeld aan het resultaat van de CanExecute.

    We hebben nog een event, CanExecuteChanged. Die moet je afvuren op het moment dat de, tja, CanExecute een andere waarde heeft. Logisch. Op die manier weten alle controls die gekoppeld zijn aan dit command dat ze hun status moeten updaten.

    Laten we deze nieuwe class eens gaan gebruiken. Pas de ViewModel die we net gemaakt hebben aan:

    public class MyViewModel : INotifyPropertyChanged
    {
        public MyViewModel()
        {
               
        }

        private SayHelloCommand _SayHelloCmd = new SayHelloCommand();
        public ICommand SayHelloCmd
        {
            get
            {
                return _SayHelloCmd;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            var eventCopy = PropertyChanged;
            if (eventCopy == null)
                return;

            eventCopy(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    Zoals je ziet heb ik een private member toegevoegd, _SayHelloCmd en die gelijk geinstantieerd. Daarnaast heb ik een public property aangemaakt van het type ICommand welke de _SayHelloCmd teruggeeft.

    Nu nog even de XAML aanpassen:

    <UserControl x:Class="SilverlightApplication6.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"
       d:DesignHeight="300" d:DesignWidth="400">

        <Grid x:Name="LayoutRoot" Background="White">
            <Button Content="Klik!" Width="100" Height="24" Command="{Binding SayHelloCmd}"/>
        </Grid>
    </UserControl>

    Je ziet dat we de Click weggehaald hebben en vervangen door een binding naar onze nieuwe property SayHelloCmd in de ViewModel.

    Runnen maar, en klik op de button.

    We hebben nu precies hetzelfde effect als we eerst hadden, maar de code is verplaatst naar de ViewModel. Op deze manier heb je dus je scheiding tussen UI en Business Logica nog groter gemaakt, wat de testbaarheid nog meer ten goede komt!

    Uiteraard werkt dit ook in WPF, sterker nog: daar heb je nog meer mogelijkheden.

    Volgende keer zal ik laten zien hoe je met MVVM een webcam snapshot applicatie bouwt.

    Stay tuned!

  • Silverlight 4 Breinsessie bij Humiq

    breinstormbanner

    Op 12 oktober organiseert dotNed samen met Humiq in Gorinchem een breinsessie. Breinsessies bij Humiq zijn gratis bijeenkomsten die over verschillende technische onderwerpen gaan. In oktober is het onderwerp Silverlight 4.

    Deze avond, die om 18.00 uur begint met ontvangst en een buffet, zal duren tot 21.00, gevolgd door een borrel. Aangezien we bij dotNed gratis events over .net altijd ondersteunen, hebben wij onze medewerking hieraan toegezegd. Deze avond zijn er twee sprekers. Als eerste ben ik zelf aan de beurt met een overview van het platform Silverlight en de mogelijkheden om Silverlight in te zetten als Line Of Business platform.

    Als tweede spreker hebben we Arjen de Blok, die verder ingaat op een specifieke mogelijkheid van Silverlight: development van Windows Phone 7 Series applicaties in Silverlight.

    Als je dus meer wilt weten over Silverlight, mag je deze avond niet missen! Meer informatie vind je hier op de site van Humiq.

    Technorati Tags: ,,
  • Isolated Storage en OpenFileDialog in Silverlight

    In een project waar ik aan werk hebben we de mogelijkheid om plaatjes te uploaden naar een server. Niet echt wereldschokkend, we hebben dit immers vaak genoeg gezien.

    Het scenario is dit: een klant kan een verzoek doen tot een onderzoek, en om de onderzoeker te helpen kan hij foto’s van het te onderzoeken object meesturen. De flow is ongeveer dit:

    image

    In stap twee krijgt de gebruiker de mogelijkheid om een aantal foto’s te selecteren die met de aanvraag meegestuurd moeten worden. Pas na het bevestigen in stap 4 wordt de gehele aanvraag verstuurd naar de server.

    Simpel.

    In stap 2 moet ik de gebruiker dus een OpenFileDialog presenteren en de geselecteerde bestanden opslaan tot we de foto’s kunnen uploaden. In mijn ViewModel sla ik dus het pad naar de bestanden op, zodat ik die later kan versturen. Tenminste, dat was het plan. Zie onderstaande code snippet:

    OpenFileDialog ofd = new OpenFileDialog();

    if (ofd.ShowDialog().GetValueOrDefault() == true)

    {

        string fullName = ofd.File.FullName;

        // Store in the viewmodel

    }

    tja.. Niet dus. Als je dit runt krijg je een exception met de mooie tekst “File Operation not permitted. Access to path ‘’ is denied.” Je kunt wel de filename opvragen, maar niet het pad. En op dat moment maakte ik een domme fout. Ik dacht: “Ok, als ik dit niet kan doen, dan ga ik het bestand wel even kopieren naar de IsolatedStorage zodat ik ze later kan versturen.” Op zich niet verkeerd: de bestanden worden gekopieerd en pas verstuurd als de gebruiker dit bevestigd heeft. Daarna kan ik de bestanden verwijderen uit de IsolatedStorage en zoals ze zeggen “All is well…”. Voor diegene die niet weten wat de IsolatedStorage is: het is een container waar alleen jouw applicatie bij kan en die vertrouwd wordt door Silverlight. Dat is dus de plek waar je bestanden kunt wegschrijven. In principe heb je immers geen rechten om bestanden weg te schrijven in het filesystem: dit zou een enorm security probleem kunnen veroorzaken.

    Nu is de ruimte die je hebt in de IsolatedStorage standaard niet al te groot. Default bij mij is dat slechts 1MB, niet genoeg om de foto’s in op te slaan. Gelukkig kan je de ruimte vergroten met een call naar IncreaseQuotaTo(long newQuotaSize); Deze geeft een boolean terug om aan te geven of het gelukt is of niet. Dus:

    OpenFileDialog ofd = new OpenFileDialog();

    if (ofd.ShowDialog().GetValueOrDefault() == true)

    {

        var chosenFileSize = ofd.File.Length;

        // Haal de locatie op voor de applicatie

        IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();

        if (isf.Quota < chosenFileSize)

            if (isf.IncreaseQuotaTo(chosenFileSize))

            {

                // Kopieer het bestand

            }

            else

            {

                // Toon een error...

            }               

    }

    Maar… wat ik ook probeer, de IncreaseQuotaTo blijft false teruggeven. Ik krijg simpelweg niet de ruimte die ik heb, dus ik kan mijn bestanden niet kopieren. Tijd om te onderzoeken wat er gebeurd. Om het testen te versnellen haal ik de call naar de OpenFileDialog even weg, zodat mijn method er als volgt uit ziet:

    // 16 MB moet voor nu genoeg zijn

    var chosenFileSize = (16 * 1024 * 1024);

    IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();

    if (isf.Quota < chosenFileSize)

        if (isf.IncreaseQuotaTo(chosenFileSize))

        {

            // Kopieer het bestand

        }

        else

        {

            // Toon een error...

        }

    En bij het runnen krijg ik nu de volgende dialog:

    image

    Ok. Dit werkt dus. Nou ja, ik heb het druk, ik zoek het later wel uit, dacht ik. Ik reserveer gewoon 16MB, ze mogen immers maar 4 foto’s maximaal uploaden dus dat moet genoeg zijn. Komt ‘ie:

    // 16 MB moet voor nu genoeg zijn

    var chosenFileSize = (16 * 1024 * 1024);

    IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();

    if (isf.Quota < chosenFileSize)

        if (!(isf.IncreaseQuotaTo(chosenFileSize)))

        {

            // Toon fout en stop

            return;

        }

    OpenFileDialog ofd = new OpenFileDialog();

    if (ofd.ShowDialog().GetValueOrDefault() == true)

    {

        // Kopieer het bestand...

    }

    Niet dus. Ik krijg nu een error bij de OpenFileDialog.ShowDialog: SecurityException: Dialogs must be user-initiated. WTF? Ik zit in de BtnClick event handler, hoezo “must be user-initiated?”.

    Na enig nadenken wordt het me iets duidelijker. In mijn code heb ik 2 dialogs: eerst die van de IsolatedStorage, dan die van de OpenFileDialog. Vreemd genoeg werkt het ook niet als de quota al wel groot genoeg is en ik dus niet die dialog krijg.

    Een zoektocht op het net bevestigde dit: bovenstaand scenario is niet mogelijk. Je moet het in twee stappen doen. Eigenlijk zou ik een button moeten maken die de storage vergroot (alleen weet ik niet met hoeveel want de foto’s zijn nog niet geselecteerd), en dan in een ander event de OpenFileDialog tonen. Maar waar in mijn flow moet ik dat doen? Het is onzin om de gebruiker te vragen om meer opslag ruimte als hij helemaal geen foto’s wil uploaden. Maar blijkbaar is dit de enige oplossing.

    Microsoft, verzin hier iets op. Bovenstaand scenario is een vrij gangbaar iets: kies bestanden, dan reserveer je de ruimte.

    Nu weet je het: dit gaat dus niet werken.

    PS Ik heb het probleem wel opgelost: de OpenFileDialog.File is een FileInfo object, en die sla ik nu op in mijn viewmodel in plaats van het pad wat ik eerst van plan was. Daarmee kan ik later de foto’s gaan uploaden. Op die manier heb ik de hele IsolatedStorage niet meer nodig.

    Waarom de FileInfo wel benaderd mag worden maar het pad niet, is mij een raadsel. Maar in ieder geval werkt mijn code nu volgens de flow die we bedacht hadden.

  • DispatcherTimer kostte mij mijn nachtrust

    Je kent het wel. Het project is bijna af, je moet alleen nog een paar kleine puntjes oplossen. "Uurtje werk" denk je en je gaat zitten om het allemaal netjes af te ronden. Alles is getest, alleen de gebruikers hebben nog een klein dingetje in de user interface gevonden. Kan niet al te spannend zijn, nietwaar?

    Ik zat vorige week ook in een dergelijke situatie. We hebben een project gedaan op Microsoft Surface en alles was eigenlijk prima. Er was alleen een klein puntje.

    Het systeem geeft informatie over een bouwproject. Er is een 3D model van het bouwterrein en gebruikers kunnen een gebouw selecteren. Als ze dat doen krijgen ze een soort van popup (geen echte, die bestaan niet in Surface) met meer info. Een onderdeel van die extra informatie zijn foto's van het bouwterrein. Gebruikers kunnen nu een foto pakken, die verslepen van de popup en op het hoofdscherm plaatsen. Als ze dat doen wordt de foto daar weergegeven en kunnen zie die vergroten, roteren, verplaatsten, affijn: alle dingen die Surface zo leuk maken.

    Maar om te voorkomen dat het scherm volloopt halen we de popup weg na 30 seconden van inactiviteit. Uiteraard nemen we gelijk de foto's mee als dat gebeurt. We willen niet dat de foto's achterblijven terwijl het informatiescherm al weg is.

    Nu kreeg ik de melding dat de foto's af en toe spontaan verdwenen. Typisch een opmerking van een gebruiker zou je denken, het is wat vaag. Maar goed, ik ging het testen. Ik startte de Surface op, koos een willekeurig gebouw, zag de info, plaatste een foto op het scherm en wachtte op de timeout van 30 seconden. En ja hoor: het informatiescherm ging weg en de foto's verdwenen ook keurig. Precies zoals het hoorde. Ik pakte een paar andere gebouwen maar overal werkte het zoals het hoorde. Natuurlijk werkte dat goed! De gebruikers snapten het niet.

    Tegelijkertijd kwam er iemand langs die onze Surface app wel even wilde zien. Hij ging spelen met ons systeem en toen gebeurde het: ik zag dat hij een informatie paneel opende, een foto pakte en die foto verdween na ongeveer 3 seconden al van het scherm. Vreemd. ik had het toch getest? Het was 4 uur 's middags, de volgende dag moesten we de definitieve oplevering doen. Nou ja, dit kon geen groot probleem zijn, dacht ik.

    Eerst proberen om het probleem te localiseren. Wanneer gebeurde dit nou precies? Na lang experimenteren ontdekte ik dat het gebeurde bij de volgende stappen:

    1. Kies een gebouw
    2. Open het informatie paneel
    3. Kies een foto
    4. Wacht tot het informatiepaneel en de foto vanzelf weggaan
    5. Wacht even
    6. Kies hetzelfde gebouw
    7. Kies dezelfde foto
    8. En kijk: de foto verdwijnt voor het informatiepaneel weggaat!

    Zucht. Toch iets meer dan een uurtje werk, dacht ik toen.

    Als ik in de code van de applicatie keek, zag ik dat de method die er voor zorgt dat foto's (geanimeerd) van het scherm verdwijnen alleen maar werd aangeroepen vanuit de method die de informatiepanelen verwijderd. Nergens anders vond een call plaats naar die method. En inderdaad: de code die het informatiepaneel opruimt werd aangeroepen terwijl de timeout van het informatiepaneel nog niet verstreken was. Het informatiepaneel zelf bleef ook keurig staan. Vreemd. Heel vreemd. In die code verwijder in de ViewModel van het informatiepaneel uit de Items property van de ScatterView (voor diegene die Surface niet kennen: een ScatterView is een listbox on steroids, meer hoef je nu niet te weten). In de items komt dat hele viewmodel niet voor dus hoe kan het dat die method aangeroepen wordt?

    Ik maak gebruik van een timer in de View van het informatiepaneel. Die timer wordt iedere keer gereset als er iets op het informatiepaneel gebeurt. Als er na 30 seconden geen activiteit is geweest roep ik de ViewModel (die in de DataContext van de View staat) aan en zorg ervoor dat de boel opgeruimd wordt. Hoe kan het dan dat een ViewModel die niet in de Items staat wordt aangeroepen? En hoe kan het dat de View blijft staan op het scherm maar dat zijn foto's wel weg gaan?

    Ik zal je mijn verdere zoektoch besparen, maar rond 4 uur 's nachts had ik het gevonden:

    Bij het opruimen van een informatiepaneel ga ik zoeken naar alle items in de ScatterView die als ViewModel een PhotoViewModel hebben, die ook voorkomt in de PhotoViewModel collectie in mijn informatiepaneel viewmodel. Als die er zijn, laat ik die geanimeerd verdwijnen. Er zat ergens in het geheugen een verwijzing naar een ViewModel van een infopaneel die toevallig dezelfde data bevat als het huidig getoonde informatiepaneel. Aangezien de spook infoViewModel dezelfde foto's bevat als die momenteel op het scherm staan, worden deze weggehaald. Het is echter niet hetzelfde ViewModel als die nu de informatie op het scherm zet, dus de huidige blijft zichtbaar. Dat deel van het mysterie was opgelost. Maar hoe kan het nu dat de oude ViewModels nog in het geheugen staan? Ik had ze toch na de animatie opgeruimd? En inderdaad: ik gooi ze echt weg uit de Items collectie. Voor de zekerheid zet ik ze zelfs op null, maar dat mocht niet baten.

    Ik besloot uiteindelijk maar om te kijken of het zou helpen als ik de timer uit de View haalde en in de ViewModel plaatste. Niet dat ik wist waarom  dat zou helpen maar het was na vieren 's nachts en ik zag het allemaal niet zo helder meer.

    Om de timer in de ViewModel te plaatsen moest ik uiteraard de juiste references in mijn library project hebben, dus die zocht ik even op in de help. En toen viel mijn oog op de volgende regel:

    A DispatcherTimer will keep an object alive whenever the object's methods are bound to the timer.

    Ah. Oh ja. Dat uhm, wist ik ergens ook wel.. maar helemaal niet meer aan gedacht. De views hebben een timer, en uiteraard een handler gekoppeld aan het Tick event. En in de help staat heel duidelijk dat de timer het object 'alive' houdt als er een method gekoppeld is aan het event. Met andere woorden: ik kan ze verwijderen uit hoeveel collecties ik ook maar wil, ik kan alle variabelen die verwijzen naar de views (en dus de ViewModel die de datacontext is) op null zetten, maar de CLR houdt het object levend en dus zal iedere 30 seconden de Tick genereerd worden. En dus zullen alle foto's behorende bij het object in de ViewModel na 30 seconden opgeruimd worden. De 30 seconden interval gaat lopen na de eerste keer dat deze info getoond wordt en blijft lopen.

    De oplosing was simpel uiteraard: in de tick event handler even _Timer.Tick -= TickHandler toevoegen en het probleem was opgelost.

    Programmeren kan af en toe zo simpel zijn.

    Technorati Tags: ,,
  • MVVM voor beginners, de categorieen

    Vorige keer begon ik een reeks artikelen over MVVM. In dit tweede deel ga ik verder met het voorbeeld dat ik daar begonnen ben en ga ik de categorieen uitbreiden.

    Specificaties

    De klant wil graag voor iedere categorie van een contact een kleurtje zien. Net als in Outlook dus. Voor de eerste versie hebben we twee categorieen, namelijk Prive (geel) en zakelijk (blauw). Een contact kan 0, 1, of meerdere categorieen hebben, deze moeten bij de contact getoond worden.

    Simpel dus.

    Implementatie

    We hebben de Category class al gedefinieerd. Laten we de ViewModel maken. Ook deze implementeert INotifyPropertyChanged, net zoals de ContactViewModel, dus het is handig om een baseclass te maken en ContactViewModel en CategoryViewModel hiervan af te leiden:

    using System;
    using System.ComponentModel;

    namespace dotNedContactManager.ViewModels
    {
        public abstract class ViewModelBase : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            protected void RaisePropertyChanged(string propertyName)
            {
                var eventCopy = PropertyChanged;
                if (eventCopy == null)
                    return;

                eventCopy( this, new PropertyChangedEventArgs( propertyName ) );
            }
        }
    }

    We moeten nu wel even ContactViewModel aanpassen: het event eruit, de RaisePropertyChanged eruit, en niet meer INotifyPropertyChanged implementeren maar afleiden van ViewModelBase. Voor CategoryView ziet het er dan als volgt uit:

    using System;
    using dotNedContactManager.Model;
    namespace dotNedContactManager.ViewModels
    {
        public class CategoryViewModel : ViewModelBase
        {
            private Category _Category;

            public CategoryViewModel(Category category)
            {
                if (category == null)
                    throw new ArgumentNullException( "category", "category is null." );
                _Category = category;
            }

            public string Description
            {
                get { return _Category.Description; }
                set
                {
                    _Category.Description = value;
                    RaisePropertyChanged( "Description" );
                }
            }

            // Deze hebben we nodig in de ContactViewModel.
            internal Category Category
            {
                get { return _Category; }
            }
        }
    }

    In ContactViewModel voegen we nu de Categories toe (die hadden we nog niet).

    private ObservableCollection<CategoryViewModel> _Categories;
    public ObservableCollection<CategoryViewModel> Categories
    {
        get
        {
            return _Categories;
        }
    }

    De Categories is een ObservableCollection<CategoryViewModel>, wat inhoudt dat als er een wijziging op de collectie optreedt we daar iets mee kunnen doen. In de constructor van onze ViewModel maken we de Categories aan en zetten de events. We moeten de constructor dus aanpassen:

    public ContactViewModel(Contact contact)
    {
        if (contact == null)
            throw new ArgumentNullException( "contact", "contact is null." );
        _Contact = contact;

        _Categories = new ObservableCollection<CategoryViewModel>();
        foreach (var category in contact.Categories)
        {
            _Categories.Add( new CategoryViewModel( category ) );
        }
        _Categories.CollectionChanged += Categories_CollectionChanged;
    }

    We zetten de eventhandler pas na het vullen van de initiele collectie, anders gebeuren er rare dingen. En deze hebben we ook nodig: de implementatie van de Categories_CollectionChanged:

    private void Categories_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // Wijzig de categorieeen in de Contact
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (CategoryViewModel newCategory in e.NewItems)
                    _Contact.Categories.Add( newCategory.Category );
                break;

            case NotifyCollectionChangedAction.Remove:
                foreach (CategoryViewModel oldCategory in e.OldItems)
                    _Contact.Categories.Remove( oldCategory.Category );
                break;

            default:
                // etc...
                break;
        }
    }

    Zo gauw er in de ViewModel iets verandert met de Categories van de Contact, sturen we dit direct door naar onze domeinclass Contact. Je hoeft dit niet te doen, maar voor nu is dat wel zo handig.

    We gaan nu een tweetal views maken. Een voor de Category maar ook een voor de CategoryCollection. Die laatste is niets anders dan een lijst met CategoryViews maar is wel handig. Zie:

    <UserControl x:Class="dotNedContactManager.Views.CategoryView"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                mc:Ignorable="d" 
                d:DesignHeight="300" d:DesignWidth="300">
        <Grid>
            <TextBlock Text="{Binding Description}"/>           
        </Grid>
    </UserControl>

    En de lijst:

    <UserControl x:Class="dotNedContactManager.Views.CategoryListView"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:views="clr-namespace:dotNedContactManager.Views"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                mc:Ignorable="d" 
                d:DesignHeight="300" d:DesignWidth="300">
        <Grid>
            <ListBox ItemsSource="{Binding}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <views:CategoryView DataContext="{Binding}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>           
        </Grid>
    </UserControl>

    Nu alleen de categorieen toevoegen in onze ContactView. We hebben een namespace nodig:

    xmlns:views="clr-namespace:dotNedContactManager.Views"

    En de view zelf:

    <views:CategoryListView Grid.Row="3" Grid.Column="1" DataContext="{Binding Categories}"/>

    Ik hoop dat je al die Bindings nog kunt volgen. Even voor de duidelijkheid:

    In MainWindow maken we een instance van ContactView. Deze krijgt de DataContext van de ContactViewModel. In de ContactView staan bindings naar FirstName, LastName, FullName en Categories. Dit zijn allen properties van ContactViewModel, dus die worden gevonden. Echter, de CategoryListView krijgt een binding op de datacontext ({Binding Categories}). Dus in de CategoryListView hebben we nu een DataContext (te weten: een ObservableCollection<CategoryView>). Dit wordt gebruikt als bron voor de ListBox.ItemsSource. We geven bij Binding daar geen pad op, dus hij krijgt het gehele object, i.e. de ObservableCollection. Vervolgens wordt er voor ieder item in die collection een CategoryView aangemaakt, deze krijgt als DataContext dit item mee (dus een CategoryViewModel uit de ObservableCollection).  In de CategoryView hebben we nu dus een DataContext, wat in dit geval een instance van CategoryViewModel is, en die heeft de property Description. Die wordt getoond in een textbox.

    Je ziet dat alles aan elkaar gelinkt wordt door bindings en niets in de code.

    Voor je runt moet je uiteraard nog wel even de juiste data meegeven in MainWindow.xaml.cs:

    public partial class MainWindow : Window
    {
        public ContactViewModel ContactVM { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            // Tijdelijk wat data genereren
            Model.Contact newContact = new Model.Contact()
            {
                FirstName = "Dennis",
                LastName = "Vroegop"
            };
            Model.Category catPrivate = new Model.Category() { Description = "Prive" };
            Model.Category catBusiness = new Model.Category() { Description = "Zakelijk" };
            newContact.Categories.Add( catPrivate );
            newContact.Categories.Add( catBusiness );

               
            ContactVM = new ContactViewModel( newContact );
        }
    }

    We maken even snel twee categorieen aan en geven deze mee aan ons Contact.

    Run en zie:

    image

    Leuk, niet waar? Is alleen nog niet conform specificaties. We willen geen tekstuele beschrijving van de categorie, maar een vierkantje met een kleur..

    Hoe gaan we dat nu doen? Deze specificatie is alleen van belang bij de User Interface. Het domein verandert niet, en eigenlijk ook de ViewModel niet. Alleen de UI moet wat anders laten zien. En dit is nu precies de kracht van MVVM: dit soort designdingen kun je overlaten aan diegene die bezig is met de UI: de onderliggende lagen veranderen niet.

    We willen dus een andere view voor Category, dus CategoryView moet anders. Geen probleem. We maken een andere XAML, maar we voegen wel iets toe. Om precies te zijn: we maken een converter. Een converter is een class die wordt gebruikt voor het converteren van een bepaald type naar een ander type in Bindings. Bijvoorbeeld: het converteren van een string met een bepaalde waarde naar een kleur.. Eerst de converter schrijven:

    using System;
    using System.Windows.Data;
    using System.Windows.Media;

    namespace dotNedContactManager.Converters
    {
        public class DescriptionToBrushConverter : IValueConverter
        {
            public object Convert(object value,
                Type targetType,
                object parameter,
                System.Globalization.CultureInfo culture)
            {
                if (!(value is String))
                    return value;

                var cleanedString = ((string)value).Trim().ToUpper();
                if (String.IsNullOrEmpty( cleanedString ))
                    return Brushes.White;

                switch (cleanedString)
                {
                    case "PRIVE":
                        return Brushes.Yellow;

                    case "ZAKELIJK":
                        return Brushes.Blue;

                    default:
                        return Brushes.HotPink;
                }
            }

            public object ConvertBack(object value,
                Type targetType,
                object parameter,
                System.Globalization.CultureInfo culture)
            {
                // Negeren we voor nu, we gaan
                // geen brushes terugconverteren naar strings
                return value;
            }
        }
    }

    We krijgen een string binnen, we kijken naar de inhoud en geven een SolidColorBrush met een bepaalde kleur terug. Als we geen string krijgen, krijgen we een witte brush, als we een andere categorie krijgen dan we verwachten dan wordt hij mooi roze (zodat hij goed opvalt en we weten dat we een bug ergens hebben).

    We passen de CategoryView even aan:

    <UserControl x:Class="dotNedContactManager.Views.CategoryView"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:converters="clr-namespace:dotNedContactManager.Converters" 
                Width="64" Height="64">
        <UserControl.Resources>
            <converters:DescriptionToBrushConverter x:Key="descToBrushConv"/>
        </UserControl.Resources>
        <Grid>
            <Border 
               BorderBrush="#8D8D8D" 
               BorderThickness="2" 
               CornerRadius="2" 
               Background="{Binding Description, Converter={StaticResource descToBrushConv}}">
               
                <TextBlock Text="{Binding Description}" 
                          Foreground="Black" 
                          HorizontalAlignment="Center" 
                          VerticalAlignment="Center"/>
            </Border>
        </Grid>
    </UserControl>

    Ik heb een namespace aangemaakt die verwijst naar de converters. In de Resources heb ik nu die converter aangemaakt en een key gegeven. Om de originele Textblock die we al hadden is nu een Border geplaatst.

    De border heeft een BackGround en die wordt gebind aan Description. Uiteraard is Description een string en geen Brush, maar daar zorgt onze converter voor. Die maakt een mooie brush aan naar gelang de waarde van de description.

    Runnen maar:

    image

    Stukken beter, niet waar? Een echte designer kan hier nu mee aan de gang en zich helemaal uitleven op dit soort schermen. Alle logica is immers los van de frontend. En wat de designer ook doet, zolang de Bindings er maar blijven staan werkt het allemaal wel. Wij als ontwikkelaars kunnen verder gaan met het schrijven van code voor ViewModels (en dus ook de unittests die daarbij horen).

    Volgende keer meer!

    Technorati Tags: ,,
    geplaatst Friday, June 25, 2010 9:56 AM door dvroegop | 3 Reacties
    Filed Under: ,
  • MVVM voor beginners

    Model View ViewModel, kortweg MVVM. Als je met WPF of Silverlight bezig bent, ben je deze vast tegen gekomen. Op het internet zijn momenteel erg veel discussies over dit onderwerp gaande. Wat is de beste aanpak? Welk framework kun je het beste gebruiken? Hoe ga je om met dialogs? Het begint bijna op een religieuze strijd te worden, de voorstanders van de ene aanpak vliegen de tegenstanders regelmatig in de haren.

    Als je geen ervaring hebt met MVVM lijkt dit allemaal een beetje overdreven. Immers, we hebben het maar over een pattern, oftewel een manier van werken die we allemaal wel kennen maar die we maar even een naam gegeven hebben. En als je zo denkt, dan heb je volgens mij gelijk.

    Toch is MVVM een heel mooie pattern. Het stelt mij in ieder geval in staat om mijn WPF/Silverlight code netjes te structureren en alle logica in mijn applicatie perfect testbaar in unittests te maken. Dat laatste alleen al is volgens mij een goede reden om MVVM eens te bekijken.

    Voor iedereen die al met MVVM werkt: sla deze post gerust over. Ik ga hier nog veel meer over schrijven en wellicht is dat interessanter. Voor de rest: ik ga hier uitleggen wat MVVM is en hoe je het toepast.

    Frameworks

    De vraag die ik het meest gesteld krijg van mensen die met MVVM beginnen is: welk framework kan ik het beste gebruiken? Het antwoord daarop is altijd: geen. Maak zelf een framework.

    Nu ben ik in het algemeen geen voorstander van het opnieuw uitvinden van het wiel, maar in dit geval raad ik het toch aan. MVVM is niet ingewikkeld en de frameworks die er zijn hebben niet al te veel features (hoewel sommige wel hele slimme oplossingen hebben bedacht). Ga eerst een zelf aan de gang, zorg dat je de concepten begrijpt en dat je weet wat MVVM inhoudt. Als je dat eenmaal in de vingers hebt, kun je gaan kijken naar MVVMFoundations, MVVMLight en de andere varianten.

    In deze blogpost ga ik uitleggen wat MVVM is en hoe het werkt, we beginnen gelijk met het bouwen van onze eigen MVVM framework. Zullen we hem DutchMVVM noemen? Nah..

    Achtergrond

    MVVM is niets nieuws. Het is een variant op de oudere, meer bekende MVP en MVC patterns die al jaren gepropageerd worden door mensen als Martin Fowler. Ze gaan allemaal uit van hetzelfde principe: scheiding van je userinterface, je business logica en je domeinmodel. Voor al deze patterns geldt het volgende plaatje:

    image

    We hebben in principe drie lagen. Iedere laag heeft zijn eigen verantwoordelijkheid. De UI laag bevat alle grafische componenten zoals textboxen, labels, buttons, listboxen en wat niet meer. De domein laag bevat je classes met daarin je business objecten, zoals een Contact class, een Order class enzovoorts. De laag daartussen, de logica laag, koppelt de andere twee aan elkaar en bevat de logica.

    Ik hoor je al zeggen: maar wij hebben meer lagen! Dat is natuurlijk in de praktijk zo, maar je kunt, als het goed is, iedere applicatie opdelen op bovenstaande manier. Zelfs als je een eenvoudige ASP.Net HelloWorld maakt heb je dit principe. Je ASPX pagina is je UI, je CodeBehind bevat de logica en de data die je weergeeft (in het geval van HelloWorld alleen de string "Hello World!") is je domein model.

    Het maakt de patterns niet uit hoe je domein laag eruit ziet. Je kunt deze zelf schrijven, genereren met Linq to SQL, Entity Framework gebruiken, of wat je maar wilt. Het belangrijkste is echter dat je domeinlaag geen kennis heeft van de lagen daarboven. Hetzelfde geldt voor je tussenlaag: die moet wel kennis hebben van je domeinlaag, maar mag niets weten van de UI laag. En als laatste: je UI laag heeft wel weet van je BL laag maar niet van je domeinlaag.

    Als je je applicaties op die manier structureert zie je dat je je lagen later kunt aanpassen zonder grote impact op de rest van je systeem. MVVM zorgt er zelfs voor dat je je BL laag (of zoals dat daar heet: je ViewModel) en je domeinmodel los kunt testen in een testomgeving, zonder last te hebben van je User Interface.

    MVVM is specifiek bedoelt voor WPF en Silverlight. Het maakt gebruik van de enorme kracht van Bindings in die platformen. Hierdoor kunnen we losse lagen schrijven die echt onafhankelijk zijn van de laag erboven.

    Binding in WPF/SL

    Ik ga hier niet uitgebreid in op de werking van Bindings in WPF en SL. Daar zijn genoeg andere bronnen van informatie over. Maar even een hele kleine achtergrond is wel op zijn plaats.

    Binding zorgt ervoor dat de elementen op het scherm gevuld worden met gegevens die ergens anders vandaan komen. Met andere woorden: een textbox bevat een string die niet in de definitie van je scherm staat maar die een andere oorsprong heeft.

    Om dat te bereiken zijn er twee elementen in WPF van belang. Als eerste is er de content van je control. Een Textbox heeft bijvoorbeeld een Text property, een Button heeft een Content Property. Dit zijn allemaal dependencyproperties en dependencyproperties kun je binden aan iets anders.

    Een Binding geeft aan hoe de data opgehaald moet worden. De data zelf kan van alles zijn. Van een eenvoudige string tot aan een complete usercontrol, zolang de property die gebind wordt er mee overweg kan kun je het via binding erin stoppen.

    In XAML ziet een Binding er bijvoorbeeld als volgt uit:

    <TextBox Text="{Binding Path=MyText}"/>

    In plaats van een hardcoded string in de TextBox te plaatsen, vertellen we WPF nu dat er ergens een property is met de naam MyText en de inhoud daarvan willen we graag in de TextBox zien.

    Maar waar staat die MyText property dan? Dat is de taak van de DataContext. De DataContext geeft aan wat de bron is van de Bindings. Dus: de DataContext geeft aan waar de data staat, de binding specificeert wat er moet staan. Die twee elementen zijn genoeg om de binding te maken.

    Om aan te geven dat de MyText property een property is van de huidige window, moeten we even wat kunstgrepen uithalen. Voor nu nemen we even de makkelijkste weg. In de codebehind van ons scherm plaatsen we in de constructor de volgende regel:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;           
    }

    We zetten de DataContext dus op zichzelf. Als je nu in je window een property MyText hebt (van het type String), zal de inhoud van die property in de textbox geplaatst worden. En andersom: als je de textbox wijzigt, zal die nieuwe waarde in MyText terechtkomen! Er is nog veel meer te vertellen over Bindings, als je meer wilt weten raad ik je aan om deze link te volgen.

    Maar: je ziet dat je door Bindings te gebruiken in staat bent om de data ergens neer te zetten en dan in de UI laag aan te geven waar deze data staat. Je hoeft dus niet meer in je logica data naar je UI te kopieren, je UI laag haalt het zelf wel op. Zie je MVVM al een beetje voor je?

    MVVM

    MVVM zou niet mogelijk zijn zonder de binding technieken in WPF en Silverlight. Laten we de verschillende onderdelen eens onder de loep nemen.

    Het idee is dat je voor alles wat op het scherm gaat komen usercontrols maakt. Deze noemen we de views. Een view bevat alle UI elementen die je nodig hebt om je data weer te geven en/of te bewerken. Alle data in dat scherm wordt door middel van databinding gekoppeld.

    Je model is simpelweg je domeinmodel. Zoals al eerder gezegd kunnen die gewone classes zijn, of iets uit NHibernate, of wat dan ook. Er hoeft in principe geen relatie te bestaan tussen je UI en je model: deze kunnen los ontwikkeld worden.

    Het voordeel van deze loskoppeling is dat het nu mogelijk is dat een designer in Blend de UI aanmaakt, terwijl een developer zich bezig houdt met het domein model. Deze twee mensen kunnen los van elkaar werken.

    De lijm die alles met elkaar verbindt is de ViewModel. Laten we eens een voorbeeld nemen.

    Ik neem een simpel voorbeeld: een applicatie die contacten toont. Een contact is een persoon met een voornaam, een achternaam en een lijst met categorieen. Een categorie kan zijn "Prive", "Zakelijk", enzovoorts. Ons domein model ziet er als volgt uit:

    image

    Alle properties zijn strings, met uitzondering van Categories, wat een List<Category> is. In de constructor wordt deze laatste aangemaakt, de setter is private zodat we deze niet kunnen wijzigen.

    De View is net zo simpel, met een kanttekening. In de specificaties staat dat boven ieder contact de volledige naam moet staan, dus de voornaam en achternaam samengevoegd.

    image

    Met de XAML:

    <UserControl x:Class="dotNedContactManager.Views.ContactView"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                mc:Ignorable="d" 
                d:DesignHeight="300" d:DesignWidth="300">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
           
            <TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" 
                      Text="{Binding FullName}" FontSize="16" FontWeight="Bold" />
            <TextBlock Grid.Row="1" Grid.Column="0" Text="Voornaam:"/>
            <TextBlock Grid.Row="2" Grid.Column="0" Text="Achternaam:"/>
            <TextBlock Grid.Row="3" Grid.Column="0" Text="Categorieen:"/>
           
            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding FirstName}"/>
            <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding LastName}"/>
            <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Categories}"/>
        </Grid>
    </UserControl>

    Geen rocketscience, zoals ik al zei.

    In ons domein hebben we geen FullName. Dat hoeft ook niet, want dat hoort niet bij het domein. Het is puur een UI ding: de gebruikers willen graag de volledige naam boven ieder contact hebben. Aangezien het composite is (dus samengesteld) zou het onzinnig zijn om de FullName bijvoorbeeld ook in de database op te slaan.

    Het wordt tijd voor onze ViewModel.

    Een ViewModel is een class met als enige bijzonderheid dat hij de interface INotifyPropertyChanged event implementeert. Deze interface bevat een event, PropertyChanged, die moet worden afgevuurd als een property in de class van inhoud wijzigt. Het Binding mechanisme van WPF en SL let op deze events en zal, indien nodig, de Bindings verversen (en dus de nieuwe data op het scherm tonen).

    Komt 'ie:

    using System;
    using System.ComponentModel;
    using dotNedContactManager.Model;

    namespace dotNedContactManager.ViewModels
    {
        public class ContactViewModel : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            private void RaisePropertyChanged(string propertyName)
            {
                var eventCopy = PropertyChanged;
                if (eventCopy == null)
                    return;

                eventCopy( this, new PropertyChangedEventArgs( propertyName ) );
            }

            private Contact _Contact;

            public ContactViewModel(Contact contact)
            {
                if (contact == null)
                    throw new ArgumentNullException( "contact", "contact is null." );
                _Contact = contact;
            }

            public string FirstName
            {
                get { return _Contact.FirstName; }
                set
                {
                    _Contact.FirstName = value;
                    RaisePropertyChanged( "FirstName" );
                    RaisePropertyChanged( "FullName" );
                }
            }

            public string LastName
            {
                get { return _Contact.LastName; }
                set
                {
                    _Contact.LastName = value;
                    RaisePropertyChanged( "LastName" );
                    RaisePropertyChanged( "FullName" );
                }
            }

            public string FullName
            {
                get { return String.Format( "{0} {1}", _Contact.FirstName, _Contact.LastName ); }
            }
        }
    }

    We hebben in onze ViewModel een referentie gelegd naar onze domein class Contact. Zoals ik al eerder zei is dat geen probleem: een bovenliggende laag mag enige kennis hebben van de laag daar direct onder. Voor de eenvoud geef ik deze contact even mee in de constructor; dat gaan we later aanpassen. Voor nu is dat wel even makkelijk zo.

    De ViewModel heeft 3 properties, te weten FirstName, LastName en FullName. De eerste twee halen hun data rechtstreeks uit de _Contact. Als de data verandert, zetten we deze ook direct in _Contact, maar we roepen ook de method RaisePropertyChanged(string propertyName) aan. Deze method vuurt het event af met de juiste propertynaam. Op die manier weet de UI laag dat er iets is verandert en dat de UI dus geupdate moet worden. Maar. aangezien de derde property FullName afhankelijk is van FirstName en LastName, moeten we ook aangeven dat deze gewijzigd is. Immers, als de voornaam wijzigt moeten ook de UI elementen die aan FullName gebonden zijn meeveranderen! FullName is verder readonly: het heeft geen zin om daar een Setter voor te schrijven.

    Laten we de boel eens samenvoegen. In de MainWindow heb ik een property gemaakt van het type ContactViewModel. In de constructor van MainWindow vul ik even wat data in. Nu zit in MainWindow.xaml.cs dus kennis van de Contact class, die geven we immers door aan de ViewModel, en dat is niet zoals het hoort. Later zullen we dit eruit halen, voor nu is dit goed genoeg.

    using System;
    using System.Windows;
    using dotNedContactManager.ViewModels;

    namespace dotNedContactManager
    {
        public partial class MainWindow : Window
        {
            public ContactViewModel ContactVM { get; set; }

            public MainWindow()
            {
                InitializeComponent();
                DataContext = this;

                // Tijdelijk wat data genereren
                Model.Contact newContact = new Model.Contact()
                {
                    FirstName = "Dennis",
                    LastName = "Vroegop"
                };

                ContactVM = new ContactViewModel( newContact );
            }
        }
    }

    En de XAML:

    <Window x:Class="dotNedContactManager.MainWindow"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:views="clr-namespace:dotNedContactManager.Views"
           Title="MainWindow" Height="350" Width="525">
        <Grid>
            <views:ContactView DataContext="{Binding ContactVM}"/>       
        </Grid>
    </Window>

    Ik heb hier een namespace 'views' toegevoegd, en in het scherm een instance van de ContactView gemaakt. De DataContext van deze view is gebonden aan onze property ContactVM (en aangezien MainWindow als DataContext zichzelf heeft, wordt deze ook gevonden).

    In de ContactView hebben we de bindings {Binding FirstName}, {Binding LastName} en {Binding FullName} staan. Omdat de DataContext gezet wordt op een instance van onze ContactViewModel, worden deze properties ook gevonden en uitgelezen.

    Als je dit nu runt krijg je je scherm te zien. Wanneer je een van de twee naam velden wijzigt, zul je zien dat de FullName meegaat.

    We hebben nu dus in onze UI laag nergens code staan die de teksten neerzet, en ook de logica voor het samenvoegen van de namen is niet terug te vinden in onze UI laag. We kunnen nu dus een unit test maken om onze ViewModel te testen:

    [TestMethod]
    public void FullName_Should_Reflect_FirstName_And_LastName()
    {
        string firstName = "A";
        string lastName = "B";
        string expected = "A B";
        Contact newContact = new Contact()
        {
            FirstName = firstName,
            LastName = lastName
        };

        ContactViewModel contactVM = new ContactViewModel( newContact );
        Assert.AreEqual( expected, contactVM.FullName, "FullName doesn't work." );
    }

    Alle logica zit nu in de Model en ViewModel classes, we kunnen die dus perfect unittesten.

    En nu?

    Dit is de basis van MVVM. Er is nog veel meer over te vertellen, en dat ga ik doen ook. Maar niet nu. Speel er eens mee en de volgende keer gaan we dieper in op de Category ViewModel. Die is namelijk leuker :-)

    Technorati Tags: ,,
    geplaatst Thursday, June 24, 2010 11:43 AM door dvroegop | 5 Reacties
    Filed Under: ,
Meer Berichten Volgende pagina »
Powered by Community Server, by Telligent Systems