Showing posts with label WinForms. Show all posts
Showing posts with label WinForms. Show all posts

23/09/2014

10000 UserObjects

Home

10000 to limit User Objects jakie na moim sprzęcie mogła zaalokować aplikacja zanim się wywaliła. 10000 to w gruncie rzeczy bardzo duża liczba i jeśli osiągamy ten limit to z dużą pewnością mamy jakiś problem i tak było w przypadku aplikacji napisanej w WinForms jaką miałem naprawić.

Po podłączeniu się debugger'em do aplikacji szybko stwierdziłem, że problem dotyczy jednej z kontrolek, która była alokowana dynamcznie w pętli. Na menadżerze zadań bardzo ładnie było widać jak wzrasta liczba User Objects z każdą kolejną instancją tej kontrolki. Nie byłem natomiast pewny czemu te kontrolki nie były zwalniane pomimo, że były usuwane z UI i nie było na nich wskazań (jak się później okazało pozornie). Tutaj z pomocą przyszedł ANTS Memory Profiler. Jedno uruchomienie i już wiedziałem w czym tkwi problem. Mrówki pokazały, że nie zwolnione kontrolki wciąż są wskazywane przez obiekt klasy System.Windows.Forms.DropTarget.

To dość częsty błąd, a dzieje się tak jeśli kontrolka wspiera Paste & Copy i w związku z tym ma ustawioną właściwość AllowDrop na true. Powoduje to, że kontrolka wskazywana jest przez wspominany obiekt klasy DropTarget dopóki nie ustawimy AllowDrop z powrotem na false. Poprawka błędu okazała się więc trywialna. Wystarczyło, że przy usuwaniu kontrolki z UI ustawiłem tą właściwość na false. W sumie drobna rzecz, ale łatwa do pominięcia.

Jeszcze krótki komentarz odnośnie technologii WPF. Tutaj też mamy właściwość AllowDrop, ale opisany problem nie występuje ponieważ WPF ma zupełnie inne bebechy niż WinForms.

16/11/2011

Hawkeye

Home

Hawkeye .NET Runtime Object Editor to program, który znalazłem w sieci dobre dwa lata temu. W tym czasie wielokrotnie mi się przysłużył, a jest przydatny w szczególności tym, którzy pracują z technologią Windows Forms. W skrócie, pozwala modyfikować UI działającej aplikacji. Jego użycie jest proste. Wskazujemy myszką interesujący nas fragment aplikacji, a Hawkeye oznacza wybraną kontrolkę przy pomocy czerwonej ramki i wyświetla listę właściwości i prywatnych pól klasy, których wartości możemy modyfikować. Jak by tego było mało, Hawkeye pozwala również dynamicznie wywoływać metody dla wybranych obiektów.

Zastanawiamy się jak nasza aplikacja będzie wyglądać z różowym tłem? Chcemy przesunąć na próbę kontrolkę o 2 piksele w prawo? A może chcemy nacisnąć przycisk, który jest nieaktywny? Te i inne rzeczy osiągamy zmieniając wartości odpowiednich właściwości, w tym przypadku odpowiednio: BackColor, Location, Enabled. W trzecim wypadku możemy też wywołać dynamicznie metodę PerformClick. Pozwala to w łatwy i przyjemny sposób zobaczyć, jak przy danych ustawieniach, będzie w runtime'ie będzie wyglądała nasza aplikacja, bez potrzeby jej rekompilacji.

Hawkeye pozwala również poruszać się w hierarchii kontrolek tworzących interfejs i sprawdzić kto jest rodzicem interesującej nas kontrolki. Podaje też pełną nazwę klasy dla aktualnie wybranego obiektu. Przydaje się to, kiedy pracujemy z dużą, skomplikowaną aplikacją i chcemy dowiedzieć się gdzie u licha znajduje się kod obsługujący aktualnie widoczną kontrolkę. Przykład z życia. Ostatnio kumpel potrzebował znaleźć kod odpowiedzialny za pewną część UI. W tym celu chciał wyświetlić designera dla interesującego go okna i na tej podstawie dojść do kontrolki. Niestety Visual Studio odmówiło posłuszeństwa i przy próbie wyświetlenia designera raportowało błąd. Hawkeye rozwiązał problem w kilka minut.

Uwaga! Na dzień dzisiejszy, na stronie programu znajdują się jego dwie wersje do pobrania. Jedna działa tylko z aplikacjami skompilowanymi na platformę .NET 4, a druga z aplikacjami skompilowanymi na starsze wersje platformy.

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.

05/03/2009

Xceed Chart

Home

Ostatnie kilka dni w pracy spędziłem na oprogramowywaniu komponentu do rysowania wykresów firmy Xceed w wersji dla Windows Forms (jest też wersja dla ASP.NET). Wcześniej nie korzystałem z produktów tej firmy i po tych kilku dniach kontaktu z jednym z nim muszę przyznać, że pracowało mi się bardzo przyjemnie.

API nie jest bardzo trudne, a jeśli sprawia trudności zawsze można sięgnąć do bogatej biblioteki interaktywnych przykładów. Komponent wręcz przytłacza swoimi możliwościami. Mam na myśli naprawdę przeogromną liczbę opcji konfiguracyjnych. Wybierać możemy pomiędzy wykresami dwu- i trój- wymiarowymi, liniowymi, słupkowymi, radarowymi... i prawie dowolnie dostosowywać ich wygląd do swoich potrzeb. Podczas pracy komponent zachowywał się bardzo stabilnie. Jak do tej pory nie zauważyłem również problemów z wydajnością. Nadmienię jeszcze, że pracowałem z starszą wersją komponentu (4.0). Zakładam, że wersja najnowsza (4.2) musi być co najmniej tak dobra jak stara.

Jeśli ktoś potrzebuje kontrolki do rysowania wykresów to naprawdę polecam. Największy mankament to niestety cena - kilkaset dolarów. Do użytku osobistego drogo ale dla firmy nie jest to już duży wydatek.