尝试创建一个 WPF window 其行为类似于 VS 编辑器 window

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

我使用了 CodeProject 中的 CodeBox 项目,它工作得很好,除了我无法禁用文本换行。在普通的 TextBox 中,只需将 TextWrapping 属性 设置为 NoWrap 就可以解决问题,但对于 CodeBox(它在代码隐藏中继承自 TextBox)则不行。我试过添加水平滚动条,但这无济于事。滚动条是可见的,它会改变拖动按钮的大小,以显示它看到展开的文本比查看区域宽,但由于文本已经被包裹,拖动它没有任何区别。

我已将问题追踪到 OnRender 中的一行:

"formattedText.MaxTextWidth = this.ViewportWidth; // space 用于滚动条"

我对 WPF 还很陌生,它的很多内容对我来说仍然是个谜,所以对于有更多经验的人来说,解决方案可能是显而易见的。

如果有任何建议,我将不胜感激。这是 C# 的代码隐藏,很长,但已被精简到仅足以显示正在发生的事情。其余的(已被修改)只是进行更多文本着色的代码。

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 ();
    }
}

来自项目的xaml:

<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 来自使用 CodeBox 代码的父 window:

<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"/>

这是将文本加载到 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;
}

这就是我想要的: https://i.stack.imgur.com/M4ts0.png 以及出现的内容: https://i.stack.imgur.com/gdBco.png

我还注意到,当文本换行并且我使用垂直滚动条时,窗格顶部的文本消失了,我越向下滚动,它消失的越多: 1

解决方法是将 MaxTextWidth 设置为线宽而不是 ViewportWidth 属性:

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