How to place TextBox's ScrollBar in a different place?

Emon Haque 3,176 Reputation points
2021-05-06T06:59:05.387+00:00

There're three columns in this custom Textbox, first for the left icon, second for the TextBox and the third for the right icon. As I keep adding lines in the TextBox the ScrollBar appears in its normal position.:

94321-test.gif

Is it possible to Translate it? I want it to appear appear below the tick mark?

----
EDIT

I've tried, first, with MarginProperty and then with a TranslateTransForm:

class EditTextScrollbar : ControlTemplate  
{  
    public EditTextScrollbar() {  
        var grid = new FrameworkElementFactory(typeof(Grid));  
        var row1 = new FrameworkElementFactory(typeof(RowDefinition));  
        var row2 = new FrameworkElementFactory(typeof(RowDefinition));  
        var col1 = new FrameworkElementFactory(typeof(ColumnDefinition));  
        var col2 = new FrameworkElementFactory(typeof(ColumnDefinition));  
        var vScroll = new FrameworkElementFactory(typeof(ScrollBar)) { Name = "PART_VerticalScrollBar" };  
        var hScroll = new FrameworkElementFactory(typeof(ScrollBar)) { Name = "PART_HorizontalScrollBar" };  
        var translate = new FrameworkElementFactory(typeof(TranslateTransform));  

        row2.SetValue(RowDefinition.HeightProperty, new GridLength(1, GridUnitType.Auto));  
        col1.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));  
        //vScroll.SetValue(ScrollBar.MarginProperty, new Thickness(50, 50, 0, 0));  
        hScroll.SetValue(ScrollBar.OrientationProperty, Orientation.Horizontal);  
        hScroll.SetValue(Grid.RowProperty, 1);  
        hScroll.SetValue(Grid.ColumnProperty, 1);  
        translate.SetValue(TranslateTransform.XProperty, 20d);  

        vScroll.AppendChild(translate);  
        grid.AppendChild(row1);  
        grid.AppendChild(row2);  
        grid.AppendChild(col1);  
        grid.AppendChild(col2);  
        grid.AppendChild(vScroll);  
        grid.AppendChild(hScroll);  
        VisualTree = grid;  
    }  
}  

and in TextBox, named inputBox, I'd added this:

inputBox.Resources.Add("x", new Style() {  
    TargetType = typeof(ScrollViewer),  
    Setters = {  
        new Setter() {  
            Property = ScrollViewer.TemplateProperty,  
            Value = new EditTextScrollbar()  
        }  
    }  
});  

with MarginProperty nothing, apparently, changes and with TranslateTransform it throws:

System.ArgumentException: ''TranslateTransform' type must derive from FrameworkElement, FrameworkContentElement, or Visual3D.'

Developer technologies Windows Presentation Foundation
0 comments No comments
{count} votes

Accepted answer
  1. Emon Haque 3,176 Reputation points
    2021-05-07T09:11:47.417+00:00

    @DaisyTian-MSFT, I've translated your xaml into code, not that complicated BUT it looks weird because of those ...Factory. The primary problem with my original code was in this inputBox.Resources.Add("x", new Style() line. Here instead of "x" I've to have typeof(ScrollViewer). Now it shifts the ScrollBar to the right and below the tick mark:

    94697-test.gif

    BUT there're two issues. First, as I keep adding lines in the TextBox the Thumb keeps getting smaller BUT I don't see what I'm typing in the TextBox as it doesn't scroll to the caret position automatically. Second, If I move/scroll the PointerWheel on the ScrollBar or on the TextBox it doesn't scroll. The only way to scroll is drag the Thumb or click on the ScrollButton. How to fix those issues? The equivalent xaml of my code is:

    <TextBox BorderThickness="1"  
                BorderBrush="SkyBlue"  
                Height="70"   
                TextWrapping="Wrap"  
                AcceptsReturn="True"  
                ScrollViewer.VerticalScrollBarVisibility="Auto">  
        <TextBox.Resources>  
            <Style TargetType="{x:Type ScrollViewer}">  
                <Setter Property="Template">  
                    <Setter.Value>  
                        <ControlTemplate TargetType="{x:Type ScrollViewer}">  
                            <Grid>  
                                <Grid.ColumnDefinitions>  
                                    <ColumnDefinition />  
                                    <ColumnDefinition Width="Auto"/>  
                                </Grid.ColumnDefinitions>  
                                <ScrollContentPresenter />  
                                <ScrollBar Name="PART_VerticalScrollBar"    
                                            Grid.Column="1"   
                                            Margin="0 20 -20 0"  
                                            ViewportSize="{TemplateBinding ViewportHeight}"  
                                            Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"  
                                            Maximum="{Binding ScrollableHeight, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"   
                                            Visibility="{Binding ComputedVerticalScrollBarVisibility, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}">  
                                </ScrollBar>  
                            </Grid>  
                        </ControlTemplate>  
                    </Setter.Value>  
                </Setter>  
            </Style>  
        </TextBox.Resources>  
    </TextBox>  
    

    I've used Binding instead of TemplateBinding to match it exactly with my code behind approach. It doesn't solve those issues even if I use TemplateBinding in Value, Maximum and Visibility instead of Binding.

    You can paste the xaml in the MainWindow.xaml to see the behavior or if you're interested in the way I did, without xaml, here's the ControlTemplate:

    class EditTextScrollbar : ControlTemplate  
    {  
        public EditTextScrollbar() {  
            TargetType = typeof(ScrollViewer);  
            var grid = new FrameworkElementFactory(typeof(Grid));  
            var col1 = new FrameworkElementFactory(typeof(ColumnDefinition));  
            var col2 = new FrameworkElementFactory(typeof(ColumnDefinition));  
            var vScroll = new FrameworkElementFactory(typeof(ScrollBar)) { Name = "PART_VerticalScrollBar" };  
            var content = new FrameworkElementFactory(typeof(ScrollContentPresenter));               
            col2.SetValue(ColumnDefinition.WidthProperty, new GridLength(1, GridUnitType.Auto));  
            vScroll.SetValue(Grid.ColumnProperty, 1);  
            vScroll.SetValue(ScrollBar.MarginProperty, new Thickness(0, 20, -26, 0));  
            vScroll.SetValue(ScrollBar.ViewportSizeProperty, new TemplateBindingExtension(ScrollViewer.ViewportHeightProperty));  
            vScroll.SetBinding(ScrollBar.ValueProperty, new Binding() {  
                Path = new PropertyPath(nameof(ScrollViewer.VerticalOffset)),  
                RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),  
                Mode = BindingMode.OneWay  
            });  
            vScroll.SetBinding(ScrollBar.MaximumProperty, new Binding() {  
                Path = new PropertyPath(nameof(ScrollViewer.ScrollableHeight)),  
                RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),  
                Mode = BindingMode.OneWay  
            });  
            vScroll.SetBinding(ScrollBar.VisibilityProperty, new Binding() {  
                Path = new PropertyPath(nameof(ScrollViewer.ComputedVerticalScrollBarVisibility)),  
                RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),  
                Mode = BindingMode.OneWay  
            });  
            grid.AppendChild(col1);  
            grid.AppendChild(col2);  
            grid.AppendChild(content);  
            grid.AppendChild(vScroll);  
            VisualTree = grid;  
        }  
    }  
    

    and here's how it's set to the inputBox:

    inputBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;  
    inputBox.Resources.Add(typeof(ScrollViewer), new Style() {  
        TargetType = typeof(ScrollViewer),  
        Setters = { new Setter(ScrollViewer.TemplateProperty, new EditTextScrollbar()) }  
    });  
    

    ----
    EDIT

    With this: content.SetValue(ScrollContentPresenter.CanContentScrollProperty, true); in the ControlTemplate it works as expected.

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. DaisyTian-1203 11,646 Reputation points
    2021-05-07T07:19:51.3+00:00

    To implement with C# code is complicated, how about using xaml code like below:

      <TextBox HorizontalAlignment="Left" Height="100" Margin="208,181,0,0" TextWrapping="Wrap"  
                     Text="TextBox Vertical Scroll BarVisibility System Windows Application Current Resources Remove TextBox Vertical Scroll BarVisibility System Windows Application Current Resources Remove   "   
                     VerticalAlignment="Top" Width="150">  
                <TextBox.Resources>  
                    <Style TargetType="{x:Type ScrollViewer}">  
                        <Setter Property="OverridesDefaultStyle" Value="True"/>  
                        <Setter Property="VerticalScrollBarVisibility" Value="Visible"></Setter>  
                        <Setter Property="HorizontalScrollBarVisibility" Value="Visible"></Setter>  
                        <Setter Property="Template">  
                            <Setter.Value>  
                                <ControlTemplate TargetType="{x:Type ScrollViewer}">  
                                    <Grid>  
                                        <Grid.ColumnDefinitions>  
                                            <ColumnDefinition Width="Auto"/>  
                                            <ColumnDefinition/>  
                                        </Grid.ColumnDefinitions>  
                                        <Grid.RowDefinitions>  
                                            <RowDefinition/>  
                                            <RowDefinition Height="Auto"/>  
                                        </Grid.RowDefinitions>  
                                        <ScrollContentPresenter Grid.Row="0" Grid.Column="1"/>  
      
                                        <ScrollBar Name="PART_VerticalScrollBar"  Grid.Row="0" Grid.Column="1" Orientation="Vertical" HorizontalAlignment="Left"  
                                                   Value="{TemplateBinding VerticalOffset}"  
                                                   Maximum="{TemplateBinding ScrollableHeight}" ViewportSize="{TemplateBinding ViewportHeight}"   
                                                   />   
                                    </Grid>  
                                </ControlTemplate>  
                            </Setter.Value>  
                        </Setter>  
                    </Style>  
                </TextBox.Resources>                  
            </TextBox>  
    

    I try a lot and find it is hard to use Resources.Add(key,object). When the style be set with key, it needs to set Binding to the ScrollViewer, the ScrollViewer is inside the TextBox.


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.