In this post I wish to introduce a custom control derived from TextBox which add a line number margin to the text area.

WPF TextBox is a control that rarely is customized. Very often the default style is what a classic application needs. Usually the only customizations regard the border and background color. But TextBox is much more than this. For example consider you have an application that shows a long text, letting user change it or search for a particalur word: adding a margin with line numbers will increase a lot the usability of the control.

All margins of a text box control should follow some guidelines:

  1. When you click a margin usually the corrisponding line must be selected.
  2. Users can start a text selection by clicking the margin and move over the text area.
  3. Must be possible to hide a margin.
  4. A margin must be integrated with the textbox letting user change its appearance.

Of course this short list can grow depending on applications requirements.

Here you have a screenshot of how appear TextBoxEx control with Aero theme under vista:

Now a short introduction to the code. I named the control TextBoxEx and I derived it from TextBox. Standard TextBox has some useful functions/properties to access the internal text area like First/LastVisibleLineIndex or GetCharacterIndexFromPoint(). TextBoxEx adds some more properties which are anchored to the internal scroll viewer.Them are called in order to know where text is drawn (i.e. line height, vertical offset etc).

If you restyle a TextBox you must place in the control template a ScrollViewer (or a Decorator element) named exactly 'PART_ContentHost'. This element will be used by internal code of TextBox to render the text. TextBoxEx achives a reference to this element and calculates line height and vertical offset.

This is the provided TextBoxEx style under Aero theme:


<Style x:Key="{x:Type tbe:TextBoxEx}" TargetType="{x:Type tbe:TextBoxEx}"> (....)
<ControlTemplate TargetType="{x:Type tbe:TextBoxEx}">
<theme:ListBoxChrome x:Name="Bd"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
RenderMouseOver="{TemplateBinding IsMouseOver}"
RenderFocused="{TemplateBinding IsKeyboardFocusWithin}"
SnapsToDevicePixels="true">



<Grid>


<Grid.ColumnDefinitions>


<ColumnDefinition Width="Auto"/>


<ColumnDefinition Width="*"/>


</Grid.ColumnDefinitions>



<Border


BorderThickness="0,0,1,0"


BorderBrush="{StaticResource ButtonNormalBorder}"


Visibility="{Binding Path=IsMarginVisible, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource boolToVisibilityConverter}}"


>


<tbe:TextBoxExLineMargin x:Name="lineMargin"


TextAlignment="Right"


Margin="0,0,2,0"


/>


</Border>


<ScrollViewer x:Name="PART_ContentHost"


Grid.Column="1"


SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>


</Grid>


</theme:ListBoxChrome>


</ControlTemplate>


</Setter.Value>


</Setter>


</Style>

This template add a margin element (a class deriving from FrameworkElement) to the left side of text area. A margin is rappresented by a class named TextBoxExMargin which essentially gets a reference to the templated textbox and arranges its size to make enough room for the content.

By now I created only one margin type TextBoxExLineMargin (a TextBoxExMargin) which draw a consecutive  number for each line but one can add other margins as application requirements. In future I hope to have time to code a line modification tracking margin.

This is the code for the OnRender override:

protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)

{

base.OnRender(drawingContext);

double y = TextBox.StartOffset;

double lineOffset = TextBox.LineHeight / 2 - TextBox.FontSize / 2;

for (int i = TextBox.StartLine;

i < TextBox.StartLine + TextBox.VisibleLines; i++)

{

FormattedText textToDraw = new System.Windows.Media.FormattedText(

(i+1).ToString(),

CultureInfo.CurrentCulture,

FlowDirection,

new System.Windows.Media.Typeface(TextBox.FontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), TextBox.FontSize, Foreground);

double x = 0.0;

if (TextBlock.GetTextAlignment(this) == TextAlignment.Right)

{

x = this.ActualWidth - textToDraw.Width;

}

else if (TextBlock.GetTextAlignment(this) == TextAlignment.Center)

{

x = (this.ActualWidth - textToDraw.Width) / 2;

}

drawingContext.DrawText(textToDraw,

new Point(x, y + lineOffset));

y += TextBox.LineHeight;

}

}

Of course I suppose there are many other ways to add a margin to TextBox control but I found this very easy to implement and extend.

Hope can be useful to someone:)

Click to download TextBoxEx.rar (120.24 kb)! (I posted a new article about WPF TextBox Margins here)

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
4 Comments