W przeszłości korzystałem z dziedziczenia, aby umożliwić rozszerzenie formularzy Windows w mojej aplikacji. Gdyby wszystkie moje formularze miały wspólne elementy sterujące, kompozycję i funkcje, utworzyłbym formularz podstawowy implementujący wspólne elementy sterujące i funkcje, a następnie pozwalałem innym elementom sterującym na dziedziczenie z tego formularza podstawowego. Jednak natrafiłem na kilka problemów z tym projektem.
Kontrole mogą znajdować się tylko w jednym pojemniku na raz, więc wszelkie statyczne kontrole będą trudne. Na przykład: Załóżmy, że masz podstawową formę o nazwie BaseForm, która zawiera TreeView, który tworzysz chroniony i statyczny, aby wszystkie inne (pochodne) instancje tej klasy mogły modyfikować i wyświetlać ten sam TreeView. Nie działałoby to dla wielu klas dziedziczących z BaseForm, ponieważ TreeView może znajdować się tylko w jednym kontenerze na raz. Prawdopodobnie byłby w ostatniej zainicjowanej formie. Chociaż każda instancja może edytować kontrolkę, będzie wyświetlana tylko w jednym w danym momencie. Oczywiście istnieją obejścia, ale wszystkie są brzydkie. (Wydaje mi się, że to naprawdę zły projekt. Dlaczego wiele kontenerów nie może przechowywać wskaźników do tego samego obiektu? W każdym razie jest to, co to jest.)
Stan między formularzami, to znaczy stany przycisków, tekst etykiety itp., Muszę użyć zmiennych globalnych i zresetować stany na obciążeniu.
To nie jest tak naprawdę wspierane przez projektanta Visual Studio.
Czy jest lepszy, ale wciąż łatwy w utrzymaniu projekt? A może dziedziczenie formy jest nadal najlepszym podejściem?
Aktualizacja Przeszedłem od patrzenia na MVC na MVP na Wzorzec Obserwatora na Wzorzec Wydarzenia. Oto, o czym teraz myślę, proszę o krytykę:
Moja klasa BaseForm będzie zawierać tylko formanty i zdarzenia powiązane z tymi formantami. Wszystkie zdarzenia, które wymagają jakiejkolwiek logiki, aby je obsłużyć, zostaną natychmiast przekazane do klasy BaseFormPresenter. Ta klasa będzie obsługiwać dane z interfejsu użytkownika, wykonywać wszelkie operacje logiczne, a następnie aktualizować model BaseFormModel. Model ujawni zdarzenia, które będą wyzwalane po zmianach stanu, klasie Presenter, której będzie subskrybować (lub obserwować). Gdy Prezenter otrzyma powiadomienie o zdarzeniu, wykona dowolną logikę, a następnie Prezenter odpowiednio zmodyfikuje Widok.
W pamięci będzie tylko jedna klasa modeli, ale potencjalnie może istnieć wiele wystąpień BaseForm, a zatem BaseFormPresenter. To rozwiązałoby mój problem synchronizacji każdej instancji BaseForm z tym samym modelem danych.
Pytania:
Która warstwa powinna przechowywać takie rzeczy, jak ostatni naciśnięty przycisk, aby mógł być podświetlony dla użytkownika (jak w menu CSS) między formularzami?
Krytykuj ten projekt. Dzięki za pomoc!
źródło
Odpowiedzi:
Nie wiem, dlaczego potrzebujesz kontroli statycznej. Może wiesz coś, czego ja nie wiem. Użyłem wielu elementów dziedziczenia wizualnego, ale nigdy nie widziałem, aby konieczne były elementy statyczne. Jeśli masz wspólną kontrolę widoku drzewa, pozwól, aby każda instancja formularza miała własną instancję kontroli, i współużytkuj pojedyncze wystąpienie danych powiązanych z widokami drzewa.
Współdzielenie stanu kontroli (w przeciwieństwie do danych) między formularzami jest również nietypowym wymogiem. Czy na pewno FormB naprawdę musi wiedzieć o stanie przycisków na FormA? Rozważmy projekty MVP lub MVC. Pomyśl o każdej formie jako głupim „widoku”, który nic nie wie o innych widokach, a nawet o samej aplikacji. Nadzoruj każdy widok za pomocą inteligentnego prezentera / kontrolera. Jeśli ma to sens, pojedynczy prezenter może nadzorować kilka widoków. Skojarz obiekt stanu z każdym widokiem. Jeśli masz jakiś stan, który musi być współużytkowany między widokami, pozwól prezenterom na mediację (i rozważ powiązanie danych - patrz poniżej).
Uzgodnione, Visual Studio spowoduje bóle głowy. Rozważając dziedziczenie formy lub kontroli użytkownika, musisz dokładnie wyważyć korzyści z potencjalnym (i prawdopodobnym) kosztem zmagań z frustrującymi dziwactwami i ograniczeniami projektanta formy. Sugeruję ograniczenie dziedziczenia formy do minimum - używaj go tylko wtedy, gdy wypłata jest wysoka. Pamiętaj, że jako alternatywę dla podklas możesz stworzyć wspólną „podstawową” formę i po prostu utworzyć ją raz dla każdego potencjalnego „dziecka”, a następnie dostosować ją w locie. Ma to sens, gdy różnice między poszczególnymi wersjami formularza są niewielkie w porównaniu do wspólnych aspektów. (IOW: złożona forma podstawowa, tylko nieznacznie bardziej złożone formularze potomne)
Korzystaj z kontroli użytkowników, gdy pomaga to uniknąć znacznego powielania rozwoju interfejsu użytkownika. Rozważ dziedziczenie kontroli użytkownika, ale zastosuj te same uwagi, co w przypadku dziedziczenia formularzy.
Myślę, że najważniejszą radą, jaką mogę zaoferować, jest to, że jeśli obecnie nie stosujesz jakiejś formy wzorca widoku / kontrolera, gorąco zachęcam do tego. Zmusza Cię do nauki i doceniać zalety luźnego sprzęgania i rozdzielania warstw.
odpowiedź na twoją aktualizację
Która warstwa powinna przechowywać takie rzeczy, jak ostatni naciśnięty przycisk, dzięki czemu mogę wyróżnić go dla użytkownika ...
Możesz dzielić stan pomiędzy widokami tak samo, jak byś dzielił stan pomiędzy prezenterem i jego widokiem. Utwórz specjalną klasę SharedViewState. Dla uproszczenia możesz uczynić go singletonem lub możesz utworzyć go w głównym prezenterie i stamtąd przekazać go do wszystkich widoków (przez ich prezenterów). Gdy stan jest powiązany z kontrolkami, w miarę możliwości używaj powiązania danych. Większość
Control
właściwości może być powiązana z danymi. Na przykład właściwość BackColor przycisku może być powiązana z właściwością klasy SharedViewState. Jeśli zrobisz to wiązanie na wszystkich formularzach, które mają identyczne przyciski, możesz podświetlić Button1 na wszystkich formularzach po prostu przez ustawienieSharedViewState.Button1BackColor = someColor
.Jeśli nie jesteś zaznajomiony z wiązaniem danych WinForm, naciśnij MSDN i poczytaj. To nie jest trudne. Dowiedz się o tym
INotifyPropertyChanged
i już jesteś w połowie drogi.Oto typowa implementacja klasy viewstate z właściwością Button1BackColor jako przykład:
źródło
Utrzymuję nową aplikację WinForms / WPF na podstawie wzorca MVVM i Kontroli aktualizacji . Zaczęło się jako WinForms, a następnie stworzyłem wersję WPF ze względu na marketingowe znaczenie interfejsu użytkownika, który wygląda dobrze. Pomyślałem, że interesującym ograniczeniem projektowym byłoby utrzymanie aplikacji obsługującej dwie zupełnie różne technologie interfejsu użytkownika z tym samym kodem zaplecza (modele i modele), i muszę powiedzieć, że jestem całkiem zadowolony z tego podejścia.
W mojej aplikacji, ilekroć muszę udostępniać funkcje pomiędzy częściami interfejsu użytkownika, używam niestandardowych kontrolek lub kontrolek użytkownika (klasy pochodzące z WPF UserControl lub WinForms UserControl). W wersji WinForms istnieje UserControl wewnątrz innego UserControl wewnątrz pochodnej klasy TabPage, która jest ostatecznie wewnątrz kontrolki tabulatorów w głównym formularzu. Wersja WPF faktycznie ma UserControls zagnieżdżony o jeden poziom głębiej. Korzystając z niestandardowych elementów sterujących, można łatwo skomponować nowy interfejs użytkownika z utworzonych wcześniej elementów UserControl.
Ponieważ używam wzorca MVVM, umieszczam jak najwięcej logiki programu w ViewModel (w tym w modelu prezentacji / nawigacji ) lub w modelu (w zależności od tego, czy kod jest powiązany z interfejsem użytkownika, czy nie), ale od tego samego ViewModel jest używany zarówno przez widok WinForms, jak i widok WPF, ViewModel nie może zawierać kodu, który jest przeznaczony dla WinForms lub WPF lub który bezpośrednio współpracuje z interfejsem użytkownika; taki kod MUSI przejść do widoku.
Również we wzorze MVVM obiekty interfejsu użytkownika powinny unikać interakcji! Zamiast tego wchodzą w interakcje z modelami widoku. Na przykład TextBox nie zapyta pobliskiego ListBox, który element jest wybrany; zamiast tego pole listy zapisuje odniesienie do aktualnie wybranego elementu gdzieś w warstwie viewmodel, a TextBox zapyta warstwę viewmodel, aby dowiedzieć się, co jest teraz zaznaczone. To rozwiązuje twój problem z ustaleniem, który przycisk został wciśnięty w jednej formie z innej formy. Wystarczy udostępnić obiekt Modelu Nawigacji (który jest częścią warstwy viewmodel aplikacji) pomiędzy dwiema formami i wstawić właściwość do tego obiektu, który reprezentuje, który przycisk został wciśnięty.
Sam WinForms nie obsługuje bardzo dobrze wzorca MVVM, ale Update Controls zapewnia własne unikalne podejście MVVM jako bibliotekę umieszczoną na WinForms.
Moim zdaniem takie podejście do projektowania programów działa bardzo dobrze i planuję wykorzystać je w przyszłych projektach. Powody, dla których działa on tak dobrze, to (1), że Kontrola Aktualizacji automatycznie zarządza zależnościami oraz (2), że zazwyczaj jest bardzo jasne, jak należy ustrukturyzować swój kod: cały kod, który wchodzi w interakcję z obiektami interfejsu użytkownika należy do Widoku, cały kod, który jest Związane z interfejsem użytkownika, ale NIE MUSI oddziaływać z obiektami interfejsu użytkownika, należy do ViewModel. Dość często dzielisz kod na dwie części, jedną część dla View i drugą część dla ViewModel. Miałem trochę trudności z zaprojektowaniem menu kontekstowych w moim systemie, ale w końcu wymyśliłem też projekt do tego.
Mam opowiadania o kontroli aktualizacji na moim blogu też. Podejście to wymaga jednak przyzwyczajenia się i w aplikacjach na dużą skalę (np. Jeśli twoje listy zawierają tysiące elementów) możesz napotkać problemy z wydajnością z powodu obecnych ograniczeń automatycznego zarządzania zależnościami.
źródło
Odpowiem na to pytanie, mimo że już przyjęliście odpowiedź.
Jak zauważyli inni, nie rozumiem, dlaczego musiałeś używać czegokolwiek statycznego; brzmi to tak, jakbyś robił coś bardzo źle.
W każdym razie miałem taki sam problem jak Ty: w mojej aplikacji WinForms mam kilka formularzy, które mają pewną funkcjonalność i pewne elementy sterujące. Ponadto wszystkie moje formularze już wywodzą się z formularza podstawowego (nazwijmy go „MyForm”), który dodaje funkcjonalność na poziomie frameworka (niezależnie od aplikacji). Projektant formularzy Visual Studio podobno obsługuje edytowanie formularzy dziedziczących z innych formularzy, ale w praktyka działa tylko tak długo, jak twoje formularze robią tylko „Witaj, świecie!
Skończyło się na tym, że utrzymuję moją wspólną klasę bazową „MyForm”, która jest dość złożona i nadal czerpię z niej wszystkie formularze aplikacji. Jednak nie robię absolutnie nic w tych formularzach, więc VS Forms Designer nie ma problemów z ich edycją. Formularze te składają się wyłącznie z kodu generowanego dla nich przez Projektanta formularzy. Następnie mam osobną równoległą hierarchię obiektów, które nazywam „surogatami”, które zawierają wszystkie funkcje specyficzne dla aplikacji, takie jak inicjowanie formantów, obsługa zdarzeń generowanych przez formularz i formanty itp. Istnieje jeden do jednego korespondencja między klasami zastępczymi i oknami dialogowymi w mojej aplikacji: istnieje podstawowa klasa zastępcza, która odpowiada „MyForm”, następnie wyprowadzana jest inna klasa zastępcza, która odpowiada „MyApplicationForm”,
Każdy surogat przyjmuje jako parametr czasu budowy określony typ formularza i dołącza się do niego rejestrując się na swoje zdarzenia. Deleguje również do bazy, aż do „MySurrogate”, który akceptuje „MyForm”. To surogat rejestruje się w zdarzeniu „Disposed” formularza, tak że gdy forma zostanie zniszczona, surogat wywołuje nadrzędność samego siebie, aby on i wszyscy jego potomkowie mogli wykonać czyszczenie. (Wyrejestruj z wydarzeń itp.)
Do tej pory działało dobrze.
źródło