Dráhy planet
K čemu aplikace slouží?
Program počítá a následně vykreslí dráhy planet (jejich relativní vzdálenosti vzhledem ke zvolené "středové" planetě). Navolené planety a nastavení lze uložit do souboru a ze souboru zase načíst. Program podporuje exportování obrázku ve formátu .png nebo .bmp.
Proč zrovna dráhy planet?
Při hodinách astronomie v rámci fyziky jsme tyto dráhy museli pracně rýsovat (trvalo to několik hodin). Na popud kamaráda jsem proto začal pracovat na programu, který měl práci zjednodušit. Algoritmus pro zjištění pozice planety v daném čase jsem vymyslel již tehdy při hodině a není nijak složitý. Netušil jsem ale, že se nakonec aplikace se vší omáčkou kolem (návrh aplikace, GUI dle mých představ, správné vykreslení, flexibilita programu, ukládání do souboru atd.) rozroste v můj zatím asi největší projekt a bude trvat desítky hodin ho naprogramovat.
Poznámka: Planetou je pro jednoduchost myšleno jakékoli těleso, které obíhá kolem jiného.
Co jsem se naučil?
Chtěl bych se podělit o několik ze zkušeností, které jsem během vývoje získal. Doufám, že pomohou vyřešit problémy podobné těm, se kterými jsem se během vývoje setkal.
Křivka spojující body
Problém
Jak spojit body značící dráhu planety plynulou křivkou? Nechtěl jsem body spojovat rovnými úsečkami - nevypadalo by to dobře a pro trochu hladší křivku by bylo potřeba vygenerovat velmi mnoho bodů. Co s tím?
Řešení
Zjistil jsem, že napsat vykreslení takovéto křivky pomocí Beziérových
křivek vypadá poněkud složitě. Nakonec jsem našel, že knihovny Windows
Forms již takovouto funkci obsahují. Konkrétně metodu
Graphics.DrawCurve(Pen pen, Point[] points);
Rozhodl jsem se tedy
aplikaci kompletně předělat z WPF do WinForms.
Přizpůsobení šířky kontrolek scrollbaru
Problém
Máme FlowLayoutPanel
s FlowDirection = TopDown
.
Chceme, aby se při nedostatku místa pro kontrolky objevil ScrollBar,
nastavíme tedy AutoScroll
na True
a
WrapContents
na False
. Zde nastává problém.
ScrollBar se sice objeví podle očekávání, ale zabere místo v panelu. Tím
pádem se kontrolky nevejdou vodorovně a objeví se i vodorovný ScrollBar. To
ovšem není požadované chování. Lepší by bylo, kdyby se kontrolky
vodorovně o trochu zmenšily a udělaly tak místo pro ScrollBar. Nepřišel
jsem ale na to, jak tohoto chování dosáhnout bez pomoci vlastního kódu na
pozadí. Přišel jsem ale na jiné řešení.
Řešení
Řešením je nepoužívat FlowLayoutPanel, ale klasický Panel. Panelu se
nastaví AutoScroll
na True
a kontrolkám uvnitř
Dock = Top
. Jednoduché, že? Když se nyní kontrolky na panel
nevejdou, objeví se svislý ScrollBar a kontrolky se trochu zmenší, aby mu
udělaly místo.
Skrytí položek v DropDownu
Problém
Planety v programu mají vlastnost Parent
, která udává, kolem
jaké planety má daná planeta obíhat. Výběr planety jsem ze začátku
řešil ComboBoxem (DropDownStyle = DropDownList
), který byl
Bindingem napojen na seznam všech planet. Problémem je v tom, že jako
rodičovskou planetu nemůžu nastavit planetu samotnou ani žádnou z jejích
"dětí". Ty se tedy nesmí objevit ani v ComboBoxu. Otázkou je, jak je v
ComboBoxu skrýt. První pokusy vedly přes událost
Binding.Format
. Nebyly ale úspěšné. Buď by se nevhodné
planety odebraly i z původního BindingListu (což samozřejmě nechceme), nebo
by nefungoval binding, jelikož jsme položky překopírovali do nové kolekce.
Další možností, kterou jsem našel bylo skrýt nežádané položky v
ComboBoxu. To má ale hned několik háčků. Za prvé, je třeba přepsat
ComboBox tak, aby skryl nechtěné položky. To zahrnuje vytvoření vlastní
kontrolky a přepsání logiky vykreslování a pozicování, přičemž se
navíc můžou vyskytnout nové problémy. Za druhé, uživatelé vždy najdou
způsob, jak něco rozbít. Například, pokud bychom formulář ovládali
pomocí klávesnice, můžeme označit i skryté položky (které tam stále
jsou, jen nejsou vidět). Za třetí, bylo by třeba nějak upravit program tak,
aby bylo možné aktualizovat informaci o tom, zda má být položka skrytá -
znamenalo by to zasahovat do již existujícího kódu a velmi ho
znepřehlednit.
Dlouho jsem hledal a vymýšlel další řešení, ale na nic funkčního jsem nepřišel. Nakonec jsem se rozhodl, že se pokusím najít jiný způsob výběru rodičovské planety. Vymyslel jsem, že po kliknutí na tlačítko se zobrazí dialog s ComboBoxem na výběr planety, který před zobrazením načte planety, která aktuálně mohou být "rodičem". V tom se mi rozsvítilo. Vždyť vůbec nebylo potřeba vytvářet nový dialog, který stejně obsahoval jen původní ComboBox.
Řešení
Řešením je aktualizovat ComboBox v události Enter
, která se
spustí, když se daná kontrolka stane aktivní kontrolkou formuláře. Zde
jednoduše získáme validní položky (s pomocí LINQ je to otázkou jednoho
řádku) a zobrazíme je uživateli. DropDown se tak aktualizuje vždy, když
uživatel bude chtít vybrat novou položku, ať už myší nebo
klávesnicí.
Dodatek: Stejně jsem na něco zapomněl. Když uživatel najede myší na
ComboBox a scrolluje, může změnit označenou položku bez spuštění
události Enter. Na řešení se můžete podívat do zdrojových kódů
(události MouseEnter
a MouseWheel
).
Prázdná položka v DropDownu
Problém
Když planeta neobíhá kolem jiné planety (jako například Slunce), je
její vlastnost Parent
nastavena na hodnotu null
. Do
ComboBoxu by tedy bylo vhodné přidat prázdnou položku, která by
reprezentovala hodnotu null
. Do kolekce s planetami ani do
DropDownu hodnotu null
přidat nemůžeme, jelikož všechny
prázdné pozice v seznamu mají hodnotu "null" a nic by se nám nezměnilo.
Řešení
Řešením je v metodě, která filtruje vhodné planety přidat položku s
textem například "<None>" (může být i prázdný řetězec). Při
přetypování označené položky v události SelectedValueChanged
zpět na původní typ (v tomto případě Planet
) nepoužít
notaci (Planet) ComboBox.SelectedItem
, ale
ComboBox.SelectedItem as Planet
. Tím nám přetypování stringu
na planetu nevyhodí výjimku, ale jednoduše bude null
- přesně
jak jsme chtěli.
Automatické měřítko, vycentrování a zoom
Problém
Vzdálenosti mezi planetami mohou být velmi odlišné a při pořád stejném měřítku můžeme někdy vidět jedinou planetu přes celou obrazovku a jindy zas celou sluneční soustavu pouze jako tečku. Bylo by dobré měřítko automaticky přizpůsobovat.
Řešení
Winforms nám tuto práci značně zjednoduší svými vestavěnými
funkcemi. Nejprve přesuneme bod [0; 0] doprostřed:
g.TranslateTransform(.VisibleClipBounds.Width / 2, g.VisibleClipBounds.Height / 2)
.
Pro změnu měřítka stačí jednoduše funkci
Graphics.ScaleTransform(float sx, float sy)
předat měřítko v
ose x a y a při vykreslování se na každý bod automaticky aplikuje
transformace. Zbývá nám zjistit správné měřítko. To získáme
následovně:
zoom * velikost plátna / maximální velikost vykreslovaného obrázku
.
Obrázkové fonty
Problém
Původně jsem v aplikaci pro ikonky používal obrázkové fonty jako jsou například Wingdings3. Zjistil jsem ale, že na některých zařízeních není font nainstalován a místo ikonek se zobrazují "obdélníčky".
Řešení
Trochu složitější cestou s použitím systémových knihoven lze sice fonty importovat, mohly by ale vzniknout problémy s vlastnickými právy. Udělat (ještě hezčí ) ikonky v Inkscapu a nastavit je jako obrázek na pozadí kontrolky bylo otázkou několika minut a ušetřil jsem si tak práci i nesmyslné otázky ohledně copyrightu.
Otevírání souborů pomocí aplikace
Problém
Bylo by dobré, kdyby aplikace podporovala otevírání souborů i pomocí "Otevřít v programu" nebo přetáhnutím souboru na ikonku aplikace.
Řešení
Řešení je velmi jednoduché, nepotřeboval jsem internet a bylo to
přesně tak, jak jsem si to představoval. V souboru Program.cs
přidáte do static void Main()
argument string[] args
(tak, jak to bývá v konzolové aplikaci). Získáte tak argumenty, se kterými
byla aplikace spuštěna (v tomto případě bude pole obsahovat jednotlivé
cesty souborů).
Závěrem
Rád bych se vývoji aplikace věnoval ještě dál, ale nevím jestli se k tomu ještě dostanu. Se vším všudy jsem odhadem strávil vývojem něco pod 100 hodin (= cca 10 000 Kč), takže budu rád za vaši podporu (byť i jen milý komentář že aplikace dobře slouží ). V budoucí verzi by se mohl například objevit progressBar, který ukazuje aktuální stav vykreslování.
I když aplikace svůj původní účel ušetření práce vůbec nesplnila, jsem velmi rád, že jsem se do toho pustil. Nejvzácnější na tom jsou totiž zkušenosti, které jsem díky tomu získal.
Jako vždy, v komentářích se budu těšit na připomínky, případné hlášení bugů a diskuse o jiných možných řešeních, která vás napadnou.
Galerie
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 66x (458.72 kB)
Aplikace je včetně zdrojových kódů v jazyce C#