2014-09-26 7 views
12

Ich benutze Visual Studio 2013-Designer, um meine Benutzersteuerung in WPF zu erstellen, und ich verwende einen MVVM-Ansatz.Design-Time-Setup eines ViewModel

Ich versuche den besten Weg zu finden, "Design-Time" Setup meines Viewmodels zu haben, so dass ich sofort den Effekt im Designer sehen kann, zB den Wert einer Eigenschaft zu ändern. Ich habe verschiedene Designs und Techniken verwendet, um dies zu unterstützen, aber nichts ist genau das, was ich will. Ich frage mich, ob jemand bessere Ideen hat ...

Situation (vereinfacht): So habe ich ein "Gerät", ich möchte ein UserControl, um Zustände und Operationen anzuzeigen. Von oben nach unten:

  • Ich habe eine IDeviceModel die ein Feld bool IsConnected {get;} (und ordnungsgemäße Benachrichtigung von Zustandsänderungen)
  • ich eine FakeDeviceModel haben hat die IDeviceModel implementiert und somit ermöglicht es mir, nicht auf einem realen Gerät verlassen für Entwurfszeit und Testen
  • Ein DeviceViewModel, das ein IDeviceModel enthält und die Eigenschaften des Modells einkapselt. (Ja hat es richtig INotifyPropertyChanged-Benachrichtigungen in it)
  • Mein Usercontrol, die eine Datacontext vom Typ DeviceViewModel haben, und würde einen benutzerdefinierten Stil CheckBox haben, die IsChecked={Binding IsConnected, Mode=OneWay ist
  • Mein Ziel: Ich möchte auf Design-Zeit, um eine Vorschau wie funktioniert das IsConnected Zustand Modell beeinflusst meine Usercontrol (so könnte es andere Dinge beeinflusst als nur IsChecked)

Framework:

  • ich die Idee des MVVM Licht ViewModelLocator verwenden, Rückkehr nicht-statische Felder (so neu ich nstances von ViewModels). Zur Laufzeit wird die reale Datacontext von dem einen gegeben werden Instanziierung dieses Usercontrol

d:DataContext="{Binding DeviceViewModelDesignTime, Source={StaticResource ViewModelLocator}}"

public class ViewModelLocator 
{ 
    private static MainWindowViewModel _mainWindowViewModel; 
    public MainWindowViewModel MainWindowViewModelMainInstance 
    { 
     get 
     { 
      if (_mainWindowViewModel == null) 
      { 
       _mainWindowViewModel = new MainWindowViewModel(); 
      } 
      return _mainWindowViewModel; 
     } 
    } 

    public DeviceViewModel DeviceViewModelDesignTime 
    { 
     get 
     { 
      //Custom initialization of the dependencies here 
      //Could be to create a FakeDeviceModel and assign to constructor 
      var deviceViewModel = new DeviceViewModel(); 

      //Custom setup of the ViewModel possible here 
      //Could be: deviceViewModel.Model = new FakeDeviceModel(); 

      return deviceViewModel; 
     } 
    } 

Lösungen Ich habe versucht:

Compile-Time-Lösung

einfach codieren die Setup des ViewModels im ViewModelLocator.

var deviceViewModel = new DeviceViewModel(fakeDeviceModel); 
var fakeDeviceModel = new FakeDeviceModel(); 
fakeDeviceModel.IsConnected = true; 
deviceViewModel.AddDevice(fakeDeviceModel); 

Vorteile: Einfache

Nachteile: Das ist mehr Iterationen immer den Wert in Code wird sich ändern, neu kompilieren, um Designer-Ansicht zurück, warten Ergebnis

Instanz in Ressourcen und gehalten statisch in ViewModelLocator

Also ich erstelle eine Instanz in XAML und ich versuche, es in das aktuelle ViewModel vom Designer zu schieben.Nicht der sauberste Weg, arbeitete aber für eine Weile in einfacher Situation (ja, es gibt einige wierdness mit der Sammlung, war aber mit der Idee, dass ich mehrere Geräte und aktuellen haben könnte)

XAML:

<UserControl x:Class="Views.StepExecuteView" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     mc:Ignorable="d" 
     d:DataContext="{Binding DeviceViewModelDesignTime, Source={StaticResource ViewModelLocator}}"> 
<UserControl.Resources> 
    <viewModels:DesignTimeDeviceManager x:Key="DesignTimeDeviceManager"> 
     <viewModels:DesignTimeDeviceManager.DesignTimeDevices> 
      <device:FakeDeviceModel IsConnected="True" 
            IsBusy="False" 
            IsTrayOpen="True" 
            NumberOfChipSlots="4" 
            /> 
     </viewModels:DesignTimeDeviceManager.DesignTimeDevices> 

[... CheckBox binding to datacontext and so on...] 

Und ViewModelLocator.cs:

public class ViewModelLocator 
{ 
    private static MainWindowViewModel _mainWindowViewModel; 
    public MainWindowViewModel MainWindowViewModelMainInstance 
    { 
     get 
     { 
      if (_mainWindowViewModel == null) 
      { 
       _mainWindowViewModel = new MainWindowViewModel(); 
      } 
      return _mainWindowViewModel; 
     } 
    } 

    public static FakeDeviceModel DeviceModelToAddInDesignTime; 
    public DeviceViewModel DeviceViewModelDesignTime 
    { 
     get 
     { 
      var deviceViewModel = new DeviceViewModel(); 
      if (DeviceModelToAddInDesignTime != null) 
       deviceViewModel.AddDevice(DeviceModelToAddInDesignTime); 

      return deviceViewModel; 
     } 
    } 
} 

public class DesignTimeDeviceManager 
{ 
    private ObservableCollection<FakeDeviceModel> _DesignTimeDevices; 
    public ObservableCollection<FakeDeviceModel> DesignTimeDevices 
    { 
     get { return _DesignTimeDevices; } 
     set 
     { 
      if (_DesignTimeDevices != value) 
      { 
       _DesignTimeDevices = value; 
       ViewModelLocator.DeviceModelToAddInDesignTime = value.FirstOrDefault(); 
      } 
     } 
    } 
} 

Pro:

  • Arbeitete super toll auf einer projec t. Die Instanz, die ich in XAML hatte, konnte ich die booleans ändern und ich würde -im- momented- Feedback bekommen, wie es mein UserControl beeinflusst. So in der einfachen Situation, muss die Checkbox des „Karo“ Zustand würde sich ändern, und ich konnte mein Styling in Echtzeit ändern, ohne dass

Cons neu kompilieren:

Es blieb in einem anderen Projekt arbeiten, und diese Ich selbst konnte den Grund nicht finden. Aber nach dem Kompilieren und Ändern von Sachen, würde der Designer mir Ausnahmen geben, die aussehen wie "FakeDeviceModel" kann nicht in "FakeDeviceModel" umgewandelt werden !! Meine Vermutung ist, dass der Designer intern Caches für diese Typen kompiliert und verwendet (C: \ Benutzer \ Vorname.Nachname \ AppData \ Lokal \ Microsoft \ VisualStudio \ 12.0 \ Designer \ ShadowCache). Und das in meiner Lösung, abhängig von der Reihenfolge der Dinge, habe ich ein "FakeDeviceModel" erstellt, das einer statischen Instanz zugewiesen wurde, und "später", wenn der ViewModelLocator das nächste Mal nach einem ViewModel gefragt wird, würde es das verwenden Beispiel. Wenn er in der Zwischenzeit "neu kompiliert" oder einen anderen Cache benutzt, dann ist es nicht "genau" der gleiche Typ. Also musste ich den Designer (XDescProc) töten und neu kompilieren, damit es funktioniert, und dann ein paar Minuten später wieder fehlschlagen. Wenn mich jemand hier korrigieren kann, wäre das großartig.

Multi-Bindung für d: Datacontext und benutzerdefinierte Konverter

Das Problem der bisherigen Lösung mich auf die Tatsache hinwies, dass das Ansichtsmodell und die FakeDeviceModel in der Zeit zu verschiedenen Zeitpunkt erstellt wurden (die Art/cast Problem geben) und es zu lösen, würde ich brauche sie zugleich

XAML zu erstellen:

<UserControl x:Class="MeltingControl.Views.DeviceTabView" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     mc:Ignorable="d"> 
<d:UserControl.DataContext> 
    <MultiBinding Converter="{StaticResource DeviceDataContextConverter}"> 
     <Binding Path="DeviceViewModelDesignTime" Source="{StaticResource ViewModelLocator}" /> 
     <Binding> 
      <Binding.Source> 
       <device:FakeDeviceModel IsConnected="False" 
            IsBusy="False" 
            IsTrayOpen="False" 
            SerialNumber="DesignTimeSerie" 
            /> 
      </Binding.Source> 
     </Binding> 
    </MultiBinding> 
</d:UserControl.DataContext> 

public class DeviceDataContextConverter: IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (values == null || values.Length == 0) 
      return null; 

     var vm = (DeviceViewModel)values[0]; 
     if (values.Length >= 2) 
     { 
      var device = (IDeviceModel)values[1]; 
      vm.AddDevice(device); 
     } 

     return vm; 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    { 
     throw new NotImplementedException(); 
    } 
} 

Pros: -Works super nett! Wenn für die Datacontext die Bindung für das Ansichtsmodell fragt, nutze ich den Konverter, dass Ansichtsmodell zu ändern und das Gerät zu injizieren, bevor es

Cons Rückkehr:

Wir intelissense verlieren (mit ReSharper), da er doesn t wissen, welcher Typ vom Konverter zurückgegeben wird

Weitere Ideen oder Modifikationen, die ich machen könnte, um dieses Problem zu lösen?

+0

Haben Sie das in Blend versucht? Mit Visual Studio 2010 war es einfacher, mit diesem Muster in Blend als mit Visual Studio zu arbeiten, da der Designer robuster war. Ich bin mir nicht sicher, wie Blend VS2013 vergleicht. –

+0

Wie hast du das in Blend gehandhabt? – FrankyB

+0

VS und Blend teilen den gleichen Designer jetzt, obwohl nicht alle die gleiche Funktionalität in keiner Weise. Obwohl VS den Apfelwein-Designer nicht mehr verwendet, können Sie immer noch eine Menge von dem tun, was Sie in VS jetzt mischen können/wollten. –

Antwort

4

Sie können eine Entwurfszeit Ansichtsmodell erstellen, das IsConnected = true (FakeDeviceViewModel) und legen Sie es als eine Entwurfszeitdatenkontext zurückgibt:

d:DataContext="{d:DesignInstance viewModels:FakeDeviceViewModel, 
IsDesignTimeCreatable=True}" 
0
  1. ich zum Beispiel eine Instanz von Fake Ansicht Modell in einem separaten XAML schaffen würde DeviceViewModelDataSample.xaml (siehe Beispiel unten)

  2. Set Build Action auf DesignData

  3. Referenz die Datei als solche

    <UserControl x:Class="YourNameSpace.YourControl" 
           xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
           mc:Ignorable="d" 
           d:DataContext="{d:DesignData Source=/DataSample/DeviceViewModelDataSample.xaml}"> 
    <!-- Skiped details for brevity --> 
    </UserControl> 
    

DeviceViewModelDataSample.xaml

<vm:DeviceViewModel xmlns:dm="clr-namespace:YourNameSpace.DataModel" 
       xmlns:vm="clr-namespace:YourNameSpace.ViewModel"> 
    <vm:DeviceViewModel.DeviceManager> <!-- Assuming this is a collection --> 
     <dm:DeviceModel DeviceName="Fake Device" IsConnected ="true" /> <!-- This creates an instance at design time --> 
    </vm:DeviceViewModel.DeviceManager>  
</vm:DeviceViewModel> 
0

Ich möchte eine alternative Lösung vorschlagen.

Sie könnten dasselbe Ansichtsmodell für Entwurfszeitdaten und normale Laufzeit verwenden und in Ihrem (einzelnen) Ansichtsmodell überprüfen, ob der Designer aktiv ist, und dann die Entwurfszeitdaten dort laden.

Ihrer Ansicht nach Modell würden Sie so etwas tun:

public class ExampleViewModel : ViewModelBase 
{ 
    public ExampleViewModel() 
    { 
     if (IsInDesignMode == true) 
     { 
      LoadDesignTimeData(); 
     } 
    } 

    private void LoadDesignTimeData() 
    { 
     // Load design time data here 
    }  
} 

Die IsInDesignMode Eigenschaft könnte Ihrer Ansicht nach Modell Basisklasse platziert werden - falls Sie einen haben - und sieht wie folgt aus:

DesignerProperties.GetIsInDesignMode(new DependencyObject()); 

Bitte werfen Sie einen Blick auf meine Antwort here