2

I have a TextBox in WPF like so:

<TextBox Height="48" />

Nothing special, looks like this:

enter image description here

Now what I need to accomplish is to have clear and nice long line markers:

enter image description here

So basically I want to draw a line marker at position 50. I will use a fixed-width font to make this easier for myself so I can calculate the position easily.

Now the thing is, I don't want to restrict typing over it, but I want it to give a visual glue when you reached the 50 character limit.

To make matters harder, I need this limit to be higher for the following lines/rows (72 characters) like so:

enter image description here

The line markers sharing the same position (rows 2 - n) could be a single line rather than multiple.

I'm also open to other suggestions, but as long as it offers a clean way to tell the user he is about to exceed the limit, I'm cool with it.

Note: I don't want any text saying that "you have x characters left" or something. Space is limited and I want it to be visual.

Update: I would really appreciate if it was possible to add a tooltip so that when hovering a marker it stated its purpose to the user.

Tower
  • 98,741
  • 129
  • 357
  • 507
  • You could easily do it by overriding the draw event of a WinForms TextBox. I know this is not a solution, but it's a possible alternative... oh, and you can use WinForms controls in WPF, I believe.. – caesay May 12 '12 at 16:32

1 Answers1

3

Since you want to visually decorate the Textbox, the first thing that pops to my mind is the aptly named Adnorner.

I have written a quick demo showing how to adorn a Textbox. The line drawing part is easy, the tricky part will be to figure out where the lines should be drawn. I have hard coded the line positions in my example. I supposed you would have to do some sort of text measuring to find out how tall your texts are(for the line height) and how long is 50 chars worth(to offset your lines by).

enter image description here

Here is the xaml:

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid  HorizontalAlignment="Stretch"  VerticalAlignment="Stretch" >
        <AdornerDecorator>
            <TextBox TextWrapping="Wrap"  AcceptsReturn="True" x:Name="myTextBox" Width="200px" Height="200px">hello</TextBox>
        </AdornerDecorator>
    </Grid>
</Window>

And the code behind

using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;

namespace WpfApplication4
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            AdornerLayer myAdornerLayer = AdornerLayer.GetAdornerLayer(myTextBox);
            myAdornerLayer.Add(new LineAdorner(myTextBox));
        }

        // Adorners must subclass the abstract base class Adorner.

        #region Nested type: LineAdorner

        public class LineAdorner : Adorner
        {
            // Be sure to call the base class constructor.
            public LineAdorner(UIElement adornedElement)
                : base(adornedElement)
            {
            }

            // A common way to implement an adorner's rendering behavior is to override the OnRender
            // method, which is called by the layout system as part of a rendering pass.
            protected override void OnRender(DrawingContext drawingContext)
            {
                var adornedElementRect = new Rect(AdornedElement.DesiredSize);

                var renderPen = new Pen(new SolidColorBrush(Colors.Red), 1.5);

                // Draw lines.
                drawingContext.DrawLine(renderPen,
                                        new Point(adornedElementRect.TopLeft.X + 75, adornedElementRect.TopLeft.Y),
                                        new Point(adornedElementRect.TopLeft.X + 75, adornedElementRect.TopLeft.Y + 20));
                drawingContext.DrawLine(renderPen,
                                        new Point(adornedElementRect.TopLeft.X + 120, adornedElementRect.TopLeft.Y + 20),
                                        new Point(adornedElementRect.TopLeft.X + 120, adornedElementRect.TopLeft.Y + 40));
                drawingContext.DrawLine(renderPen,
                                        new Point(adornedElementRect.TopLeft.X + 120, adornedElementRect.TopLeft.Y + 40),
                                        new Point(adornedElementRect.TopLeft.X + 120, adornedElementRect.TopLeft.Y + 60));
            }
        }

        #endregion
    }
}
Bojin Li
  • 5,769
  • 2
  • 24
  • 37
  • If I keep adding lines, how would I get it to render the vertical bars per each line? – Tower May 12 '12 at 16:44
  • Implement the OnRender override accordingly. You can declare a data structure which holds the information regarding how to draw the lines and instantiate it as a member variable to your Adorner class . Then when the text change on your textbox, update this data structure. OnRender will draw according to the contents of the data structure member. – Bojin Li May 12 '12 at 16:48
  • do you think it's somehow possible to display a tooltip when hovering the line marks to tell the user what they are about? – Tower May 12 '12 at 18:09
  • 1
    I am not sure how you can do this with a simple on render override. There might be a simple way, but I don't know of it. If you want more complex behavior than just a visual overlay, you probably need to implement your Adorner differently. Instead of overriding OnRender, you will need to supply your own visuals for the Adorner and override GetVisualChild as documented here: http://stackoverflow.com/questions/6941538/tooltip-on-adorner-doesnt-show-up . For your visual, you will probably have a canvas with line shapes, and each line shape can have a tooltip attached. – Bojin Li May 12 '12 at 18:36