Reputation: 402
I am unable to display the CurrentStatus
property from my ViewModelBase
class in the status bar of my WPF application.
ViewModelBase
is inherited by TasksViewModel
and UserViewModel
.
UserViewModel
is inherited by ImportViewModel
and TestViewModel
.
MainWindow
has a DataContext of 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>
If I bind a text block to the CurrentStatus property inside the ImportUsers
UserControl it updates without issue, but the "parent" status bar does not update.
My suspicion is that it can't be displayed in the MainWindow status bar because, although both ImportViewModel
and TasksViewModel
inherit ViewModelBase
, they don't have any link to each other, and the TasksViewModel CurrentStatus
property isn't being updated.
Upvotes: 0
Views: 1088
Reputation: 25126
Your window's DataContext
is a TaskViewModel
, but nothing on that view model is watching for property changes in it's collection, and updating itself. essentially, TasksViewModel is containing the other viewmodels, but not aggregating any of their behaviors.
you need something like:
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;
}
}
Upvotes: 1
Reputation: 38333
I think your suspicion is correct.
The DataContext on the Window is a different ViewModel instance to that of the ImportUsersViewModel.
While CurrentStatus is defined in the same object hierarchy, the CurrentStatus line in the ImportUsersViewModel is changing a different object instance than the CurrentStatus property attached to the Window DataContext.
Upvotes: 1