Przekazywanie informacji pomiędzy formami

0

Mam pytanie. Piszę program WinForms próbując używać MVP i ciekawi mnie jedna rzecz.

W MVC pisząc apke webową np. daję użytkownikowi jakiś imput albo coś do wybrania. Użytkownik sobie zaznacza coś, potem ja wysyłam to sobie formularzem do jakiegoś kontrolera, a potem w tym kontrolerze mogę zrobić sobie coś z przesłaną przez użytkownika informacją.

Jak to jest jakbym chciał zrobić to samo pisząc programik w windows forms?

np. użytkownik wybiera sobie w Form A książkę z listy. Klika OK, kliknięcie OK przenosi go do Form B (żeby tak się stało muszę utworzyć nowy obiekt tej formy i przesłać informacje o wybranej książce do konstruktora).

Jak zrobić coś takiego poprawnie? W przypadku jak mam dwie formy to spoko, ale jak mam ich 10 to robi się niezły labirynt i jeżeli wysyłalbym między nimi informacje to będę miał zależności i spory problem.

0

Przez P - Presenter.

0

Skup się bardziej na celu a nie na prezenterach, kontrolerach. Za dużą uwagę przywiązujesz do teorii.

ale jak mam ich 10 to robi się niezły labirynt
Dziesięciu form nie ogarniesz? Może przemyśl projekt by było ich np. tylko pięć?

1

Można w Prezenterze, ale ja raczej robię oddzielny interfejs do nawigacji między widokami. Zawiera on metody pozwalające na nawigacje między oknami z uwzględnieniem przekazywanych do nich parametrów (np. id książki do edycji). Czyli np. BooksListPresenter z metody ShowEditor woła INavigator.ShowBookEditor(123). Konkretna implementacja INavigatora musi znajdować się w warstwie GUI - z tej prostej przyczyny, że inaczej wygląda nawigacja między oknami w aplikacji desktopowej, a inaczej webowej.
Dodatkowo daje to nam:

  1. łatwą możliwość nawigowania do jednego widoku z wielu miejsc aplikacji (np. widok błędu albo jakiś główny widok użytkownika), bez powielania kodu w wielu prezenterach;
  2. SRP - prezentery obsługują widok, nawigator nawiguje. :)
0

@somekind, masz może jakiś "szablon" albo kod na gicie żeby sobie podejrzeć jak infrastruktura takiego programu powinna wyglądać?

0

Zrobiłem coś takiego, pogmatwane te winformsy..

INAVIGATOR

public interface INavigator
{
      void NavigateTo(string screenName);
      void NavigateTo(string screenName, var parameter);
}
 

BOOK LIST PRESENTER

public BookListPresenter(INavigator navigator)
{
      _navigator = navigator;
}

public void RedirectToForm(string formName, var parameter)
{
      _navigator.NavigateTo(formName, parameter);
}
 

NAVIGATOR

public class Navigator : INavigator
{
     public void NavigateTo(string screenName, var parameter)
     {
          var form = Assembly.GetExecutingAssembly().CreateInstance(screenName);
          form.Show();
     }
}
 

VIEW - MainForm

BookListPresenter presenter = new BookListPresenter();

private void EditBookbutton_Click(object sender, EventArgs e)
{
      presenter.RedirectToForm("EditBook", selectedBookID);
}
 

PROGRAM.CS

var navigator = new Navigator();
var BookListPresenter = new BookListPresenter(navigator);

Application.Run(MainForm);
 

Czy o coś takiego chodzi?

1

Projektu na githubie nie mam, ale może się kiedyś pojawi, bo co jakiś czas ktoś wraca z tym pytaniem.

Nie widzę sensu w jakimś dynamicznym tworzeniu obiektów na podstawie nazwy typu, a tym bardziej przekazywaniu nazw okienek w argumentach metod. To doprowadzi do masy literówek i nudnych błędów przy refaktoryzacji.

Mi chodziło o coś takiego:

INAVIGATOR

public interface INavigator
{
      void ShowBooksListView();
      void ShowNewBookView();
      void ShowEditBookView(int bookId);
}
 

BOOK LIST PRESENTER

public BookListPresenter(INavigator navigator)
{
      _navigator = navigator;
}

public void ShowNewBookView()
{
      _navigator.ShowNewBookView();
}

public void ShowEditBookView(int bookId)
{
      _navigator.ShowEditBookView(bookId);
}
 

NAVIGATOR

public class Navigator : INavigator
{
     public void ShowBooksListView()
     {
          var form = new BooksListForm();
          form.Position = FormPosition.CenterParent;
          form.Size = new Size(500, 400);
          form.Show();
     }

     public void ShowNewBookView()
     {
          var form = new NewBookForm();
          form.ShowDialog();
     }

     public void ShowEditBookView(int bookId)
     {
          var form = new EditBookForm(bookId);
          form.ShowDialog();
     }
}
 

VIEW - MainForm

BookListPresenter presenter = new BookListPresenter();

private void EditBookbutton_Click(object sender, EventArgs e)
{
      presenter.ShowEditBookView(this.SelectedBookId);
}
 
0

Czyli nie ma sensu jakoś zautomatyzować tworzenie nowych instancji form, tylko dodawać kolejne metody do Navigator ,żeby tworzyć kolejne formy?

0

I jeszcze mam takie pytanie, w jaki sposób wstrzykiwać zależności

bo jak robię to tak

            var navigator = new Navigator();
            var booksRepository = new BooksRepository();
            var startFormPresenter = new StartFormPresenter(navigator, booksRepository);


            Application.Run(new StartForm());
 

a potem w widoku

BookListPresenter presenter = new BookListPresenter();
 
private void EditBookbutton_Click(object sender, EventArgs e)
{
      presenter.ShowEditBookView(this.SelectedBookId);
}
 

to tworząc nowy obiekt BookListPresenterpozbawiam się wstrzykniętych konkretnych implementacji interfejsów do konstruktora BookListPresenter jak sobie z tym mogę poradzić? Myślałem o eventach ale to chyba odpada.

0

Tam wyżej miało być BookListPresenter a nie StartFormPresenter

Albo za każdym razem będę robił to w ten sposób

BookListPresenter bookListPresenter = new StartFormPresenter(new Navigator(), new BooksRepository()); 

Czy użyć jakiegoś frameworka, żeby robił to za mnie? ale wtedy i tak będę tworzył nowe obiekty prezenterów w widokach żeby się do nich dostać więc nie wiem czy jest sens. Mam wrażenie że na opak to robię, że da się to jakoś lepiej zrobić.

0

W sumie to nie potrafię z tego wzorca korzystać. Nie mam pojęcia do czego ma mi służyć ten prezenter i co się ma w nim znajdować skoro dostęp do labeli przyciskow mam tylko w widoku. mi on tylko przeszkadza. Po tym jak usiłowałem pisać na podstawie tego co przeczytałem o MVP mój prorgamik to jeden wielki antywzorzec wszystkich wzorców :D

0

To ćwicz. Nikt się nie urodził ze znajomością wzorców :)
https://lostechies.com/wp-content/uploads/2011/03/pablos_solid_ebook.pdf
Tu masz wyjaśnienie MVP na przykładzie przy okazji wyjaśniania SRP.
Od strony 12.

0

Elegancko, może w końcu załapię.. Dzięki

0
Krzywy krawiec napisał(a):

Czyli nie ma sensu jakoś zautomatyzować tworzenie nowych instancji form, tylko dodawać kolejne metody do Navigator ,żeby tworzyć kolejne formy?

Ale gdzie tu widzisz automatyzację? Moim zdaniem to zbędny zamęt i utrudnianie sobie życia.
Istnieje skończony zbiór widoków w aplikacji, i wiadomo jakich parametrów potrzebuje każdy z nich. Sensowne wydaje się napisanie zbioru metod im odpowiadających, a nie jednej metody, która przyjmuje wszystko i na drugi dzień nie wiadomo już jak jej użyć.

Krzywy krawiec napisał(a):

to tworząc nowy obiekt BookListPresenterpozbawiam się wstrzykniętych konkretnych implementacji interfejsów do konstruktora BookListPresenter jak sobie z tym mogę poradzić? Myślałem o eventach ale to chyba odpada.

W klasach implementujących widoki, czyli jak sądzę CośtamForm, dodaj konstruktory przyjmujący odpowiedni Prezentery.

Krzywy krawiec napisał(a):

Czy użyć jakiegoś frameworka, żeby robił to za mnie? ale wtedy i tak będę tworzył nowe obiekty prezenterów w widokach żeby się do nich dostać więc nie wiem czy jest sens. Mam wrażenie że na opak to robię, że da się to jakoś lepiej zrobić.

Użycie jakiegoś kontenera IoC zapewne pomogłoby Ci rozwiązać ten problem sprawniej.

Krzywy krawiec napisał(a):

W sumie to nie potrafię z tego wzorca korzystać. Nie mam pojęcia do czego ma mi służyć ten prezenter i co się ma w nim znajdować skoro dostęp do labeli przyciskow mam tylko w widoku. mi on tylko przeszkadza.

Prezenter ma na celu oddzielenie logiki aplikacji od implementacji GUI. W prawidłowo zaimplementowanym MVP możesz zmienić interfejs z desktopowego na webowy bez zmiany kodu Prezenterów (a także wszystkich warstw leżących pod nimi).
Prezenter realizuje logikę w rodzaju: jeśli dane w widoku są poprawne, to przetwórz je z viewmodelu na jakieś DTO i wyślij do serwisu, który będzie je przetwarzał (albo np. zapisze do bazy). Jeśli operacja się powiedzie, to wyświetl widok komunikatu potwierdzenia, jeśli nie, to widok komunikatu błędu. Albo - po dodaniu nowego rekordu danego typu, automatycznie powróć do ekranu listy tych rekordów.
Oczywiście, MVP (jak większość wzorców) sprawia, że kodu jest nieco więcej niż gdyby go nie było, za to ten kod jest czytelniejszy. Bo dzięki MVP kod odpowiadający za szczegóły Widoku - np. wstawianie/pobieranie odpowiednich wartości z kontrolek GUI, nie jest wymieszany z kodem sterującym przebiegiem aplikacji.

0
somekind napisał(a):

W klasach implementujących widoki, czyli jak sądzę CośtamForm, dodaj konstruktory przyjmujący odpowiedni Prezentery.

No i jak tak zrobię to co ja mam zrobić dalej? Wystartuje pierwsza forma po uruchomieniu programu to może to działać bo utworze sobie nowy obiekt tego prezentera w Program.cs dla Form1. Dalej kliknę dodaj nową książkę odpali się kolejna forma której konstruktor również będzie czekał na implementacje innego prezentera + konkretna implementacja INavigator, a ja nie wiem jak mam mu to zapewnić.
Mój jedyny pomysł to w klasie Navigator tworząc nowe Formy wstrzykiwać nowy obiekt określonych prezenterów i nawigatora, a w przypadku formy startowej zrobić to w Program.cs ? Jakby komuś się chciało to proszę o skrawki kodu o co dokładnie chodzi i jak to zrobić, bo nie potrafię sobie tego inaczej wyobrazić.
Jedynie co jestem w stanie sobie wyobrazić jak framework za mnie binduje interfejsy do konkretnych implementacji a ja sobie tylko w konstruktorach wpisuje te interfejsy.. Ale nie spotkałem jeszcze żeby ktoś robił interfejs dla prezenterów np FormPresenter i IFormPresenter. Ale często widzę jak prezentery przyjmują w konstruktorze interfejs po którym dziedziczy widok.

0

Trochę bzdury popisałem tam wyżej ale już dobra, bo nie mam siły już tego poprawiać. Wstawiam kod jak ja to widzę.

Program.cs

(...)
INavigator navigator = new Navigator();

Application.Run(new Form1(new Form1Presenter(navigator)));
 

Form1

public Form1Presenter presenter { get; set; }

        public Form1(Form1Presenter presenter)
        {
            this.presenter = presenter;
            InitializeComponent();
        }

       private void addNewArtistbutton_Click(object sender, EventArgs e)
        {
            presenter.ShowNewArtistView();
        }
 

Form1Presenter

 
public INavigator _navigator { get; set; }

        public Form1Presenter(INavigator navigator)
        {
            _navigator = navigator;
        }

        public void ShowNewArtistView()
        {
            _navigator.ShowNewArtistView();
        }

AddNewArtistForm - na potrzeby tego przykładu zadaniem tego widoku jest po naciśnięciu przycisku przenieść się z powrotem do Form1

public AddNewArtistPresenter _presenter { get; set; }

        public AddNewArtistForm(AddNewArtistPresenter presenter)
        {
            _presenter = presenter;
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            _presenter.ShowFirstForm();
 

AddNewArtistPresenter

public INavigator _navigator { get; set; }

        public AddNewArtistPresenter(INavigator navigator)
        {
            _navigator = navigator;
        }

        public void ShowFirstForm()
        {
            _navigator.ShowArtistListView();
        }
 

Navigator : INavigator

public void ShowArtistListView()
        {
            var form = new Form1(new Form1Presenter(new Navigator()));            
            form.Show();
        }

        public void ShowNewArtistView()
        {
            var form = new AddNewArtistForm(new AddNewArtistPresenter(new Navigator()));
            form.ShowDialog();
        }

Mam nadzieje, że niczego nigdzie nie popieprzyłem tym razem. @somekind @AreQrm proszę o jakiś komentarz, bo to wszystko chyba co jestem w stanie wymyślić.. :/

0
  1. W Navigatorze nie musisz robić new Navigator(), wystarczy przekazać do Presentera odwołanie do bieżącego obiektu, czyli po prostu this. :)
  2. Nie ma sensu aby Navigator był publiczną właściwością Prezentera, wystarczy jeśli to będzie prywatne pole. To samo dotyczy Prezenterów w Formach.
  3. Prezenter może przyjmować Widok w konstruktorze, ale nie musi. Jeśli tego nie robi, to musisz widok przekazywać do prawie każdej metody Prezentera, co nie zawsze jest fajne - za to jest proste w implementacji.

Poza tym to jest to, o co mi chodziło. Jak kiedyś nauczysz się korzystać z jakiegoś kontenera IoC, to zobaczysz, jak bardzo można sobie nim ułatwić życie w takim przypadku.

1 użytkowników online, w tym zalogowanych: 0, gości: 1