Czy istnieje jakaś systematyczna strategia projektowania i wdrażania GUI?

18

Korzystam z programu Visual Studio, aby utworzyć aplikację GUI w języku C #. Przybornik służy jako sprytna paleta komponentów, która pozwala mi łatwo przeciągać i upuszczać przyciski i inne elementy (dla jasności powiem przycisk, gdy mam na myśli „sterowanie”) na moim formularzu, co sprawia, że ​​formy statyczne są dość łatwe do wykonania. Mam jednak dwa problemy:

  • Tworzenie przycisków to przede wszystkim dużo pracy. Kiedy mam formularz, który nie jest statyczny (tj. Przyciski lub inne formanty są tworzone w czasie wykonywania zgodnie z tym, co robi użytkownik), nie mogę w ogóle korzystać z palety. Zamiast tego muszę utworzyć każdy przycisk ręcznie, wywołując konstruktor dowolną metodą, której używam, a następnie ręcznie inicjuję, określając wysokość przycisku, szerokość, pozycję, etykietę, moduł obsługi zdarzeń i tak dalej. Jest to niezwykle żmudne, ponieważ muszę zgadnąć wszystkie te parametry kosmetyczne, nie mogąc zobaczyć, jak będzie wyglądać forma, a także generuje wiele wierszy powtarzalnego kodu dla każdego przycisku.
  • Zmuszenie przycisków do zrobienia czegoś to również dużo pracy. Radzenie sobie ze zdarzeniami we w pełni funkcjonalnej aplikacji to ogromny ból. Jedyny sposób, w jaki wiem, jak to zrobić, to wybrać przycisk, przejść do karty zdarzeń we właściwościach, kliknąć OnClickzdarzenie, aby wygenerowało zdarzenie w Formkodzie, a następnie wypełnić treść zdarzenia. Ponieważ chcę oddzielić logikę i prezentację, wszystkie moje procedury obsługi zdarzeń to wywołania jednej linii do odpowiedniej funkcji logiki biznesowej. Ale użycie tego do wielu przycisków (na przykład, wyobraź sobie liczbę przycisków obecnych w aplikacji takiej jak MS Word) zanieczyszcza mój kod Formdziesiątkami metod obsługi zdarzeń i jest to trudne do utrzymania.

Z tego powodu każdy program GUI bardziej skomplikowany niż Hello World jest dla mnie bardzo niepraktyczny. Żeby było jasne, nie mam żadnych problemów ze złożonością programów, które piszę, które mają minimalny interfejs użytkownika - czuję, że jestem w stanie używać OOP z dość wysokim poziomem kompetencji do uporządkowania kodu logiki biznesowej. Ale podczas tworzenia GUI utknąłem. Wydaje się to tak nużące, że mam wrażenie, że odkrywam na nowo koło, a gdzieś tam jest książka wyjaśniająca, jak poprawnie wykonać GUI, której nie przeczytałam.

Czy coś brakuje? Czy też wszyscy programiści C # akceptują niekończące się listy powtarzalnych procedur obsługi zdarzeń i kodu tworzenia przycisku?


Jako (miejmy nadzieję pomocną) wskazówkę oczekuję, że dobra odpowiedź będzie mówić o:

  • Wykorzystanie technik OOP (takich jak wzór fabryczny) w celu uproszczenia powtarzalnego tworzenia przycisków
  • Łączenie wielu programów obsługi zdarzeń w jedną metodę, która sprawdza Sender, który przycisk go nazwał, i zachowuje się odpowiednio
  • XAML i używanie WPF zamiast Windows Forms

Oczywiście nie musisz wspominać o żadnym z nich. To moje najlepsze przypuszczenie, jakiej odpowiedzi szukam.

Superbest
źródło
Widzę, że tak. Fabryka byłaby dobrym wzorcem, ale widzę znacznie więcej wzorca Model-View-Controller z poleceniami podłączonymi do kontrolek zamiast zdarzeń bezpośrednio. WPF to zwykle MVVM, ale MVC jest całkiem możliwe. Obecnie mamy ogromne oprogramowanie w WPF wykorzystujące MVC w 90%. Wszystkie polecenia są przekazywane do kontrolera, który obsługuje wszystko
Franck
Czy nie możesz stworzyć formy dynamicznej ze wszystkimi możliwymi przyciskami za pomocą edytora GUI i po prostu sprawić, że rzeczy, których nie potrzebujesz, są dynamicznie niewidoczne? Brzmi jak znacznie mniej pracy.
Hans-Peter Störr,
@ hstoerr Ale ciężko byłoby uruchomić układ. Wiele przycisków zachodziłoby na siebie.
Superbest,

Odpowiedzi:

22

Istnieje kilka metodologii, które ewoluowały przez lata w celu rozwiązania tych problemów, o których wspomniałeś, i są to, jak się zgadzam, dwa główne problemy, które struktury interfejsu użytkownika musiały rozwiązać w ostatnich latach. Pochodzące z tła WPF, są one traktowane w następujący sposób:

Projekt deklaratywny, a nie konieczny

Opisując skrupulatnie pisanie kodu w celu tworzenia instancji formantów i ustawiania ich właściwości, opisujesz imperatywny model projektowania interfejsu użytkownika. Nawet z projektantem WinForms po prostu używasz opakowania tego modelu - otwórz plik Form1.Designer.cs i zobaczysz cały ten kod tam.

Z WPF i XAML - i podobnymi modelami w innych frameworkach, oczywiście począwszy od HTML - oczekuje się, że opiszesz swój układ i pozwolisz ramie na ciężkie wdrażanie. Korzystasz z inteligentniejszych elementów sterujących, takich jak panele (siatka lub WrapPanel itp.), Aby opisać relacje między elementami interfejsu użytkownika, ale oczekuje się, że nie ustawisz ich ręcznie. Elastyczne koncepcje, takie jak powtarzalne elementy sterujące - takie jak Repeater ASP.NET lub ItemsControl WPF - pomagają tworzyć dynamicznie skalowany interfejs użytkownika bez pisania powtarzającego się kodu, umożliwiając dynamicznie rosnącą liczbę jednostek danych dynamicznie reprezentowanych jako elementy sterujące.

Dane DataTemplates WPF pozwalają zdefiniować - ponownie deklaratywnie - małe samorodki dobroci interfejsu użytkownika i dopasować je do danych; na przykład lista obiektów danych klienta może być powiązana z ItemsControl, a inny szablon danych może zostać wywołany w zależności od tego, czy jest on zwykłym pracownikiem (użyj standardowego szablonu wiersza siatki z nazwą i adresem), czy też głównym klientem, podczas gdy inny szablon , wraz ze zdjęciem i łatwo dostępnymi przyciskami są wyświetlane. Ponownie, bez pisania kodu w określonym oknie, tylko inteligentniejsze elementy sterujące, które są świadome swojego kontekstu danych, a tym samym pozwalają ci po prostu powiązać je z danymi i pozwolić im wykonać odpowiednie operacje.

Wiązanie danych i rozdzielanie poleceń

Dotknięcie powiązania danych, powiązanie danych WPF (i ponownie również w innych ramach, takich jak AngularJS) pozwala określić zamiar, łącząc kontrolę (powiedzmy pole tekstowe) z jednostką danych (powiedzmy, nazwa klienta) i pozwól rama obsługuje hydraulikę. Logika jest obsługiwana podobnie. Zamiast ręcznie podłączać procedury obsługi zdarzeń związane z kodem do logiki biznesowej opartej na kontrolerze, używasz mechanizmu powiązania danych, aby powiązać zachowanie kontrolera (na przykład właściwość Command przycisku) z obiektem Command reprezentującym samorodek działania.

Umożliwia współdzielenie tego polecenia między oknami bez przepisywania procedur obsługi zdarzeń za każdym razem.

Wyższy poziom abstrakcji

Oba te rozwiązania dwóch problemów stanowią przejście na wyższy poziom abstrakcji niż oparty na zdarzeniach paradygmat formularzy Windows Forms, który słusznie uważasz za męczący.

Chodzi o to, że nie chcesz definiować w kodzie każdej właściwości, która ma formant, i każdego zachowania począwszy od kliknięcia przycisku i dalej. Chcesz, aby twoje kontrolki i podstawowa struktura działały dla Ciebie więcej i pozwoliły ci myśleć w bardziej abstrakcyjnych koncepcjach wiązania danych (które istnieje w WinForm, ale nie jest tak przydatne jak w WPF) i wzorcu poleceń do definiowania połączeń między interfejsem użytkownika i zachowanie, które nie wymaga schodzenia do metalu.

MVVM wzór jest podejście Microsoftu do tego paradygmatu. Sugeruję poczytać o tym.

To jest przybliżony przykład tego, jak ten model wyglądałby i jak zaoszczędziłby ci czasu i linii kodu. To się nie skompiluje, ale to pseudo-WPF. :)

Gdzieś w zasobach aplikacji definiujesz szablony danych:

<DataTemplate x:DataType="Customer">
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

<DataTemplate x:DataType="PrimeCustomer">
   <Image Source="GoldStar.png"/>
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

Teraz łączysz ekran główny z ViewModel, klasą, która ujawnia twoje dane. Powiedzmy, zbiór Klientów (po prostu a List<Customer>) i obiekt Command (znowu zwykła własność publiczna typu ICommand). Ten link umożliwia wiązanie:

public class CustomersnViewModel
{
     public List<Customer> Customers {get;}
     public ICommand RefreshCustomerListCommand {get;}  
}

i interfejs użytkownika:

<ListBox ItemsSource="{Binding Customers}"/>
<Button Command="{Binding RefreshCustomerListCommand}">Refresh</Button>

I to wszystko. Składnia ListBox pobierze listę klientów z ViewModel i spróbuje wyrenderować ich w interfejsie użytkownika. Ze względu na dwa zdefiniowane wcześniej DataTemplates zaimportuje odpowiedni szablon (oparty na DataType, zakładając, że PrimeCustomer dziedziczy po kliencie) i umieści go jako zawartość ListBox. Brak zapętlania, brak dynamicznego generowania kontroli za pomocą kodu.

Button ma również wcześniej istniejącą składnię, aby powiązać swoje zachowanie z implementacją ICommand, która prawdopodobnie wie, jak zaktualizować Customerswłaściwość - co zachęca strukturę powiązania danych do automatycznej aktualizacji interfejsu użytkownika.

Oczywiście podjąłem tutaj skróty, ale to jest sedno.

Avner Shahar-Kashtan
źródło
5

Najpierw polecam tę serię artykułów: http://codebetter.com/jeremymiller/2007/07/26/the-build-your-own-cab-series-table-t-contents/ - nie zajmuje się szczegółowymi problemami za pomocą kodu przycisku, ale zapewnia systematyczną strategię projektowania i wdrażania własnych ram aplikacji, zwłaszcza aplikacji GUI, pokazując, jak zastosować MVC, MVP, MVVM do typowej aplikacji Forms.

Odpowiadając na pytanie dotyczące „radzenia sobie ze zdarzeniami”: jeśli masz wiele formularzy z przyciskami działającymi w podobny sposób, prawdopodobnie opłaci się samodzielne wdrożenie przypisania procedury obsługi zdarzeń w „strukturze aplikacji”. Zamiast przypisywać zdarzenia kliknięcia za pomocą projektanta GUI, stwórz konwencję nazewnictwa, w jaki sposób należy nazwać moduł obsługi „kliknięcia” dla przycisku o określonej nazwie. Na przykład - dla przycisku MyNiftyButton_ClickHandlerdla przycisku MyNiftyButton. Wtedy nie jest tak naprawdę trudno napisać procedurę wielokrotnego użytku (wykorzystującą odbicie), która iteruje wszystkie przyciski formularza, sprawdź, czy formularz zawiera powiązany moduł obsługi kliknięć i automatycznie przypisaj moduł obsługi do zdarzenia. Zobacz tutaj przykład.

A jeśli chcesz użyć tylko jednego modułu obsługi zdarzeń dla kilku przycisków, będzie to również możliwe. Ponieważ każda procedura obsługi zdarzeń przekazuje nadawcę, a nadawca jest zawsze przyciskiem, który został naciśnięty, można wysłać kod biznesowy, korzystając z właściwości „Nazwa” każdego przycisku.

Do dynamicznego tworzenia przycisków (lub innych kontrolek): możesz tworzyć przyciski lub inne kontrolki z projektantem w nieużywanej formie tylko w celu utworzenia szablonu . Następnie używasz odbicia (patrz tutaj przykład ), aby sklonować formant szablonu i zmienić tylko takie właściwości, jak lokalizacja lub rozmiar. To prawdopodobnie będzie znacznie prostsze niż ręczne zainicjowanie dwóch lub trzech tuzinów właściwości w kodzie.

Napisałem to powyżej, mając na myśli Winforms (tak jak ty, jak sądzę), ale ogólne zasady powinny mieć zastosowanie do innych środowisk GUI, takich jak WPF o podobnych możliwościach.

Doktor Brown
źródło
Rzeczywiście można to zrobić w WPF i jest to o wiele łatwiejsze. Wystarczy wypełnić listę poleceń (wstępnie zakodowane określone funkcje) i ustawić źródło w oknie. Tworzysz ładny prosty szablon wizualny, a wszystko, co musisz zrobić, to wypełnić kolekcję żądanymi poleceniami, a świetne wiązanie WPF zajmie się resztą wizualnie.
Franck