I am putting my first MVVM project together. I have a StatusBar that will be updated from various views (UserControls) from within the application. Each view will have its own DataContext. My original thought was to create a ViewModelBase class which implemented the INotifyPropertyChanged interface and also contained a public property to bind the the text of my StatusBar to. All other ViewModels within the application would then inherit the ViewModelBase class. Of course, this does not work. How can I accomplish this? I am not using MVVM Light or any other frameworks and I am programming in vb.net. Thanks in advance.
Update - Below is the translation of what Garry proposed in the 2nd answer, I am still unable to modify the status text from the MainViewModel?? Anyone see a problem with the vb translation of his c# code? This MVVM transition is causing major hair loss!!
ViewModelBase.vb
Imports System.ComponentModel
Public Class ViewModelBase
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
StatusViewModel.vb
Public Interface IStatusBarViewModel
Property StatusBarText() As String
End Interface
Public Class StatusBarViewModel
Inherits ViewModelBase
Implements IStatusBarViewModel
Private _statusBarText As String
Public Property StatusBarText As String Implements IStatusBarViewModel.StatusBarText
Get
Return _statusBarText
End Get
Set(value As String)
If value <> _statusBarText Then
_statusBarText = value
OnPropertyChanged("StatusBarText")
End If
End Set
End Property
End Class
MainViewModel.vb
Public Class MainViewModel
Inherits ViewModelBase
Private ReadOnly _statusBarViewModel As IStatusBarViewModel
Public Sub New(statusBarViewModel As IStatusBarViewModel)
_statusBarViewModel = statusBarViewModel
_statusBarViewModel.StatusBarText = "Test"
End Sub
End Class
Status.xaml (UserControl)
<StatusBar DataContext="{Binding StatusViewModel}">
...
<w:StdTextBlock Text="{Binding StatusText, UpdateSourceTrigger=PropertyChanged}" />
Application.xaml.vb
Class Application
Protected Overrides Sub OnStartup(e As System.Windows.StartupEventArgs)
Dim iStatusBarViewModel As IStatusBarViewModel = New StatusBarViewModel()
Dim mainViewModel As New MainViewModel(iStatusBarViewModel)
Dim mainWindow As New MainWindow() With { _
.DataContext = mainViewModel _
}
mainWindow.Show()
End Sub
End Class
MainWindow.xaml
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:GlobalStatusBarTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<local:Status Grid.Row="1" />
</Grid>
</Window>
To begin, take your main window, the application shell, and change it to something like this...
<DockPanel>
<sample:StatusBarControl DockPanel.Dock="Bottom" x:Name="StatusBarRegion"/>
<sample:MainContentControl DockPanel.Dock="Top" x:Name="MainContentRegion"/>
</DockPanel>
</Grid>
This partitions the shell into regions much like the Prism Region Manager. Note that the 'content' is now a user control, along with any other views you want to place in that region. Note also that the status bar has a region, but will not change its view or view model.
In the code behind of your shell, place two dependency properties like this...
public MainWindow()
{
InitializeComponent();
}
#region MainContentDp (DependencyProperty)
public object MainContentDp
{
get { return GetValue(MainContentDpProperty); }
set { SetValue(MainContentDpProperty, value); }
}
public static readonly DependencyProperty MainContentDpProperty =
DependencyProperty.Register("MainContentDp", typeof(object), typeof(MainWindow),
new PropertyMetadata(OnMainContentDpChanged));
private static void OnMainContentDpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MainWindow mainWindow = d as MainWindow;
if (mainWindow != null)
{
mainWindow.MainContentRegion.DataContext = e.NewValue;
}
}
#endregion
#region StatusBarDp (DependencyProperty)
public object StatusBarDp
{
get { return GetValue(StatusBarDpProperty); }
set { SetValue(StatusBarDpProperty, value); }
}
public static readonly DependencyProperty StatusBarDpProperty =
DependencyProperty.Register("StatusBarDp", typeof(object), typeof(MainWindow),
new PropertyMetadata(OnStatusBarDpChanged));
private static void OnStatusBarDpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MainWindow mainWindow = d as MainWindow;
if (mainWindow != null)
{
mainWindow.StatusBarRegion.DataContext = e.NewValue;
}
}
#endregion
These dependency properties are substitutes for the Prism Region Manager. Each Dependency Property sets the data context of its associated region. In your case, you want to change the MainContentDp to switch back and forth between view models.
In the app.xaml.cs file, you can override the start up method to look like this...
public partial class App
{
protected override void OnStartup(System.Windows.StartupEventArgs e)
{
IStatusBarViewModel iStatusBarViewModel = new StatusBarViewModel();
MainViewModel mainViewModel = new MainViewModel(iStatusBarViewModel);
OtherViewModel otherViewModel = new OtherViewModel(iStatusBarViewModel);
MainWindow mainWindow = new MainWindow
{
StatusBarDp = iStatusBarViewModel,
MainContentDp = mainViewModel
};
mainWindow.Show();
}
}
This code creates the shell and assigns the two Dependency Properties, which in turn will populate the regions with their respective view models.
A view model would look like this...
public class MainViewModel : ViewModelBase
{
public ICommand ClickCommand { get; set; }
private readonly IStatusBarViewModel _statusBarViewModel;
public MainViewModel(IStatusBarViewModel statusBarViewModel)
{
_statusBarViewModel = statusBarViewModel;
ClickCommand = new RelayCommand(ExecuteClickCommand, CanExecuteClickCommand);
}
private void ExecuteClickCommand(object obj)
{
_statusBarViewModel.StatusBarText = "Updating the db";
}
private bool CanExecuteClickCommand(object obj)
{
return true;
}
public void DoSomethingVeryImportant()
{
_statusBarViewModel.StatusBarText = "Starting some work";
// do some work here
_statusBarViewModel.StatusBarText = "Done doing some work";
}
}
And another view model...
public class OtherViewModel : ViewModelBase
{
private readonly IStatusBarViewModel _statusBarViewModel;
public OtherViewModel(IStatusBarViewModel statusBarViewModel)
{
_statusBarViewModel = statusBarViewModel;
}
public void UpdateTheDatabase()
{
_statusBarViewModel.StatusBarText = "Starting db update";
// do some work here
_statusBarViewModel.StatusBarText = "Db update complete";
}
}
}
Both VM's get the same status bar in their constructors and both are writing to the same status bar. Both VM's take turns sharing the same region in your shell, "MainContentRegion". Prism does all of this without the verbosity of this answer, but since you are in VB and do not use Prism, this approach will work fine.