Trying to create a WPF window that behaves like the VS editor window

227 views Asked by At

I've used the CodeBox project from CodeProject and it works very well except for the fact that I can't disable text wrapping. In a normal TextBox, simply setting the TextWrapping property to NoWrap does the trick, but not with CodeBox (which inherits from TextBox in code-behind). I've tried adding a horizontal scrollbar but that doesn't help. The scrollbar is visible and it changes the size of the drag button to show that it sees that the unwrapped text is wider than the viewing area, but since the text has already been wrapped, dragging it doesn't make any difference.

I've tracked the problem to a line in OnRender:

"formattedText.MaxTextWidth = this.ViewportWidth; // space for scrollbar"

I'm fairly new to WPF and there's much to it that is still mysterious to me, so the solution may be obvious to someone with more experience with it.

I'd appreciate any suggestions. This is the code-behind C#, lengthy, but it has been trimmed down to only enough to show what's going on. The rest (that has been reomved) is just code that does more text-coloring.

public partial class CodeBox : TextBox
{
    bool m_bScrollingEventEnabled;

    SolidColorBrush m_brRed      = new SolidColorBrush (Colors.Red);
    SolidColorBrush m_brOrange   = new SolidColorBrush (Colors.Orange);
    SolidColorBrush m_brBlack    = new SolidColorBrush (Colors.Black);

    public CodeBox ()
    {
        this.TextChanged += new TextChangedEventHandler (txtTest_TextChanged);
        this.Foreground = new SolidColorBrush (Colors.Transparent);
        this.Background = new SolidColorBrush (Colors.Transparent);
        this.TextWrapping = System.Windows.TextWrapping.NoWrap;
        base.TextWrapping = System.Windows.TextWrapping.NoWrap;
        InitializeComponent ();
    }

    public static DependencyProperty BaseForegroundProperty = DependencyProperty.Register ("BaseForeground", typeof (Brush), typeof (CodeBox),
                  new FrameworkPropertyMetadata (new SolidColorBrush (Colors.Black), FrameworkPropertyMetadataOptions.AffectsRender));

    public Brush BaseForeground
    {
        get { return (Brush)GetValue (BaseForegroundProperty); }
        set { SetValue (BaseForegroundProperty, value); }
    }

    public static DependencyProperty BaseBackgroundProperty = DependencyProperty.Register ("BaseBackground", typeof (Brush), typeof (CodeBox),
                  new FrameworkPropertyMetadata (new SolidColorBrush (Colors.Black), FrameworkPropertyMetadataOptions.AffectsRender));

    public Brush BaseBackground
    {
        get { return (Brush)GetValue (BaseBackgroundProperty); }
        set { SetValue (BaseBackgroundProperty, value); }
    }

    void txtTest_TextChanged (object sender, TextChangedEventArgs e)
    {
        this.InvalidateVisual ();
    }

    protected override void OnRender (System.Windows.Media.DrawingContext drawingContext)
    {
        //base.OnRender(drawingContext);
        if (this.Text.Length > 0)
        {
            EnsureScrolling ();
            FormattedText formattedText = new FormattedText (
                this.Text,
                CultureInfo.GetCultureInfo ("en-us"),
                FlowDirection.LeftToRight,
                new Typeface (this.FontFamily.Source),
                this.FontSize,
                BaseForeground);  //Text that matches the textbox's
            double leftMargin = 4.0 + this.BorderThickness.Left;
            double topMargin = 2 + this.BorderThickness.Top;
            ***formattedText.MaxTextWidth = this.ViewportWidth; // space for scrollbar***
            formattedText.MaxTextHeight = Math.Max (this.ActualHeight + this.VerticalOffset, 0); //Adjust for scrolling
            drawingContext.PushClip (new RectangleGeometry (new Rect (0, 0, this.ActualWidth, this.ActualHeight)));//restrict text to textbox

            int iStartVisibleLine = GetFirstVisibleLineIndex ();
            int iEndVisibleLine = GetLastVisibleLineIndex ();
            for (int iIdx = iStartVisibleLine; iIdx <= iEndVisibleLine - 1; ++iIdx)
            {
                // Text coloring
                int iOffset = GetCharacterIndexFromLineIndex (iIdx);
                int iOffsetNext = GetCharacterIndexFromLineIndex (iIdx + 1);
                string strLine = Text.Substring (iOffset, iOffsetNext - iOffset);
            }

            drawingContext.DrawText (formattedText, new Point (leftMargin, topMargin - this.VerticalOffset));
        }
    }

    private void EnsureScrolling ()
    {
        if (!m_bScrollingEventEnabled)
        {
            DependencyObject dp = VisualTreeHelper.GetChild (this, 0);
            ScrollViewer sv = VisualTreeHelper.GetChild (dp, 0) as ScrollViewer;
            sv.ScrollChanged += new ScrollChangedEventHandler (ScrollChanged);
            m_bScrollingEventEnabled = true;
        }
    }

    private void ScrollChanged (object sender, ScrollChangedEventArgs e)
    {
        this.InvalidateVisual ();
    }
}

The xaml from the project:

<TextBox x:Class="CodeBoxControl.CodeBox"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:c="clr-namespace:CodeBoxControl">
<TextBox.Template>
    <ControlTemplate TargetType="c:CodeBox" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Border BorderThickness="{TemplateBinding Border.BorderThickness}" BorderBrush="{TemplateBinding Border.BorderBrush}"
            Background="{TemplateBinding Panel.Background}" Name="Bd" SnapsToDevicePixels="True">
            <ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"/>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="UIElement.IsEnabled">
                <Setter Property="Panel.Background" TargetName="Bd">
                    <Setter.Value>
                        <DynamicResource ResourceKey="{x:Static SystemColors.ControlBrushKey}" />
                    </Setter.Value>
                </Setter>
                <Setter Property="TextElement.Foreground">
                    <Setter.Value>
                        <DynamicResource ResourceKey="{x:Static SystemColors.GrayTextBrushKey}" />
                    </Setter.Value>
                </Setter>
                <Trigger.Value>
                    <s:Boolean>False</s:Boolean>
                </Trigger.Value>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
</TextBox.Template>

Xaml from the parent window that uses the CodeBox code:

<c:CodeBox Name="DisassemblyOutput"
                 FontFamily="Courier New"
                 FontSize="20"
                 VerticalScrollBarVisibility="Auto"
                 HorizontalScrollBarVisibility="Auto"
                 BaseForeground="Black"
                 Margin="4,4,4,4"
                 Background="#CEE9C9"
                 Foreground="Magenta"
                 TextWrapping="NoWrap"
                 AutoWordSelection="False"/>

This is a sample of the code that loads the text into the CodeBox window:

private void OnLoadDASM (object sender, RoutedEventArgs e)
{
    DisassemblyOutput.FontSize = 12;
    DisassemblyOutput.Clear ();
    DisassemblyOutput.m_eWindowData = CodeBox.EWindowData.EDasm;

    DisassemblyOutput.Background = new SolidColorBrush (Colors.Transparent);//Color.FromRgb (0xCE, 0xE9, 0xC9));
    DisassemblyOutput.Foreground = new SolidColorBrush (Colors.Transparent);
    DisassemblyOutput.BaseBackground = new SolidColorBrush (Color.FromRgb (0xCE, 0xE9, 0xC9));
    DisassemblyOutput.BaseForeground = new SolidColorBrush (Colors.Transparent);

    DisassemblyOutput.TextWrapping = TextWrapping.NoWrap;

    DisassemblyOutput.Text += "Loop_02_0A0F  0A0F: SIO   F3 10 28                 5475 Keyboard  Set Error Indicator  Restore Data Key              " + Environment.NewLine;
    DisassemblyOutput.Text += "                                                   Disable Interrupt                                                 " + Environment.NewLine;
    DisassemblyOutput.Text += "              0A12: SNS   70 12 FF,1       0x0B0C  5475 Keyboard  2 sense bytes                                      " + Environment.NewLine;
}

This is what I want: https://i.stack.imgur.com/M4ts0.png and what's showing up: https://i.stack.imgur.com/gdBco.png

I've also noticed that when the text wraps and I use the vertical scrollbar, the text in the top part of the pane disappears, and the more I scroll down, the more of it disappears: 1

1

There are 1 answers

0
Gregor J On BEST ANSWER

The fix is to set MaxTextWidth to the width of the line instead of the ViewportWidth property:

iStartVisibleLine = GetFirstVisibleLineIndex ();
iEndVisibleLine   = GetLastVisibleLineIndex ();
iOffset           = GetCharacterIndexFromLineIndex (0);
iOffsetNext       = GetCharacterIndexFromLineIndex (1);
strLine           = Text.Substring (iOffset, iOffsetNext - iOffset);
geomFirstLine     = formattedText.BuildHighlightGeometry (new Point (leftMargin, topMargin - this.VerticalOffset), iOffset, strLine.Length);
rcBounds          = geomFirstLine.GetRenderBounds (null);
formattedText.MaxTextWidth = rcBounds.Width; // Space for scrollbar