2
votes

I'm learning WPF, and I think I'm missing something with user controls. I'll try and demonstrate by example. Basically, suppose I have a treeview, and I want to bind a labels text to the treeviews selectedItem. This seems simple :

<!-- Window.xaml -->
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="100" />
    </Grid.RowDefinitions>

    <TreeView Name="MyTreeView">
        <TreeViewItem Header="Root">
            <TreeViewItem Header="Item1"></TreeViewItem>
            <TreeViewItem Header="Item2"></TreeViewItem>
        </TreeViewItem>
    </TreeView>

    <Label Content="{Binding ElementName=MyTreeView, Path=SelectedItem.Header}" Grid.Row="1"></Label>
</Grid>

Now, to complicate things I'll add a user control into the mix. The user control is basically a TreeView in a grid :

<!-- ExampleUserControl.xaml -->
<UserControl>
    <Grid>
        <TreeView Name="UserControlTreeView">
            <TreeViewItem Header="Root">
                <TreeViewItem Header="Item1"></TreeViewItem>
                <TreeViewItem Header="Item2"></TreeViewItem>
            </TreeViewItem>
        </TreeView>
    </Grid>
</UserControl>

The intent is to replace the vanilla treeview used above with the usercontrol, and have the labels content changed based on what is selected. So, I try something like this :

<Window>    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="100" />
        </Grid.RowDefinitions>

        <local:ExampleUserControl1 x:Name="MyUserControl">            
        </local:ExampleUserControl1>

        <Label Content="{Binding ElementName=MyUserControl, Path=SelectedItem.Header}" Grid.Row="1"></Label>
    </Grid>
</Window>

This compiles and runs, but when I select items in the treeview, the label doesn't change. I think I understand why : the user control contains a treeview, but is clearly not a treeview. I'm not sure on the best way to fix this. I can think of one way that seems problematic :

  • Add a property 'SelectedItem' to the user control in code, that returns the treeviews selected item.

This would work for one property, or a few, but the thought of doing this for each property in treeview seems like a serious waste of code + time. Is there some way to still use the user control, but make it behave like a normal TreeView?

3

3 Answers

2
votes

Child controls are not accessible from outside like this. One way is to have wrapper property in code behind for SelectedItem and bind to that from outside which you already mentioned.

But i would suggest to use Tag property of UserControl to contain the reference of TreeView and bind to Tag property from outside. That way you don't have to create multiple properties in code behind.

Use x:Reference to bind to TreeView:

<UserControl Tag="{Binding Source={x:Reference UserControlTreeView}}">
    <Grid>
        <TreeView Name="UserControlTreeView">
            <TreeViewItem Header="Root">
                <TreeViewItem Header="Item1"></TreeViewItem>
                <TreeViewItem Header="Item2"></TreeViewItem>
            </TreeViewItem>
        </TreeView>
    </Grid>
</UserControl>

Bind label control now:

<Label Content="{Binding ElementName=MyUserControl,
                         Path=Tag.SelectedItem.Header}"/>
1
votes

You are correct in your reasoning about why it is not working. The SelectedItem property is not visible to the Label.

Instead of using a UserControl to simply host a TreeView, you can use a TreeView like you would normally, but bind to it's ItemsSource. For Example:

<!--MainWindow.xaml-->
<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:coll="clr-namespace:System.Collections;assembly=mscorlib"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
        <Grid.Resources>
        <coll:ArrayList x:Key="data">
            <TreeViewItem Header="Parent 1">
                <TreeViewItem Header="Child 1"/>
                <TreeViewItem Header="Child 2"/>
            </TreeViewItem>
            <TreeViewItem Header="Parent 2">
                <TreeViewItem Header="Child 1"/>
                <TreeViewItem Header="Child 2"/>
            </TreeViewItem>
        </coll:ArrayList>
    </Grid.Resources>

    <TreeView Grid.Row="0" ItemsSource="{StaticResource data}" x:Name="tv1"/>

    <Label Grid.Row="1" Content="{Binding ElementName=tv1, Path=SelectedItem.Header}"/>
    </Grid>
</Window>

For this example, I am using a StaticResource, which is not dynamic... BUT, all you have to do is bind to the itemsource. This is the best way IMHO to handle multiple TreeViews.

EDIT:

Looking again, I see that you were more so trying to get the usercontrol to behave like a treeview, not just get the results.

You can not do exactly what you're talking about with a UserControl.

What you would want to do is make a new class which extends treeview. It doesn't need to be a user control.

1
votes

Try the following way.

Create a public property of type TreeView in your user control and return the treeview as shown below.

    //ExampleUserControl.xaml.cs
    public TreeView UTreeView
    {
        get
        {
            return UserControlTreeView;
        }
    }

Then use this property in Window.xaml as given below.

  <local:ExampleUserControl1 x:Name="MyUserControl">
    </local:ExampleUserControl1>

    <Label Content="{Binding ElementName=MyUserControl, Path=UTreeView.SelectedItem.Header}" Grid.Row="1"></Label>