Search code examples
c#wpfvb.netmvvmviewmodel

MVVM - Update StatusBar from various views


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!!

enter image description here

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>

Solution

  • 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.