Inne argumenty obok wstrzykiwanej zależności w konstruktorze klasy

Inne argumenty obok wstrzykiwanej zależności w konstruktorze klasy
lukaszek016
  • Rejestracja:około 9 lat
  • Ostatnio:ponad rok
  • Postów:249
0

Witam,

mam pewien problem. Myślę i myślę, ale nie mam pomysłu. Chodzi o to, że mam okno z przyciskiem powiedzmy Zobacz szczegóły klienta. I teraz w momencie kliknięcia przycisku metoda wywołuje mi kod:

Kopiuj
ClientDetailsWindow clientDetailsWindow = new ClientDetailsWindow();

Wiadomo, że w oknie musi być wyświetlony cały klient, czyli okno musi "wiedzieć" co wyświetlić. I teraz chcę przekazać jakiegoś integera, a w ViewModelu wykorzystując wstrzyknięty przez konstruktor z Unity Containera serwis pobrać klienta do zmiennej. O ile konstruktor mojego ViewModelu wygląda tak:

Kopiuj
public ClientDetailsViewModel(IClientService clientService)
        {
            _clientService = clientService;
        }

To ładnie Unity wstrzykuje mi ten serwis i mogę z niego korzystać. Problem pojawia się, że chcę dopisać tutaj drugi parametr typu int. Wtedy, przy tworzeniu obiektu okna muszę podać w argumencie albo tego inta i w konstruktorze okna jakoś wstrzyknąć ten serwis, albo podać nowy obiekt konstruktora i z poziomy okna "matki" jakoś wstrzyknąć ten serwis.

Wiem, może trochę namieszałem opisując, ale może ktoś miał taką sytuację i może się podzielić.

Istnieje rozwiązanie, aby nie wstrzykiwać tam serwisu, tylko do okna od razu w parametrze podać obiekt typu Client. Ale nie o to chodzi.

Póki co mam to zaimplementowane tak, że wstrzykuję sam konstruktor, a Id klienta przed utworzeniem nowego okna zapisuję sobię do pola w mojej klasie statycznej TempValues, a potem w tym nowo otwartym oknie pobieram sobie tę wartość. Ale to nie jest chyba dobre rozwiązanie.

grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
0
  1. Odepnij automatyczne spinanie widoku z viewmodelem: prism:ViewModelLocator.AutoWireViewModel="False";
  2. Zamiast tego w code behind okna wstrzyknij do konstruktora kontener IoC: public SomeWindow(IUnityContainer container);
  3. Utwórz obiekt viewmodelu w konstruktorze z punktu nr 2 tworząc go za pomocą kontenera, a nie zwykłym new, przy okazji wstrzykując swojego inta. Implementacje interfejsów zostaną wstrzyknięte z automatu;
  4. Zrób z tego viewmodelu datacontext vide: DataContext = vm;

Przykład wstrzykiwania własnych argumentów:

Kopiuj
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity;
using Unity.Injection;
using Unity.Resolution;

namespace IoC
{
    interface ITest
    {
        string TestProperty { get; set; }
    }

    class TestImplementation : ITest
    {
        public string TestProperty { get; set; }
    }

    class MyPrettyViewModel
    {
        public MyPrettyViewModel(ITest testImplementation, int i) { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var container = new UnityContainer();
            container.RegisterType<ITest, TestImplementation>();

            //  Wstrzykiwanie parametrów:
            var test_value = 19;
            var obj = container.Resolve<MyPrettyViewModel>(new ResolverOverride[] { new ParameterOverride("i", test_value) });
        }
    }
}

Póki co mam to zaimplementowane tak, że wstrzykuję sam konstruktor, a Id klienta przed utworzeniem nowego okna zapisuję sobię do pola w mojej klasie statycznej TempValues, a potem w tym nowo otwartym oknie pobieram sobie tę wartość.

To jest bardzo niedobre rozwiązanie. Lepszym jest wspomniane wstrzyknięcie przez konstruktor, albo zarejestrowanie instancji int'a w kontenerze i odczytanie jej w viewmodelu okna potomnego, kiedy będzie taka potrzeba. Nie będziesz wtedy potrzebować osobnego pola w klasie, specjalnie do przechowywania tego int'a, bo weźmiesz go od razu z kontenerka. Przy rejestrowaniu dobrze jest nazwać sobie jakoś to co wkładasz do kontenera i wyciągać również korzystając z nazwy - nie pomieszają Ci się argumenty.

Kolejną metodą jest wykorzystanie Prism'a i przesłanie pomocniczych danych pod spodem, za pomocą eventów. Tylko tutaj trzeba pamiętać o odpowiedniej kolejności, bo możesz wysłać do okna potomnego event z Id'kiem, w momencie kiedy okno potomne jeszcze nie istnieje i tym samym nie posiada możliwości zasubskrybowania eventu. Informacja pójdzie wtedy w kosmos. :) Jeżeli nie chcesz angażować kontenera i konstrutorów, w których chcesz mieć tylko interfejsy to jest to najlepszy sposób.

edytowany 7x, ostatnio: grzesiek51114
lukaszek016
  • Rejestracja:około 9 lat
  • Ostatnio:ponad rok
  • Postów:249
0

Ok, ten sposób co na początku opisałeś mi się spodobał. Ale jak tak, patrzę, to piszesz, żeby dać w parametrze konstruktora okna interfejs kontenera. Ale, musiałbym dodać do tego jeszcze jeden parametr, inta id klienta. Pytanie, czy mi się implementacja kontenera załaduje w takim razie.

Póki co rozwiązałem to tak, utworzenie okna po kliknięciu powiedzmy buttona:

Kopiuj
ClientDetails clientDetails = new ClientDetails();
                        clientDetails.DataContext = new ClientDetailsViewModel(clientId);

A ViewModel wygląda tak:

Kopiuj
public class ClientDetailsViewModel : BindableBase
    {
        [Dependency]
        public IClientService ClientService { get; set; }

        private ClientDTO client;
        public ClientDTO Client
        {
            get { return client; }
            set { SetProperty(ref client, value); }
        }


        public ClientDetailsViewModel(int clientId)
        {
            IUnityContainer container = (UnityContainer)Application.Current.Resources["IoC"];
            ClientService = container.Resolve<IClientService>();
            Client = ClientService.GetClient(clientId);
        }
    }

Co prawda korzystam tutaj z Resources, ale jak patrzę Twój przykład, to wartość temu int-owi nadawałbym w CodeBehind, a u mnie byłoby to jeszcze warstwę niżej.

grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
1

Pytanie, czy mi się implementacja kontenera załaduje w takim razie.

Tak, implementacja kontenera załaduje się z automatu.

Co prawda korzystam tutaj z Resources, ale jak patrzę Twój przykład, to wartość temu int-owi nadawałbym w CodeBehind, a u mnie byłoby to jeszcze warstwę niżej.

To pchnij tego inta Prismem za pomocą EventAggregator'a, bo tak to powstanie niepotrzebne drzewo kaskadowych wywołań konstruktów z przekazywaniem identycznych parametrów. Oczywiście wywołanie container.Resolve w podobny sposób co w poście wyżej dla konstruktora klasy zawierającego IUnityContainer i int'a zadziała też prawidłowo.

Za dużo kombinujesz :)

edytowany 5x, ostatnio: grzesiek51114
grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
1

@lukaszek016: masz poniżej przykład jak to może wyglądać:

Kopiuj
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity;
using Unity.Lifetime;
using Unity.Injection;
using Unity.Resolution;

namespace IoC
{
    class MainWindow
    {
        private object DataContext { get; set; }

        public MainWindow(IUnityContainer container, int testInteger)
        {
            var vm = container.Resolve<MainWindowViewModel>(new ResolverOverride[] { new ParameterOverride("testInteger", testInteger) });
            DataContext = vm;
        }

        public void Show()
        {
            //  dla symulacji powiedzmy, ze wiem jakiego typu jest DataContext.
            var dc = (MainWindowViewModel)DataContext;
            Console.WriteLine($"testInteger: {dc.TestInteger}");
        }
    }

    interface ITest { }
    class TestImplementation : ITest { }

    class MainWindowViewModel
    {
        public int TestInteger { get; private set; }

        public MainWindowViewModel(ITest testImplementation, int testInteger)
        {
            TestInteger = testInteger;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var container = new UnityContainer();
            container.RegisterType<ITest, TestImplementation>();

            var test_int = 19;
            var window = container.Resolve<MainWindow>(new ResolverOverride[] { new ParameterOverride("testInteger", test_int) });
            window.Show();
        }
    }
}

Resolve'ować można nie tylko typy zarejestrowane.

edytowany 1x, ostatnio: grzesiek51114
lukaszek016
  • Rejestracja:około 9 lat
  • Ostatnio:ponad rok
  • Postów:249
0

Ok dzięki Ci za pomoc! Dam znać jak poszło. Ale wygląda na to, że to jest to czego szukam.

grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
0

No, tylko, że teraz masz tam drabinkę dwóch dziwnych wywołań, miast jednego eventu puszczonego Prismem, niemniej jednak będzie to działać. :-)

lukaszek016
  • Rejestracja:około 9 lat
  • Ostatnio:ponad rok
  • Postów:249
0

Wiem, spróbuję wykorzystać EventAggregator, ale jak pewnie pamiętasz to moja pierwsza aplikacja MVVM, także po kolei się uczę wszystkiego w miarę potrzeb, i właśnie dotarłem do EventAggregatora, który z tego, co patrzyłem bardzo ułatwi robotę nie tylko jeśli chodzi o ten konkretny przypadek.

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 11 godzin
  • Lokalizacja:Wrocław
0

Na WPF się nie znam, ale zarówno wstrzykiwanie kontenera do viewmodelu jak i rejestrowanie intów w kontenerze brzmią dla mnie groźnie. Czy viewmodel nie może po prostu wyciągnąć z modelu odpowiednich danych na podstawie znanego sobie ID?

grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
0

wstrzykiwanie kontenera do viewmodelu

Nie wstrzykuję kontenera do VM tylko do klasy wyżej.

rejestrowanie intów w kontenerze brzmią dla mnie groźnie

Z rejestrowaniem trzeba uważać, bo można spowodować nieświadomie wycieki pamięci, to akurat prawda.

Czy viewmodel nie może po prostu wyciągnąć z modelu odpowiednich danych na podstawie znanego sobie ID?

Żeby rozpropagować dane do vm na podstawie Id to najpierw trzeba to Id jakoś mu przekazać. Jak nie przez konstruktor, bo autor np. chce mieć tam same tylko interfejsy, to trzeba jakoś pod spodem to zrobić. Zresztą WPF jest stanowy więc, np. edytując jakiś obiekt można go przekazać w ogóle w całości, nie ładując od nowa z bazy na podstawie Id. Chyba, że chcesz mieć zawsze świeże dane, bo obawiasz się, że inny klient mógł je zmienić.

edytowany 3x, ostatnio: grzesiek51114
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 11 godzin
  • Lokalizacja:Wrocław
0

@grzesiek51114: to co jest źródłem ID? I czy to ID nie może zostać użyte przez jakąś "fabrykę" ViewModeli aby utworzyć gotowy obiekt, bez przekazywania intów w konstruktorze, bo to naprawdę zły pomysł. Nie przez wycieki pamięci tylko przez to, że kontenery powinny zajmować się wstrzykiwaniem zależności, a ID to są dane, a nie zależność.

grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
0

No jest możliwość: przekazać to ID evetem z event aggregatora z Prism'a. Pisalem o tym wszak ale pytacz jeszcze nie zna dobrze Prisma. ;)

Takie rzeczy przez Prisowe eventy ładnie działają.

edytowany 2x, ostatnio: grzesiek51114
lukaszek016
  • Rejestracja:około 9 lat
  • Ostatnio:ponad rok
  • Postów:249
0

Grzesiek, jak już zacząłeś temat

Zresztą WPF jest stanowy więc, np. edytując jakiś obiekt można go przekazać w ogóle w całości, nie ładując od nowa z bazy na podstawie Id.

Masz na myśli, jak będę robił window do edycji np. klienta, to mogę przekazać cały obiekt klienta, a potem go zapisać rozumiem ze zmienionymi danymi korzystając z bindowania TwoWay?

A drugie, jak wrzucam obiekt Klienta do ViewModelu z powiedzmy 20 polami, to lepiej zostawić jako obiekt dla widoku i w widoku pisać np.

Kopiuj
Text={Binding Client.Name}

Czy lepiej w ViewModelu porozbijać to na właściwości?

grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
0

Masz na myśli, jak będę robił window do edycji np. klienta, to mogę przekazać cały obiekt klienta, a potem go zapisać rozumiem ze zmienionymi danymi korzystając z bindowania TwoWay?

Dokładnie, tylko trzeba pamiętać, że grzebiąc w vm przekazywanego obiektu grzebiemy w oryginale i jak się rozmyślisz w edycji, a wyedytujesz kilka pól to w oryginale także będą zmienione. Ot, klasa jako typ referencyjny ale zawsze można przekazać kopię, tylko do edycji.

Czy lepiej w ViewModelu porozbijać to na właściwości?

W zasadzie wszytko jedno, bo to mała zmiana, a na jedno wychodzi.

edytowany 1x, ostatnio: grzesiek51114
lukaszek016
  • Rejestracja:około 9 lat
  • Ostatnio:ponad rok
  • Postów:249
0

Hm, teraz napisałbym, że skoro nie zrobię na kontekście SaveChanges i zamknę okno po rozmyśleniu się to nic się nie stanie, ale w gdyby tak było to pewnie byś o tym nie pisał ;)

Ok w razie potrzeby założę nowy temat, ale skoro już tu jesteś, chcę zrobić w głównym oknie na pasku aktualny czas z "lecącymi" sekundami. Dodałem właściwość do zbindowania i napisałem taką metodę, którą odpalam w konstruktorze ViewModelu. Czy to jest ok Twoim/Waszym zdaniem?

Kopiuj
private async void TickTime()
        {
            await Task.Run(() =>
            {
                while (true)
                {
                    DateTimeValue = DateTime.Now.ToLongTimeString();
                    Task.Delay(1000);
                }

            });
        }
grzesiek51114
grzesiek51114
  • Rejestracja:ponad 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:2442
1

Czy to jest ok Twoim/Waszym zdaniem?

W sumie czemu nie ale sam zrobiłbym to w oparciu o timer i co sekundę aktualizowałbym własność. Tego taska np. nie ma jak przerwać, a Timer już tak. W Twoim przypadku może się zdarzyć, że będziesz odpalać taska wielokrotnie i nawet nie będziesz o tym wiedział. :)

https://msdn.microsoft.com/pl-pl/library/system.timers.timer(v=vs.110).aspx

PS: to nie jest tak, że na dany problem istnieje tylko jedno, najpiękniejsze rozwiązanie. :-) Kiedyś np. w oparciu o Timer bindowałem pasek postępu, pokazujący ile zostało do odświeżenia się jakiegoś tam DataGrid'a. Ten sposób wydawał mi się najlepszy, co nie znaczy, że taki rzeczywiście był... ale działał bez zarzutu.

edytowany 4x, ostatnio: grzesiek51114
lukaszek016
  • Rejestracja:około 9 lat
  • Ostatnio:ponad rok
  • Postów:249
0

Wiesz co, co do odpalania go wielokrotnie, odpalony byłby w oknie głównym programu i jedyna opcja, aby zamknąć to okno i otworzyć ponownie to wylogowanie się z programu, co skutkuje wywołaniem metody Close na oknie, która chyba zwalnia wszystkie zasoby.

Ale faktycznie, skoro jest gotowe rozwiązanie to nie pozostaje nic jak z niego skorzystać, dzięki ;)

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)