Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
150 views
in Technique[技术] by (71.8m points)

c# - Adjust FlowDocumentReader's Scroll Increment When ViewingMode Set to Scroll?

I'm displaying a FlowDocument in a FlowDocumentReader with the ViewingMode="Scroll". If I use the wheel on my mouse, the document scrolls very slowly. I'd like to increase the scroll step.

  1. I've tried to change the Scroll setting of my mouse in Control Panel, but that doesn't have any effect. I think that WPF ignores that setting for the FlowDocumentScrollViewer.

  2. I've added a Scroll event on the FlowDocument and FlowDocumentReader, but that doesn't fire when I use the mouse wheel.

  3. I've added a Loaded event on the FlowDocumentReader, got the ScrollViewer descendant, found the ScrollBar ("PART_VerticalScrollBar") from the scroll viewer's template and adjusted the SmallChange & LargeChange properties. That also didn't have any effect.

Anyone have any ideas?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

We can modify this in a Control's MouseWheel event, like Sohnee sugested, but then it'd just be solved for one specific case, and you'd have to have access to the FlowDocumentReader, which if your usinging something like MVVM, you wont. Instead, we can create an attached property that we can then set on any element with a ScrollViewer. When defining our attached property, we also are going to want a PropertyChanged callback where we will perform the actual modifications to the scroll speed. I also gave my property a default of 1, the range of speed I'm going to use is .1x to 3x, though you could just as easily do something like 1-10.

public static double GetScrollSpeed(DependencyObject obj)
{
    return (double)obj.GetValue(ScrollSpeedProperty);
}

public static void SetScrollSpeed(DependencyObject obj, double value)
{
    obj.SetValue(ScrollSpeedProperty, value);
}

public static readonly DependencyProperty ScrollSpeedProperty =
    DependencyProperty.RegisterAttached(
    "ScrollSpeed",
    typeof(double),
    typeof(ScrollHelper),
    new FrameworkPropertyMetadata(
        1.0,
        FrameworkPropertyMetadataOptions.Inherits & FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        new PropertyChangedCallback(OnScrollSpeedChanged)));

private static void OnScrollSpeedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
}

Now that we have our Attached Property we need to handle the scrolling, to do this, in the OnScrollSpeedChanged we can handle the PreviewMouseWheel event. We want to hook into the PreviewMouseWheel, since it is a tunneling event that will occur before the ScrollViewer can handle the standard MouseWheel event.

Currently, the PreviewMouseWheel handler is taking in the FlowDocumentReader or other thing that we bound it to, however what we need is the ScrollViewer. Since it could be a lot of things: ListBox, FlowDocumentReader, WPF Toolkit Grid, ScrollViewer, etc, we can make a short method that uses the VisualTreeHelper to do this. We already know that the item coming through will be some form of DependancyObject, so we can use some recursion to find the ScrollViewer if it exists.

public static DependencyObject GetScrollViewer(DependencyObject o)
{
    // Return the DependencyObject if it is a ScrollViewer
    if (o is ScrollViewer)
    { return o; }

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
    {
        var child = VisualTreeHelper.GetChild(o, i);

        var result = GetScrollViewer(child);
        if (result == null)
        {
            continue;
        }
        else
        {
            return result;
        }
    }

    return null;
}

private static void OnScrollSpeedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
    var host = o as UIElement;
    host.PreviewMouseWheel += new MouseWheelEventHandler(OnPreviewMouseWheelScrolled);
}

Now that we can get the ScrollViwer we can finally modify the scroll speed. We'll need to get the ScrollSpeed property from the DependancyObject that is being sent through. Also, we can use our helper method to get the ScrollViewer that is contained within the element. Once we have these two things, we can get and modify the ScrollViewer's VerticalOffset. I found that dividing the MouseWheelEventArgs.Delta, which is the amount that the mouse wheel changed, by 6 gets approximately the default scroll speed. So, if we multiply that by our ScrollSpeed modifier, we can then get the new offset value. We can then set the ScrollViewer's VerticalOffset using the ScrollToVerticalOffset method that it exposes.

private static void OnPreviewMouseWheelScrolled(object sender, MouseWheelEventArgs e)
{
    DependencyObject scrollHost = sender as DependencyObject;

    double scrollSpeed = (double)(scrollHost).GetValue(Demo.ScrollSpeedProperty);

    ScrollViewer scrollViewer = GetScrollViewer(scrollHost) as ScrollViewer;

    if (scrollViewer != null)
    {
        double offset = scrollViewer.VerticalOffset - (e.Delta * scrollSpeed / 6);
        if (offset < 0)
        {
            scrollViewer.ScrollToVerticalOffset(0);
        }
        else if (offset > scrollViewer.ExtentHeight)
        {
            scrollViewer.ScrollToVerticalOffset(scrollViewer.ExtentHeight);
        }
        else
        {
            scrollViewer.ScrollToVerticalOffset(offset);
        }

        e.Handled = true;
    }
    else
    {
        throw new NotSupportedException("ScrollSpeed Attached Property is not attached to an element containing a ScrollViewer.");
    }
}

Now that we've got our Attached Property set up, we can create a simple UI to demonstrate it. I'm going to create a ListBox, and a FlowDocumentReaders so that we can see how the ScrollSpeed will be affected across multiple controls.

<UniformGrid Columns="2">
    <DockPanel>
        <Slider DockPanel.Dock="Top"
            Minimum=".1"
            Maximum="3"
            SmallChange=".1"
            Value="{Binding ElementName=uiListBox, Path=(ScrollHelper:Demo.ScrollSpeed)}" />
        <ListBox x:Name="uiListBox">
            <!-- Items -->
        </ListBox>
    </DockPanel>
    <DockPanel>
        <Slider DockPanel.Dock="Top"
            Minimum=".1"
            Maximum="3"
            SmallChange=".1"
            Value="{Binding ElementName=uiListBox, Path=(ScrollHelper:Demo.ScrollSpeed)}" />
        <FlowDocumentReader x:Name="uiReader"
            ViewingMode="Scroll">
            <!-- Flow Document Content -->
        </FlowDocumentReader>
    </DockPanel>
</UniformGrid>

Now, when run, we can use the Sliders to modify the scrolling speed in each of the columns, fun stuff.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...