尝试创建一个 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
我使用了 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