Dec 4, 2008

Think outside the TextBox

I recently presented WPF and Silverlight material at Xamlfest - Denver. While I was there one of the students had an interesting idea. He was trying to do a simple drop shadow TextBlock in Silverlight.

Since Silverlight doesn't support Bitmap effects it wasn't as easy as adding a new BitmapDropShadow effect, like you would in WPF.

My first reaction was to have him try to re-template a TextBlock and fake the drop shadow. However, as we quickly found out, you can't re-template a TextBlock. This is because TextBlock inherits directly from FrameworkElement and handles it's own rendering. The Control class is where the Control Template is implemented. A good way to see this is by checking out the WPF poster created by our own John Bowen. This shows the hierarchy of the classes and you can see that TextBlock is a direct descendant of FrameworkElement, which means no control template.

So what to do? Well I thought about it for a little while and then decided that you could re-template a TextBox to act like a TextBlock (read only, no entry box, bind directly to a text property) TextBox supports re-templating (because it inherits from Control). Then we can fake a drop shadow effect inside the new template.

So first things first. Open blend and create a new Silverlight Application 


Then right click on the TextBlock icon on the left hand tool strip and select TextBox


This will change the tool strip icon to a TextBox control (instead of a TextBlock). Double click on the TextBox icon in the toolstrip to add a default size TextBox to your canvas.

Now we need to re-template the TextBox to act like TextBlock. That is, we want a read only piece of text. We want to be able to set the "Text" property to anything we want, and we don't want to Input box border and chrome to show up.

To do this in Blend you can right click on the TextBox that you added to your canvas and select "Edit Control Parts (Template)" and select "Edit a copy"


Give your new style a name (I named mine DropShadowTextBlock) and click ok

This will take you in to Template editing mode you will notice that the Objects and Timeline window now shows the visual elements that make up the TextBox control Template


What we want to do is replace this template with a TextBlock and Bind the Text property of the TextBlock to the Text Property of the TextBox. This will allow the people who use our control to set the Text property and get the expected behavior.

So first highlight (ctrl+click) all the elements under the RootElement and hit delete.


Now go over to your tool strip and select the TextBlock element and double click to add it to the RootElement.


Now we need to Bind the Text property of the TextBlock element to the Text property of the TextBox. To do this we will use a TemplateBinding.

Make sure your TextBlock is selected and go the properties panel and select the advanced property options for the text property


From the Advanced property options context menu select TemplateBinding | Text


Now let's test our TextBox as a TextBlock functionality. Exit template editing mode by click on the breadcrumb above the design canvas


Now we are back to our main design canvas and you can see our TextBox control looks like a TextBlock now (no border, read only). Let's test our template binding by setting the Text property

Select the TextBox from the Object and Timeline window. Go to the properties panel and set the Text property to something (I chose "Some Text"). You should see the text you enter reflected in your control on the design canvas (click on the design surface to make the control update)

Great! Now we have a control that we can re-template that acts like a TextBlock. Problem one solved. Now we need to fake a drop shadow.

Since we have a template-able "TextBlock" this part is fairly trivial.

We are going to add another TextBlock to our template, and TemplateBind it's Text property just like we did on the first one, and we are going to offset it to give a poor mans drop shadow effect.

So go back in to the TextBox template by selecting it from the Breadcrumb


Now add one more TextBlock to the RootElement grid (when you add multiple children to a grid it will stack them on top of each other)

When you do this you will see a jumbled up mess of text on your canvas. This is because we have not TemplateBound the Text property of the new TextBlock to the Text Property of the TextBox.

Mine look like this


So let's fix the TemplateBinding for the new TextBlock. Select the text block and then go to the Property panel and go to the Advanced Property Options for the Text property (just like we did for the first TextBlock) and then set the TemplateBinding to the Text property (just like before)

Once you set the Binding we just need to offset the TextBlock .5px below the TextBlock before it.

To set the Top Margin in Blend select the TextBlock you want to set the Margin on and go to the Property Panel. Find the Margin section and find the one with the arrow pointing upward. Then set the value you want.


Finally you probably want to give the new text block an opacity setting of 50% so that it is a lighter than the first one. This gives that shadow effect. When you are done you get something like this 


Like I said it is a poor man's drop shadow but it works and is pretty easy to implement.

This little exercise shows that you can use the power of control templating to do many different things. Sometimes you just have to think a little outside of the ... TextBox?