【问题标题】:DataItem=null on binding, can't find out why?DataItem=null 绑定,不知道为什么?
【发布时间】:2015-02-21 05:13:39
【问题描述】:

我正在尝试重现Sheridan's answer to this question 中的建议,以便在将 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 只是用于测试目的的虚拟视图模型/视图。

【问题讨论】:

    标签: c# wpf mvvm data-binding navigation


    【解决方案1】:

    在我的回答中,我声明您应该在App.xaml 中声明您的视图模型DataTemplates,以便每个视图都可以访问它们。将它们放在MainWindow 类中是您的第一个问题。

    另一个错误是您的Binding Path 为您的ICommand。如果您想从设置为Window.DataContext 的视图模型中访问某些内容,那么您应该使用RelativeSource Binding。试试这个:

    <Button Content="Create new user" Grid.Column="3" Grid.Row="3" Margin="5" Padding="5" 
        Command="{Binding DataContext.DisplayTest}, Mode=OneWay}" />
    

    还请记住,无论出于何种原因,您选择来使您的 MainViewModel 类扩展 ViewModelNavigationBase 类...这也可能会给您带来问题。

    无论如何,如果这不能解决您的问题,请告诉我。此外,如果您想随时在 Stack Overflow 上通知用户,只需在他们的姓名前加上 @ 符号,他们就会收到通知。如果你这样做了,你可以直接问我这个问题。

    【讨论】:

      【解决方案2】:

      MainViewModel 不是visual or logical tree 的直系祖先,这就是RelativeSource={RelativeSource AncestorType={x:Type vm:MainViewModel}} 找不到它的原因。

      你是怎么解决的?首先,请不要尝试通过各种 UI 组件来触发命令。仅仅因为您在互联网上的某个地方看到它并不意味着它是一个理想的设计选择。这样做意味着 LoginView 对其他视图和视图模型有深入的了解——这不好。如果您打算这样做,那么您不妨将所有内容编码为一个具有单个视图模型的单个 UI 类,这实际上只是类后面的大量代码。

      更好(但仍不是最佳)的方法是让 MainView(或视图模型)生成 LoginView。因为它持有对视图的引用,所以它也负责处理它。因此,可以显示 LoginView 以收集凭据,然后如果主视图表明凭据已成功验证,则可以进行处置。或者它可以只收集凭据并将其留给 MainView/viewmodel 来验证它们(这可以通过 MainView/viewmodel 触发后台调用以根据商店检查凭据来完成)。

      一个简单(粗略)的经验法则是:父视图可以知道子视图,但通常不应该发生相反的情况。 MVVM 是关于 decoupling and segregating functionality,但你是 tightly coupling 他们。当然,所有这些都比我所说明的要复杂得多,但是您仍然可以在保持实用性而不是过度设计的同时做一些这样的事情。

      所以,TLDR;:

      【讨论】:

      • 这已经发生了,我的LoginViewModel(和视图)在创建时由我的MainViewModel 生成。我的LoginViewModel 或 View 将如何向MainViewModel 发出凭据已通过验证的信号?这就是我在链接问题的答案中使用该方法的原因。另外,您提到我正在做的事情很糟糕,因为它将所有内容结合在一起。还有什么其他方法可以在不使用Pages 的情况下浏览视图?这就是我在找到链接问题的答案之前最初寻找的内容。
      • 在 MVVM 风格中,ViewModel 使用直接事件处理(如果一个 VM 创建另一个 VM)或使用 Messenger 类(如果 VM 是独立的)相互交互。 Messenger 有在方法Messenger.Send(eventArgs) 中触发的公共事件。每个视图模型都会为 Messenger 的公共事件注册事件处理程序。看看 MVVM 轻量级工具包。
      • 要在页面之间导航,请使用Frame 控件。在 MainWindow 中放置一个框架,保存对框架实例的引用并使用Frame.NavigateTo 方法进行导航。
      • 感谢您到目前为止的回答。你能给我指出一个关于如何使用事件处理进行导航的好教程吗?我用谷歌搜索了很多,似乎弹出“mvvm view navigation”的所有内容都是与这个问题相关的问题答案中描述的方法。这是我认为这是解决方法的另一个原因。再次感谢
      【解决方案3】:

      将 App 范围内的 MainViewModel 定义为静态资源。

      <App.Resources>
          <MainViewModel x:Key="MainViewModel" />
      </App.Resources>
      

      然后您将能够从任何视图绑定MainViewModel 命令。

      <Button Command="{Binding Source={StaticResource MainViewModel}, Path=DisplayTest}" />
      

      编辑

      或者试试这个代码:

      <Button Command="{Binding DisplayTest, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window), Path=DataContext}}"/>
      

      【讨论】:

      • 您的第一个解决方案是每次启动应用程序时都会弹出一个新窗口(另一个主窗口),但也没有解决问题。第二种解决方案无法编译。我已经尝试修复它,但它说路径设置了 2 次。
      • 第一个解决方案在每次应用程序启动时创建MainViewModel 实例。不是MainWindow。第二种解决方案必须适应您的代码。复制粘贴不起作用。此外,仅当您在 MainPage 中显示子窗口时,第二种解决方案才有效。
      • 给出一个应该适应我的项目的代码,和给出一个有错误导致无法编译的代码是有区别的。一方面,Path="DataContext" 应该是 Path=DataContextMode=FindAncestor AncestorType=... 甚至在语法上都不是有效的,等等。
      • 上面的代码是我凭脑子写的。在发布评论或答案之前编译每个代码将花费太多时间。无论如何,如果您无法修复代码,请询问。 - 我已经编辑了我的答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 1970-01-01
      • 2018-01-05
      • 1970-01-01
      • 2018-03-24
      • 1970-01-01
      • 2016-12-16
      相关资源
      最近更新 更多