2017-11-24 1 views
0

eine Ansicht Modell SomeViewModel von SomeView verwendet Stellen Sie sich vor:Wie ViewModel zu machen - View-Oberfläche klarer?

public class SomeViewModel : BindableBase 
{ 
    public NumberEditorViewModel VoltageEditor { get; private set; } 

    public SomeViewModel() 
    { 
     VoltageEditor = new NumberEditorViewModel(...) { ... } 
    } 
} 

, die für die Bearbeitung aller Zahlen eine wieder verwendbare Unteransicht Modell verwendet, so etwas wie:

public class NumberEditorViewModel: BindableBase 
{ 
    public string Title { get; private set; } 
    public string Value { get; set; } 
    public string Unit { get; private set; } 

    ... 
} 

So ist der Ansicht, es wie folgt verwendet werden:

<StackPanel Orientation="Horizontal"> 
    <TextBlock Text={Binding VoltageEditor.Title} /> 
    <TextBox Text={Binding VoltageEditor.Value} /> 
    <TextBlock Text={Binding VoltageEditor.Unit} /> 
</StackPanel> 

Diese Eigenschaften - Title, Value, Unit sind, worauf die Ansicht zugreifen wird.

in Wirklichkeit aber die NumberEditorViewModel ist eine komplizierte Klasse, bietet Einheitenumrechnung (metrisch/imperial), Formate Werte, so dass sie die richtige Präzision (Formatstrings) etc. Aus diesem Grund haben sie eine ganze Reihe von Eigenschaften angeben, welche Einheit es verwendet, welche Art von Genauigkeit erforderlich ist etc., so kann es den Wert richtig formatieren und analysieren. Sie würden in SomeViewModel eingestellt werden, wie:

VoltageEditor = new NumberEditorViewModel() 
{ 
    Title = "My Voltage:", 
    UnitType = UnitType.Voltage, 
    PrecisionType = PrecisionType.SomePrecision, 
    ... 
} 

Dieses NumberEditorViewModel Modell unordentlich macht, weil es eine Menge von Eigenschaften hat, und niemand weiß, wer von ihnen sind von der Ansicht verwendet werden soll, und welche durch verwendet werden der Ersteller dieses Objekts, um sein Verhalten zu steuern.

Wie kann ich dies verbessern, um eine klarere Schnittstelle zwischen der Ansicht und dem Ansichtsmodell zu schaffen, und auch zwischen diesem Ansichtsmodell und seinem Ersteller?

Ich habe versucht, eine Schnittstelle zu verwenden, um der Ansicht nur eine Teilmenge von Eigenschaften zu geben, aber WPF scheint dies zu ignorieren und verwendet alles, was auf dem Objekt für die Bindung verfügbar ist. Und dies tötet auch den ersten Ansatz des Betrachtungsmodells.

+0

Sie könnten View-Modell ein anderes Objekt wie 'NumberViewModelConfig' übergeben, aber es ist schwer, dies als ein Problem zu sehen ... Wen kümmert es, dass die Ansicht wissen muss, an welche Eigenschaften zu binden? Es ist unvermeidlich, da jede Bindung eine Eigenschaft spezifizieren muss, so dass eine Schnittstelle oder irgendetwas anderes nur für mehr Arbeit nicht viel Gewinn scheint. Vielleicht ist nur eine gute Dokumentation die Antwort hier. – blins

+0

Übergeben Sie Eigenschaften, die das Verhalten im Konstruktor steuern? – Evk

+0

@Evk das ist, was ich jetzt mache, aber der Konstruktor bekommt ~ 10 Parameter an diesem Punkt und es wächst - es wird unordentlich. Ganz zu schweigen davon, dass Sie sie später nicht ändern können. – Arek

Antwort

0

Entschuldigen Sie, dass ich zuerst frage, und dann antworte ich alleine, ich fühle mich schlecht dabei. Aber vielleicht hilft es jemandem.

Ich glaube, ich hatte da ein Designproblem, was dazu führte, dass zu viel Zeug in das View-Modell gesteckt wurde. Die sauberste Lösung scheint es zu sein, all diese Funktionalität zu einem separaten (logischen) Objekt zu extrahieren. Und dann das View-Modell (numerisches Sub-View-Modell) als ein sehr einfaches Objekt, das das logische Objekt im Konstruktor übernimmt. Ich denke @Evk hat etwas Ähnliches vorgeschlagen (Danke), aber ohne die ganze Logik zu verändern.

Wenn es erforderlich ist, das Verhalten weiter zu ändern (dh etwas nur lesen), geschieht dies durch Ändern der Eigenschaften des logischen Objekts.

Die einzigen Eigenschaften auf dem Ansichtsmodell sind jetzt diese, die von der Ansicht benötigt werden.

Am Ende sieht es so aus (Entschuldigung, Komplikationen im Code, aber das sind echte Produktionscode-Ausschnitte, zum besseren Verständnis).

Logical Objekt:

public class RealParameter<T> : BindableBase2 where T : struct 
{ 
    private readonly IUnitService _unitService; 
    private readonly IValueProvider<T> _valueProvider; 
    private readonly IValueReceiver<T> _valueReceiver; 
    private readonly IValueProvider<bool> _canUseProvider; 
    private bool _canUseOverride = true; 
    private readonly IValueProvider<bool> _readOnlyProvider; 
    private bool _readOnlyOverride = false; 
    private UnitType _unitType; 
    private PrecisionType _precisionType; 
    private string _customFormatString; 
    private string _customUnitString; 

    public RealParameter(
     IUnitService unitService, 
     IValueProvider<T> valueProvider, 
     IValueReceiver<T> valueReceiver = null, 
     UnitType unitType = UnitType.None, 
     PrecisionType precisionType = default(PrecisionType), 
     IValueProvider<bool> canUseProvider = null, 
     IValueProvider<bool> readOnlyProvider = null) 
    { 
     _unitService = unitService; 
     _valueProvider = valueProvider; 
     ... 
    } 

    public UnitType UnitType 
    { 
     get { return _unitType; } 
     set 
     { 
      _unitType = value; 
      OnPropertyChanged(); 
     } 
    } 

    public PrecisionType PrecisionType 
    { 
     get { return _precisionType; } 
     set 
     { 
      _precisionType = value; 
      OnPropertyChanged(); 
     } 
    } 

    public T Value 
    { 
     get ... 
     set ... 
    } 

    public T LocalValue 
    { 
     // unit conversion goes here 
     get ... 
     set ... 
    } 

    [DependsOn(nameof(LocalValue))] 
    [DependsOn(nameof(CustomFormatString))] 
    [DependsOn(nameof(PrecisionType))] 
    public string LocalValueDisplay 
    { 
     // formatting to/parsing from a string goes here 
     get ... 
     set ... 
    } 

    ... 
} 

Numerische Ansicht Modell:

public class RealParameterVm<T> : BindableBase2 where T : struct 
{ 
    private readonly RealParameter<T> _parameter; 
    private readonly string _title; 

    public RealParameterVm(RealParameter<T> parameter, string title = null) 
    { 
     _parameter = parameter; 
     _title = title; 

     _parameter.PropertyChanged += (s, e) => 
     { 
      if (e.PropertyName == nameof(_parameter.LocalValueDisplay)) 
       OnPropertyChanged(nameof(Value)); 

      if (e.PropertyName == nameof(_parameter.UnitDisplay)) 
       OnPropertyChanged(nameof(Unit)); 

      ... 
     }; 
    } 

    public string Title => _title; 

    public string Value 
    { 
     get { return _parameter.LocalValueDisplay; } 
     set { _parameter.LocalValueDisplay = value; } 
    } 

    public string Unit => _parameter.UnitDisplay; 

    public string ValueAndUnit => _parameter.LocalValueAndUnitDisplay; 

    public bool CanUse => _parameter.CanUse; 

    public bool IsReadOnly => _parameter.IsReadOnly; 

    public T NumericGlobal => _parameter.Value; 

    public T NumericLocal => _parameter.LocalValue; 
} 

und Verwendung in der Hauptansicht Modell der Ansicht:

public class SomeViewModel : BindableBase 
{ 
    private readonly IPlcService _plc; 
    private readonly IUnitService _unitService; 
    public RealParameterVm<double> Distance { get; } 

    public SomeViewModel(IPlcService plc, IUnitService unitService) 
    { 
     _plc = plc; 
     _unitService = unitService; 

     Distance = new RealParameterVm<double>(new RealParameter<double>(
      _unitService, 
      _plc.Main.Interface.HmiDev.Distance, 
      _plc.Main.Interface.HmiDev.DistanceCanUse, 
      _plc.Main.Interface.HmiDev.DistanceIsReadOnly, 
      UnitType.Distance_mm, 
      PrecisionType.Position 
      ), "Some distance:"); 
    } 
} 

Und XAML:

<StackPanel Orientation="Horizontal" IsEnabled="{Binding Distance.CanUse}"> 
    <TextBlock Text={Binding Distance.Title} /> 
    <TextBox Text={Binding Distance.Value} IsReadOnly="{Binding Distance.IsReadOnly}" /> 
    <TextBlock Text={Binding Distance.Unit} /> 
</StackPanel> 
1

Hier ist eine Idee, anstatt eine Schnittstelle zu verwenden, um eine Teilmenge von Eigenschaften zu geben, könnten Sie Unterklassen als Adapter verwenden.

Jede Unterklasse kann von einem Getter von Ihrem NumberEditorViewModel gegeben werden.

Etwas wie folgt aus:

public class NumberEditorViewModel 
{ 
    // quick & dirty 
    public SubTypeA ExampleA { get { return new SubTypeA (this); } } 
    public SubTypeB ExampleB { get { return new SubTypeB (this); } } 
} 

Dann Ihrer Ansicht:

<StackPanel Orientation="Horizontal"> 
    <TextBlock Text={Binding VoltageEditor.ExampleA.Title} /> 
    <TextBox Text={Binding VoltageEditor.ExampleA.Value} /> 
    <TextBlock Text={Binding VoltageEditor.ExampleA.Unit} /> 
</StackPanel> 
  • Bearbeiten -

(1) Wir sind in der Regel für diese Art verschachtelte Klassen zu verwenden, von Ding, aber es ist nur eine Frage der Präferenz.

(2) Hier ist ein approch, die das Property Ereignis zwischen Subtypen erhöhen wird:

Sie können das Property Ereignis zwischen Viewmodels kommunizieren nutzen, da sie eng mit jedem anderen verbunden sind.

public class NumberEditorViewModel : BindableBase 
{ 
    // subtypes /////////////////////////////// 

    public class SubTypeA 
    { 
     public string PrecisionType 
     { 
      get { return _precisionType; } 
      set { _precisionType = value; OnPropertyChanged (); } 
     } 
     private string _precisionType; 
    } 

    public class SubTypeB 
    { 
     public string INPC 
     { 
      get { return _inpc; } 
      set { _inpc = value; OnPropertyChanged (); } 
     } 
     private string _inpc; 
    } 

    // ctor /////////////////////////////// 

    public NumberEditorViewModel () 
    { 
     ExampleA = new SubTypeA (); 
     ExampleB = new SubTypeB (); 

     ExempleA.PropertyChanged += (sender, e) => 
     { 
      if (e.PropertyName == nameof (SubTypeA.PrecisionType)) 
      { 
       var exempleA = sender as ExempleA; 

       ExampleB.INPC = ... /* newValue calculated */ 
      } 
     } 
    } 

    public SubTypeA ExampleA { get; private set; } 
    public SubTypeB ExampleB { get; private set; } 
} 
+0

Das klingt nach einer Idee. Ein zusätzlicher Schritt in dem Bindungspfad ist ein Nachteil, aber möglicherweise akzeptabel. Fragen, die ich habe: (1) sind SubTypeX verschachtelte Klassen oder nur normale Typen? (2) Nehmen wir an, jemand ändert die Eigenschaft "SubTypeA.PrecisionType". Wie rufe ich 'INPC.PropertyChanged' auf' SubTypeB.Value' auf? – Arek

+0

Ich habe meinen Beitrag bearbeitet, um einen Vorschlag hinzuzufügen. – Seb

+0

Endete die Festlegung eines Entwurfs (siehe eine andere Antwort), aber aktualisierte diesen. – Arek

Verwandte Themen