niedziela, 4 czerwca 2017

Podsumowanie konkursu.

Konkurs "Daj się poznać", edycja 2017 został oficjalnie zakończony. Pragnę pogratulować tym samym wszystkim uczestnikom, a tym którym udało się zakwalifikować dalej, życzyć powodzenia w fazie finałowej.

Jako, że spełniłem wszystkie wymagania, udało mi się dostać do grona finalistów. Oddałem również głosy na projekty, które uważałem za interesujące. Aczkolwiek, mój wybór chciałbym pozostawić w tajemnicy. Widziałem sporo interesujących projektów i blogów, więc ograniczenie wyboru do trzech pozycji nie było łatwym zadaniem. Śledząc inne blogi dowiedziałem się kilku interesujących rzeczy. Być może ktoś dowiedział się czegoś wartego poznania z mojego bloga.

Na koniec, małe podsumowanie:

  • Cały projekt rozwijałem przez 11 tygodni.
  • Podczas tego czasu zrobiłem 45 commit'ów.
  • Utworzyłem 8302 linie kodu.
  • Usunąłem 3223 linie kodu.

Powyższe statystyki nieco przekłamują wielkość aplikacji, ponieważ obejmują wszystkie utworzone linie kodu, które zostały commitowan'e oraz wszystkie które zostały usunięte. Aczkolwiek różnica w ilości linii dodanych i usuniętych wynosi 5079, co nie odzwierciedla prawidłowej ilości 4285 linii kodu (wraz z pustymi liniami, bez pustych linii - 3522). Szybkie poszukiwania z użyciem google nie dały mi odpowiedzi na to, w jaki sposób liczone są commit'owane linie na github'ie.

Statystyki dotyczące ilości linii kodu.

Name: statistics_gui.py              | Lines: 895
Name: markers_gui.py                 | Lines: 532
Name: run.py                         | Lines: 345
Name: main_gui.py                    | Lines: 317
Name: texts.py                       | Lines: 280
Name: options_gui.py                 | Lines: 213
Name: statistics.py                  | Lines: 135
Name: png_export_gui.py              | Lines: 92
Name: export_canvas_utilities.py     | Lines: 90
Name: jpg_export_gui.py              | Lines: 88
Name: tif_export_gui.py              | Lines: 81
Name: samples.py                     | Lines: 67
Name: widgets_geometries.py          | Lines: 61
Name: about_gui.py                   | Lines: 58
Name: shapes.py                      | Lines: 56
Name: settings_utilities.py          | Lines: 54
Name: export_statistics.py           | Lines: 32
Name: manage_settings.py             | Lines: 31
Name: screen_utilities.py            | Lines: 28
Name: statuses.py                    | Lines: 26
Name: marker.py                      | Lines: 22
Name: marker_utilities.py            | Lines: 19

TOTAL: 3522

Wszystkie pliki zostały automatycznie przeskanowane, a linie w plikach policzone z zastosowaniem prostego skryptu, z wyłączeniem, pustych linii, ale z uwzględnieniem docstrings oraz komentarzy.

niedziela, 28 maja 2017

Kolejny błąd odnaleziony.

Przeznaczyłem trochę czasu na testowanie BioCounter'a poprzez jego użytkowanie. To znaczy, wczytywałem zdjęcia, w różnym formacie i zaznaczałem na nich różne elementy. Przy czym moim celem nie było celowe zepsucie działania aplikacji, ale sprawdzenie, czy jakiś błąd nie wyjdzie podczas normalnego użytkowania.

Znalazłem w ten sposób jeden błąd. Okazało się, że aplikacja nie trzymała popranie informacji dotyczącej rozszerzenia wczytanego pliku. Po wczytaniu np. pliku w formacie "jpg", aplikacja trzymała informacje na temat tego rozszerzenia. Jeśli użytkownik oznaczył interesujące elementy na zdjęciu, a następnie zdecydował się na eksport takiego zdjęcia, wszystko przebiegało zgodnie z założeniami i program poprawnie sugerował rozszerzenie pliku jako "jpg". Problem pojawiał się w momencie kiedy po otwarciu pierwszego zdjęcia, wybierano otwieranie kolejnego, ale proces ten anulowano. W tym momencie program gubił informacje o rozszerzeniu obrabianego pliku, a podczas próby eksportu tego pliku, brakowało domyślnego rozszerzenia w oknie dialogowym.

Rozwiązanie tego problemu okazało się bardzo proste i polegało na zmodyfikowaniu zmiennej odpowiedzialnej, za przechowywanie informacji dotyczącej rozszerzenia wczytanego pliku graficznego.

czwartek, 25 maja 2017

Czyszczenie kodu.

Czystość pisanego kodu jest ważna, ponieważ poprawia czytelność i ułatwia pisanie samej aplikacji. Jednak w tym przypadku chodzi o usuwanie zbędnych elementów programu, które były potrzebne do debugowania jego kodu. Gdyż nie powinny być one widoczne dla użytkownika.

Oczywiście elementy te nie muszą być usunięte na stałe. Byłoby to nawet niewskazane, ponieważ za każdym razem kiedy zaistniała by potrzeba sprawdzenia działania jakiegoś stałego elementu aplikacji, kod odpowiedzialny za debugowanie trzeba byłoby na nowo dodawać. Wystarczy je sprytnie zdezaktywować dodając zmienną "debug" oraz stosując instrukcje warunkowe w odpowiednich, potrzebnych miejscach w programie. Jest to bardo wygodne, gdyż w łatwy sposób pozwala na włączanie i wyłączanie całych sekcji kodu.

Zdaję sobie sprawę, że do testowanie przeznaczone są testy jednostkowe. Jednak nie wiem, czy za pomocą takich testów można by przetestować wszystkie elementy implementowane z użyciem Tkinter. Dotychczas używałem testów jednostkowych do sprawdzania funkcjonowania klas, czy też modułów i do tego nadawały się wyśmienicie. Jednak nie wiem, czy można przeprowadzić takie testy w połączeniu z event'ami Tkinter.

Na moje potrzeby wystarczające okazało się wypisywania potrzebnych wartości w konsoli, ponieważ interesowało mnie, czy coś ma daną, spodziewaną wartość. Metoda ta, bardzo szybko osiąga swoje limity i nie nadaje się do testowania szerokiego zakresu wartości jednej zmiennej. Jednak dla moich potrzeb okazała się szybka i efektywna, co było wystarczającym powodem na jej zastosowanie.

niedziela, 21 maja 2017

Kontrola nad aplikacją.

Użytkownik powinien posiadać swobodę pracy z aplikacją, ale jaka kontrola zostanie mu dana, zależy jedynie od developera. Oczywiście wszystko powinno być wyważone i wskazane jest, aby zachować złoty środek. Aczkolwiek należy pamiętać, że obecność pewnych funkcji wiąże się z pewnymi oczekiwaniami dotyczącymi funkcji towarzyszących.

Dlatego kiedy dodałem do programu możliwość eksportowania obrazów z naniesionymi markerami, poszedłem o krok dalej i zaimplementowałem możliwość regulowania jakości eksportowanego obrazu. Dyskusyjne jest, czy było to potrzebne, ponieważ normą są dyski o kilkuset gigabajtowej pojemności, więc kilkaset kilobajtów wydaje się nie robić problemu. Jednak funkcja pozwalająca na zmianę jakości zapisywanego obrazu, jest nieodzownie związana z możliwością eksportowania samego obrazu. Te dwie funkcje w programach graficznych są połączone z sobą, a obecność jednej, bez dodania drugie powoduje, że produkt może sprawiać wrażenie niekompletnego.

Oczywiście na popularność programu, czyli to czy odniesie sukces, czy też nie, składa się wiele innych czynników. Jednak to jak dany program jest odbierany przez użytkownika stanowi jeden z wyznaczników, który jest podwaliną tego sukcesu. Obecność pewnych funkcji programu może mieć charakter minimalistyczny, ale ważnym czynnikiem jest to, jakie rozwiązania są swoiste dla implementacji danych rozwiązań. Dlatego też, dodając pewne funkcjonalności programu nie można ograniczać się do nich samych, ale łączyć je z funkcjonalnościami, których może oczekiwać sam użytkownik. Podnosi to znacznie jakość samego programu, ale sprawia również, że praca z nim staje się bardziej komfortowa.

czwartek, 18 maja 2017

Drobnostki poprawiające ogólne wrażenie.

W jednym ze wcześniejszych postów pisałem, że na to czy program jest odbierany jako dobry, czy kiepski mogą wpływać niewielkie niuanse. Oprócz tego w programie może znajdować się wiele elementów poprawiających ogólne wrażenie i dodających elementy nie związane z podstawową funkcjonalnością.

Jednym z takich elementów jest okno z informacjami o programie. Jest to w zasadzie szczegół, na który nie zwraca się prawie uwagi. Aczkolwiek sam pamiętam, że czasami sprawdzałem, co w takim oknie znajdę i byłem zaskoczony informacjami jakie tam odkryłem. W szczególności kiedy dowiadywałem się, że np. dany program stworzyła tylko jedna osoba. Oczywiście takie informacje jak autorstwo programu, można umieścić w samym kodzie aplikacji, ale utworzenie do tego celu specjalnego okna, pokazuje pewną dbałość o szczegóły.

Oprócz informacji o autorstwie w oknie takim mogą znajdować się informacje dotyczące wersji programu, łącznie z numerem buildu, odnośnik do strony internetowej autora, czy też informacje o użytych bibliotekach. Takie ono może być również dobrym miejscem na dodanie loga programu. Możliwości realizacji takiego pomysłu jest na prawdę wiele.

niedziela, 14 maja 2017

Dodatkowe okna, kolejnych słów kilka.

Postanowiłem zaimplementować możliwość zmiany jakości dla zapisywanego pliku "jpg". Wybór padła na zastosowanie klasy Toplevel() i zbudowanie odpowiedniego interfejsu graficznego w nowym oknie.

Konstruowanie okna przebiegło bez żadnych problemów. Jednak mimo przekazywania wszystkich koniecznych parametrów, do odpowiednich ustawień, eksportowane pliki zapisywały się z domyślną wartością kompresji, wynoszącą 95. Okazało się, że nowe okno jest tworzone, ale cały program nie czeka na wprowadzenie zmian, tylko kontynuuje pracę w tle. Dlatego też zmiana ustawień nie maiła, żadnego wpływu na jakość eksportowanego pliku. Wprowadzone zmiany byłyby zastosowane dopiero do kolejnego eksportowanego pliku.

W celu pauzowania pracy głównego programu, trzeba wywołać metodę wait_window() na odpowiednim elemencie, co jest zależne od przyjętej implementacji tej metody. Oprócz pauzowania dobrym rozwiązaniem jest wcześniejsze wykonanie metody transient() na instancji klasy Toplevel() i przekazanie do tej metody głównego elementu programu. Spowoduje to związanie nowego okna z głównym oknem programu, dzięki czemu nowe okno nie pokaże się jako dodatkowa ikona na pasku zadań w Windows.

wtorek, 9 maja 2017

Dodatkowe okna w Tkinter.

Cała aplikacja Tkinter działa w pętli określonej za pomocą metody mainloop(), a wszystkie procesy zachodzą na jednym wątku. Aczkolwiek nie znam Tkinter tak dobrze, aby móc stwierdzić, czy wykorzystywany jest Threading, czy też nie. W związku z tym, kiedy trzeba utworzyć dodatkowe okno programu, nie można po prostu utworzyć kolejnej instancji Tk() i wywołać na niej kolejnej metody mainloop(). Nie wiem, jak wyglądało by działanie Tkinter z utworzeniem dwóch osobnych okien, na dwóch osobnych procesach, ale sama koncepcja nie wydaję się być za dobra.

Problem wielu okien można w łatwy sposób obejść dodając do listy kolejne instancje widget'ów, z danego segmentu GUI i w razie potrzeby niszczyć te obiekty, poprzez wywołanie na nich metody destroy(). W miejsce usuniętych elementów można utworzyć nowe, wchodzące w skład wywołanej części interfejsu.

list_of_widgets = [self.a_button_1, self.a_button_2]
for element in list_of_widgets:
    element.destroy()

Rozwiązanie to działa bardzo dobrze i sam stosowałem je wielokrotnie, a jedynym minusem jest konieczność tworzenia list widget'ów. Szybkość tworzenia nowych elementów GUI również nie stanowi problemu i jest niezauważalna dla użytkownika.

Jeśli jednak zajdzie sytuacja, kiedy konieczne jest utworzenie nowego okna, to w tym celu należy użyć klasy Toplevel(). Oferuje ona szereg standardowych opcji konfiguracji, takich jak przy tworzeniu głównego okna. Największą różnicą jest to, że na instancji klasy Toplevel() nie wywołuje się metody mainloop(). Przekazywanie zmiennych między obydwoma oknami również zachodzi bezproblemowo.

Użycie jednej, albo drugiej metody zależy tylko i wyłącznie od przyjętego projektu interfejsu. Nic nie stoi na przeszkodzie, aby połączyć obydwie metody i stosować je zamiennie, w odpowiednich do tego miejscach.