Konkurs „Daj się poznać” – podsumowanie

Czas szybko leci, nie tak dawno Procent ogłosił swój konkurs „Daj się poznać”, konkurs miał trwać aż 15 tygodni. W zeszły poniedziałek konkurs się zakończył. Czas jednak szybko mija. W tym czasie ilość czytelników mojego bloga wzrosła na tyle dużo, że była zauważalna w statystykach – dziękuję wszystkim nowym czytelnikom. Jeśli chodzi o aplikację Desktop Info to wreszcie udało mi się zrobić mechanizm dynamicznie ładowanych pluginów (przy pomocy MEF) oraz obsłużyć mapy Google wraz z rozpoznawaniem pozycji. To czego bardzo brakuje aplikacji w obecnym stanie to możliwość przesuwania gadżetów oraz brak integracji z flickr-em co miałem zamiar zorbić. Po drodze jednak rozpoznałem kilka narzędzi (jak PEX i kilka nowych „ficzerów” samego Visual Studio 2010). Powstał również całkiem spory kawał tekstu na temat aplikacji NDepend oraz NCover – które już w krótce będą dostępne na blogu.

Co dalej z aplikacją Desktop Info? Mam zamiar dalej ją rozwijać, tak aby doprowadzić do stanu używalności – obecnie jest to coś czego nie nazwał bym jeszcze wczesną alfą 🙂 a co się zaś tyczy dalszych wpisów z tematyki .net oraz około .net to tak jak wcześniej napisałem, w niedalekiej przyszłości przeczytacie o rewelacyjnym narzędziu NDepend oraz NCover (w ramach uzupełnienia wpisu o testach jednostkowych oraz TDD – test driven development).

 

Dwuwymiarowe kody QR – generator

Wszyscy znamy kody kreskowe, są bardzo popularne. Praktycznie każdy produkt jest nimi opatrzony. Problem jaki mają tradycyjne kody kreskowe to praktycznie brak mechanizmu korekcji błędów, który powoduje, że zniszczenie części kodu powoduje jego całkowitą nieczytelność. Dzięki temu możemy dłużej postać w kasach sklepowych. W tych samych kasach można zauważyć, że czasem ciężko odczytać kod, gdy jest pomięty. To wszystko powoduje, że już jakiś czas temu powstała alternatywa w postaci kodów 2D – kodów QR – czy kodów kropkowych (jak czasem się je nazywa). Sam pisałem już o kodach 2d ponad 2 lata temu tutaj. Temat ten (patrząc na statystyki oglądalności) ciągle jest popularny i wiele osób poszukuje informacji na temat samych kodów jak i sposobu generowania ich.

Wyższość kodów 2D polega na tym, że są skonstruowane w sposób pozwalający odczytać je prawidłowo pomimo częściowego uszkodzenia. Co więcej, ilość znaków, które możemy zapisać za ich pomocą jest teoretycznie nieograniczona (w niektórych formatach oczywiście). Same kody pozwalają zapisywać zarówno cyfry jak i znaki alfanumeryczne (oraz kilka innych) dzięki czemu za ich pomocą możemy zapisywać adresy stron WWW co oczywiście jest często wykorzystywane. Praktycznie każdy telefon ma obecnie aparat (nazwijmy to) fotograficzny. To znowu pozwala aby robić zdjęcia kodów i odczytywać je w telefonie. To jest idealny sposób, aby przepisać adres strony czy coś innego do komórki. Dzisiejszy wpis poświęcony będzie zatem małemu narzędziu, które pozwoli na wygenerowanie kodu QR, oczywiście w postaci dodatku do DesktopInfo.

Na pomoc w generowaniu kodów QR przychodzi biblioteka messagingtoolkit, która jest odpowiedzialna za stworzenie odpowiedniej bitmapy. Biblioteka upraszcza proces generowania kodów QR jest uproszczona do bólu.

[csharp]

var qrGenerator = new QRCodeEncoder{
QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE,
QRCodeScale = 4,
QRCodeErrorCorrect = QRCodeEncoder.ERROR_CORRECTION.M,
QRCodeVersion=4
};

var image = qrGenerator.Encode((String)textBox.Text);

[/csharp]

Ot cała filozofia. Potrzebujemy ustawić 4 zmienne. QRCodeEncodeMode pozwala określić czy chcemy kodować tekst, cyfry czy wszystko inne. Nas interesuje byte – czyli wszystko inne – co pozwala na kodowanie wszystkiego tego co chcemy (adresów internetowych przede wszystkim) z zachowaniem wielkości znaków. QRCodeScale pozwala określić rozmiar, QRCodeErrorCorrection określa tryb korekcji błędów co jednoznacznie przekłada się na nadmiarowość powodującą zwiększenie rozmiaru kodu przy jednoczesnym zwiększeniu jego odporności na błędy odczytu oraz uszkodzenia samego kodu. Ostatnia zmienna QRCodeVersion pozwala określić ilość znaków jakie możemy zakodować. Praktycznie wartość ta może wynosić od 1 do 40. Co daje zmiana tej wartości można sprawdzić na tej tabeli.

Generalnie zwiększając QRCodeVersion zwiększamy ilość znaków, które możemy zakodować, zwiększając QRCodeErrorCorrect zmniejszamy ilość znaków jakie możemy zakodować ale zwiększamy odporność (CodeErrorCorrect może przyjmować wartości L,M,Q,H – od najsłabszej do najsilniejszej korekcji błędu).

Wracając do samej biblioteki, w zmiennej image otrzymujemy Bitmap zwierający nasz obraz. Ponieważ aplikacja działa w WPF to aby wyświetlić ją w Image należy przekonwertować Bitmap do ImageSource (tutaj dziękuję za pomoc Piotrowi)

 [csharp]ImageSource TransformBitmap(Bitmap bmp){
 var bi = new BitmapImage();
 bi.BeginInit();
 bi.StreamSource = ConvertImageToMemoryStream(bmp);
 bi.EndInit();
 return bi;
}

private static Stream ConvertImageToMemoryStream(Bitmap bmp){
 var result = new MemoryStream();
 bmp.Save(result, ImageFormat.Bmp);
 return result;
} 
[/csharp]



Synchronizacja z kalendarzem Google-a

Chcę, aby aplikacja DesktopInfo pokazywała wydarzenia z kalendarza Google-a na moim pulpicie. Dzięki temu będę miał listę pod ręką. Jak przygotować odpowiedni kawałek kodu? Wykorzystamy tutaj oficjalne, przygotowane przez firmę Google biblioteki dla .net-a (do pobrania tutaj). To co będzie potrzebne do projektu to dodanie referencji:

Google.GData.AccessControl
Google.GData.Calendar
Google.GData.Client
Google.GData.Extensions

Tworzymy CalendarService – obiekt odpowiedzialny za połączenie (jako parametr konstruktora podajemy nazwę aplikacji – zupełna dowolność).

 

CalendarService calService = new CalendarService(„Calendar Reader”);

 

To co należy ustawić teraz to użytkownik, hasło i adres do usługi.

 

calService.Credentials = new GDataCredentials(„moje.konto@gmail.com”„moje.tajne.haslo”);
Uri postUri = new Uri(„http://www.google.com/calendar/feeds/default/private/full”);

 

oraz stworzyć zapytanie, które będzie wysłane do Google-a

 

EventQuery query = new EventQuery();
query.Uri = calendarUri;
query.StartTime = DateTime.Now.AddMonths(-1);
query.EndTime = DateTime.Now.AddMonths(12);

To co tutaj jest ciekawe, to możliwość ustawienia zakresu dat, jakie nas interesują. Im szerszy zakres, tym potencjalnie więcej wyników będzie trzeba przesłać przez sieć. Warto o tym pamiętać, bo aplikacja może być uruchamiana na powolnych połączeniach. Po ustawieniu parametrów pozostaje pobrać wyniki

EventFeed calFeed = calService.Query(query) as EventFeed;

I coś z nimi zrobić 🙂

foreach (EventEntry gcalEntry in calFeed.Entries) {
Console.WriteLine(gcalEntry.Times[0].StartTime);
Console.WriteLine(gcalEntry.Times[0].EndTime);
Console.WriteLine(gcalEntry.SelfUri.Content);

}

Jak wydać, przygotowana przez firmę Google udostępnia dane w sposób łatwy i przyjemny. Podobne API dostępne jest do innych usług jednak to czego mi brakuje to brak api do dostępu do listy zadań. Lista zadań dostępna jest w kalendarzu i poczcie Google-owej od dawna. Usługa ta powstała w ramach 20% wolnego czasu programistów i szybko okazało się, że jej lekkość i nieprzeładowana funkcjonalność zaskarbiła sobie wielu fanów – szkoda że nie doczekała się do dnia dzisiejszego swojego API.

Testy jednostkowe oraz TDD – test driven development.

Testy jednostkowe to proste testy pozwalające na sprawdzanie działania aplikacji na poziomie klas i metod. Ich celem jest wspomaganie pracy programisty. Testów jednostkowych nie należy traktować jak testów aplikacji, które należą do zupełnie innej kategorii.

Ciekawą koncepcją jest TDD czyli test driven development. Wg. niej najpierw tworzone zostają testy aby dopiero później stworzyć kod. Dzięki takiemu podejściu zmuszamy umysł do przemyślenia kodu zanim ten zostanie stworzony. Największa moc drzemiąca w testach jednostkowych pojawia się przy wprowadzaniu zmian do wcześniej napisanego kodu. Pilnują one czy logika aplikacji nie zmieniła się w sposób przypadkowy. Więcej na temat testów jednostkowych można znaleźć w sieci, a dla fanów TDD można znaleźć w sieci kubki, koszulki, czapki i przypinki.

To że testy jednostkowe stają się coraz bardziej popularne i coraz chętniej wykorzystywane niech świadczy fakt, że firma Microsoft wypuściła bibliotekę PEX, która generuje testy jednostkowe na podstawie kodu. Co prawda dla TDD takie narzędzie nie wydaje się przydatne, ponieważ wg. tej metodyki należy najpierw stworzyć testy a później kod, to warto wykorzystać je do wyszukania ewentualnych braków i/lub niedopatrzeń. I tak, na stronie Microsoft Research znajdziemy bibliotekę PEX. Po instalacji w menu podręcznym mamy możliwość automatycznego wygenerowania testów dla metod publicznych

Pex sam wygeneruje testy i jeśli już wcześniej stworzyliśmy testy, możemy poszukać takie, które uzupełnią nasze.

A jeśli rzeczywiście znajdziemy test, który uzupełni te nasze, wówczas pozostaje jedynie dodać automatycznie wygenerowany test – promować.

W najbliższym czasie dla aplikacji DesktopInfo stworzony zostanie mechanizm zapisywania ustawień użytkownika dla gadżetów. Ten mechanizm zostanie stworzony w podejściu TDD. Już teraz zachęcam do pisania propozycji testów – słownie, co takie testy mogły by zawierać.

A w najbliższym czasie pojawi się więcej szczegółów na temat PEX-a oraz Molex – kolejny bardzo dobrze zapowiadający się wynalazek firmy Microsoft.

Architektura aplikacji w sposób łatwy i przyjemny

Każda bardziej skomplikowana (niż „hello word”) aplikacja posiada jakąś architekturę. Chyba najbardziej popularna wśród początkujących to spaghetti. Architektura spaghetti charakteryzuje się tym, że różne kawałki kodu są po prostu przemieszane ze sobą. Czasem tak dobrze, że w jednej procedurze i/lub funkcji znajdziemy wszystkie składniki. Na szczęście kilka programów później, prawie każdy zaczyna coraz bardziej myśleć o architekturze. O tym, jak poszczególne elementy aplikacji będą poskładane i jak będą współpracowały między sobą. Nie będę tutaj jednak prowadził dywagacji na temat prawidłowej architektury – są od tego odpowiednie książki.

Co jednak warto opisać to jak powstał załączony do wpisu diagram.

Visual Studio 2010 (niestety dopiero w wersji Ultimate) pozwala na rysowanie takich ładnych diagramów. W dodatku na podstawie projektu. Jak to zrobić?

Do solution należy dodać nowy projekt – Modeling Project:

A następnie dodajemy w tak stworzonym projekcie diagram warstw.

Otwierając teraz Architecture Explorer możemy dodawać do diagramu poszczególne klasy, namespace-y, projekty etc. I łączymy elementy pomiędzy sobą.

Powyższy zrzut z ekranu pokazuje, jak może wyglądać przykładowa architektura.

Teraz rozpoczyna się magia,

Wybierając Validate Architecture możemy szybko sprawdzić czy nasz projekt jest zgodny z zaprojektowaną architekturą – jeśli nie, otrzymamy listę błędów. Jeżeli jesteśmy pewni, że nasz projekt trzyma się założonej architektury to możemy wygenerować zależności pomiędzy elementami diagramu na podstawie solution.

Wisienką na torcie jest opcja w menu Create/Link Work Item. Pozwala ona na związanie work itemów z konkretną częścią architektury – jeśli oczywiście korzystamy z TFS-a.

CodePlex i TFS czyli o wiązaniu work itemów z check-in

Wchodząc na stronę http://desktopinfo.codeplex.com/WorkItem/AdvancedList.aspx znajdziesz listę rzeczy i/lub błędów które zostały zapisane w ramach pracy nad projektem DesktopInfo.

Pozwala to na wygodne administrowanie projektem. Możemy robić listę błędów oraz nowej funkcjonalności. Lista ta dostępna jest także w Visual Studio

Wszystko to dzięki Team Explore-owi oraz TFS-owi. Mamy funkcjonalność, która pozwoli nam łatwiej zarządzać projektem w przypadku pracy wieloosobowej.

Prawdziwa magia zaczyna się jednak kiedy chcemy zwrócić zmiany do TFS-a.

Standardowo gdy używamy Team Explorera wraz z TFS-em to to okno służy nam do zwracania kodu do repozytorium:

Warto w polu comment dopisać czego tyczy się zmiana, jednak gdy przełączymy się na drugą zakładkę to zobaczymy, że możemy związać zmiany w kodzie z konkretnym work itemem czyli z zadaniem jakie mieliśmy do wykonania

W ten sposób nie tylko możemy zakończyć WorkItem ale możemy również związać z nim kod. Czym to skutkuje? W pracy grupowej nad kodem, łatwiej będzie dowiedzieć się kto i co zrobił, jakie wprowadził zmiany oraz które pliki zostały zmienione na potrzeby konkretnego zadania.

Ponieważ to nie koniec możliwości TFS-a, wkrótce postaram się pokazać kilka przydatnych operacji oferowanych przez TFS.

Jak stworzyć program z pluginami w C#

Od początku pracy nad DesktopInfo wiadome było, że będzie potrzebny mechanizm do ładowania pluginów, dodatkowych modułów, które rozszerzą możliwości aplikacji bez potrzeby jej rekompilacji.

Pierwszy pomysł jaki przechodzi do głowy to refleksja. Jednak jakiś czas temu postała biblioteka MEF, która została włączona do .NET Framework 4.0 Ponieważ aplikacja DesktopInfo wykorzystuje właśnie tą wersję .NET, to dlaczego nie wykorzystać nowej funkcjonalności.

Zatem zobaczmy krok po kroku co jest potrzebne aby wykorzystać dobroci MEF-a

Dodajemy referencję do biblioteki

System.ComponentModel.Composition

oraz

[csharp]

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

[/csharp]

Obiekt typu CompositionContainer będzie odpowiedzialny za załadowanie naszych bibliotek.

[csharp]

CompositionContainer pluginsLoader;

[/csharp]


A kawałek kodu odpowiedzialny za ich załadowanie (pluginów) wygląda tak:

[csharp]

private void LoadPlugins()
{
DirectoryCatalog appDir = new DirectoryCatalog(‚ściezka do katalogu z pluginami’);
pluginsLoader = new CompositionContainer(appDir);
pluginsLoader.ComposeParts(this);
}

[/csharp]”That’s fucking it” says the geezer.

„That’s fucking what” says Rorry. (Lock stock and two smoking barrels).

No właśnie czegoś brakuje. LoadPlugins powoduje, że MEF przeglądnie wszystkie pliki dll w katalogu aplikacji. ComposeParts powoduje, że uruchamiamy cały mechanizm. Tylko gdzie te wszystkie pluginy będą załadowane? Potrzebujmy jakiś kontener, który będzie je przechowywał.

[csharp]

[ImportMany(typeof(IDesktopInfoPlugin))]
private IList<IDesktopInfoPlugin> plugins { get; set; }

[/csharp]Kontener jest oznaczony atrybutem ImportMany, który mówi MEF-owi gdzie ma wczytać obiekty oraz podaje interfejs, który obiekty muszą spełniać aby zostać zaczytane. Proste i eleganckie. Po wykonaniu ComposeParts w kontenerze plugins otrzymamy listę obiektów implementujących IDesktopInfoPlugin oraz znajdujących się w bibliotekach, które MEF znalazł w „ścieżce do katalogu z plikami”.

.NET ciągle mnie zadziwia dzięki takim właśnie rodzynkom.

Aktualizacja kontrolki z innego wątku, invoke oraz metody rozszerzające

Czy otrzymałeś kiedyś taki oto błąd?

The calling thread cannot access this object because a different thread owns it.

Dzieje się tak, najczęściej wtedy, gdy próbujemy aktualizować kontrolki użytkownika z innego wątku. Taka czynność nigdy nie była dobrym pomysłem i trzeba było o tym pamiętać, jednak od wersji bodajże 2 .net Framework-a dostajemy taki wyjątek jak powyżej. Dzięki temu, nawet jeśli coś zostanie przeoczone i będziemy jednak chcieli wykonać aktualizację kontrolki użytkownika z innego wątku niż wątek interfejsu, wówczas dostaniemy ładny i zgrabny wyjątek.

Zatem jak zaktualizować kontrolkę użytkownika z innego wątku? Należy posłużyć się motodą Invoke, która wywołuje metodę w wątku właściwym dla wykonywanej operacji. W aplikacji gdzie często wykonujemy tego typu operacje, warto wykorzystać metody rozszerzające, dzięki którym możemy dodać własny plumbing do istniejących klas (nawet systemowych).

I tak w desktop info jest klasa ControlExtensions:

public static class ControlExtensions
    {
        public static void InvokeIfRequired(this Control control, Action action)
        {
            if (System.Threading.Thread.CurrentThread != control.Dispatcher.Thread)
            {
                control.Dispatcher.Invoke(action);
            }
            else
            {
                action();
            }

        }

        public static void InvokeIfRequired<T>(this Control control, Action<T> action, T parameter)
        {
            if (System.Threading.Thread.CurrentThread != control.Dispatcher.Thread)
                control.Dispatcher.Invoke(action, parameter);
            else
                action(parameter);
        }
    }

 

Definiuje ona dwie metody rozszerzające (w parametrach metody pojawia się nagle tajemnicze słowo this) InvokeIfRequired. Dzięki nim mogę wywoływać ją bez potrzeby nadmiernego pisania kodu w każdym miejscu, które wymaga lub potencjalnie wymaga invoke-a

 

Jak z takiego czegoś korzystać?

 

 this.InvokeIfRequired((value) => seconds.Content = value, DateTime.Now.Second.ToString("00"));

 

W metodzie obsługującej zdarzenie Elapsed dla timera znajduje się powyższy kod, który wpisuje ilość sekund do kontrolki seconds. Proste i zgrabne.

Jak pokazać pozycję użytkownika na mapie

Wcześniej pisałem o kontrolce GMap do wyświetlania map Google-a w aplikacji WPF, która pozwala na wyświetlanie map w aplikacji WPF. Użycie kontrolki nie jest jakoś trudne. Wystarczy podglądnąć w przykładowym projekcie co i jak poustawiać aby wszystko zaczęło pracować. Co jednak jeśli chcielibyśmy wyświetlić pozycję użytkownika?… ale tak dynamicznie.

W Windows 7 znajduje się mechanizm pozwalający na dostęp do lokalizacji oraz sensorów. W panelu sterowania w sekcji sprzęt i dzwięk znajdziesz to co nas interesuje.

Tutaj możemy uzyskać dostęp do urządzeń lokalizacyjnych takich jak gps-y. Co jeżeli jednak nie mamy GPS-a w naszym komputerze? Jest rozwiązanie. Możemy pobrać lokalizację na podstawie ip użytkownika. Oczywiście takie określenie lokalizacji nie będzie zbytnio dokładne – w moim przypadku dokładność wynosi ok. 4-5km 🙂 – ale zawsze da nam to pewien pogląd na temat położenia maszyny. Jak to wykorzystamy, zależy tylko i wyłącznie od nas i naszej wyobraźni.

Jak zatem pobrać lokalizację na podstawie ip? Wystarczy zainstalować geosense for windows http://www.geosenseforwindows.com/ Sterownik ten symuluje urządzenie typu GPS tyle tylko że określa swoją pozycję na podstawie Google Location Services…. Tak, wykorzystamy wyniki danych zbieranych przez googla, o których tak głośno było niedawno.

Mając zainstalowany GeoSense for Windows będziemy potrzebowali coś do wyświetlenia pozycji. Do tego użyjemy http://greatmaps.codeplex.com/

Do projektu dodajemy referencje do GMap.NET.Core i GMap.NET.WindwosPresentation, które pozwolą nam korzystać z gmap. Cały xaml wygląda arcy skomplikowanie:

<UserControl x:Class=”Map.MapControl”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006″
xmlns:d=”http://schemas.microsoft.com/expression/blend/2008″
xmlns:my=”clr-namespace:System.Windows.Controls;assembly=GMap.NET.WindowsPresentation”
mc:Ignorable=”d”
d:DesignHeight=”229″ d:DesignWidth=”460″>
<Grid>
<my:GMapControl Name=”gMap”/>
</Grid>
</UserControl>

W pliku Cs w konstruktorze dodajemy:

Singleton<GMaps>.Instance.Mode = AccessMode.ServerOnly;gMap.MaxZoom = 0x11;gMap.MinZoom = 5;gMap.Zoom = 12;
To nam pozwoli na podstawowe wykorzystanie mapy.

Dodajemy obsługę sensorów. Dodajemy referencję do biblioteki Windows7.SensorAndLocation (do pobrania z tego miejsca http://code.msdn.microsoft.com/SensorsAndLocation/Release/ProjectReleases.aspx?ReleaseId=2359)

I dopisujemy dalszy kawałek kodu:

LatLongLocationProvider location = new LatLongLocationProvider(100);if (location.ReportStatus == ReportStatus.Running){

location.LocationChanged += new LocationChangedEventHandler(location_LocationChanged);
}
else if (location.ReportStatus == ReportStatus.AccessDenied)
{
//label1.Content = „Cannot retrieve information”;
}

To co robi powyższy kod, to praktycznie podpięcie się do zdarzeń obiektu LatLongLocationProvider. Zdarzenie to informuje o zmianie położenia. My to położenie wykorzystamy do zaktualizowania naszego miejsca na mapie jak poniżej:

var location = (LatLongLocationReport)locationProvider.GetReport();this.InvokeIfRequired((value) => gMap.CurrentPosition = new PointLatLng(location.Latitude, location.Longitude), String.Empty);

Pierwsza linijka daje nam szerokość I długość geograficzną; druga linikja wykorzystuje te dane do zaktualizowania pozycji na mapie. Na co warto tu zwrócić uwagę, to fakt, że LocationChanged jest wywoływane z innego wątku niż wątek interfejsu użytkownika, dlatego musimy trochę pobawić się aby zaktualizować pozycję.

Dla ciekawskich, metoda InvokeIfRequred wygląda tak:

 public static void InvokeIfRequired<T>(this Control control, Action<T> action, T parameter){if (System.Threading.Thread.CurrentThread != control.Dispatcher.Thread)                control.Dispatcher.Invoke(action, parameter);            else                action(parameter);}

Z uwagi na późną godzinę, powyższe rozwiązanie jest typu Quick n Dirty ale działa. Teraz pozostaje obudowanie całości w coś sensowniejszego oraz uwzględnienie faktu, że użytkownik niekoniecznie musi posiadać dostęp do sensorów – nie mówiąc o tym, że w systemie może w ogóle nie być sensorów. Szczegóły implementacji i działania do podglądnięcia w źródłach aplikacji DesktopInfo.

Splash Screen w aplikacji WPF

Niektóre aplikacje warto wyposażyć w Splash Screen. W przypadku WPF-a (który jest wykorzystywany w DesktopInfo) sprawa jest banalnie prosta. Wystarczy dodać plik png do projektu a następnie we właściwościach pliku wybrać SplashScreen

I tyle. Bez kodowania, bez kombinowania. Po prostu kilka kliknięć. Jeśli to jednak za dużo roboty to można zrobić SplashScreen inaczej:

Dodajemy New Item do projektu:

Który nazywa się SplashScreen:

Łatwiej chyba się nie da.

Plus korzystania z plików typu png jest taki, że dodając przeźroczystość do pliku, otrzymamy splash screen w dowolnym kształcie. Czyli chcąc mieć nietypowy splash screen o zupełnie dowolnym kształcie wystarczy projekt takiego splasha zapisać w formacie PNG i dodać do projektu (jak to zostało opisane powyżej). Czy można prościej?