我正在使用以下文章作为入门代码: Navigating Between Views in WPF MVVM Simple Injector WPF Integration
目的: 尝试使用按钮绑定命令和Simple Injector将视图从视图1转到视图2,以便将依赖项注入到视图中。注意:这些依赖项是保存来自外部源的数据的存储库。
问题:
在使用Simple Injector将依赖项注入到我的MainWindow和MainWindowViewModel之后,我的按钮不再更改当前视图(到我的另一个视图)。当使用Visual Studio并使用断点进行调试时,代码似乎永远停留在RelayCommand.cs的CanExecute
函数中(请参阅Navigating Between Views in WPF MVVM),其中有些东西一遍又一遍地调用它。我无法在CanExecute
函数中调试更多,因为有很多代码被传递(来自DLL等)。不使用断点时,它看起来好像我的按钮什么都不做。
我在输出窗口中没有按钮错误,也没有抛出异常。命令绑定正在运行,因为我可以看到在调试时调用的MainWindowViewModel.cs中找到的函数OnGo2Screen
。在调用OnGo2Screen
之后,它按预期移动代码,直到它卡在CanExecute
中。
我试过的 我检查了我的MainWindow的数据上下文,我可以看到它具有所有正确的功能。
我为Navigating Between Views in WPF MVVM文章做了一个单独的项目,我能够让观点改变得很好。但每当我尝试使用Simple Injector时,我的按钮就会断开。
我注意到,当不使用Simple Injector时,代码从CanExecute
函数移动到CanExecuteChanged
EventHandler并执行remove和添加mutator,然后按预期更改视图。但是,在使用Simple Injector时,它不会这样做。
代码 我正在使用我的App.xaml.cs作为启动程序,其中我的App.xaml具有“页面”的构建操作。
SimulationCaseView是View 1(默认的起始视图)。 StreamsView是View 2(另一个视图)。 UserControl3是View 3(只是另一个视图)。
以下是我的代码。请参阅为任何剩余代码提供的两个链接,因为我基于该功能的许多功能。
App.xaml中
<Application x:Class="MyApp.Desktop.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MyApp.Desktop.Views">
<Application.Resources>
<DataTemplate DataType="{x:Type views:SimulationCaseViewModel}">
<views:SimulationCaseView />
</DataTemplate>
<DataTemplate DataType="{x:Type views:StreamsViewModel}">
<views:StreamsView />
</DataTemplate>
<DataTemplate DataType="{x:Type views:UserControl3ViewModel}">
<views:UserControl3 />
</DataTemplate>
</Application.Resources>
</Application>
App.xaml.cs
namespace MyApp.Desktop
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
App()
{
InitializeComponent();
}
[STAThread]
static void Main()
{
var container = Bootstrap();
// Any additional other configuration, e.g. of your desired MVVM toolkit.
RunApplication(container);
}
private static Container Bootstrap()
{
// Create the container as usual.
var container = new Container();
// Register your types, for instance:
container.Register<IPreferencesRepository, PreferencesRepository>(Lifestyle.Singleton);
container.Register<IStreamRepository, StreamRepository>(Lifestyle.Singleton);
// Register your windows and view models:
container.Register<MainWindow>();
container.Register<MainWindowViewModel>();
container.Verify();
return container;
}
private static void RunApplication(Container container)
{
try
{
var app = new App();
var mainWindow = container.GetInstance<MainWindow>();
MainWindowViewModel viewModel = container.GetInstance<MainWindowViewModel>();
mainWindow.DataContext = viewModel;
app.Run(mainWindow);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
MainWindow.xaml
<Window x:Class="MyApp.Desktop.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyApp.Desktop"
mc:Ignorable="d"
Title="MainWindow"
Height="350" Width="525"
xmlns:views="clr-namespace:MyApp.Desktop.Views">
<Grid>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</Grid>
</Window>
MainWindowViewModel.cs
namespace MyApp.Desktop.Views
{
public class MainWindowViewModel : BaseViewModel
{
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
public List<IPageViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;
}
}
public IPageViewModel CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentPageViewModel = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
private void OnGo1Screen(object obj)
{
ChangeViewModel(PageViewModels[0]);
}
private void OnGo2Screen(object obj)
{
ChangeViewModel(PageViewModels[1]);
}
private void OnGo3Screen(object obj)
{
ChangeViewModel(PageViewModels[2]);
}
public MainWindowViewModel(IStreamRepository streamRepository)
{
// Add available pages and set page
PageViewModels.Add(new SimulationCaseViewModel(streamRepository));
PageViewModels.Add(new StreamsViewModel());
PageViewModels.Add(new UserControl3ViewModel());
CurrentPageViewModel = PageViewModels[0];
Mediator.Subscribe("GoTo1Screen", OnGo1Screen);
Mediator.Subscribe("GoTo2Screen", OnGo2Screen);
Mediator.Subscribe("GoTo3Screen", OnGo3Screen);
}
}
}
SimulationCaseView.xaml
<UserControl x:Class="MyApp.Desktop.Views.SimulationCaseView"
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"
xmlns:local="clr-namespace:MyApp.Desktop"
mc:Ignorable="d"
d:DesignHeight="280" d:DesignWidth="280">
<Grid>
<Button
Content="Go to Streams"
Command="{Binding GoTo2}"
Width="90" Height="30" Margin="166,220,24,30">
</Button>
</Grid>
</UserControl>
SimulationCaseViewModel.cs
namespace MyApp.Desktop.Views
{
public class SimulationCaseViewModel : BaseViewModel, IPageViewModel
{
private ICommand _goTo2;
private readonly IStreamRepository _repo;
public SimulationCaseViewModel(IStreamRepository repo)
{
_repo = repo;
Application application = _repo.GetApplicationReference();
CurrentSimulationCases = new ObservableCollection<SimulationCase>();
Streams = new ObservableCollection<object>();
foreach (SimulationCase simulationCase in application.SimulationCases)
{
CurrentSimulationCases.Add(simulationCase);
}
//FetchStreams = new RelayCommand(OnFetch);
}
public ObservableCollection<SimulationCase> CurrentSimulationCases { get; set; }
public ObservableCollection<object> Streams { get; private set; }
public ICommand GoTo2
{
get
{
return _goTo2 ?? (_goTo2 = new RelayCommand(x =>
{
Mediator.Notify("GoTo2Screen", "");
}));
}
}
}
}
任何有关按钮不起作用的帮助表示赞赏。谢谢。
问题出在ViewModel的Lifestyle
中,必须设置为Singleton
而不是默认的Transient
。
private static Container Bootstrap()
{
// Create the container as usual.
var container = new Container();
// Register your types, for instance:
// Register your windows and view models:
//container.Register<MainWindow>(Lifestyle.Singleton); //not needed
container.Register<MainWindowViewModel>(Lifestyle.Singleton);
container.Verify();
return container;
}
然后,您可以以简单的方式启动应用程序
private static void RunApplication(Container container)
{
try
{
var mainWindow = container.GetInstance<MainWindow>();
var app = new App();
app.InitializeComponent();
app.Run(mainWindow);
}
catch (Exception ex)
{
//Log the exception and exit
Debug.WriteLine(ex.Message);
}
}
完整的代码在github上。
当您在container.Verify
中调用Bootstrap
时,您将创建一个MainWindowViewModel
实例来验证其实例化,另一个实例验证MainWindow
类。
顺便说一下,你可以通过简单地不验证容器来解决你的问题!
所以第二个解决方案是
//container.Register<MainWindow>(); // => Lifestyle.Transient;
container.Register<MainWindowViewModel>(); // => Lifestyle.Transient;
//container.Verify();
现在,请注意您在Mediator
c.tor中拥有MainWindowViewModel
订阅。
public static void Subscribe(string token, Action<object> callback)
{
if (!pl_dict.ContainsKey(token))
{
var list = new List<Action<object>>();
list.Add(callback);
pl_dict.Add(token, list);
}
else
{
bool found = false;
//foreach (var item in pl_dict[token])
// if (item.Method.ToString() == callback.Method.ToString())
// found = true;
if (!found)
pl_dict[token].Add(callback);
}
}
foreach
循环 - 我上面只评论过(它是解决问题的第三种选择) - 会让你跳过对第二个正确的ViewModel方法的调用,并会让你得到第一个错误的方法(请记住Bootstrap
验证创建了两次)。如果你想要第四种替代解决方案,使用IComponent
的经典Mediator pattern界面
public interface IComponent
{
void OnGo1Screen(object obj);
void OnGo2Screen(object obj);
}
public class MainWindowViewModel : BaseViewModel, IComponent
您也可以将订阅移出c.tor
public MainWindowViewModel()
{
// Add available pages and set page
PageViewModels.Add(new UserControl1ViewModel());
PageViewModels.Add(new UserControl2ViewModel());
CurrentPageViewModel = PageViewModels[0];
//Mediator.Subscribe("GoTo1Screen", OnGo1Screen);
//Mediator.Subscribe("GoTo2Screen", OnGo2Screen);
}
进入你的Program
:
var context = mainWindow.DataContext as IComponent;
Mediator.Subscribe("GoTo1Screen", context.OnGo1Screen);
Mediator.Subscribe("GoTo2Screen", context.OnGo2Screen);
更新命令状态后调用此方法:
CommandManager.InvalidateRequerySuggested();
命令仅在发生这些常规事件时更新:
KeyUp
MouseUp
GotKeyboardFocus
LostKeyboardFocus
有关详细信息,请参阅此源代码:CommandDevice.cs
对于其他控件,它有更多要刷新的事件:
DataGrid
...SinglePageViewer
...您可以双击CommandManager.InvalidateRequerySuggested()
的this link方法来查看刷新命令状态的其他事件。
因此,如果您的更新不在这些事件中发生,那么您的命令状态将不会更新。
你说当使用Visual Studio并使用断点进行调试时,代码似乎永远停留在RelayCommand.cs的CanExecute
函数中。
这不是CanExecute
的循环,当您的活动窗口在应用程序和Visual Studio之间切换时,这是GotKeyboardFocus
和LostKeyboardFocus
事件。