2014-09-26 16 views
12

Używam projektanta Visual Studio 2013 do tworzenia kontroli użytkownika w WPF i używam podejścia MVVM.Ustawianie czasu projektowania ViewModel

Próbuję znaleźć najlepszy sposób na ustawienie "Design-Time" mojego viewmodelu, tak aby od razu zobaczyć efekt w projektancie zmiany wartości właściwości na przykład. Użyłem różnych projektów i technik, aby to wspierać, ale nic nie jest dokładnie tym, czego chcę. Zastanawiam się, czy ktoś ma lepsze pomysły ...

Sytuacja (uproszczona): Mam więc "urządzenie", które chcę, aby UserControl pokazywał stany i operacje. Od góry do dołu:

  • Mam IDeviceModel który ma pole bool IsConnected {get;} (i właściwego powiadomienia o zmianach stanu)
  • Mam FakeDeviceModel który implementuje IDeviceModel, a tym samym pozwala mi nie polegać na rzeczywistym urządzeniu czas projektowania i testowanie:
  • Urządzenie DeviceViewModel, które zawiera IDeviceModel i enkapsuluje właściwości modelu. (Tak, że ma odpowiednie powiadomienia INotifyPropertyChanged w nim)
  • Moje UserControl które będą miały DataContext typu DeviceViewModel i musiałby niestandardowego stylu CheckBox który IsChecked={Binding IsConnected, Mode=OneWay
  • Mój cel: chcę obejrzeć na czasie projektowania jak robi stan IsConnected modelu wpływają mojego UserControl (więc może to mieć wpływ na inne rzeczy niż tylko IsChecked)

ramowa:

  • używam pomysł MVVM światła ViewModelLocator, wracając pola non-statyczne (tak nowy ja właściwości ViewModels). W czasie wykonywania prawdziwy datacontext zostaną podane przez jednego instanciating ten 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; 
     } 
    } 

Solutions Próbowałem:

rozwiązanie kompilacji

Wystarczy Kodeksu konfiguracja ViewModel w ViewModelLocator.

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

Plusy: Proste

Wady: To dłuższe iteracji zawsze zmieni wartość w kodzie, rekompilacji, wróć do projektanta widoku czekać na wynik

Instancji w zasobach i przechowywane static w ViewModelLocator

Tak więc tworzę instancję w XAML i próbuję ją wepchnąć do bieżącego ViewModel używanego przez projektanta.Nie najczystszy sposób, ale pracował przez jakiś czas w prostych sytuacji (tak jest jakaś wierdness z kolekcji, ale z myślą, że mogę mieć wiele urządzeń i stare)

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...] 

I 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(); 
      } 
     } 
    } 
} 

Plusy:

  • Pracował bardzo dobrze na jednym projec t. instancja, którą miałem w XAML, mogłem zmodyfikować booleans i otrzymywałbym natychmiastową informację zwrotną o tym, jak wpływa ona na moją UserControl. Tak więc w prostej sytuacji „sprawdzone” stan pole wyboru byłby zmienić i mogę zmodyfikować moje stylizacji w czasie rzeczywistym, bez konieczności rekompilacji

Wady:

przestał działać w innym projekcie, a to Sam nie mogłem znaleźć przyczyny. Ale po ponownym skompilowaniu i zmianie rzeczy, projektant dałby mi wyjątki wyglądające jak "Nie mogę rzucić" FakeDeviceModel "na" FakeDeviceModel "" !! Domyślam się, że Designer wewnętrznie kompiluje i wykorzystuje pamięci podręczne dla tych typów (C: \ Users \ firstname.lastname \ AppData \ Local \ Microsoft \ VisualStudio \ 12.0 \ Designer \ ShadowCache). I w moim rozwiązaniu, w zależności od porządku rzeczy, tworzyłem "FakeDeviceModel", który został przypisany statycznym instancjom i "później", następnym razem, gdy ViewModelLocator zostanie poproszony o ViewModel, użyłby tego instancja. Jeśli jednak w międzyczasie "przekompiluje" lub użyje innej pamięci podręcznej, to nie jest "dokładnie" tego samego typu. Musiałem więc zabić projektanta (XDescProc) i przekompilować go, aby działał, a następnie ponowić awarię kilka minut później. Jeśli ktoś może mnie poprawić, byłoby wspaniale.

Wielu Oprawa do d: DataContext i niestandardową konwertera

Problem poprzedniego rozwiązania była wskazując mnie fakt, że ViewModel i FakeDeviceModel powstały w innym momencie w czasie (podając typ/Problem cast) i go rozwiązać, to trzeba utworzyć je w tym samym czasie

XAML:

<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(); 
    } 
} 

Plusy: PRACE super miły! Gdy wiążąca dla DataContext prosi o ViewModel, mogę skorzystać z konwertera do modyfikowania że ViewModel i wstrzyknąć moje urządzenie przed wpuszczeniem go

Wady:

Tracimy intelissense (z ReSharper), ponieważ on nie robi” t Wiesz, jaki typ jest zwracany przez konwerter

Jakieś inne pomysły lub modyfikacje, które mogłem wprowadzić, aby rozwiązać ten problem?

+0

Czy próbowałeś tego w Blend? W Visual Studio 2010 łatwiej było pracować z tym wzorcem w Blend niż Visual Studio, ponieważ projektant był bardziej wytrzymały. Nie jestem pewien, jak Blend porównuje się do VS2013. –

+0

Jak sobie z tym radziłeś w Blend? – FrankyB

+0

VS i Blend dzielą tego samego projektanta, ale nie wszystkie takie same funkcje w jakikolwiek sposób. Chociaż VS nie używa już projektanta cydru, nadal możesz zrobić dużo tego, co możesz/może mieszać w VS teraz. –

Odpowiedz

4

może utworzyć ViewModel czasu projekt, który zwraca IsConnected = true (FakeDeviceViewModel) i ustawić go jako kontekst danych czasu projektowania:

d:DataContext="{d:DesignInstance viewModels:FakeDeviceViewModel, 
IsDesignTimeCreatable=True}" 
0
  1. Chciałbym utworzyć wystąpienie fałszywych Widok Model w oddzielnym XAML np DeviceViewModelDataSample.xaml (patrz przykład poniżej)

  2. Set Budowa Action do DesignData

  3. Reference plik jako taki

    <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

Chciałbym zaproponować alternatywne rozwiązanie.

Można użyć tego samego modelu widoku dla danych czasu projektu i normalnego czasu wykonywania, a także sprawdzić (pojedynczy) model wyświetlania, czy projektant jest aktywny, a następnie załadować tam dane czasu projektu.

w widoku modelu byś zrobił coś takiego:

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

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

Obiekt IsInDesignMode może być umieszczony w widoku model klasy podstawowej - jeśli masz jedno - i wygląda tak:

DesignerProperties.GetIsInDesignMode(new DependencyObject()); 

Proszę spojrzeć na moją odpowiedź here

Powiązane problemy