Question

I want to have ruler always visible even though I have scroll bar visible. See below Images

enter image description here

If I scroll down or scroll to right, both canvas ruler will be disappeared as I am scrolling whole panel to bottom/right. And both rulers are Dock.Left and Dock.To.

enter image description here

How can I set both ruler always visible , even though I scroll all the way right to bottom. I can not put scroll bar out side panel, because left ruler needs to move up and down as per vertical scroll and Horizontal ruler need to move left and right as per Horizontal scroll.

Was it helpful?

Solution

The nicest way I could think of doing this is by making a custom control to encapsulate the behaviour like the following:

public class RulerScrollViewer : ScrollViewer
{
    static RulerScrollViewer()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(RulerScrollViewer), new FrameworkPropertyMetadata(typeof(RulerScrollViewer)));
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        this.ScrollChanged -= RulerScrollViewer_ScrollChanged;
        this.ScrollChanged += RulerScrollViewer_ScrollChanged;
    }

    void RulerScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        var h = this.Template.FindName("PART_HorizontalRulerScrollViewer", this) as ScrollViewer;

        if (h != null)
            h.ScrollToHorizontalOffset(this.HorizontalOffset);

        var v = this.Template.FindName("PART_VerticalRulerScrollViewer", this) as ScrollViewer;
        if (v != null)
            v.ScrollToVerticalOffset(this.VerticalOffset);
    }
}

public class HorizontalRuler : Control
{
    protected override void OnRender(DrawingContext drawingContext)
    {
        for (int i = 0; i < this.ActualWidth / 100; i++)
        {
            var ft = new FormattedText(i.ToString(), CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface("Tahoma"), 8, Brushes.Black);
            drawingContext.DrawText(ft, new Point(i * 100, 0));
        }
    }
}

public class VerticallRuler : Control
{
    protected override void OnRender(DrawingContext drawingContext)
    {
        for (int i = 0; i < this.ActualHeight / 100; i++)
        {
            var ft = new FormattedText(i.ToString(), CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, new Typeface("Tahoma"), 8, Brushes.Black);
            drawingContext.DrawText(ft, new Point(0, i*100));
        }
    }
}

With something like the following in the generic.xaml for it:

<ControlTemplate x:Key="RulerScrollViewer_Template" TargetType="local:RulerScrollViewer">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <ScrollViewer x:Name="PART_HorizontalRulerScrollViewer" Grid.Row="0" Grid.Column="1" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
            <local:HorizontalRuler Width="{Binding ElementName=content,Path=ExtentWidth}" Height="20" />
        </ScrollViewer>

        <ScrollViewer x:Name="PART_VerticalRulerScrollViewer" Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
            <local:VerticallRuler Height="{Binding ElementName=content,Path=ExtentHeight}" Width="20" />
        </ScrollViewer>

        <ScrollContentPresenter Name="content" Grid.Row="1" Grid.Column="1" />

        <ScrollBar x:Name="PART_VerticalScrollBar"
                   Grid.Row="1" Grid.Column="2"
                   Value="{TemplateBinding VerticalOffset}"
                   Maximum="{TemplateBinding ScrollableHeight}"
                   ViewportSize="{TemplateBinding ViewportHeight}"
                   Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>

        <ScrollBar x:Name="PART_HorizontalScrollBar"
                   Orientation="Horizontal"
                   Grid.Row="2" Grid.Column="1"
                   Value="{TemplateBinding HorizontalOffset}"
                   Maximum="{TemplateBinding ScrollableWidth}"
                   ViewportSize="{TemplateBinding ViewportWidth}"
                   Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>

    </Grid>
</ControlTemplate>

<Style TargetType="{x:Type local:RulerScrollViewer}">
    <Setter Property="Template" Value="{StaticResource RulerScrollViewer_Template}" />
</Style>

Edit:

My reasoning for making it a custom control is because I wanted to gaurentee that the width of the horizontal ruler, and the height of the vertical ruler, were the same size size as the visible content. This was so I didn't get the weird behaviour of the rulers moving at slightly different rates vs the content due to the visible content area being slightly smaller from the scrollbars being inside the ScrollViewer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top