Kontext
V GOPASu jsem pracoval na nové generaci StoryBoardu, rozsáhlé WPF/MVVM aplikaci postavené na .NET Frameworku.
Projekt původně začal můj kolega, který vytvořil základní architektonickou kostru, snímací jádro a počáteční databázový model. Poté mi aplikaci předal a od té chvíle jsem na jejím vývoji pracoval samostatně.
Aplikaci v té době stále chyběla většina funkční části: uživatelské rozhraní, aplikační logika, práce s lekcemi, mapování dat, editor nasnímaných elementů, jejich filtrování a dokončený exportní proces.
Mým úkolem bylo z existujícího technického základu vytvořit aplikaci, kterou mohli interní uživatelé skutečně používat. Implementoval jsem desktopové UI a aplikační logiku, rozšiřoval databázový model a doplnil mapování mezi entitami Entity Frameworku, doménovými objekty a view modely. Pracoval jsem také na editoru lekcí, exportu, ClickOnce nasazování a opravách snímacího jádra.
Při návrhu funkcí jsem spolupracoval především s interními uživateli předchozí verze StoryBoardu. Ti znali proces tvorby výukových lekcí a pomáhali mi pochopit, jak má výsledná aplikace fungovat v praxi.
Jak StoryBoard fungoval
StoryBoard nebyl pouze běžný editor. Jeho hlavním úkolem bylo zachytit vzhled a chování jiné aplikace a připravit data pro interaktivní výukový tutoriál.
Uživatel obvykle pracoval se dvěma monitory. Na jednom měl otevřený StoryBoard a na druhém například Microsoft Word, Excel nebo jinou aplikaci, pro kterou vytvářel lekci.
Ve StoryBoardu spustil snímání aktuální podoby aplikace. Snímací jádro nejprve vytvořilo screenshot okna a pomocí Windows UI Automation prošlo jeho strukturu. Postupně navštěvovalo jednotlivá tlačítka, nabídky a další ovládací prvky a zaznamenávalo jejich vzhled v běžném stavu i při najetí myší.
Snímání zahrnovalo také procházení plochy kurzorem. StoryBoard tím zjišťoval, ve kterých oblastech se mění jeho podoba, například ze standardní šipky na textový kurzor nebo jiný typ ukazatele. Zaznamenával také tooltipy a další vizuální změny, které nebylo možné získat pouze ze statického screenshotu.
Pokud měl tutoriál například ukázat napsání textu a jeho následné formátování, uživatel nejprve nechal StoryBoard nasnímat Word ve výchozí podobě. Poté napsal text, znovu nasnímal aktuální podobu aplikace a stejný postup opakoval například po změně textu na tučný, kurzívu nebo po úpravě fontu.
Každá lekce obsahovala několik oken. Jedno okno odpovídalo jednomu nasnímání aplikace v určitém bodě postupu a obsahovalo screenshot, strukturu UI elementů, tooltipy, oblasti s různými podobami kurzoru a další zachycená data.
Po dokončení byla lekce exportována. Výsledná data následně používala navazující aplikace, která z nich přehrávala interaktivní softwarový tutoriál.
Protože jedno okno mohlo obsahovat desítky UI elementů a zachycení každého z nich trvalo několik sekund, používal StoryBoard lokální cache. Snímací jádro porovnávalo identifikátory nalezených elementů s již uloženými záznamy a známé prvky nemuselo znovu snímat. To výrazně urychlovalo opakované zachytávání stejné aplikace v dalších částech lekce.
Automatické snímání nebylo ve všech situacích dokonalé, a proto StoryBoard obsahoval také editor. Uživatelé v něm mohli opravit pozici nebo vzhled elementu, doplnit chybějící snímek nebo ručně upravit výsledek automatického zachycení.
Nasnímaný obrázek bylo možné otevřít například ve Photoshopu. StoryBoard sledoval dokončení externí editace a po uložení změny upravený obrázek znovu načetl a aktualizoval příslušný element v lekci.
Co aplikace podporovala
StoryBoard umožňoval například:
- vytváření výukových lekcí složených z několika oken a kroků;
- snímání aplikací, jejich oken a jednotlivých UI elementů;
- zachycení běžného vzhledu prvků, hover stavů a tooltipů;
- mapování oblastí s různými typy kurzoru;
- správu scén, instrukcí, textů, jazyků, metadat a zdrojových souborů;
- validaci struktury a obsahu lekce;
- ruční opravy automaticky nasnímaných elementů;
- ukládání a opětovné načítání rozpracovaných lekcí;
- export do HTML, JSON, XML a DOCX;
- zabalení výsledné lekce a jejích zdrojů do archivu.
Technický kontext
StoryBoard byl tvořen několika C# projekty s oddělenými odpovědnostmi. Řešení obsahovalo WPF views, MVVM view modely, aplikační služby, entity Entity Frameworku, repositories, mappery, exportní modul, komponenty pro snímání obrazovky, sdílené utility a automatizované testy.
Projekt používal Ninject pro dependency injection, AutoMapper pro převod mezi datovými reprezentacemi a Entity Framework 6 nad SQL Serverem.
Architektonická kostra byla navržena s cílem vést méně zkušené vývojáře bezpečným a předvídatelným způsobem. Obsahovala proto větší množství mezivrstev, abstrakcí a pravidel, která měla omezit nesprávné použití jednotlivých částí systému.
Při praktickém vývoji jsem ale postupně zjistil, že některé z těchto abstrakcí běžné změny spíše komplikují a místy se dostávají do konfliktu s přirozeným fungováním MVVM. Architekturu jsem proto zjednodušoval, odstraňoval nadbytečné vazby a upravoval její části tak, aby lépe odpovídaly skutečným potřebám aplikace.
Databázový model byl kvalitním základem, ale společně s přibývajícími funkcemi jsem jej musel dále rozšiřovat a postupně dolaďovat.
Jednou z důležitých změn bylo oddělení Entity Framework entit od prezentační vrstvy. Doplnil jsem doménové objekty a mappery mezi databázovými entitami, doménovým modelem a view modely, aby uživatelské rozhraní nebylo přímo závislé na struktuře databáze.
Editor a práce s elementy
V editoru jsem implementoval práci s nasnímanými elementy a jejich vizuální reprezentací.
Elementy bylo možné přesouvat pomocí drag-and-drop namísto ručního zadávání souřadnic. Vizuální plocha byla zároveň synchronizována se stromem elementů: označení prvku na ploše vybralo odpovídající položku ve stromu a výběr ve stromu zvýraznil element v editoru.
Uživatelé tak mohli rychleji opravovat nepřesnosti automatického snímání a doplňovat části, které se nepodařilo zachytit automaticky.
Zachytávání obrazovky a Windows API
Jednou z technicky nejzajímavějších oblastí bylo snímání obrazovky, aplikačních oken a jednotlivých UI prvků.
Snímací jádro využívalo Windows UI Automation, User32, GDI32, mouse hooks a funkce jako Graphics.CopyFromScreen, GetWindowDC, CreateCompatibleBitmap nebo BitBlt.
Jádro jsem nevytvářel od začátku, ale při praktickém používání jsem se v něm musel postupně zorientovat a opravovat jeho chování. Řada problémů nesouvisela s běžnou aplikační logikou, ale s reálným chováním Windows, aplikačních oken, kurzoru, focusu, tooltipů a různých stavů zachytávaných programů.
Podpora novějších verzí Microsoft Office
Při prvním praktickém použití snímacího jádra jsme zjistili, že nefunguje správně s tehdy aktuálními verzemi Wordu, Excelu a PowerPointu.
Při refaktoringu jsem objevil, že snímač používá přímo v kódu zapsaný seznam elementů, které má ignorovat. Novější aplikace Office obsahovaly překryvné prvky zakrývající téměř celé okno, kvůli kterým se snímač nedostal k elementům pod nimi.
Filtrování jsem oddělil od samotného snímacího jádra a postupně z něj vytvořil konfigurovatelný systém. Uživatel mohl problematický element zaměřit a přidat jej mezi ignorované prvky. Konfigurace se nejprve ukládala lokálně a později jsme řešení rozšířili o centrální databázi ověřených elementů sdílenou mezi uživateli.
Tím jsme nejen obnovili podporu novějších verzí Office, ale také vytvořili řešení, které bylo možné průběžně přizpůsobovat dalším aplikacím a jejich novým verzím.
Exportní pipeline
Exportní část vycházela ze starší verze StoryBoardu, ale pro novou aplikaci ji bylo nutné zásadně přepracovat.
Jejím úkolem bylo převést interní strukturu lekce do formátů jako HTML, JSON, XML a DOCX, připojit obrázky a další zdroje a zachovat přesný formát očekávaný navazujícími částmi systému.
Původní implementace byla tvořena převážně dlouhým lineárním procesem s opakující se logikou. Při jejím přepracování jsem jednotlivé části postupně odděloval do menších pojmenovaných metod, odstraňoval duplicity a zpřehledňoval zpracování scén, elementů, barev a dalších dat.
Současně bylo nutné zachovat zpětnou kompatibilitu se stávajícím importem a navazujícími nástroji. I zdánlivě nedůležité rozdíly, například formát hexadecimálních barev v různých částech JSON výstupu, mohly způsobit, že se lekce nepodařilo znovu načíst.
Byla to pro mě jedna z prvních výrazných zkušeností s refaktoringem legacy kódu. Nestačilo vytvořit čistší implementaci; stejně důležité bylo rozpoznat a zachovat skryté kontrakty, na kterých závisely ostatní části systému.
Testovatelnost
Testovatelnost byla důležitou součástí architektury StoryBoardu. Projekt obsahoval testy datové vrstvy, DTO, view modelů i širšího chování aplikace.
Používaly se testovací náhrady databáze a souborového systému, knihovna Moq a fasády kolem I/O operací. Díky tomu bylo možné testovat aplikační logiku bez přímé závislosti na skutečné databázi, souborovém systému nebo konkrétním prostředí.
Tato zkušenost mi ukázala, že testovatelnost nevzniká až při psaní testů. Musí se promítnout už do návrhu aplikace – do způsobu, jakým oddělujeme databázi, souborový systém, mapování, aplikační služby a uživatelské rozhraní.
Co jsem si z projektu odnesl
StoryBoard byl jedním z prvních projektů, na kterých jsem nesl odpovědnost za aplikaci jako celek, nikoli pouze za jednu technologickou vrstvu.
Pracoval jsem napříč desktopovým UI, aplikační a datovou logikou, databázovým modelem, architekturou, exportem, nasazováním i integrací s operačním systémem a externími aplikacemi.
Zároveň jsem se naučil, že práce na existujícím systému není jen otázkou napsání lepšího kódu. Každá změna musí respektovat historické chování, existující data, skryté závislosti a způsob, jakým aplikaci používají skuteční uživatelé.
StoryBoard pro mě byl jednou z prvních zkušeností, při kterých jsem začal software vnímat jako celý produkt: kombinaci kódu, dat, uživatelských workflow, historických omezení a provozní reality.
Shrnutí
Převzal jsem StoryBoard ve stavu, kdy existovala základní architektura, snímací jádro a počáteční databázový model. Následně jsem samostatně implementoval většinu funkční části aplikace: uživatelské rozhraní, aplikační logiku, mapování dat, editor lekcí, práci s UI elementy, rozšíření databázového modelu, exportní pipeline a ClickOnce nasazování.
Jedním z mých nejvýznamnějších technických přínosů bylo vyřešení kompatibility snímacího jádra s novějšími verzemi Microsoft Office a přeměna pevně zakódovaného filtrování na konfigurovatelný systém s lokálními profily a centrální databází sdílených pravidel.
Během práce na projektu jsem se dostal od implementace jednotlivých funkcí až k odpovědnosti za fungování celé aplikace a její praktické použití.