Generics to the rescue

Door RobIII op dinsdag 16 december 2008 12:38 - Reacties (3)
CategorieŽn: Development, Life as a developer, Views: 4.477

Als ik ergens een hekel aan heb in het programmeer-vak dan is het wel het koppelen van de GUI laag aan alle onderliggende code. Met een beetje uitgebreide GUI heb je al gauw ellenlange pagina's code waarin je niet veel anders doet dan controls opzetten, aan events koppelen en diezelfde controls' events weer vangen en afhandelen in de onderliggende (interessante(re) :P ) laag etc. Als er iets 'saai' werk is, dan is het wel een GUI opzetten.

Zo betrapte ik mezelf vandaag op een stuk code dat regelmatig voorkomt op verschillende plaatsen. Stel; ik heb een form waar een lijst met gebruikers op staat in een listview. Dubbelklik ik een gebruiker in dat form, dan ga ik in de huidige open forms kijken of er al een form met die gebruiker open staat en als dat het geval is brengen we het naar voren. Zo niet dan open ik een nieuwe instantie voor die gebruiker en toon het nieuwe form. Dan krijg je al gauw code als:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//Retrieve user
User SomeUser = foo.bar.GetUser(123);
...
//Find open form, if any
EditUserForm myForm;
foreach (Form f in Application.OpenForms)  //Iterate all open forms
{
    //Check for the correct type
    if (f.GetType().Equals(typeof(EditUserForm)))
        //We have the correct type; now check the property containing
        //the editable object
        if ((f as EditUserForm).User.id == SomeUser.id)
            //We have a match!
            myForm = f;
}
//Got a match?
if (myForm != null)
    //Show the form by bringing it to the front
    myForm.BringToFront();
else
{
    //Create a new instance and show the form
    myForm = new EditUserForm(SomeUser);
    //Wire up events and show
    myForm.ChangesApplied += delegate(object sndr, EventArgs ea) {
        this.RefreshList();
    }; 
    myForm.Show();
}
...


Diezelfde code schrijf je dan ook weer als je een form zoekt met een bepaalde auto. Of met een bepaalde supermarkt. Of... Je herhaalt jezelf constant, omdat de voorwaarde waar je aan wil voldoen telkens anders is en je dus de ene keer een .User.id property van een form gebruikt om te bepalen of je het juiste form te pakken hebt en de andere keer het .Car.id property. En als je het juiste form niet kunt vinden wil je een nieuwe instantie van dat form waarbij je meteen de betreffende gebruiker, auto, supermarkt etc. meegeeft.

En toch kan het zoveel makkelijker. Met behulp van generics maken we daar natuurlijk een generieke methode voor:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Windows.Forms;

public static class FormHelper
{
    /// <summary>
    /// Searches for a form that matches the conditions defined by the predicate
    /// and returns the first occurence with the application's open forms
    /// </summary>
    /// <typeparam name="T">The type of the form to look for</typeparam>
    /// <param name="match">The predicate that defines the conditions of the form
    /// to search for</param>
    /// <returns>Returns the form when found, null otherwise</returns>
    public static T GetWindow<T>(Predicate<T> match)
        where T : Form
    {
        return GetWindow<T>(match, null);
    }

    /// <summary>
    /// Searches for a form that matches the conditions defined by the predicate
    /// and returns the first occurence with the application's open forms
    /// </summary>
    /// <typeparam name="T">The type of the form to look for</typeparam>
    /// <param name="match">The predicate that defines the conditions of the form
    /// to search for</param>
    /// <param name="newinstance">A new instance to return when no instance 
    /// could be found</param>
    /// <returns>Returns the form when found, returns the new instance specified
    /// by newinstance otherwise</returns>
    public static T GetWindow<T>(Predicate<T> match, T newinstance)
        where T : Form
    {
        foreach (Form form in Application.OpenForms)
            if (form.GetType().Equals(typeof(T))
                &&
                match.Invoke(form as T))
                return form as T;
        return newinstance;
    }
}


De eerste functie geeft het gezochte form wanneer dat gevonden wordt terug en null wanneer er geen form gevonden kon worden. Deze functie roept intern een overloaded functie aan welke als tweede parameter een nieuwe instantie accepteert waarbij deze parameter op null gezet wordt waardoor we dus de garantie hebben dat de return waarde null zal zijn wanneer er niets gevonden wordt.

De tweede functie voert het eigenlijke werk uit; deze accepteert een predicate en een form instance als nieuwe instance wanneer er geen open form gevonden wordt dat aan de voorwaarde voldoet. Er wordt door alle open forms gelopen en als het type van dat form overeenkomt met het gewenste type dan wordt de predicate uitgevoerd om te zien of het form voldoet aan de voorwaarde(s) die we stellen. Is dat het geval dan wordt een referentie naar het gevonden form geretourneerd en zo niet dan wordt de nieuwe instantie geretourneerd.

Concreet betekent dit dat we nu lekker lui naar forms kunnen zoeken:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void EditUser(User user)
{
    //Get existing form if any
    EditUserForm editform = FormHelper.GetWindow<EditUserForm>(
        delegate(EditUserForm f) { 
            return (user.id != 0) && (f.User.id == user.id);
        }, new EditUserForm(user));
    //Form already shown?
    if (editform.Visible)
        editform.BringToFront();
    else
    {
        //Wire up events and show
        editform.ChangesApplied += delegate(object sndr, EventArgs ea) {
            this.RefreshList();
        };
        editform.Show();
    }
}


We krijgen nu dus in 1 regel code het juiste open form of een nieuwe instantie ervan terug en kunnen dat vervolgens tonen danwel naar voren halen. Zoals je in de anonymous method ziet stel ik hier de voorwaarde dat het userid moet matchen om zeker te weten dat we het juiste form te pakken hebben en dat het geen 0 mag zijn (nieuwe gebruikers die nog niet opgeslagen zijn hebben nog geen id; in dit geval 0) omdat ik wťl meerdere forms met nieuwe gebruikers wil kunnen openen.

http://tweakers.net/ext/f/YUObulvF2QBo2nnFNqHRAF9Z/full.gif

Soms is het leven zo simpel 8)

TwitterNuJIJeKudosFacebookFriendfeedGoogle BookmarksDiggdel.ici.ousTechnoratiSphinnMixxStumpleUponYahoo! BookmarksMaak je eigen RML op RobIII.nl!

Volgende: War of the Worlds 02-'09 War of the Worlds
Volgende: Thank you NVidia! 12-'08 Thank you NVidia!

Reacties


Door Tweakers user bimm, dinsdag 16 december 2008 15:11

Je hebt helemaal gelijk. Generics zijn erg handig. Ik moet zeggen dat ik ze zelf niet zo heel veel gebruik omdat C# slechts mijn hobby taal is die ik s'avonds doe, na de vriendin en na het eten .. en huishouden.

Ik vind generic collections ook zeker prettig.

Echter zit ik op dit moment meer te ruzien over het thread-safe maken van IList objecten die als public property zijn van een class. Misschien dat ik er maar een vraag over moet stellen in het forum. Mmm.... ja.

Door Tweakers user Gerco, dinsdag 16 december 2008 21:01

Maak je nu niet altijd een nieuwe instance aan van de Form, om die vervolgens meteen weer weg te laten gooien door de garbage collector wanneer er al een instance is? Lijkt me niet erg handig wanneer de initialisatie van een bepaald Form zwaar is (veel db lookups ofzo).

Door Tweakers user RobIII, woensdag 17 december 2008 02:12

Lijkt me niet erg handig wanneer de initialisatie van een bepaald Form zwaar is (veel db lookups ofzo).
De initialisatie (dus 'dure db lookups' e.d.) hou ik meestal in het _Load event; in de ctor staan enkel wat 'wire-up' zaken zoals het koppelen van events van controls aan bepaalde functies. Maar het zou ook 'uitgesteld' kunnen worden door een delegate mee te geven in plaats van een nieuwe instantie en is relatief simpel toe te passen in bovenstaande code.

[Reactie gewijzigd op donderdag 14 januari 2010 12:58]


Reageren is niet meer mogelijk