Zoey Hewll
Zoey Hewll

Reputation: 5435

How to access the main DataContext from within a template

Summary

I've got an element within a data template, that I want bound to some property of the main data context. I realise that in this specific situation, a different solution may be preferable (and I have a working solution that avoids this), but I suspect this kind of problem may come up again and I want to know how to solve it in the general case.

Below are the specifics of my situation.

The Details

Data Hierarchy: I have a list of type A, each instance of A has a list of type B, each instance of B has some other data including a string for a text log.

UI Structure: I have a ComboBox to select an item of type A. I have a TabControl with the tabs representing items of type B, taken from the selected A above. In each tab, there is a means to enter data to populate the object of type B, and a log, representing changes to that instance of B.

Backing Logic: I track the selected item in each list with properties (SelectionA and SelectionB in the data context, MainWindowViewModel) that notify when they change. The B object also notifies when its log text changes. These ensure that the UI responds to changes to the backing data.

Problem: I want to move the notify logic to all be in one place (the DataContext, i.e. MainWindowViewModel), rather than having some in the B class and needing to duplicate the notify logic. To achieve this, I add a property (SelectionBLogText) to track the LogText property of the SelectionB object, and bind the log (in the templated tabpanel) to the main SelectionBLogText property. The problem is that within the tabpage, I can only seem to bind to properties of the selected B object (from the selected tab), and I need to bind to a property of the DataContext instead. I've tried using RelativeSource but nothing I've tried so far works, and the more I look at the docs the more I feel it's designed for another job.

The XAML (with irrelevant details removed):

<Window x:Class="WPFQuestion.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:local="clr-namespace:WPFQuestion"
        mc:Ignorable="d"

        Title="MainWindow"
        Height="350"
        Width="930">
    <DockPanel>
        <ComboBox
            ItemsSource="{Binding ListOfA}"
            SelectedItem="{Binding SelectionA}"
            DisplayMemberPath="Name"/>
        <TabControl
            ItemsSource="{Binding SelectionA}"
            SelectedItem="{Binding SelectionB}"
            DisplayMemberPath="Name">
            <TabControl.ContentTemplate>
                <ItemContainerTemplate>
                    <StackPanel>
                        <TextBox
                            IsReadOnly="True"
                            Text="{Binding Path=???.SelectionBLogText}"/>
                        <Button Click="ClearLogButton_Click"/>
                    </StackPanel>
                </ItemContainerTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </DockPanel>
</Window>

And the code-behind:

public partial class MainWindow : Window
{
    internal MainWindowViewModel vm;
    public MainWindow()
    {
        InitializeComponent();
        vm = new MainWindowViewModel();
        DataContext = vm;
    }
    // Various methods for event handling
}

public class A : IEnumerable<B>
{
    public string Name { get; set; }
    public List<B> Bs { get; set; }
}

public class B // previously : INotifyPropertyChanged
{
    public string Name { get; set; }
    public string LogText { get; set; }

    // various other properties
}

public class MainWindowViewModel : INotifyPropertyChanged
{
    private A _a;
    private B _b;

    public event PropertyChangedEventHandler PropertyChanged;

    public List<A> ListOfA { get; set; }

    public A SelectionA
    {
        get => _a;
        set
        {
            if (_a == value)
            {
                return;
            }
            _a = value;
            RaisePropertyChanged(nameof(SelectionA));
        }
    }

    public B SelectionB
    {
        get => _b;
        set
        {
            if (_b == value)
            {
                return;
            }
            _b = value;
            RaisePropertyChanged(nameof(SelectionB));
            RaisePropertyChanged(nameof(SelectionBLogText));
        }
    }

    public string SelectionBLogText
    {
        get => SelectionB.LogText;
        set
        {
            if (SelectionB.LogText == value)
            {
                return;
            }
            SelectionB.LogText = value;
            RaisePropertyChanged(nameof(SelectionBLogText));
        }
    }

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Upvotes: 0

Views: 570

Answers (1)

neelesh bodgal
neelesh bodgal

Reputation: 662

have you tried something like this when you used relative binding? if not please check this out.

     <TextBox IsReadOnly="True"
             Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},
             Path=Datacontext.SelectionBLogText}"/>

Upvotes: 1

Related Questions