Clipping the layout

By default, the TextBlock class clips its textual content at its bounding rectangle, so that text does not leak out of it. Clipping is the process of cutting off a portion of the visible output of a control. But what about if we want the text to extend its bounds?

There is a property named Clip, that we typically use to adjust the visible portion of controls. However, this can only reduce what is already visible. It cannot increase the rendering space available to the control. Before we continue with our example, let's take a short detour to investigate this property.

The Clip property, which is defined in the UIElement class, takes a Geometry object as its value. The object that we pass it can be created from any of the classes that extend the Geometry class, including the CombinedGeometry class. Therefore, the clipped object can be made into any shape. Let's view a simple example:

<Rectangle Fill="Salmon" Width="150" Height="100" RadiusX="25" RadiusY="50"> 
  <Rectangle.Clip> 
    <EllipseGeometry Center="150,50" RadiusX="150" RadiusY="50" /> 
  </Rectangle.Clip> 
</Rectangle> 

Here, we use an EllipseGeometry object to make a Rectangle element appear as a small bullet shape. It works by displaying all of the image pixels from the Rectangle element that lies within the oval boundary of the EllipseGeometry object and hiding all those that lie outside the boundary. Let's take a look at the visual output of this code:

Returning to our previous example, the TextBlock class also clips its content in a similar way, but with a rectangle the size of the control, instead of an off-centered oval. Rather than using the Clip property, which provides the user with the same ability to clip the control as the other controls offer, it uses a protected method to ask for the Geometry object to use in the clipping process.

We could indeed return any geometric shape from this method, but it would not have the same visual effect as passing the shape to the Clip property would. For our example, we don't want to restrict the visible size of the control, but instead, remove the clipped area at the bounds of the control.

If we knew exactly what size we wanted to set the clipped range at, we could return a Geometry object of that size from the GetLayoutClip method. However, for our purposes, and to enable any of our custom TextBlock objects to leak endless text out of their bounds, we can simply return null from this method. Let's look at the difference between the two.

First, we create our BoundlessTextBlock class by extending the TextBlock class. Probably, one of the easiest ways to do this in Visual Studio is to add a WPF User Control object into our Controls folder and then simply replace the word UserControl with the word TextBlock in both the XAML file and its associated code behind file. Failure to change both will result in a design-time error that complains that Partial declarations of 'BoundlessTextBlock' must not specify different base classes:

<TextBlock x:Class="CompanyName.ApplicationName.Views.Controls.BoundlessTextBlock" 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" /> 

As can be seen from this example, our XAML file can be left remarkably empty, and for our requirements, we only need to override the single GetLayoutClip method in the code behind file. In this first example, we will return an EllipseGeometry object with the same size as the text block that will be used in the user interface:

using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Media; 
 
namespace CompanyName.ApplicationName.Views.Controls 
{ 
  public partial class BoundlessTextBlock : TextBlock 
  { 
    public BoundlessTextBlock() 
    { 
      InitializeComponent(); 
    } 
 
    protected override Geometry GetLayoutClip(Size layoutSlotSize) 
    { 
      return new EllipseGeometry(new Rect(new Size(150, 22))); 
    } 
  } 
} 

Let's see how we can use our new class. First, we need to define a XAML Namespace that maps to the CLR namespace where we saved the class. Next, for demonstration purposes, we wrap our BoundlessTextBlock object in a Border object, so that we can see its natural bounds:

xmlns:Controls="clr-namespace:CompanyName.ApplicationName.Views.Controls" 
...
<Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Center"
VerticalAlignment="Center" SnapsToDevicePixels="True"> <Controls:BoundlessTextBlock Text="Can you see what has happened?"
Background="Aqua" FontSize="14" Width="150" Height="22" />
</Border>

Let's take a look at the visual output from this example:

As you can see, the visual output from our BoundlessTextBlock object has been restricted to display only the pixels that lie within the EllipseGeometry object that was returned from the GetLayoutClip method. But what will happen if we return an EllipseGeometry object that is larger than our custom text block? Let's find out, by returning this object instead:

return new EllipseGeometry(new Rect(new Size(205, 22)));

Now, looking at the visual output of our BoundlessTextBlock object, we can see that the content of our custom text block now extends beyond its bounds, thanks to the Border object and the blue background:

So, we can see that the clipping that is applied using the Geometry object that is returned from the GetLayoutClip method is not only unaffected by the control's natural bounds, but in fact, can directly alter them. Returning to our original idea on this subject, if we want to totally remove the clipping at the control's bounding edges, we can simply return null from this method instead:

protected override Geometry GetLayoutClip(Size layoutSlotSize) 
{ 
  return null; 
} 

Let's see the result of this change now:

As you can see, the text now reaches right out of the boundary of the containing TextBlock object, and continues until the end of the text value. Note that it would extend as long as the text string requires, if given enough space by its parent control(s).

Let's look at another example of extending these classes to alter their functionality now.