PDA-applikationer med .NET: Lösningar till tentamen 2006-06-10

Observera att detta är förslag på lösningar. Det kan finnas andra lösningar som också är korrekta, och det kan hända att en del av lösningarna är mer omfattande än vad som krävs för full poäng på uppgiften. En del av lösningarna är kanske inte fullständiga, utan hänvisar bara till var man kan läsa svaret.

Uppgift 1 (10 p)

Den här uppgiften ska man bara göra om man läst kursen enligt den gamla kursplanen (höstterminen 2005), när det inte ingick några obligatoriska inlämningsuppgifter i kursen.

using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Data;

namespace Tentauppgift1
{
    /// <summary>
    /// Summary description for Form1.
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.CheckBox checkBox1;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.MainMenu mainMenu1;

        public Form1()
        {
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();

            //
            // TODO: Add any constructor code after InitializeComponent call
            //
        }
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            base.Dispose( disposing );
        }
        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.mainMenu1 = new System.Windows.Forms.MainMenu();
            this.checkBox1 = new System.Windows.Forms.CheckBox();
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            // 
            // checkBox1
            // 
            this.checkBox1.Location = new System.Drawing.Point(88, 64);
            this.checkBox1.Text = "Ikryssad";
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(32, 128);
            this.button1.Size = new System.Drawing.Size(72, 24);
            this.button1.Text = "Kryssa i";
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // button2
            // 
            this.button2.Location = new System.Drawing.Point(128, 128);
            this.button2.Size = new System.Drawing.Size(72, 24);
            this.button2.Text = "Kryssa ur";
            this.button2.Click += new System.EventHandler(this.button2_Click);
            // 
            // Form1
            // 
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.checkBox1);
            this.Menu = this.mainMenu1;
            this.Text = "Tentauppgift 1";

        }
        #endregion

        /// <summary>
        /// The main entry point for the application.
        /// </summary>

        static void Main() 
        {
            Application.Run(new Form1());
        }

        private void button1_Click(object sender, System.EventArgs e)
        {
            this.checkBox1.Checked = true;
        }

        private void button2_Click(object sender, System.EventArgs e)
        {
            this.checkBox1.Checked = false;
        }
    }
}

Rättningskommentarer:
Man måste ha en MainMenu för att få en SIP. Annars -1p.
Man måste ha metoden InitializeComponent, och den måste heta så, för att koden ska "fungera" i den betydelsen att det går att redigera formuläret i Designern. Annars -1p.

Uppgift 2 (5 p)

a) (2p)

Man lägger de tre radioknapparna (RadioButton) ovanpå på samma behållar-kontroll, till exempel en panel (Panel) eller en etikett (Label), eller i ett formulär (Form).

Vill man ha flera separata grupper av knappar i samma formulär, får man lägga dem på olika behållar-kontroller.

b) (3p)

        private void nyttigtknappen_Click(object sender, System.EventArgs e)
        {
            this.pommeknappen.Enabled = false;
            this.mosknappen.Enabled = true;
            this.salladsknappen.Enabled = true;
        }

        private void gottknappen_Click(object sender, System.EventArgs e)
        {
            this.pommeknappen.Enabled = true;
            this.mosknappen.Enabled = true;
            this.salladsknappen.Enabled = false;
        }
    }
}

Uppgift 3 (5 p)

a) (1p)

Intermediate Language är samma i .NET Framework (skrivbords-.NET) som i .NET Compact Framework (.NET för smådatorer). (Däremot skiljer det sig lite när det gäller hur just-in-time-kompilerad maskinkod kan sparas och återanvändas.)

b) (1p)

Klassbiblioteken skiljer sig åt, eftersom .NET Compact Framework är skapat för att ta mindre minne och processorkraft. Därför har .NET Compact Framework färre klasser, och de klasser som finns har ibland färre finesser (som metoder och egenskaper). I huvudsak ska dock de klasser som är gemensamma fungera likadant. Det finns också klasser som bara finns för .NET Compact Framework, och inte för skrivbords-.NET, till exempel klasserna för SQL Server CE.

En skillnad som inte gäller själva klassbiblioteken, men som handlar om hur programmen körs, är vad som händer när en klass som ett program vill använda inte finns tillgänglig. I .NET Compact Framework kan programmet startas, och man får ett fel när klassen verkligen ska användas, men i .NET Framework går det inte ens att starta programmet.

c) (1p)

Både i .NET Framework och i .NET Compact Framework kan applikationer använda sig av såväl hanterad som ohanterad kod. (Även om man förstås kan säga att när man anropar ohanterad kod, så är man inte längre kvar i .NET (Compact) Framework!) Eftersom den ohanterade koden är maskinkod, som ska köras direkt av processorn, måste den förstås vara rätt sort för just den processor som just den datorn använder, och smådatorer som PDA:er har ofta en annan typ av processor (t ex typen ARM) än vad skrivbordsdatorer har.

Hanterad kod i .NET Compact Framework kan inte lika enkelt komma åt ohanterade data, och anropa ohanterad kod, som i .NET Framework.

Exempel på ett långt men felaktigt svar, för det handlar ju om vad hanterad och ohanterad kod är, inte hur de skiljer sig mellan .NET Framework och .NET Compact Framework:

"Hanterad kod" (managed code) är programkod uttryckt i Intermediate Language (IL), som inte kan köras direkt av processorn, utan som först måste kompileras till körbar maskinkod (eller alternativt interpreteras, som i Java, men det är inte så man gör i .NET). Hanterad kod körs i Common Language Runtime (CLR), som sköter om saker som säkerhet, minneshantering och trådning. C# i Visual Studio .NET skapar hanterad kod.

"Ohanterad kod" (unmanaged code) är programkod uttryckt i maskinkod, som kan köras direkt av processorn. Hanterad kod körs utan stöd av i Common Language Runtime (CLR), och det enda stöd och skydd den har är vad den gör själv, och vad hårdvaran kan göra (till exempel om själva processorn kan begränsa åtkomst till vissa delar av minnet).

Hanterad kod kallas ofta ".NET-kod" och ohanterad kod kallas ofta "native" (vilket på svenska kanske borde bli "infödd"?).

d) (1p)

Utvecklingsverktygen, i första hand Visual Studio .NET, körs på vanliga skrivbordsdatorer, inte på PDA:er, och använder därför .NET Framework och inte .NET Compact Framework.

Kommentarer:

e) (1p)

Den funkar annorlunda i .NET Compact Framework: användaren kan inte skriva in egna val. (Det går dock att simulera beteendet från skrivbords-Windows genom att placera en TextBox ovanpå ComboBox-kontrollen.)

Uppgift 4 (3 p)

a) (1p)

I stället för att det är programmet som styr vad som ska göras, genom att det står steg för steg i programkoden vad som ska hända, så delar man upp programmet i ett antal callback-metoder, även kallade händelsehanterare eller hanterare. Programmet talar om för systemet (till exempel .NET-omgivningen) vilken hanterare som ska anropas för de "händelser" som kan inträffa. En händelse kan bestå i att användaren klickar på en knapp, eller i att en timer (tänk: äggklocka) räknat klart sin tid. När sen en händelse inträffar, så anropas rätt hanterare, som då kan göra det jobb som behöver göras, till exempel öppna ett nytt fönster som svar på användarens knapptryckning.

I stället för att programmet självt styr vad som ska göras, och i vilken ordning, genom att det står steg för steg i programkoden, så är det alltså bland annat användaren med sina musklick som styr vad som ska hända.

b) (1p)

En "händelse" är ett tecken på att något inträffat i programmet eller dess omgivning. En händelse kan signalera att användaren gjort något med användargränssnittet, till exempel klickat på en knapp eller flyttat musen en bit, eller i att en timer (tänk: äggklocka) räknat klart sin tid, eller att något förändrats i ett objekt, till exempel att ett fönster ändrat storlek.

c) (1p)

En händelsehanterare är en metod (eller, i andra programspråk, till exempel en funktion) som ska köras när en händelse inträffat. Programmeraren skriver metoden, och talar sen (med särskilda konstruktioner i programkoden) om för systemet vilken hanterare som ska anropas för de "händelser" som kan inträffa.

Uppgift 5 (3 p)

a) (2p)

Metoden Finalize, även kallad en destruktor, körs av skräpsamlaren, när den bestämt sig för att objektet är skräp och att minnet som det lagras i behövs till annat. Eftersom det alltså är skräpsamlaren, som är en del av systemet och inte av ens program, som kör Finalize, så har man som C#-programmerare inte mycket kontroll över när den körs: kanske så fort objektet inte längre går att nå från programmet, kanske om en timme, kanske i morgon när användaren avslutar programmet, kanske aldrig.

Därför ska man inte lita på destruktorn för att städa upp saker som behöver städas upp, till exempel om man måste stänga en fil eller ta bort ett varningsfönster. I stället bör man använda en särskild uppstädningsmetod, som kan anropas explicit från programmet när det inte behöver objektet längre, och vill stänga den där filen eller ta bort det där varningsfönstret. Det är det som Dispose-metoden är till för. Efter ett anrop till Dispose ligger objektet kvar i minnet, i väntan på att så småningom tas bort av skräpsamlaren.

Finalize kan ses som en sorts räddningslina, en "sista chansen"-upprensning, så att skräpsamlaren kan städa upp det som inte blivit gjort av programmet självt.

(Ett tips i C#: konstruktionen using, som automatiskt anropar Dispose.)

b) (1p)

Finalize är det interna .NET-namnet på uppstädningsmetoden, men i C#-koden använder man samma syntax som för destruktorer i C++: tecknet tilde, följt av klassnamnet. Exempel:

class Svampstuvning {
    public Svampstuvning()
        // Konstruktorn sätter startvärden på variabler
    }
    public ~Svampstuvning() {
        // Det här är "Finalize", som sköter återstående uppstädning
    }
}

Uppgift 6 (3 p)

a) (1p)

Man skapar ett objekt av klassen Thread, och då skickar man också till Thread-konstruktorn med den metod som ska köras i tråden. Sen måste man också starta tråden, med ett anrop till metoden Start.

Mer detaljerat så är det en "delegat" man skapar och skickar till Thread-konstruktorn, och den delegaten används sen av Thread-objektet för att anropa metoden. Delegat-typen heter ThreadStart, och man skapar en sån med new, och skickar då med metodnamnet.

Med programkod:

private void MetodenSomSkaGöraJobbet()
{
    // Här står programkoden som ska köras i tråden
}

// Och så här startar man tråden:
Thread t = new Thread(new ThreadStart(MetodenSomSkaGöraJobbet));
t.Start();
Eller samma sak, fast i två steg:
// Och så här startar man tråden:
ThreadStart ts = new ThreadStart(MetodenSomSkaGöraJobbet);
Thread t = new Thread(ts);
t.Start();

b) (2p)

Om man gör allt jobbet direkt i callback-metoden, eller i metoder som anropas från den på vanligt sätt, kommer allt jobbet att utföras av samma tråd som sköter användargränssnittet. Innan jobbet är klart, så att callback-metoden kan returnera, kommer användargränssnittet därför inte att svara på inmatning. Det händer till exempel inget när man trycker på knapparna.

Om man i stället låter callback-metoden starta en ny tråd, och låter den tråden sköta jobbet, kan callback-metoden returnera direkt efter att den startat tråden, och då fungerar användargränssnittet igen. Den nya tråden kan samtidigt jobba vidare i bakgrunden, så länge den behöver.

Uppgift 7 (1 p)

Den har ju, normalt, ingen mus, utan man ritar och pekar med en pekpenna ("stylus") på en tryckkänslig skärm. Då blir en muspekare både onödig (för man ser ju själv var man pekar med pennan) och inte särskilt användbar (för den kan ju inte följa pennan när man håller den ovanför skärmen).

Uppgift 8 (6 p)

Samma data kan alltså finnas på flera olika ställen, till exempel visade i en listkontroll på skärmen, i en ArrayList i datorns primärminne, och i en databas på samma eller en annan dator.

För lagringen av data kan man skilja mellan två grundidéer, som i .NET kallas "uppkopplad lösning" (connected approach) och "icke uppkopplad lösning" (disconnected approach):

På en vanlig skrivbordsdator har man normalt tillgång till ett snabbt och driftsäkert nätverk, och kan upprätthålla en koppling till en databasserver, så där är en uppkopplad lösning användbar. Ännu mer så på en serverdator, om tillämpningsprogrammet körs på samma dator (eller åtminstone i samma datorhall) som databashanteraren. På en PDA med trådlöst nätverk är nätkopplingen långsammare, mindre driftsäker och kanske väldigt dyr, och då är en uppkopplad lösning mot en databashanterare på en annan maskin inte lika rolig. Däremot kan man ha en uppkopplad lösning mot en databas (till exempel med Windows Server CE) på själva PDA:n. På en PDA utan trådlöst nätverk är det förstås olämpligt med en uppkopplad lösning mot en databashanterare på en annan maskin.

Eftersom ett program normalt ligger kvar och kör i bakgrunden på en PDA, och inte avslutas som på en skrivbordsdator, och PDA:n bara går ner i ett strömbesparande, sovande läga i stället för att stängas av helt och hållet som en skrivbordsdator, är det inte lika viktigt på en PDA som på en skrivbordsdator att hela tiden spara undan data på fil eller i en databas. Men något sätt att spara måste man ändå ha, för om programmet eller PDA:n kraschar (eller kommer bort!) så blir man annars av med de data som fanns i programmets primärminne.

När det gäller data som visas på skärmen, måste det finnas en koppling mellan de data som finns i primärminnet (eller, vid en uppkopplad lösning, i databasen) och vad som visas på skärmen. .NET har två lösningar:

Rättningskommentarer:


Thomas Padron-McCarthy (Thomas.Padron-McCarthy@tech.oru.se) 18 juni 2006