Click here to Skip to main content
Click here to Skip to main content

WPF Control Composition (Part 1 of 2)

, 16 Mar 2012
Rate this:
Please Sign up or sign in to vote.
Composing controls in a user control improves the consistency of a resulting application but does not add much extra work or complexity.

Introduction

The Windows Presentation Foundation (WPF) offers at least two ways of implementing controls. There are User Controls and Custom Controls. User Controls are composed of one or more standard controls (and cannot be styled) while Custom Controls either extend existing controls or start from scratch (and can be styled). This series of two articles shows a particular simple but effective technique for implementing Custom Controls and extends one of them in the second article into a theme-able (Custom Control).

The article assumes that you have a working knowledge of WPF. In particular, knowledge of Dependency Properties and Commanding are required.

I sometimes observe myself doing repetitive things when designing input forms: put a label here, put a textbox there, put another label and another textbox ... you get the picture. And the problem sometimes gets even worse when I find that a need a Margin of 3 on all labels and a margin of 0 on all textboxes...

This article shows a technique that lets us declare a new control that is, for example, composed of a label and a textbox, with the addition of a new TextBoxLabel property which can be used in XAML to set the text of the label but let the control decide where to put the label in relation to the textbox.

This way, the number of elements in the XAML is reduced and an input form containing a few controls is easy to maintain because the content is likely to look more consistent. Let's have a look at a:

  • Textbox (with Watermark) and Label

to understand how this works and extend this concept later with a

  • Hyperlink with custom tooltip and context-menu
  • Combobox and Label, and a
  • Labeled Combobox and Textbox

This concept is later extended with a second part that shows how one of the controls discussed here can be turned into a look-less custom control.

Compiling the code

StyleCop

I use StyleCop in my projects to make the code readable in a uniform way. So, if you should get an error when compiling the project, you can either download and install StyleCop, or edit/remove the corresponding entries in each .csproj file:

<Import Project="$(ProgramFiles)\MSBuild\StyleCop\v4.7\StyleCop.Targets" />

Textbox (with Watermark) and Label Composition

The Watermark textbox control behaves like a normal textbox with the addition of being labeled and displaying a watermark in the text portion. The watermark disappears as soon as the user starts typing.

332539/TextboxWithWatermark.png

This control is declared in the TestWindow.xaml like so:

<textbox:TextBoxWithWatermark 
    Text="" Watermark="First and second given name" LabelTextBox="Name:" /> 

The Text property can be used to read and write the text entered in the textbox. You can use this just like the Text property on the standard textbox control.

The Watermark property can be used to set the text that is shown in the textbox when the user has not entered anything, yet.

The LabelTextBox property sets the text of the label that is shown above the textbox.

Normally, none of the above properties are this easily available, because the control composition leads to hiding internal elements. The solution is in the XAML of the TextBoxWithWatermark user control. This is where internal properties (content property of label) and external properties (LabelTextBox) are bound such that both appear to be one.

<Label Content="{Binding Path=LabelTextBox, RelativeSource={RelativeSource FindAncestor, AncestorType=local:TextBoxWithWatermark, AncestorLevel=1}}"
        HorizontalAlignment="Left" VerticalAlignment="Bottom"
        Grid.Column="0" Grid.Row="0"/>

The above XAML code is contained in the TextBoxWithWatermark.xaml file. The binding statement in the Content of the label control binds to the LabelTextBox Dependenncy Property of the TextBoxWithWatermark control. This is backed by the following standard dependency property code pattern:

private static readonly DependencyProperty LabelTextBoxProperty =
  DependencyProperty.Register("LabelTextBox", typeof(string), typeof(TextBoxWithWatermark));
public string LabelTextBox
{
   get { return (string)GetValue(TextBoxWithWatermark.LabelTextBoxProperty); }
   set { SetValue(TextBoxWithWatermark.LabelTextBoxProperty, value); }
}

So, in a nutshell: We declare a TextBoxWithWatermark control in the demo window and bind to its dependency properties. And the DPs of the TextBoxWithWatermark control bind to each property of the corresponding control in the composition.

Voila. Other than that, nothing else is needed to get an XAML value through a user control (TextBoxWithWatermark) to the (Label) original control.

I adopted this particular way of implementing the watermark from elsewhere: Create Watermark TextBox in WPF Application. The original approach proposed to use a textbox with a transparent background and have the textbox shine through that transparent background. This caused a problem in ExpressionDark since the background is black and WPF uses a black cursor on a transparent background. I fixed this problem by:

  • removing the transparent background on the textbox
  • putting the textblock on top of the textbox (declare the textblock after the textbox) and
  • setting the textblock to IsHitTestVisible=false (to route all inputs to the textbox).

Hyperlink with custom tooltip and Context-Menu

The Hyperlink control behaves like a normal Hyperlink except that this one is optimized for web hyperlinks. It shows the URL in its tooltip and the user can use a context menu on it to:

  • Copy & Paste the URL
  • To navigate to the URL (instead of clicking the underlined portion)

332539/WebHyperlink.png

This control is declared in the TestWindow.xaml like so:

<hyperlink:WebHyperlink Text="Exposing inner Control properties for binding in WPF"
        NavigateUri="http://stackoverflow.com/questions/4169090/exposing-inner-control-properties-for-binding-in-wpf"
        Grid.Column="1" Grid.Row="1" Margin="6, 3" VerticalAlignment="Center"/>

We can check the XAML portion of the WebHyperlink control to find that it is quite similar to the TextBoxWithWatermark control.

<TextBlock Text="{Binding Path=Text, RelativeSource={RelativeSource FindAncestor, AncestorType=hyperlink:WebHyperlink, AncestorLevel=1}}" />

We have a Text property that is bound to the Text property of a textblock, which is placed inside the content portion of a standard hyperlink control. The same appears to be true for the NavigateUri property on the Hyperlink control.

The Hyperlink control also contains a RequestNavigate event which is triggered when a user clicks on the hyperlink. This executes this code:

private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
    Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
}

A similar code is executed if the NavigateToUri command is executed. Note that the target of the navigation is supplied as command parameter:

private static void Hyperlink_CommandNavigateTo(object sender, ExecutedRoutedEventArgs e)
{
    if (sender == null || e == null) return;

    e.Handled = true;

    WebHyperlink whLink = sender as WebHyperlink;

    if (whLink == null) return;

    Process.Start(new ProcessStartInfo(whLink.NavigateUri.AbsoluteUri));
}

The context menu binds via commanding (plus parameter) to the Hyperlink control. You can take this as an example of how commands can be more flexible by using command parameters. Interesting is also the CommandTarget bit.

<ContextMenu>
  <MenuItem Header="Copy Url to Clipboard"
            Command="{x:Static hyperlink:WebHyperlink.CopyUri}"
            CommandParameter="{Binding ElementName=PART_Hyperlink, Path=NavigateUri}"
            CommandTarget="{Binding PlacementTarget,
            RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
  <MenuItem Header="Open Target in Browser"
            Command="{x:Static hyperlink:WebHyperlink.NavigateToUri}"
            CommandTarget="{Binding PlacementTarget,
            RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>

The CommandTarget bit is necessary because the command would otherwise not be executed. The command would not be executed because the context menu has the keyboard focus and is not in the same visual and logical tree as the hyperlink is.

References

Whether the additional functions are useful or not is another matter. But just imagine the work we would have if we had to declare this each time we want to use a hyperlink.

Combobox and Label Composition

The ComboBoxWithLabel control behaves like a normal ComboBox but it comes with the addition of a label placed on top of it.

332539/ComboboxWithLabel.png

Composing a control with a combobox (or any other control with an ItemsControl at its core) means a little bit more work because there are a few dependency properties that are useful. Nevertheless, the control exhibits the same XAML binding mechanisms as we have seen before:

SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, " + 
          "AncestorType=local:ComboBoxWithLabel, AncestorLevel=1}, Path=SelectedItem}"
private static readonly DependencyProperty SelectedItemProperty = 
  ComboBox.SelectedItemProperty.AddOwner(typeof(ComboBoxWithLabel));

public object SelectedValue
{
  get { return (object)GetValue(ComboBoxWithLabel.SelectedValueProperty); }
  set { SetValue(ComboBoxWithLabel.SelectedValueProperty, value); }
}

The data displayed in the comboboxes is loaded from an ObjectDataProviderdefined in the resources section of the TestWindow class. The ObjectDataProvider itself receives its data from a static method declared in the collections class. This static method generates a dictionary based on an enumeration.

Combobox and Textbox with Label Composition

332539/ComboboxWithTextMarkAndLabel.png

These samples are really just compositions of the combobox with label and the textbox with watermark and label samples discussed above. so, assuming that you have read and understood the above, there is not much new here, except that we have yet another composition of controls that might be useful being placed as one solid composition. You can use this to review the binding pattern one more time. Go back to the samples discussed above if you find this to be too complex to understand.

Conclusions

Simple control composition is an affective method for re-using UI code multiple times. It ensures that similar controls look and behave similar even if they occur a few hundred times in a large application. Adding special behaviors or properties to existing controls has never been easier.

Please take a moment time to rate this article and give me your feedback.

Further Developments

The controls in this article cannot be themed in the sense that they are look less. Although, they can be themed if they are made up of theme-able controls. The Combobox with Label control is, for example, theme-able as long as each theme contains a ControlTemplate for a Combobox control and a ControlTemplate for a Label control. But the control is not look-less because the Label will always appear on top of the Combobox.

Worse still, the watermark of the textbox control discussed above may even be invisible because its color cannot be defined in a style or ControlTemplate.

Part two of this article series shows how simple controls can be themed by developing the watermark textbox control into a skin-able control and adding three demo themes to the demo application.

History

  • 20-February 2012: Initial creation.
  • 16-March 2012: Small patch in source code (Binding to WebHyperlink Uri and Text dependency properties works now)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dirkster99

Germany Germany
The Windows Presentation Foundation (WPF) and C# are one of my favorite past time hobbies since I started developing Edi:
 
https://edi.codeplex.com/
 
and a few other sub-projects on Codeplex. I am normally an algorithms and structure type person but WPF has such interesting sides that I cannot help myself but get into this and MVVM.

Comments and Discussions

 
QuestionNice idea, but I have a few suggestions PinmvpSacha Barber19-Feb-12 21:59 
AnswerRe: Nice idea, but I have a few suggestions PinmemberDirkster9921-Feb-12 12:31 
Hey Sacha,
 
thanks for your comment. I was actually just aiming at documenting the pattern not really trying to supply a ready solution. But you are right, I should try to provide something more useful...
 
I have extended ComboBoxWithLabel.xaml.cs properties such that the ComboBox exposes DPs from the Combobox, Selector, and ItemsControl derived classes and I was looking at the class Control DPs and realized this pattern cannot be taken further this way, because:
 
- existing UserControl DPs (eg.: Background) will clash with the DPs of the ComboBox or
- DPs from different sub-controls (eg.: ComboBox.Background clashes with TextBox.Background) clash with each other
 
See ComboBoxWithLabel.xaml.cs and ComboBoxWithLabel.xaml in SimpleControls Version1_Advanced.zip attached to this article to verify what I am looking at.
 
So, my intention was to change the name of each DP to something else like CmbBackgound for the ComboBox.Background DP). But to do that I would have to go all the way and implement new wrapper DPs including OnPropertyChanged Callback functions, right? Is that not a little off the agenda (which was to provide a simple solution)?
 
Can you thing of a simple pattern to expose the DPs with a change in property name as I outlined above?
 
Cheers Dirk

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140821.2 | Last Updated 16 Mar 2012
Article Copyright 2012 by Dirkster99
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid