WPF, Dialog Service come servizio iniettabile o classe statica

1

Lo sfondo

In MVVM, tendiamo a "non" voler accoppiare finestre per visualizzare i modelli per vari motivi. Anche se dalla seconda schermata della tua applicazione inizi a colpire questa zona grigia concettuale.

Il problema

Vedete che i viewmodals tendono a voler aprire Windows / Views (è solo un fatto della vita). Le API di Windows / dialog tendono ad amare un proprietario, perché questo è solo la natura del sistema operativo Windows. Come si può vedere qui Window.Owner

When a child window is opened by a parent window by calling ShowDialog, an implicit relationship is established between both parent and child window. This relationship enforces certain behaviors, including with respect to minimizing, maximizing, and restoring.

Tutto ciò crea un po 'di dilemma in cui Viewmodals è disconnesso in modo nativo dalla loro vista / finestra, quindi è necessario utilizzare una sorta di aggregazione di eventi di messaggistica disaccoppiata, o qualche altra logica per accoppiare la relazione di dialogo.

A questo punto le persone usano framework o altri regimi che consistono fondamentalmente nel mondo strano e inventato di DI / IOC, visualizzare costruttori, locatori, messaggi o aggregazione di eventi per fare tutto l'accoppiamento, la rotazione di viewmodals e così via e mantenere il MVVM polizia felice.

La soluzione attuale

Nei progetti attuali su cui sto lavorando, il principale sviluppatore ha risolto questo problema usando i messaggi doppi disaccoppiati (pub / sub), che vengono rilevati in un costruttore di viste con un regime di mappatura delle viste dell'applicazione che mappa questi messaggi per visualizzare a Windows, converte e passa oggetti ai modelli di vista appena creati attraverso più messaggi in quello che può essere chiamato solo più spurio idraulico, e alla fine della finestra di dialogo lifecyles restituisce i parametri indietro nel messaggio digitato originale che può essere esaminato dal chiamante originale.

Questo è tutto molto confuso (IMO) eccessivamente complicato progettato eccessivamente e inadeguato progettato.

Servizio di dialogo

Immagino questo in cui entra in gioco la nozione di un servizio di dialogo. Una semplice libreria Fluent che può usare viewmap o comandi implicitamente accoppiati per chiamare, finestre a rotazione.

// Modal Dialog no results
_dialogService.Dialog<Window1Vm>(this)
              .OnInit(vm => vm.SomeProperty = false)
              .ShowDialog();

// Dialog with some results  
var someResult = _dialogService.Dialog<Window1Vm>(this)
                               .OnInit(vm => vm.SomeProperty = false)
                               .ShowDialog();

// Dialog with some input params
var someResult = _dialogService.Dialog<Window1Vm>(this)
                               .WithParams(new SomeParamsToPassIn())
                               .ShowDialog();

// And the one the "decoupled police" will be wanting to arrest me for
// Dialog with explicit notation to map a view to a viewmdal
// (I know i should be shot and fed to the wolves)
var dialog1 = _dialogService.Dialog<Window1Vm, Window1>(this)
                            .OnInit(vm => vm.SomeProperty = false)
                            .ShowDialog();

La mia domanda ora è, se questo è un servizio iniettabile, o una classe statica

I vantaggi di un servizio iniettabile sono che, ViewModals e il codice che hanno bisogno di accedere a questo, possono solo chiederlo. È facile vedere chi sta implorando questa funzionalità tramite il costruttore

Gli svantaggi sono se si hanno elenchi di elenchi di vms e qualcosa nell'elenco dei child deve aprire una finestra, è necessario inserirlo in tutte le viewmodals secondarie o inviare nuovamente un messaggio.

I vantaggi di essere una classe statica, è che puoi chiamarla ovunque, un po 'come una vera preoccupazione trasversale dell'interfaccia utente.

Personalmente ritengo che il servizio DI sia la strada da percorrere, non ha una logica di stato, quindi può essere un singleton. Tuttavia ho avuto diversi commenti su questo approccio (principalmente dallo sviluppatore principale) che i messaggi sono solo migliori e possono essere chiamati da qualsiasi luogo, anche che la staticità è male mmkay.

Tuttavia voglio solo avere delle opinioni su se la logica, come questo si adatta meglio come un servizio iniettabile, una libreria statica, o in altre esperienze che potrebbero avere un sistema totalmente disaccoppiato come quello che abbiamo in realtà è un approccio migliore in grandi sistemi e lungo termine

    
posta TheGeneral 04.02.2018 - 05:26
fonte

1 risposta

1

Il tuo approccio attuale suona come il modello di mediatore. Quale è una soluzione riconosciuta a questo tipo di problema e molti altri problemi di comunicazione cross ViewModel.

Tuttavia, sono d'accordo sul fatto che possa essere complicato. soprattutto con qualcosa di semplice come voler aprire una nuova finestra e averli ovunque.

Effettivamente nella cosa a cui sto lavorando attualmente usiamo un MessageBox di terze parti (dall'aspetto professionale) che usa un metodo statico. Non lo consiglierei assolutamente perché è un incubo da testare.

Ecco un approccio alternativo, che consente di mantenere l'associazione in XAML e la creazione del modello di visualizzazione e l'attivazione di eventi in ViewModel

Quindi abbiamo un comportamento con una proprietà di dipendenza di azione che può essere associata a:

public class OpenWindowBehaviour : Behavior<Control>
{
    public Type TypeOfWindow { get; set; }

    public Action<object> OpenWindowWithVM
    {
        get { return (Action<object>)this.GetValue(OpenWindowWithVMProperty); }
        set { this.SetValue(OpenWindowWithVMProperty, value); }
    }

    public static readonly DependencyProperty OpenWindowWithVMProperty = 
        DependencyProperty.Register(
            "OpenWindowWithVM", 
            typeof(Action<object>), 
            typeof(OpenWindowBehaviour), 
            new PropertyMetadata(null)
            );

    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.Loaded += AssociatedObject_Loaded;
    }

    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        this.OpenWindowWithVM = OpenWindow;
    }

    private void OpenWindow(object ViewModel)
    {
        var constructor = TypeOfWindow.GetConstructors()[0];
        var w = constructor.Invoke(new object[] { }) as Window;
        w.DataContext = ViewModel;
        w.Show();
    }
}

Dobbiamo attendere l'evento Loaded, che viene dopo l'associazione, in modo che possiamo impostare l'azione associata come il nostro codice Open New Window.

Ora nel nostro ViewModel abbiamo un "evento" di azione e un ICommand che legheremo a un pulsante. Quando si preme il pulsante, passeremo al passaggio successivo per lo pseudo evento StageChanged a cui passiamo il modello di visualizzazione per lo stage 2

public class ViewModel : INotifyPropertyChanged
{
    public Action<object> StageChanged {
        get;
        set;
    }

    public ICommand MoveToNextStage { get; set; }

    public ViewModel()
    {
        MoveToNextStage = new DelegateCommand((o) =>
        {
            if (this.StageChanged != null)
            {
                StageChanged( new Stage2VM());
            }
        });
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

il nostro xaml lega insieme i due, in modo che premendo il pulsante si attivi l'evento comportamentale.

<Window
namespaces.....
>
    <i:Interaction.Behaviors>
        <local:OpenWindowBehaviour 
            TypeOfWindow="{x:Type local:NewWindow}" 
            OpenWindowWithVM="{Binding StageChanged, Mode=TwoWay}"/>
    </i:Interaction.Behaviors>
    <Grid>
        <Button Content="Click Me" Command="{Binding MoveToNextStage}"/>
    </Grid>
</Window>

(Sto usando Fody PropertyChanged per il boilerplate e Expression.Blend.Sdk per i comportamenti di Windows.Interactivity)

    
risposta data 04.02.2018 - 09:11
fonte

Leggi altre domande sui tag