Question

I'm having problems customizing the ControlTemplate for a TextBox. The idea is to automatically print text neatly on lined paper with as little user interaction as possible, while remaining as flexible as possible with regard to text length, font size, etc.

To that end one setting is text height relative to a printed line (how close to/far above the line it appears on paper). Since changing TextBox LineHeight adds space below text and not above it, I've been using Padding on the top of the textbox to translate text downward.

This causes a problem whenever the specified LineHeight is greater than the remaining visible space inside the textbox. It is possible to inadvertently scroll down to the bottom of the line, causing the text to scroll up into the padded area and disappear.

To fix this, I need to prevent MouseWheel/PgUp/PgDwn scrolling inside the textbox or figure out how render text along the bottom edge of a line instead of the top.

Using Snoop, I found the TextBox control has a ScrollContentPresenter whose CanContentScroll property is determined by its ParentTemplate from a ScrollViewer. Un-checking CanContentScroll in Snoop while the application's running disables scrolling and prevents this behavior just as I want, but for some reason specifying CanContentScroll = False in the template does work. It remains True.

XAML:

<TextBox.Style>
    <Style TargetType="TextBoxBase">
        . . . 
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TextBoxBase}">
                    <theme:ListBoxChrome x:Name="Bd" . . . >
                        <ScrollViewer x:Name="PART_ContentHost"
                                      CanContentScroll="False"/>
                    </theme:ListBoxChrome>
                    . . . 
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</TextBox.Style>

The style is lifted directly from Microsoft's WPFThemes/Aero.NormalColor.xaml with only one change to the controltemplate to disable scrolling. Changes to other (omitted) setters such as background color did work. I specified the style directly in the TextBox since it won't be used anywhere else and assuming that local styles precede implicit ones, but I'm guessing this might not be the right place to do this.

Can anyone point out where I'm going wrong with this, or confirm whether it's possible to change where text is rendered on a line?

Thank you

EDIT: Here's a better description of what this TextBox is actually doing

Suppose you have a paper form such as a loan or permit application which has a several questions, each with 3 pre-printed lines on which to write your answer. The TextBox:

  • Is sized/positioned to cover the entire answer area
  • Is set to contain 3 lines
  • Gives the user font size, alignment, typeface options
  • Automatically adjusts LineHeight to space lines evenly and sets Padding to position text just above the printed line
  • Does not allow overflow, even if text input is longer than expected. The # of lines increases in multiples (doubles, then triples, and so on) and font size is adjusted to try to print everything neatly in the provided space (no lines of text crossed out by printed lines, for example)

In practice, this works very well and results in tidy looking forms, as if someone did it by hand with an old Selectric. The only problem is this accidental scrolling issue happens once in awhile.

Was it helpful?

Solution

The meaning of my question changed after Anatoliy reminded me that CanContentScroll does not enable or disable scrolling of content despite its name, but switches between scrolling by pixel and scrolling by item. However my intent was to stop scrolling behavior within a TextBox altogether, and I have solved that problem now and also improved scrolling behavior in the ListBox hosting them by eliminating the nested ScrollViewers:

First, I found this blog post which explains why ScrollViewer always handles MouseWheel. It is possible to subclass ScrollViewer to prevent this, which I was going to do until I wondered why I couldn't just change the content host element in the template to something without scrolling, since I don't need it at all. ContentPresenter didn't work, but the Important Note Box on this MSDN page mentioned that TextBox only works with ScrollViewer or AdornerDecorator.

Switching to AdornerDecorator worked perfectly:

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type TextBoxBase}">
            . . .
            <AdornerDecorator x:Name="PART_ContentHost"
                              ClipToBounds="True"
                              Margin="{Binding GetTextOffset}"
                              />
        </ControlTemplate>
    </Setter.Value>
</Setter>

I was able to bind the margin to my existing text offset property to position text as before, and ClipToBounds confines the content to the visible area. The other major improvement is that scrolling through a collection of these TextBoxes in a ListBox now works as expected since the textboxes aren't handing MouseWheel anymore. Nested ScrollViewers are unpleasant.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top