LektionsBlogg: MDI, ärva fönster, polymorfism

Så här sista veckan innan sommarlovet och andra veckan in på vår Windows/Visual C#-kurs tänkte jag köra igång en serie bloggposter under vinjetten LektionsBloggen. Dessa bloggposter kommer helt enkelt ta sin utgångspunkt i de lektioner vi har på min pågående KY-utbildning i .Net. Observera att dessa bloggposter utgår från hur jag uppfattat saker och ting och att det säkert kan finnas andra sätt att göra samma saker på.

Lektionens datum: 30/6 2008
Lektionens rubrik: Repetition, Arv, Polymorfism, Delegates And Events, MDI (1/2)
Lektionsbloggens fokus: MDI, hur ett fönster kan ärva ett annat fönster, polymorfism

Vår lärare har en tendens att röra ihop allting på en och samma gång. Här kommer ett försök till att särskilja de olika sakerna. MDI MDI står för Multiple Document Interface och handlar om applikationer som har ett huvudfönster som i sin tur kan innehålla flera "child"-fönster. Att skapa en MDI-applikation i Visual Studio är väldigt enkelt då det finns en färdig mall för detta. Starta ett nytt Windows Project. Högerklicka på projektet i Solution Explorer och välj Add -> New Item... Välj därefter mallen MDI Parent Form;

 

Nu får man ett fönster med en hel del färdig funktionalitet där man får lägga till och ta bort vid behov. För att det ska bli detta fönster som körs när man startar applikationen, ändra Form1 till FrmMainWindow i Main-metoden (i Program.cs);

    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new FrmMainWindow());
    }

Om du nu kör programmet får du följande fönster;

 

Ärva formulär
Ett fönster i Windows Forms är precis som allt annat i C# ingenting annat än en klass. Därför kan du självklart ärva från ett fönster till ett annat. Så här går det till enligt dagens lektion: Skapa ett formulär, jag fortsätter med projektet ovan och använder det redan existerande Form1-fönstret, men döper om det till FrmBase. Dra ut lite kontroller i fönstret. Jag är lite inkonkret nu för jag kan inte riktigt komma på nåt bra exempel då man har två (eller flera) olika slags fönster som är väldigt lika varandra, men ändå så pass olika att man vill ha olika fönsterklasser med ett gemensamt basfönster. Go figure... I mitt fall drar jag ut en label-kontroll; lblSound. För att nu göra ett nytt fönster som ärver från detta fönster väljer du Add -> New Item..., sedan går du till Windows Forms i vänsterspalten där du hittar mallen Inherited Form;

 

Namnge ditt nya fönster frmDog. I nästa steg får du välja från vilken Form du vill ärva och då väljer du förstås FrmBase. Ett annat sätt att åstadkomma samma sak är att skapa en ny Form precis som vanligt (mallen Windows Form) och sedan gå till kodläge och se till att ärva från FrmBase. Jag gjorde så med ännu ett fönster, FrmCat;

namespace MDI
{
    public partial class FrmCat : MDI.FrmBase
    {
        public FrmCat()
        {
            InitializeComponent();
        }
    }
}

MDI
Vi återvänder till vårt MDI-fönster för att nu göra det möjligt att öppna våra Dog- och Cat-fönster i MDI-fönstret. Ta bort menyalternativen New... och Open... och lägg till två menyalternativ i File-menyn och flytta dom längst upp;

 

Dubbelklicka på respektive menyval och implementera följande kod i respektive händelsemetod:

    private void newCatToolStripMenuItem_Click(object sender, EventArgs e)
    {
        FrmCat cat = new FrmCat();
        cat.MdiParent = this;
        cat.Text = "Cat";
        cat.Show();
    }

    private void newDogToolStripMenuItem_Click(object sender, EventArgs e)
    {
        FrmDog dog = new FrmDog();
        dog.MdiParent = this;
        dog.Text = "Dog";
        dog.Show();
    }

Kör programmet och kolla att du nu kan öppna ex antal av varje slags fönster. (Kolla gärna vad som händer om du skippar att sätta fönstrets MdiParent.) Polymorfism Vi ska nu använda våra fönster för att illustrera polymorfism. Implementera följande metod i FrmBase;

    public void MakeSound()
    {
        lblSound.Text += "Woof/Mjau ";
    }

Återgå sedan till MDI-fönstret, dra ut en Timer i designläget, sätt Enabled till true och sätt Interval till 1000 (= 1 sekund). Dubbelklicka sedan på Timern och implementera följande kod;

      Random r = new Random(DateTime.Now.Millisecond);
      private void timer1_Tick(object sender, EventArgs e)
      {
          if (this.MdiChildren.Length > 0)
          {
              //Slumpa ett tal mellan 0 och antal öppna fönster
              int randomNumber = r.Next(0, this.MdiChildren.Length);

              FrmBase randomWindow = (FrmBase)this.MdiChildren[randomNumber];
              randomWindow.MakeSound();
          }
      }

Om du nu kör programmet och öppnar ett gäng fönster kommer du se att de slumpmässigt fylls på med strängen Woof/Mjau. Men så här kan vi ju inte ha det, ett Cat-fönster borde bara skriva ut Mjau och ett Dog-fönster borde bara skriva ut Woof. Implementera därför metoden MakeSound ovan i både FrmCat och FrmDog, men ändra strängen så att den bara innehåller det ena "ljudet". Försök sedan köra programmet igen.

Skapa property för att accessa del av privat medlem
När du nu försöker kompilera får du följande felmeddelande två gånger, en för FrmCat och en för FrmDog: 'MDI.FrmBase.lblSound' is inaccessible due to its protection level. Detta beror på att vår label-kontroll i FrmBase är private och kan därför inte kommas åt i de ärvda klasserna. För att råda bot på problemet implementerar vi en Property i FrmBase som ger tillgång till Labelns Text-property;

      public string Sound
      {
          get { return lblSound.Text; }
          set { lblSound.Text = value; }
      }

Polymorfism
Därefter ändrar vi implementationen av MakeSound i FrmCat och FrmDog. I FrmDog blir den så här;

      public void MakeSound()
      {
          Sound += "Woof ";
      }

Kör programmet igen. Men vad nu då? Fortfarande skrivs det ut Woof/Mjau i alla fönster. Det är alltså fortfarande MakeSound i FrmBase som anropas... För att åtgärda detta lägger du till ordet virtual mellan public och void på metoddeklarationen i FrmBase. På samma plats i FrmCat och FrmDog lägger du till ordet override. Kör programmet igen. Voila! Vi har nu åstadkommit polymorfism! Vad handlar det om egentligen? Jo, notera i Timer1_Tick-metoden att referensen randomWindow endast är av typen FrmBase, den har alltså ingen aning om fönstret är av typ FrmDog eller FrmCat. Men när vi anropar metoden MakeSound som deklarerats med ordet virtual blir den tvungen att kolla upp; Exakt vilken typ av FrmBase är jag egentligen? Jaha, jag är en FrmCat, då måste jag kolla om FrmCat har nån implementation av MakeSound, ja det har den, då kör vi den. Testa detta genom att kommentera bort MakeSound-metoden i FrmCat. När du nu kör programmet kommer Woof/Mjau att skrivas ut i alla Cat-fönster medan Woof skrivs ut i alla hund-fönster. Testa också vad som händer om du tar bort virtual i FrmBase men har kvar override i de andra klasserna, samt tvärtom.

Skällande hundar och mjauande katter;

Morgondagens lektion kommer att handla om delegater och interface, lektionsbloggen återkommer kring detta!

Ladda hem "the LektionsBloggen Solution"

2 comments :

  1. Tack Dan! Du gör livet enklare! =)

    ReplyDelete
  2. Håller med.
    Grymt bra jobbat!!

    ReplyDelete