09/06/2011

Bardzo wymagająca rekrutacja 2

Home

W ostatnim poście opisałem przebieg pewnej rekrutacji, w której uczestniczyłem, aż do rozmowy z kierownikiem projektu. Post ten stanowi dokończenie tego tematu, a w szczególności zawiera odpowiedź na pytania jakie pojawiły się w komentarzach.

Po jakimś czasie po rozmowie z kierownikiem projektu, w tej chwili już nie pamiętam szczegółów, zostałem zaproszony do kolejnego etapu rekrutacji. Tym razem musiałem pofatygować się do innego miasta na serię rozmów z przedstawicielami firmy z zagranicy. O takiej konieczności zostałem zresztą poinformowany dużo wcześniej i dlatego również na każdym z wcześniejszych etapów rekrutacji była sprawdzana moja znajomość angielskiego. Nie ukrywam, że taka podróż nie do końca mi się podobała ale ponieważ praca wyglądała bardzo obiecująca to zdecydowałem się na wyjazd.

Na rozmowę pojechałem samochodem ze względu na elastyczność, jadę kiedy chcę, nie przejmuję się godziną odjazdu pociągu... Decyzja była dobra i zła. Zła ponieważ 6 godzin jazdy to męcząca sprawa, a dobra bo pociągiem nie byłoby wiele krócej, a musiałbym jeszcze płacić za taksówkę itd. Na rozmowę pojechałem dzień wcześniej aby się porządnie wyspać. Nocleg miałem prawie za darmowo, a za benzynę zwrócono mi pieniądze. Tutaj dodam, że z perspektywy uważam, że po tak długiej jeździe samochodem, a chyba nawet pociągiem porządny odpoczynek to konieczność. Innymi słowy nie ma sensu umawiać się na rozmowę o pracę tuż po długiej podróży. Zapewne można wypaść dobrze ale jestem przekonany, że zawsze będzie to gorzej niż kiedy będziemy wypoczęci.

Rozmowy z przedstawicielami z zagranicy trwały 3 godziny. W sumie odbyłem trzy rozmowy. Każda z nich była mocno techniczna ale dotyczyła trochę innych rzeczy. Rozmawiałem o algorytmach np.: programowanie dynamiczne, implementacji funkcji wirtulanych w C++, złożoności obliczeniowej, a także o rzeczach bardziej biznesowych. Muszę przyznać, że po tych rozmowach nabyłem większej pewności co do mojego mówionego języka angielskiego. To jednak co innego rozmawiać po angielsku na wczasach czy prowadzić luźną rozmowę, a co innego mieć kilkugodzinną rozmowę o pracę w tym języku z ludźmi, z którymi w inny sposób sie nie dogadasz.

Z przebiegu tych rozmów byłem zadowolony, a moje wrażenie zostało wkrótce potwierdzone bo zaproponowano mi pracę. Tutaj dodam, bo zapomniałem o tym wcześniej napisać, że rekrutacja dotyczyła stanowiska programisty czy jak to się nazywało w nomenklaturze tejże firmy. Z tą istotną różnicą, że było to stanowisku tzw. programisty algorytmicznego czyli takiego, który zajmuje się na co dzień przede wszystkim implementacją i analizą algorytmów np.: data miningowych, sortowania itp., a nie realizacją typowo biznesowych wymagań.

Dalszy etap rekrutacji to oczywiście negocjacje, które trwały dość długo (dwa spotkania, rozmowy telefoniczne). Jak się skończyły? Finalnie odrzuciłem ofertę pracy. Czemu? Wbrew pozorom nie chodziło o kwestie finansowe, a na pewno nie miały one decydującej roli. Głównym argumentem przeciw były powody natury rodzinno osobistej. Co tu dużo mówić, przeprowadzka do innego miasta kiedy całe życie spędziło się w Warszawie, ma się tutaj rodzinę, przyjaciół, pracę, mieszkanie i jest się zadowolony z życia to bardzo ciężka decyzja. Pozostaje mi tylko żałować, że oferta nie dotyczyła pracy w Warszawie.

Jako podsumowanie chciałbym wymienić te cechy/zalety tego procesu rekrutacyjnego, które spowodowały, że go zapamiętałem:
  • Bardzo kompetentne osoby sprawdzające wiedzę techniczną.
  • Dyskusja, a nie tylko ocena przedstawionych przeze mnie rozwiązań.
  • Praca domowa do zrobienia.
  • Sprawdzenie znajomości algorytmów i rozwiązywania zadań algorytmicznych.
  • Prowadzenie części rozmowy w języku angielskim.
  • Szczerość w odpowiedziach na moje pytania nawet jeśli odpowiadający wiedział, że taka odpowiedź może mnie zniechęcić.
  • Jeśli firma wkłada tyle wysiłku w znalezienie pracowników to znaczy, że planuje dłuższą współpracę.
  • Jeśli firma wkłada tyle wysiłku w znalezienie pracowników to znaczy, że potencjalni współpracownicy są bardzo kompetentni.
Opisana rekrutacja jest dla mnie wzorem rekrutacji dobrze sprawdzającej umiejętności i wiedzę technologiczną kandydata i porównuję do niej inne. Nie twierdzę jednak, że zawsze powinno to wyglądać w taki sposób. Nie każdej firmy na to stać, a po drugie nie zawsze potrzebne jest tak gruntowne sprawdzenie potencjalnego pracownika. Każdy proces rekrutacyjny na stanowisko programistyczne powinien jednak charakteryzować się kilkoma rzeczami (łatwymi do osiągnięcia):
  • Pisanie jakiegoś kodu przez kandydata.
  • Test z wiedzy technicznej.
  • Rozmowa z osobą techniczną i biznesową.
  • Sprawdzenie znajomość języka angielskiego przynajmniej w stopniu pozwalającym czytać dokumentację.

06/06/2011

Bardzo wymagająca rekrutacja

Home

W swojej dotychczasowej karierze wziąłem udział w wielu rekrutacjach. W przeważającej liczbie przypadków zostałem zaproszony na rozmowę ale czasami skończyło się na wysłaniu CV. Bardzo często zaproszenie na rozmowę poprzedzone było wywiadem telefonicznym. Wielokrotnie rozwiązywałem różnego rodzaju testy, dużo rzadziej byłem proszony o wykonanie pracy domowej. Część rekrutacji organizowana była przez firmy HR'owe inne bezpośrednio przez zatrudniającą firmę. Spośród tych wszystkich rekrutacji kilka zapadło mi w pamięci, a szczególnie jedna niezwykle wymagająca. Aby nie budzić wątpliwości napiszę, że rekrutacja ta odbyła się już sporo czasu temu i finalnie nie podjąłem współpracy z tą firmą. Chciałbym jednak opisać jak wyglądała ponieważ dużo się dzięki niej nauczyłem i jest dla mnie dzisiaj wzorem, do którego porównuję inne rekrutacje. Post ze względu na jego długość postanowiłem rozbić na dwie części, tutaj prezentuję pierwszą.

Wszystko rozpoczęło się w sposób standardowy od zapytania na portalu internetowym czy jestem zainteresowany taką, a taką ofertą pracy. Propozycja była na tyle interesująca, że odpowiedziałem na nią pozytywnie. Po jakimś czasie zadzwoniła do mnie Pani z HR'ów aby potwierdzić moje zainteresowanie i umówić się na dłuższą rozmowę telefoniczną.

O ile mnie pamięć nie myli rozmowa odbyła się w ciągu kolejnego tygodnia i trwała około półgodziny. W tym czasie zostałem poproszony o opisanie swojego dotychczasowego doświadczenia, w jakich projektach brałem udział, jakich technologii używałem... jednym słowem standard. Padło również pytanie o oczekiwania finansowe, na które to mimo starań i prób aby to druga strona wyszła najpierw z propozycją w końcu udzieliłem odpowiedzi. Rozmowę wyróżnia trochę to, że już na tym etapie została zweryfikowana moja znajomość języka angielskiego. Z drugiej strony taka praktyka jest chyba coraz powszechniejsza. Na koniec dowiedziałem się, że dalszy etap rekrutacji będzie polegał na rozwiązaniu dwóch zadań algorytmicznych i jednego biznesowego. Przy czym większą uwagę miałem zwrócić na zadania algorytmiczne.

Następnego dnia otrzymałem drogą mailową treść zadań. Niestety ze względu na klauzulę poufności nie przytoczę ich tutaj, chociaż chciałbym ponieważ były bardzo ciekawe. Pierwsze zadanie algorytmiczne rozwiązałem następnego wieczoru. Moje pierwsza implementacja bazowała na przeszukiwaniu przestrzeni stanów i dawało poprawne wyniki ale kiedy już miałem je wysłać zauważyłem, że do problemu można podejść w zupełnie innych sposób. Finalne rozwiązanie zajmowało kilka linii kodu zamiast kilkuset! Za drugie zadanie zabrałem się następnego dnia i doszedłem do wniosku, że to pytanie z hakiem ponieważ przedstawiony problem należy do klasy NP, co też napisałem w odpowiedzi.

Rozwiązanie pierwszego zadania została zaakceptowane. Co do drugiego to zostałem poproszony żebym się jeszcze mu przyjrzał ponieważ postawiony problem można rozwiązać w czasie wielomianowym. Początkowo byłem przekonany, że to układający/oceniający zadanie pomylił się. Błąd tkwił jednak po mojej stronie. Na czym polegał? Przedstawiony problem można było sprowadzić do znalezienie ścieżki Hamiltona w grafie i to rzeczywiście jest problem NP. Ja zapomniałem jednak o tym, że wśród wszystkich możliwych grafów są takie ich odmiany dla, których problem znalezienia ścieżki Hamiltona można sprowadzić do znalezienia ścieżki Eulera, a to można zrobić w czasie wielomianowym. Co do zadania biznesowego to w końcu z braku czasu go nie rozwiązałem ale tak jak pisałem nie było ono bardzo istotne.

Co dalej? Zostałem zaproszony na rozmowę, połączoną z testem. Test składał się z 20 otwartych pytań i na jego rozwiązanie było około 30 minut. Pytania były tak ułożone, że można na nie było odpowiedzieć w jednym/dwóch zadaniach. Czy były trudne? Dla mnie raczej nie, chociaż nad niektórymi musiałem się dłużej zastanowić. Pomimo, że pytań było tylko 20 sądzę, że dobrze weryfikowały ogólną znajomość platformy .NET. Było coś o zwalnianiu zasobów, użyciu słowa kluczowego using, synchronizacji, LINQ'u itd. Te 30 minut starczyło dokładnie na tyle aby napisać odpowiedzi, ktoś nie orientujący się w temacie miałby z tym problem.

Po rozwiązaniu testu przyszła kolej na rozmowę z dwoma "technicznymi" osobami. Rozmowa odbyła się za pośrednictwem Skype. Początkowo zapraszano mnie na wizytę do innego miasta ale kiedy powiedziałem, że będzie z tym ciężko zorganizowano mi video konferencję. Rozmowa miała być połączona z omówieniem testu ale widać wypadłem dobrze ponieważ zadano mi raptem 1 albo 2 pytania na ten temat. Całą rozmowę zapamiętałem z 2 powodów. Po pierwsze trwała bite 3 godziny, a po drugie zadawano mi naprawdę trudne pytania. Pytania nad którymi musiałem się dogłębnie zastanowić przed podaniem odpowiedzi. W gruncie rzeczy nie były to po prostu pytania ale spore problemy do rozwiązania. Fajne było to, że nawet po podaniu prawidłowej odpowiedzi mówiono mi żebym się zastanowił bo można to zrobić jeszcze lepiej, bardziej optymalnie. W czasie tej rozmowy powtórnie zweryfikowano moją znajomość języka angielskiego tyle, że tym razem rozmawialiśmy o zagadnieniach technicznych. Po tych 3 godzinach byłem równie zmęczony jak po całym dniu pracy.

W czasie tej rozmowy miałem też okazję zadać kilka pytań, na które w miarę możliwości udzielono mi odpowiedzi. Jak zwykle w takich przypadkach poprosiłem również o feedback. Powiedziano mi, że pełnego raportu raczej nie otrzymam ale na pewno dostanę informację zwrotna czy było coś nie tak, a jeśli tak to co. Dwa dni później dostałem zaproszenie na następną rozmowę, tym razem z kierownikiem projektu. Rozmowa ta również odbyła się za pośrednictwem video konferencji i trwała około półgodziny. I tym razem sprawdzono moją znajomość języka angielskiego i zadano kilka "biznesowych" pytań. Miałem też okazję dopytać o interesujące rzeczy. Na koniec kierownik projektu powiedział, że w razie jakichś wątpliwości zaprasza do kontaktu. Z możliwości tej skorzystałem kilkukrotnie i za każdym razem otrzymałem wyczerpującą odpowiedź...

05/06/2011

Aplikacje wielojęzyczne - WPF

Home

Przystępując do tłumaczenia aplikacji WPF miałem dokładny plan jak się za to zabrać. Mianowicie postanowiłem użyć narzędzia LocBaml, o którym dowiedziałem się z training kit'a do egzaminu 70-502. Opis całej procedury można znaleźć tutaj. Praktyka pokazała jednak, że narzędzie to pozostawia bardzo dużo do życzenia. Dalej opiszę kolejne kroki pracy z LocBaml wraz z komentarzem jak to wygląda w praktyce.

UIDs

Mechanizm wielojęzyczności aplikacji w WPF koncepcyjnie zbliżony jest to tego co znamy z ASP.NET. W szczególności każda kontrolka, która posiada jakieś zasoby do przetłumaczenia powinna mieć odpowiedni identyfikator tzw. UID. Identyfikatory te można nadać automatycznie przy pomocy polecenia:

msbuild /t:updateuid NAZWA_PROJEKTU.csproj

Wszystkie pliki XAML, które mają zostać przetworzone przez msbuild powinny być checkoutowane (brzmi okropnie ale nie przychodzi mi do głowy dobry polski odpowiednik). Narzędzie to działa i dobrze i źle. Dobrze bo rzeczywiście wygeneruje dla wszystkich kontrolek UID. Źle bo zrobi to dla wszystkich kontrolek, a właściwie powinienem napisać dla wszystkich elementów dokumentu XAML. msbuild nie wykonuje jakiejkolwiek analizy przetwarzanych elementów. Skutkuje to dużym bałaganem. Po przeprowadzeniu tej operacji w plikach XAML pojawi się bardzo dużo, niepotrzebnych, śmieciowych UID'ów, na przykład po co nadawać identyfikator UID kontrolce Border, Line lub StackPanel. Potem aż bolą oczy jak się patrzy na tak przetworzony XAML. Moja rada jest taka. Jeśli chcemy stosować LocBaml to zawczasu, tworząc interfejs użytkownika, powinniśmy nadawać kontrolkom UID'y.

Wyciąganie zasobów do przetłumaczenia

Po wygenerowaniu UID'ów przechodzimy do następnego kroku czyli używamy LocBaml aby wyciągnać zasoby do przetłumaczenia do pliku CSV. Służy do tego takie polecenie:

LocBaml.exe /parse NAZWA_PROJEKTU.resources.dll /out:NAZWA_PLIKU_WYJSCIOWEGO.CSV

Skompilowane zasoby czyli pliki dll umieszczane są w podkatalogach o nazwach zgodnych z kulturą (językiem) zasobów np.: en-US, pl-PL itp. Jeśli nasz projekt nazywa isę SimpleApplication to plik dll z zasobami będzie nazywał się SimpleApplication.resources.dll. W praktyce wydanie tego polecenia skończy się błędem:

Could not load file or assembly '...' or one of its dependencies. The system cannot find the file specified.

Rozwiązanie jest proste ale trochę upierdliwe. Otóż plik LocBaml.exe, plik z zasobami NAZWA_PROJEKTU.resources.dll oraz plik exe/dll powstały po skompilowaniu aplikacji/biblioteki muszą znajdować się w tym samym folderze. Najszybciej to oskryptować. Podobny ale trochę inny komunikat o błędzie:

Could not load file or assembly '...' or one of its dependencies. An attempt was made to load a program with an incorrect format.

Otrzymamy jeśli nasza aplikacja ma ustawioną docelową platformę na x86. W takim wypadku możemy zmienić ustawienia naszej aplikacji, przekompilować LocBaml na x86 albo użyć narzędzia corflags o czym już zresztą pisałem tutaj lub tutaj.

Tak wygenerowany plik CSV powinniśmy teraz przetłumaczyć. Jeśli użyliśmy msbuild do wygenerowania UID'ów to plik ten będzie zawierał dla dużej aplikacji nawet kilkanaście tysięcy wierszy. W moim przypadku było to około 13 tysięcy wierszy z czego jakieś 10% wymagało przetłumaczenia!!! Po prawdzie tak duża liczba wierszy jest również związana ze sposobem działania LocBaml. Otóż dla każdej kontrolki z UID'em w pliku CSV znajdziemy wiele wierszy. Na przykład dla kontrolki TextBlock możemy zlokalizować, co oczywiste, właściwość Text ale również mniej sensowne jak Foreground czy Margin.

Stworzenie pliku dll z przetłumaczonymi zasobami

To jest chyba najtrudniejszy, a zarazem najmniej wygodny krok w całym procesie. W teorii jest to proste. Bierzemy przetłumaczony plik CSV i używamy LocBaml do wygenerowania dll'ki z zasobami o nazwie NAZWA_PROJEKTU.resources.dll, a następnie umieszczamy ją w odpowiednim katalogu aplikacji np.: en-US. Za wczytanie odpowiedniej wersji zasobów odpowiada już silnik WPF, a decyzje podejmuje podobnie jak w ASP.NET czy WinForms na podstawie właściwości Thread.CurrentThread.CurrentUICulture.

Problem pierwszy związany jest z bugiem w implementacji LocBaml, który objawia się tym, że identyfikatory zasobów w pliku CSV mogą się powtarzać. W związku z tym proces odwrotny czyli wygenerowanie dll'ki na podstawie pliku CSV się nie powiedzie. Rozwiązanie problemu można znaleźć tutaj i jest ono trywialne ale wymaga rekompilacji projektu. Można też, ale tego nie próbowałem, zapewnić, że wszystkie pliki XAML w naszym projekcie mają inne nazwy nawet jeśli znajdują się w innych katalogach.

Pierwszy problem w porównaniu z drugim to nic. Wyobraźmy sobie taką sytuację. Mamy projekt aplikacji WPF i grzecznie, zgodnie z zasadami wszystkie komunikaty wyświetlane użytkownikowi trzymamy w plikach zasobów resx. Po jakim czasie chcemy przygotować angielską wersję aplikacji. Zaczynamy od przetłumaczenia tych zasobów, a więc plik np.: msg.resx kopiujemy i zmieniamy mu nazwę na msg.en-US.resx, a następnie tłumaczymy.

Teraz zabieramy się za XAML. Nadajemy kontrolką UID'y, wyciągamy zasoby do pliku CSV, tłumaczymy i ponownie używamy narzędzie LocBaml do wygenerowania dll'ki z zasobami. Wszystko poszło jak po maśle i zadowoleni chcemy skopiować świeżutką dll'ke z przetłumaczonymi zasobami do katalogu en-US, a tu figa z makiem. W katalogu znajduje się już dll'ka o takiej nazwie. Skąd się wzięła? Została wygenerowana przez VS i zawiera komunikaty, które umieściliśmy w pliku msg.en-US.resx. Mamy więc dwie dll'ki o takiej samej nazwie, jedną z przetłumaczonymi komunikatami, a drugą z przetłumaczonym GUI i musimy umieścić je w tym samym katalogu.

I tutaj zaczynają się schody, wysokie, wąskie, ciemne i niewygodne. Nie pozostaje nic innego jak użyć linkera al.exe. Da się to zrobić ale tak jak powiedziałem jest to bardzo niewygodne. Nie będę tutaj tego opisywał bo moim zdaniem jest to strasznie nudne. Jeśli ktoś tego potrzebuje to zapraszam do kontaktu.

Inne

Takie podejście do lokalizowania aplikacji WPF ma również inne wady. Tak jak wspomniałem wersja zasobów jaka zostanie wczytana zależy od właściwości Thread.CurrentThread.CurrentUICulture. Trzeba więc pamiętać aby ustawić ją na odpowiednią kulturę zanim zaczniemy robić cokolwiek z interfejsem użytkownika. W przeciwnym wypadku możemy doprowadzić do wczytania złej wersji zasobów i okaże się, że cześć okien będzie w języku polskim, a część w angielskim. Po drugie, modyfikacja Thread.CurrentThread.CurrentUICulture nie powoduje automatycznego przełączenia się pomiędzy jedną, a drugą wersją zasobów. Dopiero zamknięcie okna i ponowne jego wyświetlenie spowoduje pobranie odpowiedniej wersji językowej zasobów.

Podsumowanie

LocBaml można użyć, pytanie czy z wszystkimi wadami i ograniczeniami tego narzędzia warto. W omawianym przypadku skończyło się to użyciem wyszukanej przez kolegę biblioteki WPFLocalizeExtension. Też ma swoje wady, też nie jest idealna, w szczególności w żaden sposób nie automatyzuje procesu lokalizowania aplikacji ale nie wymaga UID'ów, nie trzeba pisać skryptów, linkować itd. Tutaj zresztą kłania się to co już pisałem dwa razy:

Jeśli chcemy aby nasza aplikacja miała wiele wersji językowych to przygotowujmy się do tego od pierwszej linijki tej aplikacji.


26/05/2011

Aplikacje wielojęzyczne - WinForms

Home

Przyszła pora wrócić do tematu aplikacji wielojęzycznych. Tym razem skupię się na WinForms. Zacznę od tego, że część rzeczy, o których pisałem we wcześniejszym poście na temat aplikacji ASP.NET można zastosować do innych technologii, w szczególności do WinForms. Dla przypomnienia:
  • Jeśli chcemy aby nasza aplikacja miała wiele wersji językowych to przygotowujmy się do tego od pierwszej linijki tej aplikacji.
  • Stałe znakowe w kodzie są złe, bardzo złe, niewyobrażalnie złe... Stałe zawierające komunikaty dla użytkownika itp. powinny znajdować się w zasobach aplikacji, a pozostałe, nazwijmy je techniczne powinny zostać zdefiniowany w jednym konkretnym miejscu np.: klasie o nazwie Constans.
Tyle tytułem wstępu. Kiedy przystępowałem do prac nad aplikacją WinForms byłem o tyle w gorszej sytuacji w porównaniu do wcześniejszych prac nad aplikacją ASP.NET, że nie wiedziałem o żadnych narzędziach wbudowanych w Visual Studio wspomagających lokalizację WinForms. Co do samego mechanizmu przełączania się pomiędzy różnymi wersjami językowymi zasobów to wygląda to tak samo jak w ASP.NET. Mamy więc odpowiednio ponazywane pliki np.: Resources.resx, Resources.en.resx itd. zawierające zasoby dla poszczególnych języków (kultur). Teraz w zależności od tego jaką kulturę ustawimy na właściwości Thread.CurrentThread.CurrentUICulture taka wersja zasobu zostanie wczytana. Na samym początku pomyślałem więc aby przejrzeć kod wygenerowany przez designer i w nim pozamieniać stałe znakowe na odwołania do zasobów. Podobnie postąpiłem przecież dla ASP.NET. Czyli poniższy kod:
...
// 
// cancelButton
// 
this.cancelButton.Location = new System.Drawing.Point(85, 103);
this.cancelButton.Name = "cancelButton";
this.cancelButton.Size = new System.Drawing.Size(75, 23);
this.cancelButton.Text = "Anuluj";
...
Zamienić na taki:
...
// 
// cancelButton
// 
this.cancelButton.Location = new System.Drawing.Point(85, 103);
this.cancelButton.Name = "cancelButton";
this.cancelButton.Size = new System.Drawing.Size(75, 23);
this.cancelButton.Text = Resources.Cancel;
...
To nie jest jednak dobry pomysł przynajmniej z kilku powodów. Kod generowany przez designer WinForms nie jest przeznaczony do samodzielnej modyfikacji. Podejście to zadziała ale wystarczy, że ktoś użyje designera aby zmienić położenie kontrolki lub zrobić coś równie prostego, a kod zostanie ponownie wygenerowany, a nasze zmiany usunięte. Po drugie przeglądanie kodu designera w poszukiwaniu stałych znakowych to żmudna, nudna i błędogenna robota. W następnej kolejności pomyślałem więc o innym rozwiązaniu:
public Form1()
{
  InitializeComponent();
  cancelButton.Text = Resources.Cancel;
  ...
}
Czyli zanim wyświetlimy formę, pobieramy zasoby i lokalizujemy GUI. Podejście bardzo proste, wręcz prymitywne. Można je trochę udoskonalić na przykład dodać do bazowej klasy metodę LocalizeGUI:
public class BaseForm : Form
{
  protected override void OnShown(EventArgs e)
  {
    LocalieGUI();
    base.OnShown(e);
  }

  protected virtual void LocalieGUI()
  {}
}
...
public partial class Form1 : BaseForm
{
  protected override void LocalieGUI()
  {
    cancelButton.Text = Resources.Cancel;
    ...
  }
}
Również bardzo proste rozwiązanie i można jeszcze dużo w nim zmienić. Zasadniczy problem polega jednak na tym, że takie rzeczy sprawdzają się kiedy używa się ich od początku. Ja dostałem gotową aplikację i jak wyobraziłem sobie przeglądanie całego kodu w poszukiwaniu etykiet, przycisków itd., a następnie przenoszenie ustawiania tekstu wyświetlanego użytkownikowi do metody LocalizeGUI to mi się odechciało.

Na szczęście istnieje dużo lepsze rozwiązanie problemu. Otóż Visual Studio posiada wsparcie dla wielojęzycznych WinForms i w przeciwieństwie do narzędzia Tools->Generate Local Resources dla ASP.NET działa całkiem dobrze. Używa się go bardzo prosto:
  • Otwieramy designer dla formy (kontrolki użytkownika).
  • Otwieramy okno właściwości (Properties) dla formy (kontrolki użytkownika).
  • Pole Localizable z kategorii Design ustawiamy na True i zapisujemy zmiany. W tym momencie designer wyniesie do pliku o nazwie NAZWA_FORMY.resx wszystkie stałe znakowe, które podlegają lokalizacji. Jeśli zajrzymy teraz do kodu generowanego przez designer to będzie on wyglądał trochę inaczej niż wcześniej. W szczególności nie znajdziemy tam kodu takie jak this.cancelButton.Text = "Anuluj";, a taki resources.ApplyResources(this.cancelButton, "cancelButton"); gdzie resources to obiekt klasy ComponentResourceManager. Co bardzo ważne dla kontrolek, które dodamy do formy później będzie to wyglądało tak samo.
  • Wracamy do okna właściwości (Properties).
  • Zmieniamy pole Language z Default na docelowy język np.: English.
  • Przechodzimy do okna designera i dokonujemy tłumaczenia. Czyli przycisk 'Anuluj' zamieniamy na 'Cancel', a etykietę 'Nazwa' na 'Name' itd.
  • Zapisujemy zmiany. W projekcie pojawi się nowy plik z zasobami o nazwie NAZWA_FORMY.JEZYK.resx.
  • Przywracamy poprzednią wartość pola Language czyli Default. Zawartość wszystkich zlokalizowanych kontrolek powinna wrócić do stanu wyjściowego.
  • Proces powtarzamy dla innych języków.
Z narzędziem tym pracuje się naprawdę przyjemnie. Trzeba tylko pamiętać, że w designerze na pierwszy rzut oka nie widać wszystkich rzeczy do przetłumaczenia na przykład pozycji menu. Praktyka pokazało również, że narzędzie to współpracuje z kontrolkami zewnętrznych dostawców. Mam tylko dwa zastrzeżenia. W jednym przypadku zmodyfikowany przez designer kod nie chciał się potem kompilować i musiałem go poprawić. Po drugie, o czym pisałem już w poście dotyczącym ASP.NET, jeśli nasza aplikacja składa się z wielu okien to otrzymamy wiele plików z zasobami. Moim zdaniem wprowadza to niestety bałagan do projektu i zmusza nas do wielokrotnego tłumaczenie tych samych tekstów.

19/05/2011

Toad, parametry typu OUT i kursory

Home

Będzie krótko i zwięźle. Pracujemy z aplikacją Toad for Oracle i napisaliśmy procedurę składowaną, która ma kilka parametrów oraz zwraca kursor przez parametr typu OUT. Dla ustalenia uwagi niech jej deklaracja wygląda następująco:

TYPE cursorType IS REF CURSOR;
...
PROCEDURE SOMEPROCEDURE(param1 VARCHAR2, param2 VARCHAR2, outParam OUT cursorType); 

Chcemy ją wywołać i przetestować w łatwy i szybki sposób. Zależy nam na tym aby aplikacja pokazała nam "zawartość" kursora. Rozwiązanie jest proste, a pokazałem je poniżej.

BEGIN
  SOMEPACKAGE.SOMEPROCEDURE (:PARAM1, :PARAM2, :CURSOR);
END;

Wystarczy zaznaczyć ten kod i nacisnąć Ctrl+Enter. Pojawi się okienko, w którym będziemy mogli zdefiniować wartości poszczególnych parametrów. W przypadku parametru :CURSOR środowisko powinno wykryć, że to kursor i zająć się jego obsługą. Po zamknięciu okienka kod zostanie wykonany, a "zawartość" kursora zostanie wyświetlona w gridzie (zakładka Data Grid). Proste, łatwe i przyjemne. Wcześniej nie znałem ten funkcjonalności ale bardzo mi się podoba.