Seriál Windows Powershell: Tipy a triky - mapy (část 14.)
Programátorům a administrátorům, kteří objevili možnosti .NETu, PowerShell nabízí možnosti, jak rychle prozkoumat neznámé API. Dnes si ukážeme, jakým způsobem bychom mohli postupovat v případě, že bychom si chtěli osahat kontrol na zobrazování map, GMap.NET - Great Maps for Windows Forms & Presentation. Pro jednoduchost si vybereme kontrol pro Windows Forms. Výsledkem pak bude malá aplikace, která nám vrátí GPS souřadnice vybraného místa.
Poznámka: Příklad bude běžet v pořádku pouze pokud bude PowerShell spuštěn v režimu STA. Při spuštění PowerShellu tedy nezapomeňte na přepínač -sta
.
V průběhu se seznámíme s některými tipy na práci s enumy, [out] parametry a jak zapisovat handlery k událostem.
Kostra
Budeme potřebovat Form
objekt, který bude mapový kontrol obsahovat:
Add-Type -AssemblyName System.Windows.Forms
$f = New-Object System.Windows.Forms.Form
$f.Size = New-Object System.Drawing.Size 700,500
... místo pro další kód
[void]$f.showdialog()
Programátoři zřejmě nic nového neobjevili, tento kód se opakuje pokaždé, když chceme psát WinForms aplikaci.
Dále si potřebujeme stáhnout příslušné assembly a načíst je. V našem případě půjde o tři assembly, které najdete v příloze. Samozřejmě si je můžete stáhnout i z výše uvedené adresy.
# assembly máme uložené v adresáři lib, načteme je všechny takto:
gci g:\lib\ *.dll | % { Add-Type -path $_.FullName }
Závislosti mezi assemblies
Pokud byste potřebovali najít "ty správné" assemblies, které máte načíst, čtěte dál. Jinak můžete pokračoval dál k vytváření kontrolů.
Assembly, kterou musíte načíst, najdete podle projektu. My budeme používat GMapControl
. Rychle proletíme adresářovou strukturu (autor používá zásadu co třída, to nový soubor) a zjistíme, že kontrol se nachází v projektu GMap.NET.WindowsForms. Poté vyhledáme *.csproj soubor a v něm element AssemblyName. Tak jsme zjistili, že kontrol se kompiluje do assembly GMap.NET.WindowsForms.dll.
Našli jsme první assembly, kterou určitě načíst musíme. Stejně tak ale budeme pravděpodobně potřebovat i další assembly, na kterých tato závisí. Na závislosti se můžeme podívat přes Reflector, ale stejně dobrou práci zvládne i PowerShell.
PS> $assembly = [reflection.assembly]::LoadFile('g:\lib\GMap.NET.WindowsForms.dll')
PS> $assembly | fl
CodeBase : file:///g:\lib\GMap.NET.WindowsForms.dll
EntryPoint :
EscapedCodeBase : file:///g:\lib\GMap.NET.WindowsForms.dll
FullName : GMap.NET.WindowsForms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b85b9027b614afef
GlobalAssemblyCache : False
HostContext : 0
ImageFileMachine :
ImageRuntimeVersion : v2.0.50727
Location : g:\lib\GMap.NET.WindowsForms.dll
ManifestModule : GMap.NET.WindowsForms.dll
MetadataToken :
PortableExecutableKind :
ReflectionOnly : False
Properties assembly jsme sice ještě nezjistili, ale zajímavá může být určitě informace o FullName. Ta se vyplatí v případě, kdy nám runtime hlásí, že nemůže načíst nějakou assembly. Je pravděpodobné, že se mu snažíme podstrčit špatnou verzi. Všimněte si také ImageRuntimeVersion – ta nám říká, že assembly byla překompilována pro runtime 2.0. Pro aplikace pod .NET 4.0 pak budete potřebovat zřejmě jinou verzi assembly. PowerShell ovšem stále běží na .NET 2.0/3.5, tedy pro naše potřeby máme verzi správnou.
Vraťme se zpátky. Když předhodíme $assembly
commandletu Get-Member -membertype method
, zjistíme metody, které nám nabízí. Pro stručnost už je zde nebudu uvádět. Ta správná metoda, která nám zjistí, na kterých ostatních assemblies ta naše závisí, se skrývá pod jménem …
PS> $assembly.GetReferencedAssemblies()
Version Name
------- ----
2.0.0.0 System.Windows.Forms
2.0.0.0 mscorlib
1.5.3.3 GMap.NET.Core
2.0.0.0 System
2.0.0.0 System.Drawing
Víme tedy, že assembly GMap.NET.Core budeme potřebovat načíst také. A když se podíváme na ni …
PS> $assembly2 = [reflection.assembly]::LoadFile('g:\lib\GMap.NET.Core.dll')
PS> $assembly2.GetReferencedAssemblies()
Version Name
------- ----
...
1.0.66.0 System.Data.SQLite
Zjistili jsme závislost na System.Data.SQLite. Z tohoto důvodu jsou všechny tyto tři assembly přibaleny k tomuto článku a výše je načítáme pomocí příkazu:
gci .. | % { Add-Type .. }
Vytvoření kontrolek
Na oficiálních stránkách bohužel mnoho dokumentace není. Inspirovat se můžeme převážně jen z demo aplikace, která je mimochodem dostupná pro Windows Forms i WPF. Zkopírujeme si základní kód na inicializaci kontrolu:
$map = New-Object GMap.NET.WindowsForms.GMapControl
$map.Anchor = 'top', 'bottom', 'left', 'right'
$map.Location = New-Object System.Drawing.Point 0,0
$map.MapType = 'GoogleMap'
$map.MarkersEnabled = $true
$map.MaxZoom = 17
$map.MinZoom = 2
$map.MouseWheelZoomType = 'MousePositionAndCenter'
$map.ShowTileGridLines = $false
$map.Size = new-object System.Drawing.Size 700, 500
$map.Zoom = 16
$map.Position = new-object GMap.NET.PointLatLng 50.207, 16.849
Povšimněte si properties Anchor
, MapType
a MouseWheelZoomType
. PowerShell nám velmi pomáhá, při práci s enumy. Pokud se snažíme přiřadit do proměnné typu enum hodnotu typu string, PowerShell ji automaticky zkonvertuje na příslušný enum. Když přiřazujeme pole stringů a daný enum má přiřazený atribut [FlagsAttribute]
, pak všechny stringové hodnoty jsou zkonvertovány na enumové hodnoty a následně kombinovány dohromady. Můžete si to ověřit například takto: $map.Anchor -band [system.windows.forms.anchorstyles]::right
.
Abychom si mohli přepínat mezi více typy map, přidáme si ještě na formulář combo box. Ten bude reagovat na změnu indexu a nastaví příslušný typ mapy do objektu $map
:
$change = new-object System.Windows.Forms.ComboBox
$change.Anchor = 'top', 'right'
$change.Location = New-Object System.Drawing.Point 615,0
$change.DropDownStyle = 'DropDownList';
$change.FormattingEnabled = $true;
$change.Items.AddRange(@('GoogleMap', 'GoogleSatellite', 'GoogleTerrain', 'GoogleHybrid'))
$change.Size = new-object System.Drawing.Size(80, 21);
$change.TabIndex = 2;
$change.add_SelectedValueChanged({ $map.MapType = $change.SelectedItem })
$change.SelectedIndex = 1
Hodnoty GoogleMap až GoogleHybrid jsou opět hodnoty enumu. Jak zjistíme dostupné hodnoty? Například takto:
PS> $map.MapType.gettype().fullname
GMap.NET.MapType
PS> [enum]::getnames([GMap.NET.MapType])
None
GoogleMap
GoogleSatellite
GoogleLabels
GoogleTerrain
GoogleHybrid
GoogleMapChina
GoogleSatelliteChina
GoogleLabelsChina
GoogleTerrainChina
GoogleHybridChina
....
K hodnotám enumu se přistupuje – pokud nemůžeme zrovna použít automatické konverze ze stringu – jako k statickým properties. Tedy jméno enumu uzavřeme do hranatých závorek a k hodnotám se dostaneme přes dvojtečkovou notaci, viz. příklad s AnchorStyles
: [system.windows.forms.anchorstyles]::right
Možná jste si všimli, jakým způsobem definujeme handler pro událost SelectedValueChanged
– voláme metodu se jménem add_jmeno-udalosti
a jako parametr předáváme scriptblock, který se má vykonat. Handlery mívají dva parametry, které se často ale ani nevyužijí, jako v tomto případě. Pokud byste přesto potřebovali jeden z nich přečíst, níže je zobrazen handler na událost Click, kde se získává aktuální pozice myši.
Můžeme si vyzkoušet, jestli bylo naše dosavadní snažení k něčemu dobré. Kontroly vložíme do kolekce a zobrazíme formulář.
$f.Controls.Add($change)
$f.Controls.Add($map)
[void]$f.showdialog()
Naši malou aplikaci teď můžeme spustit. Rolováním kolečka myši měníme zoom. Klikem pravým tlačítkem a tažením pak posouváme výřez.
Vidíme, že vcelku zadarmo jsme získali aplikaci, která je schopna bez prohlížeče zobrazovat mapy a dokonce je cachovat. Vnitřně totiž používá SQLite, kde si uchovává stažené náhledy a funguje tak i v případě, že zrovna není dostupné síťové připojení.
Přidání křížku
Pokud chceme ještě přidat na mapu značku, kterou bychom zacílili na námi vybraný bod, potřebujeme přidat vrstvu a do ní značku zasadit:
$overlay = New-Object GMap.NET.WindowsForms.GMapOverlay $map, "point"
$map.Overlays.Add($overlay)
$marker = New-Object GMap.NET.WindowsForms.Markers.GMapMarkerCross($map.Position)
$overlay.Markers.Add($marker)
Na mapě se nám tak bude zobrazovat červený křížek. Při kliku chceme, aby se křízek přesunul na místo, kam jsme kliknuli. To docílíme opět zavěšením na události Click
.
$map.add_Click({
param($s, $e)
$marker.Position = $map.FromLocalToLatLng($e.X, $e.y)
$map.Tag = $marker.Position.Lat,$marker.Position.Lng
})
Všimněte si, že tentokráte jsme použili scriptblock s parametry. Druhý parametr obsahuje umístění myši v okamžiku kliknutí. Podle nich nastavíme červenému křížku nové souřadnice a uložíme je do property $map.Tag
.
Poté, co zavřeme formulář, budeme mít v property Tag
poslední kliknuté souřadnice. Přečteme je tedy a vrátíme jako výsledek skriptu. Poslední řádek skriptu tedy bude:
$map.Tag
Nastavení počátečních souřadnic
Ve skriptu máme nyní počáteční souřadnice nastavené napevno. My ovšem máme i možnost si souřadnice vyhledat. Níže uvedený kód vložte kdekoliv za inicalizaci mapového kontrolu a před zobrazení formuláře:
[GMap.NET.GeoCoderStatusCode]$status = 'G_GEO_SUCCESS'
$loc = [GMap.NET.GMaps]::Instance.GetLatLngFromGeocoder("Kralický sněžník", [ref] $status);
if ($status -eq 'G_GEO_SUCCESS') {
Write-Host "Nalezen Kralický Sněžník, souřadnice: $($loc.Lat), $($loc.Lng)"
$lat,$lng = $loc.lat,$loc.Lng
$map.Position = new-object GMap.NET.PointLatLng $lat, $lng
} else {
$lat,$lng = 49.6034664,17.254005
}
Zde si povšimněte použití metody GetLatLngFromGeocoder
. Druhý parametr by měl být předáván jako [out]
. Jenže PowerShell nic takového nezná. V PowerShellu se oba případy ( [ref] i [out] ) předávají jako [ref] . Příklad můžete vidět například na https://wiki.poshcode.org.
Někdy se nám může hodit, když známe, jak zapsat typovou proměnnou: [GMap.NET.GeoCoderStatusCode]$status
. Takovým způsobem určíme typ proměnné $status
, který zaručuje, že do proměnné vždy můžeme uložit pouze hodnotu typu [GMap.NET.GeoCoderStatusCode]
. Opět platí, že pokud se snažíme do ní uložit stringovou hodnotu, je tato hodnota konvertována a v případě úspěchu uložena. Můžeme si to ukázat na tomto příkladu:
[GMap.NET.GeoCoderStatusCode]$status = 'G_GEO_SUCCESS'
$status = 'test' #chyba
Cannot convert value "test" to type "GMap.NET.GeoCoderStatusCode" due to invalid enumeration values.....
$status = 'G_GEO_TOO_MANY_QUERIES' #uspěje
Povedlo se?
S trochou štěstí jste se dostali až k opravdu funkčnímu kódu a dozvěděli se pár drobných triků, které vám mohou pomoci programovat téměř jako v C#. Opravdu jen téměř. Nezapomínejme totiž, že PowerShell je primárně skriptovací jazyk, který rozhodně nemá ambice stát se plnohodnotným objektově orientovaným jazykem.
Download
- Skript: maps-pub.ps1
- Assembly: lib.zip
- Josef Štefan