Najlepszy projekt dla formularzy Windows, które będą miały wspólną funkcjonalność


20

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.

  1. 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.)

  2. Stan między formularzami, to znaczy stany przycisków, tekst etykiety itp., Muszę użyć zmiennych globalnych i zresetować stany na obciążeniu.

  3. 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!


Nie rozumiem, dlaczego jesteś zmuszony używać zmiennych globalnych, ale jeśli tak, to na pewno musi być lepsze podejście. Może fabryki stworzą wspólne komponenty w połączeniu z kompozycją zamiast dziedziczenia?
stijn 12.12.11

Twój cały projekt jest wadliwy. Jeśli już wiesz, co chcesz zrobić, nie jest obsługiwane przez Visual Studio, to powinno ci coś powiedzieć.
Ramhound,

1
@Ramhound Jest obsługiwany przez studio wizualne, po prostu nie jest dobrze. W ten sposób Microsoft nakazuje ci to zrobić. Ja po prostu znaleźć to być ból w A. msdn.microsoft.com/en-us/library/aa983613(v=vs.71).aspx Tak czy inaczej, jeśli masz lepszy pomysł, jestem wszystkie oczy.
Jonathan Henson,

@stijn Przypuszczam, że mógłbym mieć metodę w każdej formie podstawowej, która załaduje stany kontrolne do innej instancji. tj. LoadStatesToNewInstance (instancja BaseForm). Mogę to nazwać w dowolnym momencie, gdy chcę pokazać nową formę tego typu podstawowego. form_I_am_about_to_hide.LoadStatesToNewInstance (this);
Jonathan Henson,

Nie jestem programistą .net, czy możesz rozwinąć punkt 1? Mogę mieć rozwiązanie
Imran Omar Bukhsh,

Odpowiedzi:


6
  1. 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.

  2. 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).

  3. 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ść Controlwł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 ustawienie SharedViewState.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 INotifyPropertyChangedi już jesteś w połowie drogi.

Oto typowa implementacja klasy viewstate z właściwością Button1BackColor jako przykład:

public class SharedViewState : INotifyPropertyChanged
{
    // boilerplate INotifyPropertyChanged stuff
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    // example of a property for data-binding
    private Color button1BackColor;
    public Color Button1BackColor
    {
        get { return button1BackColor; }
        set
        {
            if (value != button1BackColor)
            {
                button1BackColor = value;
                NotifyPropertyChanged("Button1BackColor");
            }
        }
    }
}

Dziękuję za twoją przemyślaną odpowiedź. Czy znasz dobre odniesienie do wzorca widoku / kontrolera?
Jonathan Henson,

1
Pamiętam, że pomyślałem, że to było całkiem dobre wprowadzenie: codebetter.com/jeremymiller/2007/05/22/…
Igby Largeman

proszę zobaczyć moją aktualizację.
Jonathan Henson

@JathanathanHenson: odpowiedź zaktualizowana.
Igby Largeman

5

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.


3

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.