Telerik blogs

It is often necessary to customize the header of a RadPane. Really common is to need to put an image in the header, to bind that image to a property, etc. There are many scenarios in which you need to add some custom behavior to the header. One of this situations is when you need to have close button or another button in the header of the pane. The tricky thing here is that you need to implement an event handler for the Click event of the button and to write some code that will do something with the Pane.

 

In this post we will create a Docking control with some panes and the panes will have close buttons in their headers. This will be achieved by using the idea of attached behavior.

 

First of all we need to add the button to the header and just to have it displayed correctly. As in most of the controls for Silverlight (and WPF) you can do that using a DataTemplate. The DataTemplate, used for displaying the header of the pane, is the template set to the HeaderTemplate property. The DataContext in this template is the value of the Header property of the pane. Here is the XAML that will add “X” button to the header of the pane:

<telerikDocking:RadDocking>
 <telerikDocking:RadDocking.Resources>
 <DataTemplate x:Key="PaneWithCloseButton_HeaderTemplate">
 <StackPanel Orientation="Horizontal">
 <ContentPresenter Content="{Binding}" />
 <Button Content="X" Width="18" Height="18" />
 </StackPanel>
 </DataTemplate>
 </telerikDocking:RadDocking.Resources>
 <telerikDocking:RadDocking.DocumentHost>
 <telerikDocking:RadSplitContainer>
 <telerikDocking:RadPaneGroup>
 <telerikDocking:RadPane Header="PANE"
 HeaderTemplate="{StaticResource PaneWithCloseButton_HeaderTemplate}">
 <Button Content="PANE" />
 </telerikDocking:RadPane>
 <telerikDocking:RadPane Header="PANE1"
 HeaderTemplate="{StaticResource PaneWithCloseButton_HeaderTemplate}">
 <Button Content="PANE1" />
 </telerikDocking:RadPane>
 </telerikDocking:RadPaneGroup>
 </telerikDocking:RadSplitContainer>
 </telerikDocking:RadDocking.DocumentHost>
</telerikDocking:RadDocking>

 

Now we may need to perform some action when the the button is clicked. In our case we will need to close the pane. We can add custom actions as response of events by using attached properties. We will create a static class, called CloseButton and will add the attached properties we need to it. Here is the code of the class without any actions implemented:

public static readonly DependencyProperty IsCloseButtonProperty =
    DependencyProperty.RegisterAttached("IsCloseButton", typeof(bool), typeof(CloseButton), new PropertyMetadata(OnIsCloseButtonChanged));
 
public static bool GetIsCloseButton(DependencyObject obj)
{
 return (bool)obj.GetValue(IsCloseButtonProperty);
}
 
public static void SetIsCloseButton(DependencyObject obj, bool value)
{
    obj.SetValue(IsCloseButtonProperty, value);
}
 
private static void OnIsCloseButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
 bool oldValue = (bool)e.OldValue;
 bool newValue = (bool)e.NewValue;
    var button = d as ButtonBase;
 
 if (button != null && oldValue != newValue)
    {
 if (!oldValue && newValue)
        {
            button.Click += OnCloseButtonClick;
        }
 else
        {
            button.Click -= OnCloseButtonClick;
        }
    }
}
 
private static void OnCloseButtonClick(object sender, RoutedEventArgs args) { }

 

What this code does is to hook up to the Click event of the button of the IsCloseButton attached property is set to true and to remove the handler if it is set to false. Now we need just to close that pane that owns the button that was clicked. Here is one suggestion how you can do that:

private static void OnCloseButtonClick(object sender, RoutedEventArgs args)
{
    var button = sender as FrameworkElement;
 
 if (button != null)
    {
        var pane = button.ParentOfType<RadPane>();
 if (pane != null)
        {
            pane.IsHidden = true;
        }
    }
}

What we need to do now is just to set this property to true for the button in the HeaderTemplate of the pane. Now we can run and try the application. We notice that it works correctly and accomplishes our main task – to add close button in the header of a pane and to make it close the pane when clicked without writing code-behind.

 

Now we can notice that the close button is displayed even in the AutoHide area and in the panes that are not in the DocumentHost area and we may want to not show them in these cases. We will implement attached property that if is set to true will hide the element to which is set when it is in a pane that is in an AutoHide area. You could use the same approach to implement different scenarios.

 

We will create attached property in the same class that will be called HideInAutoHideArea. Here is the code that will take care for adding the property and attaching the needed events:

public static readonly DependencyProperty HideInAutoHideAreaProperty =
    DependencyProperty.RegisterAttached("HideInAutoHideArea", typeof(bool), typeof(CloseButton), new PropertyMetadata(OnHideInAutoHideAreaChanged));
 
public static bool GetHideInAutoHideArea(DependencyObject obj)
{
 return (bool)obj.GetValue(HideInAutoHideAreaProperty);
}
 
public static void SetHideInAutoHideArea(DependencyObject obj, bool value)
{
    obj.SetValue(HideInAutoHideAreaProperty, value);
}
 
private static void OnHideInAutoHideAreaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
 bool oldValue = (bool)e.OldValue;
 bool newValue = (bool)e.NewValue;
    var button = d as FrameworkElement;
 
 if (button != null && oldValue != newValue)
    {
 if (!oldValue && newValue)
        {
            button.Loaded += OnCloseButtonLoaded;
        }
 else
        {
            button.Loaded -= OnCloseButtonLoaded;
        }
    }
}
 
private static void OnCloseButtonLoaded(object sender, RoutedEventArgs args) { }

As you could notice we are using the Loaded event of the visual element. This is because the only way for a Pane to move to the AutoHide area or to another PaneGroup is to be detached and reattached to the visual tree. Now we need to detect is the Pane in an AutoHide area or not. Here is the code that accomplishes this task:

private static void OnCloseButtonLoaded(object sender, RoutedEventArgs args)
{
    var button = sender as FrameworkElement;
 
 if (button != null)
    {
        var pane = button.ParentOfType<RadPane>();
 if (pane != null)
        {
            button.Visibility =
                pane.Parent is AutoHideArea
                        ? Visibility.Collapsed
                        : Visibility.Visible;
        }
    }
}

Now we can just set both of the properties to true for the “X” button in the  HeaderTemplate and the button will close the pane on click and will disappear when the pane is in the AutoHide area.

You can find the full source code here.


About the Author

Miroslav Nedyalkov

is a XAML enthusiast. Speaker at various local user groups and events. Miroslav is passionate about cutting edge technologies and likes to share his knowlage with others. You can follow him on Twitter at @miro_nedyalkov.

Comments

Comments are disabled in preview mode.