Pääsivulle | TIETOTEKNIIKAN PÄÄSIVULLE
Ohjelmointivinkkejä (C#)
C# ja Visual Studio
Seuraavassa esitetään muutamia ohjelmointivinkkejä asioista, joita
aikoinaan jouduin miettimään hieman enemmän.
- Vain yksi sovellusikkuna (WPF)
- Kartan näyttäminen ja koordinaatit
- Koordinaattipisteiden keskinäisen etäisyyden laskeminen
- Ovatko koordinaatit Suomesta
- Onko meillä nettiyhteys
- Videoleikkeet
- Macista haetun tiedostonimen skandit
- Exif-koodit
- Alasvetovalikko (WPF)
- Kuva ikkunassa (WPF)
- Tietolähteen käyttäminen (WPF)
- Kuvamatriisi (WPF)
- Datagrid (taulukkonäkymä) (WPF)
Vain yksi sovellusikkuna (WPF)
Sovellukseni etenee tyypillisesti näkymästä toiseen, esimerkiksi:
päävalikon sisältämä näyttö -> Kasvin haku (hakuehdot ja
taulukko hakutuloksista) -> Katsotaan kasvin tiedot. Tähän asti
jokainen näkymä on minulla avautunut omaan ikkunaansa. Tämä tekee
yleisvaikutelman sekavaksi ja joskus se aktiivinen ikkuna on
hukassa.
Löysin syksyllä WPF:n elementin "UserControl". Se voi
sisältää kaikkia osaelementtejä mitä ikkunassakin on eli
buttoneita, tekstikenttiä, taulukoita jne. Ensin tehdään
edellämainittu päävalikon sisältämä näyttö. Siinä on
Window-elementti ja sen sisällä UserControl, jossa on tarpeelliset
buttonit ym. Muissa näkymissä (xaml-tiedostoissa) ei ole ollenkaan
Window-elementtiä, vaan se on yksi UserControl buttoneineen ym.
Kun aloitusikkunassa esimerkiksi klikataan buttonia haluten
siirtyä seuraavaan näkymään (UserControlliin) nimeltä Kasvihaku,
niin aloitusikkunan sisältö korvataan uudella näkymällä:
Window win
= Window.GetWindow(this); // saadaan taustaikkuna
win.Content = new Kasvihaku(); // sisältö vaihdetaan
Ohjelmallisesti tämä merkitsee myös sitä, että näkymää avatessa
sen sisältö määritellän kokonaan uudestaan. Jos olemme tehneet
kasvihaun näkymässä Kasvihaku(), ja siellä klikataan katsomaan
jonkun kasvin tietoja, niin näkymää Kasvihaku() ei enää ole
"alla". Ja kun palataan kasvin tietoja katsomasta, niin avautuu
tyhjä Kasvihaku. Yleensä tässä tilanteessa haluaa nähdä samat
hakuehdot ja tulokset kuin hetki sitten.
Jokaisen avattavan näkymän alkuarvojen on siis oltava olemassa.
Tämän olen toteuttanut listarakenteella:
public
static List<Tila> seurx = new(); // usercontrollien input
Eli meillä on listana avattavien "päällekkäisten" usercontrollien
lähtödata. Kun esimerkiksi Kasvihaku-näytöllä kirjoitetaan
hakuehtoja, ne tallennetaan myös alkuarvojen listaan.
Edellämainittu Tila on esimerkiksi seuraavaa:
public
class Tila
{
public string Seurx { get; set; } = "";
// seuraava näkymä, esim "KASVIHAKU"
public KashakuKent Kkent { get; set; } =
new(); // Kasvihaku
// ....
public string Kpolkuz { get; set; } = ""; //
kuvan koko polku mm -> Kuvaikku
public string Kinfoz { get; set; } = ""; //
kuvaan ym liittyvä info
// ...
public int IDkohde { get; set; } = EIOO;
public int IDkasvi { get; set; } = EIOO;
}
Aina kun avataan seuraava näkymä (usercontrol), se tapahtuu näin:
Window win
= Window.GetWindow(this); // tallennetaan oma ikkunakoko
poistumishetkellä
seurx[0].Wheight = win.Height;
seurx[0].Wwidth = win.Width;
// kerätään seuraavan ikkunan data:
Tila tila = new();
tila.Seurx = "KASVIKATSO"; // seuraavan ikkunan nimi
tila.IDkasvi = 55; // esimerkki inputista
tila.Wheight = 400; // seuraavan ikkunan koon vakioarvot
tila.Wwidth = 700;
seurx.Add(tila); //
lisätään avattavan näkymän tiedot pinoon
seurx.Insert(0, tila);
// ikkunan koko on annettava ennen näkymän avaamista
win.Height = tila.Wheight;
win.Width = tila.Wwidth;
win.Content = new Kasvikatso(); // avataan seuraava näkymä
Ja kun palataan takaisinpäin, niin tarvitaan tällaisia vaiheita:
//
katsotaan edellisen, nyt avattavan ikkunan mitat
Window win = Window.GetWindow(this);
win.Height = seurx[1].Wheight;
win.Width = seurx[1].Wwidth; // ikkunalle koko ennen
näkymän avaamista
seurx[1].IDkuva = ... // voidaan viedä tietoa nyt avattavalle
näkymälle
seurx.RemoveAt(0); // poistetaan pinosta suljettava näkymä
win.Content = ValitseNakyma(seurx[0].Seurx); // edellinen näkymä
avataan
Edellä oleva funktio ValitseNakyma nimensä mukaan suorittaa
kutsun, jolla haluttu näkymä avataan. Funktion olennainen sisältö
on switch-rakenne, esimerkiksi (C# 8.0):
return
nimiz switch
{
"KUVAIKKU" => new Kuvaikku(),
"PAAIKKU" => new Paaikku(),
"KAIKIKKU" => new Kaikikku(),
}
Näkymässä muutettuja arvoja (edellä kasvihaun hakuehtoja) voi
xaml-rivillä määritellä meneväksi haluttuu paikkaan, esimerkiksi:
<TextBox
x:Name="txtSunimi" Text="{Binding Kkent.Sunimi}"
Margin="3" Width="100" />
Tämä ei ole täydellinen ratkaisu, koska binding-paikkaan
vietäessä avatun luokan (näkymän) sisällä, tekstikenttä ei
päivity. Lisäksi ComboBox toimii hieman eri logiikalla.
Yksinkertaisinta voi olla:
- juuri ennen seuraavaan näkymään menemistä kerätään ilman
bindingeja kentissä olevat arvot talteen pinon Tila-muuttujaan
- heti näkymän avaamisen jälkeen viedään Tila-muuttujasta arvot
tekstikenttiin tyyliin
txtSunimi.Text = "Valkovuokko";
Näillä periaatteilla olen päivittänyt Herbaariosovellukseni
onnistuneesti. Loppuviimeistelyaikaa meni ikkunakokojen ja värien
määrittelyyn.
Kartan näyttäminen ja koordinaatit
Monissa sovelluksissani asioiden sijaintia näytetään kartalla.
Esimerkiksi albumisovelluksessa kuvan ottopaikka ja
herbaariosovelluksessa kasvin löytöpaikka. Näiden sovellusten
tietokannoissa on EUREF-FIN -koordinaatit, jotka nykyään saa
helpoiten suoraan kameran tai kännykän tekemästä kuva- tai
videotiedostosta. Saahan ne myös GoogleMapsin näytöltä valitsemalla
hiiren oikealla painikkeella 'Mitä täällä on'. Sovellukseni
näyttävät sijainnin käyttäjän valinnan mukaan joko OpenStreetMapin
karttapohjalla tai Maanmittauslaitoksen maastokartalla.
Käytin aiemmin Googlen APIa (GoogleMap), mutta sitten Google vaati
luottokortin numeroa ja suostumusta veloitukseen, vaikka
tämäntyyppisestä käytöstä Google ei (toistaiseksi) laskuta.
Luottamukseni ei ole kovin suurta siihen, etteikö siinäkin asiassa
asiakkaalle ilmoittamatta muutettaisi sopimusehtoja.
Karttapiste on helpointa esittää urlin avulla, jossa on sisään
leivottuna koordinaatit. OpenStreetMap haluaa koordinaatit
LatiLongi, kun taas Maanmittauslaitoksen maastokartta haluaa
koordinaatit muodossa NorthEast:

Sovelluksessa ensin haetaan koordinaattiarvot (tietokannasta, tai
käyttäjä antaa, tai valokuvasta jne.) ja sitten muodostetaan
ylläolevan mallin mukainen merkkijono. Sovelluksessa on otettu
käyttöön kirjasto WebView2, jonka kanssa näyttö tehdään näin:
<Window
x:Class="Albumi8.Karttaselain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Albumi8"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
Title="Karttaselain"
Height="600" Width="800">
<DockPanel>
<wv2:WebView2
Name="webView"
Source="https://www.microsoft.com"
/>
</DockPanel>
</Window>
Ja sitten cs-tiedostossa annetaan näkymälle lähde:
webView.Source
= new Uri(maasz); // tai openz edeltä
Ennen edelläkuvattua koodausta kuitenkin asennuksia:
Microsoftin WebView2 -kirjasto otetaan Visual Studion ja sovelluksen
käyttöön seuraavasti:
1) Kehityskoneeseen ja loppukäyttäjän koneeseen on asennettava
Webview2-kirjasto täältä
Lataa sieltä Evergreen WebView2 Runtime
Bootstrapper
asennettavaksi tulee MicrosoftEdgeWebview2Setup.exe
2) Kehityskoneessa Visual Studioon menusta Project "Manage NuGet
Packages" ja asenna sieltä
Microsoft.Web.WebView2 by Microsoft
Sovelluksessa voi olla tarpeen tehdä koordinaattimuunnoksia
LatiLongi -> NorthEast. Täällä
on käyttöösi koodia.
Koordinaattipisteiden keskinäisen etäisyyden laskeminen
Sovelluksessa voi olla tarpeen selvittää kuinka kaukana jokin
paikka on toisesta paikasta, kun molempien koordinaatit tunnetaan.
Minulla tämä tuli eteen, kun halusin löytää tietokannasta kuvat,
joiden ottopaikat on tietyn ympyrän sisällä tietystä
koordinaattipisteestä. Tietokannassa on kuvien koordinaatit.
Seuraava funktio laskee onko kahden pisteen etäisyys alle vai yli
annetun etäisyyden ja tarvittaessa myös pisteiden etäisyyden
kilometreinä. Funktion sisällä on nopea karkealaskenta ja lisäksi
noin 7 kertaa hitaampi tarkka laskenta:
//-----------------------------------------------------------------------
// (Windows C#)
// Lasketaan etäisyys näiden välillä: koord_1 ja koord_2.
Outputtina
// etäisyys metreinä tai
// -1 jos oli ongelma tai
// detmax + 1000.0, jos pikalaskenta antoi, että
etäisyys > detmax
// Jos detmax < 0.0, tuloksena aina etäisyys metreinä
käyttäen tarkempaa laskentaa
// Jos detmax > 0.0, tehdään ensin nopea karkealaskenta ja
sen jälkeen tarvittaessa tarkempi
// Jos detmax > 0.0 ja etäisyys sitä suurempi, tuloksena
joko -1, detmax + 1000 tai todellinen etäisyys
// Käytetään etäisyyden laskentaan Pythagooran teoreemaa
// Ennen kutsua tulee olla tarkastettu, että koordinaateissa
vain numeroita ja piste
public static long KoordEtaisyys(double dlat1, double dlon1,
string lat2, string lon2, double detmax)
{
long R = 6371000; // maapallon säde
metrejä
if ((detmax > 1000000.0) || (detmax <
0.0 && detmax != -1.0)) // yli 1000 km ei
kelpaa
{
MessageBox.Show($"Input
etmax virheellinen: {detmax}", "(M25-01) Ohjelmointivirhe");
return -1;
}
if (lat2.IsEmpty() || lon2.IsEmpty())
{
MessageBox.Show("Input-koordinaateissa tyhjää", "(M25-02)
Ohjelmointivirhe");
return -1;
}
double dlat2 = double.Parse(lat2,
CultureInfo.InvariantCulture);
double dlon2 = double.Parse(lon2,
CultureInfo.InvariantCulture);
// Tarkastetaan ovatko koordinaatit
identtiset
double ero1 = dlat1 - dlat2;
double ero2 = dlon1 - dlon2;
if (ero1 < 0.0001 && ero1 >
-0.0001 && ero2 < 0.0001 && ero2 >
-0.0001)
return 0;
// Muunnetaan inputit radiaaneiksi
double fii1 = (Math.PI / 180) * dlat1;
double fii2 = (Math.PI / 180) * dlat2;
if (detmax > 0.0) // Pikalaskenta
leveyspiiriä pitkin:
{
double ddlat =
Math.Abs(R * (fii2 - fii1));
if (ddlat > detmax)
return (long)detmax + 1000; // liian kaukana
toisistaan
}
double lan1 = (Math.PI / 180) * dlon1;
double lan2 = (Math.PI / 180) * dlon2;
if (detmax > 0.0) // Pikalaskenta
pituuspiiriä pitkin:
{
double ddlon =
Math.Abs(R * (lan2 - lan1) * Math.Cos((fii1 + fii2) / 2));
if (ddlon > detmax)
return (long)detmax + 1000; // liian
kaukana toisistaan
}
// Lasketaan etäisyys tarkemmalla kaavalla
double x = (lan2 - lan1) * Math.Cos((fii1 +
fii2) / 2);
double y = fii2 - fii1;
double dd = Math.Sqrt(x * x + y * y) * R;
return (long)dd;
}
Ovatko koordinaatit Suomessa
using
System.Net.Http;
//------------------------------------------------------------
// 26.1.2022 M27
// Missä maassa koordinaatit ovat
// Output "Suomi", "Muu"
// "" ei selvinnyt eli joko ei
nettiä tai googlen palvelu ei vastannut
// Maa selvitetään ensisijaisesti oheisten koordinaattien avulla:
// - Suomi suorakaiteen sisällä: ulkopuolinen alue on "Muu"
// - Suomen sisäpuoli (alla taulukko luvut): sisäpuolinen alue on
"Suomi"
// Em. koordinaattien välinen alue tutkitaan neOpenStreettistä
-palvelun avulla: onko inputtina
// olevat koordinaatit Suomea vai ei, tulos "Suomi", "Muu" tai ""
(ei nettiä)
// Input latiz ja longiz oltava oheisessa muodossa:
// string maaz = EtsiMaa("60.582535", "25.682804");
public static string EtsiMaa(string latiz, string longiz)
{
// Suomen sisäpuoli on "viipaloitu
vaakasuoraan"
// Vaakasuora latitude-viiva, pystysuorat rajat
logi vasen ja longi oikea:
// vaaka[i], lonvas[i], lonoik[i]
// lati, vaakasuoran viivan vasen reuna
(lonvas), oikea reuna (lonoik)
double[] luvut =
{
59.77325,
21.80186, 23.58164,
59.92775,
19.94517, 24.72422,
60.15270,
19.45078, 25.95469,
60.34352,
19.36289, 27.65757,
60.54943,
19.91221, 27.75645,
60.68419,
20.97788, 27.96519,
61.32852,
21.27451, 29.15171,
62.69959,
20.67257, 31.31554,
63.35327,
20.96920, 30.75787,
63.69035,
22.30675, 29.97521,
64.74155,
23.94419, 29.62364,
65.52683,
24.96544, 29.52477,
65.91980,
24.11949, 29.75548,
66.36346,
23.77057, 29.48346,
66.80510,
24.04259, 28.81733,
68.40746,
23.53290, 28.50567,
68.54852,
25.18085, 28.27900,
69.02590,
25.79411, 28.36766,
69.27517,
25.75291, 28.84556,
69.58398,
25.96440, 28.98907,
69.88141,
26.46625, 28.14716
};
double[] vaaka = new double[luvut.Length / 3];
double[] lonvas = new double[luvut.Length / 3];
double[] lonoik = new double[luvut.Length / 3];
int j = 0;
for (int i = 0; i < luvut.Length; i += 3)
{
vaaka[j] = luvut[i];
lonvas[j] = luvut[i + 1];
lonoik[j] = luvut[i + 2];
j++;
}
double lond = 0.0;
bool ctu = double.TryParse(latiz.Replace('.',
','), out double latd);
if (ctu)
{
ctu =
double.TryParse(longiz.Replace('.', ','), out lond);
}
if (latd is < 59.65135 or >
70.15173) // Suomi kokonaan tämän suorakaiteen sisällä
return "Muu";
if (lond is < 18.87949 or > 31.71152)
return "Muu";
if (latd >= vaaka[0] && latd <=
vaaka[vaaka.Length - 1])
{
for (int i = 0; i <
vaaka.Length - 1; i++)
{
if (latd
>= vaaka[i] && latd < vaaka[i+1])
{
// Kun piste on vaakasuorien vakioviivojen
välissä, tehdään
// Uusi vaakaviiva pisteen kautta, päät
lineaarisesti
// ylä- ja alapuolten päiden suhteen
double kerr = (latd - vaaka[i]) / (vaaka[i+1] -
vaaka[i]);
double lonva = lonvas[i] + (lonvas[i+1] -
lonvas[i]) * kerr;
double lonoi = lonoik[i] + (lonoik[i+1] -
lonoik[i]) * kerr;
if (lond >= lonva && lond <=
lonoi)
{
return "Suomi";
}
}
}
}
// jos jouduimme määriteltyjen alueiden väliin,
niin kysytään maa netistä:
StringBuilder sb = new("");
int imaa = EIOO; // EIOO = ei Suomi, >
0 Suomi, VIRHE = ei yhteyttä palvelimeen
// Etsi maa OpenStreet-palvelusta
_ =
sb.Append("https://nominatim.openstreetmap.org/reverse?format=xml&lat=");
_ = sb.Append(latiz);
_ = sb.Append("&lon=");
_ = sb.Append(longiz);
_ =
sb.AppendLine("&zoom=18&addressdetails=1");
try
{
using (HttpClient client = new())
{
string kysyz =
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR
1.0.3705;)";
client.DefaultRequestHeaders.Add("user-agent", kysyz);
Task<string> t =
client.GetStringAsync(sb.ToString());
string z = t.Result;
imaa =
z.IndexOf("<country_code>fi<");
if (imaa == EIOO &&
z.Contains("<country_code>") == false)
{
_ =
MessageBox.Show("Nettipalvelu toimii, mutta ei saatu mitään
maatietoa:\n" + z, "Ilmoitus");
}
}
}
catch (Exception ex)
{
_ = MessageBox.Show($"Virhe:
{ex.Message}", "Ilmoitus");
imaa = VIRHE;
}
finally
{
}
return (imaa == VIRHE) ? "" : ((imaa == EIOO) ?
"Muu" : "Suomi");
}
Onko meillä nettiyhteyttä
Edelläesitettyjen funktioiden kanssa voi olla hyödyllistä ensin
tarkistaa onko tietokoneemme nettiyhteydessä vai ei. Tarkistuksen
tulee olla nopea eikä se saa kaataa sovellusta. Seuraava funktio
pingaa google.com:ia.
using
System.Net.NetworkInformation;
//-----------------------------------------------------------
// 1.8.2017 M45
// Tarkistetaan onko pääsy nettiin, tai tarkemmin Googlen
palvelimelle
// Tämä tarkistus on nopea eikä kaada softaa
public static bool OnkoNetti()
{
try
{
Ping myPing = new Ping();
String host = "google.com";
byte[] buffer = new
byte[32];
int timeout = 1000;
PingOptions pingOptions =
new PingOptions();
PingReply reply =
myPing.Send(host, timeout, buffer, pingOptions);
return (reply.Status ==
IPStatus.Success);
}
catch (Exception)
{
return false;
}
}
Videoleikkeet
Videoleikkeiden katsomiseen on yksinkertaisinta, varsinkin Windows
Forms -ohjelmoinnissa, käyttää Windows-työaseman oletussovellusta:
// filez = videoleikkeen polku
System.Diagnostics.Process.Start(filez);
WPF-ohjelmoinnissa on kätevintä käyttää kirjaston omaa työkalua:
<MediaElement x:Name="itseVideo" LoadedBehavior="Manual"/>
Tämä ei tue flv-tiedostoja, mutta tukee laajasti muita. Lisäksi
määritellään painikkeita esimerkiksi videon pysäyttämistä ja
soittamista varten. Koodiin seuraavia funktioita sopiviin kohtiin:
itseVideo.Source = new Uri(failiz); // määritellään
soitettava video, failiz = täysi polku
itseVideo.Play(); // videon käynnistys
itseVideo.Pause(); // videon keskeyttäminen, Play() jatkaa
siitä
itseVideo.Stop(); // videon pysäytys, Play() jatkaa
alusta
Täällä
on esitelty tekemäni käytännöllinen videoleikkeiden katseluun
tarkoitettu sovellus. Sen rakenteita ja tekniikoita olen käyttänyt
omissa sovelluksissani.
Videoleikkeistä tarvitaan hakutuloksiin ja metatiedon
katselunäyttöihin yksittäinen kuva. Se otetaan videoleikkeen alusta
kolmen sekunnin päästä. Siihen käytetään sovellusta ffmpeg. Aina voi
käyttää sovelluksen 32-bittistä versiota.
Sovelluksella ffmpeg on valtava määrä vaihtoehtoisia lippuja sen
toiminnan määrittämiseksi. Alla oleva komento ottaa videopätkästä
120x90 -kokoisen kuvan kolmen sekunnin päästä videon alusta. Huomaa,
että otettavan kuvan koon määreiden on oltava parillisia.
Sovelluksessa tämä rivi tarkistuksineen on tietysti C#-koodin
sisällä.
ffmpeg.exe -y -i video.mpg -an -ss 00:00:03 -s 120x90
-vframes 1 -f mjpeg tuloskuva.jpg
Tässä tarpeelliset koodirivit, jotka tekevän kuvan, jonka mitat on
sama kuin videon:
// vidz =
videotiedoston koko polku
// polz = lopputuloksena olevan kuvan koko polku
// progz == ffmpeg.exe:n koko polku
using (System.Diagnostics.Process proc = new
System.Diagnostics.Process())
{
proc.EnableRaisingEvents = false;
StringBuilder sb = new StringBuilder("-y -i
\"");
sb.Append(vidz);
sb.Append("\" -an -ss 00:00:03 ");
sb.Append("-vframes 1 -f mjpeg \"");
sb.Append(polz);
sb.Append("\"");
proc.StartInfo.Arguments = @sb.ToString();
proc.StartInfo.FileName = progz;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.CreateNoWindow =
false; // ei tee hommaansa jos tässä on true
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
proc.WaitForExit();
proc.Close();
}
Macista haetun tiedostonimen skandit
Macissä sovellukset voivat tallentaa tiedostonimiin skandeja
omintakeisella tavalla, eli ne voivat olla koottu kahdesta
perättäisestä ascii-koodista. Tämä voi tulla vastaan tilanteessa,
kun tiedosto on tallennettu Macin sovelluksesta uudella nimellä,
joka sisältää skandeja. Taustalla on se, että sovellusten
ohjelmakirjaston NSOpenPanel (failidialogi) tallentaa failin nimen
skandit kahdessa osassa "a cluster of two scalars". Jos
tiedostonimiä (tiedostoja) on tarkoitus käyttää myös Windowsissa, on
skandit korjattava yksiosaisiksi. Tällaista skandia osaa sekä Mac
että Windows käsitellä oikein ja se tulee myös näytölle oikein.
Muussa yhteydessä Mac käsittelee skandeja Windows-yhteensopivasti.
Tarkastellaan tiedostonimiä Windowsissa:
Tämä kohde (Käenkukka326) on nyt tietokannassa väärässä muodossa eli
tallennettu Macissa kantaan ilman korjausta:

Tämä kohde (Heinäratamo89279) on tallennettu Macissä korjattuna, tai
Windowsissa:

Jos samaa tietokantaa käytetään sekä macissä, että Windowsissa, niin
tämä skandiasia tulee ottaa huomioon joko Mac-sovelluksessa tai
Windows-sovelluksessa, tai molemmissa. Itse olen huolehtinut tästä
Macin puolella, ks. Ohjelmointivinkkejä (Swift)
Exif-koodit
Exif-koodit kertovat yksityiskohtaisia tietoja valokuvasta eli
kameralla tehdystä kuvatiedostosta. Netistä löytyy helposti tietoja
mitä exif-koodeja ylipäätään on olemassa ja miten niitä saa ulos
esimerkiksi C#:lla.
Visual C#:ssa exif-koodeihin pääsee helposti käsiksi. Alla on
esimerkki siitä, miten voidaan käsitellä kuvan ominaisuus
"orientation" eli onko kohde kuvassa oikeassa suunnassaan.
Turvallisinta on sijoittaa exif-koodien tutkiminen lausekkeiden try
- catch sisään, koska GetPropertyItem() saattaa pahastua vääristä
parametriarvoista ja kokeillessa se kaatoi minulta jopa Visual C#:n.
Orientation löytyy numerolla 274 eli 0x112 kuten koodista näkyy.
Esimerkkejä muista numeroista:
2 PropertyTagGpsLatitude
256 ImageWidth
257 ImageLength
258 BitsPerSample
272 Model
306 DateTime
34853 GPSInfo Pointer
Jotkut suureista on lukuja, toiset merkkijonoja tai bittijonoja
joissa esimerkiksi tietyt kaksi merkkiä tarkoittavat jotain.
Exif-avaimet ja tarkoitukset on selitetty mm. täällä
Alla on Windows Forms -esimerkki (C#) toimivasta Orientation-suureen
käsittelystä, se on peräisin täältä.
WPF-ympäristössä vastaava löytyy esimerkkikoodista, joka on täällä.
//------------------------------------------------------------
// Windows Forms, dotnet Framework 4.6
// Tarkista kuvan tiedoista pitääkä kuvaa kääntää
public static void tarkistaAsento(Image img)
{
if (Array.IndexOf(img.PropertyIdList, 274)
> -1)
{
var orientation =
(int)img.GetPropertyItem(274).Value[0];
switch (orientation)
{
case 1:
// No rotation required.
// MessageBox.Show("Property Orientation: ei
tarvitse kiertää", "(M13-01) exif");
break;
case 2:
// MessageBox.Show("Property Orientation:
flip X-suunnassa", "(M13-02) exif");
img.RotateFlip(RotateFlipType.RotateNoneFlipX);
break;
case 3:
// MessageBox.Show("Property Orientation:
Rotate 180", "(M13-03) exif");
img.RotateFlip(RotateFlipType.Rotate180FlipNone);
break;
case 4:
// MessageBox.Show("Property Orientation:
Rotate 180 + flip X-suunnassa", "(M13-04) exif");
img.RotateFlip(RotateFlipType.Rotate180FlipX);
break;
case 5:
// MessageBox.Show("Property Orientation:
Rotate 90 + flip X-suunnassa", "(M13-05) exif");
img.RotateFlip(RotateFlipType.Rotate90FlipX);
break;
case 6:
// MessageBox.Show("Property Orientation:
Rotate 90", "(M13-06) exif");
img.RotateFlip(RotateFlipType.Rotate90FlipNone);
break;
case 7:
// MessageBox.Show("Property Orientation:
Rotate 270 + flip X-suunnassa", "(M13-07) exif");
img.RotateFlip(RotateFlipType.Rotate270FlipX);
break;
case 8:
// MessageBox.Show("Property Orientation:
Rotate 270", "(M13-08) exif");
img.RotateFlip(RotateFlipType.Rotate270FlipNone);
break;
default:
// This EXIF data is now invalid and should
be removed.
img.RemovePropertyItem(274);
break;
}
}
else
{
//
MessageBox.Show("Property Orientation ei löydy", "(M13-09)
exif");
}
}
Muutamia koodiesimerkkejä C# WPF
Tietolähteen käyttäminen
Sovelluksessa voi esittää tietolähteen kahdella päätavalla:
DataContext ja ItemsSource. Lisäksi datan voi kerätä koodissa
luokkaan DataView, minkä käyttämisellä saadaan xaml tiiviimmäksi.
Lisäksi tietolähde voidaan määritellä joko C#-koodissa tai
xaml-tiedostossa. Itse olen etupäässä käyttänyt ItemsSource:a ja
määritellyt tietolähteen C#-koodissa (katso esimerkkejä alla
viitatuissa koodiesimerkeissä).
dgTiedot.ItemsSource = kuvataulu; // taulukko rivit
esittävistä luokista, ks. koodiesimerkit
// Tietolähde
perustettu ekan kerran tai uudestaan
dgTiedot.ItemsSource = null; // Tietolähteen
tyhjennys
dgTiedot.Items.Refresh(); // Näkymän päivitys,
erityisesti tarpeen jos yksittäistä itemiä muutettu
// Refresh() tarpeen myös jos tietolähteen linkki vaihtui new...
Parempi laittaa koodiin lbHlot.ItemsSource = Hlolista;
kuin xaml:ään ItemsSource="{Binding Hlolista}"
Kuvan esittäminen ikkunassa ja sen näyttäminen
pikselitarkkana tai ikkunan kokoisena. Esimerkkikoodi on täällä.
Alasvetovalikon (comboboxin) käyttöönotto ja käyttäminen.
Esimerkkikoodi on täällä.
Tietolähteen käyttämisessä useimmissa tapauksissa on kätevin
ItemsSource-ominaisuuden käyttäminen, niin myös tässä esimerkissä.
Kuvajoukon esittäminen matriisina ja valitun kuvan
tunnistaminen jatkotehtäviä varten on myös usein perustehtäviä. Tässä on esitetty
esimerkkikoodi.
Tietojen esittäminen taulukkomuodossa on monessa tapauksessa
käytännöllistä. Tämän voi tehdä WPF-ympäristössä monella tavalla.
Itse käytän yleisimmin Datagridiä sen monipuolisuuden ja koodauksen
yksinkertaisuuden takia. Periaatteessa ListView-GridView olisi
kevyempi rakenne kuin Datagrid, kun arvoja vain katsotaan ja
valitaan, mutta olen todennut, että käytännössä Datagrid on
selkeämpi ohjelmoida niiden asioiden osalta mitä olen tarvinnut.
Datagridin sarakkeet voi lajitella näytöllä, ListView-GridView:n
vain ohjelmallisesti. Täältä
löytyy esimerkkikoodi.
--------------------------------
(sivua muokattu 3.3.2022)