Smart Apps voor Smart Devices met VS.NET 2003
Een van de meest opvallende nieuwe features in VS.NET 2003 is de mogelijkheid om er applicaties mee te ontwikkelen voor zogeheten smart devices. Voor Microsoft is een smart device het een of andere apparaat waarop Pocket PC of Windows CE draait. In de praktijk komt dat op dit moment met name neer op PDA's en een enkele opgevoerde GSM, maar in de naaste toekomst zullen we overspoeld worden door apparaten met een ingebouwd "Windows-computertje" zoals audio, (GPS) navigatie, koelkasten, of wat de industrie maar weet te verzinnen. Met VS.NET 2003 is het maken van krachtige maatwerk applicaties voor deze apparatuur binnen het bereik van iedere ontwikkelaar gekomen.
VS.NET 2003 heeft een aantal nieuwe keuzes bij het maken van een nieuw project:
Ik kies hier voor Smart Device Application. Het wordt een applicatie die op mijn PDA de UI zo veel mogelijk gaat benutten, als ware het een kleine Windows applicatie. Daarbij zal ik zeker concessies moeten doen; per slot van rekening heeft een Pocket PC device vaak maar 32 MB geheugen aan boord en dit geheugen wordt ook nog eens gebruikt als opslagmedium. Vergeleken bij de 100-en MB's aan werkgeugen en de vele GB's aan schijfruimte op een hedendaagse PC blijft dat natuurlijk behelpen.
Om de applicatie te bouwen heb ik geen fysiek smart-device nodig; een onderdeel van VS.NET 2003 is een geïntegreerde Pocket PC emulator.
Deze emulator biedt de volledige functionaliteit van een Pocket PC of Windows.CE device. Het leukste is nog wel de netwerk functionaliteit. Als je PC een internet connectie heeft, dan kan je met de Explorer van de emulator ook surfen. Normaliter bedien je een Pocket PC met een stylus, in de emulator gebruik je de muis. Om tekst in te voeren kun je de letters stuk voor stuk gaan tekenen, maar het toetsenbord van je PC werkt ook.
Het .NET compact framework
Het verschil tussen Windows CE en Pocket PC is niet erg duidelijk, maar ook niet iets waar je je druk over hoeft te maken. Met VS.NET 2003 bouw je applicaties die werken met het .NET compact framework. Dat framework draait op beide platforms. Het .NET framework is in feite niet veel meer dan een grote verzameling class-libraries, het compact framework is hier een subset van.
Het .NET framework is georganiseerd in een groot aantal namespaces. Het compact framework biedt (een groot aantal van) dezelfde namespaces, maar de implementatie van de classes in deze namespaces wordt verzorgd door andere dll's. In een Windows Forms applicatie wordt de namespace Windows.Form geïmplementeerd door System.Windows.Forms.dll in de directory Windows\Microsoft.NET\Framework;in een smartdevice applicatie door de System.Windows.Forms.dll in een subdirectory van de compact framework SDK.
Het compact framework bevat minder controls en componenten dan het volledige framework.
Het compact framework bevat minder controls en componenten dan het volledige framework. De componenten hebben ook minder properties, deels omdat de properties niet door het onderliggende platform worden ondersteund, deels uit besparing. Er is ook bezuinigd op methodes en met name op hun overloads. Een overload is een andere versie van een methode die met een ander aantal parameters of parameters van een ander type werkt. Het volle framework is erg rijk aan overloads. De documentatie van het compact framework valt samen met die van het volle framework. Bij elke class, property en method wordt vermeld of die ook te vinden is in het compact framework.
Het enige grote gemis in het compact framework is dat het geen getypeerde datasets ondersteunt.
Het enige grote gemis in het compact framework is, wat mij betreft, dat het geen getypeerde datasets ondersteunt. In voorgaande verhalen voor het SDGN magazine en op mijn website (www.gekko-software.nl/dotnet/index.htm) ben ik uitgebreid ingegaan op de mogelijkheid om met XSD schema's complexe datastructuren inclusief hun meta-data uit te wisselen. Helaas begrijpt het compact framework deze schema's niet. Ik neem aan dat voor het werken met deze schema's meer resources nodig zijn dan de huidige generatie van "smart" devices kunnen bieden. Het compact framework kan wel goed werken met XML in de vorm van ongetypeerde datasets.
Een smartdevice application
Na deze inleiding wordt het tijd voor de praktijk. De wizard van VS.NET heeft een form gemaakt van een vaste grootte. Deze grootte valt samen met de schermgrootte van het device. Het form kent veel minder layout mogelijkheden dan een "volwaardig" Windows form; dat zie je in het property window.
Alle beschikbare controls en componenten staan samen op één pagina van de toolbox. Naast een aanzienlijk deel van de Windows controls zie je een timer als enige component. Op het form plaats ik een tabcontrol, op de eerste tab komt een listbox en een button.
Een InputPanel is een specifiek smart control. Het inputpanel van een Pocket PC gebruik je om tekst op in te voeren. Je kunt schrijven met de stylus of op de toetsjes van het toetsenbordje klikken.
Ook heeft elk smartform een menu, met desgewenst verdere submenu's. Het invoeren hiervan gaat net zo makkelijk als in een Windows applicatie.
Elk menu-item heeft een event-handler. Het coderen daarvan is een kwestie van C# kloppen.
private void menuItem10_Click(object sender, System.EventArgs e)
{
MessageBox.Show("Is op");
}
Centraal in .NET staan webservices. Ook een smart device applicatie kan daar gebruik van maken. De emulator heeft verbinding met het netwerk, en het is dus zeker de moeite waard om dit eens te gaan proberen.
De webservice
De webservice voor dit project maakt een klein zijstapje naar één van de vele andere spannende componenten die .NET te bieden heeft. De webservice gaat informatie geven over de inhoud van het eventlog van de server. Hij zal een lijst opleveren van alle errors die er in het System log staan. Dit klinkt ingewikkeld, maar al het lastige werk wordt verzorgd door een EventLog component.
Aan de solution voeg ik een nieuw project toe, een ASP.NET webservice. Om in stijl te blijven noem ik hem FoodService. In de components pagina van de toolbox staat het EventLog component; hiervan drop ik er één op de component designer van de webservice. In het property window kies ik als log voor System.
Je hebt geen database nodig om een dataset te maken.
De inhoud van dit eventlog levert een hele lijst van data op. De aangewezen manier om die lijst te verpakken is natuurlijk weer een XML dataset. Je hebt geen database nodig om een dataset te maken. Je kunt een nieuwe, blanko dataset aan het project toevoegen via Add ew Item in het File menu, of via de rechter muis knop in de solution explorer. Om de dataset te ontwerpen hoef je niet veel van XML te weten. Door dubbelklikken kom je in de dataset designer van VS.NET terecht. De dataset krijgt één tabel met twee kolommen. De eerste kolom wordt een integer element. Door hier het autoincrement attribuut op true te zetten levert deze kolom automatisch een identificerend nummer op. De tweede kolom wordt een string om de melding uit het eventlog op te slaan.
Nu staat alles klaar om de webservice een webmethod te geven. Ik noem die Eventlog en hij levert een dataset op. Het compact framework kan niet werken met getypeerde datasets; het resultaat-type van de methode moet dus DataSet zijn. Maar binnen de implementatie van de method - die draait tenslotte op de server - is het erg handig om met een getypeerde DataSetEventLog te kunnen werken.
[WebMethod]
public DataSet EventLog()
{
DataSetEventLog ds = new DataSetEventLog();
foreach(EventLogEntry entry in eventLog1.Entries)
{
if (entry.EntryType == EventLogEntryType.Error)
ds.EventlogEntries.AddEventlogEntriesRow(entry.Message);
}
return ds as DataSet;
}
De EventLog component heeft een Entries property. Die levert een EventLogEntry collectie op die je met foreach kunt doorlopen. Elke entry heeft een property EntryType die te testen is tegen een lid van de EventLogEntryType enumeratie. De entries die een Error zijn, worden toegevoegd aan de dataset. Ik maak daarbij dankbaar gebruik van de "typische" properties en methods van de dataset. Tot slot typecast ik het resultaat naar een DataSet.
Nu hebben we een webservice die alle errors in het eventlog van de web-server oplevert als XML dataset.
De webservice gebruiken in de smartdevice app
Het refereren aan webservices was in VS.NET al niet zo'n toer, in VS.NET 2003 maakt de wizard het nog makkelijker.
Om de webservice te gebruiken in de smart device app voeg je hem toe aan de web references. Het refereren aan webservices was in VS.NET al niet zo'n toer, in VS.NET 2003 maakt de wizard het nog makkelijker.
De webservice is te vinden op de local machine. Als web reference naam zal de wizard localhost voorstellen. Ik maak hier FoodServiceHost van. Localhost is een URL die de webbrowser van je PC zal weten te vinden, maar de applicatie gaat draaien op de emulator. Op die emulator draait geen lokale IIS en die kent dus ook geen localhost. Hij gaat via een DNS naar localhost zoeken en zal daar niet uitkomen. Je zal de URL van de webreference aan moeten passen. Als je zelf geen lokale DNS hebt draaien, is het het makkelijkste om meteen maar het IP adres van de PC in te vullen.
Na deze aanpassing is de webservice beschikbaar en ga ik hem gebruiken om de listbox te vullen met de entries uit de webservice
private void button2_Click(object sender, System.EventArgs e)
{
Cursor.Current = Cursors.WaitCursor;
DataSet ds = new DataSet();
try
{
FoodServiceHost.FoodService fs = new FoodServiceHost.FoodService();
ds = fs.EventLog();
DataTable dt = ds.Tables[0];
listBox1.Items.Clear();
treeView1.Nodes.Clear();
for (int rn =0; rn < dt.Rows.Count; rn++)
{
listBox1.Items.Add(dt.Rows[rn].ItemArray[1].ToString());
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
Cursor.Current = Cursors.Default;
}
}
Het ophalen van de data en het vullen van de listbox kan even duren. De cursor vertelt dit aan de gebruiker. Om de data op te vangen declareer ik een dataset. De webservice wordt gestart en het resultaat van de call naar de EventLog methode wordt opgevangen in de dataset. Dit gaat goed: hij wordt gevuld met de binnenkomende XML.
Nu is het wel een beetje lastig dat de dataset niet getypeerd is. Ik ga ervan uit, dat de eerste tabel de gewenste data bevat. Die stop ik in een tussenvariabele getypeerd als een DataTable. De inhoud moet ik uit de ItemArray property van de rijen halen. Dat de kolom met index 1 de gewenste tekst bevat is ook maar een weet.
Stel dat de dataset wel getypeerd was, dan had het zo gekund:
ds = fs.EventLog();
listBox1.Items.Clear();
treeView1.Nodes.Clear();
foreach (DataSetEventLog.EventlogEntriesRow dr in ds.EventlogEntries)
{
listBox1.Items.Add(dr.EventMessage);
}
Maar alleen al het toevoegen van het DataSetEventlog type aan de applicatie leidt tot onoverkomelijke problemen. Het is op zich al lang mooi dat de applicatie de binnenkomende XML zonder lastige truuks weet te lezen. Daarnaast is het ook goed te zien hoe je alle mooie zaken van C# kunt gebruiken. Zo vangt de exception handling alle eventueel optredende problemen, zoals het niet kunnen vinden van de webservice, netjes op.
Een treeview component
Nu we een (potentieel) lange lijst van gegevens van de webservice binnen hebben gekregen, is het leuk om hier nog iets meer mee te gaan doen. Op de tweede tabpagina plaats ik een treeview, die wordt gevuld door individuele regels in de listbox te selecteren.
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
treeView1.Nodes.Add(
(sender as ListBox).Items[(sender as ListBox).SelectedIndex].ToString());
}
Het click-event wordt afgevuurd door de listbox; de listbox komt als sender binnen in de parameters. Met de as operator typecast je het object naar een listbox, waarna alle properties van de listbox in kwestie benaderbaar zijn. Het lijkt misschien een beetje een omweg, maar het voordeel is dat je deze eventhandler nu ook kan gebruiken als eventhandler van een andere listbox. Wie bekend is met Delphi zal dit heel bekend voorkomen; mocht je meer willen weten hoe dit in C# werkt dan vind je dat in SDGN magazine #69 of op www.dotnetjunkies.com/tutorials.aspx?tutorialid=313 .
Een treeview control bevat een lijst van nodes. De control zelf heeft een verzameling root nodes. Elk van de nodes in deze verzameling heeft weer een verzameling van childnodes. Zo kan de control een in principe eindeloze boom opbouwen. De inhoud van een geselecteerd listboxitem wordt toegevoegd aan de rootnodes van de control.
De tabpagina met de treeview bevat ook een textbox en een button. Als je een item in de treeview selecteert, dan wordt de text daarvan gekopieerd naar de textbox.
private void treeView1_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
{
textBox1.Text = e.Node.Text;
}
Het event in kwestie krijgt de geselecteerde node in de tweede parameter van het event mee. De geselecteerde tekst is zo makkelijk te kopieren.
Je kunt de tekst nu wijzigen. Je ziet dat het zaak is de invoercontrols niet te laag op het scherm te zetten omdat ze anders bedekt worden door het input panel. Onder dat panel staat een button. Die voegt de ingevoerde tekst toe als child-node van het oorspronkelijk geselecteerde item.
private void button1_Click(object sender, System.EventArgs e)
{
try
{
treeView1.SelectedNode.Nodes.Add(textBox1.Text);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
De code voegt de tekst toe aan de (child-)nodes collectie van het geselecteerde item. Er ontstaat zo een hele boom van georganiseerde opmerkingen.
Debuggen en deployen
De emulator is niet alleen erg handig om de smart applicatie te testen, je kunt er ook mee debuggen. In VS.NET zet je een breakpoint en als de emulator die regel code uitvoert zal die stoppen.
Met de vele debug tools kun je nu alle variablen inspecteren.
Een applicatie op een smart device wordt in principe nooit afgesloten. Op het device zelf kun je diep in het settings menu een applicatie stoppen. VS.NET kan het ook heel goed: als je daar kiest voor stop debugging, dan sluit de applicatie meteen.
Zo'n emulator is prachtig, maar uiteindelijk wil je de applicatie natuurlijk meenemen op je PDA
Zo'n emulator is prachtig, maar uiteindelijk wil je de applicatie natuurlijk meenemen op je PDA, smartphone of koelkast. Zelf heb ik al een tijdje een Pocket PC PDA. Dat ding draait nog onder Pocket PC 2000 en toen ik hem kocht, was .NET nog geen verkrijgbaar product. De PDA zit met een USB cradle aan mijn PC en beviel me tot nu toe prima. Ook VS.NET ziet hem zitten: naast de emulator kan ik ook kiezen voor mijn "Pocket PC device". De eerste keer dat ik de applicatie vanuit VS.NET hierop probeerde te draaien, zag VS.NET dat daar geen .NET framework op stond en begon dat prompt te installeren. Een paar minuten wachten later draaide de smart applicatie ook op de fysieke PDA, compleet met alle functionaliteit. Zolang de PDA in zijn cradle staat kan die bij het netwerk en vindt die de webservice. Nog mooier is de debugger: ook als ik op de fysieke PDA op de button klik, stopt VS.NET op het breakpoint.
Tot slot
Dit hele verhaal is gemaakt met de final beta van VS.NET 2003. Inmiddels is de definitieve versie gelanceerd, maar op de beta heb ik eigenlijk helemaal niets aan te merken. Het werkt allemaal en is zo stabiel als wat. Het is jammer dat ik mijn dataset stokpaardje niet volledig heb kunnen uitleven, maar ook zonder dat vind ik het allemaal smart zat.