【问题标题】:INotifyPropertyChanged Event handler is always nullINotifyPropertyChanged 事件处理程序始终为空
【发布时间】:2016-10-23 10:10:34
【问题描述】:

我使用 .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>

注意: &lt;Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" &gt;&lt;/Label&gt;

MainWindow.xaml.cs constructor:中的DataContext设置如下

this.DataContext = new MainViewModel();

&lt;Frame&gt; 中,加载了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

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

成功调用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中改了

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

【问题讨论】:

    标签: c# .net wpf xaml mvvm


    【解决方案1】:

    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 中的工厂类。

    希望对你有帮助。

    【讨论】:

    • 噢噢噢噢!我是这么想的!我将如何实现这一目标,同时遵循 MVVM?
    • 从 MainViewModel 中实例化子视图模型(即 DashboardViewModel、StatisticsViewModel 等),或者最好从注入 MainViewModel 的工厂类中实例化。这样您就可以将 MainViewModel 实例传递给新的 ViewModel。
    猜你喜欢
    • 1970-01-01
    • 2011-06-30
    • 1970-01-01
    • 2013-07-17
    • 2018-02-03
    • 2016-09-16
    • 2010-11-27
    • 1970-01-01
    相关资源
    最近更新 更多