Asetusten tallentaminen, verkko, lokalisointi - Pääteohjaus 8
Käydään läpi miten ohjelman asetuksia voi tallentaa rekisteriin (registry) ja .config-tiedostoihin. Tutustutaan hieman verkko-ohjelmointiin ja toteutetaan yksinkertainen asiakas ja palvelin eli WWW-selain ja WWW-palvelin.
Registry
Tee luentomallin ja artikkelin Registry In's and Out's Using C# avulla seuraavat tehtävät:
- Luo uusi WPF-lomake. Lisää lomakkeelle muutama textbox joihin voit kirjoittaa nimesi ja sähköpostiosoitteesi.
- Lisää lomakkeelle painike joka tekee seuraavan: Luo rekisteriin HKEY_CURRENT_USER-tason alle uusi avain nimeltään: GKO. Lisää tälle avaimelle kaksi avainta joiden nimet ovat Name ja Email. Tallenna näihin avaimiin lomakkeelle syöttämäsi nimi ja sähköpostiosoite. Huomaa, että kirjoitustarve pitää tietää jo avainta avattaessa.
- Varmista, että painike toimii järkevästi vaikka edellämainitut avaimet olisivat jo olemassa (poikkeukset). Lisää siis Try-Catch-lohko kaiken varalta. Kaikkiin avaimiin ei aina ole luku- tai kirjoitusoikeutta jolloin aiheutuisi poikkeus.
- Lisää lomakkeelle painike, joka lukee edellämainitut tiedot rekisteristä ja päivittää ne lomakkeella oleviin kenttiin.
- Lisää vielä painike, joka poistaa edelläluodut avaimet rekisteristä. Ole varoinen, että et poista mitään muuta!
Settings
Katso luentovideosta ja mallikoodista miten käytetään settingsejä Visual Studiossa. Peruskaava lyhyesti selitettynä on seuraava:
- Asetuksia voi lukea ja muokata suoraan Properties.Settings-objektin kautta esim.
Properties.Settings.Default.Email = "tjlahton@mit.jyu.fi"; // tai Properties.Settings.Default["Email"] = "tjlahton@mit.jyu.fi";
- Asetukset tallennetaan:
Properties.Settings.Default.Save();
- Jos käyttää asetuksia app.xaml.cs-tasolla niin pitää laittaa
projektin nimi asetusten eteen:
string email = Ohjelma.Properties.Settings.Default.Email;
- Asetukset tallennetaan käyttäjän asetuksiksi johonkin kansioon, jonka
sijaintia emme tiedä eikä tarvitsekaan tietää.
Asetuksia on myös applikaatiotasolla ja ne tallennetaan ohjelman omaan kansioon ohjelma.config-tiedostoon mutta näitä ei ole tarkoitus muuttaa ohjelman suorituksen aikana vaan kaikki normaalit asetukset pitää tallentaa käyttäjätasolle!
- ConfigurationUserLevel Enumeration
- Client Settings FAQ
Tehtäviä
- Omia asetuksia pääset muokkaamaan kun etsit Solution Explorerista Properties-valinnan alta settings.settings-tiedoston.
- Tee vastaavat asetukset mitä rekisteritehtävän yhteydessä ja toteuta niiden avulla sama toiminnallisuus kuin rekisteritehtävässä. Tosin nyt et tarvitse poista-painiketta.
- Yritä sitoa (Binding) käyttöliittymässä olevat kontrollisi suoraan settingseissä oleviin tietoihin. Tee sidonnasta kaksisuuntainen. Huomaa, että et voi tehdä tätä helposti pelkästään XAMLin kautta vaan joudut määrittelemään kontrollin DataContextin ohjelmakoodissa. Jos haluat tehdä saman pelkästään XAMLin kautta niin tutustu sivuun WPF Binding My.Settings collection to Combobox items
- Kokeile mitä eroa on Application- ja User-asetuksilla. Tutki mitä tiedostoja ilmestyi
ohjelmasi kansioon sekä seuraaviin kansioihin:
C:\Documents and Settings\omatunnus\Application Data\University_Of_Jyväskylä C:\Documents and Settings\omatunnus\Local Settings\Application Data\University_Of_Jyväskylä
WWW-selain
- Tehdään hyvin yksinkertainen prototyyppi WWW-selaimesta. HTTP-protokollan tuntemus voi olla eduksi.
- Aloita uusi WPF-projekti. Lisää lomakkeelle textbox www-palvelimen nimeä varten, hakupainike ja listbox johon haetaan WWW-palvelimelta saatavat tiedot.
- Lisää seuraavat kirjastot käyttöösi:
using System.Net; using System.Net.Sockets; using System.Net.Mime; using System.Threading; using System.Windows.Threading; using System.IO;
-
Ensimmäiseksi pitää avata yhteys www-palvelimeen. Aseta painikkeesta tapahtumaan jotain seuraavanlaista:
try { // huomaa, että textboxissa saa lukea vain palvelimen osoite eli esim. www.google.com eikä mitään muuta palvelin = textBoxPalvelin.Text; // palvelin on luokan attribuutti string palvelin TcpClient client = new TcpClient(); // avataan yhteys palvelimen porttiin 80 // asynkronisesti eri säikeeseen (thread), joka kutsuu httpCallBack-metodia kunhan yhteys aukeaa // viimeinen parametri saa olla mitä tahansa client.BeginConnect(palvelin, 80, new AsyncCallback(httpCallBack), client); } catch (Exception err) { MessageBox.Show(err.Message); }
- Kirjoita myös httpCallBack-metodi:
public void httpCallBack(IAsyncResult Ar) { MessageBox.Show("yhteys on avattu"); }
- Kokeile ohjelmaa. Kokeile mitä tapahtuu jos yrität messageboxin sijaan kirjoittaa "yhteys on avattu" lomakkeella olevaan listboxiin. Miten käy?
- Joudut kirjoittamaan seuraavan apumetodin listboxin päivittämistä
varten:
// päivittää käyttöliittymässä olevaa listboxia muistakin // säikeistä kuin käyttöliittymäsäikeesta void addToListBox(ListBox b, string e) { this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { b.Items.Add(e); })); }
- Kokeile nyt korvata messagebox seuraavantyyppisellä kutsulla:
addToListBox(listBoxTulos, "yhteys on avattu");
- Yhteys on auki mutta sillä ei vielä tehdä mitään.
Lisää httpCallBackiin seuraavat:
TcpClient client = (TcpClient)Ar.AsyncState; // luodaan stream josta voidaan lukea ja johon voidaan kirjoittaa NetworkStream ns = client.GetStream(); StreamReader sr = new StreamReader(ns); StreamWriter sw = new StreamWriter(ns); // http-protokolla edellyttää seuraavia. Pyydetään // palvelimen juuressa olevan index.html-tiedoston otsaketietoja sw.WriteLine("HEAD /index.html HTTP/1.1"); // pitää kertoa myös miltä palvelimelta näitä kysytään koska // samassa ip-osoitteessa voi sijaita useita palvelimia sw.WriteLine("Host: " + palvelin); // http-protokolla edellyttää lopuksi tyhjää riviä sw.WriteLine(""); sw.Flush(); // luetaan palvelimen lähettämää dataa niin kauan kuin sitä tulee while (sr.Peek() != -1) { string data = sr.ReadLine(); addToListBox(listBoxTulos, data); // annetaan toiminta-aikaa myös muille säikeille Thread.Sleep(100); } sr.Close(); sw.Close(); ns.Close(); client.Close(); }
- Kokeile miten ohjelma toimii nyt. Jos kaikki meni oikein niin WWW-palvelin palauttaa sivun otsikkotietoja ja saat ne näkyviin listboxin sisään.
- Voit kokeilla miten sama toimisi command promptin kautta telnet-ohjelmalla niin saat paremmin hahmotettua http-protokollan toimintaa. kirjoita command promptiin telnet www.google.com ja sen jälkeen samat asiat mitä ohjelmasi.
- Kokeile mitä tapahtuu jos muutat HEAD-sanan tilalle GET.
- Mahdollista minkä tahansa sivun hakeminen. Salli textboxiin kokonaisen www-sivun osoitteen kirjoittaminen ja parsi siitä erikseen palvelimen osoite ja varsinaisen sivun polku. Korvaa /index.html haluamasi sivun polulla.
- Nyt osaat kirjoittaa yksinkertaisen asiakas-ohjelman, joka ymmärtää tekstimuotoista protokollaa. Samaan tapaan onnistuisi moni muukin kuin http-protokolla. Olennaista on tietää käytettävän protokollan muoto eli siirtyykö data määrämittaisina paloina vai erotinmerkein rajattuna. Esim. http-protokollassa rivinvaihto toimii erotinmerkkinä. Jos käytössä olisi määrämittaisia paloja niin streamista lukeminen pitäisi toteuttaa hieman eri tavalla.
- Kokeile miten vastaava onnistuisi valmiilla webclient-luokalla.
WWW-palvelin
- Aloita uusi WPF-sovellus josta tulee hyvin yksinkertainen WWW-palvelin
- Palvelin ei tarvitse mitään graafista käyttöliittymää. Lisää suoraan
lomakkeen loaded-tapahtumaan seuraava ohjelmakoodi:
private void Window_Loaded(object sender, RoutedEventArgs e) { // haetaan tämän koneen osoite. Voitaisiin myös suoraan käyttää osoitetta 127.0.0.1 System.Net.IPAddress address = Dns.GetHostEntry("").AddressList[0]; // varmistetaan, että käytetään IPV4-osoitetta eikä vahingossa IPV6:sta foreach (System.Net.IPAddress item in Dns.GetHostEntry("").AddressList) { if (item.AddressFamily == AddressFamily.InterNetwork) { address = item; break; } } this.Title = address.toString(); TcpListener server = new TcpListener(address.AddressList[0], 9001); // ei laiteta porttiin 80 mikä olisi oikea www-palvelimen portti server.Start(); server.BeginAcceptTcpClient(new AsyncCallback(serverCallBack), server); }
- Lisää myös tarvittava serverCallBack:
public void serverCallBack(IAsyncResult Ar) { TcpListener server = (TcpListener)Ar.AsyncState; TcpClient client = (TcpClient)server.EndAcceptTcpClient(Ar); NetworkStream ns = client.GetStream(); StreamReader sr = new StreamReader(ns); StreamWriter sw = new StreamWriter(ns); // luetaan arraylistiin kaikki rivit mitä selain (asiakas) lähettää ArrayList data = new ArrayList(); while (sr.Peek() != -1) { try { data.Add( sr.ReadLine() ); } catch (IOException) { break; } Thread.Sleep(0); // annetaan muiden mahdollisesti odottavien threadien toimia välillä } // tässä pitäisi tarkistaa, että ensimmäinen rivi on GET jne... ja viimeinen rivi tyhjä rivi (pelkkä rivinvaihto) sw.WriteLine("HTTP/1.x 200 OK"); sw.WriteLine("Content-type: text/html"); // Tämä pitää olla // ohitetaan läjä muita rivejä sw.WriteLine("\n"); // rivinvaihdon jälkeen alkaa HTML tai mitä sitten ikinä palvelimelta pyydettiinkin // tässä pitäisi oikeasti tulostaa pyydetyn tiedoston sisältö sw.WriteLine("<html><head><title>Foobar</title>%lt;/head><body><h1>foobar</h1>/body></html>"); sw.Close(); sr.Close(); ns.Close(); }
- Kokeile miten palvelin toimii. Muuta selain-ohjelmaasi siten, että se käyttää samaa porttia mitä palvelin (9001) ja yritä sen jälkeen ottaa yhteys palvelimeesi. Jos kaikki menee hyvin niin saat palvelimelta vastauksen.
- Kokeile ottaa yhteyttä myös vieruskaverisi koneella olevaan palvelimeen
- Huomaa, että palvelimesi osaa nyt käsitellä vain yhden pyynnön. Miten saat palvelimen käsittelemään useita pyyntöjä? vinkki: BeginAcceptTcpClient.
- Voisit nyt parannella palvelinta lisäämällä siihen oikeaa protokollan tunnistamista eli tarkistaa onko ensimmäisen rivin ensimmäinen sana GET jne.
Lokalisointi
Lokalisoidaan WPF-sovelluksia käyttäen resx-tiedostoja ja LocBaml-työkalua.
- Tee pieni WPF-sovellus jossa on muutama painike, tekstikenttä ja ehkä muitakin kontrolleja. Lue luentosivun ohje lokalisoinnista resx-tiedostojen avulla. Tee ohjelmastasi kaksikielinen resx-tiedostojen avulla.
- Tee uusi pieni WPF-sovellus. Lue luentosivun ohje lokalisoinnista LocBaml-työkalun avulla. Tee ohjelmastasi kaksikielinen LocBaml-työkalulla avulla. Huomaa, että LocBamlia ja lokalisoituja resx-tiedostoja ei kannata käyttää samassa ohjelmassa vaan vain jompaa kumpaa.
- Huomaa, että ohjelman kieltä ei voi vaihtaa lennosta ohjelman suorittamisen aikana. Jos haluat ajonaikaisesti toimivan kielen vaihtamisen niin joudut tekemään kokonaan oman lokalisointijärjestelmän tai muita lisävirityksiä. Aiheesta lisää artikkeleissa: WPF Runtime Localization ja WPF Localization Using RESX Files
Käyttäjien kommentit