Autor Téma: MVVM+DSharp+Spring4D DI+Marhmallow  (Přečteno 232 krát)

Offline egroups

  • Nováček
  • *
  • Příspěvků: 17
  • Karma: 0
MVVM+DSharp+Spring4D DI+Marhmallow
« kdy: 23-11-2017, 18:44:11 »
Poprosím o radu zdejší machry.Snažím se ve zděděné "legacy" aplikaci zavést poněkud pořádek a hlavně ji zmodernizovat.
Snažím se teď pochopit,jak nejlépe implementovat Model-View-ViewModel.
Ve Spring,respektive Marshmallow mám nadefinované entity z DB.K tomu samozřejmě repozitáře,takže strana Modelu je mi v podstatě jasná.ViewModel by se měl starat přes Repository o konkrétní Entity,čímž jsem se samozřejmě odstínil od konkrétní DB.Všechno co je důležité,by měl zařizovat ViewModel,potud jakžtakž je mi to jasné.
Problém mám s View.
Začal jsem se základním číselníkem,nepodstatným čeho,třeba sazebník DPH.Vytvořil jsem formulář,který obsahuje PageControl s 2 záložkami (List a Detail).
Na první záložce je v podstatě jen grid se seznamem všech entit,které nabinduji pomocí DSharp z ViewModelu,který si v podstatě hrábne přes repository pro konkrétní List všech Entit.
Na druhé záložce mám vlastně jen Edity atd,do kterých nabinduji opět pomocí DSharp hodnoty konkrétní vybrané Entity.
Mám to doplněno o několik tlačítek pod seznamem,typu New/Edit/Delete a o tlačítka Save/Cancel pod detailním náhledem těch editů.
Mám to udělané tak,že při otevření formuláře se zobrazí pouze záložka se seznamem a uživatel má možnost vytvořit nový záznam nebo si rychle najít konkrétní záznam (pomocí filtrů nad gridem) a buď ho zeditovat nebo smazat.Pokud chce vytvořit nový nebo zeditovat,tak samozřejmě zavolám nějakou TAction,ať už přes příslušné tlačítko nebo třeba přes doubleclick na gridu a v tu chvíli se formulář přepne na záložku Detail,čímž skryje záložku List a má přednaplněné kolonky a může editovat.Nakonec buď dá něco jako Cancel a záložka s Detailem se schová a vrátí se zpět na záložku List,přičemž se nic nezměnilo.Nebo dá něco jako Save a přes repository by se měly úpravy uložit do DB a vrátit na List.

A tady jsem právě narazil na kámen úrazu s kterým si nevím rady.Jde o to,že formulář by neměl dělat nic jiného,než příslušně přepínat záložky,předat přes binding do ViewModelu patřičné údaje nebo si je naopak vzít a pak říct ViewModel,aby se postaral o to ukládání,mazání atd.
Ale nevím,jak to udělat,aby View dalo vědět ViewModelu,co po něm chce za činnost.
Snažím se to udělat nějak univerzálně pro všechny číselníky,protože funkčnost bude skoro furt stejná,takže jsem udělal něco jako TCiselnikViewModel a pomocí generic mu říkám o jakou Entitu se jedná a až v konkrétních případech předám View konkrétní konkrétní implementaci z DI kontejneru.
V jednom z velmi mála příkladů,které se mi podařilo na inetu najít používá jeho autor tuto konstrukci:
Kód: Delphi [Vybrat]
  1. [Inject(ViewModel_Ciselnik_Skupina)]
  2. property ViewModel: TObject read FViewModel write SetViewModel;
  3.  
View získá z kontejneru správnou implementaci ViewModel,která mám schované za něčím takovým:
Kód: Delphi [Vybrat]
  1. ICiselnikViewModel = interface(IBaseViewModel)
  2.   ['{E04D38A0-1A83-410E-8B9A-951265A7CB64}']
  3.   procedure New;
  4.   procedure Edit;
  5.   procedure Delete;
  6.   procedure Save;
  7. end;
  8.  
  9. ICiselnikViewModel<TEntity: class, constructor;TID> = interface(ICiselnikViewModel)
  10.    ['{4098570A-18E7-41C5-8FE7-37D001FEA534}']
  11.    function GetEntity: TEntity;
  12.    procedure SetEntity(const value: TEntity);
  13.   procedure Add;
  14.   function GetCanDelete: Boolean;
  15.   function GetCanEdit: Boolean;
  16.   property CanDelete: Boolean read GetCanDelete;
  17.   property CanEdit: Boolean read GetCanEdit;
  18. end;
  19.  
V tuto chvíli nemám problém s bindováním třeba tohoto:
Kód: Delphi [Vybrat]
  1.   bgMain.AddBinding(ViewModel,'Entity.Nazev',edtNazev,'Text',bmTwoWay);
  2.   bgMain.AddBinding(ViewModel,'Entity.Marze',edtMarze,'Text',bmTwoWay);
  3.  
Ale nevím si naprosto rady,jak ve View v nějaké proceduře/akci zavolat ViewModel.Save.
Kompilátor samozřejmě zakřičí,že TObject nezná metodu Save.
Zkoušel jsem to překopat tak,aby property ViewModel nebyla TObject,ale třeba ICiselnikViewModel,ale v tu chvíli mi zase přestal Container vracet správnou konkrétní implementaci i když byla správnš zaregistrovaná.

Na netu je zoufale malinko příkladů MVVM v Delphi potažmo s DSharp,prakticky skoro žádné,(narozdíl od C#,což je pro mne španělská ves).
Našel jsem vlastně jen 2.
Nicméně něco jsem vyčetl a zkusil využít a dost mi toho už funguje dle mých představ.
Ale tady jsem se hrozně zasekl.

Možná,že na to jdu úplně blbě nebo jen nějakou maličkost dělám blbě.
Budu vděčný,kdyby mě někdo nakopl správným směrem (ne do určitého místa:).Určitě se tu najde někdo,kdo MVVM běžně používá i když možná ne s DSharp,ale to není až tak podstatné,princip bindingu budu skoro furt stejný a třeba by se podělil o pár kousků svého kódu pro inspiraci nebo aspoň naznačil,jak na to,případně doplnil,čeho se vyvarovat.

Předem moc díky.

Offline Delfin

  • Hrdina
  • ****
  • Příspěvků: 431
  • Karma: 21
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:MVVM+DSharp+Spring4D DI+Marhmallow
« Odpověď #1 kdy: 23-11-2017, 22:00:55 »
Otazka je strasne dlouha (opticky) ::) Nehledas metody instance TSession? Viz. napr.

..\Marshmallow\Demos\GettingStarted\Views\ViewMain.pas
« Poslední změna: 23-11-2017, 22:08:37 od Delfin »
A co chudinky ovce? Koupíš jim snad plovací vesty? Nebo jim nasadíš chůdy? Ještě lepší, kdybys je zkřížil s delfíny na ovce hopkavé!

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 1866
  • Karma: 91
    • Verze Delphi: D2007, XE3, DX10
Re:MVVM+DSharp+Spring4D DI+Marhmallow
« Odpověď #2 kdy: 24-11-2017, 07:28:06 »
Možná,že na to jdu úplně blbě nebo jen nějakou maličkost dělám blbě.
Budu vděčný,kdyby mě někdo nakopl správným směrem (ne do určitého místa:).Určitě se tu najde někdo,kdo MVVM běžně používá i když možná ne s DSharp,ale to není až tak podstatné,princip bindingu budu skoro furt stejný a třeba by se podělil o pár kousků svého kódu pro inspiraci nebo aspoň naznačil,jak na to,případně doplnil,čeho se vyvarovat.
Nemuzu rict, ze bych MVVM pouzival, v Delphi vubec a v minulosti jsem ho pouzil s aplikaci napsanou v MS WPF, pro kterou je jakoby delany. A tam View nema zadny kod, zadne obsluhy udalosti apod. Jen se deklarativne popise binding a to obousmerne: a) kde ma vzit data pro zobrazovani b) jake commandy ma plivat, kdyz se treba klikne na tlacitko.

No a zbytek uz je jen o vytvoreni ViewModelu, ktery umi propagovat zmeny, aby se pri prirazeni odlisne hodnoty do property prekreslil ksicht a aby reagoval na commandy. No a WPF platforma obsahuje infrastrukturu pro praci s temi commandy tj. jejich registraci atd. Muzes ti to predstavit jako analogii k broadcastu WM_xxxx zprav kamsi do prostredi, kde by mel existovat listener, ktery danou zpravu umi zpracovat.

Takze ve view treba definice tlacitka vypada takhle:
Kód: XML [Vybrat]
  1. <Button Grid.Column="1" HorizontalAlignment="Stretch" Name="bntCashIn" VerticalAlignment="Stretch"
  2.                                         Command="{x:Static cmd:CommandSet.CashIn}">
  3.   <Image HorizontalAlignment="Stretch" Name="imgBtnCashIn" Stretch="Fill" VerticalAlignment="Stretch" >
  4.     <Image.Style>
  5.       <Style TargetType="Image">
  6.         <Setter Property="Source" Value="../Images/Glyphs/64x64/transparent_down_arrow_green_64x64.png" />
  7.         <Style.Triggers>
  8.           <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}, Path=IsEnabled}" Value="False">
  9.             <Setter Property="Source" Value="../Images/Glyphs/64x64/transparent_down_arrow_grey_64x64.png" />
  10.           </DataTrigger>
  11.         </Style.Triggers>
  12.       </Style>
  13.     </Image.Style>
  14.   </Image>
  15. </Button>
  16.  

Tohle je registrace toho commandu:
Kód: C [Vybrat]
  1. CommandManager.RegisterClassCommandBinding(typeof(Control), new CommandBinding(CommandSet.CashIn, ExecuteCashInCommand, CanExecuteCashInCommand));
  2.  

A tohle jeho obsluha:
Kód: C [Vybrat]
  1. private void CanExecuteCashInCommand(object sender, CanExecuteRoutedEventArgs e)
  2. {
  3.   e.CanExecute = base.HasCommand(CommandSet.CashIn) && IsViewActive && !IsProcessingCommand;
  4. }
  5.  
  6. private void ExecuteCashInCommand(object sender, ExecutedRoutedEventArgs e)
  7. {
  8.   _Log.InfoFormat("Executing \"{0}\" command in \"{1}\"...", CommandSet.CashIn.Name, this.GetType().Name);
  9.  
  10.   CheckButtonAsEventSource(e.OriginalSource, "open shutter");
  11.   AllowedCommands.RemoveCommand(CommandSet.CashIn);
  12.  
  13.   Model.SetWaitingForCashInPhase();
  14.   RaiseInvalidateRequerySuggested();
  15.   UserControl UC = ((DependencyObject)sender).GetNearestParentUserControl();
  16.   if (UC != null)
  17.     UC.Refresh();
  18.  
  19.   Messenger.NotifyColleagues(MessageSet.DepositStartCommand, null);
  20. }
  21.  

Jak neceho takoveho docilit v Delphi netusim, dokonce se da rict, ze bych se do MVVM v Delphi nepoustel, ale ja bych se v Delphi nepoustel uz vubec do niceho...

Treba te to aspon nakopne podivat se, jestli v tech Delphi zalezitostech neexistuje i binding obracenym smerem.


Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 1866
  • Karma: 91
    • Verze Delphi: D2007, XE3, DX10
Re:MVVM+DSharp+Spring4D DI+Marhmallow
« Odpověď #3 kdy: 24-11-2017, 07:37:34 »
Otazka je strasne dlouha (opticky) ::)
S dlouhymi dotazy se roztrhl posledni dobou pytel ;-)

Citace
Nehledas metody instance TSession?
IMHO TSession predstavuje rozhrani  ViewModel -> Model tj. persistentni repository, ale on potrebuje rozhrani View -> ViewModel

Offline egroups

  • Nováček
  • *
  • Příspěvků: 17
  • Karma: 0
Re:MVVM+DSharp+Spring4D DI+Marhmallow
« Odpověď #4 kdy: 24-11-2017, 08:41:18 »
Omlouvám se za ten dlouhý dotaz,ale chtěl jsem podrobně popsat o co mi skutečně jde.
Ano v C# je tato implementace asi jednodušší,navíc tam existuje spousta knihoven pro toto.
Nicméně,já chápu MVVM jako design pattern,který není závislý na jazyku.Akorát v Delphi není zřejmě až taková podpora.DSharp je jediný,o kterém vím.

Pro pf1957:
To co popisuješ jako naprosto hloupý formulář by byl pro mne asi ideál,ale to by znamenalo,že do ViewModel bych musel "zavést" všechny Actions pro View a ty by šli bindovat,ale to nevím,jestli je správně.Co když udělám zcela jiné View a použiji ten samý ViewModel?To bych asi musel furt přidávat specifické Actions pro všechny použité View a to se mi nějak nezdá.Pochopil jsem to tak,že ViewModel nemá vůbec vědět o tom,jak vypadá View a jaké Actions potřebuje.Proto právě hledám ideání způsob,jak to udělat.Bindování dat View<->ViewModel je jasné (i když ještě přesně nevím,jak bindovat třeba do Combobox,ale to nějak půjde).Jde mi právě o to ovládání.

Pro Delfin:
Přesně,jak píše pf1957.Session s tím skoro nemá vůbec nic společného,protože stručně řečeno je to takto ViewModel<->Repository<->Session<->DB.Na této straně nemám problém s chápaním.A ten příklad,který uvádíš,je sice hezký,ale přesně tomuhle se chci vyhnout,aby ve formuláři bylo úplně vše,včetně komunikace s DB.Ten formulář nesmí vědět nic o DB.

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 1866
  • Karma: 91
    • Verze Delphi: D2007, XE3, DX10
Re:MVVM+DSharp+Spring4D DI+Marhmallow
« Odpověď #5 kdy: 24-11-2017, 09:02:18 »
ViewModel nemá vůbec vědět o tom,jak vypadá View a jaké Actions potřebuje.
No ale to presne ten fragment kodu, co jsem posilal splnuje: ViewModel vystavi prikaz, kteremu rozumi a na ktery je ochoten reagovat a je mu uplne jedno, v kolika View ho pouzijes a zda vubec a jak ty view vypadaji. Takze samozrejme muzes mit hafo ruznych View, ktere emituji dany command. A pokud v dany cas existuje nejaky ViewModel ochotny ho zpracovat, tak se i neco stane.
« Poslední změna: 24-11-2017, 09:18:56 od pf1957 »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 1866
  • Karma: 91
    • Verze Delphi: D2007, XE3, DX10
Re:MVVM+DSharp+Spring4D DI+Marhmallow
« Odpověď #6 kdy: 24-11-2017, 09:17:34 »
Nicméně,já chápu MVVM jako design pattern,který není závislý na jazyku.
No to je sice do jiste miry pravda, ale tech skoro stejnych patternu jako MVP apod. je nekolik... A Gossman, ktery MVVM predstavil, je/byl jeden z hlavnich architektu WPF u MS, takze za me: kdyz MVVM, tak WPF, jinak to bude vzdycky drbani se levou rukou za pravym uchem.

Offline egroups

  • Nováček
  • *
  • Příspěvků: 17
  • Karma: 0
Re:MVVM+DSharp+Spring4D DI+Marhmallow
« Odpověď #7 kdy: 24-11-2017, 09:45:32 »
Tak první část problému jsem vyřešil.Samozřejmě jsem se do toho zamotal tak,že jsem přehlédl základní triviální věc.
Ve View jsem upravil:
Kód: Delphi [Vybrat]
  1. [Inject(ViewModel_Ciselnik_Skupina_Intf)]
  2. property ViewModel: ICiselnikViewModel read FViewModel write SetViewModel;
  3.  
Nyní mohu ve View použít třeba něco takového:
Kód: Delphi [Vybrat]
  1. procedure TVSkupinaIntf.actEditExecute(Sender: TObject);
  2. begin
  3.   SwitchToDetail;
  4.   ActiveControl:=edtNazev;
  5.   ViewModel.Edit;
  6. end;
  7.  
Takže mohu formulář udělat "nezbytnosti" o kterých ViewModel vůbec neví a pak předat práci ViewModelu.
Jediný problém je,že TBindingGroup potřebuje TObject,ne Interface,takže tohle neprojde:
Kód: Delphi [Vybrat]
  1. bgMain.AddBinding(ViewModel,'ViewTitle',lblTitle,'Caption',bmOneWay);
  2.  
Musím to přetypovat:
Kód: Delphi [Vybrat]
  1. lVM:=ViewModel as TSkupinaCiselnikViewModel;
  2. bgMain.AddBinding(lVM,'ViewTitle',lblTitle,'Caption',bmOneWay);
  3.  
což se mi sice moc nelíbí,ale zatím to funguje.

Offline Delfin

  • Hrdina
  • ****
  • Příspěvků: 431
  • Karma: 21
  • SW konzultant
    • Verze Delphi: 2009, Tokyo
Re:MVVM+DSharp+Spring4D DI+Marhmallow
« Odpověď #8 kdy: 24-11-2017, 14:05:06 »
Pro Delfin:
Přesně,jak píše pf1957.Session s tím skoro nemá vůbec nic společného,protože stručně řečeno je to takto ViewModel<->Repository<->Session<->DB.Na této straně nemám problém s chápaním.A ten příklad,který uvádíš,je sice hezký,ale přesně tomuhle se chci vyhnout,aby ve formuláři bylo úplně vše,včetně komunikace s DB.Ten formulář nesmí vědět nic o DB.

Chyba je na me strane, otazku jsem necetl celou :-[ Asi uz chapu o co Ti jde.. Spring4D s DSharp nepouzivam, ale muzu se do nuch podivat :P
A co chudinky ovce? Koupíš jim snad plovací vesty? Nebo jim nasadíš chůdy? Ještě lepší, kdybys je zkřížil s delfíny na ovce hopkavé!

 

S rychlou odpovědí můžete používat BB kódy a emotikony jako v běžném okně pro odpověď, ale daleko rychleji.

Jméno: E-mail:
Ověření:
Datový typ v Delphi, který má True a False: