WPF: ContentControl vs ContentPresenter

A small post to explain the little but important difference between ContentControl and ContentPresenter.

The most significant difference is that ContentPresenter has the ContentSource property while ContentControl hasn’t.

The ContentPresenter.ContentSource property specify which dependency property of the parent template instnace should be used to fill the ContentPresenter.Content.

For instance if you have a UserControl “MyControl” that defines a dependency property called  “MyProperty”, you can use the value of MyControl.MyProperty in the MyControl.Template in this way:

<ControlTemplate TargetType="MyControl">
      <StackPanel>
          <ContentPresenter ContentSource="MyProperty" />
      </StackPanel>
</ControlTempalte>

Instead using the ContentControl you could write the same template in this way:

<ControlTemplate TargetType="MyControl">
      <StackPanel>
          <ContentControl Content="{TemplateBinding MyProperty}" />
      </StackPanel>
</ControlTempalte>

In fact the ContentControl has a template that uses a ContentPresenter to show it’s own Content property using the ContentSource. The ContentPresenter is a light-weight component that is supposed to be used in a template as a simple place-holder for the Content property. The default value for the ContentPresenter.ContentSource is “Content”, so you just need to add an empty ContentPresenter in a template to let be the place-holder of the Content property of the template parent instance.

Advertisements

WPF: Attached Property in XAML Markup (The object ‘…’ already has a child and cannot add ‘…’)

Let’s say you need an attached property to set an arbitrary header content to any DependencyObject, to let you use that value and populate the Header property of any HeaderedContentControl, creating the class directly in your WPF test application:

public static class HeaderManager
{
	public static readonly DependencyProperty HeaderProperty = DependencyProperty.RegisterAttached(
		&quot;Header&quot;,
		typeof(object),
		typeof(HeaderManager),
		new PropertyMetadata(null));

	public static void SetHeader(DependencyObject element, object value)
	{
		element.SetValue(HeaderProperty, value);
	}

	public static object GetHeader(DependencyObject element)
	{
		return (object)element.GetValue(HeaderProperty);
	}
}

Then you want to attach that property to an UserControl that when injected in a TabItem can self declare it’s own header, and in first attempt you will end up trying to do this:

&lt;UserControl x:Class=&quot;RadicalTabRegion.Presentation.FirstView&quot;
             xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
             xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
             xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
             xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
             xmlns:s=&quot;clr-namespace:RadicalTabRegion.Presentation.Regions.Specialized&quot;
             mc:Ignorable=&quot;d&quot;
             d:DesignHeight=&quot;300&quot; d:DesignWidth=&quot;300&quot;&gt;

    &lt;s:HeaderManager.Header&gt;
        &lt;StackPanel Orientation=&quot;Horizontal&quot;&gt;
            &lt;TextBlock Text=&quot;First View&quot;/&gt;
            &lt;CheckBox/&gt;
        &lt;/StackPanel&gt;
    &lt;/s:HeaderManager.Header&gt;

    &lt;Grid&gt;
        &lt;TextBlock Text=&quot;I'm the first view&quot;/&gt;
    &lt;/Grid&gt;

&lt;/UserControl&gt;

But if you try to compile this, you will get this error:

“The object ‘UserControl’ already has a child and cannot add ‘Grid’. ‘UserControl’ can accept only one child.”

This is because the compiler needs to know that HeaderManager.Header is an attached property, before compile xaml in baml, but because the HeaderManager class is declared in the same assembly of the xaml, it can’t. Actually I think this can be overcome by Microsoft, but never mind there’s a couple of solutions for that.

The first solution is to move the markup declaration after the first element in the UserControl content, and our case after the Grid:

&lt;UserControl x:Class=&quot;RadicalTabRegion.Presentation.FirstView&quot;
             xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
             xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
             xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
             xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
             xmlns:s=&quot;clr-namespace:RadicalTabRegion.Presentation.Regions.Specialized&quot;
             mc:Ignorable=&quot;d&quot;
             d:DesignHeight=&quot;300&quot; d:DesignWidth=&quot;300&quot;&gt;

    &lt;Grid&gt;
        &lt;TextBlock Text=&quot;I'm the first view&quot;/&gt;
    &lt;/Grid&gt;

    &lt;s:HeaderManager.Header&gt;
        &lt;StackPanel Orientation=&quot;Horizontal&quot;&gt;
            &lt;TextBlock Text=&quot;First View&quot;/&gt;
            &lt;CheckBox/&gt;
        &lt;/StackPanel&gt;
    &lt;/s:HeaderManager.Header&gt;

&lt;/UserControl&gt;

The second solution is to move the HeaderManager class into a different class library, so in a different assembly, and this is the best approach because let you use the attached property more naturally, without incurring in that compile error even if you declare the property just before the UserControl.Content, that is more natural for an Header property. For instance if you add the class into a class library called “RadicalTabRegion.Windows.Presentation” you will end-up with this XAML that just works fine:

&lt;UserControl x:Class=&quot;RadicalTabRegion.Presentation.FirstView&quot;
             xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
             xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
             xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
             xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
             xmlns:s=&quot;clr-namespace:RadicalTabRegion.Windows.Presentation.Regions.Specialized;assembly=RadicalTabRegion.Windows.Presentation&quot;
             mc:Ignorable=&quot;d&quot;
             d:DesignHeight=&quot;300&quot; d:DesignWidth=&quot;300&quot;&gt;

    &lt;s:HeaderManager.Header&gt;
        &lt;StackPanel Orientation=&quot;Horizontal&quot;&gt;
            &lt;TextBlock Text=&quot;First View&quot;/&gt;
            &lt;CheckBox/&gt;
        &lt;/StackPanel&gt;
    &lt;/s:HeaderManager.Header&gt;

    &lt;Grid&gt;
        &lt;TextBlock Text=&quot;I'm the first view&quot;/&gt;
    &lt;/Grid&gt;

&lt;/UserControl&gt;

This example is a part of an implementation I’m making for Radical to implement a TabControlRegion, that is able to inject a simple UserControl into a TabItem (generated at runtime) and let the developer deeply customize the TabItem.Header simply declaring the attached property directly into the UserControl. This is under development right now, and it is just one of the possible solutions, if I will like it, I will post the entire code, and pull a request for integrating the new region directly into Radical.

C#: Enable Cookie Container on System.Net.WebClient (for Authentication)

As I told on the previous post the WebClient can be easily extended to add more functionality such in this case the Cookie Container, and let use it to authenticate versus the web pages that are protected using an authenticated cookie:

public class CookieWebClient : WebClient
{
	public CookieContainer CookieContainer { get; private set; }

	/// <summary>
	/// This will instanciate an internal CookieContainer.
	/// </summary>
	public CookieWebClient()
	{
		this.CookieContainer = new CookieContainer();
	}

	/// <summary>
	/// Use this if you want to control the CookieContainer outside this class.
	/// </summary>
	public CookieWebClient(CookieContainer cookieContainer)
	{
		this.CookieContainer = cookieContainer;
	}

	protected override WebRequest GetWebRequest(Uri address)
	{
		var request = base.GetWebRequest(address) as HttpWebRequest;
		if (request == null) return base.GetWebRequest(address);
		request.CookieContainer = CookieContainer;
		return request;
	}
}

And then you can use it as follow:

using (var client = new CookieWebClient())
{
	var loginData = new NameValueCollection
	{
		{ "UserName", "TestUser" },
		{ "Password", "MyPassword" }
	};

	// Login into the website (at this point the Cookie Container will do the rest of the job for us, and save the cookie for the next calls)
	client.UploadValues("http://domain.com/Account/LogOn", "POST", loginData);

	// Call authenticated resources
	client.UploadString("http://domain.com/ProtectedArea/MyProtectedResource", "POST", "some data");
}

CookieWebClient takes also a CookieContainer as parameter, to let you control the cookie container outside, and passing over different CookieWebclient instantiation.

How to use a Local Storage Resource

The Windows Azure Managed Library provides classes for accessing the local storage resource from within code that is running in a role instance.

You will just need to retrieve the full path of a named local storage, and then you can store any file you want. To retrieve the full path, you simple need this line of code:

RoleEnvironment.GetLocalResource("MainLocalStorage").RootPath

C#: Enable Automatic Decompression on System.Net.WebClient

The WebClient class is really easy to use, but actually doesn’t provide so much control over the underline request, or so it seems, but we can still inherits from it to get the control over the WebRequest that the WebClient using.

With this in mind I’ve could extend the WebClient enabling the powerfull AutomaticDecompression of the HttpWebRequest, and getting compressed web resources:

public class AutomaticDecompressionWebClient : WebClient
{
	protected override WebRequest GetWebRequest(Uri address)
	{
		var request = base.GetWebRequest(address) as HttpWebRequest;
		if (request == null) throw new InvalidOperationException("You cannot use this WebClient implementation with an address that is not an http uri.");
		request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
		return request;
	}
}