Question

I've been working on a control for a while that can be used to repeat a layout in our application. It's working great but the problem is that the controls inside the layout control aren't participating in the logical tree.

It's a custom control with a left header, a right header a body and a standard footer.

Generic.xaml:

<Style TargetType="{x:Type local:FrameControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:FrameControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <Border VerticalAlignment="Top" Height="50" Background="LightGray"/>

                        <DockPanel Background="{TemplateBinding HeaderColor}">
                            <ItemsControl ItemsSource="{TemplateBinding HeaderLeftContent}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <StackPanel DockPanel.Dock="Left" FlowDirection="LeftToRight" HorizontalAlignment="Left"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                            </ItemsControl>

                            <ItemsControl ItemsSource="{TemplateBinding HeaderRightContent}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <StackPanel DockPanel.Dock="Right" Height="40" 
                                                    Margin="5, 5, 5, 0" VerticalAlignment="Top"
                                                    Orientation="Horizontal" FlowDirection="RightToLeft"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                            </ItemsControl>
                        </DockPanel>

                        <Border BorderThickness="1" BorderBrush="Black" Margin="0,49,0,0" Background="White">
                            <ItemsControl ItemsSource="{TemplateBinding BodyContent}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <DockPanel Background="White" Margin="5"/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                            </ItemsControl>
                        </Border>
                        <Border VerticalAlignment="Bottom" Height="0" Background="LightGray"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

FrameControl.cs

public class FrameControl : Control
{
    public Brush HeaderColor
    {
        get { return (Brush)GetValue(HeaderColorProperty); }
        set { SetValue(HeaderColorProperty, value); }
    }

    public static readonly DependencyProperty HeaderColorProperty =
        DependencyProperty.Register("HeaderColor", typeof(Brush), typeof(FrameControl), new UIPropertyMetadata(null));

    public Collection<Object> HeaderLeftContent
    {
        get { return (Collection<Object>)GetValue(HeaderLeftContentProperty); }
        set { SetValue(HeaderLeftContentProperty, value); }
    }

    public static readonly DependencyProperty HeaderLeftContentProperty =
       DependencyProperty.Register("HeaderLeftContent", typeof(Collection<Object>), typeof(FrameControl),
       new UIPropertyMetadata(null));

    public Collection<Object> HeaderRightContent
    {
        get { return (Collection<Object>)GetValue(HeaderRightContentProperty); }
        set { SetValue(HeaderRightContentProperty, value); }
    }

    public static readonly DependencyProperty HeaderRightContentProperty =
        DependencyProperty.Register("HeaderRightContent", typeof(Collection<Object>), typeof(FrameControl),
        new UIPropertyMetadata(null));

    public Collection<Object> BodyContent
    {
        get { return (Collection<Object>)GetValue(BodyContentProperty); }
        set { SetValue(BodyContentProperty, value); }
    }

    public static readonly DependencyProperty BodyContentProperty =
        DependencyProperty.Register("BodyContent", typeof(Collection<Object>), typeof(FrameControl),
        new UIPropertyMetadata(null));


    static FrameControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(FrameControl), new FrameworkPropertyMetadata(typeof(FrameControl)));
    }

    public FrameControl()
    {
        SetValue(HeaderLeftContentProperty, new Collection<Object>());
        SetValue(HeaderRightContentProperty, new Collection<Object>());
        SetValue(BodyContentProperty, new Collection<Object>());
    }
}

Usage example

   <fc:FrameControl>
        <fc:FrameControl.HeaderLeftContent>
            <Label Content="Left content of the header"/>
        </fc:FrameControl.HeaderLeftContent>
        <fc:FrameControl.HeaderRightContent>
                <Button>
                <TextBlock Text="Example button"/>
            </Button>
                <ComboBox SelectedIndex="0">
                    <ComboBoxItem Content="Filter A"/>
                    <ComboBoxItem Content="Filter B"/>
                    <ComboBoxItem Content="Filter C"/>
                </ComboBox>
        </fc:FrameControl.HeaderRightContent>
        <fc:FrameControl.BodyContent>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <Label Grid.Row="0" Grid.Column="0"  Content="name"/>
                <TextBox Grid.Row="0" Grid.Column="2"/>

                <Label Grid.Row="1" Grid.Column="0"  Content="Type name"/>
                <TextBox Grid.Row="1" Grid.Column="2" />

                <Label Grid.Row="2" Grid.Column="0"  Content="Keyword"/>
                <TextBox Grid.Row="2" Grid.Column="2"/>

                <Label Grid.Row="3" Grid.Column="0"  Content="Parameter"/>

                <ComboBox Grid.Row="4" Grid.Column="0" SelectedIndex="0">
                    <ComboBoxItem Content="Width"/>
                    <ComboBoxItem Content="Height"/>
                </ComboBox>
                <ComboBox Grid.Row="4" Grid.Column="1" SelectedIndex="0">
                    <ComboBoxItem Content="..."/>
                    <ComboBoxItem Content="="/>
                    <ComboBoxItem Content="&lt;"/>
                    <ComboBoxItem Content="&gt;"/>
                </ComboBox>
                <TextBox Grid.Row="4" Grid.Column="2"/>
            </Grid>
        </fc:FrameControl.BodyContent>
    </fc:FrameControl>

I've tried using different type of properties to hold the elements for the different region like UIElementCollection but that made no difference. I also tried manually adding the controls to the logical tree, but I did not succeed in that. Am I missing something?

Was it helpful?

Solution

You say that your actual problem is that you cannot bind using ElementName with this control. Elements that are inside the FrameControl cannot be approached this way. However, that problem has absolutely nothing to do with the visual tree. Those elements are in the visual tree, but you just can't access them because they were defined inside a ControlTemplate.

You can use the ControlTemplate.FindName method to 'find' the elements for you. However, you need to have an element that the ControlTemplate has been applied to and a named element in the ControlTemplate:

// Assuming that your DockPanel in the ControlTemplate was named DockPanel
DockPanel dockPanel = 
    frameControl.Template.FindName("DockPanel", frameControl) as DockPanel;
if (dockPanel != null) // You must check for null
{
    DoSomethingHereWith(dockPanel);
}

See the linked page for more help.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top