2016-10-23 61 views
0

我使用.NETFramework,版本= v4.6.1INotifyPropertyChanged的事件處理程序總是空

我有一個窗口,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>如下,一個PageDashboard.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總是空。我該如何解決?這不會更新MainWindow.xaml中的文本。我測試過是否屬性值發生了變化,是的,它在MainViewModel.cs

中更改此外,我測試了標籤本身是否可見。爲此,我給變量一個值並正確顯示,但不更新。

回答

0

DashboardViewModel正在實例化MainViewModel的新實例,而不是使用分配給MainWindow的DataContext(因此視圖綁定到的實例)的實例。

爲您的代碼工作,你需要通過MainViewModel到DashboardViewModel的正確實例,因爲它是這種情況下,將有屬性更改事件的處理程序。

編輯:按照下面的評論,你應該實例化子的ViewModels如下:

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中的工廠類。

希望它有幫助。

+0

Ohhhhhhhh!我是這麼想的!我將如何實現這一點,同時遵循MVVM? –

+0

實例化來自MainViewModel內的子視圖模型(即DashboardViewModel,StatisticsViewModel等) - 或者最好是注入到MainViewModel中的Factory類。這樣您可以將MainViewModel實例傳遞給新的ViewModel。 – ibebbs