Controllerizing the ScrollViewer Thumbnail

In the last post we created a ScrollViewer Thumbnail feature using a just a bit of Xaml and databinding magic.

Since it‘s a pretty useful feature, it makes sense to ‘Controllerize‘ it and make it more reusable. Originally, I was just planning to do this using a UserControl as this is a very lightweight way to Controllerize a bit of Xaml. However, creating a full custom control isn‘t much harder so it makes sense go that route.

We need to add a bunch of files to our new class library to get this started. First, we need a simple class that inherits from Control

public class ScrollViewerThumbnail : Control
{
    static ScrollViewerThumbnail()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ScrollViewerThumbnail), newFrameworkPropertyMetadata(typeof(ScrollViewerThumbnail)));
    }

public ScrollViewer ScrollViewer
    {
        get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
        set { SetValue(ScrollViewerProperty, value); }
    }

// Using a DependencyProperty as the backing store for ScrollViewer. This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ScrollViewerProperty =
    DependencyProperty.Register("ScrollViewer", typeof(ScrollViewer), typeof(ScrollViewerThumbnail), newUIPropertyMetadata(null));
    
}

There‘s only two things going on in there. One is a new dependency property of type ScollViewer. We‘ll use this to specify the ScrollViewer whose content we should thumbnail. The other part is a static constructor that overrides the default style key so we can find our default template.

Next, we need a Generic.xaml file that should sit inside a ‘Themes‘ folder:

Note: the Generic.xaml file‘s build action must be set to Page in the properties panel.

And here‘s the contents of our Generic.xaml file - it‘s just a resource dictionary with a single style that targets our ScrollViewerThumbnail type.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Controls="clr-namespace:TheJoyOfCode.Wpf.Controls">

<Style TargetType="{x:Type Controls:ScrollViewerThumbnail}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Controls:ScrollViewerThumbnail}">
                    <Viewbox DataContext="{TemplateBinding ScrollViewer}" Stretch="Uniform">
                        <Grid>
                            <Rectangle
                                Width="{Binding Content.ActualWidth}" 
                                Height="{Binding Content.ActualHeight}">
                                <Rectangle.Fill>
                                    <VisualBrush Visual="{Binding Content}" />
                                </Rectangle.Fill>
                            </Rectangle>
                            <Border
                                Background="{TemplateBinding HighlightFill}" 
                                Width="{Binding ViewportWidth}" 
                                Height="{Binding ViewportHeight}"
                                HorizontalAlignment="Left" 
                                VerticalAlignment="Top">
                                <Border.RenderTransform>
                                    <TranslateTransform 
                                        X="{Binding HorizontalOffset}" 
                                        Y="{Binding VerticalOffset}" />
                                </Border.RenderTransform>
                            </Border>
                        </Grid>
                    </Viewbox>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

This style has only one Setter that sets the Template of the control to a new ControlTemplate. From then on the xaml (inside the ControlTemplate) is almost identical to the previous example with two notable differences.

1. The DataContext of the Viewbox is now a TemplateBinding instead of a normal binding. This directly targets the ScrollViewer dependency property we created earlier.

2. The Background of the highlight is also a TemplateBinding that uses the HighlightFill property of our ScrollViewerThumbnail. Mmmm, but we didn‘t have a HighlightFill property. We better create that now (inside the ScrollViewerThumbnail class):

public Brush HighlightFill
{
    get { return (Brush)GetValue(HighlightFillProperty); }
    set { SetValue(HighlightFillProperty, value); }
}

public static readonly DependencyProperty HighlightFillProperty = 
    DependencyProperty.Register("HighlightFill", 
        typeof(Brush), 
        typeof(ScrollViewerThumbnail), 
        new UIPropertyMetadata(new SolidColorBrush(Color.FromArgb(128,255,255,0))));

Done. Notice that we specified a default fill of transparent yellow. Cool.

We‘re almost done. One really important last thing: You must add the following code to your assemblyinfo.cs file so WPF knows where to go looking for our default template:

[assembly: ThemeInfo(
    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
    //(used if a resource is not found in the page, 
    // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
    //(used if a resource is not found in the page, 
    // app, or any theme specific resource dictionaries)
)]

Using the new ScrollViewerThumbnail control

... couldn‘t be easier!

<Grid>
    <ScrollViewer x:Name="scrollViewer" HorizontalScrollBarVisibility="Auto">
        <!-- Your ScrollViewer content here as normal -->
    </ScrollViewer>
    <Controls:ScrollViewerThumbnail ScrollViewer="{Binding ElementName=scrollViewer}" 
        Width="150" Height="150" 
        HorizontalAlignment="Right" VerticalAlignment="Bottom" 
        Margin="10" />
</Grid>

We just bind the ScrollViewer proprety of the ScrollViewerThumbnail to a ScrollViewer. Done.

Next we‘ll look at making the ScrollViewerThumbnail interactive.

=============================================================================

In the last post we took the simple ScrollViewer thumbnail and controllerized it. This time, we‘re going to make it interactive.

And, because WPF totally rocks, it‘s stupidly easy to do.

Normally, we‘d do lots of mouse capture, remember the original location and the new location to calculate the delta etc etc. With WPF, we can simply use the Thumb control that fires a DragDelta event if the user makes a drag gesture over the control. Huzzah.

So, we need to quickly change the ScrollViewerThumbnail‘s template in Generic.xaml to look like this:

<ControlTemplate TargetType="{x:Type Controls:ScrollViewerThumbnail}">
    <Viewbox DataContext="{TemplateBinding ScrollViewer}" Stretch="Uniform">
        <Grid>
            <Rectangle
                Width="{Binding Content.ActualWidth}" 
                Height="{Binding Content.ActualHeight}">
                <Rectangle.Fill>
                    <VisualBrush Visual="{Binding Content}" />
                </Rectangle.Fill>
            </Rectangle>
            <Thumb Name="PART_Highlight"
                Background="{TemplateBinding HighlightFill}"
                Width="{Binding ViewportWidth}" 
                Height="{Binding ViewportHeight}"
                HorizontalAlignment="Left" 
                VerticalAlignment="Top">
                <Thumb.RenderTransform>
                    <TranslateTransform 
                        X="{Binding HorizontalOffset}" 
                        Y="{Binding VerticalOffset}" />
                </Thumb.RenderTransform>
                <Thumb.Template>
                    <ControlTemplate TargetType="Thumb">
                        <Border Background="{TemplateBinding Background}" />
                    </ControlTemplate>
                </Thumb.Template>
            </Thumb>
        </Grid>
    </Viewbox>
</ControlTemplate>

Note that we‘ve changed the highlight from a Border to a Thumb. We‘ve named it PART_Highlight and I‘ve given the thumb a ControlTemplate that is a simple Border (Thumbs can‘t have content).

Next, we need to find the PART_Highlight in our ScrollViewerThumbnail template and attach a handler to it‘s DragDelta event. We can‘t just use this.FindName("PART_Highlight") because it‘s part of a template. We have to override the OnApplyTemplate method of the ScrollViewerThumbnail class and use this.Template.FindName("PART_Highlight"):

private const string PART_Highlight = "PART_Highlight";

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

var partHighlight = (Thumb)this.Template.FindName(PART_Highlight, this);
    partHighlight.DragDelta += partHighlight_DragDelta;
}

Next, we need to move the ScrollViewer when the DragDelta event occurs.

void partHighlight_DragDelta(object sender, DragDeltaEventArgs e)
{
    ScrollViewer.ScrollToVerticalOffset(ScrollViewer.VerticalOffset + e.VerticalChange);
    ScrollViewer.ScrollToHorizontalOffset(ScrollViewer.HorizontalOffset + e.HorizontalChange);
}

And we‘re done. Seriously! I‘m blown away that this can be achieved with just four-ish lines of code.

Just click on the Arrows icon to display a popup showing the ScrollViewer thumbnail.

PS - you can use the mouse wheel to change the size of the ScrollViewerThumbnail control too.

时间: 2024-11-13 06:44:59

Controllerizing the ScrollViewer Thumbnail的相关文章

滚动条——WPF ScrollViewer的应用

WPF ScrollViewer的应用 我们知道在一个限定高的窗体和容器中,想要把内容显示完是有些问题的,这个时候我们就要使用类似于浏览器的那个滚动条的效果了,在wpf中也同样如此,最近就碰到了这样的问题,也纠结了很长时间,调试滚动效果的时候一直出不来,还好现在调试好了,把代码贴出来 xaml: <StackPanel> <ScrollViewer Name="scroll" Margin="0,50" Width="720"

Windows Store App 中使用Scrollviewer实现缩放功能

Windows Store App中可以直接使用Scrollviewer实现缩放功能,基本代码如下: <ScrollViewer Background="Yellow" HorizontalScrollBarVisibility="Visible" MaxZoomFactor="2" MinZoomFactor="0.5" VerticalScrollBarVisibility="Visible" Z

WPF获取控件内部的ScrollViewer,并控制ScrollViewer操作

//获取内部  ScrollViewer方法 public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject        {            if (obj != null)            {                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)          

Windows Phone 8.1开发:如何从ListView中,获取ScrollViewer对象

在使用ListView作为信心呈现载体开发应用程序时,我们经常需要通过监视滚动条(ScrollViewer)的位置状态来完成一些交互逻辑.最直接的体现就是 延时加载,(上滑加载更多,下拉获取更新数据). 我们可以通过 scrollViewer的ViewChanged事件,来完成这些逻辑. 那么问题来了.(- -!不是说挖掘机--..) 如何从ListView中,获取ScrollViewer对象? 1.我们可以通过这样一个方法来 获取ListView下的ScrollViewer对象.  方法定义如

WPF ScrollViewer 滚动条宽度

将WPF的程序,移植到触屏的一体机上执行时,scrollviewer的滚动条太细,导致手动点击的时候,操作不是很方便,所以需要重写一个scrollviewer的样式,加宽滚动条. 示例如下:将宽度设置为想要的数值即可. <ScrollViewer>     <ScrollViewer.Resources>         <Style TargetType="ScrollBar">             <Style.Triggers>

WPF 自定义滑动ScrollViewer

自定义滑动滚动条 预期目标:横向滚动条,可以左右滑动,用鼠标按住(触摸)然后释放可以实现快速滑动. 我们有几种方案: 1.ScrollViewer,修改其中的横向滚动条,将其中的背景设置为透明. 但是,存在的问题是,没有快速滑动的效果 2.ListBox,修改Panel为WrapPanel,或者直接将内容作为ItemSource填充进去. 这个能实现多个子元素切换的效果,但是针对单个或者一个区域的内容,就有点大材小用了. 3.用DevExpress中的LayoutControl.ScrollBo

WinStore控件之Button、HyperlinkButton、RadioButton、CheckBox、progressBar、ScrollViewer、Slider

1.Button protected override void OnNavigatedTo(NavigationEventArgs e) { /* * Button - 按钮控件,其全部功能是通过其基类 ButtonBase 提供的 * ClickMode - 引发 Click 事件的模式:ClickMode.Release(默认值), ClickMode.Press, ClickMode.Hover * IsPointerOver - 设备指针(鼠标或手指等)是否在按钮上 * IsPress

重新想象 Windows 8.1 Store Apps (78) - 控件增强: ScrollViewer, FlipView, Popup

[源码下载] 重新想象 Windows 8.1 Store Apps (78) - 控件增强: ScrollViewer, FlipView, Popup 作者:webabcd介绍重新想象 Windows 8.1 Store Apps 之控件增强 ScrollViewer - 滚动视图控件的增强 FlipView - 滑动视图控件的增强 Popup - 弹出框控件的增强 示例1.演示 ScrollViewer 的新特性ScrollViewerDemo.xaml <Page x:Class="

Windows Phone 8.1中ScrollViewer(一)

开篇之前: 推荐王磊老师的Windows 8.1关于ScrollViewer的讲解的博客 链接:重新想象 Windows 8 Store Apps (9) - 控件之 ScrollViewer 基础 ScrollViewer的作用就是当内容超出了设定的范围的时候,出现滚动条用来滚动查看超出的内容 要想在ScrollViewer里面写东西,OK,你可以直接写个<TextBlock>标签,但是当写第二个<TextBlock>的时候就会报 错了,说是多次设置Content值 所以要用布局