logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

C# Windows Form: przekazywanie parametrów do głównej formy

armant 03 Cze 2011 08:50 9902 9
REKLAMA
  • #1 9571951
    armant
    Poziom 9  
    Posty: 31
    Problem na pierwszy rzut oka wydaje się banalny. Mam formatkę Form2, której właścicielem jest Form1 (wywołanie przez ShowDialog). Chciałbym, aby przy zamykaniu Form2 przekazywała do swojego właściciela kilka parametrów. Zacząłem standardowo dodając do konstruktora Form2 owe parametry poprzedzone "ref" coby przy tworzeniu formy przekazywane były przez referencje a nie wartości.

    I tu zaczynają się schody. Bowiem przekazany w ten sposób SerialPort jak najbardziej działa i wszystko jest ok, ale integer działać nie chce, tj. wszelkie modyfikacje jego wartości przez Form2 nie są po jej zamknięciu widoczne przez Form1. I nie ma tu znaczenia sposób deklarowania tego integera w Form1 - nie działa zarówno dla private jak i dla public.

    Póki co znalazłem trzy różne rozwiązania tego problemu i wszystkie wydają mi się kiepskie. Pierwsze polega na tym, żeby nie zamykać Form2, tylko ją ukrywać (stosując Hide) i z poziomu Form1 pobierać sobie dane (np. this.zmienna=Form2.zmienna). Rozwiązanie nie do przyjęcia, bo Form2 trzeba zamykać z poziomu formy głównej, a to czyni ją mało uniwersalną.
    Druga metoda, znaleziona tu i tu, polega na przekazaniu do Form2 dostępu do całej (sick!) Form1. To czyni naszą Form2 całkowicie nieuniwersalną, bo przecież w konstruktorze tej formy trzeba definiować formę, do której uzyskujemy dostęp, a poza tym parametry, które chcemy w niej zmieniać, w nowym programie na 99% będą się inaczej nazywać i w zasadzie trzeba modyfikować połowę kodu Form2.
    Metoda trzecia, znaleziona tutaj, polega na użyciu dodatkowej klasy pośredniczącej, w której byłyby przechowywane wymieniane pomiędzy formami parametry. Ten sposób też za bardzo mi nie odpowiada, choć jest najbardziej uniwersalny.

    Czy da się w jakiś prosty sposób przekazać przy wywoływaniu Form2 referencje do obiektów na Form1? I dlaczego "ref" działa dla parametru typu SerialPort, ale nie działa dla typu integer?
  • REKLAMA
  • Pomocny post
    #2 9571996
    marcinj12
    Poziom 40  
    Posty: 3404
    Pomógł: 1024
    Ocena: 250
    Ja bym to rozwiązał w ten sposób: w formie 2 wstawił:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    a w formie 1 odwołał się do tego tak:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    pozdrawiam
  • REKLAMA
  • #3 9572038
    armant
    Poziom 9  
    Posty: 31
    marcinj12 napisał:
    Ja bym to rozwiązał w ten sposób: w formie 2 wstawił:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    a w formie 1 odwołał się do tego tak:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    pozdrawiam


    Hmmm... dziwne... działa... a przecież instrukcja myIntForm1 = form2.MyInt jest wykonywana zaraz po instrukcji Form2.Dispose(). Zatem metoda Dispose nie usuwa z pamięci obiektu? No bo wyraźnie pobieramy dane z obiektu Form2, który nie powinien już istnieć...
  • Pomocny post
    #4 9572130
    marcinj12
    Poziom 40  
    Posty: 3404
    Pomógł: 1024
    Ocena: 250
    armant napisał:
    przecież instrukcja myIntForm1 = form2.MyInt jest wykonywana zaraz po instrukcji Form2.Dispose().

    Niezupełenie.
    Metoda .Dispose() formy 2 jest wykonywana na końcu "klamerki" using { }, samo zamknięcie tej formy metodą .Close() nie pociąga za sobą wykonania .Dispose().

    Zobacz jak to robią na stronie MSDNu - bez using {} musisz wywołać .Dispose() żeby "posprzątać" po formie.
    I cytując za MSDN'em:
    Cytat:
    The two conditions when a form is not disposed on Close is when
    (1) it is part of a multiple-document interface (MDI) application, and the form is not visible; and
    (2) you have displayed the form using ShowDialog. In these cases, you will need to call Dispose manually to mark all of the form's controls for garbage collection.


    W momencie kiedy przypisujesz zmienną instrukcją myIntForm1 = form2.MyInt, forma 2 ciągle istnieje, tylko jest niewidoczna. Wstaw sobie na form2 jakiegoś textboxa i zmień kod na taki:

    Kod: C#
    Zaloguj się, aby zobaczyć kod


    Kiedy po pierwszym otwarciu form2 wpiszesz w textboxa jakiś tekst i zamkniesz formę, zostanie ona otwarta ponownie ze wpisanym już tekstem - czyli nic nie jest zwalniane przy zamykaniu.
  • Pomocny post
    #5 9572132
    wiesniak
    Poziom 31  
    Posty: 1006
    Pomógł: 231
    Ocena: 52
    A gdzie w tym kodzie masz Dispose?

    Poza tym Dispose nie usuwa od razu obiektu. Może to dziwne, ale robiąc Dispose tylko sugerujesz garbage collectorowi, że dany obiekt może zostać usunięty.
    Dlatego najczęściej nie ma potrzeby wołania Dispose - GC sam posprząta w odpowiednim momencie.
  • REKLAMA
  • #6 9572228
    armant
    Poziom 9  
    Posty: 31
    wiesniak napisał:
    A gdzie w tym kodzie masz Dispose?

    A chociażby tu:
    armant napisał:
    Chciałbym, aby przy zamykaniu Form2 przekazywała do swojego właściciela kilka parametrów.

    Zarówno na końcu metody Form2_FormClosing, jak i przy kilku innych metodach zamykających tą formę (kilka buttonów zamyka formę z różnym efektem), mam wstawione Dispose() - właśnie dlatego iż wywołuję ją przez ShowDialog() a nie przez zwykłe Show(). A mimo to, odwołanie się do obiektu tej formy działa po jej zamknięciu...

    wiesniak napisał:
    Poza tym Dispose nie usuwa od razu obiektu. Może to dziwne, ale robiąc Dispose tylko sugerujesz garbage collectorowi, że dany obiekt może zostać usunięty.
    Dlatego najczęściej nie ma potrzeby wołania Dispose - GC sam posprząta w odpowiednim momencie.


    Dobrze wiedzieć... Pytanie tylko czy ten "odpowiedni moment" dla GC będzie również odpowiednim dla mnie ;) Trochę nieprofesjonalnie byłoby pisać aplikację, której działanie byłoby zależne od tego czy GC usunął formę zaraz po instrukcji Dispose() czy też odczekał chwilę, dzięki której można było wyciągnąć z tej formy dane. Lepiej chyba będzie zastosować zaproponowane przez Marcina using{} i usunąć z kodu formy Form2 wszystkie instrukcje Dispose(). Ergo ucierpi na tym uniwersalność klasy Form2, bo przy każdym jej użyciu trza będzie pamiętać o konieczności "posprzątania" po niej.

    Sądząc z Waszych wypowiedzi nie da się przekazać do Form2 parametrów przez referencję i trza po prostu wyciągnąć z tej formy dane przed jej usunięciem z pamięci (niezależnie od tego, kiedy to usunięcie nastąpi). Ergo, nie da się jej użyć jako uniwersalnej klasy i trzeba znać nazwy zmiennych w niej występujących jeśli chce się jej używać.

    Trochę mnie to rozczarowuje, gdyż po C# i w ogóle po całym .NET spodziewałem się lepszego nastawienia na uniwersalność i prostotę używania stworzonych klas. Myślałem, że stworzę sobie kilka dobrze napisanych uniwersalnych klas form, których w przyszłości będę mógł używać nie wnikając w jaki sposób działają... o naiwności! ;)
  • Pomocny post
    #7 9572292
    wiesniak
    Poziom 31  
    Posty: 1006
    Pomógł: 231
    Ocena: 52
    IMO te Twoje wywołania Dispose'a są zbędne. Kiedy GC będzie niszczył klasę sam zawoła Dispose.

    Nie musisz się bawić w ręczne sprzątanie, używanie using też wydaje mi się zbędne.
    Skoro narzucasz sprzątanie przy zamykaniu okienka, to równie dobrze możesz pisać
    Kod: C#
    Zaloguj się, aby zobaczyć kod

    Przy każdym przypisaniu nowego obiektu, stary będzie oznaczany jako "do usunięcia" i GC sobie go zamiecie.

    Oczywiście, że da się przekazywać parametry przez referencję.
    W klasie okna możesz stworzyć metodę Pokaz(), która będzie przyjmować dowolne parametry, a na końcu tej metody zawołasz ShowDialog. Po stworzeniu obiektu okna, zamiast wołać ShowDialog zawołasz Pokaz() z odpowiednimi parametrami.

    A jeśli chcesz przekazywać różne parametry do tego samego okna, to przekazuj kolekcję jako jeden parametr.
    Wiesz uniwersalność to jedno, ale jeśli do jednego okienka wpakujesz funkcjonalność całej aplikacji, to nie będzie zbyt dobre.
    Zawsze możesz też zrobić jedną formę, a do niej ładować kontrolki użytkownika (User Control) w oparciu o parametry przekazane do okna, które będą miały specyficzne parametry.

    Pisałem trochę w C# pod formsami i nigdy nie miałem problemu "to jest za mało uniwersalne". Jeśli już, to ja coś źle zaprojektowałem albo nie przemyślałem odpowiednio :-)
  • REKLAMA
  • #8 9572401
    armant
    Poziom 9  
    Posty: 31
    Może "uniwersalność" to nie najlepsze słowo, zwłaszcza w tym kontekście - może być opacznie zrozumiane. Nie chodziło mi o to, żeby wrzucić funkcjonalność całej aplikacji w jedno okno - wręcz przeciwnie.
    Używając klas i metod z wbudowanych w .NET bibliotek w zasadzie nie musisz wiedzieć co i jak one robią, jakich funkcji używają i na jakich zmiennych operują - interesuje Cię tylko to, co musisz do takiej metody "wrzucić" i co ona "wyrzuca" po skończeniu działania.
    Ja właśnie chcę iść podobną drogą - chcę sobie zrobić porządnie napisane i dopracowane okienko wykonujące ściśle określoną rzecz, do którego później nie będę musiał w ogóle zaglądać (stąd również uparte stosowanie Dispose() - po prostu nie wiem jak często i w jaki sposób będę tego okna w przyszłości używał i wolę z góry się zabezpieczyć przed sytuacją, kiedy w pamięci pojawi się kilkadziesiąt/kilkaset nie zamkniętych okienek).
    Będę po prostu podpinał klasę z tym oknem do nowych aplikacji, nie wnikając więcej w jego kod. Dlatego za wszelką cenę chcę uniknąć sytuacji, w której używając tego okienka muszę znać jego wewnętrzne nazwy zmiennych itp. Okno po prostu ma dostać kilka zmiennych (dla kilku nie trzeba tworzyć kolekcji), w zależności od tego co użytkownik na nim "wyklika" odpowiednio je obrobić i zwrócić do okna głównego te same, obrobione zmienne. Dla takiej sytuacji najlepsze wydaje mi się właśnie przekazanie przez referencję.

    wiesniak napisał:
    Oczywiście, że da się przekazywać parametry przez referencję.
    W klasie okna możesz stworzyć metodę Pokaz(), która będzie przyjmować dowolne parametry, a na końcu tej metody zawołasz ShowDialog. Po stworzeniu obiektu okna, zamiast wołać ShowDialog zawołasz Pokaz() z odpowiednimi parametrami.


    Czyli rozumiem, że przez referencję nie da się przekazać parametrów do klasy okna (za pomocą konstruktora), ale już do metody tej klasy można? Zaraz to sprawdzę :) I nadal nurtuje mnie dlaczego SerialPort przekazany referencyjnie w konstruktorze klasy działa bez problemu (okno rzeczywiście zmienia parametry portu znajdującego się na formie głównej).
  • Pomocny post
    #9 9572411
    marcinj12
    Poziom 40  
    Posty: 3404
    Pomógł: 1024
    Ocena: 250
    W większości zgadzam się z przedmówcą wiesniak. :)
    Wywoływanie .Dispose() wewnątrz formy przy zamykaniu jest co najmniej niepewne - to programista używając klasy powinien decydować, kiedy - jeśli w ogóle - wykona .Dispose().
    Patrz: przykład gdzie 2x wywołujesz .ShowDialog() tej samej formy - jeżeli wykonasz .Dispose() w zdarzeniu zamykania formy, ten przykład nie zadziała tylko wyrzuci wyjątek. W zasadzie tracisz wtedy "uniwersalność" o której piszesz, bo narzucasz wykonanie .Dispose() w momencie zamykania formy (co nie jest standardowym i często spotykanym rozwiązaniem).

    Co do tego czy używać w ogóle .Dispose() dla formy czy nie - sądzę że to kwestia dyskusyjna :)
    Ja z kolei pracując z WinForms'ami bez .Dispose() okienek natknąłem się kiedyś na problem jakby "wycieku pamięci" - aplikacja działała 24h/dobę, pokazywała okienka bez użycia .Dispose() i przyrastała w pamięci aż ubiła serwer :)... Być może problem tkwił w czym innym, jednak użycie using() {} rozwiązało mi ten problem, więc - profilaktycznie - wolę stosować to w ten sposób (vide: MSDN który w przykładach dla .ShowDialog() jednak stosuje .Dispose())

    Jeśli chodzi o przykład z przekazaniem parametrów do formy to da się jak najbardziej, używając ref lub out, np. w formie 1 (pozostanę przy moim sposobie z using { ... } :)):
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    a w konstruktorze formy 2:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    Oczywiście w przypadku typów takich jak integer, ograniczasz zmiany na myInt tylko do obszaru konstruktora (bądź metody do której go przekazujesz). Poza nim nie odwołasz się już do myInt, a próbując go przypisać do innej zmiennej, np. tak:
    Kod: C#
    Zaloguj się, aby zobaczyć kod

    tak naprawdę tylko utworzysz kopię zmiennej i _myInt = 9 zmieni wartość tej kopii.
    Tak działają typy wartościowe, jak integer.

    I tutaj wkraczają przekazywanie przez referencje - jak w przypadku klas. Twój SerialPort na który się wciąż powołujesz też jest klasą. Są one przekazywane przez referencję, czyli jakby wskaźnik w C++. Dlatego możesz zrobić coś takiego:

    w formie 1 (zauważ deklarację nowej klasy):
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    a w formie 2 np. tak:
    Kod: C#
    Zaloguj się, aby zobaczyć kod


    Masz podane przykłady chyba najczęściej stosowanych rozwiązań.
    Ja osobiście skłaniam się do metody z get; i set; z mojego pierwszego postu (forma też jest klasą, nic więc dziwnego w tym że odbierasz w innej klasie jakieś jej parametry), ewentualnie do opisanej przed chwilą metody z dodatkową klasą MojeZmienne.

    Wybór należy do Ciebie ;)
    Pozdrawiam
  • #10 9572579
    armant
    Poziom 9  
    Posty: 31
    marcinj12 napisał:
    W większości zgadzam się z przedmówcą wiesniak. :)
    Wywoływanie .Dispose() wewnątrz formy przy zamykaniu jest co najmniej niepewne - to programista używając klasy powinien decydować, kiedy - jeśli w ogóle - wykona .Dispose().
    Patrz: przykład gdzie 2x wywołujesz .ShowDialog() tej samej formy - jeżeli wykonasz .Dispose() w zdarzeniu zamykania formy, ten przykład nie zadziała tylko wyrzuci wyjątek. W zasadzie tracisz wtedy "uniwersalność" o której piszesz, bo narzucasz wykonanie .Dispose() w momencie zamykania formy (co nie jest standardowym i często spotykanym rozwiązaniem).


    Funkcjonalność mojego okienka jest taka, że jest bardzo niewielkie (a praktycznie zerowe) prawdopodobieństwo aby raz zamknięte okno było wywołane po raz kolejny. Za to może być ono tworzone wielokrotnie dla różnych obiektów i nie chcę zapchać w ten sposób pamięci po kilku godzinach pracy programu.

    marcinj12 napisał:
    Oczywiście w przypadku integera ograniczasz zmiany na myInt tylko do obszaru konstruktora (bądź metody do której go przekazujesz). Poza nim nie odwołasz się już do myInt, a próbując go przypisać do innej zmiennej(...) tak naprawdę tylko utworzysz kopię zmiennej i _myInt = 9 zmieni wartość tej kopii.
    Tak działają typy wartościowe, jak integer.

    I tu leży pies pogrzebany ;) Byłem święcie przekonany, iż użycie referencji w konstruktorze klasy rozwiązuje sprawę przekazania parametrów dla całej klasy (a więc również i jej metod). Jak widać się myliłem.

    Problem zatem można uznać za rozwiązany. Dziękuję za bardzo przejrzyste i łopatologiczne wyjaśnienia. Wieczorem zamknę temat.
    Pozdrawiam

Podsumowanie tematu

✨ Użytkownik napotkał problem z przekazywaniem parametrów z Form2 do Form1 w aplikacji Windows Forms w C#. Po zamknięciu Form2, zmiany w wartościach typu integer nie były widoczne w Form1, mimo użycia referencji. Proponowane rozwiązania obejmowały dodanie właściwości publicznych w Form2 oraz użycie bloku using do zarządzania cyklem życia formy. Użytkownicy dyskutowali na temat wywoływania metody Dispose oraz jej wpływu na zarządzanie pamięcią, podkreślając, że Dispose nie usuwa obiektu od razu, a jedynie sugeruje garbage collectorowi, że obiekt może być usunięty. Ostatecznie użytkownik uznał problem za rozwiązany, zrozumiał zasady przekazywania parametrów i zarządzania pamięcią w kontekście WinForms.
Wygenerowane przez model językowy.
REKLAMA