INotifyPropertyChanged事件处理程序始终为null

时间:2016-10-23 10:10:34

标签: c# .net wpf xaml mvvm

我使用的是.NETFramework,Version = v4.6.1

我有一个窗口,MainWindow。这是XAML:

<Window x:Class="VexLibrary.DesktopClient.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


        xmlns:local="clr-namespace:VexLibrary.DesktopClient.Views"

        Title="MainWindow" Height="600" Width="800">
    <Grid>
        <StackPanel>
            <Grid Style="{StaticResource TitleBar}">
                <Border Style="{StaticResource TitleBarBorder}">
                    <DockPanel>
                        <StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
                            <TextBlock Style="{StaticResource TitleBarIcon}" Text="&#xE10F;" />
                            <Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
                        </StackPanel>
                        <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
                            <Label Style="{StaticResource TitleBarTime}">12:05 AM</Label>
                            <StackPanel Orientation="Horizontal">
                                <Label Style="{StaticResource TitleBarUsername}">Hassan</Label>
                                <Button>
                                    <TextBlock Style="{StaticResource TitleBarIcon}" Text="&#xE7E8;" />
                                </Button>
                            </StackPanel>
                        </StackPanel>
                    </DockPanel>
                </Border>
            </Grid>
            <Frame Width="700" Height="507" Source="Pages/Dashboard.xaml" />
        </StackPanel>
    </Grid>
</Window>

注意: <Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>

DataContext在MainWindow.xaml.cs constructor:

中设置如下

this.DataContext = new MainViewModel();

<Frame>中,加载了Page Dashboard.xaml

页面Dashboard.xaml包含来源:

<Page x:Class="VexLibrary.DesktopClient.Views.Pages.Dashboard"
      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:VexLibrary.DesktopClient.Views.Pages"
      mc:Ignorable="d" 
      d:DesignHeight="460" d:DesignWidth="690"
      Title="Page1">

    <Grid Width="690" Height="460" HorizontalAlignment="Center" VerticalAlignment="Center">
        <!-- Members, Users, Books -->
        <!-- Returns, Subscriptions, Statistics -->
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>

        <Button Style="{StaticResource MenuButton}" Grid.Column="0" Grid.Row="0">&#xE125;</Button>
        <Button Style="{StaticResource MenuButton}" Grid.Column="0" Grid.Row="1">&#xE845;</Button>
        <Button Style="{StaticResource MenuButton}" Grid.Column="1" Grid.Row="0">&#xE13D;</Button>
        <Button Style="{StaticResource MenuButton}" Grid.Column="1" Grid.Row="1">&#xE821;</Button>
        <Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="0">&#xE8F1;</Button>
        <Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="1" Command="{Binding ViewStatistics}">&#xEA37;</Button>
    </Grid>
</Page>

Dashboard.xaml.cs constructor中,我已经像这样定义了DataContext:DataContext = new DashboardViewModel();

DashboardViewModel.cs源代码是这样的(省略名称空间)

namespace VexLibrary.DesktopClient.ViewModels
{
    class DashboardViewModel : ViewModel
    {
        private MainViewModel parentViewModel;

        public DashboardViewModel()
        {
            this.parentViewModel = new MainViewModel();
        }

        public ICommand ViewStatistics
        {
            get
            {
                return new ActionCommand(p => this.parentViewModel.LoadPage("Statistics"));
            }
        }
    }
}

现在,在此代码中,请注意ButtonCommand

<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="1" Command="{Binding ViewStatistics}">&#xEA37;</Button>

成功调用Command并正确执行父LoadPage方法。父视图模型如下所示:

namespace VexLibrary.DesktopClient.ViewModels
{
    public class MainViewModel : ViewModel
    {
        private string currentPageTitle;

        public string CurrentPageTitle
        {
            get
            {
                return this.currentPageTitle;
            }
            set
            {
                currentPageTitle = value;
                NotifyPropertyChanged();
            }
        }

        public void LoadPage(string pageName)
        {
            this.CurrentPageTitle = pageName;
            Console.WriteLine(CurrentPageTitle);
        }
    }
}

CurrentPageTitle已成功更新。但是,它未在视图中更新。

父视图模型继承了基本上具有以下代码的ViewModel

namespace VexLibrary.Windows
{
    public abstract class ViewModel : ObservableObject, IDataErrorInfo
    {
        public string this[string columnName]
        {
            get
            {
                return OnValidate(columnName);
            }
        }

        [Obsolete]
        public string Error
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        protected virtual string OnValidate(string propertyName)
        {
            var context = new ValidationContext(this)
            {
                MemberName = propertyName
            };

            var results = new Collection<ValidationResult>();
            bool isValid = Validator.TryValidateObject(this, context, results, true);

            if (!isValid)
            {

                ValidationResult result = results.SingleOrDefault(p =>
                                                                  p.MemberNames.Any(memberName =>
                                                                                    memberName == propertyName));

                return result == null ? null : result.ErrorMessage;
            }

            return null;
        }
    }
}

ObservableObject.cs:

namespace VexLibrary.Windows
{
    public class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        // [CallerMemberName] automatically resolves the property name for us.
        protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            Console.WriteLine(handler == null);
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

调试后,我发现调用了NotifyPropertyChanged,但handler始终为null。我该如何解决?这不是更新MainWindow.xaml中的文本。我测试了属性值是否已更改,是的,它在MainViewModel.cs

中已更改

另外,我测试了标签本身是否可见。为此,我给变量一个值,它正确显示,但它没有更新。

1 个答案:

答案 0 :(得分:0)

DashboardViewModel实例化MainViewModel的新实例,而不是使用分配给MainWindow的DataContext的实例(因此视图绑定的实例)。

要使代码正常工作,您需要将正确的MainViewModel实例传递给DashboardViewModel,因为这个实例将具有属性更改事件的处理程序。

编辑:根据下面的评论,您应该按如下方式实例化您的子视图模型:

namespace VexLibrary.DesktopClient.ViewModels
{
    public class MainViewModel : ViewModel
    {
        private ViewModel _currentViewModel;

        public MainViewModel()
        {
            _currentViewModel = new DashboardViewModel(this);
        }

        public ViewModel CurrentViewModel
        {
            get { return _currentViewModel; }
            private set
            {
                _currentViewModel = value;
                OnPropertyChanged();
            }
        }
    }
}

然后你可以修改你的Xaml,使得框架从CurrentViewModel属性获取它的数据上下文,如下所示:

<Window x:Class="VexLibrary.DesktopClient.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:VexLibrary.DesktopClient.Views"
        Title="MainWindow" Height="600" Width="800">
    <Grid>
        <StackPanel>
            <Grid Style="{StaticResource TitleBar}">
                <Border Style="{StaticResource TitleBarBorder}">
                    <DockPanel>
                        <StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
                            <TextBlock Style="{StaticResource TitleBarIcon}" Text="&#xE10F;" />
                            <Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
                        </StackPanel>
                        <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
                            <Label Style="{StaticResource TitleBarTime}">12:05 AM</Label>
                            <StackPanel Orientation="Horizontal">
                                <Label Style="{StaticResource TitleBarUsername}">Hassan</Label>
                                <Button>
                                    <TextBlock Style="{StaticResource TitleBarIcon}" Text="&#xE7E8;" />
                                </Button>
                            </StackPanel>
                        </StackPanel>
                    </DockPanel>
                </Border>
            </Grid>
            <Frame Width="700" Height="507" Source="Pages/Dashboard.xaml" DataContext="{Binding CurrentViewModel}"/>
        </StackPanel>
    </Grid>
</Window>

然后需要使用某种形式的视图位置/导航来更改框架以显示正确的视图。一些MVVM框架(例如,CaliburnMicro)可以为您完成此任务。

同样,为了使此代码可测试,应将子视图模型的实例化委托给注入MainViewModel的工厂类。

希望它有所帮助。