Archive for the ‘.NET’ Category

Object Thinking

maj 7, 2008

Będąc w Galway wysłuchałem prezentacji ‘Object Thinking’ Alan’a Deana. Początek prezentacji, trzeba to powiedzieć, był zniechęcający, pojawiło się sporo akademickich formalizmów, mających nikłe odzwierciedlenie w codziennym życiu. Przykładowo przydługawe, dywagacje o tym który z terminów jest właściwy: software developer czy software engineer? Dość mistrzu, nie po to tu przyszedłem, nawijaj o obiektach.

Jednak gdy doszliśmy do meritum prezentacji, przestałem ziewać i zacząłem słuchać . Otóż padła propozycja aby odrzucić dotychczasowy sposób myślenia o klasach jako o zbiorze pól i właściwości, no i metod ale o nich za chwilę, i zastąpić je koncepcją samo-opisywalności. Zgodnie z propozycją, najprostsza, definicja klasy wyglądała by następująco:

public class Customer : Dictionary<Uri, Object> {}

Ciekawe prawda, ot taki pojemnik na wszystko. Metody, rzecz jasna, pozostają a ich celem jest opisywanie intencji klasy.

Uzasadnieniem takiego podejścia do klas, jest twierdzenie, że zdecydowana większość modyfikacji jakie, jesteśmy zmuszeni wprowadzać, dotyczy właśnie sposobu reprezentacji danych. Oczywiście tego typu zmiany zwykle, powodują reakcję łańcuchową i pociąga modyfikacje w innych klasach. Więc po co się z tym męczyć? Umożliwmy przechowywanie dowolnych danych i wszyscy będą szczęśliwi.

No chyba niezupełnie. Nie twierdzę, że idea jest pozbawiona sensu, bo widzę logikę tego rozumowania, jednak dostrzegam również kilka potencjalnych problemów.

Przede wszystkim porzucamy strong typing, wszystko może być obiektem więc konieczna jest walidacja elementów przechowywanych w naszym pojemniku. Oczywiście bez jawnego określenia typów, intellisense Visual Studio, przestanie nas ostrzegać o potencjalnych problemach. Dalej z punktu widzenia bazy danych, tabele też miały by zostać tylko pojemnikami na dane? Żadnych kluczy, indeksów etc. ? Już widzę jak przystają na to administratorzy baz danych.

Kolejnym problemem może być czynnik ludzki i nie chodzi mi tu o mentalne reperkusje przestawienia się na nowy sposób myślenia, a czysto pragmatyczne podejście. Nie oszukujmy się większość ludzi jest leniwa, a programiści to już w szczególności ;) . Wcale nie jest takie nieprawdopodobne, że niektóre wartości mogą być przechowywane w klasie po kilka razy (szczególnie jeśli pracujemy w zespole). Co może prowadzić do wszelakiej maści problemów.

Jak na razie nie oceniam, czekam, aż tęższe głowy od mojej wypowiedzą się w tej sprawie. Może za jakiś czas pojawią się jakieś study case, projektów wykorzystujących to podejście. Poczekamy zobaczymy. Tymczasem dla zainteresowanych slajdy i przykłady z prezentacji są dostępne tutaj.

Emulator & WM 6.1

kwiecień 6, 2008

Microsoft udostępnił obraz systemu Windows Mobile 6.1 dla emulatorów. Jak na razie dostępna jest tylko wersja anglojęzyczna. Pliki do pobrania znajdują się tutaj.

Windows Services

marzec 18, 2008

Odkąd pracuję z Visual Studio tworzenie usług sytemu Windows (ang. Windows service), zawsze było żmudnym procesem. Co by nie powiedzieć o usprawnieniach wprowadzanych w kolejnych wersjach środowiska, proces ten nie uległ znaczącym zmianom. Chyba największym problemem jest pracochłonny i kłopotliwy proces testowania, który wymaga wykonania następujących kroków:

  • Zainstalowania naszej usługi w systemie, konieczny jest do tego specyficzny instalator - opis jak takowy przygotować można znaleźć m.in. tutaj. Sam projekt powinien być skompilowany w trybie Debug, aby zachować informacje o ustawionych breakpoint’ach.
  • Do katalogu w którym zainstalowaliśmy naszą program, kopiujemy plik *.pdb który znajdziemy w katalogu bin naszego projektu.
  • Uruchamiamy usługę. Możemy skorzystać z Panelu Sterowania lub wystukać na linki komend net start “nazwa naszej usługi”
  • Uruchamiamy Visual Studio i z menu Tools wybieramy Attach to Process (lub wciskamy: Ctrl + Alt + P), pojawia się przed nami następujące okienko:

image

  • Z listy dostępnych procesów wybieramy ten reprezentujący naszą usługę. Ponieważ, zwykle usługi działają ze specjalnymi uprawnieniami, musimy najpierw zaznaczyć opcję Show processes from all users.
  • Klikamy Attach i Visual Studio podłączy się do naszego procesu. Dzięki skopiowanemu wcześniej plikowi *.pdb, zawierającemu symbole debugowania środowisko będzie wstanie określić położenie kodu źródłowego naszej aplikacji. W przypadku gdy źródła będą niedostępne, zostaniemy poproszeni o podanie ich położenia.
  • I możemy swobodnie debugować.

Niestety, cały proces musimy powtórzyć gdy zmodyfikujemy kod i ponownie chcemy go przetestować.

Problemem jaki możemy napotkać po drodze są kłopoty z ponowną instalacją naszej usługi, komunikat:

image

Nie doszedłem czym jest on spowodowany, jednak podejrzewam, że nie wszystko zostało, prawidłowo usunięte podczas wcześniejszej de-instalacji. Aby temu zaradzić, musimy ręcznie odinstalować usługę. Z linii komend wpisujemy polecenie:

sc delete “nazwa naszej usługi”

Wszystko powyższe sprawdza się jeśli korzystamy z systemu Windows XP, jeśli jednak używamy osławionej Visty. W liście procesów okna Attach to Process, nie pojawi się pozycja reprezentująca naszą aplikację. Ma to prawdopodobnie ma to związek z nową architekturą zabezpieczeń systemu. W takim wypadku ‘zewnętrze’ podłączenie środowiska do procesu możemy zastąpić jedną linią kodu, która spowoduje automatyczne uruchomienie debugger’a:

System.Diagnostics.Debugger.Launch();

Oczywiście nie zmienia to faktu, że musimy naszą usługę zainstalować i przekopiować odpowiednie pliki. Inną użytecznym poleceniem może być:

System.Diagnostics.Debugger.Break();

która wymusi wstrzymanie wykonania programu. Dobrze jest umieścić powyższe metody w dyrektywie kompilacji warunkowej, aby przypadkiem nie zostały, częścią finalnej wersji programu.

#if DEBUG
System.Diagnostics.Debugger.Launch();
#endif

Cisco IP Phone

luty 7, 2008

Ostatnio pracuję nad nowym projektem, również związanym z urządzeniami zewnętrznymi, jednak już nie przenośnymi. Moim zadaniem jest umożliwić interakcję zwykłego, stacjonarnego telefonu z całym back-end’owym zapleczem. No dobrze, przyznaje ten ‘zwyczajny’ telefon, nie jest znowuż taki zwyczajny, to Cisco IP Phone 7960.

Obawiałem się, że projekt będzie niejakim wyzwaniem, gdzie będę musiał rozgryzać magiczne kody komunikacyjne etc. jak to swego czasu robiłem pisząc ’sterownik’ dla drukarki fiskalnej POSNET‘u. Jednak ku mojej radości (nienawidzę nudnej, żmudnej dłubaniny) okazało się to całkiem proste. Tak dla ścisłości to był mój pierwszy bezpośredni kontakt z urządzeniami firmowanymi przez Cisco i nie bardzo wiedziałem czego się spodziewać. Muszę przyznać, że jestem miło zaskoczony tym jak chłopaki sobie to wszystko wymyślili.
Otóż, wszystko działa w architekturze klient-serwer, gdzie telefon po podłączeniu do cyfrowej centralki (np. zwykłego serwera z zainstalowanym oprogramowaniem centralki PABX) spełnia, jak się łatwo domyślić rolę klienta. Jest to możliwe, gdyż modele Cisco IP Phone z rodziny 7900, mają wbudowany interpreter plików XML, dzięki któremu możemy poinstruować telefon czego od niego oczekujemy. Czyli całą ‘trudnością’ jest stworzenie aplikacji ASP.NET komunikującej się z, w moim przypadku, bazą danych i generującą odpowiednie pliki XML. Struktura plików XML jest łatwa i przejrzysta, przykładowo aby wyświetlić menu na wbudowanym w telefon wyświetlaczu, musimy wysłać do niego następującą strukturę:

<CiscoIPPhoneMenu>
    <Title>Title of Menu</Title>
    <Prompt>Prompt text.</Prompt>
    <MenuItem>
          <Name>Name of Menu Item.</Name>
          <URL>http://url.of.site.com/services/command.aspx</URL>
    </MenuItem>
    <MenuItem>
          <Name>Name of Menu Item.</Name>
          <URL>http://url.of.site.com/services/command.aspx</URL>
    </MenuItem>
</CiscoIPPhoneMenu>

Więcej na temat struktury obiektów XML można znaleźć tutaj. Parametr URL prowadzi do kolejnego pliku XML, parametry możemy przekazywać za pomocą POST i GET. Plik XML który chcemy aby był punktem wejściowym dla naszej aplikacji definiujemy w centralce skąd odpowiedni URL jest przekazywanych do określonych telefonów.

Pisanie tego typu usług można sobie przetestować bez dostępu do całego zaplecza telefonów/serwerów etc. wystarczą nam tylko dwa programy. Pierwszym jest Call Manager Simulator, jest to aplikacja dołączona do książki Developing Cisco IP Phone Service jest ona typu freeware’owa i jako taka jest całkowicie nie supportowana przez Cisco. Emuluje ona serwer TFTP i pozwala określić jaka strona jest punktem startowym naszej usługi. Drugim programem jest jakikolwiek SoftPhone, ja skorzystałem z Cisco IP Communicator oczywiście możemy skorzystać z normalnego urządzenia jeśli takowe mamy pod ręką. Co musimy zrobić, po pierwsze uruchomić symulator:

i określić jego adres IP, w tym wypadku należy podać adres IP naszego komputera.

Następnie musimy zainstalować emulator telefonu, np.:

i w jego ustawieniach wskazać na serwer TFTP emulowany przez nasz Call Manager Simulator.

Teraz po zrestartowaniu naszego telefonu, jeśli wszystko będzie w porządku, w Call Managerze powinniśmy zobaczyć dostępne nowe urządzenie. Teraz musimy umieścić przykładową aplikację na naszym serwerze IIS, jej kod jest dziecinnie prosty i wygląda mniej więcej tak:

Response.Write(“<CiscoIPPhoneMenu>”);
Response.Write(“<Title>Please select Service:</Title>”);
Response.Write(“<Prompt></Prompt>”);
Response.Write(“<MenuItem>”);
Response.Write(“<Name>Hello World Service</Name>”);
Response.Write(“<URL>http://” + this.IPAdress +
               “/IPPhoneServices/AskName.aspx</URL>”);
Response.Write(“</MenuItem>”);
Response.Write(“</CiscoIPPhoneMenu>”);

Wiem przypomina to bardziej stare spaghetti ASP niż ASP.NET, ale cóż poradzić. Na koniec musimy jeszcze poinformować nasz telefon gdzie szukać naszej aplikacji, w tym celu wracamy do Call Managera zaznaczamy urządzenie i wybieramy Configure -> Selected Device. Tutaj w pozycji Services URL podajemy adres do naszej strony i gotowe. Na koniec po ponownym uruchomieniu telefonu i naciśnięciu przycisku Services powinniśmy zobaczyć efekt naszej pracy.

OAC - Orientation Aware Control

styczeń 30, 2008

Stworzenie przejrzystego i funkcjonalnego interfejsu dla aplikacji mobilnej nie jest prostym zadaniem. Szczególnie obecnie gdy dynamika rynku urządzeń mobilnych cały czas się zwiększa i co rusz spotykamy się z nowymi rodzajami wyświetlaczy. Projektując interfejs aplikacji mobilnej musimy mieć na uwadze trzy parametry wyświetlania: rozdzielczość, rozmiar ekranu, i tryb wyświetlania. Rozdzielczość określana jest w DPI (ang. dot per inch) im większa tym więcej możemy zmieścić na ekranie. Typowymi rozdzielczościami są QVGA (Quater - VGA) która wynosi 96 DPI i VGA wynosząca 192 DPI. Rozmiar ekranu, podawany jest w pikselach i typowo wynosi 240×320 (szerokość/wysokość). Tryb wyświetlania określa czy nasze okno wyświetlane jest pionowo (vertical/portrait) bądź poziomo (horizontal/landscape).

Rozdzielczością, automatycznie zajmuje się Visual Studio, po za tymi sporadycznymi przypadkami gdy ręcznie tworzymy kontrolki. Musimy wtedy rozmiary komponentu przemnożyć przez, że tak to nazwę, współczynnik skalujący - dostosowujący wymiary do aktualnej rozdzielczości. Skalę możemy określić na przykład tak:


static class Program
{

[MTAThread]
static void Main()
{
  frmScaling f = new frmScaling();

  // rozdzielczość dla której projektowaliśmy aplikację
  const float designResolution = 96.0f;
  System.Drawing.Graphics g = f.CreateGraphics();
  float runningResolution = g.DpiX;

  ScaleFactor = runningResolution / designResoluion;
  g.Dispose();

  Application.Run(f);
}

  // Przemnóż wszystkie ‘ręcznie’ tworzone kontrolki przez
  // Program.ScaleFactor,
  // Przez większość czasu będą one przyjmowały jedną z
  // trzech wartości:
  // 1 - gdy zaprojektowaliśmy aplikację dla rozdzielczości
  //     na której pracujemy
  // 2 - gdy zaprojektowaliśmy aplikację dla 96dpi ale
  //     pracujemy na 192 dpi
  // 1.365 - gdy zaprojektowaliśmy aplikację dla 96dpi ale
  //     pracujemy na 131dpi (QVGA Smartphone)
  internal static float ScaleFactor;
}

Więcej na ten temat można znaleźć w książce Microsoft Mobile Development Handbook na strony 63 - 66.

Niestety wymiarami ekranów i trybem pracy musimy zająć się sami. Jest kilka sposobów aby ułatwić sobie, życie. Jednym z nich jest projektowanie interfejsu z myślą o kwadratowym ekranie. Mato swoje zalety - interfejs zawsze będzie taki sam niezależnie czy aplikacja pracuje w trybie pionowym czy poziomym. Wadą tego rozwiązania jest to, że na ekranach prostokątnych, marnujemy całkiem sporą ilość wolnej przestrzeni. Myślę, że można wziąć to rozwiązanie pod uwagę dla prostych aplikacji bez wyszukanego interfejsu użytkownika.

Innym rozwiązaniem jest stworzenie osobnych kontrolek - reprezentujących zawartość danego okna - i dynamicznej ich zamianie w zależności od wymaganego położenia. Rozwiązanie to wymaga jednak większego nakładu pracy gdyż nie dość, że musimy zaprojektować dwa różne ekrany i obsłużyć zdarzenie zmiany położenia ekranu. Prosty przykład tego rozwiązania można znaleźć w jednym z wcześniejszych moich postów. Przykład ten prezentuje dodatkowo jak wykorzystać wzorzec MVP (Model-View-Presenter) aby nie duplikować kodu wewnątrz kontrolek.

Kolejnym rozwiązaniem, obecnie chyba najłatwiejszym, jest skorzystanie w Orientation Aware Control w skrócie OAC. OAC jest kontrolką kontenerem i odpowiada funkcjonalności standardowej UserControl. Jednak posiada szereg nowych właściwości dzięki którym, możemy, na tylko jednej (!) kontrolce zaprojektować layout’y dla różnych trybów pracy ekranu. Czyli w sumie cała ciężka praca jest zrobiona za nas - jedyne co nam pozostaje to rozplanować prawidłowe położenie kontrolek.

Komponent OAC możemy pobrać z dwóch źródeł. Pierwszym jest pakiet Microsoft Patterns & Practises - Mobile Client Software Factory (MCSF). Niestety został on wydany prawie dwa lata temu (lipiec 2006) i od tego czasu nie był uaktualniany. Drugim źródłem jest strona www.orientationaware.net - tutaj możemy znaleźć najnowszą wersję tejże kontrolki, niestety jest już to kontrolka komercyjna. Fakt na stronie jest udostępniona wersja Community Edition ale jest ona dość poważnie ograniczona tzn. obsługuje tylko jedną rozdzielczość (QVGA).

Z moich doświadczeń wynika, że wersja kontrolki dostarczona MCSF w zupełności wystarcza i nie powoduje większych problemów. Jednakże trzeba mieć na uwadze, że w przypadku jakichkolwiek kłopotów, możemy mieć problem z otrzymaniem pomocy (ostatnie tematy z forum są datowane na rok 2006), i albo kupimy komercyjną wersję kontrolki, albo sami będziemy musieli zgłębić jej kod źródłowy.

Jak to wszystko działa? Otóż, przede wszystkim musimy dodać referencje do assembly:
Microsoft.Practices.Mobile.UI.OrientationAware.dll
i
Microsoft.WindowsMobile.Status.dll
lub tylko
Clarius.UI.OrientationAware.dll
w przypadku wersji komercyjnej. Kolejnym krokiem będzie utworzenie nowej UserControl i zmianie klasy z której ona dziedziczy na OrientationAwareControl.

 

Teraz możemy otworzyć naszą kontrolkę w designerze, właściwościami na które powinniśmy zwrócić uwagę to Orientation i Resolution. Jak nazwy wskazują Orientation określa jak nasze okno jest położone (vertical/horizontal) a Resolution pozwala nam wybrać rozdzielczość dla jakiej projektujemy nasz interfejs. Kontrolka dostarczona nam przez MS Pattern & Practises pozwala nam na wybór pomiędzy: 240×320 (QVGA), 480×640 (VGA), 240×240 (kwadratowa QVGA), 480×480 (kwadratowa VGA) co w większości przypadków jest wystarczające. Kontrolka dostarczona nam przez Clarius Consulting (www.orientationaware.net) - rozszerza ten zakres i pozwala na definiowanie własnych rozmiarów. Następnie na powierzchni naszej kontrolki umieszczamy resztę elementów, np.:

Gdy jesteśmy zadowoleni z wyglądu kontrolki pora utworzyć interfejs dla trybu landscape. W tym celu możemy zmienić właściwość Orientation z okna Properties bądź skorzystać z menu kontekstowego kontrolki i wybrać ‘Rotate‘. Teraz nie pozostaje nam nic innego jak dostosować wielkość kontrolki i odpowiednio rozmieścić elementy na niej.

Nie jesteśmy bynajmniej ograniczeni tylko do zmiany położenia elementów naszej kontrolki - możemy również zmieniać pozostałe właściwości jak np. ich rozmiary, właściwość Text czy Image. Ograniczeniem z jakim się spotykamy jest niemożliwość dodawania nowych elementów na widokach innych niż domyślny. Jeśli chcemy aby jakaś kontrolka była widoczna tylko w trybie landscape - musimy najpierw przejść do widoku domyślnego domyślnego, tzn. nie tylko położenia horizontal ale także domyślnej rozdzielczości, możemy tego dokonać poprzez wybranie opcji z menu kontekstowego kontrolki ‘Switch to Default Layout‘. Teraz dodajemy nową kontrolkę i ustawiamy jej właściwość Visible na false i zmieniamy ją w kodzie w momencie wykrycia zmiany orientacji ekranu. Właściwości Orientation Aware Control, dla wszystkich trybów wyświetlania, oprócz domyślnego są przechowywane jako zasoby. Dlatego też, jeśli przyjrzymy się pozycji Resources naszej kontrolki zobaczymy coś takiego:

Pojedynczy plik określa właściwości jednego trybu wyświetlania i aby zmniejszyć rozmiar zasobów przechowuje tylko wartości które uległy zmianie z widoku domyślnego.

Problem jaki wiąże się z przechowywaniem właściwości w zewnętrznych zasobach jest to, że są one odczytywane przy każdej zmianie widoku i nie zachowują aktualnego stanu poszczególnych kontrolek. Weźmy na przykład aplikację korzystającą z OAC w którym znajduje się kontrolka TextBox, użytkownik wpisuje do niego dane i zmienia orientację ekranu - w tym momencie właściwości kontrolki OAC są odczytywane z zasobów i zawartość naszej kontrolki zostaje zastąpiona domyślną wartością. Dlatego też musimy sami zadbać o przechowywanie zawartości elementów naszej OAC.

Na koniec kilka szczegółów o których powinniśmy wiedzieć:

  • Forma na której zamierzamy umieścić naszą kontrolkę pochodzącą od OAC powinna mieć ustawioną właściwość AutoScaleMode na None, gdyż skalowanie odbywa się trakcie projektowania i prawidłowe rozmiary są zapisane w zasobach.
  • Gdy projektujemy interfejs dla trybu VGA, wszystkie kontrolki są dwa razy większe niż zazwyczaj. Nie jest to błąd - Visual Studio 2005 (i zapewne VS 2008 też, ale nie testowałem tego) odwzorowuje kontrolki w ich rzeczywistych rozmiarach a ponieważ VGA jest dwa razy lepsza od QVGA na ekranach naszych monitorów otrzymujemy nieco większe niż przywykliśmy. Niestety taka jest specyfika Visual Studio i nie jesteśmy wstanie nic z tym zrobić.

Dodatkowych informacji można szukać na stronach Clarius Consulting bądź w udostępnionym przez Microsoft webcast’cie ‘Designing Zero-Code Adaptive UIs Using the Orientation Aware Control’. Źródła przykładowej aplikacji jest dostępne na tutaj.

Nowe ‘mobilne’ screencast’y

styczeń 24, 2008

Kompatybilność i SQL Server Compact Edition

styczeń 10, 2008

Na blogu SQL Server Compact Team Blog pojawił się artykuł omawiający wzajemną kompatybilność poszczególnych wersji SQL Server Compact Edition. I tak np. można się dowiedzieć, że np. designer z Visual Studio 2008 będzie pracował tylko z wersją 3.5, co jest nieco zaskakujące bo po produkcie takim jak Visual Studio można by się spodziewać nieco lepszej kompatybilności wstecznej.

LINQ ciąg dalszy

styczeń 9, 2008

Tym razem napiszę o problemie, na jaki natknąłem się korzystając z LINQ, otóż załóżmy, że mamy kolekcję zawierającą jako elementy obiekty typu np. Item. Teraz wykonuję jakąś kwerendzie LINQ i jako rezultat zwracam kolekcję niektórych właściwości klasy Item. Następnie otrzymaną listę bindujemy, do powiedzmy, kontrolki ComboBox. Wykonujemy jakieś operacje etc. a następnie chcemy pobrać zaznaczony obiekt poprzez ComboBox.SelectedItem. I tu pojawia się problem na jaki typ rzutować? Dla zobrazowania przykładowy kod:

class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Value { get; set; }
    public double Scale { get; set; }

    // etc. jakieś inne właściwości

    public Item(int id, string name, int value, double scale)
    {
        Id = id;
        Name = name;
        Value = value;
        Scale = scale;
    }
}

List<Item> list = new List<Item>();
list.Add(new Item(1, “Item1″, 101, 5.99));
…
list.Add(new Item(9, “Item9″, 909, 7));

var query = from o in list
            where o.Id < 10 && o.Id > 4
            select new { o.Id, o.Name, o.Value };

cboTest.DataSource = query.ToList();
cboTest.DisplayMember = “Name”;
cboTest.ValueMember = “Id”;

Ponieważ w kwerendzie LINQ, zwracam typ generowany przez IL (tzw. anonymous type) nie mogę jawnie na niego rzutować, i tym samym zastosowanie typów object czy var nic mi nie daje (tj. dostępu do właściwości typu). Zawsze można skorzystać z mechanizmu refleksji, jednak nie jest to ścieżka którą chciałbym podążyć. Szukam raczej jakiegoś ’schludnego’ podejścia do tego ograniczenia/ciekawostki. Jakieś pomysły?

.NET Compact Framework 3.5 Redistributable

styczeń 8, 2008

Microsoft udostępnił wersję redistributable .NET Compact Framework 3.5 - do pobrania tutaj.

Lambda Expression

styczeń 7, 2008

Aby zaktualizować interfejs naszej aplikacji z innego wątku musimy się uciec na przykład do metod Invoke/BeginInvoke. Korzystając z .NET 2.0 musimy przykładowo:

private delegate void UpdateStringValue(string value); 

UpdateStringValue SetLabelStatus = delegate(string value)
{
    tsStatusLabel.Text = value;
};

BeginInvoke(SetLabelStatus, new object[] { Generating … });

Jeśli jednak skorzystamy z Lambda Expression jednego z dobrodziejstw najnowszego Frameworka .NET 3.5, wystarczy tylko jedna linia:

BeginInvoke(new Action(() => tsStatusLabel.Text = Generating …));
To się nazywa postęp! ;)