dotNed

Welkom bij dotNed Inloggen | Aanmelden | Help
in Zoeken

Dennis' avonturen in .net

  • 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 | 1 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 | 2 Reacties
    Filed Under: ,
  • Expression 4 Community Launch bij Sixin!

    Op 8 juni aanstaande organiseren onze collega's van Sixin, samen met Centric en Microsoft het Expression 4 Community Launch event.

    Ze hebben nog een aantal plaatsen beschikbaar. Daarom nodigt Sixin ook onze leden uit om eens met ze te netwerken, maar vooral om een paar goede sessies bij te wonen.

    Dit event zal worden gehouden in de avond van 8 juni. Tijdens dit event zullen de 2 sprekers van die avond je inlichten over de nieuwe mogelijkheden van versie 4 Expression Blend, Expression Web en Expression Encoder.

    Als je het leuk vindt wacht dan niet en registreer je bij Sixin voor het event:
    http://sixin.nl/meetings.aspx?eventid=efdd6929-4246-4b6e-921f-a2ac8663b092

    De agenda ziet er als volgt uit:

    17:45 - 18:45 Eten, drinken en netwerken

    18:45 - 19:00 Ontvangst & introductie door Koen Zwikstra, bestuurslid van Sixin en Silverlight MVP

    19:00 - 20:00 Building a Windows Phone 7 application using the new features of Expression Blend 4 door Loek van den Ouweland, oprichter van en webdesigner bij Toverstudio

    20:00 - 20:15 Pauze

    20:30 - 21:30 Rondleiding door Expression Web en Encoder door Antoni Dol, senior designer bij Macaw

    21:30 - 22:00 Netwerken onder het genot van een drankje

    Uiteraard zijn de Sixin leden ook van harte welkom op de DotNed Events! Hou daarom onze events ook in de gaten, daar zit vast wel iets tussen wat van pas komt!

  • Visual Studio 2010 Community Launch bij Afas

    Op 20 april organiseren de nederlandse .net communities dotNed, SDN, VBCentral en Sixin samen met Microsoft de Nederlandse Visual Studio 2010 Launch!

    Deze avond, die gehouden wordt bij Afas te Leusden, kun je op twee manier bijwonen. Je kunt de avond online volgen of je kunt er bij zijn! Het zal een interactieve gebeurtenis zijn dus laat je deze kans niet voorbij gaan.

    De keynote wordt gegeven door Amit Chatterjee, verantwoordelijk voor de Visual Studio Test Business bij Microsoft. Na deze keynote zijn er interactieve sessies, gegeven door Alex Thissen, Anko Duizer, Marcel de Vries en Wouter van Vugt.

    Een aantal dotNed bezoekers heeft samen met Sevensteps en Detrio een applicatie gebouwd op Microsoft Surface waarmee we in staat zijn om vragen vanuit het publiek (zowel in de zaal als mensen die online meedoen) te kunnen tonen, beoordelen, er op stemmen en dergelijke. Deze unieke community effort zal voor het eerst gebruikt worden op deze avond, nog een reden om mee te doen.

    Het programma ziet er als volgt uit:

    • 17:30 - 18:15 uur Ontvangst bij AFAS
    • 18:15 - 18:45 uur Welkom Keynote Amit Chatterjee - Host
    • 18:45 - 20:15 uurVisual Studio 2010, Interactieve sessie
      • Web development
      • Office development / SharePoint
      • Windows Azure
      • ALM

    Sprekers Anko Duizer, Alex Thissen, Marcel de Vries, Wouter van Vugt

    • 20:15 - 21:30 uur Borrel - Interviews met communities

    We hopen dat jullie allemaal langskomen bij dit event!

    Je kunt je aanmelden voor de avond op twee manieren:

    1. Aanmelden voor het bijwonen van dit event in Leusden: https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032448067&Culture=nl-NL

    2. Aanmelden voor het online event: https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032448182&Culture=nl-NL

  • Surface Lanparty bij Sevensteps

    Eind maart vinden weer de DevDays plaats. Voor mij is dat 2 dagen lang met vakgenoten bijpraten en mijn kennis bijspijkeren over alles wat er gebeurd op het gebied van .net ontwikkelingen. En net als voorgaande jaren kun je 's avonds naar de Geek Night. Het idee achter de Geek Night is dat hier een platform geboden wordt waarin onderwerpen de revue passeren die je niet zo gauw in een reguliere sessie ziet. De nadruk hier ligt op het 'fun' gedeelte van de technologie waar we dagelijks mee werken. En dan bedoel ik niet de fun die je normaal ondergaat als je bezig bent met de nieuwste release van het MVC pattern voor ASP.Net (hoewel dat ook leuk is!). Nee, met fun praten we over toepassingen die je niet zo gauw tegen komt in je dagelijkse werkzaamheden. Denk bijvoorbeeld aan het aansturen van robots met .net. Je weet wel, de dingen waar je je vroeger als hobbyist mee bezig hield.

    Sevensteps had een leuk idee. Zou het niet aardig zijn om een applicatie te maken die gebruikt kan worden tijdens de keynote zodat het publiek enigzins kan participeren? Op zich kun je daar wel wat voor verzinnen maar aangezien het een applicatie voor de Geek Night moest zijn, moest het een hoog gadget gehalte hebben. Dus kwam de keuze al gauw op het maken van een Microsoft Surface applicate in combinatie met Twitter. En belangrijker: het zou nog mooier zijn als die applicatie een community effort zou zijn.

    Het resultaat was dat Sevensteps op donderdag 11 maart een Lanparty organiseerde. Op die dagen kwam een groep enthousiaste ontwikkelaars bij elkaar in Amersfoort om samen, in een dag, een geweldige applicatie voor de Geek Night te maken. In die ene dag zou alles wat te maken heeft met software ontwikkeling de revue passeren:

    • brainstormen over functionaliteit
    • bedenken van design
    • opdelen van het team in werkgroepen
    • scrum sessies (sprints van 90 minuten!)
    • ontwikkelen van de software
    • 'tekenen' van de User Experience
    • testen

    Met andere woorden: alles wat we de hele dag al doen maar nu in 1 dag. Om eerlijk te zijn vond ik het hele plan nogal ambitieus. Het idee voor de applicatie bestond alleen maar in hele vage vorm, en dan zouden wij in 1 dag een geheel systeem kunnen bouwen? Maar goed, ook als het niet zou lukken zou het een leuke dag worden waar iedereen als het goed is wat van kon leren.

    Dus vol goede moed stond in om half 9 in Amersfoort bij Sevensteps voor de deur. In het half uur dat volgde kwam iedereen binnen en rond 9.15 waren er 12 mensen aanwezig. Na een korte inleiding door Sevensteps over het idee, de manier van werken en een korte introductie in het Model View Viewmodel pattern verzamelden we ons rond een whiteboard en begonnen te brainstormen.

    Al snel ontstonden er een aantal goede maar ook minder goede ideeen, en daar werden leuke discussies over gehouden. Vervolgens werden er werkgroepjes van 2 a 3 personen gevormd die elkaar goed aanvulden qua kennis en interesse. Iedere 90 minuten zou ieder team iets bouwen of designen en na die 90 minuten werd er een scrum gehouden om te kijken wat er gedaan was, wat er nog moest gebeuren en waar de eventuele problemen lagen.

    Nou heb ik veel ervaring in het maken van Surface systemen maar dat goldt uiteraard niet voor iedereen. De eerste horde die genomen moest worden was dat men de Surface SDK op de machine moest krijgen. Tegen de tijd dat iedereen aan de gang kon gaan liep het tegen 11 uur. En bedenk dat de meeste mensen nog nooit voor Surface ontwikkeled hadden. Op dat moment was ik er van overtuigd dat het allemaal niet zou gaan lukken.

    Maar tot mijn verbazing ging het na de eerste sprint opeens erg snel. Het team pakte het goed op, leerde veel van elkaar en werden steeds enthousiaster. Er ontstonden nieuwe ideeen en  verfijningen van het ontwerp en iedereen hielp elkaar waar het kon. Uiteraard speelde de goede voorbereiding van Sevensteps een belangrijke rol: het netwerk deed het prima, SVN was goed ingeregeld dus niemand hoefde zich om dat soort randvoorwaarden zorgen te maken.

    Na iedere sprint zagen we de software steeds beter worden. De manier van werken (MVVM) zorgde er voor dat men in staat was om snel code te produceren en die over te dragen aan de designers die er dan iets moois van gingen maken. De sfeer werd eigenlijk alleen maar beter en beter wat er voor zorgde dat de productiviteit enorm hoog was.

    Na de pizza (met dank aan Microsoft) kwamen we op het punt dat alles samengevoegd moest gaan worden. En tot onze grote verassing stond er rond 10 uur 's avonds opeens een werkend systeem op de Surface unit.

    De les die ik hiervan geleerd heb is dat als je een enorm gemotiveerd team hebt, en je zorgt dat de randvoorwaarden (goede machines, goede omgeving, goede sfeer) voor elkaar zijn, een groep ontwikkelaars in staat is om heel snel hele mooie dingen op te leveren. Nu moet ik nog kijken hoe ik dat kan doorvoeren bij mijn klanten.

    Al met al was het een enorm leuke en leerzame dag, waarbij ik een aantal mensen die ik eigenlijk alleen via Twitter sprak beter heb leren kennen. Wat mij betreft: volgend jaar weer!

    PS Nee. Ik ben niet vergeten om te beschrijven wat voor systeem we gemaakt hebben. Als je dat wilt zien zul je toch echt naar de Geek Night op DevDays moeten komen. Je kunt ook op twitter zoeken naar de hashtag #7slp of #s7lp (die laatste was een typfout maar werd wel gebruikt) om de voortgang van de dag te zien.

  • Terug naar de blogosphere.

    Ok. Ik weet het. We hebben het allemaal druk. Het is crisis/depressie/chaos of hoe je de markt ook wil noemen en dat betekent dat we allemaal een stapje harder moeten lopen. Is dat een reden om niet meer te bloggen? Nou uhm nee. Eigenlijk niet. Maar tel daar bij op dat ik de afgelopen maanden bezig geweest ben met het oprichten van een nieuw bedrijf, weken heb gemaakt van over de 130 uur (ik weet het, dat betekent meerdere nachten niet slapen maar werken) en je begrijpt dat mijn focus een tijdje ergens anders heeft gelegen.

    Maar die tijd is voorbij: ik ben weer druk bezig om orde in de chaos te scheppen en een van de gevolgen is dat ik weer veel meer tijd heb gekregen om zaken op te pakken die ik belangrijk vindt. Naast tijd voor mijn gezin houdt dat ook in dat ik meer tijd heb voor dotNed, de Surface user group en uiteraard het schrijven van artikelen, blogposts en het houden van lezingen. Goh, wat heb ik dat gemist.

    Dus.. ik ben weer terug en je hoort heel gauw nog veel meer van me!

  • CodeCamp 2009: Sessies en OpenSpace

    Als eerste: de sessies voor de CodeCamp 2009 zijn bekend en staan online op http://www.codecamp.nl We denken dat het een erg interessante mix van sessies is, met genoeg sessies voor iedereen om een aantal interessante onderwerpen voor iedereen. De agenda ziet er nu als volgt uit:

     

    Tijdslot Track A Track B Track C
    09:30 - 10:45 Around .net framework 4.0 in an hour (Ronald Guijt) ASP.Net - MVC 2.0 (Sander Gerz) Windows Mobile en het werken met data (Arjan van Huizen)
    11:00 - 12:15 ADO.NET EF 4.0 (Kurt Claeys) SharePoint Nightmares (Marianne van Wanrooij) iPhone development met jQTouch (Maurice de Beijer)
    13:15 - 14.30 VSTO 2010 met Office 2010 (Hassan Fadili) Modulaire Silverlight apps met Prism (Timmy Kokke) Microsoft Surface Development (Freena Eijffinger & Dennis Vroegop)
    14:45 - 16:00 VSTS 2010 (Pieter de Bruijn) Windows Identity Foundation (Michiel van Otegem) SQL Azure (Marcel Meijer)

    Naast deze sessies hebben we ook nog de OpenSpace sessies. Daar hebben we geen agenda voor maar dat ligt nu eenmaal in de aard van een OpenSpace gebeuren. In het kort komt het neer op het volgende: als je iets hebt waar je graag met een aantal mensen over wilt discussieren, schrijf je dat 's ochtends op een flip-over. Mochten mensen dat interessant vinden, dan kunnen ze een stem uitbrengen op dat onderwerp. In de lunchtijd (van 12:15 - 13:15) is dan de keuze aan de mensen waar ze heen gaan en aan welke discussie ze mee willen gaan doen. Heeft jouw sessie genoeg stemmen dan komen de mensen vanzelf wel naar je toe, zo niet: dan is je sessie blijkbaar niet interessant voor een grote groep. Het idee is dat we een aantal van deze sessies tegelijkertijd hebben zodat mensen kunnen kiezen wat ze doen. De inhoud van de lunch sessies laten we dus volledig aan de bezoekers over!

    Denk eens na over sessies of onderwerpen en discussieer mee met je mede-ontwikkelaars over jouw favoriete onderwerp!

    Ik kan haast niet wachten tot het 21 november is..

  • CodeCamp 2009: Call For Speakers

    Op 21 november 2009 organiseren de SDN, de dotNED User Group en VBcentral.nl samen de derde Nederlandse Code Camp. Een unieke dag, voortkomend uit een unieke samenwerking. Kenmerkend aan deze dag is, dat het een evenement is dóór ontwikkelaars en vóór ontwikkelaars!

    We zijn op zoek naar collega ontwikkelaars die het leuk vinden om ook een sessie voor hun rekening te nemen. Er is ruimte voor de meer traditionele sessies, maar bijvoorbeeld ook voor Chalk & Talk sessies. In principe is zijn de mogelijke onderwerpen zeer divers en worden door ons op voorhand niet beperkt. Ben jij geïnteresseerd om ook een sessie te doen? Laat het ons weten! Ook als je geen of nog niet zoveel sprekerservaring hebt ben je meer dan welkom. Indien je dat wilt willen we je hier best mee helpen.

    Het evenement vindt plaats in Rotterdam en meer informatie kun je vinden op de Code Camp website:www.codecamp.nl

    We zien graag jouw sessievoorstel tegemoet! Stuur deze zo snel mogelijk naar: andre@obelink.com.

    Een lijstje van onderwerpen waar deelnemers ondermeer om vragen:

    • Architectuur/Design patterns
    • Frameworks
    • Surface
    • .NET 4/3.5
    • Visual Studio 2010
    • VSTS
    • Open Source
    • SharePoint
    • ASP.NET/MVC/Web development/Silverlight
    • Windows 7
    • iPhone
    • DNN
    • VSTO
    • LINQ
    • SQL Server 2008
    • Geneva
    • BizTalk
    • Windows Presentation Foundation
    • Windows Communication Foundation
    • Windows Workflow Foundation
  • OpenCoffee was leuk!

    Op vrijdag 4 september hadden we bij dotNed de tweede OpenCoffee. Dit keer was de avond gesponsort door QDelft. De opkomst was beter dan verwacht: we hadden ongeveer twee keer zoveel mensen dan we eigenlijk verwacht hadden.

    Veel mensen die aanwezig waren kenden elkaar eigenlijk alleen via het online circuit, en dan met name via Twitter. Het was leuk om te zien hoe dat werkte: je zag mensen eerst voorzichtig aftasten ("hee, uhm, ben jij niet.") maar al snel werden de gespreken die eerst via Twitter verliepen doorgezet onder het genot van een biertje en een hapje. QDelft had de catering prima voor elkaar, er was genoeg te eten en te drinken voor iedereen, iets wat de sfeer enorm positief veranderde.

    De gesprekken gingen over allerlei verschillende onderwerpen. Uiteraard werd er veel over .net gesproken, maar ook andere onderwerpen passeerden de revue. Er zijn een hoop nieuwe contacten gelegd en oude contacten werden verstevigd. Het was dus een leuke manier om te netwerken.

    Wat mij betreft houden we binnenkort graag weer zo'n avond!

    Met dank aan Mendelt (met een passie voor Mono, dus mocht je een Mono expert nodig hebben moet je bij hem zijn :-) ) voor zijn inzet en uiteraard aan QDelft voor het hosten van deze avond!

  • OpenCoffee op 4 september

    Het is weer eens tijd voor een OpenCoffee / OpenBeer event bij dotNed. De meeste mensen zijn weer terug van vakantie, een mooie gelegenheid om bij elkaar te komen en te praten met je mede ontwikkelaars!

    Op een OpenCoffee zijn er geen presentaties, geen workshops en geen chalk 'n talks. Wel zijn er ontwikkelaars zoals jij, die graag over hun werk praten en ideeen willen uitwisselen!

    Deze OpenCoffee (uiteraard gratis, dit keer gesponsort door QDelft) vindt plaats op 4 september te Delft. Een routebeschrijving vind je hier. Aanmelden hoeft niet, maar we vinden het wel fijn als je even laat weten dat je langs komt. Dit in verband met de catering. Je bent van harte welkom vanaf 5 uur!

    Als je langs wilt komen stuur dan even een berichtje. Dat kan naar mij (dennis@dotned.nl of twitter @dvroegop) of naar de initiatiefnemer van deze OpenCoffee Mendelt Siebenga (twitter @mendelt)

    We zien je graag op de 4e!

  • Geslaagde launch van de Surface Gebruikers groep Nederland

    Logo_Sugar Donderdag 25 juni stond de dotNed meeting in het teken van Microsoft Surface. We hebben de avond benut om de nieuwe gebruikersgroep te lanceren, je vindt hier meer informatie over op http://surface.dotned.nl Sevensteps waren zo vriendelijk om een video impressie te maken, deze kan je hier bekijken.

    Wat mij betreft houden we gauw weer zo'n avond!

  • Surface, design challenges

    Surface is cool. Iedereen die er naar gekeken heeft is dat wel met me eens. Hoewel de hardware op het eerste gezicht niet echt indrukwekkend is (een tafel met een PC erin, dat is toch niets nieuws?) zijn er behoorlijk wat geavanceerde technieken toegepast. Niet voor niets heeft Microsoft meer dan 100 patenten aangevraagd op toegepaste ideeen in de tafel. De meeste daarvan hebben te maken met het projectie en vision gedeelte, ik neem aan dat er heel wat drempels overwonnen zijn om dit allemaal voor elkaar te krijgen.

    Het programmeren voor Surface is makkelijk. Nou ja, makkelijk voor mensen die ervaring hebben met WPF. Ook XNA kenners kunnen goed aan de slag met Surface maar ik denk dat 90% van de developers met de WPF versie aan de gang gaan.

    Als ik een demo geef aan mensen zegt iedereen dat het zo eenvoudig is om software te schrijven voor Surface. WPF, wat extra controls en meer heb je niet nodig. Iedereen kan het! We moeten echter niet vergeten dat 'die paar extra controls' heel wat manjaar werk in zich hebben: het is vaak zo dat hoe makkelijker iets in het gebruik is, hoe moeilijker het is geweest om het te bedenken en te bouwen.

    Maar goed,  hoewel de hardware dus best indrukwekkend is en de SDK goed in elkaar zit, is er eigenlijk niets dat ons tegenhoudt om hele mooie applicaties te schrijven voor Surface. Toch? Nou. er is nog een klein dingetje dat we moeten doen: het verzinnen van een goede applicatie voor Surface.

    En daar schuilt het grote probleem.

    Surface is een heel nieuw platform. Niet qua hardware en zeker niet qua software, maar wel voor wat betreft het gebruik.

    Surface is een platform voor de Natural User Interface (NUI). Dit is iets heel anders dan de Graphical User Interface (GUI) die we gewend zijn. Ik durf zelf te zeggen dat de overgang van GUI naar NUI net zo groot, dan niet groter is dan de overgang van Command Line Interface (CLI) naar GUI! En dat was al geen makkelijke stap!

    In een GUI maken we gebruik van metaforen. We maken afbeeldingen die iets voorstellen in de echte wereld. Denk aan een icoontje van een prullebak om aan te geven dat we iets weg willen gooien. Een tekening van een papiertje zal wel iets met afdrukken te maken hebben, en zo voorts. Ook het gebruik van de mousepointer, menus, windows en dergelijke zijn allemaal metaforen voor dingen uit het echte leven. De bedoeling is dat de gebruikers die zaken herkennen en snappen wat de bedoeling is. Dat dit niet altijd zo werkt wordt wel bewezen door de enorme hoeveelheid trainingen en documentatie die applicaties nodig hebben om duidelijke te maken wat de gebruiker met de software kan doen. Blijkbaar is voor veel gebruikers de mentale stap van abstract icoontje naar 'real-life' toch te groot! Voor de duidelijkheid: ik heb het niet over mensen zoals jij en ik: wij zijn dit gewend en we weten niet beter. Ik praat hier over de gemiddelde gebruiker die slechts af en toe achter een computer zit. Voor die mensen is het allemaal erg ingewikkeld!

    Een NUI gaat een stap verder. In plaats van een abstracte afbeelding maken we gebruik van echte, fysieke objecten. In plaats van gegevens op een formulier in te vullen met je naam en wachtwoord om in te loggen, leg je je visitekaartje neer op de tafel. De machine weet nu wie je bent, je hoeft verder geen actie te ondernemen. In plaats van iets te selecteren en dan op een knopje met een prullebak te drukken, sleep je een foto op het scherm naar een prullebak: zelfs mijn dochter van 6 snapt dat.

    De uitdagingen voor Surface zijn echter groter.

    In Surface kennen we in principe geen CurrentUser. Een applicatie kan gelijkertijd door meerder mensen bedient worden. Dat betekent dat als we een systeem schrijven we bijvoorbeeld geen acties kunenn maken die de state van de gehele applicatie veranderd. Een voorbeeld verduidelijkt dit:

    Stel, we maken een vingerverf applicatie. Op het scherm kunnen we een kaartje neerleggen met een kleur erop: op het moment dat bijvoorbeeld een groen kaartje neergelegd wordt worden alle vingerbewegingen op het scherm neergezet als groene strepen. Halen we het groene kaartje weg en vervangen we deze door een rode, kun je met rood verfen. Hartstikke leuk natuurlijk, maar wat als jij en ik nu samen aan onze versie van Mona Lisa bezig zijn? Ik wil het haar zwart maken, maar net op het moment dat ik met mijn vingers dat begin in te kleuren vervang jij het zwarte kaartje door eeen witte (want jij wilt de ogen inkleuren). Op datmoment kan ik niets meer doen: ik moet wachten tot jij klaar bent met wit.

    Daarnaast zijn wij, als traditionele programmeurs, gewend aan een aantal standaard metaforen. Zo zie je in veel applicaties een of andere vorm van een 'save' button om de huidige status op te slaan. Als je hier over nadenkt is dat best vreemd: in het echte leven doen we dat ook niet. Als ik iets op een papiertje schrijf hoef ik dat niet op te slaan. Dus in een NUI zou ik dat ook niet moeten doen!

    Nu is het niet zo moeilijk om software zo te schrijven dat de state altijd opgeslagen wordt. Maar wat als je nu de state voor meerdere cases wilt opslaan? In onze vingerverf applicatie wil ik een tekening kunnen maken, maar ik wil niet dat jouw tekening de mijne overschrijft (andersom vind ik persoonlijk niet zo erg :-) ) Hoe lossen we dat nou op? Dat kunnen we bijvoorbeeld doen door iedereen een object te geven (bijvoorbeeld een pasfoto). Op het moment dat die pasfoto op het systeem ligt weet Surface voor welke gebruiker deze tekening is. Als ik mijn foto weg haal en jij legt de jouwe neer, zal mijn kunstwerk opgeslagen worden en komt de jouwe tevoorschijn (indien het tekenen een groepsactiviteit is kunnen we een groepsfoto plaatsen.) Geen 'save' knop meer: altijd toegang tot mijn werk en ik heb geen toegang tot jouw werk. Precies zoals een normale gebruiker verwacht.

    Surface is een tafel. Het leuke van een tafel is dat mensen er om heen gaan staan. Het nadeel is dus wel dat je geen applicaties kunt maken die een duidelijke onder- en bovenkant hebben. Immers, als er tekst op het scherm staat is dat voor een persoon goed te lezen, maar de andere gebruikers kunnen daar niets mee (nou ja, ze kunnen het op z'n kop lezen maar handig is dat niet). Je moet dus rekening houden met dit aspect. Niet eenvoudig!

    Ik denk dat veel ontwikkelaars in het begin dit soort fouten gaan maken. Ook ik maak me daar nog steeds schuldig aan. Ik heb op zich een goede oplossing: mijn dochter van 6 is een geweldige beta tester. Hoewel ze niet begrijpt wat de systemen doen,  kan ze er wel mee overweg. De User Interface heeft geen geheimen voor haar. Als die geheimen er wel zijn, heb ik mijn werk niet goed gedaan.

    Ik hoef nu alleen nog maar even een killer applicatie voor Surface bedenken..

  • Introducing: Surface User Group Netherlands

    Logo Surface UG

    Met gepaste trots introduceren we een nieuw onderdeel van dotNed: de Surface User Group Nederland. We hebben een nieuwe Surface User Group Website (nog in ontwikkeling, maar de homepage staat er), een nieuw logo en veel nieuwe ideeen!

    We denken dat het introduceren van een nieuwe GG (gebruikersgroep, voor het geval je de afkorting niet kent) een aantal vragen opwerpt:

    • Waarom een nieuwe gebruikersgroep?
    • Waarom kan dit niet gewoon onder de vlag van dotNed?
    • Wat is uberhaupt Surface?

    In deze blogpost zullen we uitleggen wat het idee is en wat we van plan zijn met de nieuwe GG.

    Indien je niet weet wat Microsoft Surface is, raad ik je aan even naar de officiele Microsoft Surface Website te gaan. Daar kun je een voorproefje krijgen van wat het platform biedt.

    We denken dat Surface een concept is dat een revolutie in computer gebruik teweeg kan brengen. Het platform is dusdanig vernieuwend dat mensen niet goed weten wat het is en wat ze er mee kunnen. In tegenstelling tot het .net framework snappen veel mensen het concept van Surface niet helemaal.

    In de oprichtingsnotulen van dotNed staat dat het doel van dotNed is "het promoten van het .net framework en het verspreiden van kennis hierover". Nu is het promoten van het .net framework niet echt meer nodig: iedereen weet ondertussen wel wat het is en waarom het gebruik van .net een goed idee is. De laatste jaren houden we ons dus voornamelijk bezig met het verspreiden van de kennis over .net. Dat doen we door maandelijks een gratis bijeenkomst te organiseren, aanwezig te zijn op de diverse grote events en door middel van onze blogs.

    Voor Surface ligt dit anders. Ook hier geloof ik dat het belangrijk is om de uitgangspunten van dotNed uit te dragen, namelijk het promoten van het platform en het uitwisselen van kennis hiervan, maar het nut van Surface is nog niet bij iedereen duidelijk. We willen ons bij de Surface GG gaan richten op het beantwoorden van de onderstaande 3 vragen:

    WatWaaromHoe"Wat" gaat in op de vraag wat Surface nou precies is. Wat is het voor machine, wat is het concept erachter, hoe bestel ik er een, wat is de techniek erachter. "Waarom" is meer conceptueel: waarom zou ik als organisatie zo'n machine aanschaffen? Waarom is dat handig voor mij? Wat kan ik er allemaal mee dat mij een voorsprong op mijn concurrenten geeft? Met andere woorden: met het beantwoorden van de "waarom" vraag gaan we in het conceptuele niveau van Surface.

    "Hoe" is meer technisch: hoe maak ik nu applicaties op Surface? Hoe voldoe ik aan de guidelines van Microsoft? Hoe plaats ik een ElementMenu in mijn ScatterViewItem? (Mocht je niet weten wat een ElementMenu of een ScatterViewItem is, dan geven we daar natuurlijk eerst uitleg over :-) ) Naast de technische aspecten zullen we ook diep ingaan op vraagstukken op het gebied van User Experience. Het concept van Surface stelt hele andere eisen aan de user interface dan een Windows of Web applicatie.

    Je ziet dat we met deze 3 vragen verschillende doelgroepen bedienen. Met de "waarom" vraag helpen we de decisionmakers, met de "hoe" vraag helpen we de ontwikkelaars op dit platform. De "wat" vraag is uiteraard bedoelt voor iedereen die iets meer over Surface wil weten.

    Hoe bereiken we nu dat we de goede antwoorden geven? We hebben daarvoor een paar mogelijkheden:

    1. Blog posts. Dit is behoorlijk eenrichtingsverkeer, maar we kunnen daardoor wel veel informatie bij jullie kwijt
    2. Een forum. Dit zal de plek worden waar mensen vragen kunnen stellen en beantwoorden. Wij van dotNed zullen hier een actieve rol in spelen.
    3. Bijeenkomsten. We zullen regelmatig bijeenkomsten organiseren, bij voorkeur bij bedrijven die een Surface Unit hebben staan, om daar hands-on ideeen op te doen. Dit is uiteraard de beste manier om de Surface Unit van dicht bij te zien en er mee te werken.
    4. Lezingen geven. Om de wat/waarom vragen te beantwoorden zijn we altijd bereid om naar organisaties toe te gaan om ze daar uit te leggen wat Surface is en wat het voor kun kan doen (verzoeken even mailen naar dennis@dotned.nl ) Uiteraard geldt dit niet alleen voor commerciele organisaties maar ook op het gebied van onderwijs en non-profit organisaties
    5. Acte de presence geven op events van andere partijen. Denk aan een DevDays, SDN events en dergelijke.
    6. Delen van code libaries, met daarin 'best practices' voor het ontwikkelen op Surface.

    De mensen achter de Surface GG zijn allemaal early adopters van het platform. We hebben de trainingen gevolgd en hebben reeds een aantal applicaties opgeleverd op Surface bij onze klanten. We mogen dan ook wel stellen dat het kennis niveau behoorlijk hoog is, maar we willen altijd meer leren en weten wat er allemaal gebeurd op dit platform! De Surface GG wordt momenteel aangestuurd door drie personen:

    Mocht je dus bij een bedrijf werken die een Surface heeft, of worstelt met de vraag of ze er een (of meerderer!) aan moeten gaan schaffen, neem dan even contact op met ons zodat we kunnen kijken wat we voor elkaar kunnen betekenen!

    We zijn een community. Een community bestaat bij de gratie van de communityleden. Wij hebben veel ideeen, maar die kunnen pas tot leven komen als jullie mee denken en actief meedoen. Wat dat betreft is geen enkel verschil met dotNed, we zijn er voor en door jullie! We willen graag jullie ideeen horen. Dat kan op verschillende manieren:

    We hopen er met z'n allen iets moois van te maken!

Meer Berichten Volgende pagina »

This Blog

Post Calendar

<September 2010>
SuMoTuWeThFrSa
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

Syndication

Powered by Community Server, by Telligent Systems