我无法在WPF应用程序的状态栏中显示CurrentStatus
类的ViewModelBase
属性。
ViewModelBase
由TasksViewModel
和UserViewModel
继承。
UserViewModel
由ImportViewModel
和TestViewModel
继承。
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状态栏中,因为虽然ImportViewModel
和TasksViewModel
都继承ViewModelBase
,但它们之间没有任何链接,TasksViewModel CurrentStatus
属性未更新。
答案 0 :(得分:1)
我认为你的怀疑是正确的。
Window上的DataContext是与ImportUsersViewModel不同的ViewModel实例。
虽然CurrentStatus是在同一对象层次结构中定义的,但ImportUsersViewModel中的CurrentStatus行正在更改与附加到Window DataContext的CurrentStatus属性不同的对象实例。
答案 1 :(得分:1)
您的窗口DataContext
是TaskViewModel
,但该视图模型上没有任何内容正在关注其收集中的属性更改,并自行更新。从本质上讲,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;
}
}