Я использовал курс Brian Noyes Pluralsight, "WPF MVVM In Depth" в качестве основного источника, и то, что он показывает, отлично работает.
Однако вместо переключения Views на основе кнопок, нажимаемых на UtilitiesView, я хочу переключать Views на основе кнопки панели инструментов (которая является частью пакета расширения VS 2015), где пользователь может выбрать конкретный экземпляр.
UtilitiesView - это пользовательский элемент управления в окне, которое открывается расширением пакета. Итак, вот xaml в UtilitiesView: `
<UserControl.Resources>
<DataTemplate DataType="{x:Type engines:CalcEngineViewModel}">
<engines:CalcEngineView/>
</DataTemplate>
<DataTemplate DataType="{x:Type engines:TAEngineViewModel}">
<engines:TAEngineView/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="NavContent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width ="*"/>
<ColumnDefinition Width ="*"/>
<ColumnDefinition Width ="*"/>
</Grid.ColumnDefinitions>
<Button Content="Calc"
Command ="{Binding ChangeViewModelCommand}"
CommandParameter="CalculationEngine"
Grid.Column="0"/>
<Button Content="TA"
Command ="{Binding ChangeViewModelCommand}"
CommandParameter="TAEngine"
Grid.Column="1"/>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl Content="{Binding CurrentEngineViewModel}"/>
</Grid>
</Grid>
</UserControl>`
Как можно видеть, есть две кнопки, которые переключают представление путем привязки к ChangeViewModelCommand и передают строковое значение (либо "CalculationEngine", либо "TAEngine" ) через.
Вот класс UtilitiesViewModel.cs:
public class UtilitiesViewModel : BindableBase
{
#region Fields
public RelayCommand<string> ChangeViewModelCommand { get; private set; }
private CalcEngineViewModel calcViewModel = new CalcEngineViewModel();
private TAEngineViewModel taViewModel = new TAEngineViewModel();
private BindableBase currentEngineViewModel;
public BindableBase CurrentEngineViewModel
{
get { return currentEngineViewModel; }
set
{
SetProperty(ref currentEngineViewModel, value);
}
}
#endregion
public UtilitiesViewModel()
{
ChangeViewModelCommand = new RelayCommand<string>(ChangeViewModel);
}
#region Methods
public void ChangeViewModel(string viewToShow) //(IEngineViewModel viewModel)
{
switch (viewToShow)
{
case "CalculationEngine":
CurrentEngineViewModel = calcViewModel;
break;
case "TAEngine":
CurrentEngineViewModel = taViewModel;
break;
default:
CurrentEngineViewModel = calcViewModel;
break;
}
}
#endregion
}
Вот BindableBase.cs:
public class BindableBase : INotifyPropertyChanged
{
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Я использую простой класс ViewModelLocator, чтобы связать Views с их ViewModels:
public static class ViewModelLocator
{
public static bool GetAutoWireViewModel(DependencyObject obj)
{
return (bool)obj.GetValue(AutoWireViewModelProperty);
}
public static void SetAutoWireViewModel(DependencyObject obj, bool value)
{
obj.SetValue(AutoWireViewModelProperty, value);
}
// Using a DependencyProperty as the backing store for AutoWireViewModel. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoWireViewModelProperty =
DependencyProperty.RegisterAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false, AutoWireViewModelChanged));
private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
var viewTypeName = viewType.FullName;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
var viewModel = Activator.CreateInstance(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
}
Как упоминалось ранее, переключать представления с помощью кнопок, определенных в UtilitiesView.xaml, отлично.
Кнопки панели инструментов вызывают вышеупомянутый метод ChangeViewModel в UtilitiesViewModel.cs из класса Package.cs, но даже если свойство CurrentEngineViewModel задано по-разному, оно не отражается на UtilitiesView.xaml.
Когда я отлаживаю, то в обоих случаях он корректно подходит к SetProperty для BindableBase, но тогда в случае кнопок ToolBar метод AutoWireViewModelChanged в ViewModelLocator никогда не вызывается.
Я не знаю, почему нет. Я бы подумал, что привязки в UtilitiesView с свойством CurrentEngineViewModel из UtilitiesViewModel будет достаточно? Я пытаюсь думать об этом, как если бы я внес изменения в модель-компонент, и View должен ответить на это, даже если у меня на самом деле есть кнопки панели инструментов в качестве части того, что можно было бы рассмотреть компонент вида.
Так вызывается метод ChangeViewModel в классе Package.cs:
if (Config.Engine.AssemblyPath.Contains("Engines.TimeAndAttendance.dll"))
{
uvm.ChangeViewModel("TAEngine");
}
else //Assume Calculation Engine
{
uvm.ChangeViewModel("CalculationEngine");
}
Надеюсь, я дал достаточно подробностей.
ОБНОВЛЕНИЕ 1
Что касается комментариев gRex, я думаю, что, возможно, есть два объекта UtilitiesViewModel.
Это то, что происходит, когда открывается пользовательское окно расширения пакета:
public class SymCalculationUtilitiesWindow : ToolWindowPane
{
/// <summary>
/// Initializes a new instance of the <see cref="SymCalculationUtilitiesWindow"/> class.
/// </summary>
public SymCalculationUtilitiesWindow() : base(null)
{
this.Caption = "Sym Calculation Utilities";
this.ToolBar = new CommandID(new Guid(Guids.guidConnectCommandPackageCmdSet), Guids.SymToolbar);
// This is the user control hosted by the tool window; Note that, even if this class implements IDisposable,
// we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on
// the object returned by the Content property.
this.Content = new UtilitiesView();
}
}
Метод AutoWireViewModelChanged вызывается для связывания утилиты UtilitiesViewModel с UtilitiesView для содержимого.
В классе Package.cs у меня есть это поле:
private UtilitiesViewModel uvm;
а в методе Initialize у меня есть:
uvm = new UtilitiesViewModel();
Объект uvm используется как в фрагменте кода в исходном сообщении (чуть выше UPDATE), чтобы вызвать метод ChangeViewModel с соответствующим строковым параметром.
Это даст мне два разных объекта, не так ли? Если да, и если предположить, что это может быть основной причиной проблемы, как я могу ее улучшить, должен ли я сделать UtilitiesViewModel синглом?
ОБНОВЛЕНИЕ 2
Я добавил решение для Github. Функциональность немного изменена, так что мне не нужно было взаимодействовать с остальной частью исходного решения. Таким образом, кнопка Connect (на панели инструментов) вызывает метод ChangeViewModel с параметром "TAEngine", а кнопка "Сохранить" (на панели инструментов) делает то же самое, но с параметром "CalculationEngine". В настоящее время DataTemplates по-прежнему закомментированы, поэтому вы просто видите имя класса в тексте. Вот ссылка . В экспериментальном экземпляре Visual Studio это окно можно найти в меню Вид → Другие Windows → SymplexityCalculationUtilitiesWindow. Возможно, вам потребуется загрузить SDK Visual Studio, если у вас его еще нет.
Обновление 3
Я использовал контейнер Unity IoC ContainerControlledLifetimeManager, чтобы убедиться, что у меня нет двух разных UtilitiesViewModels. После выполнения этой функции кнопки панели инструментов могут перемещаться по правильному представлению.