2014-09-26 9 views
12

Utilizzo la finestra di progettazione di Visual Studio 2013 per creare il controllo utente in WPF e sto utilizzando un approccio MVVM.Configurazione in fase di progettazione di un ViewModel

Sto cercando di trovare il modo migliore di avere l'impostazione "Design-Time" del mio viewmodel in modo tale da vedere immediatamente l'effetto nel progettista di modificare un valore di una proprietà, ad esempio. Ho usato diversi disegni e tecniche per supportare questo, ma nulla è esattamente quello che voglio. Mi chiedo se qualcuno ha idee migliori ...

Situazione (semplificata): Quindi ho un "Dispositivo" che voglio che un UserControl mostri stati e operazioni. Dall'alto verso il basso:

  • Ho un IDeviceModel, che ha un campo bool IsConnected {get;} (e la corretta notifica delle modifiche di stato)
  • Ho un FakeDeviceModel che implementa IDeviceModel, e quindi mi permette di non fare affidamento su un dispositivo reale per la fase di progettazione e test
  • Un DeviceViewModel, che contiene un IDeviceModel e incapsula le proprietà del modello. (Sì, ha corretto le notifiche INotifyPropertyChanged in esso)
  • mio UserControl che avrà un DataContext di tipo DeviceViewModel, e avrebbe un CheckBox personalizzato in stile che è IsChecked={Binding IsConnected, Mode=OneWay
  • mio obiettivo: voglio vedere in anteprima sulla fase di progettazione come fa del modello di stato IsConnected influisce sul mio UserControl (così potrebbe influenzare le altre cose che solo IsChecked)

quadro:

  • io uso l'idea del MVVM Luce ViewModelLocator, tornando campi non statici (così nuovo io istanze di ViewModels). In fase di esecuzione, il vero DataContext sarà dato da colui instanciating questo 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 ho provato:

soluzione Compile-Time

semplicemente il codice configurazione di ViewModel in ViewModelLocator.

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

Pro: semplici

Contro: Ecco iterazioni più lunghe di sempre andando a cambiare il valore nel codice, ricompilare, tornare alla visualizzazione progettista, attendere risultato

istanza di risorse e conservato static in ViewModelLocator

Così creo un'istanza in XAML e provo a inserirla nell'attuale ViewModel utilizzato dal progettista.Non il modo più pulito, ma ha lavorato per qualche tempo in una situazione semplice (sì c'è qualche wierdness con la raccolta, ma è stato con l'idea che avrei potuto avere più dispositivi e una quella attuale)

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

E 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:

  • Ha lavorato super-grande su uno projec t. l'istanza che ho avuto in XAML, ho potuto modificare i booleani e ottenere un feedback immediato su come influenza il mio UserControl. Quindi, nella semplice situazione, stato "controllato" del CheckBox cambierebbe e ho potuto modificare la mia styling in tempo reale, senza la necessità di ricompilare

Contro:

ha smesso di funzionare in un altro progetto, e questo da solo non ho trovato il motivo per cui. Ma dopo aver ricompilato e cambiato roba, il progettista mi avrebbe dato delle eccezioni come "Non posso lanciare" FakeDeviceModel "su" FakeDeviceModel "" !! La mia ipotesi è che il Designer compila internamente e utilizza cache per questi tipi (C: \ Users \ firstname.lastname \ AppData \ Local \ Microsoft \ VisualStudio \ 12.0 \ Designer \ ShadowCache). E che nella mia soluzione, a seconda dell'ordine delle cose, stavo creando un "FakeDeviceModel" che è stato assegnato a istanze statiche, e "più tardi", la prossima volta che ViewModelLocator sarebbe stato richiesto per un ViewModel, lo userebbe esempio. Tuttavia, nel frattempo "ricompila" o utilizza una cache diversa, quindi non è "esattamente" lo stesso tipo. Quindi ho dovuto uccidere il progettista (XDescProc) e ricompilarlo per farlo funzionare, e poi fallire di nuovo pochi minuti dopo. Se qualcuno può correggermi su questo sarebbe fantastico.

Multi-Binding per d: DataContext e personalizzato convertitore

problema della soluzione precedente mi stava indicando il fatto che il ViewModel e FakeDeviceModel sono stati creati al momento diverso nel tempo (che indica il tipo/problema pressofuso) e per risolverlo, avrei bisogno di creare loro allo stesso tempo

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

Pro: -Funziona super bello! Quando il vincolante per il DataContext chiede il ViewModel, approfitto del convertitore per modificare il ViewModel e iniettare il mio dispositivo prima di restituirlo

Contro:

Perdiamo intelissense (con ReSharper), dal momento che doesn' so quale tipo viene restituito dal convertitore

Altre idee o modifiche che potrei apportare per risolvere questo problema?

+0

Hai provato questo in Blend? Con Visual Studio 2010, è stato più semplice lavorare con questo modello in Blend rispetto a Visual Studio perché il designer era più robusto. Non sono sicuro di come la miscela si paragona a VS2013. –

+0

Come stavi gestendo questo in Blend? – FrankyB

+0

VS e Blend condividono lo stesso designer ora, anche se non tutte le stesse funzionalità in alcun modo. Sebbene VS non utilizzi più il designer di sidro, puoi ancora fare molto di ciò che puoi/potresti combinare in VS adesso. –

risposta

4

È possibile creare un ViewModel fase di progettazione che restituisce IsConnected = true (FakeDeviceViewModel) e impostarlo come un contesto dati in fase di progettazione:

d:DataContext="{d:DesignInstance viewModels:FakeDeviceViewModel, 
IsDesignTimeCreatable=True}" 
0
  1. vorrei creare un'istanza di Fake View Modello in un XAML separata per esempio DeviceViewModelDataSample.xaml (vedi esempio sotto)

  2. Set Operazione di generazione DesignData

  3. riferimento il file come ad esempio

    <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

Vorrei proporre una soluzione alternativa.

È possibile utilizzare lo stesso modello di visualizzazione per i dati del tempo di progettazione e il tempo di esecuzione normale e controllare nel proprio (singolo) modello di visualizzazione se il progettista è attivo e quindi caricare i dati del progetto.

Nel vostro modello di vista si potrebbe fare qualcosa di simile:

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

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

La proprietà IsInDesignMode potrebbe essere collocato nella vista classe base del modello - se ne avete uno - e si presenta in questo modo:

DesignerProperties.GetIsInDesignMode(new DependencyObject()); 

Si prega di dare un'occhiata alla mia risposta here