通过派生类修改时,WPF属性未在UI中更新

时间:2016-01-12 07:21:27

标签: c# wpf xaml inheritance data-binding

我无法在WPF应用程序的状态栏中显示CurrentStatus类的ViewModelBase属性。

ViewModelBaseTasksViewModelUserViewModel继承。

UserViewModelImportViewModelTestViewModel继承。

MainWindow的DataContext为TasksViewModel

ViewModelBase:

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        private string _currentStatus;

        public string CurrentStatus
        {
            get { return _currentStatus; }
            set
            {
                if (value == _currentStatus)
                {
                    return;
                }
                _currentStatus = value;
                OnPropertyChanged(nameof(CurrentStatus));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

TasksViewModel:

    public  class TasksViewModel : ViewModelBase
    {
        public IEnumerable<ViewModelBase> Collection => _collection;
        public override string ViewModelName => "Tasks";
        public TasksViewModel()
        {
            _collection = new ObservableCollection<ViewModelBase>
                          {
                              new ImportUsersViewModel(),
                              new TestFunctionsViewModel()
                          };

            // Added as per John Gardner's answer below.
            // watch for currentstatus property changes in the internal view models and use those for our status
            foreach (ViewModelBase i in _collection)
            {
                i.PropertyChanged += InternalCollectionPropertyChanged;
            }
        }

        // Added as per John Gardner's answer.
        private void InternalCollectionPropertyChanged(object source, PropertyChangedEventArgs e)
        {
            var vm = source as ViewModelBase;
            if (vm != null && e.PropertyName == nameof(CurrentStatus))
            {
                CurrentStatus = vm.CurrentStatus;
            }
        }
      }

ImportUsersViewModel:

internal class ImportUsersViewModel : UserViewModel
{
    private async void BrowseInputFileAsync()
    {
        App.Log.Debug("Browsing for input file.");
        string path = string.IsNullOrWhiteSpace(InputFile)
                          ? Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
                          : Path.GetDirectoryName(InputFile);
        InputFile = FileFunctions.GetFileLocation("Browse for User Import File",
                                                  path, FileFunctions.FileFilter.CSVTextAll) ?? InputFile;

        CurrentStatus = "Reading Import file.";
        ImportUsers = new ObservableCollection<UserP>();
        ImportUsers = new ObservableCollection<User>(await Task.Run(() => ReadImportFile()));
        string importResult =
            $"{ImportUsers.Count} users in file in {new TimeSpan(readImportStopwatch.ElapsedTicks).Humanize()}.";
        CurrentStatus = importResult; // Property is updated in ViewModelBase, but not in UI.
    }
}

MainWindow.xaml:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:viewModel="clr-namespace:ViewModel"
    xmlns:view="clr-namespace:Views"
    x:Class="MainWindow"
    Title="Users"
    Height="600"
    Width="1000"
    Icon="Resources/Icon.png">
    <Window.Resources>
        <DataTemplate DataType="{x:Type viewModel:ImportUsersViewModel}">
            <view:ImportUsers />
        </DataTemplate>
        <DataTemplate DataType="{x:Type viewModel:TestFunctionsViewModel}">
            <view:TestFunctionsView />
        </DataTemplate>
    </Window.Resources>
    <Window.DataContext>
        <viewModel:TasksViewModel />
    </Window.DataContext>
    <DockPanel>
        <StatusBar DockPanel.Dock="Bottom" Height="auto">
            <TextBlock Text="Status: " />
            <!-- Not updated in UI by any View Model -->
            <TextBlock Text="{Binding CurrentStatus}" />
        </StatusBar>
    </DockPanel>
</Window>

如果我将文本块绑定到ImportUsers UserControl中的CurrentStatus属性,它会更新而不会出现问题,但“父”状态栏不会更新。

我怀疑它无法显示在MainWindow状态栏中,因为虽然ImportViewModelTasksViewModel都继承ViewModelBase,但它们之间没有任何链接,TasksViewModel CurrentStatus属性未更新。

2 个答案:

答案 0 :(得分:1)

我认为你的怀疑是正确的。

Window上的DataContext是与ImportUsersViewModel不同的ViewModel实例。

虽然CurrentStatus是在同一对象层次结构中定义的,但ImportUsersViewModel中的CurrentStatus行正在更改与附加到Window DataContext的CurrentStatus属性不同的对象实例。

答案 1 :(得分:1)

您的窗口DataContextTaskViewModel,但该视图模型上没有任何内容正在关注其收集中的属性更改,并自行更新。从本质上讲,TasksViewModel 包含其他视图模型,但不会聚合他们的任何行为。

你需要这样的东西:

public  class TasksViewModel : ViewModelBase
{
    public IEnumerable<ViewModelBase> Collection => _collection;
    public override string ViewModelName => "Tasks";
    public TasksViewModel()
    {
        _collection = new ObservableCollection<ViewModelBase>
                      {
                          new ImportUsersViewModel(),
                          new TestFunctionsViewModel()
                      };

        // watch for currentstatus property changes in the internal view models and use those for our status
        foreach (var i in _collection)
        {
             i.PropertyChanged += this.InternalCollectionPropertyChanged;
        }
    }
  }

  //
  // if a currentstatus property change occurred inside one of the nested
  // viewmodelbase objects, set our status to that status
  //
  private InternalCollectionPropertyChanged(object source, PropertyChangeEvent e)
  {
      var vm = source as ViewModelBase;
      if (vm != null && e.PropertyName = nameof(CurrentStatus))
      {
           this.CurrentStatus = vm.CurrentStatus;
      }
  }