Modifying a Visual Tree/ControlTemplate

Hi,
I'm trying to build a custom controls based on an existing one.

For example, I'd like to add an icon to a textbox so that it can be used for some additional information etc.

My idea is to make a custom control by inheriting from textbox and modifying the default visual tree. Most of the examples describe how to replace the visual tree by applying a ControlTemplate. What I try, is to get the original VisualTree from TextBox. I want to create a new ControlTemplate with a Grid containing an Image and the visual tree of the TextBox as children.
The result should be a custom control that behaves and looks exactly like a TextBox except for the Image.

Is it possible to get the default visual tree of a FrameworkElement in the constructor and alter it?

[781 byte] By [|L] at [2008-1-5]
# 1
I'd suggest you consider using a ControlTemplate -- they're easier to manage, make your control more stylable, and don't rely on the ControlTemplate 'being' a certain way. By that, other than a specific "PART" that might be required such as the text editing features of a text box, you don't need to assume the existence of any particular element within the ControlTemplate.

<Style x:Key="MyAdvancedTextBox" BasedOn="{x:Null}" TargetType="{x:Type local:MyTextBox}">
<Setter Property="Foreground" Value="{DynamicResource {xTongue Tiedtatic SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{DynamicResource {xTongue Tiedtatic SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.859*"/>
<ColumnDefinition Width="0.141*"/>
</Grid.ColumnDefinitions>
<ScrollViewer SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" x:Name="PART_ContentHost" Margin="2,2,0,2"/>
<Ellipse Fill="#FF0BAEE5" Stroke="#FF000000" Margin="4,0,0,2" Grid.Column="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {xTongue Tiedtatic SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

The above just is a simple control template with a grid and an ellipse, along with the text editing features of a text box defined as the PART_ContentHost.

If you follow the instructions near the bottom of this article, Control Authoring Overview, you should be well on your way to having the control you want.

If you try to get access to the default visual tree and modify it -- you'll need to adjust the behavior based on each theme WPF supports now and in the future. (For example, some Vista controls use "Chrome" while XP styles use standard borders or other special FrameworkElements).

WPCoder at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Presentation Foundation (WPF)...
# 2
Hmm, this describes how to replace the look of the textbox. The goal should be a way to add new elements to any FrameworkElement. The TextBox was just an example.

What about adding a "fixed row" to a ListBox? The fixed row doesn't need to be part of the ListBox and so on. With the methode above I'd have to define the complete ControlTemplate of the ListBox first. In addition, this solution is not theme independant.

I thought, by reusing the original visual tree, I would not have to adjust any behavior based on different themes. By using the original "content" of a Control and putting some stuff around it, everything should be fine....

Is this the right way to do that?

I just want to have a control with some additional features/controls.

<Grid>
<TextBox Text="foobar" TextChanged="FooText" FontSize="100"/>
<Image Source="src1.png /><!-- ...ignoring the size and position of the image ... -->
</Grid>

This shall become something like
<l:ImageTextBox ImageSource="src1.png" Text="foobar" TextChanged="FooText" FontSize="100"/>

I don't want to use a UserControl since I'd had to expose every property of Textbox.

|L at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Presentation Foundation (WPF)...
# 3
You're right, it is just the text box, and yes, it is dependent on the theme -- but the article I pointed to describes how to handle the multiple themes that could be installed and have a fallback available.

I added an ellipse to the textbox box representing 'anything'. It could be an image like in your code, or a button, or anything. You can attach event handlers, commands, etc.

Without having exacting knowledge of the visuals of the controls, you can't just 'add to them'. For example a text box doesn't have a grid in it by default. It only has a border, which only supports one child. So, you'd have to remove some visuals, add new ones ... again, relying on internal knowledge of how the control was constructed. Most controls don't have any extra Panels for layout. You can't just add a new header to a list box without changing how it's built.
By deriving from TextBox, creating YourTextBox, and doing a custom control template, you get the best of both. You control the visuals, you get inheritance and all the properties, but you can also add new Commands, etc. to add functionality. The only downside really is being theme aware as far as I'm concerned. Being able to do "control is TextBox" can be very useful. A UserControl wouldn't give you that.

Doing your own UserControl would be a lot of extra work as you pointed out if you want it to really "be" a textbox with properties and such. If you want to be lazy, you can expose the internal textbox as a property -- but that's not a good design -- just functional. Smile

Control containment in a usercontrol (or other control) may introduce focus issues that would need to be handled in code -- controls not getting focus, etc.

WPCoder at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Presentation Foundation (WPF)...
# 4
I thought, one could just put the border (the root child of the control) as child into a panel and make the panel the new root child of the control.
TextBox
- Border
- - Other Stuff

NewTextBox
- Panel
- - My Stuff
- - Border
- - - Other Stuff

Btw: the link is broken (404) and your example did not work because of the missing border in the ControlTemplate. I got a runtime exception mentioning that. Smile

|L at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Presentation Foundation (WPF)...
# 5
I fixed the link -- for some reason it had http:// ... Smile

My example works fine -- I'm not sure what Border you're talking about though? I purposely used a grid rather than a border inside the textbox as I wanted to lay the text editor area out with an ellipse as I mentioned. I could have added another border around the text field - yet that border wouldn't necessarily have encapsulated the ellipse.

This is a great example of where you shouldn't assume what is in a ControlTemplate unless you authored it. Things changes. Microsoft could change the visual templates to be more efficient (for one, I hope they do with textboxes as they use a lot of visual elements when they aren't active!).

Of course, you can go either way you want with your design and implementation. I'm just suggesting the way that I believe Microsoft intended control authoring to work.

You'll need to work with a few methods and overrides the other way:

VisualChildrenCount
GetVisualChild
AddLogicalChild
AddVisualChild
MeasureOverride
ArrangeOverride

There may be a few more that I've forgotten -- but if you search for either of the first two methods, you should be on your way.

WPCoder at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Presentation Foundation (WPF)...
# 6
I guess, changing the visual tree of an instanciated object isn't the right way, to do that. Does someone know how to get the default style with its default control template or at least the ControlTemplate? I'd try to change it then.
For example:

- TextBox default Style
-- ControlTemplate
Root node of the TextBox-Template

->

- MyTextBox Style
-- ControlTemplate
My new Panel
- Root node of the TextBox-Template
- My own stuff

|L at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Presentation Foundation (WPF)...
# 8
Thx so far...
With the method described in that text I am able to get the default style with the default template, but the VisualTree property of the template is a null reference.

//I think, I only have to find out where to initialize or get this data.
I still don't know how to get this data. I can apply this template, so the data must be somewhere in the ControlTemplate....

|L at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Presentation Foundation (WPF)...

Visual Studio Orcas

Site Classified