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"

Accessa kontroller i FormView InsertItemTemplate

Hade häromdagen problem med att från koden accessa en dropdownlist som var placerad i en FormView InsertItemTemplate. Efter mycket googlande och ihoppusslande av små ledtrådar lyckades jag till slut få det att fungera. Tänk dig följande struktur i aspx-filen:

<asp:FormView ID="FormView1" runat="server" /.../ >
    <InsertItemTemplate>
        <asp:DropDownList ID="DropDownList1" runat="server">
            /.../
        </asp:DropDownList>
    </InsertItemTemplate>
    /.../
</asp:FormView>

Vad jag ville göra var att när det är InsertItemTemplate som visas så behövde jag kolla en grej och beroende på utfallet enabla eller disabla denna DropDownList (jag hoppar över detaljerna för att fokusera på hur jag kommer åt dropdownlistan). Först och främst kommer jag inte åt DropDownList1 direkt via koden, jag måste använda FindControl, men på vad? Rätt svar visade sig vara följande:

DropDownList DropDownList1 = (DropDownList)FormView1.Row.FindControl("DropDownList1");

Row är en referens till den datapost som just nu visas i FormView-kontrollen och det är också där kontrollerna som just nu visas finns att hitta. Men inte nog med detta, jag måste först kolla så att det är InsertItem-läget som visas;

        if (FormView1.CurrentMode == FormViewMode.Insert)
        {
            DropDownList DropDownList1 = (DropDownList)FormView1.Row.FindControl("DropDownList1");
        }

Men vad ska nu denna kod placeras då? Det som kändes logiskt när jag kollade i FormView-kontrollens Event-lista var ModeChanged-eventet men det funkade inte och det var då det verkliga googlandet fick ta fart. Till slut hittade jag en liten hint nånstans om att just när det gäller Insert-läget så är det PreRender-eventet som gäller. Innan dess är inte kontrollerna i InsertItem-template tillgängliga;

    protected void FormView1_PreRender(object sender, EventArgs e)
    {
        if (FormView1.CurrentMode == FormViewMode.Insert)
        {
            DropDownList DropDownList1 = (DropDownList)FormView1.Row.FindControl("DropDownList1");
            //Gör nåt med DropDownList1
        }
    }

Länka namn till personsida

Jag fortsätter mina förändringar av den ItemTemplate som jag bloggat om tidigare. Efter senaste bloggposten ser min ItemTemplate nu ut så här;

   <ItemTemplate>
       <div class="item">
           <div class="icon"><img src="../images/icon_board.png" /></div>
           <span class="credit">
               <asp:Label ID="authorNameLabel" runat="server" Text='<%# Bind("authorName") %>' />
               <br />
               <asp:Label ID="dateLabel" runat="server" Text='<%# Bind("timeStamp", "{0:d}") %>' />
               <asp:Label ID="timeLabel" runat="server" Text='<%# Bind("timeStamp", "{0:t}") %>' />
           </span>
           <br />
           <asp:Label ID="messageLabel" runat="server" Text='<%# Bind("message") %>' />
           <asp:LoginView ID="LoginView1" runat="server">
               <RoleGroups>
                   <asp:RoleGroup Roles="Admin">
                       <ContentTemplate>
                           <br />
                           <asp:Button ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Ta bort" />
                       </ContentTemplate>
                   </asp:RoleGroup>
               </RoleGroups>                                
           </asp:LoginView>
       </div>
   </ItemTemplate>

I denna bloggpost ska vi fokusera på att få namnet på användaren länkat till en sida som presenterar just den personen. Något som visade sig vara betydligt enklare än min första lösning på problemet. Återkommer till det. Målet är alltså att skapa en länk i stil med person.aspx?userId=2938219038. Den SqlDataSource som ListView-kontrollen är kopplad till hämtar förstås upp användarens UserId så att jag har tillgång även till det via ett Bind-kommando, <%# Bind("UserId") %>. Då kan man ju tycka att det borde funka att ändra labelkontrollen authorNameLabel till en HyperLink så här;

<asp:HyperLink ID="lnkAuthorName" runat="server" Text='<%# Eval("authorName") %>' NavigateUrl='~/Authenticated/person.aspx?userID=<%# Bind("userId") %>' />

Men se det går inte. Sidan går utmärkt att köra men resultatet blir en länk med följande slut: person.aspx?userID=%3C%#%20Bind(%22userId%22)%20%%3E. Mitt Bind-kommando utvärderas alltså inte av servern och den lösning jag då kom på var att göra om till en LinkButton på följande sätt;

<asp:LinkButton ID="NameLink" runat="server" Text='<%# Eval("authorName") %>' CommandArgument='<%# Bind("userId") %>' OnClick="nameLink_Click" />

På så vis gör jag inga konstigheter med Bind-kommandot. Som tillägg till detta måste jag dock implementera en metod på server-sidan som tar hand om klicket - nameLink_Click. Den metoden valde jag att placera i den page-klass jag skapat som alla min sidor ärver från, på så sätt kan jag använda denna metod från alla sidor i alla sammanhang där ett namn ska länkas till en personsida.

public class ttPage : System.Web.UI.Page
{
    /.../

    public void nameLink_Click(object sender, EventArgs e)
    {
        string userID = ((IButtonControl)sender).CommandArgument;
        Response.Redirect(this.ResolveUrl("~/Authenticated/person.aspx?userId=" + userID));
    }

    /.../
}

Vad händer här då? Jo, UserId vill jag ta från knappens CommandArgument och eftersom sender är en referens till det objekt som triggat eventet typar jag den till en IButtonControl. Vad är nu detta då? Jo IButtonControl är ett interface som implementeras av alla knappkontroller, som LinkButton och Button, och det är i IButtonControl som CommandArgument definieras. Självklart hade jag kunnat typa till en LinkButton men då låser jag fast mig vid att mina namnlänkar aldrig kommer vara av någon annan knapptyp, så det här blir helt enkelt en snyggare lösning. På nästa rad skickar jag webbläsaren vidare till rätt sida med Response.Redirect genom att konkatenera ihop url:en med userId:t. Page.ResolveUrl utvärderar ~-tecknet så att man hamnar där man ska. (~-tecknet symboliserar roten i web-applikationen) Det här är dock ingen snygg lösning egentligen.

Ikväll när jag googlar för att kolla upp skillnaden mellan Eval och Bind hamnar jag i denna forumtråd och ett inlägg där leder mig till att googla på "container.dataitem vs eval" +asp.net vilket i sin tur leder mig till denna artikel där en kommentar uppmärksammar mig på att jag kan lösa mitt problem på exakt samma sätt som med datumutskriften i tidigare blogginlägg. Nämligen så här;

<asp:HyperLink ID="lnkAuthorName" runat="server" Text='<%# Eval("authorName") %>' NavigateUrl='<%# Bind("userId", "~/Authenticated/person.aspx?userID={0}") %>' />

Fördelen med detta är förstås att jag blir av med PostBacken som en LinkButton orsakar, och jag blir av med den Response.Redirect som jag hade i nameLink_Click-metoden... Sammanfattningsvis kan man säga att det lönar sig att vänta ett par dagar innan man skriver bloggposten. ;-)

Visa innehåll för viss grupp av användare

Dags att fortsätta mitt förra inlägg där jag inledde några förändringar av en ItemTemplate i en ListView-kontroll. Anledningen till att jag valt just ListView-kontrollen, som är ny i Asp.NET 3.5, är att jag behöver en kontroll som kan loopa ut inläggen på anslagstavlan, vilket en Repeater kan klara av. Men jag vill också erbjuda möjligheten att ta bort ett inlägg genom att visa en ta-bort-knapp i anslutning till varje inlägg. Visst hade jag kunnat fixa det i en Repeater med en knapp vars click-event jag själv hanterar men med hjälp av ListView-kontrollen kan jag hooka in på SqlDataSource-kontrollens deletecommand vilket förstås är smidigare.

Men det var egentligen inte det jag skulle skriva om utan nu ska vi kika på punkt 2 från föregeånde inlägg; Hur dölja ta-bort-knappen för alla användare utom administratörer? Eftersom jag använder mig av Asp.NET:s inbyggda Membership och Role-hantering är det relativt enkelt. Är du inte så inläst på det föreslår jag ett besök på The Copy Paste Projects avdelning om Membership, som beskriver grunderna. Tack vare kontrollen LoginView kan man visa upp olika innehåll beroende på vilken Roll en användare tillhör. Så genom att omsluta Ta-bortknappen med en LoginView-kontroll enligt följande så är problemet löst:

   <ItemTemplate>
       <div class="item">
           <div class="icon"><img src="../images/icon_board.png" /></div>
           <span class="credit">
               <asp:Label ID="authorNameLabel" runat="server" Text='<%# Bind("authorName") %>' />
               <br />
               <asp:Label ID="dateLabel" runat="server" Text='<%# Bind("timeStamp", "{0:d}") %>' />
               <asp:Label ID="timeLabel" runat="server" Text='<%# Bind("timeStamp", "{0:t}") %>' />
           </span>
           <br />
           <asp:Label ID="messageLabel" runat="server" Text='<%# Bind("message") %>' />
           <asp:LoginView ID="LoginView1" runat="server">
               <RoleGroups>
                   <asp:RoleGroup Roles="Admin">
                       <ContentTemplate>
                           <br />
                           <asp:Button ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Ta bort" />
                       </ContentTemplate>
                   </asp:RoleGroup>
               </RoleGroups>                                
           </asp:LoginView>
       </div>
   </ItemTemplate>

LoginView-kontrollen kan också användas för att visa ett innehåll för användare som inte är inloggad och ett annat innehåll för den som är inloggad;

        <asp:LoginView ID="LoginView1" runat="server">
            <AnonymousTemplate>
                Du är inte inloggad.
            </AnonymousTemplate>
            <LoggedInTemplate>
                Du är inloggad.
            </LoggedInTemplate>
        </asp:LoginView>

Men utöver dessa två templates finns alltså också RoleGroups-egenskapen som är en collection av RoleGroup-objekt. Varje RoleGroup-objekt har två egenskaper, Roles och ContentTemplate. Roles är ett attribut där du skriver in den eller de roller du vill att det här objektets ContentTemplate ska visas för. Om du anger flera roller skriver du dem kommaseparerade. I ContentTemplate-elementet kan du sedan fylla på med precis vad som helst som du vill ska visas just för den valda användarrollen.

Formatering av datum och tal i Bind-expression

(Bakgrund När jag gick trean på gymnasiet för åtta år sen gick jag en slags IT-inriktning. Kursen bestod mest av att göra Shockwave-prylar [som var stort då, tänk vad tiden går ;-)] och multimediaprojekt på CD-skiva... Men jag var mer intresserad av att göra hemsidor/webbaserat och fick godkänt att ägna mina lektioner åt att självstudera det som idag kallas för klassisk ASP. Mitt projekt blev en internweb åt skolan som jag efter studenten fick anställning på skolan för att verkligen dra igång på riktigt. Den används än idag och vidareutvecklas lite då och då. Efter höstens Asp.NET-kurs på min nuvarande KY-utbildning har jag förstås inte kunnat låta bli att börja fila på en Asp.NET-version av internwebben, som för övrigt går under namnet "Tick-Tack". Nu har jag kommit en bit på vägen och här på bloggen kommer jag under utvecklingens gång dela med mig av diverse Asp.NET-upptäckter som säkert kan komma till nytta även för andra.)

En av funktionerna i Tick-Tack är en anslagstavla dit samtliga användare kan posta inlägg. Följande ItemTemplate i en ListView-kontroll kommer att visa upp inläggen från anslagstavlan;

   <ItemTemplate>
       <div class="item">
           <div class="icon"><img src="../images/icon_board.png" /></div>
           <span class="credit">
               <asp:Label ID="authorNameLabel" runat="server" Text='<%# Bind("authorName") %>' />
               <br />
               <asp:Label ID="timeStampLabel" runat="server" Text='<%# Bind("timeStamp") %>' />
           </span>
           <br />
           <asp:Label ID="messageLabel" runat="server" Text='<%# Bind("message") %>' />
           <br />
           <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False" CommandName="Delete" Text="Ta bort" />                               
       </div>
   </ItemTemplate>

ListView-kontrollen är kopplad till en SqlDataSource. Den hämtar poster där kolumnerna authorName, timeStamp och message finns med. När jag väl kommit så här långt fanns det tre saker jag ville fixa till;

  1. Möjlighet att formatera formatet på hur datumet, timeStamp, skrivs ut.
  2. Endast visa Ta bort-länken för administratörer.
  3. Länka namnet på inläggets författare till en annan sida som presenterar personen. Dessutom på ett generellt sätt så att det går att använda från andra listningar där namn skrivs ut på samtliga sidor i hela internwebsapplikationen.

I detta blogginlägg kommer jag att ta upp punkt ett, de övriga två i kommande blogginlägg. I exemplet ovan skrivs timeStamp ut i formatet 2008-06-19 11:39:00. Datatypen i databasen är en smalldatetime så sekundsiffrorna kommer alltid vara 00 och även om de hade varit nåt annat känns det ointressant att skriva ut sekundsiffrorna, timme och minut vill jag dock ha med. Efter lite googlande och sökande på msdn fann jag att Bind-uttrycket har ett andra valfritt argument som heter formatString. Den erbjuder en möjlighet att formatera datumet på samma sätt som man kan göra med string.Format-metoden;

Label1.Text = string.Format("Idag är det {0}", DateTime.Today);
//Ger utskrift: Idag är det 2008-06-19 00:00:00

Label2.Text = string.Format("Idag är det {0}", DateTime.Today.ToLongDateString());
//Ger utskrift: Idag är det den 19 juni 2008

Label3.Text = string.Format("Idag är det {0:D}", DateTime.Today);
//Ger samma utskrift som föregeånde: Idag är det den 19 juni 2008
        
Label4.Text = string.Format("Apelsinen kostade {0:C}", 5);
//Ger utskrift: Apelsinen kostade 5,00 kr

Label5.Text = string.Format("Det här är ett högt tal: {0:N}", 3247893289723324);
//Ger utskrift: Det här är ett högt tal: 3 247 893 289 723 324,00

string.Format är alltså en metod som först tar en sträng med x antal platshållare, {0} {1} etc, och därefter ytterligare ett argument för varje platshållare och returnerar en sträng där de efterföljande argumenten ersatt respektive platshållare. Men som du ser kan platshållarna göra mer än så, särskilt när det gäller datum och siffror. "D" är en standardformatsträng för "Long date pattern", vilket är samma datumformat som returnas av datetimeobjektets ToLongDateString-metod. Se MSDN:s förteckning över Standard Date and Time Format Strings. C står för Currency och N för Number och de är båda exempel på hur man kan manipulera tal-variabler via string.format. Se MSDN:s förteckning över Standard Numeric Format Strings. Så vad har nu allt detta att göra med utskriften av timeStamp i exemplet längst upp? Jo, som andra argument till Bind kan man alltså skicka in en formatString enligt följande;

<asp:Label ID="timeStampLabel" runat="server" Text='<%# Bind("timeStamp", "{0:D}") %>' />

Platshållare 0 kommer alltså ersättas av värdet i kolumnen timeStamp och utskriften kommer att formateras som ett "Long date pattern". Det var dock inte detta format jag ville ha i mitt sammanhang. Faktum är att det finns ingen standardformatsträng som erbjuder formatet hela datumet, men bara timmar och minuter i tiden. Då kan man använda sig av Custom Date and Time Format Strings. Här erbjuds en möjlighet att helt och hållet styra över hur datumet ska skrivas ut och den variant jag ville ha ser ut så här:

<asp:Label ID="timeStampLabel" runat="server" Text='<%# Bind("timeStamp", "{0:yyyy-MM-dd hh:mm}") %>' />

Fördelen med Standard-formaten är förstås att de tar hänsyn till aktuell kultur vilket är användbart om sajten du jobbar med är flerspråkig. Det skulle man kunna lösa genom att skriva ut två labels, en som får ta hand om datumdelen och en som får ta hand om tidsdelen;

<asp:Label ID="dateLabel" runat="server" Text='<%# Bind("timeStamp", "{0:d}") %>' />
<asp:Label ID="timeLabel" runat="server" Text='<%# Bind("timeStamp", "{0:t}") %>' />

Och eftersom man aldrig vet vad som händer i framtiden, blir det den lösningen jag kör.

Liten bloggpaus

Det blir inte så mycket bloggande nu då all tid går åt till UML-kursen i skolan och det ämnet har jag bestämt mig för får falla utanför ramarna för denna blogg. Under tiden kan jag verkligen rekommendera Matt Berseths blogg som jag snubblade på för ett par veckor sen, han gör grymma Javascript/ASP.Net/Ajax-grejer och har en demosite där han presenterar sina grejer på ett schysst sätt och gör mycket kod tillgänglig för nedladdning. :-)

Hoppas komma tillbaka om ett par veckor då det blir mer kodande, förhoppningsvis med lite reflektioner över den då pågående C#/Windows-kursen.

The Copy Paste Project

Efter mycket om och men finns nu mitt och tre klasskamraters projektarbete i Asp.NET tillgängligt via internet. Projektet är en enda stor Asp.NET-exempelsajt och hela projektet går att ladda ner och gör man det kommer man finna sjukt mycket kommentarer i koden, men det var också tanken i just detta projekt. Sajten innehåller en hel del grundläggande Asp.NET-info som jag därmed inte kommer att täcka in i denna blogg. Välkommen in via den inte så snygga adressen http://netteam.academedia.se/net07/Studenter/dan/aspnet/Project2/

Sökord: CSS, Stylesheets, div-layout, masterpage, themes, treeview, sitemappath, web.sitemap, membership, formulär, validation, Asp.NET-kontroller, SQL, C#, localization, user controls.

Bok till Windows-kursen på gång

På måndag drar skolan igång igen och efter tre veckor med "UML and the Unified Process" (systemdesign, typ, jag har ju inte gått kursen än ;-) är det dags för Windows-utveckling och C#-fördjupning. Vi hinner med två veckor på den kursen innan sommaruppehåll och sen fortsätter den till hösten. Boken vi ska ha är från APress och jag har sedan tidigare en bok från dom om Asp.NET-server controls som är mycket bra, så det känns helt ok. Tidigare har vi haft böcker från O'Reilly och Wrox och jag måste helt klart säga att alla böcker jag köpt från O'Reilly är grymt bra, Wrox gillar jag inte alls lika mycket.

Nu är i alla fall boken från Windows-kursen på gång från Bokus och att döma av innehållsförteckningen verkar den väldigt bred och boken kommer säkert bli mig ett kärt referensverk framöver. Faktum är att den verkar mer inriktad på C# i allmänhet än specifikt windowsutveckling och den täcker till och med in Asp.NET en del. Känns bra att den tar upp saker som LINQ och Windows Presentation Foundation som jag inte hunnit sätta mig in i ännu. På tal om det så börjar jag känna mig lite splittrad nu. Det finns så otroligt mycket att lära sig och kunna i denna programmeringsvärld så jag vet knappt vad jag ska fortsätta med nu. Under våren har det blivit en hel del CSS, javascript och Ajax, dessutom har jag satt mig in lite i utveckling av Asp.NET Custom Controls och så är jag samtidigt sugen på att sätta mig in mer i SQL och databashantering och även använda Microsoft Visio för att göra lite databasdesign... Samtidigt så ska vi de närmsta veckorna ägna oss åt systemdesign i skolan och då borde jag kanske bara pausa från allt annat och fokusera på det.

Det är bara en sak jag vet säkert, när det finns mycket att välja på blir det lätt att man inte får nåt gjort alls... :-/