piątek, 31 marca 2017

Rollback'a nie było, wiedzy przybyło.

Okazało się, że moje założenia dotyczące konieczności przeprowadzenia rollback'a, były niepotrzebne. Testowałem różne możliwe rozwiązania i nie podobało mi się, że spora część mojej pracy zostanie zmarnowana, przez to, że nie uwzględniłem przekazywania atrybutów w Tkinter.

Wygrało najprostsze rozwiązanie - tworzenie referencji.

Zastosowałem również najprostszą opcję, czyli użycie listy. Nie sprawdziłem, czy dało by radę zrobić to ze słownikiem. Aczkolwiek ze względu na zastosowanie słowników w przekazywaniu wielu elementów jako key word arguments w "**kwargs" nie sądzę, aby to było możliwe. Nie natrafiłem również na żadną wzmiankę poruszającą ten temat.

Jedyny mankament związany z wykorzystaniem list w Tkinter jest bug związany z kopiowaniem listy. Trafiłem na niego pisząc prostą grę typu "snake" wykorzystując właśnie Tkinter. Uznałem to za pewnego rodzaju eksperyment i jednocześnie wyzwanie, gdyż gdzieś na "stacku" przeczytałem, że się nie da. Efektem ubocznym tego projektu, było znalezienie bug'a związanego z kopiowaniem listy.

Otóż, w Python jeśli kopiuje się listę nazwaną "a" do listy o nazwie "b" nie wystarczy postawić zwykłego znaku równości:

a = [1, 2, 3, 4, 5]
b = a

Kod ten nie spowoduje utworzenia kopii listy "a", zmienna "b" będzie jedynie wskazywała (będzie referencją) do miejsca w pamięci gdzie znajduje się lista "a".  Aby skopiować listę, trzeba użyć:

b = a[:]

Spowoduje to utworzenie w pamięci kopii listy "a" i przypisanie jej do zmiennej "b". Błąd, który znalazłem powodował, że ten mechanizm nie działał poprawnie. Do przeprowadzenia kopiowania listy było konieczne użycie modułu "deepcopy".

poniedziałek, 27 marca 2017

Commit przed rollback.

Dowiedziałem się dlaczego programy w Python korzystające z Tkinter pisane są w jednej głównej, dużej klasie. Otóż przekazywanie danych między wieloma klasami jest problematyczne. Metoda wywoływana w czasie naciśnięcia przycisku, obsługująca dany "event", nie jest w stanie zwrócić wartości z użyciem "return". To znaczy teoretycznie dana wartość zostanie zwrócona, ale nie do klasy znajdującej się wyżej w hierarchii, tylko do "mainloop" Tkinter. Przy czym nie można jej w żaden sposób przechwycić.

Istnieje kilka możliwości w jaki sposób można to naprawić. Najprostszym rozwiązaniem wydaje się przypisanie głównych instancji klas, których używam do przechowywania najważniejszych informacji, do global namespace. Jednak takie rozwiązanie jest odradzane, że względu na wolniejszy dostęp do tych danych oraz jest postrzegane jako coś co zrobiła by osoba początkująca. Utworzona lista zawierająca markery będzie przeszukiwana, dlatego z powodu wolniejszego dostępu do tych danych takie rozwiązanie odpada. Kolejną możliwością jest utworzenie serwera i wymiana danych za jego pomocą, jednak takiego rozwiązania nigdy nie stosowałem, nigdy również nie interesowałem się jak takie rozwiązanie zaimplementować w Python. Chciałbym uniknąć takiej implementacji na siłę, a drugą sprawą jest, że nie mam pojęcia jak wyglądała by stabilność takiego rozwiązania. Innym sposobem na obejście tego problemu jest zapisywanie i odczytywanie istotnych informacji z pliku. Jednak takie rozwiązanie również nie wygląda zbyt profesjonalnie.

Nie wiem natomiast czy możliwe jest przekazywanie referencji do instancji klasy jako argumentu w Python. Pobieżne przejrzenie informacji dostępnych na "stacku" pokazuje, że można to zrobić jedynie z zmiennymi (mutable) typami, jako przykład podana jest lista. Nie mam pojęcia czy przekazanie instancji w liście załatwi sprawę, ale takie rozwiązanie nie wydaje mi się zbyt eleganckim.

Stąd powrót do struktury programu opartej o jedną dużą główną klasę jest najprostszym podejściem, mimo że kod stanie się mniej czytelny. Kiedy podstawowe założenia projektu zostaną zrealizowane, będę miał czas na eksperymenty.

niedziela, 26 marca 2017

Wczytywanie zdjęć w Tkinter.

Założeniem, jakie przyjąłem przed rozpoczęciem tego projektu, było użycie jak najmniejszej ilości zewnętrznych pakietów (bibliotek) Pythona. Wiedziałem, że wykorzystanie biblioteki Pillow będzie w tym projekcie koniecznością. Tkinter ma jedną bardzo istotną wadę, pozwala jedynie na bezpośrednie wczytywanie obrazów w formatach GIF, PGM oraz PPM. Ciężko wymagać od użytkownika, aby za każdym razem konwertował zdjęcia do mało popularnych formatów, wyłącznie w celu pracy z jednym programem.

Dlatego z pomocą przychodzi Pillow. Biblioteka ta pozwala na wczytywanie ponad 30 różnych formatów graficznych plików. Posiada również wbudowaną klasę, pozwalającą na konwertowanie tych formatów, do postaci rozpoznawalnej przez Tkinter. Wystarczy do tego tylko jedna linia kodu.

loaded_image = ImageTk.PhotoImage(Image.open('full_path_to_the_image'))

Oprócz tej niezbędnej do tego projektu funkcji Pillow jest bardzo rozbudowaną biblioteką graficzną, posiadającą szereg bardzo różnych funkcji. Jest też bardzo popularny, jeśli więc dostarczona przez twórców dokumentacja okaże się niewystarczająca, z dużym prawdopodobieństwem rozwiązanie problemu odnajdzie się na "stacku".

Oczywiście istnieje szereg innych bibliotek graficznych, posiadających podobne funkcje oraz dużo więcej. Aczkolwiek moja decyzja padła na Pillow z tego względu, że już z tą biblioteką pracowałem, co bardzo ułatwia mi zadanie oraz wydaje mi się, że posiada ona wszystko to czego potrzebuję. Oczywiście jeśli chciałbym wprowadzić funkcjonalność typu: przechwytywanie obrazu z kamery mikroskopu. To biblioteka ta, będzie niewystarczająca. Planując taką funkcjonalność lepiej użyć np. openCV.

Wybierając bibliotekę graficzną, kierowałem się również łatwością jej instalacji, gdyż zdawałem sobie sprawę, że jeśli nie będę rozprowadzał programu w formie pakietu, to na użytkowniku będzie spoczywał wymóg zainstalowania odpowiednich bibliotek. Pillow można zainstalować przez pip, manager pakietów Pythona. Pobierze on automatycznie i zainstaluje odpowiednią wersję biblioteki, dla danej wersji Pythona, co jest bardzo wygodnym i odpornym na błędy rozwiązaniem.

sobota, 25 marca 2017

Kompartmentacja kodu.

Lubię czysty i przejrzysty kod. W takim kodzie można prościej wprowadzać zmiany oraz łatwiej rozeznać się przy powrocie do pracy, nad danym programem, po dłuższej przerwie.

Kod BioCounter'a stał się zbyt nieczytelny i nadeszła pora na poprawienie jego przejrzystości. Podzieliłem główną klasę na mniejsze fragmenty i porozmieszczałem je w odrębnych klasach i plikach. Podejście jedna klasa, w jednym pliku zostało doradzone mi przez bardziej doświadczonych programistów. Stosowanie takiego zabiegu sprzyja przejrzystości całego programu, a w momencie kiedy trzeba poszukać danej klasy, aby wprowadzić w niej zmiany, nie trzeba przeglądać całego pliku, czy też korzystać z opcji wyszukaj.

Zwrócono mi również uwagę na to, że główna klasa była zbyt długa. Podział głównej klasy na mniejsze klasy, spowodował jej spore skrócenie, część metod wywoływanych przez event'y na poszczególnych widget'ach, również znalazła się w odpowiednich dla danych elementów klasach.

Programując staram się również stosować do zasady SOLID, dla zwiększenie przejrzystości i czytelności kodu.

czwartek, 23 marca 2017

Ustawienia i zmienne.

Pracowałem nad możliwością zmiany klasyfikatorów w markerach i dostrzegłem kolejne ograniczenie Tkinter.

Wyświetlanie tekstu na przycisku jest bardzo proste. Wystarczy do klasy przekazać dany tekst, jako atrybut "text". Dany napis zostanie wtedy wyświetlony na przycisku, a wielkość (długość) przycisku zostanie dopasowana do długości przekazanego tekstu. W ten sposób nic nie zostanie ucięte. Schody zaczynają się, kiedy wielkość przycisku "width" zostanie odgórnie zdefiniowana.

Niestety Tkinter próbuje umieścić na przycisku cały przekazany tekst, a jeśli napis jest zbyt długi, wtedy widoczny jest tylko jego początek. Wygląda to brzydko, a dodatkowo cięcie napisu odbywa się nie pomiędzy danymi literami, tylko miejscu, w którym kończy się "pole" do wyświetlania tekstu, a granica ta może przebiegać przez środek litery. Czyli wygląda to jeszcze brzydziej. Prostym rozwiązaniem byłoby przycięcie ciągu znaków, poprzez zastosowanie "[:]", ale tekst na przyciskach nie jest typu string, tylko odrębną klasą Tkinter StringVar(), która takiej funkcji nie posiada.

Koniecznością okazało się utworzenie osobnego słownika, przechowującego zmienne odpowiedzialne, za przechowywanie tekstu, który znajdzie się na przyciskach. Dodatkowo tekst ten jest obrabiany do odpowiedniej długości. W ten sposób jeśli nowy klasyfikator przekroczy długość 3 znaków, zostanie wyświetlony na przycisku jako skrót opatrzony na końcu kropką, aczkolwiek podczas eksportu statystyk do plików zostanie wyeksportowana jego pełna nazwa. Dzięki temu prostemu zabiegowi GUI będzie wyglądało poprawnie i żadne informacje nie zostaną utracone.

niedziela, 19 marca 2017

Eventy i dezaktywacja przycisków.

Lubię pisać dobry kod, a przede wszystkim czytelny oraz oparty na jednorodnych rozwiązaniach. Nie mogłem zrozumieć dlaczego dezaktywację przycisków zaprojektowano w taki właśnie sposób i dlaczego zmiana atrybutu "state" na "disabled" nie powoduje zatrzymania wywoływania przypisywanej metody. Postanowiłem zbadać sprawę i znalazłem wytłumaczenie.

W Tkinter istnieją dwa sposoby na przypisanie metody do przycisku (a tak na prawdę do każdego widget'u). Można to zrobić za pomocą metody "bind" oraz za pomocą atrybutu "command" będącego w klasie samego widget'u. Czyli odpowiednio:

self.button = ttk.Button(root, text='Press to activate', 
    command=self.method_to_execute)
self.button.place(x=0, y=0)

lub

self.button = ttk.Button(root, text='Press to activate')
self.button.place(x=0, y=0)
self.button.bind('<Button-1>', self.method_to_execute)

Otóż zmiana atrybutu "state" na wartość "disabled" powoduje dezaktywację przycisku jeśli przypisano mu metodę bezpośrednio do atrybutu "command". Jeśli do przypisania metody użyje się ".bind" to mimo zmiany wyglądu przycisku na nieaktywny, po jego naciśnięciu przypisana metoda zostanie wykonana. Jedynym sposobem na zablokowanie wykonywania tej metody jest użycie metody "unbind" na instancji klasy. Co będzie wyglądało następująco:

self.button.unbind('<Button-1>')

Do metody "unbind" można jedynie przekazać event, który ma zostać odłączony, jeśli wraz z event'em zostanie przekazana metoda, kompilator wywali błąd.
Po zmianie statusu przycisku na aktywny, potrzebne jest ponowne użycie metody "bind", co wiąże się również z przekazywaniem metody, która ma zostać po naciśnięciu przycisku wykonana. Nie jest to zbyt dobre rozwiązanie, ponieważ ilość kodu rośnie, a wraz z nim możliwość powstania błędów. Jednak ma tą zaletę nad "command", że pozwala na przypisywanie różnych event'ów, "command" jest wykonywane po naciśnięciu lewego przycisku myszy. Chociaż nie mogę powiedzieć, że klikanie innym przyciskiem nie działa, ponieważ sam nigdy tego nie próbowałem :)

Niedogodności związane z koniecznością używania "bind" oraz "unbind" można w pewien sposób złagodzić, umieszczając instancje przycisków w słowniku. W ten sposób używając odpowiednich kluczy oraz instrukcji warunkowych można zaimplementować nie najgorsze rozwiązanie, jednak jeśli do przycisk korzysta z więcej niż jednego event'u, to rozwiązanie to zaczyna się komplikować. Dodatkowo jeśli wraz z event'em przekazujemy do metody pewne zmienne to złożoność dodatkowo rośnie. Przykładowa implementacja nie wygląda zbyt ładnie:

for k, v in self.buttons.items():
    if k == 'button_a':
        v.bind('<Button-1>', lambda event, var_1=(1, 2, 3, 5, 7),
            block=self.block_size:self.process_method(event, var_1, block))
        v.bind('<Button-2>', lambda event,
            text_index=1:self.show_tip(event, text_index))
        v.bind('<Button-3>', self.rest_process_settings)

Podany przykład dotyczy tylko jednego przycisku, a powstała konstrukcja już jest mało czytelna. Oczywiście można wypchnąć to do zewnętrznej klasy, w ten sposób nie będzie zmniejszało czytelności głównej klasy okna, ale nie zmienia to faktu, że ten mało czytelny kod trzeba będzie gdzieś umieścić.

Dość prostym rozwiązaniem jest użycie "command" oraz "bind" w jednej instancji przycisku, jednak nie jestem fanem takiego typu rozwiązań.

Jeśli chcesz dowiedzieć się więcej o eventach zajrzyj np. tu.

sobota, 18 marca 2017

Rozdzielczości i budowanie interfejsu.

Dynamiczne dostosowywanie rozdzielczości, w reakcji na przeciąganie za krawędź ramki okna, okazało się większym wyzwaniem niż przypuszczałem. Po kilku przeprowadzonych testach doszedłem do wniosku, że zastosowanie metody ".config" na poszczególnych elementach stanowczo odpada. Jest to oczywiście wykonalne, jednak wraz ze zrostem ilości elementów, ilość kodu wymaganego do przeprowadzania konfiguracji również rośnie i nie wygląda to ani ładnie, ani czysto. Natomiast testy z zastosowaniem relacji "parent-children" okazały się niepowodzeniem. Nie znalazłem podobnego rozwiązania w internecie, które mogło by mi pomóc w zrozumieniu na czym polega błąd mojej implementacji, mimo że znalazłem kilka zastosowań metody ".winfo_children".

Kolejną sprawą jest również to, że przeliczanie pozycji i ewentualnie wielkości elementów, musiało by się również odbywać dla nieutworzonych elementów okna. Nie byłoby to problemem, jeśli elementy te byłyby tworzone, ale nie umieszczane na formularzu. Jednak jakiekolwiek użycie metody ".destroy" mogłoby powodować błędy, jeśli przy kolejnym przeliczeniu wielkości i pozycji, program próbowałby odwołać się do nieistniejącego elementu. Przez to zastosowanie interfejsu posiadającego tylko jedno okno, byłoby bardzo trudne, o ile w ogóle niemożliwe do wykonania.

Zamiast tego rozwiązania, dałem użytkownikowi do wyboru kilka rozdzielczości, wielkości okna aplikacji, a dla łatwiejszej implementacji i zmniejszenia ilości zbędnego kodu, zmiany są wprowadzane po resecie programu. Przy czym wymaga to ingerencji użytkownika i musi być wykonane ręcznie. Wybrana rozdzielczość ekranu przechowywana jest w pliku "settings" wraz z innymi ustawieniami.

Zacząłem też budować elementy z zakładki "markers". Jak na razie przewiduję możliwość zliczana 8 próbek oraz możliwość nadania 4 odrębnych klasyfikatorów, ale będących wspólnymi dla wszystkich próbek. Wywodzi się to z prostego sposobu rozumowania. Zakładając przypadkowo, że jeśli ornitolog będzie chciał zliczyć ptaki z wczytanej fotografii, to określonymi próbkami będą dane gatunki ptaków, czyli przypuśćmy dla zobrazowania - "Sikora Bogatka", "Sikora Modra" i "Zięba". Natomiast obranymi przez owego ornitologa klasyfikatorami będą mogły być - "Samiec", "Samica", "Osobnik młodociany" i "Nie można określić", które są wspólne dla wszystkich próbek. Dzięki temu podziałowi przeliczanie statystyk oraz eksportowanie pliku z wynikami będzie proste.

Wyzwaniem będzie skonfigurowanie przycisku wyboru klasyfikatora, jako przełącznika, to znaczy tak aby po naciśnięciu pozostał on widoczny jako wciśnięty. Można tego dokonać poprzez zastosowanie metody ".state". Aczkolwiek zmiana ta tyczy się tylko wyglądu. Przycisk, któremu nadano status "disabled" pozostaje nadal aktywnym, a kliknięcie na niego spowoduje wywołanie przypisanej metody. W tym przypadku nie jest to problem, ponieważ ponowne naciśnięcie danego przycisku spowoduje jedynie ponowną aktywację wybranego już markera. Jednak jeżeli chciałbym zastosować tą metodę, do przycisku, który powiększa marker, a wielkość markera miałaby pewne ograniczenie w wielkości, to napotkam już pewien problem. Można to w prosty sposób obejść, ale owe obejście nie jest sposobem eleganckim, jest w założeniu proste dla jednego przycisku. Jednak w momencie wzrostu ilości takich przycisków i chęci sekwencyjnego ich aktywowania, kiedy np. Przycisk 2 staje się aktywny po naciśnięciu Przycisku 1, złożoność programu rośnie i trudniej określić, które przyciski w danym momencie mają pozostać aktywne. Być może uda mi się znaleźć rozwiązanie, które będzie bezpośrednio blokowało event przycisku, utworzenie nowej klasy z wykorzystaniem dziedziczenia i dodanie nowego atrybutu wydaje się być równie dobrym pomysłem.

wtorek, 14 marca 2017

Plik z ustawieniami i rozdzielczości ekranu.

Postanowiłem dodać możliwość zapisywania i odczytywania ustawień programu z pliku. Jak na razie znalazły się tam ustawienia związane z rozdzielczością ekranu i inne ustawienia związane z wyświetlaniem obrazu. Wszystkie wartości ustawień przechowywane są w pliku tekstowym, dzięki czemu użytkownik może je edytować ręcznie, ale również wczytywanie opcji i wartości do klasy nie sprawia żadnych problemów.

Dostrzegłem też spory błąd. Kodowałem rozmiary i położenie elementów formularza w sposób statyczny, to znaczy używałem zmiennych, ale były one wykorzystywane w nieodpowiedni sposób i nie dawały odpowiedniego efektu. Przerobię to w ten sposób, że rozmiary i położenie zagnieżdżonych elementów będzie obliczane na podstawie tychże obiektów nadrzędnych. W ten sposób wielokrotne zagnieżdżanie obiektów będzie miało większe znaczenie i będzie lepiej użyte, poprzez zastosowanie relacji "parent-child/children".

Testowałem również reakcję elementów na zmianę wielkości okna, poprzez przeciąganie jego krawędzi, znalazłem jedno rozwiązanie, ale nie było ono zbyt ładne. Wymagało każdorazowej konfiguracji elementów, poprzez zastosowanie metody "config". Mimo, że takie rozwiązanie jest proste i działa, to jego złożoność rośnie wraz z wzrastającą ilością elementów. Niestety w takim przypadku łatwo o błędy, a i sam kod nie będzie zbyt czytelny.

Utworzyłem również wirtualną maszynę na której zainstalowałem Linuxa. Nie wiem czy będę prowadził wsparcie dla tego systemu. Na razie służy to tylko i wyłącznie testom. Nigdy nie zagłębiałem się w różnicę między skryptami Pythona na Windowsa i Linuxa. Różnice są, a jedną z tych o której mi wiadomo jest obsługa rolki myszki. Ta funkcjonalność będzie ważna ponieważ przewiduję zaprogramować funkcję przybliżania i oddalania zdjęcia w oknie programu, z użyciem kombinacji klawisza "Ctrl" oraz kółka myszy. Takie rozwiązanie zastosowano w wielu programach graficznych i jeśli ktoś miał z nimi styczność, jest bardzo intuicyjne.

niedziela, 12 marca 2017

Okienka i ramki, dużo ramek.

Budowanie interface'u graficznego z użyciem Tkinter nie jest proste. Nie dostajemy żadnego wbudowanego narzędzia, za pomocą którego możemy tworzyć formularze za pomocą metody "drag and drop". Istnieją do tego zewnętrzne narzędzia, ale nie są one jakoś specjalnie rozwijane i w zasadzie nigdy z nich nie korzystałem.

Pisanie ręczne jest mozolne, a nanoszenie wszelkich poprawek dotyczących umiejscowienia elementów wymaga cierpliwości. Jednak jest to do zrobienia i robiłem to przy każdym interface'ie graficznym wykorzystującym metodę "place".

Dobrym nawykiem wydaje się grupowanie i zakotwiczanie elementów z zastosowaniem wielu ramek. Pozwala to na lepszą i wygodniejszą kontrolę nad obiektami, jeśli trzeba przesunąć większą ich ilość. Generalnie kompartmentacja tych elementów, podobnie jak kompartmentacja kodu jest dobrą praktyką. Interesującą sztuczką jest zastosowanie atrybutu "ttk.Frame" o nazwie "relief" i ustawieniu jego wartości na "GROOVE" (bez cudzysłowu). Granice ramki stają się widoczne, dzięki powstałemu na krawędziach zagłębieniu. Trzeba pamiętać również, że obiekty stylizowane "ttk" (prawie dosłowne tłumaczenie) mimo, że mogą należeć do tej samej rodziny co bazowe elementy, np. Button, Frame, czy Label, mogą posiadać inne opcje. To znaczy mogłoby wydawać się, że "ttk.Frame" utworzono dziedzicząc klasę "Frame", ale niestety nie działa to w ten sposób. Podany przykład jest celowy, ponieważ obiekt "Frame" generalnie służy tylko do zakotwiczania i grupowania elementów, a mimo tego istnieją różnice miedzy "Frame" i "ttk.Frame". W zasadzie nie wiem na jakich regułach oparto tworzenie obiektów "ttk", nigdy się w ten temat nie zagłębiałem. Dużą zaletą "ttk" jest możliwość tworzenia stylów, które możemy przypisywać do różnych elementów formularza, ponadto "ttk" rozpoznaje domyślny styl systemu i dostosowuje do niego wygląd prezentowanego okna programu.

Konsola języka Python "IDLE" została zbudowana z zastosowaniem Tkinter i obiektów stylizowanych "ttk".

sobota, 11 marca 2017

Nawet w krótkim kodzie mogą czyhać błędy.

Ledwie co zacząłem kodować, a już zdążyłem odnaleźć pierwsze problemy.

Jednak, aby sprawę wyjaśnić muszę naświetlić jak wygląda tworzenie okienek i ich elementów w Tkinter. Otóż istnieją trzy sposoby:
- pack - polegający na stosowaniu, w sumie sam nie wiem czego, gdyż ten sposób jest tak bardzo prymintywny, że nigdy go nie stosowałem,
- grid - polegający na układaniu elementów z zastosowaniem siatki oraz z pomocą kierunków (E, W, N, S) oraz przyciągania elementów do tychże kierunków,
- place - polegający na zastosowaniu współrzędnych "x" oraz "y" w pikselach i na podstawie tych współrzędnych umieszczanie elementów w okienku.

O ile przy zastosowaniu metody "grid" nie ma problemu z minimalnymi zmianami wielkości okna, gdyż wszystko się do siebie relatywnie przyciąga, oraz przyciąga do kierunków zakotwiczenia. To przy nieodpowiednim zakotwiczaniu obiektów, ich nieodpowiednie rozjeżdżanie się jest bardzo prawdopodobne. Czyli, najprościej to ujmując, zmiana wielkości okna nie lubi się z tą metodą i generalnie nie wygląda to ładnie. Zastosowanie metody "place" pozwala na ustawianie elementów okienek z dokładnością do jednego piksela, ale o ile koordynaty nie są opisane wzorami (określającymi relatywne położenie względem siebie), to zmiana wielkości okna wiąże się z tym, że wszystkie obiekty pozostają zakotwiczone w swoich stałych miejscach.  Optymalnym rozwiązaniem byłoby wymieszanie rozwiązań z metody "grid" oraz metody "place", ale niestety Tkinter nie pozwala na takie rozwiązania.

Problem polegał na tym, że program pobierał z systemu informację o ustawionej rozdzielczości ekranu i na jej podstawie określał wielkość okna (odejmując kilka pikseli na ramkę oraz pasek start). Sęk w tym, że na systemach posiadających wiele aktywnych monitorów, okno programu było rozciągnięte na wiele monitorów i nie można było jego rozmiaru zmienić, ponieważ taką możliwość zablokowałem poprzez użycie:

root = Tk()
root.resizable(width=0 height=0)


Dlatego też stworzyłem prostą klasę zawierającą statyczną metodę, która sprawdza rozdzielczość ekranu oraz jego proporcję i na tej podstawie dobiera rozdzielczość okienka programu. Mimo tego, że rozwiązanie jest prymitywne to powinno działać. Niestety w domu nie jestem w stanie tego sprawdzić, gdyż nie mam dodatkowego monitora do laptopa. W przypadku gdyby zastosowane rozwiązanie sprawiało problemy mogę użyć biblioteki "ctypes" pozwalającej na dostęp do zasobów i informacji z systemu operacyjnego, wadą tego rozwiązania jest to, że jest ono typowo windowsowe. Mimo, że na razie rozwijam ten program typowo na Windowsa, to w przyszłości chciałbym go również przetestować na linuxie (na wirtualnej maszynie z zainstalowanym linuxem), a zastosowanie typowej dla Windowsa biblioteki pozbawi mnie tej możliwości.

piątek, 10 marca 2017

Konkurs i wybór tematu.

Postanowiłem wystartować w konkursie (link), a w zasadzie zostałem do tego namówiony.

Na potrzeby tego konkursu postanowiłem stworzyć prostą aplikację do liczenia i oznaczania elementów na potrzeby nauk biologicznych. Założenie jest takie, że ładujemy zdjęcie do programu, zostaje ono wyświetlone w oknie jak w każdym programie graficznym i następnie po utworzeniu prostej kategorii możemy na to zdjęcie nanosić znaczniki graficzne, które są następnie automatycznie zliczane. Po zliczeniu wszystkich potrzebnych elementów można to zdjęcie wraz ze znacznikami zapisać, a otrzymane statystyki wyeksportować do pliku, w celu dalszego obrobienia danych w przeznaczonym do tego programie.

Jako język programowania wybrałem - Python. Zdaję sobie sprawę, że istnieją bardziej odpowiednie języki programowania do zbudowania offline'owej aplikacji na Windowsa, ale nie znaczy to, że nie można tego dokonać w Pythonie.  Zdecydowałem się również na użycie wbudowanego modułu Tkinter, gdyż posiada on całą podstawową funkcjonalność "okienek" i nie wymaga od niedoświadczonego użytkownika instalowania, żadnych zewnętrznych modułów. Jest często również postrzegany przez użytkowników jako przestarzały oraz bezużyteczny, aczkolwiek jest to dalekie od prawdy.

Zdaję sobie sprawę, że istnieje wiele bibliotek czy framework'ów, które są bardziej rozbudowane i być może nadawały by się lepiej: pyQt, wxPython, kivy oraz pygame, ale na potrzeby założonej funkcjonalności Tkinter wydaje się wystarczający.