WPF自定义ScrollViewer模板致TextBox无法滚轮滚动及光标跟随
WPF自定义ScrollViewer模板导致多行TextBox滚动异常的修复方案
问题描述
自定义ScrollViewer模板简化滚动条样式后,开启AcceptReturn的多行TextBox出现以下异常:
- 无法通过鼠标滚轮滚动内容
- 输入新行时光标无法自动滚动到可视区域
移除模板后功能恢复正常,相关代码如下:
自定义ScrollViewer模板代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Color x:Key="ScrollBarFgOnColor">#A000</Color> <Color x:Key="ScrollBarFgColor">#7000</Color> <Color x:Key="ScrollBarBgColor">#01ffffff</Color> <Color x:Key="ScrollBarFgOffColor">#3000</Color> <Color x:Key="ScrollBarBgOffColor">#2fff</Color> <sys:Double x:Key="ScrollBarBarWidth">7</sys:Double> <sys:Double x:Key="ScrollBarBtnWidth">0</sys:Double> <sys:Double x:Key="ScrollBarMargin">1</sys:Double> <sys:Double x:Key="ScrollBarCornerRad">0</sys:Double> <!-- ####################################################### --> <Thickness x:Key="ScrollBarBtnMarginT" Top="{StaticResource ScrollBarMargin}" Left="{StaticResource ScrollBarMargin}" Right="{StaticResource ScrollBarMargin}" Bottom="{StaticResource ScrollBarMargin}"/> <Thickness x:Key="ScrollBarBarMargin" Top="{StaticResource ScrollBarMargin}" Left="{StaticResource ScrollBarMargin}" Right="{StaticResource ScrollBarMargin}" Bottom="{StaticResource ScrollBarMargin}"/> <CornerRadius x:Key="ScrollBarThumbCRad" TopLeft="{StaticResource ScrollBarCornerRad}" TopRight="{StaticResource ScrollBarCornerRad}" BottomRight="{StaticResource ScrollBarCornerRad}" BottomLeft="{StaticResource ScrollBarCornerRad}"/> <!--ScrollViewer Start #####################################################################--> <SolidColorBrush x:Key="ScrollBarForegroundBrush" Color="{StaticResource ScrollBarFgColor}" /> <SolidColorBrush x:Key="ScrollBarForegroundBrushPressed" Color="{StaticResource ScrollBarFgOnColor}" /> <SolidColorBrush x:Key="ScrollBarBackgroundBrush" Color="{StaticResource ScrollBarBgColor}" /> <SolidColorBrush x:Key="ScrollBarDisabledForegroundBrush" Color="{StaticResource ScrollBarFgOffColor}" /> <SolidColorBrush x:Key="ScrollBarDisabledBackgroundBrush" Color="{StaticResource ScrollBarBgOffColor}" /> <!-- SrollViewer ScrollBar Repeat Buttons (at each end) --> <Style x:Key="ScrollBarLineButton" TargetType="{x:Type RepeatButton}"> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="OverridesDefaultStyle" Value="true"/> <!--<Setter Property="Focusable" Value="false"/>--> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type RepeatButton}"> <Border x:Name="Border" Margin="0" CornerRadius="0" Background="Transparent" BorderBrush="{x:Null}" BorderThickness="0"> <Path HorizontalAlignment="Center" VerticalAlignment="Center" Fill="{StaticResource ScrollBarForegroundBrush}" Data="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" Margin="{StaticResource ScrollBarBtnMarginT}" Stretch="Fill" /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsPressed" Value="true"> <Setter TargetName="Border" Property="Background" Value="{StaticResource ScrollBarForegroundBrushPressed}" /> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Foreground" Value="{StaticResource ScrollBarDisabledForegroundBrush}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- SrollViewer ScrollBar Repeat Buttons (The part in the middle, not the thumb the long area between the buttons ) --> <Style x:Key="ScrollBarPageButton" TargetType="{x:Type RepeatButton}"> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="OverridesDefaultStyle" Value="true"/> <!--<Setter Property="IsTabStop" Value="false"/> <Setter Property="Focusable" Value="false"/>--> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type RepeatButton}"> <Border Background="Transparent" /> <!--BeforAndAfterBG--> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- ScrollViewer ScrollBar Thumb, that part that can be dragged up/down or left/right Buttons --> <Style x:Key="ScrollBarThumb" TargetType="{x:Type Thumb}"> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="OverridesDefaultStyle" Value="true"/> <!--<Setter Property="IsTabStop" Value="false"/> <Setter Property="Focusable" Value="false"/>--> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Thumb}"> <Border x:Name="Border" CornerRadius="{StaticResource ScrollBarThumbCRad}" Background="{StaticResource ScrollBarForegroundBrush}" BorderBrush="{x:Null}" BorderThickness="0" Margin="{StaticResource ScrollBarBarMargin}" /> <ControlTemplate.Triggers> <Trigger Property="IsDragging" Value="true"> <Setter TargetName="Border" Property="Background" Value="{StaticResource ScrollBarForegroundBrushPressed}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <ControlTemplate x:Key="VerticalScrollBar" TargetType="{x:Type ScrollBar}"> <Grid> <Grid.RowDefinitions> <RowDefinition MaxHeight="{StaticResource ScrollBarBtnWidth}"/> <RowDefinition Height="0.00001*"/> <RowDefinition MaxHeight="{StaticResource ScrollBarBtnWidth}"/> </Grid.RowDefinitions> <Border Grid.RowSpan="3" Background="{StaticResource ScrollBarBackgroundBrush}" /> <RepeatButton Grid.Row="0" Style="{StaticResource ScrollBarLineButton}" Height="{StaticResource ScrollBarBtnWidth}" Command="ScrollBar.LineUpCommand" Content="M 0 4 L 8 4 L 4 0 Z" /> <Track x:Name="PART_Track" Grid.Row="1" IsDirectionReversed="true"> <Track.DecreaseRepeatButton> <RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageUpCommand" /> </Track.DecreaseRepeatButton> <Track.Thumb> <Thumb Style="{StaticResource ScrollBarThumb}" Margin="0" Background="{StaticResource ScrollBarForegroundBrush}" BorderBrush="{x:Null}" /> </Track.Thumb> <Track.IncreaseRepeatButton> <RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageDownCommand" /> </Track.IncreaseRepeatButton> </Track> <RepeatButton Grid.Row="3" Style="{StaticResource ScrollBarLineButton}" Height="{StaticResource ScrollBarBtnWidth}" Command="ScrollBar.LineDownCommand" Content="M 0 0 L 4 4 L 8 0 Z"/> </Grid> </ControlTemplate> <!-- HorizontalScrollBar Template using the previously created Templates --> <ControlTemplate x:Key="HorizontalScrollBar" TargetType="{x:Type ScrollBar}"> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition MaxWidth="{StaticResource ScrollBarBtnWidth}"/> <ColumnDefinition Width="0.00001*"/> <ColumnDefinition MaxWidth="{StaticResource ScrollBarBtnWidth}"/> </Grid.ColumnDefinitions> <Border Grid.ColumnSpan="3" Background="{StaticResource ScrollBarBackgroundBrush}" /> <RepeatButton Grid.Column="0" Style="{StaticResource ScrollBarLineButton}" Width="{StaticResource ScrollBarBtnWidth}" Command="ScrollBar.LineLeftCommand" Content="M 4 0 L 4 8 L 0 4 Z" /> <Track x:Name="PART_Track" Grid.Column="1" IsDirectionReversed="False"> <Track.DecreaseRepeatButton> <RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageLeftCommand" /> </Track.DecreaseRepeatButton> <Track.Thumb> <Thumb Style="{StaticResource ScrollBarThumb}" Margin="-4,5" Background="{StaticResource ScrollBarForegroundBrush}" BorderBrush="{x:Null}" /> </Track.Thumb> <Track.IncreaseRepeatButton> <RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageRightCommand" /> </Track.IncreaseRepeatButton> </Track> <RepeatButton Grid.Column="3" Style="{StaticResource ScrollBarLineButton}" Width="{StaticResource ScrollBarBtnWidth}" Command="ScrollBar.LineRightCommand" Content="M 0 0 L 4 4 L 0 8 Z"/> </Grid> </ControlTemplate> <!-- Style for overall ScrollBar --> <Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}"> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="OverridesDefaultStyle" Value="true"/> <Style.Triggers> <Trigger Property="Orientation" Value="Horizontal"> <Setter Property="Width" Value="Auto"/> <Setter Property="Height" Value="{StaticResource ScrollBarBarWidth}" /> <Setter Property="Template" Value="{StaticResource HorizontalScrollBar}" /> </Trigger> <Trigger Property="Orientation" Value="Vertical"> <Setter Property="Width" Value="{StaticResource ScrollBarBarWidth}"/> <Setter Property="Height" Value="Auto" /> <Setter Property="Template" Value="{StaticResource VerticalScrollBar}" /> </Trigger> </Style.Triggers> </Style> <!-- Style for overall ScrollViewer --> <Style TargetType="{x:Type ScrollViewer}"> <Setter Property="OverridesDefaultStyle" Value="True"/> <Setter Property="Background" Value="{StaticResource ScrollBarBackgroundBrush}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ScrollViewer}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*"/> <ColumnDefinition Width="auto"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="1*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ScrollContentPresenter/> <ScrollBar x:Name="PART_VerticalScrollBar" Value="{TemplateBinding VerticalOffset}" Maximum="{TemplateBinding ScrollableHeight}" ViewportSize="{TemplateBinding ViewportHeight}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Grid.Column="1" Grid.Row="0"/> <ScrollBar x:Name="PART_HorizontalScrollBar" Orientation="Horizontal" Grid.Row="1" Grid.Column="0" Value="{TemplateBinding HorizontalOffset}" Maximum="{TemplateBinding ScrollableWidth}" ViewportSize="{TemplateBinding ViewportWidth}" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/> <Rectangle x:Name="Corner" Grid.Column="1" Fill="{StaticResource ScrollBarBackgroundBrush}" Grid.Row="1"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> <!--ScrollViewer End #######################################################################--> </ResourceDictionary>
TextBox代码
<TextBox x:Name="ContentTxb" Width="200" Height="200" IsReadOnly="True" Cursor="Arrow" TextChanged="ContentTxb_TextChanged" BorderThickness="0" Margin="10 15 10 10" Padding="0" ContextMenu="{Binding ElementName=Win, Path=ContextMenu}" Background="{StaticResource BgBru}" Foreground="{StaticResource FgBru}" FontFamily="DejaVuSansMono Nerd Font" FontSize="12" AcceptsReturn="True" AcceptsTab="True" AutoWordSelection="False" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" MinHeight="30" MinWidth="30" UndoLimit="1000" UseLayoutRounding="True" SelectionOpacity=".5"> </TextBox>
问题原因
自定义的ScrollViewer模板中,ScrollContentPresenter缺少关键绑定:
- 未绑定
ScrollViewer.ScrollOwner属性,导致内容区域无法将滚动事件(如鼠标滚轮)传递给ScrollViewer,也无法同步光标滚动逻辑 - 未绑定内容相关属性,破坏了默认的布局和交互逻辑
修复方案
修改ScrollViewer模板中的ScrollContentPresenter,补充必要的绑定:
<ScrollContentPresenter CanContentScroll="{TemplateBinding CanContentScroll}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" ScrollViewer.ScrollOwner="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
修复说明
ScrollViewer.ScrollOwner:建立内容区域与ScrollViewer的关联,确保鼠标滚轮事件被正确处理,同时让光标能跟随内容滚动到可视区域- 其他绑定:保证内容的布局、显示与默认ScrollViewer行为一致,避免出现布局偏移或交互异常
替换模板中原有的<ScrollContentPresenter/>即可解决问题。
内容的提问来源于stack exchange,提问作者Phillip
相关产品推荐
相关产品推荐

