NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!
NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.

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čí :P) 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

Program byl vytvořen v roce 2015.

 

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#

 

Všechny články v sekci
Zdrojákoviště C# .NET - Okenní aplikace Windows Forms
Program pro vás napsal David Dostal
Avatar
Uživatelské hodnocení:
3 hlasů
Autor programuje primárně v C#.Net a Ruby. Dále se zajímá o webové technologie (HTML5, CSS3, ES6) a funkcionální programování (F#). Rád se učí nové věci.
Aktivity