Jan
Jan

Reputation: 11

WPF ScrollViewer starts scrolling to early

I need a way to make a button overlap a textblock when resizing horizontally to a smaller size.This works fine for the first example, but when i put the same code in a scrollviewer, the horizontal scrollbar kicks in from the moment the textblock and button meet. How can i avoid this so that the button overlaps the same way as without the scrollviewer? Ideally scrolling should start from the moment that the textblock reaches its minimum width.

      <StackPanel>
            <!--works-->
            <Grid Height="30" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"></ColumnDefinition>
                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBlock Text="testsdfqqsdfqsdqsdfqsdfqsdfqsdfqsdfsdqsdf"  HorizontalAlignment="Left"></TextBlock>
                <Button Grid.Column="1" MinWidth="20">te</Button>
            </Grid>

            <!--doesn't work-->
            <ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled">
                <Grid Height="30" >
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="Auto"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="testsdfqqsdfqsdqsdfqsdfqsdfqsdfqsdfsdqsdf"  HorizontalAlignment="Left"></TextBlock>
                    <Button Grid.Column="1" MinWidth="20">te</Button>
                </Grid>
            </ScrollViewer>
        </StackPanel>

Update In fact i can reduce the problem to a textblock in a scrollviewer. I would like that it would only become possible for the horizontal scrollbar to scroll when the min width of the textblock is reached.

    <ScrollViewer HorizontalScrollBarVisibility="Visible" 
                      VerticalScrollBarVisibility="Disabled">
        <TextBlock Background="Yellow" 
                   MinWidth="50">reallylooooooooooooooooooooong 
                   text</TextBlock>
    </ScrollViewer>

Update I found a solution here https://social.msdn.microsoft.com/Forums/en-US/26e03045-4b57-4960-8d33-d0295855f4e3/minwidth-with-scrollviewer-to-then-take-over?forum=wpf The converter substracts the width of the button from the actual width of the grid.Now it starts scrolling the moment that the minimum width of the textblock is reached.

I don't know if a custom scrollviewer for any content could be smart enough to behave the same way?

<ScrollViewer HorizontalScrollBarVisibility="Visible" 
            VerticalScrollBarVisibility="Disabled"
            >
    <Grid Height="30" x:Name="grid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Text="testsdfqqsdfqsdqsdfqsdfqsdfqsdfqsdfsdqsdf"  
                HorizontalAlignment="Left"
                MinWidth="150"
                Width="{Binding ElementName=grid, Path=ActualWidth, 
                 Converter={x:Static conv:WidthConverter.Instance}}"
                ></TextBlock>
        <Button Grid.Column="1" MinWidth="20">te</Button>
    </Grid>
</ScrollViewer>

Upvotes: 1

Views: 451

Answers (3)

BionicCode
BionicCode

Reputation: 28968

Wrapping the complete Grid into a ScrollViewer results in the ScrollViewer to make all its contained elements visible i.e. scrollable. In your case, it will make the TextBlock and the Button scrollable.

To allow the Button to clip the TextBlock i.e. the TextBlock.Text to virtually exceed the actual width of the TextBlock, you can wrap the TextBlock alone into a ScrollViewer.
This will result in the TextBlock being able to hide or clip text that exceeds the available rendering width, while the user can still view it entirely by scrolling.

The following example will make the scroll bars appear as soon text has become hidden from the user, allowing him to still read all the text:

<Grid Height="30">
  <Grid.ColumnDefinitions>
    <ColumnDefinition />
    <ColumnDefinition Width="Auto" />
  </Grid.ColumnDefinitions>
  <ScrollViewer HorizontalScrollBarVisibility="Auto"
                VerticalScrollBarVisibility="Disabled">
    <TextBlock Text="testsdfqqsdfqsdqsdfqsdfqsdfqsdfqsdfsdqsdf" />
  </ScrollViewer>
  <Button Grid.Column="1"
          HorizontalAlignment="Right"
          MinWidth="20">te</Button>
</Grid>

Your requirement sounds like you want to hide certain amount of text from the user before he is allowed to scroll through it, enabling him to read all the text.
From a user experience point of view, I highly recommend against such a behavior.
However, to achieve this, simply modify the above example to add a negative right Padding to the ScrollViewer:

<Grid Height="30">
  <Grid.ColumnDefinitions>
    <ColumnDefinition />
    <ColumnDefinition Width="Auto" />
  </Grid.ColumnDefinitions>
  <ScrollViewer HorizontalScrollBarVisibility="Auto"
                VerticalScrollBarVisibility="Disabled"
                Padding="0,0,-150,0>
    <TextBlock Text="testsdfqqsdfqsdqsdfqsdfqsdfqsdfqsdfsdqsdf" />
  </ScrollViewer>
  <Button Grid.Column="1"
          HorizontalAlignment="Right"
          MinWidth="20">te</Button>
</Grid>

The above solution will clip the exceeding text and will show a ScrollViewer after a certain threshold (controlled via ScrollViewer.Padding), but will not allow to scroll the entire hidden text (only the portion from the moment on the ScrollViewer became visible).

To enable scrolling of the entire hidden text, you can define a simple Style that resets the Padding to 0 (in fact, it will toggle the Padding), once the scroll bars are visible:

<Grid Height="30">
  <Grid.ColumnDefinitions>
    <ColumnDefinition /n>
    <ColumnDefinition Width="Auto" />
  </Grid.ColumnDefinitions>

  <ScrollViewer HorizontalScrollBarVisibility="Auto"
                VerticalScrollBarVisibility="Disabled">
    <ScrollViewer.Style>

      <!-- Optionally, move this Style to a ResourceDictionary (e.g. App.xaml), 
           to make it apply to all ScrollViewer instance within the scope -->
      <Style TargetType="ScrollViewer">
        <Setter Property="Padding"
                Value="0,0,-150,0" />
        <Style.Triggers>
          <Trigger Property="ComputedHorizontalScrollBarVisibility"
                   Value="Visible">
            <Setter Property="Padding"
                    Value="0" />
          </Trigger>
        </Style.Triggers>
      </Style>
    </ScrollViewer.Style>

    <TextBlock Text="testsdfqqsdfqsdqsdfqsdfqsdfqsdfqsdfsdqsdf" />
  </ScrollViewer>

  <Button Grid.Column="1"
          MinWidth="20">te</Button>
</Grid>

Upvotes: 1

Mark Feldman
Mark Feldman

Reputation: 16119

You'll need to re-template the ScrollViewer control. Start by extracting the template by moving the cursor over the ScrollViewer control and from the Properties pane select Miscellaneous -> Template -> Convert To New Resource (from the drop-down). The ScrollContentPresenter in the new template that appears is what draws everything inside the "client area":

<ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanHorizontallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" CanVerticallyScroll="False" Grid.Column="0" Content="{TemplateBinding Content}" CanContentScroll="{TemplateBinding CanContentScroll}" Margin="{TemplateBinding Padding}" Grid.Row="0"/>

So move your 2-column grid from outside the template to that point there, and move the ScrollContentPresenter to it's left column and your button to it's right. You should also set the button's column width to "Auto" so that it gets all the space it needs. Final ControlTemplate should look like this:

<ControlTemplate x:Key="ScrollViewerTemplate1" TargetType="{x:Type ScrollViewer}">
    <Grid x:Name="Grid" Background="{TemplateBinding Background}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Rectangle x:Name="Corner" Grid.Column="1" Fill="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Grid.Row="1"/>
        <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanHorizontallyScroll="False" ContentTemplate="{TemplateBinding ContentTemplate}" CanVerticallyScroll="False" Grid.Column="0" Content="{TemplateBinding Content}" CanContentScroll="{TemplateBinding CanContentScroll}" Margin="{TemplateBinding Padding}" Grid.Row="0"/>
                <Button Grid.Column="1" MinWidth="20">te</Button>
            </Grid>
        </Grid>
        <ScrollBar x:Name="PART_VerticalScrollBar" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" Grid.Column="1" Maximum="{TemplateBinding ScrollableHeight}" Minimum="0" Grid.Row="0" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
        <ScrollBar x:Name="PART_HorizontalScrollBar" AutomationProperties.AutomationId="HorizontalScrollBar" Cursor="Arrow" Grid.Column="0" Maximum="{TemplateBinding ScrollableWidth}" Minimum="0" Orientation="Horizontal" Grid.Row="1" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
    </Grid>
</ControlTemplate>

And your XAML should look like this:

<StackPanel>
    <ScrollViewer Template="{DynamicResource ScrollViewerTemplate1}" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled">
        <Grid Height="30" >
            <TextBlock Text="testsdfqqsdfqsdqsdfqsdfqsdfqsdfqsdfsdqsdf"  HorizontalAlignment="Left"></TextBlock>
        </Grid>
    </ScrollViewer>
</StackPanel>

Result:

enter image description here

Upvotes: 1

dba
dba

Reputation: 1175

If you want to show multiple elements in a panel, governed by a scrollviewer you may use an ItemsControl inside the ScrollViewer

<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" >
      <ItemsControl >
        <Grid Height="30">
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
          </Grid.ColumnDefinitions>
          <TextBlock Text="testsdfqqsdfqsdqsdfqsdfqsdfqsdfqsdfsdqsdf"
                     HorizontalAlignment="Left"></TextBlock>
          <Button Grid.Column="1"
                  MinWidth="20">te</Button>
        </Grid>
        <Grid Height="30">
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
          </Grid.ColumnDefinitions>
          <TextBlock Text="testsdfqqsdfqsdqsdfqsdfqsdfqsdfqsdfsdqsdf"
                     HorizontalAlignment="Left"></TextBlock>
          <Button Grid.Column="1"
                  MinWidth="20">te</Button>
        </Grid>
      </ItemsControl>
    </ScrollViewer>

Upvotes: 1

Related Questions