Area gtkValmis appears more than once in this document. Fix this to get rid of this warning.

Työaikaseuranta (GTK)

Näissä ohjeissa tuotetaan Työaikaraportille GTK-käyttöliittymä. Muista vaihtoehdoista katso Työaikaseurannan esittelysivu.

Oletuksena on, että Työaikaraportti on tehtynä, sillä sen aliohjelmia käytetään tässä.

Työaikaseuranta lukee kaksi tiedostoa (projektit.txt ja nykproj.txt) ja tuottaa kaksi tiedostoa (ajat.txt ja nykproj.txt).

Linkkejä

  • Xamarinin editoriin Visual Studio 2017 Blue -tyyli (ei pakollinen, voi halutessaan myös tehdä itse sellaisen kuin tykkää tai valita monista valmiista vaihtoehdoita)

Tavoite ja lopputulos

Ohjelmassa voi valita pudotusvalikosta työskentelyn kohteena olevan pää- ja aliprojektin, jotka ohjelma etsii projektit.txt-tiedostosta.

Ajanoton voi käynnistää ja lopettaa työskentelyn Aloita-painikkeista. Lopeta-painike tuo esiin tallennusvalikon, jossa voi halutessaan vielä muokata aikoja ja jättää kommentin. Tallenna-painike lisää tiedot riviksi ajat.txt-tiedostoon, mikäli ajat ovat hyväksyttävässä muodossa. Ajan voi myös "unohtaa" Poista-painikkeella, tai ajanottoa jatkaa Peruuta-painikkeella.

Ohjelma osaa myös näyttää Työaikaraportin tekemän siistin raportin, joka aukeaa Tiedosto-valikosta. Auki oleva pää- ja aliprojekti tallennetaan nykproj.txt-tiedostoon, niin että ohjelmaa käynnistettäessä pudotusvalikoissa on valmiiksi valittuna viimeksi valittu projektikokonaisuus.

video ja kuva valmiista ohjelmasta.

Suunitelmakin pitää muuten tehdä, sinne ne kuvat voi piirtää kynällä ja paperilla.

10 Aug 17

Voiko se suunnitelma olla sama kaikille alustoille?

VL: ilman muuta. Suunnitelmasa ei oikeastan edes mietitä alustaa. Eikä suunitelma sido lopullists toteutusta jos jotakin huomaa että on pakko tehdä eri lailla tai joskus jopa paremmin :-)

10 Aug 17 (edited 11 Aug 17)

Valmis koodi

video

Kuvatkaa tähän video. Encoderin nauhoittaessa ei voi käyttää painikkeita.

27 Aug 17

1. Aloitus

1.1 Uuden projektin luominen

Valitse Xamarinissa New Solution.../Other/.NET/Gtk# 2.0 Project. Luo uusi projekti.

Xamarinin editorin asetuksia voi muokata Tools/Options.../Text Editor/Syntax Highlighting. Videolla käytetyn VS2017-tyylin voit ladata tästä.

Otetaan Työaikaraportin attribuutit ja staattiset funktiot koodiimme. Otetaan seuraavat asiat käyttöön koodin alussa:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.IO;
using System.Text;
using Gtk;
using Pango;

1.2 Pohjataulukko GUIhin

Saamme käyttöliittymäeditorin auki alalaidan Designer-välilehdestä. (Takaisin koodiin pääsee Source-välilehdestä.

Valitaan MainWindow ylälaidassa olevasta pudotusvalikosta. Voimme Visual Studion tavoin avata Properties- ja Toolbox-ikkunat View-valikosta. Etsitään toolboxista table ja raahataan se nyt aktiivisena olevan MainWindow-kontrollin päälle, tehden siitä tämän lapsiolion.

Tämän uuden taulukon ollessa valittuna Properties-ikkunan Table Properties -kohdasta voidaan säätää rivien ja kolumnien määrää. Tehdään tästä 1x6-kokoinen taulukko.

Koko koodi tässä vaiheessa

2. GUIn rakentaminen

Annetaan ikkunassa olevan 1x6-taulukon nimeksi tablePohja (videolla pohjataulukko).

Lisätään toiseen ja kolmanteen riviin ComboBox-kontrollit comboBoxPaaprojektivalikko ja comboBoxAliprojektivalikko, ja neljänteen Label labelIlmoitus, jonka tekstiksi voidaan vaihtaa jotain kuvaavaa.

Lisätään kahteen alimpaan riviin Table-taulukot tableAloitus ja tableLopetus (videolla aloitustaulukko ja lopetustaulukko) ja asetetaan näiden ko'oiksi 2x1 ja 3x4.

Laitetaan ylimpään riviin MenuBar, jota klikkaamalla voimme lisätä menun. Lisätään "Tiedosto"-menu, jonne lisätään Action "Raportti" ja "Poistu"

Lisätään tableAloitus-taulukkoon Aloita- ja Lopeta-painikkeet buttonAloita ja buttonLopeta.

Lisätään tableAloitus-taulukon kolmen ensimmäisen rivin ensimmäiseen kolumniin "Alku"-, "Loppu"- ja "Kommentti"-Label, sekä toiseen kolumniin Entry-tekstikentät entryAlku, entryLoppu ja entryKommentti. Näiden Table Child Layout-asetuksista asetetaan LeftAttach=1 ja RightAttach=3, jolloin tekstikentät venyvät ensimmäisen kolumnin oikeasta laidasta kolmennen kolumnin oikeaan laitaan. Lisätään viimeiselle riville vielä Tallenna-, Poista- ja Peruuta-painikkeet buttonTallenna, buttonPoista ja buttonPeruuta.

Generoitunut GUIn koodi

3. Toiminnallisuutta GUIhin

3.1 Tapahtumakäsittelijät painikkeisiin

Lisätään Aloita-painikkeeseen tapahtumakäsittelijä. Painikkeen ollessa valittuna kirjoitetaan Properties-ikkunan Signals/Button_Signals/Clicked-kohtaan Aloita_Click. Tämä generoi meille uuden aliohjelman:

    protected void Aloita_Click(object sender, EventArgs e)
    {
        throw new NotImplementedException();
    }

Tähän voimme lisätä painikkeen disabloinnin ja Lopeta-painikkeen enabloinnin. (Poistetaan virheheitto!)

        buttonAloita.Sensitive = false;
        buttonLopeta.Sensitive = true;

Tehdään vastaava mutta päinvastainen Lopeta-painikkeelle.

    protected void Lopeta_Click(object sender, EventArgs e)
    {
        buttonAloita.Sensitive = true;
        buttonLopeta.Sensitive = false;
    }

Meillä on nyt ohjelma, jonka kaksi keskimmäistä painiketta "väistelevät" vuorotellen. (Videolla painikkeet eivät nauhoituksen vuoksi toimi.)

Voidaan luoda kaikille muillekin kontrolleille tässä vaiheessa tyhjät tapahtumakäsittelijät.

Koko koodi tässä vaiheessa

4. Alueiden näkyvyydet

4.1 Näkyvyyksien asettaminen

Ohjelmassame on tällä hetkellä kaksi aluetta. Tulevaisuutta silmällä pitäen kannattaa jo tässä vaiheessa kuitenkin tehdä alueiden näkyvyyksien vaihtamisesta sellainen, että se toimii useammallakin alueella. Tämän voisi toteuttaa aliohjelmalla, joka piilottaisi kaikki muut paitsi parametrina saamansa alueen.

Luodaan attribuutiksi uusi lista

    private List<Table> tablet = new List<Table>();

Lisätään kaikki kaksi aluetta tähän listaan pääohjelman sisällä.

    public MainWindow()
        : base(Gtk.WindowType.Toplevel)
    {
        Build();
        tablet.Add(tableAloitus); tablet.Add(tableLopetus);
    }

Kirjoitetaan Lopeta_Click-aliohjelman sisään uusi aliohjelmakutsu, jolle viedään parametrina näkyväksi jäävä alue.

    protected void Lopeta_Click(object sender, EventArgs e)
    {
        buttonAloita.Sensitive = true;
        buttonLopeta.Sensitive = false;
        VaihdaNakyvyysalue(tableLopetus);
    }

Luodaan tällainen aliohjelma (luonnollisesti dokumentointeineen). Käydään foreach-silmukalla tablet-listaa läpi.

    private void VaihdaNakyvyysalue(Table alue)
    {
        foreach (Table table in tablet)
        {

        }
    }

Kun alue on sama kuin table, vaihdetaan sen lapsiolioiden näkyvyys näkyväksi, ja muulloin piilotetuksi.

        foreach (Table table in tablet)
        {
            if ( table.Equals(alue) ) table.ChildVisible = true;
            else table.ChildVisible = false;
        }

Asetetaan vielä pääohjelmassa näkyvyysalueeksi tableAloitus, jotta tableLopetus ei näkyisi ohjelmaa käynnistettäessä.

    public MainWindow()
        : base(Gtk.WindowType.Toplevel)
    {
            ...
        VaihdaNakyvyysalue(tableAloitus);
    }

Voidaan myös initiaalisesti disabloida Lopeta-painike joko koodissa (buttonLopeta.Sensitive=false) tai käyttöliittymäeditorissa (Common Widget Properties/Sensitive ruksi pois).

Tehdään vastaava vielä muille painikkeille.

    /// <summary> Tallenna-painikkeen painallus ...
    protected void Tallenna_Click(object sender, EventArgs e)
    {
        VaihdaNakyvyysalue(tableAloitus);
    }

    /// <summary> Poista-painikkeen painallus ...
    protected void Poista_Click(object sender, EventArgs e)
    {
        VaihdaNakyvyysalue(tableAloitus);
    }
    /// <summary> Peruuta-painikkeen painallus ...
    protected void Peruuta_Click(object sender, EventArgs e)
    {
        VaihdaNakyvyysalue(tableAloitus);
        buttonAloita.Sensitive = false;
        buttonLopeta.Sensitive = true;
    }

Koko koodi tässä vaiheessa

5. Pudotusvalikot

5.1 Pääprojektit pudotusvalikkoon

Lisätään aluksi projektit.txt työskentelykansioon, jotta pääsemme lukemaan tiedostoa.

Luodaan pääohjelmaan uusi aliohjelmakutsu LuoValikko() ja luodaan tynkä. Lisätään uusi attribuutti

    private string[] projektitKaikki;

ja luetaan siihen projektit.txt

    /// <summary>
    /// Lukee tiedostosta projektien nimet ja tuottaa niistä pudotusvalikon vaihtoehdot
    /// sekä asettaa viimeksi valitun projektin oletusprojektiksi
    /// </summary>
    private void LuoValikko()
    {
        projektitKaikki = File.ReadAllLines(PROJEKTIPOLKU);
    }

Käytetään nyt jo olemassaolevaa aliohjelmaamme HaePaaprojektit ja tuotetaan lista pääprojekteista. Laitetaan tämän listan sisältä comboBoxPaaprojektit-pudotusvalikon sisällöksi.

        List<string> paaprojektit = HaePaaprojektit(projektitKaikki);
        foreach (string pp in paaprojektit) comboBoxPaaprojektit.Items.Add(pp);

(Huomaa, että Paaprojektivalikko_Changed ei saa enää sisältää virheheittoa, tai ohjelman suoritus keskeytyy.)

Nyt pudotusvalikon sisältö toimii, mutta ennen sen avaamista näkyy vain tyhjää. Voimme asettaa ensimmäisen alkion "valituksi" alkioksi.

        comboBoxPaaprojektivalikko.Active = paaprojektit.IndexOf(paaprojektit[0]);

5.2 Aliprojektit pudotusvalikkoon

Aliprojektien lisääminen on siinä mielessä vaikeampaa, että aliprojektipudotusvalikon sisältöä täytyy muuttaa joka kerta kun pääprojektia vaihdetaan.

Tehdään pääprojektipudotusvalikon tapahtumakäsittelijän sisälle uusi aliohjelmakutsu.

    private void Paaprojektivalikko_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        VaihdaPaaprojektia();
    }

Luodaan tällainen VaihdaPaaprojektia-aliohjelma ja tallennetaan uuteen muuttujaan valittu pääprojekti.

    private void VaihdaPaaprojektia()
    {
        string valittuPaaprojekti = comboBoxPaaprojektit.SelectedItem.ToString();
    }

Käytetään taas valmista aliohjelmaa HaeAliprojektit ja tallennetaan valitun pääprojektin aliprojektit paikallisesti listaan ja lisätään ne vastaavasti aliprojektipudotusvalikkoon.

        List<string> valitunAliprojektit = HaeAliprojektit(projektitKaikki, valittuProjekti);
        foreach (string aliprojekti in valitunAliprojektit) 
            comboBoxAliprojektivalikko.AppendText(aliprojekti);
        comboBoxAliprojektivalikko.Active = 0;

Mutta tässä käy huonosti! Nyt pääprojektia vaihtaessa edellisen pääprojektin aliprojektit jäävät kummittelemaan aliprojektipudotusvalikkoon.

Tyhjennetään tämän estämiseksi aliprojektipudotusvalikon alkiot ennen uusien alkioiden lisäämistä.

    private void VaihdaPaaprojektia()
    {
        string valittuProjekti = comboBoxPaaprojektivalikko.ActiveText;
        List<string> valitunAliprojektit = HaeAliprojektit(projektitKaikki, valittuProjekti);
        TyhjennaComboBox(comboBoxAliprojektivalikko);
        foreach (string aliprojekti in valitunAliprojektit) 
            comboBoxAliprojektivalikko.AppendText(aliprojekti);
        comboBoxAliprojektivalikko.Active = 0;
    }

Valitettavasti GTK:ssa ComboBox.Clear ei WPF:n tavoin oikeasti hävitä lapsiolioita. Tähän tarvitsemme uuden mystisen aliohjelman TyhjennaComboBox, jonka toimintaan ei tässä tutoriaalissa sen syvällisemmin perehdytä.

    private void TyhjennaComboBox(ComboBox cb)
    {
        cb.Clear();
        CellRendererText cell = new CellRendererText();
        cb.PackStart(cell, false);
        cb.AddAttribute(cell, "text", 0);
        ListStore store = new ListStore(typeof (string));
        cb.Model = store;
    }

Koko koodi tässä vaiheessa

6. Toiminnallisuuttaa ohjelmaan

6.1 Ajanoton lisääminen

Mitä ajan ottaminen tarkoittaa? Fundamentaalisesti kulunut aika on lopetus- ja aloitusajan erotus. Meidän ei siis tarvitsi käynnistää mitään kelloa systeemin sisällä, vaan riittää että otamme ylös kaksi "nykyhetkeä" ja laskemme niiden välisen erotuksen.

Halutaan siis tallentaa "nykyhetki" DateTime-muuttujaan Aloita-napin painalluksesta.

Voisimme lisätä DateTime-attribuutin ohjelmaan, mutta koska haluamme ajan näkyviin käyttöliittymään (ainakin jossain vaiheessa), voimme tallentaa sen jo nyt sinne ja kaivaa sen sieltä myöhemmin.

Nyt voimme lisätä Aloita_Click-aliohjelman sisälle nykyhetken tallennuksen entryAlku-kentän tekstiksi.

        entryAlku.Text = DateTime.Now.ToString();

Sama tehdään Lopeta_Click-aliohjelmalle.

        entryLoppu.Text = DateTime.Now.ToString();

6.2 Tietojen tallentaminen tiedostoon

Haluamme siis tallentaa ajat.txt-tiedostoon seuraavat tiedot:

pääprojekti|aliprojekti|aloitusaika|lopetusaika|kesto|kommentti

Pää- ja aliprojektien nimet ovat Tallenna-painiketta painaessa pudotusvalikoiden valitut objektit. Aloitus- ja lopetusajat löytyvät tekstikentistä, kuten myös mahdollinen kommentti. Meillä on siis tässä vaiheessa kestoa vaille kaikki tiedot.

Muutetaan Tallenna_Click-aliohjelmassa ajat string-muodosta takaisin DateTime-muotoon.

        DateTime aloitusaika = DateTime.Parse(entryAlku.Text);
        DateTime lopetusaika = DateTime.Parse(entryLoppu.Text);

Tehdään näiden rivien jälkeen uusi aliohjelmakutsu tiedot tallentavalle aliohjelmalle, jolle viedään parametrina kaikki em. tiedot kestoa lukuunottamatta.

        TallennaTiedot(comboBoxPaaprojektivalikko.ActiveText,
            comboBoxAliprojektivalikko.ActiveText, aloitusaika, lopetusaika, kommentti);

Luodaan tällainen aliohjelma.

    public static void TallennaTiedot(string paaprojekti, string aliprojekti,
        DateTime aloitusaika, DateTime lopetusaika, string komentti)
    {
        
    }

Kuluneen ajan saamme laskettua vähentämällä lopetusajasta aloitusajan. Tämä onnistuu kätevästi DateTime-tyypin sisäänrakennetuilla funktioilla.

        TimeSpan kulunutAika = lopetusaika.Subtract(aloitusaika);

Ylläoleva on yleismaallinen tapa laskea kesto. Koska C#-kielessä niin funktioita kuin operaattoreitakin voi kuormittaa (engl. overload), myös
TimeSpan kulunutAika = lopetusaika - aloitusaika;
tuottaisi saman lopputuloksen.

Luodaan tallenneteksti, jossa data on halutussa muodossa tolpilla eroteltuna, lopussa rivinvaihto

        string tallenneteksti = paaprojekti + "|" + aliprojekti + "|" +
            aloitusaika.ToString() + "|" + lopetusaika.ToString() + "|" +
            kulunutAika.ToString() + "|" + komentti + Environment.NewLine;

Kirjoitetaan tämä tiedostoon vanhan tekstin jatkeeksi.

        File.AppendAllText(AIKAPOLKU, tallenneteksti);

Ilmeisesti em. komento toimii, vaikkei tiedostoa olisi vielä olemassa.

Tässä vaiheessa saatetaan huomata, että edellisen tallennuksen kommentit jäävät kummitelemaan tekstikenttään. Muokataan Tallenna_Click-aliohjelmaa vielä niin, että kommenttikenttä tyhjenee tallentaessa.

        string kommentti = textBoxKommentti.Text;
        entryKommentti.Text = "";
        TallennaTiedot(comboBoxPaaprojektit.SelectedItem.ToString(), ... , kommentti);

Huomataan myös, että ohjelma kaatuu mikäli käyttäjä muokkaa aloitus- tai lopetusaikaa virheelliseen muotoon. Ympäröidään tämä kohta try-catch-silmukalla. (Huomaa, että muuttujien esittely täytyy tehdä silmukan ulkopuolella.)

        DateTime aloitusaika;
        DateTime lopetusaika;
        try
        {
            aloitusaika = DateTime.Parse(entryAlku.Text);
            lopetusaika = DateTime.Parse(entryLoppu.Text);
        }
        catch (System.FormatException)
        {
            labelIlmoitus.Text = "Kirjoita ajat oikeassa muodossa!";
            return;
        }
        VaihdaNakyvyysalue(tableAloitus);

6.3 Tallennetun tiedon näyttäminen

Tietojen tallentuessa onnistuneesti voisimme myös näyttää ilmoitustekstinä tämän tiedon.

Miten saamme kuluneen ajan näytettyä? Kulunut aikahan laskettiin TallennaTiedot-aliohjelmassa. Tämä aliohjelma on kuitenkin staattinen, joten se ei voi sorkkia tekstikenttiä. Muokataan aliohjelmasta seuraavaksi sellainen, että se palauttaa ilmoitusviestin.

Muokataan Tallenna_Click-aliohjelmaan ilmoitustekstin näyttäminen entryIlmoitus-kentässä TallennaTiedot-aliohjelman paluuarvona.

        labelIlmoitus.Text = TallennaTiedot(comboBoxPaaprojektivalikko.ActiveText, ...)       

Muokataan TallennaTiedot-aliohjelma palauttamaan merkkijonon.

    public static string TallennaTiedot(string paaprojekti, ...)
    {
        TimeSpan kulunutAika = lopetusaika.Subtract(aloitusaika);
            ...
        return "Projektiin " + paaprojekti + " käytetty aika: " + kulunutAika.ToString();
    }

Koko koodi tässä vaiheessa

7. Raportin näyttäminen

7.1 Raportti uudessa ikkunassa

Raportti näytetään "Tiedosto"-menun kohdasta "Raportti", jonka tapahtumakäsittelijä on Raportti_Activated. Luetaan täällä projektit ja ajat, ja tallennetaan raportti-merkkijonoon Raportoi-aliohjelman paluuarvo.

    protected void Raportti_Activated(object sender, EventArgs e)
    {
        string[] projektit = File.ReadAllLines(PROJEKTIPOLKU);
        string[] ajat = File.ReadAllLines(AIKAPOLKU);
        string raportti = Raportoi(projektit, ajat);
    }

Luodaan tämän jälkeen aluksi tyhjä dialogi (Dialog).

        Dialog dialogi = null;

Luodaan uusi tekstinnäyttökenttä (TextView). Tämä on kuin Entry, mutta voi olla monirivinen (ja sen tekstisisältö TextView.Buffer.Text). Tallennetaan sen tekstiksi raportti.

        TextView textViewRaportti = new TextView();
        textViewRaportti.Buffer.Text = raportti;

Kirjoitetaan uusi try-finally-lohko, jonka päätteeksi dialogi tuhotaan.

        try 
        {

        } 
        finally 
        {
            if (dialogi != null) dialogi.Destroy ();
        }

GTK:n Dialog ottaa ottaa parametreina otsikon, isännän (engl. parent), DialogFlags-ominaisuudet ja taulukon painikkeiden tiedoista (otsikko ja toimintotyyppi). Luodaan try-lohkon sisään uusi Dialog dialogi.

        try 
        {
            dialogi = new Dialog("Raportti", this,
                DialogFlags.DestroyWithParent | DialogFlags.Modal,
                "Sulje", ResponseType.Close);
        }

Syntyvän dialogin otsikoksi tulee siis "Raportti", ja sen isäntä on this (joka tässä tapauksessa on ohjelman pääikkuna MainWindow). Isännän tuhoutuessa myös dialogi tuhotaan, ja dialogi on modaalinen (jättää disabloidun pääikkunan näkymään). Lisäksi luodaan painike Sulje, jonka toimintotyyppi on Close (sulje).

Lisätään lohkon sisällä vielä dialogin vertikaaliseen sisältölaatikkoon (VBox) aiemmin luomamme textViewRaportti, näytetään kaikki dialogin sisältö ja käynnistetään dialogi.

            dialogi.VBox.Add(textViewRaportti);
            dialogi.ShowAll();
            dialogi.Run();

Uusi ikkuna avautuu nyt valikosta hienosti, mutta nimet ja luvut rivittyvät rumasti. Tämä johtuu siitä ettei oletusfontti ole tasalevyistä (engl. monospaced). Tasalevyisessä fontissa kaikki merkit ovat yhtä leveitä, jolloin merkit ovat nätisti "ruudukossa". Suosittu tasalevyinen fontto on esimerkiksi Courier New. Asetetaan tämä textViewRaportti-tekstinnäyttökentän fontiksi.

        textViewRaportti.ModifyFont(FontDescription.FromString("Courier new"));

Koko koodi tässä vaiheessa

8. Valitun projektin muistaminen

8.1 Nykyisten projektien tallentaminen tiedostoon

Tahdomme ohjelmasta sellaisen, että ohjelma "muistaa" mikä projektikokonaisuus on ollut viimeksi valittuna. Jos projekteja on paljon, tämä on kätevä ominaisuus, sillä ohjelman voi tällöin huoletta sulkea, ja ensi kerran avatessa voi ajastaa taas edellistä projektia nopeasti.

Tehdään tätä varten uusi tiedosto, johon tallennetaan pää- ja aliprojekti. Luodaan pääluokan attribuutiksi tämän tiedoston polku.

    private const string NYKPROJPOLKU = "nykproj.txt";

Mihin kohtaan koodia tämän tiedoston muokkaaminen pitäisi lisätä? Meidän ei välttämättä ole tarpeen muokata nykyisen pää- ja aliprojektin tietoja erikseen, sillä ne kulkevat aina yhtenä kokonaisuutena. Koska pääprojektin vaihtaminen vaihtaa nykyisellään aina myös aliprojektin, voimme suorittaa tiedostoon tallennuksen aliprojektia vaihtaessa.

Kutsutaan aliprojektivalikon tapahtumakäsittelijästä uutta aliohjelmaa VaihdaAliprojektia.

        private void Aliprojektivalikko_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            VaihdaAliprojektia();
        }

Luodaan tällainen aliohjelma, jossa tallennetaan merkkijonoon valitutProjektit pudotusvalikoiden valitut projektit rivinvaihdolla erotettuna.

    private void VaihdaAliprojektia()
    {
        string valitutprojektit = comboBoxPaaprojektivalikko.ActiveText + Environment.NewLine +
            comboBoxAliprojektivalikko.ActiveText;
    }

Kirjoitetaan lopuksi valitutProjektit tiedostoon. File.WriteAllText korvaa surutta kaiken aiemman tiedostossa olevan.

        File.WriteAllText(NYKPROJPOLKU, valitutprojektit);

8.2 Tallennettujen projektien lukeminen

Seuraavaksi vaihdetaan ohjelman käynnistyessä pudotusvalikoiden valitut projektit tallennettuihin.

Luetaan luotu tiedosto ohjelman käynnistyksen yhteydessä suoritettavassa LuoValikko-aliohjelmassa.

        string[] valitutProjektit = File.ReadAllLines(NYKPROJPOLKU);

Tässäkin tapauksessa kannattaa ennen tiedoston lukemisen yrittämistä varautua tiedoston puuttumiseen.

        if (!File.Exists(NYKPROJPOLKU)) return;

ComboBox.Active ottaa parametrina näytettävän tekstin indeksin. Saamme tämän kaivettua paaprojektit-listasta.

        comboBoxPaaprojektivalikko.Active = paaprojektit.IndexOf(valitutprojektit[0]);

Aliprojekteja varten on aliprojektit ensin haettava omaan listaan.

        List<string> aliprojektit = HaeAliprojektit(projektitKaikki, valitutprojektit[0]);
        comboBoxAliprojektivalikko.Active = aliprojektit.IndexOf(valitutprojektit[1]);

Mutta tämä ei toimi. Nykyisellään pää- ja aliprojekteiksi tulee edelleen ensimmäiset pää- ja aliprojektit, vaikka tiedostoon tallentuukin vaihdetut projektit.

Miten ongelmaa lähteä ratkaisemaan? Videolla laitetaan keskeytyskohta valitutProjektit-tiedoston lukemiseen ja huomataan, että tiedosto on muuttunut tallentamisen jälkeen. Tämä tarkoittaa sitä, että jossakin aiemmassa vaiheessa koodia tiedostoa on sorkittu. Jos muu ei auta, lähde etsimään riviä askel kerrallaan F11 debuggaamalla.

Ongelmakohtaa löytyy tässä tapauksessa pari riviä ylempää, missä paaprojektit-listan ensimmäinen alkio asetetaan pääprojektipudotusvalikon valituksi projektiksi. Poistetaan tämä rivi.

    private void LuoValikko()
    {
        projektitKaikki = File.ReadAllLines(PROJEKTIPOLKU);

        List<string> paaprojektit = HaePaaprojektit(projektitKaikki);
        foreach (string pp in paaprojektit) comboBoxPaaprojektivalikko.AppendText(pp);
         // POISTA // comboBoxPaaprojektivalikko.Active = paaprojektit.IndexOf(paaprojektit[0]);

        if ( !File.Exists(NYKPROJPOLKU) ) return;
        string[] valitutprojektit = File.ReadAllLines(NYKPROJPOLKU);
        comboBoxPaaprojektivalikko.Active = paaprojektit.IndexOf(valitutprojektit[0]);

        List<string> aliprojektit = HaeAliprojektit(projektitKaikki, valitutprojektit[0]);
        comboBoxAliprojektivalikko.Active = aliprojektit.IndexOf(valitutprojektit[1]);
    }

Koko koodi tässä vaiheessa

9. Viimeistely

9.1 Ulkoasullista hienosäätöä

Ohjelmamme avautuvan ikkunan otsikko on tälle hetkellä "MainWindow". Voimme muuttaa tätä desginerissä valitsemalla MainWindow kohdasta Window Properties/Title.

9.2 Reunuksista

Oikea ja vasen laita ovat tällä hetkellä rumasti kiinni tai pitkällä irti ikkunan laidoista. Voimme säätää hiirellä ikkunan oikean kokoisiksi ja asettaa kontrollien reunuksia paremmaksi kohdasta Table Child Properties/XPadding.

9.3 Ohjelman sulkeminen

"Poistu"-valikohkohta ei vielä toimi. Asetetaan sen tapahtumakäsittelijään ohjelman sulkeminen.

Valmis koodi

These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.