Scott Solmer
Scott Solmer

Reputation: 3897

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);
        }
    }
}

Upvotes: 2

Views: 419

Answers (1)

Evk
Evk

Reputation: 101483

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.

Upvotes: 1

Related Questions