You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 02:52:32