Jak poprawnie zbudować projekt w winform?

26

Jakiś czas temu zacząłem tworzyć aplikację WinForm, która w tym czasie była mała i nie zastanawiałem się, jak zorganizować projekt.

Od tamtej pory dodawałem dodatkowe funkcje zgodnie z potrzebami, a folder projektu jest coraz większy i teraz myślę, że nadszedł czas, aby w jakiś sposób ustrukturyzować projekt, ale nie jestem pewien, co jest właściwe, więc mam kilka pytań.

Jak prawidłowo zrestrukturyzować folder projektu?

W tej chwili myślę o czymś takim:

  • Utwórz folder dla formularzy
  • Utwórz folder dla klas narzędzi
  • Utwórz folder dla klas zawierających tylko dane

Jaka jest konwencja nazewnictwa podczas dodawania klas?

Czy powinienem również zmieniać nazwy klas, aby ich funkcjonalność można było zidentyfikować po prostu patrząc na ich nazwę? Na przykład zmiana nazwy wszystkich klas formularzy, aby ich nazwa kończyła się na Form . Czy też nie jest to konieczne, jeśli tworzone są dla nich specjalne foldery?

Co zrobić, aby nie cały kod formularza głównego znalazł się w Form1.cs

Innym problemem, jaki napotkałem, jest to, że ponieważ główna forma staje się coraz bardziej rozbudowana z każdą dodaną funkcją, plik kodu (Form1.cs) staje się naprawdę duży. Mam na przykład TabControl i każda karta ma kilka elementów sterujących, a cały kod skończył się w Form1.cs. Jak tego uniknąć?

Ponadto, czy znasz jakieś artykuły lub książki dotyczące tych problemów?

użytkownik850010
źródło

Odpowiedzi:

24

Wygląda na to, że wpadłeś w typowe pułapki, ale nie martw się, można je naprawić :)

Najpierw musisz spojrzeć na swoją aplikację nieco inaczej i zacząć dzielić ją na części. Możemy podzielić kawałki na dwa kierunki. Najpierw możemy oddzielić logikę sterującą (reguły biznesowe, kod dostępu do danych, kod praw użytkownika itp.) Od kodu interfejsu użytkownika. Po drugie możemy rozbić kod interfejsu użytkownika na części.

Więc najpierw zajmiemy się drugą częścią, dzieląc interfejs na części. Najłatwiejszym sposobem na to jest utworzenie pojedynczego formularza hosta, na którym tworzysz interfejs użytkownika za pomocą kontrolek użytkownika. Każda kontrola użytkownika będzie odpowiedzialna za region formularza. Wyobraź sobie, że Twoja aplikacja ma listę użytkowników, a kiedy klikniesz użytkownika, pole tekstowe poniżej wypełnia się szczegółami. Możesz mieć jedną kontrolę użytkownika zarządzającą wyświetlaniem listy użytkowników, a drugą kontrolę wyświetlania danych użytkownika.

Prawdziwa sztuczka polega na tym, jak zarządzasz komunikacją między kontrolkami. Nie chcesz, aby 30 kontrolek użytkownika w formularzu losowo zawierało odniesienia do siebie i wywoływało na nich metody.

Tworzysz interfejs dla każdej kontrolki. Interfejs zawiera operacje, które kontrolka zaakceptuje oraz wszelkie zdarzenia, które wywoła. Gdy myślisz o tej aplikacji, nie przejmujesz się zmianami wyboru listy, interesuje Cię fakt, że zmienił się nowy użytkownik.

Korzystając z naszej przykładowej aplikacji, pierwszy interfejs kontrolki obsługującej listę użytkowników zawierałby zdarzenie o nazwie UserChanged, które przekazuje obiekt użytkownika.

Jest to świetne, ponieważ teraz, gdy znudzi Ci się pole listy i chcesz sterować magicznym okiem 3D, po prostu koduj go do tego samego interfejsu i podłącz go :)

Ok, więc część druga, oddzielając logikę interfejsu użytkownika od logiki domeny. Cóż, jest to dobrze zużyta ścieżka i polecam przyjrzeć się wzorowi MVP tutaj. To jest naprawdę proste.

Każda kontrolka nazywa się teraz Widok (V w MVP) i już omówiliśmy większość tego, co jest potrzebne powyżej. W tym przypadku kontrola i interfejs dla niego.

Dodajemy tylko model i prezentera.

Model zawiera logikę, która zarządza stanem aplikacji. Znasz rzeczy, to trafiłoby do bazy danych, aby uzyskać użytkowników, napisać do bazy danych po dodaniu użytkownika i tak dalej. Chodzi o to, że możesz to wszystko przetestować w całkowitej izolacji od wszystkiego innego.

Prezenter jest nieco trudniejszy do wyjaśnienia. Jest to klasa, która znajduje się między modelem a widokiem. Jest tworzony przez widok, a widok przechodzi do prezentera za pomocą interfejsu, który omówiliśmy wcześniej.

Prezenter nie musi mieć własnego interfejsu, ale i tak lubię go tworzyć. Sprawia, że ​​to, co chcesz, aby prezenter zrobił wyraźnie.

Tak więc prezenter ujawniłby metody takie jak ListOfAllUsers, których widok użyłby do uzyskania swojej listy użytkowników, alternatywnie można umieścić metodę AddUser w widoku i wywołać ją z prezentera. Wolę ten drugi. W ten sposób prezenter może dodać użytkownika do pola listy, kiedy tylko chce.

Prezenter miałby również właściwości takie jak CanEditUser, które zwrócą wartość true, jeśli wybranego użytkownika można edytować. Widok wyświetli zapytanie, które za każdym razem musi wiedzieć. Możesz chcieć edytować te w kolorze czarnym i tylko do odczytu w kolorze szarym. Technicznie jest to decyzja dotycząca widoku, ponieważ jest on skoncentrowany na interfejsie użytkownika, czy to, czy użytkownik jest edytowalny, zależy przede wszystkim od Prezentera. Prezenter wie, ponieważ mówi do Modelu.

Podsumowując, użyj MVP. Microsoft udostępnia coś o nazwie SCSF (Smart Client Software Factory), które wykorzystuje MVP w sposób, który opisałem. Robi też wiele innych rzeczy. Jest dość złożony i nie podoba mi się sposób, w jaki wszystko robią, ale może to pomóc.

Ian
źródło
8

Osobiście wolę rozdzielić różne obszary zainteresowania między kilka zestawów, zamiast łączyć wszystko w jeden plik wykonywalny.

Zazwyczaj wolę zachować absolutną minimalną ilość kodu w punkcie wejścia aplikacji - Brak logiki biznesowej, brak kodu GUI i brak dostępu do danych (bazy danych / dostęp do plików / połączenia sieciowe / itp.); Zazwyczaj ograniczam kod punktu wejścia (tj. Plik wykonywalny) do czegoś w stylu

  • Tworzenie i inicjowanie różnych komponentów aplikacji ze wszystkich zależnych zespołów
  • Konfigurowanie komponentów innych firm, od których zależy cała aplikacja (np. Log4Net dla danych diagnostycznych)
  • również prawdopodobnie dołączę bit typu „złap wszystkie wyjątki i zapisz ślad śledzenia stosu” w głównej funkcji, która pomoże rejestrować okoliczności wszelkich nieprzewidzianych awarii krytycznych / krytycznych.

Jeśli chodzi o same komponenty aplikacji, zwykle dążę do co najmniej trzech w małej aplikacji

  • Warstwa dostępu do danych (połączenia z bazami danych, dostęp do plików itp.) - w zależności od złożoności dowolnych trwałych / przechowywanych danych używanych przez aplikację może być kilka takich zestawów - prawdopodobnie utworzyłbym osobny zestaw do obsługi bazy danych (prawdopodobnie nawet wiele zestawy, jeśli interakcja z bazą danych obejmowała coś złożonego - np. jeśli masz stucx ze źle zaprojektowaną bazą danych, być może będziesz musiał obsługiwać relacje DB w kodzie, dlatego warto napisać wiele modułów do wstawiania i pobierania)

  • Warstwa logiczna - główne „mięso” zawierające wszystkie decyzje i algorytmy, które sprawiają, że aplikacja działa. Te decyzje nie powinny absolutnie nic wiedzieć na temat GUI (kto mówi, że jest GUI?) I nie powinny wiedzieć absolutnie nic o bazie danych (Huh? Jest baza danych? Dlaczego nie plik?). Dobrze zaprojektowana warstwa logiczna może zostać „wyrwana” i upuszczona do innej aplikacji bez konieczności ponownej kompilacji. W skomplikowanej aplikacji może istnieć cała masa tych zestawów logicznych (ponieważ możesz po prostu wyrywać „kawałki” bez przeciągania wzdłuż reszty aplikacji)

  • Warstwa prezentacji (tj. GUI); W małej aplikacji może istnieć tylko jedna „główna forma” z kilkoma oknami dialogowymi, które wszystkie mogą przejść do jednego zestawu - w większej aplikacji mogą istnieć osobne zestawy dla całych funkcjonalnych części GUI. Klasy tutaj zrobią niewiele więcej niż tylko interakcja użytkownika - będzie to niewiele więcej niż powłoka z podstawową weryfikacją danych wejściowych, obsługą dowolnej animacji itp. Wszelkie zdarzenia / kliknięcia przycisków, które „coś zrobią” zostaną przekazane do przodu warstwa logiczna (więc moja warstwa prezentacji nie będzie zawierała żadnej logiki aplikacji, ale nie będzie też obciążać żadnego kodu GUI na warstwie logicznej - więc wszelkie paski postępu lub inne fantazyjne elementy będą również znajdować się w zestawie prezentacji / ies)

Moje główne uzasadnienie podziału warstwy prezentacji, logiki i danych na osobne zespoły jest następujące: uważam, że lepiej jest móc uruchamiać logikę głównej aplikacji bez bazy danych lub GUI.

Innymi słowy; jeśli chcę napisać inny plik wykonywalny, który zachowuje się dokładnie tak samo jak twoja aplikacja, ale używa interfejsu wiersza poleceń lub interfejsu WWW; i zamienia pamięć bazy danych na pamięć plików (lub inną bazę danych), wtedy mogę to zrobić bez potrzeby dotykania głównej logiki aplikacji - wszystko, co muszę zrobić, to napisać trochę narzędzia wiersza polecenia i inny model danych, a następnie „podłącz to wszystko razem” i jestem gotowy do pracy.

Być może myślisz: „Cóż, nigdy nie będę chciał tego robić, więc nie ma znaczenia, że ​​nie mogę zamienić tych rzeczy na lepsze” - prawda jest taka, że ​​jedną z cech charakterystycznych aplikacji modułowej jest zdolność do wyodrębnienia „fragmentów” (bez potrzeby ponownej kompilacji czegokolwiek) i ponownego wykorzystania tych fragmentów w innym miejscu. Aby napisać taki kod, na ogół zmusza cię do długiego i intensywnego myślenia o zasadach projektowania - musisz pomyśleć o napisaniu znacznie większej liczby interfejsów i dokładnie przemyśleć kompromisy różnych zasad SOLID (w tym samym jak w przypadku rozwoju opartego na zachowaniu lub TDD)

Czasami osiągnięcie tego oddzielenia od istniejącej monolitycznej bryły kodu jest nieco bolesne i wymaga dużo starannego refaktoryzacji - to w porządku, powinieneś być w stanie to robić stopniowo - możesz nawet osiągnąć punkt, w którym jest zbyt wiele zestawów i decydujesz wrócić w drugą stronę i ponownie zacząć owijać rzeczy w całość (zbyt daleko posunięty w przeciwnym kierunku może sprawić, że te zespoły same w sobie będą raczej bezużyteczne)

Ben Cottrell
źródło
4

Zgodnie ze strukturą folderów to, co sugerujesz, jest ogólnie OK. Konieczne może być dodanie folderów zasobów (czasami ludzie tworzą zasoby, tak aby każdy zestaw zasobów był pogrupowany w kodzie języka ISO w celu obsługi kilku języków), obrazy, skrypty bazy danych, preferencje użytkownika (jeśli nie są traktowane jako zasoby), czcionki , zewnętrzne biblioteki DLL, lokalne biblioteki DLL itp.

Jaka jest konwencja nazewnictwa podczas dodawania klas?

Oczywiście chcesz oddzielić każdą klasę poza formą. Poleciłbym również plik na klasę (chociaż MS nie robi tego w kodzie generowanym na przykład dla EF).

Wiele osób używa znaczących rzeczowników w liczbie mnogiej (np. Klienci). Niektórzy używają nazwy zbliżonej do liczby pojedynczej dla odpowiedniej tabeli bazy danych (jeśli używasz odwzorowania 1-1 między obiektami i tabelami).

Dla klas nazewnictwa istnieje wiele źródeł, na przykład spójrz na: Konwencje nazewnictwa .net i standardy programowania - najlepsze praktyki i / lub wytyczne dotyczące kodowania STOVF-C #

Co zrobić, aby nie cały kod formularza głównego znalazł się w Form1.cs

Kod pomocnika powinien przejść do jednej lub kilku klas pomocników. Inny kod, który jest bardzo powszechny w kontrolkach GUI, taki jak stosowanie preferencji użytkownika do siatki, może zostać usunięty z formularza i dodany do klas pomocniczych lub przez podklasowanie kontrolki i utworzenie tam niezbędnych metod.

Ze względu na naturę akcji MS Windows Forms, nie wiem nic o tym, co mogłoby pomóc ci usunąć kod z głównej formy bez dodawania dwuznaczności i wysiłku. Jednak MVVM może być wyborem (w przyszłych projektach), patrz na przykład: MVVM dla Windows Forms .

Bez szans
źródło
2

Rozważ MVP jako opcję, ponieważ pomoże ci zorganizować logikę prezentacji, która jest wszystkim w dużych aplikacjach biznesowych. W rzeczywistości logika aplikacji znajduje się głównie w bazie danych, więc rzadko trzeba faktycznie pisać warstwę biznesową w natywnym kodzie, pozostawiając jedynie potrzebę dobrze zorganizowanej funkcjonalności prezentacji.

Struktura podobna do MVP da zestaw prezenterów lub kontrolerów, jeśli wolisz, które będą koordynować ze sobą i nie będą mieszały się z interfejsem użytkownika lub kodem za innymi rzeczami. Możesz nawet używać różnych interfejsów użytkownika z tym samym kontrolerem.

Wreszcie, proste porządkowanie zasobów, oddzielanie komponentów i określanie zależności za pomocą IoC i DI, wraz z podejściem MVP zapewni ci klucze do budowy systemu, który pozwoli uniknąć typowych błędów i złożoności, zostanie dostarczony na czas i będzie otwarty na zmiany.

Panos Roditakis
źródło
1

Struktura projektu zależy całkowicie od Projektu i jego wielkości, jednak można dodać kilka folderów np

  • Wspólne (zawierające klasy, np. Narzędzia)
  • DataAccess (klasy związane z dostępem do danych za pomocą sql lub dowolnego innego serwera bazy danych, z którego korzystasz)
  • Style (jeśli masz jakieś pliki CSS w swoim projekcie)
  • Zasoby (np. Obrazy, pliki zasobów)
  • WorkFlow (klasy związane z przepływem pracy, jeśli takie istnieją)

Nie musisz umieszczać formularzy w jakimkolwiek folderze, wystarczy odpowiednio zmienić nazwę formularzy. Mam na myśli zdrowy rozsądek, że nikt nie wie, jakie imię powinno być twoimi formami lepiej niż ty sam.

Konwencja nazewnictwa przypomina, że ​​jeśli klasa drukuje komunikat „Hello World”, to nazwa klasy powinna być powiązana z zadaniem, a odpowiednia nazwa klasy powinna być HelloWorld.cs.

możesz także tworzyć regiony np

#region Hello a potem endregionna końcu.

Możesz tworzyć klasy dla kart, jestem pewien, że potrafisz, a ostatnią rzeczą jest ponowne użycie kodu tam, gdzie to możliwe, najlepszą praktyką jest tworzenie metod i używanie ich ponownie w razie potrzeby.

Książki? erm.

Żadna książka nie mówi ci o strukturze projektów, ponieważ każdy projekt jest inny, uczysz się tego typu przez doświadczenie.

Mam nadzieję, że to pomogło!

Muhammad Raja
źródło