Search code examples
c#wpfmvvmdata-bindingstatusbar

StatusBar Bind to property in UserControl in active tab of TabControl


I made this Minimal, Complete, and Verifiable example of the challenge I'm facing. It works but is definitely not MVVM and is nothing more than a hack.

It updates the status bar based on some property changing in the user control. It also updates when the user changes the tab. For the real deal, it will be displaying a records count (how many data rows are displayed in each tab).

There must be a cleaner way of doing this...

How can this be done using MVVM?

Note: In my actual implementation, the data context for each user control is different. So if someone has a suggestion for binding that involves a similar data context, please take that into consideration.

Main Window XAML

<Window x:Class="TabControlStatusBarBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TabControlStatusBarBinding"
        Title="MainWindow" Height="150" Width="300"
        x:Name="Window">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <TabControl Grid.Row="0">
            <TabItem Header="Tab1">
                <local:UserControl1 x:Name="UC1_A"/>
            </TabItem>
            <TabItem Header="Tab2">
                <local:UserControl1 x:Name="UC1_B" />
            </TabItem>
            <TabItem Header="Tab3">
                <local:UserControl1 x:Name="UC1_C"/>
            </TabItem>
        </TabControl>
        <StatusBar Grid.Row="1">
            <TextBlock Text="{Binding DP_StatusBarText, ElementName=Window, FallbackValue='No Updates'}"/>
        </StatusBar>
    </Grid>
</Window>

User Control XAML

<UserControl x:Name="MyUserControl" x:Class="TabControlStatusBarBinding.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" d:DesignHeight="50" d:DesignWidth="90" GotFocus="MyUserControl_GotFocus">
    <Grid Background="Gray">
        <TextBox x:Name="UC1" Text="test" HorizontalAlignment="Center" VerticalAlignment="Center" TextChanged="UC1_TextChanged"/>
    </Grid>
</UserControl>

Main Window Code Behind

namespace TabControlStatusBarBinding
{
    public partial class MainWindow : Window
    {
        public static DependencyProperty dp_StatusBarText = DependencyProperty.Register("DP_StatusBarText", typeof(string), typeof(MainWindow));
        public string DP_StatusBarText
        {
            get { return (string)GetValue(dp_StatusBarText); }
            set { SetValue(dp_StatusBarText, value); }
        }
        public MainWindow()
        {
            InitializeComponent();

            DP_StatusBarText = "Main window loaded";

            UC1_A.StatusUpdated += MyEventHandlerFunction_StatusUpdated;
            UC1_B.StatusUpdated += MyEventHandlerFunction_StatusUpdated;
            UC1_C.StatusUpdated += MyEventHandlerFunction_StatusUpdated;
        }
        public void MyEventHandlerFunction_StatusUpdated(object sender, EventArgs e)
        {
            DP_StatusBarText = (string)sender;
        }
    }
}

User Control Code Behind

namespace TabControlStatusBarBinding
{
    public partial class UserControl1 : UserControl
    {
        public event EventHandler StatusUpdated;
    
        public UserControl1()
        {
            InitializeComponent();
        }

        private void RaiseStatusUpdatedEvent(string SendText)
        {
            if (this.StatusUpdated != null)
                this.StatusUpdated(SendText, new EventArgs());
        }

        private void UC1_TextChanged(object sender, TextChangedEventArgs e)
        {
            RaiseStatusUpdatedEvent(UC1.Text);
        }

        private void MyUserControl_GotFocus(object sender, RoutedEventArgs e)
        {
            RaiseStatusUpdatedEvent(UC1.Text);
        }
    }
}

Solution

  • Well your example only uses views - no models, no viewmodels, so not very clear what are your problems with MVVM :) But I'll try to help you introducing some data in your example.

    First, just simple data item.

    public class TestDataItem : INotifyPropertyChanged {
        public int ID { get; set; }
        private string _text;
        public string Text
        {
            get { return _text; }
            set
            {
                if (value == _text) return;
                _text = value;
                OnPropertyChanged();
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    UserControl1. Just binds textbox Text to model Text property. Codebehind is empty.

    <UserControl x:Class="WpfApplication2.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"              
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
        <Grid Background="Gray">
            <TextBox Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Grid>
    </UserControl>
    

    Window. Tabs in tab control are now bound to a list of data items. Tab content is just UserControl1 (it will inherit data context).

    <Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"                
        xmlns:wpfApplication2="clr-namespace:WpfApplication2"
        mc:Ignorable="d"
        Title="MainWindow" Height="500" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <TabControl x:Name="tabControl" Grid.Row="0" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
            <TabControl.ItemTemplate>
                <DataTemplate DataType="wpfApplication2:TestDataItem">
                    <TextBlock Text="{Binding ID}" />
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.ContentTemplate>
                <DataTemplate DataType="wpfApplication2:TestDataItem">
                    <wpfApplication2:UserControl1 />
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
        <StatusBar Grid.Row="1">
            <TextBlock Text="{Binding SelectedItem.Text, FallbackValue='No Updates'}"/>
        </StatusBar>
    </Grid>
    </Window>
    

    Window code-behind:

    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            this.DataContext = new MainViewModel();
        }
    }
    

    View model:

     public class MainViewModel : INotifyPropertyChanged {
        public IEnumerable<TestDataItem> Items => new[] {
                new TestDataItem() {ID = 100, Text = "item1"},
                new TestDataItem() {ID = 200, Text = "item2"},
                new TestDataItem() {ID = 300, Text = "item3"}
            };
    
        private string _statusText = "No data selected";
        public string StatusText
        {
            get { return _statusText; }
            set
            {
                if (value == _statusText) return;
                _statusText = value;
                OnPropertyChanged();
            }
        }
    
        private TestDataItem _selectedItem;
        public TestDataItem SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                if (Equals(value, _selectedItem)) return;
                _selectedItem = value;
                OnPropertyChanged();
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    So we just bind status to SelectedItem.Text and done.