Following on from my post WPF Window (or do I mean View) Navigation (or do I mean switching, opening, closing…) part 2 I have finally put together the view switching part, here goes…
First I added two User Control (WPF) items (for now I’m going to keep these in a new folder called SubViews) and a MvvmViewModel (WPF) for each:
Nav\Views\SubViews\UC1.xaml + .cs
Nav\Views\SubViews\UC2.xaml + .cs
Nav\ViewModels\UC1ViewModel.cs
Nav\ViewModels\UC2ViewModel.cs
Next I added some dependency properties to my ViewModels using the mvvminpc snippet.
Starting with UC1ViewModel, I added Val1 of type String:
/// <summary> /// The <see cref="Val1" /> property's name. /// </summary> public const string Val1PropertyName = "Val1"; private string _val1 = ""; /// <summary> /// Sets and gets the Val1 property. /// Changes to that property's value raise the PropertyChanged event. /// </summary> public string Val1 { get { return _val1; } set { if (_val1 == value) { return; } RaisePropertyChanging(Val1PropertyName); _val1 = value; RaisePropertyChanged(Val1PropertyName); } }
Similarly I added Val2 to UC2ViewModel.
These are just something to bind to and display.
Next I added a dependency property of type UserControl and called UC to AnotherViewModel, I will bind to this and change this to swap the UserControl that is displayed:
/// <summary> /// The <see cref="UC" /> property's name. /// </summary> public const string UCPropertyName = "UC"; private UserControl _uc = null; /// <summary> /// Sets and gets the UC property. /// Changes to that property's value raise the PropertyChanged event. /// </summary> public UserControl UC { get { return _uc; } set { if (_uc == value) { return; } RaisePropertyChanging(UCPropertyName); _uc = value; RaisePropertyChanged(UCPropertyName); } }
Of course next I needed the UserControl instances and commands to swap them, I added them like so:
private UC1 _uc1; private UC2 _uc2; public RelayCommand Swap1a { get; set; } public RelayCommand Swap1b { get; set; }
And in constructor:
_uc1 = new UC1() { DataContext = new UC1ViewModel() { Val1 = "AVM uc 1a" } }; _uc2 = new UC2() { DataContext = new UC2ViewModel() { Val2 = "AVM uc 1b" } }; Swap1a = new RelayCommand(() => { UC = _uc1; }); Swap1b = new RelayCommand(() => { UC = _uc2; });
Next up was to add code to ViewModelLocator to add design and blendability.
Using the mvvmlocatorproperty snippet I added a property for use when designing the UserControls, notice I don’t use the IoC container in this case:
/// <summary> /// Gets the UC1VMDesignTime property. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This non-static member is needed for data binding purposes.")] public UC1ViewModel UC1VMDesignTime { get { if (ViewModelBase.IsInDesignModeStatic) { return new UC1ViewModel() { Val1 = "Designing val1..." }; } else { return null; } } } /// <summary> /// Gets the UC2VMDesignTime property. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This non-static member is needed for data binding purposes.")] public UC2ViewModel UC2VMDesignTime { get { if (ViewModelBase.IsInDesignModeStatic) { return new UC2ViewModel() { Val2 = "Designing val2..." }; } else { return null; } } }
I also changed Another so that at design time there was data in the UC:
/// <summary> /// Gets the Another property. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This non-static member is needed for data binding purposes.")] public AnotherViewModel Another { get { AnotherViewModel avm = ServiceLocator.Current.GetInstance<AnotherViewModel>(); if (ViewModelBase.IsInDesignModeStatic) { avm.UC = new UC1() { DataContext = UC1VMDesignTime }; } return avm; } }
Finally I wired it all up in the xaml files.
UC1.xaml:
d:DataContext="{Binding UC1VMDesignTime, Source={StaticResource Locator}}" .... <StackPanel> <Label Content="uc1" Height="28" Margin="8,10,0,0" x:Name="label1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="36.803" /> <TextBox x:Name="txt1" Margin="83,0,111,0" TextWrapping="Wrap" Text="{Binding Val1}" VerticalAlignment="Top" Height="49.96"/> <Label x:Name="lbl1" Content="{Binding Val1}" Margin="68,0,71.007,58.04" VerticalAlignment="Bottom" Height="44.96"/> </StackPanel>
UC2.xaml:
d:DataContext="{Binding UC2VMDesignTime, Source={StaticResource Locator}}" .... <StackPanel> <Label Content="uc2" HorizontalAlignment="Left" Height="35.96" Width="54" /> <Label Content="{Binding Val2}" Height="39.96" Margin="78,0,40,0" /> </StackPanel>
AnotherWindow.xaml:
<ContentControl x:Name="ContentControl" Content="{Binding UC}" Margin="177.5,82,110.5,0" Height="255" Width="464" VerticalAlignment="Top"> </ContentControl> <Button Content="Swap to 1a" HorizontalAlignment="Left" Margin="0,0,0,124.04" VerticalAlignment="Bottom" Width="75" Command="{Binding Swap1a}"/> <Button Content="Swap to 1b" HorizontalAlignment="Left" Margin="0,0,0,98.08" VerticalAlignment="Bottom" Width="75" Command="{Binding Swap1b}"/>
Note that d:DataContext is ignored except at design time, so we have the ability to chose a design time model and I have coded those models so that they are null when not in design time anyway. I really like how that bit turned out, it’s really nice to have something you can design later (or pass onto a designer), there is nothing worse than a broken view that you can’t use in VisualStudio or Blend.