Yet another blog about WPF, Surface, SL, MVVM, NUI....

2011

2010

2009

2008

Tag - exception

Entries feed - Comments feed

 

UserControl/Control : how to get a reference to an element of the template

14 September 2010

When you want to create your own custom control you have two choices : create an UserControl or inherit from one of the "Control's classes" (ContentControl, ItemsControls or Control itself). When doing so, you'll surely need to access to the visual parts of your template from the code to add to it a nice behavior.

In this post, we'll discover how to access the template children by using the FindName method even on UserControl.

You create a control

I won't explain you how to create a custom control, so here is its base code :

[TemplatePart(Name = "PART_MyGrid", Type = typeof(Grid))]
  public class MyCustomControl : ContentControl
  {
    private Grid myAimedGrid;
 
    static MyCustomControl()
    {
	  //Overrides the style by ours
      DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl),
          new FrameworkPropertyMetadata(typeof(MyCustomControl)));
    }
  }



And here is how we define its template for the generic visual theme in the "Themes\generic.xaml" file. Notice that we add a named Grid :"PART_MyGrid". We'll seek for it later from the code.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:FindNamesApplication.MyContentControl">
  <Style TargetType="{x:Type local:MyCustomControl}">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:MyCustomControl }">
          <Grid x:Name="PART_MyGrid" Background="Black" Width="{TemplateBinding Width}"
              Height="{TemplateBinding Height}">
            <ContentPresenter Content="{TemplateBinding Content}" />
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>



Now how can we find the grid from the code behind ? Simply by accessing the template at the right moment : when the template is applied.
To do so we will override the OnApplyTemplate() method and access directly to the grid by its name with the FindName method. We can then act on it as we wish.

public override void OnApplyTemplate()
{
  //Effectively apply the template
  base.OnApplyTemplate();
 
  //Find the grid in the template once it's applied
  myAimedGrid = base.Template.FindName("PART_MyGrid", this) as Grid;
 
  //We can subscribe to its events
  myAimedGrid.PreviewMouseDown += 
     new MouseButtonEventHandler(myAimedGrid_PreviewMouseDown);
}
 
void myAimedGrid_PreviewMouseDown(object sender,
 System.Windows.Input.MouseButtonEventArgs e)
{
  //Proof 
  MessageBox.Show("Mouse preview Down on the grid !");
}



By the way, when you create a custom control which is focused on reusability you should imperatively declare its differents parts by using the TemplatePart attribute :

[TemplatePart(Name="PART_MyGrid",Type=typeof(Grid))]
public class CustomControl : ContentControl
{
// ....
}



You create an user control

Now the hardest part of the post : you create an user control as a reusable part of your application. To do so you create the C# file and the XAML file and as you want it to be customized, you set it's ContentTemplate as below :

/// <summary>
/// Interaction logic for MyCustomUserControl.xaml
/// </summary>
public partial class MyCustomUserControl : UserControl
{
  private Grid myAimedGrid;
 
  public MyCustomUserControl()
  {
    InitializeComponent();
  }
}

The XAML file :

<UserControl x:Class="FindNamesApplication.MyUserControl.MyCustomUserControl"
        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">
    <UserControl.ContentTemplate>
        <DataTemplate>
            <Grid x:Name="PART_MyGrid" Background="Black" Width="{TemplateBinding Width}"
                    Height="{TemplateBinding Height}">
                <ContentPresenter Content="{TemplateBinding Content}" />
            </Grid>
        </DataTemplate>
    </UserControl.ContentTemplate>
</UserControl>



Then, as you have seen before you override the OnApplyTemplate and get the child with the FindName methods : this won't do the job ! Actually, all you will get is 'null' or an InvalidOperationException sometimes.

Why ? Because by setting the controlTemplate, you define a DataTemplate which is then used by our UserControl to be applied on it's internal ContentPresenter. So by using findName on the UserControl we search the element named "PART_MyGrid" in the template of the UserControl and not in the template created by us and actually used.

So the solution is to seek the element on the correct element which is the ContentPresenter of the template of the UserControl. To do so we'll find it using the VisualTreeHelper to get the ContentPresenter and then use the FindName method with it as a parameter. Here is the code :

public override void OnApplyTemplate()
{
  base.OnApplyTemplate();
 
  //The ContentPresenter is the second child of the UserControl...
  ContentPresenter presenter = (ContentPresenter)
    (VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(this, 0), 0));
 
  //Be sure that the template is applied on the presenter
  presenter.ApplyTemplate();
 
  //get the grid from the presenter
  myAimedGrid =
    presenter.ContentTemplate.FindName("PART_MyGrid", presenter) as Grid;
 
  //We can subscribe to its events
  myAimedGrid.PreviewMouseDown
    += new MouseButtonEventHandler(myAimedGrid_PreviewMouseDown);
}
 
void myAimedGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
  //Proof 
  MessageBox.Show("Mouse preview Down on the grid !");
}



Interesting links

Here are some links to go further on the subject :



Conclusion

As we can see, nothing is impossible and once seen, it's quite easy to implement these differents solutions... Have a great code ! The source solution is linked to the post.

Shout it kick it on DotNetKicks.com



 

How I resolve my "MediaUnavailableException"'s problem in JavaFX...

10 December 2008

hey,

In my first try to create a videoPlayer I always get an exception trying to play a media which are on my local hard disk.

The problem

I was trying to execute this media :

var mediaURL : String = "file://C:/tchou.flv";

But it generates me logs like this one :

FX Media Object caught Exception com.sun.media.jmc.MediaUnavailableException: Media unavailable:


I search on the web and I notice that I was not alone facing this problem : look at this post.
But nobody seems to find the answer to this problem...

The solution

Actually I finally manage to get ride of this exception and it was quite simple : we should use '/' instead of '//' when using the "file" protocol. You just have to use the code below and it will work :

/*
* Do not use this :
* var mediaURL : String = "file://C:/tchou.flv";
*/
var mediaURL : String = "file:/C:/tchou.flv";

Quite easy in fact !