2014-09-26 129 views
12

我使用Visual Studio 2013的設計器在WPF中創建我的用戶控件,並使用MVVM方法。ViewModel的設計時設置

我試圖找到最佳方法來讓我的viewmodel具有「設計時」設置,以便我立即在設計器中看到更改屬性值的效果。我用不同的設計和技術來支持這個,但沒有什麼是我想要的。我想知道如果有人有更好的想法...

情況(簡體): 所以我有一個「設備」,我想要一個UserControl顯示狀態和操作。從上至下:

  • 我其中有一個領域bool IsConnected {get;}(和狀態的變化適當通知)
  • 我有一個FakeDeviceModel它實現IDeviceModel一個IDeviceModel,從而使我能夠不依賴於實際設備上用於設計時和測試
  • DeviceViewModel,它包含一個IDeviceModel並封裝模型的屬性。 (是的,它有它正確的INotifyPropertyChanged的通知)
  • 我的用戶,將有型DeviceViewModel的一個DataContext,並且將有一個自定義風格的複選框是IsChecked={Binding IsConnected, Mode=OneWay
  • 我的目標:我要預覽設計時如何做該型號的IsConnected狀態會影響我的用戶(所以它可能會影響其他的東西不僅僅是器isChecked)

框架:

  • 我使用MVVM光ViewModelLocator的想法,返回非靜態字段(所以新一世ViewModels的物質)。

    編譯時的解決方案

    簡單地編寫代碼:在運行時,真正的datacontext將由一個instanciating這個用戶控件

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

解決方案我試圖給出在ViewModelLocator中設置ViewModel。

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

優點:簡單

缺點:這是始終不會改變的值代碼,重新編譯,回去的設計師認爲,等待結果的資源

實例,並保持較長的迭代靜態ViewModelLocator

因此,我在XAML中創建一個實例,並嘗試在設計器使用的當前ViewModel中推送它。不乾淨的方法,但工作了簡單的情況,而(是有一些wierdness與收藏,但與我可以有多個設備和當前的想法)

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

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

優點:

  • 曾爲超極上一個工程中的噸。我在XAML中的實例,我可以修改布爾值,並且我會得到-immediate-反饋它如何影響我的UserControl。所以在簡單的情況下,CheckBox的「經過」狀態會改變,我可以改變我的實時造型,而無需重新編譯

缺點:

它停在另外一個項目工作,而這本身我找不到原因。但重新編譯和更改東西后,設計師會給我看起來像「不能將FakeDeviceModel」轉換爲「FakeDeviceModel」的異常!我的猜測是Designer在內部爲這些類型編譯和使用緩存(C:\ Users \ firstname.lastname \ AppData \ Local \ Microsoft \ VisualStudio \ 12.0 \ Designer \ ShadowCache)。在我的解決方案中,根據事物的順序,我創建了一個分配給靜態實例的「FakeDeviceModel」,並且在「稍後」,下一次ViewModelLocator被要求提供ViewModel時,它會使用實例。但是,如果在此期間他「重新編譯」或使用不同的緩存,那麼它不是「完全」相同的類型。所以我必須殺死設計器(XDescProc)並重新編譯才能正常工作,然後在幾分鐘後再次失敗。如果有人能糾正我,這將是偉大的。

多綁定爲d:DataContext的和自定義轉換器

以前的解決方案的問題是指向我的視圖模型和FakeDeviceModel在不同的時刻(給類型創建的事實/施放問題),並解決它,我需要在同一時間創建它們

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

優點: -Works超好聽!當在DataContext綁定請求視圖模型,我趁轉換器來修改視圖模型並將其返回

缺點之前注入我的設備:

我們intelissense失去(與ReSharper的),因爲他不」 t知道轉換器返回的是什麼類型

我能解決這個問題的其他想法或修改?

+0

你在Blend試過這個嗎?使用Visual Studio 2010,在Blend中處理這種模式比使用Visual Studio更容易,因爲設計器更健壯。我不確定Blend如何與VS2013進行比較。 – 2014-09-30 19:15:05

+0

你是如何處理Blend的? – FrankyB 2014-10-01 08:23:00

+0

VS和Blend現在共享同一個設計器,儘管不是所有相同的功能都是。儘管VS不再使用蘋果酒設計師,但你現在仍然可以在VS中融入很多東西。 – 2014-10-07 20:00:53

回答

4

您可以創建一個返回IsConnected =真(FakeDeviceViewModel)設計時視圖模型,並將其設置爲設計時數據方面:

d:DataContext="{d:DesignInstance viewModels:FakeDeviceViewModel, 
IsDesignTimeCreatable=True}" 
0
  1. 我會在一個單獨的XAML製造假視圖模型的實例,例如DeviceViewModelDataSample.xaml(見下例)

  2. 設置生成操作DesignData

  3. 參考文件本身

    <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

我想提出一種替代解決方案。

您可以將相同的視圖模型用於設計時間數據和正常運行時,並檢查(單個)視圖模型設計器是否處於活動狀態,然後在其中加載設計時間數據。

在您的視圖模型,你會做這樣的事情:

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

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

IsInDesignMode屬性可以被放置在您的視圖模型基類 - 如果你有一個 - 並且看起來像這樣:

DesignerProperties.GetIsInDesignMode(new DependencyObject()); 

請看看我的回答here