我试图重现在Sheridan的答案中所建议的方法在使用WPF和MVVM模式时浏览视图。不幸的是,当我这样做时,我遇到了绑定错误。以下是确切的错误:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='JollyFinance.ViewModels.MainViewModel', AncestorLevel='1''. BindingExpression:Path=DataContext.DisplayTest; DataItem=null; target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
当我查看LoginView.xaml中的xaml代码时,我注意到Visual Studio告诉我在
MainViewModel
类型的上下文中找不到DataContext.DisplayText
。我已经尝试删除DataContext.
仅保留DisplayText
,但没有效果。除非Sheridan的答案有错误,否则我肯定是漏掉了什么。为使其正常工作,我该怎么做?MainWindow.xaml:
<Window x:Class="JollyFinance.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:JollyFinance.ViewModels"
xmlns:views="clr-namespace:JollyFinance.Views"
Title="JollyFinance!" Height="720" Width="1280">
<Window.Resources>
<!-- Different pages -->
<DataTemplate DataType="{x:Type vm:LoginViewModel}">
<views:LoginView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:TestViewModel}">
<views:Test/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</Window>
MainViewModel.cs:
public class MainViewModel : BindableObject
{
private ViewModelNavigationBase _currentViewModel;
public MainViewModel()
{
CurrentViewModel = new LoginViewModel();
}
public ICommand DisplayTest
{
get
{
// This is added just to see if the ICommand is actually called when I press the
// Create New User button
Window popup = new Window();
popup.ShowDialog();
// View model that doesn't contain anything for now
return new RelayCommand(action => CurrentViewModel = new TestViewModel());
}
}
public ViewModelNavigationBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
_currentViewModel = value;
RaisePropertyChanged("CurrentViewModel");
}
}
}
}
LoginView.xaml:
<UserControl x:Class="JollyFinance.Views.LoginView"
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:vm="clr-namespace:JollyFinance.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<vm:LoginViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Username: " Grid.Column="1" Grid.Row="1" Margin="5"/>
<TextBox Text="{Binding Path=Username}" Grid.Column="2" Grid.Row="1" Grid.ColumnSpan="2" Margin="5"/>
<TextBlock Text="Password: " Grid.Column="1" Grid.Row="2" Margin="5"/>
<PasswordBox x:Name="PasswordBox" PasswordChar="*" Grid.Column="2" Grid.ColumnSpan="2" Grid.Row="2" Margin="5"/>
<Button Content="Log In" Grid.Column="2" Grid.Row="3" Margin="5" Padding="5" Command="{Binding LoginCommand}"/>
<Button Content="Create new user" Grid.Column="3" Grid.Row="3" Margin="5" Padding="5"
Command="{Binding DataContext.DisplayTest, RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}},
Mode=OneWay}"/>
</Grid>
</UserControl>
LoginViewModel.cs:
public class LoginViewModel : ViewModelNavigationBase
{
public LoginViewModel()
{
LoginCommand = new RelayCommand(Login);
}
private void Login(object param)
{
// Just there to make sure the ICommand is actually called when I press the
// Login button
Window popup = new Window();
popup.ShowDialog();
}
public String Username { get; set; }
public String Password { get; set; }
public ICommand LoginCommand { get; set; }
}
ViewModelNavigationBase
是一个实现了 INotifyPropertyChanged
接口的类,而 Test.xaml 和 TestViewModel.cs 只是用于测试目的的虚拟视图模型/视图。
LoginViewModel
(和视图)在创建时由我的MainViewModel
生成。但我的LoginViewModel
或视图如何向MainViewModel
发信号表明凭据已经验证过了呢?这就是为什么我使用链接问题答案中的方法。此外,您提到我所做的事情很糟糕,因为它将所有东西都耦合在一起。除了使用Page
之外,还有什么其他方法可以导航浏览视图呢?这就是我最初寻找链接问题答案之前想要的。 - Choub890Messenger
类(如果VM是独立的)相互交互。 Messenger具有公共事件,在方法Messenger.Send(eventArgs)
中触发。每个视图模型都为Messenger的公共事件注册事件处理程序。请查看MVVM Light工具包。 - opewixFrame
控件来在页面之间进行导航。将一个 Frame 放置在 MainWindow 中,保存对该实例的引用,并使用Frame.NavigateTo
方法进行导航。 - opewix