WPF Zoom + Scrollbar?

时间:2014-10-01 11:27:02

标签: wpf xaml zoom

我试图缩放scrollviewer中的一些内容。

我正在寻找的缩放行为是RenderTransform + ScaleTransform。但这不适用于ScrollViewer。

使用LayoutTransform + ScaleTransform,滚动查看器会受到影响(仅限ContentTemplate1),但行为不像缩放。

假设无法更改ContentTemplate1 / ContentTemplate2(即第三方控件),如何缩放以使用滚动查看器?

<Grid>
    <Grid.Resources>
        <!-- Content type 1 -->
        <DataTemplate x:Key="ContentTemplate1">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <TextBlock Background="DodgerBlue" Text="Left"/>
                <TextBlock Grid.Column="1" Background="DarkGray" Text="Right"/>
            </Grid>
        </DataTemplate>

        <!-- Content type 2 -->
        <DataTemplate x:Key="ContentTemplate2">
            <Viewbox>
                <TextBlock Background="DodgerBlue" Text="Scale to fit" Width="100" Height="70" Foreground="White" TextAlignment="Center"/>
            </Viewbox>
        </DataTemplate>
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <TabControl>
        <!-- Content 1 -->
        <TabControl.Resources>
            <ScaleTransform x:Key="ScaleTransform"
                            ScaleX="{Binding ElementName=ZoomSlider,Path=Value}"
                            ScaleY="{Binding ElementName=ZoomSlider,Path=Value}" />
        </TabControl.Resources>
        <TabItem Header="Content 1">
            <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                <ContentControl ContentTemplate="{StaticResource ContentTemplate1}" Margin="10" RenderTransformOrigin=".5,.5">
                    <!-- Affects scrollviewer, but does not behave like a zoom -->
                    <!--<FrameworkElement.LayoutTransform>
                        <StaticResource ResourceKey="ScaleTransform" />
                    </FrameworkElement.LayoutTransform>-->

                    <!-- Expected zoom behavior, but doesn't affect scrollviewer -->
                    <FrameworkElement.RenderTransform>
                        <StaticResource ResourceKey="ScaleTransform" />
                    </FrameworkElement.RenderTransform>
                </ContentControl>
            </ScrollViewer>
        </TabItem>
        <!-- Content 2 -->
        <TabItem Header="Content 2">
            <ContentControl ContentTemplate="{StaticResource ContentTemplate2}" Margin="10" RenderTransformOrigin=".5,.5">
                <!-- Affects scrollviewer, but does not behave like a zoom -->
                <!--<FrameworkElement.LayoutTransform>
                        <StaticResource ResourceKey="ScaleTransform" />
                    </FrameworkElement.LayoutTransform>-->

                <!-- Expected zoom behavior, but doesn't affect scrollviewer -->
                <FrameworkElement.RenderTransform>
                    <StaticResource ResourceKey="ScaleTransform" />
                </FrameworkElement.RenderTransform>
            </ContentControl>

        </TabItem>
    </TabControl>

    <StackPanel Grid.Row="1" Orientation="Horizontal">
        <!-- Zoom -->
        <Slider x:Name="ZoomSlider"
                Width="100"
                Maximum="5"
                Minimum="0.1"
                Orientation="Horizontal"
                Value="1" />

        <!-- Autofit -->
        <CheckBox Content="Autofit?" x:Name="AutoFitCheckBox" />
    </StackPanel>
</Grid>

3 个答案:

答案 0 :(得分:1)

如果我理解正确:

  • 您想要使用ZoomSlider滑块进行缩放吗?
  • 如果内容太大而无法容纳在其标签中,您是否希望显示滚动条?

如果是这样,那就是你想要的LayoutTransform。在测量和布局所有元素之前完成转换,ScrollViewer将能够判断是否需要滚动条。

在我的计算机上,如果您只是切换到LayoutTransform“内容1”标签会按预期工作(请注意,在“正确”消失后,您必须进行大量缩放,切换滚动条):

Content 1, un-zoomed Content 1, zoomed

“内容2”需要更多工作。首先,该选项卡中没有ScrollViewer,因此需要添加。其次,ContentTemplate2使用ViewBox,默认情况下会延伸,因此缩放效果不会有效,直到您非常接近放大。要禁用ViewBox的内置“缩放”,您可以将ContentControl容器(使用HorizontalAlignment/VerticalAlignment)居中,这会迫使它占用尽可能少的空间:

<TabItem Header="Content 2">
    <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <ContentControl ContentTemplate="{StaticResource ContentTemplate2}" ...
                        HorizontalAlignment="Center" VerticalAlignment="Center" >
            <FrameworkElement.LayoutTransform>
                ...

Content 2, un-zoomed Content 2, zoomed

答案 1 :(得分:0)

为了使缩放的元素获得精确的RenderTransform外观,我们可以坚持使用RenderTransform,而是通过实现我们自己的滚动逻辑告诉ScrollViewer如何表现。这种方法基于这个优秀的教程:

http://tech.pro/tutorial/907/wpf-tutorial-implementing-iscrollinfo

我们创建自己的自定义"ZoomableContentControl",它实现IScrollInfo并告诉ScrollViewer从那里获取其滚动逻辑(ScrollViewer.CanContentScroll = True)。魔术发生在我ArrangeOverride()ExtentWidth/ExtentHeight的{​​{1}}中。

RenderTransformOrigin

用法:

public class ZoomableContentControl : ContentControl, IScrollInfo
{
    public ZoomableContentControl()
    {
        this.RenderTransformOrigin = new Point(0.5, 0.5);
    }

    private ScaleTransform _scale = null;
    private ScaleTransform Scale
    {
        get
        {
            if (_scale == null)
            {
                _scale = this.RenderTransform as ScaleTransform;

                //RenderTransforms don't update the layout, so we need to trigger that ourselves:
                _scale.Changed += (s, e) => { InvalidateArrange(); };
            }
            return _scale;
        }
    }
    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        Statics.MessageIfDebug("Arranging");
        var layout = base.ArrangeOverride(arrangeBounds);

        var scale = this.Scale;
        if (scale != null)
        {
            //Because RenderTransforms don't update the layout,
            //we need to pretend we're bigger than we are to make room for our zoomed content:
            _extent = new Size(layout.Width * scale.ScaleX, layout.Height * scale.ScaleY);
            _viewport = layout;

            //Coerce offsets..
            var maxOffset = new Vector(ExtentWidth - ViewportWidth, ExtentHeight - ViewportHeight);
            _offset.X = Math.Max(0, Math.Min(_offset.X, maxOffset.X));
            _offset.Y = Math.Max(0, Math.Min(_offset.Y, maxOffset.Y));

            //..and move the zoomed content within the ScrollViewer:
            var renderOffsetX = (maxOffset.X > 0) ? (_offset.X / maxOffset.X) : 0.5;
            var renderOffsetY = (maxOffset.Y > 0) ? (_offset.Y / maxOffset.Y) : 0.5;
            this.RenderTransformOrigin = new Point(renderOffsetX, renderOffsetY);

            if (ScrollOwner != null)
            {
                ScrollOwner.InvalidateScrollInfo();
            }
        }

        return layout;
    }


    #region IScrollInfo

    //This is the boilerplate IScrollInfo implementation, 
    //which can be found in *the first half* of this tutorial:
    //http://tech.pro/tutorial/907/wpf-tutorial-implementing-iscrollinfo
    //(down to and including SetHorizontalOffset()/SetVerticalOffset()).
    //Note the bug reported by "Martin" in the comments.

    ...

答案 2 :(得分:-1)

我的第一个建议是使用已经支持ScrollViewer的商业第三方zoom control检查您可以使用哪种缩放功能,还有许多其他缩放和平移功能。

现在解决你的问题:

您可以使用LayoutTransform使代码工作,但您需要将ScrollViewer内容的大小设置为固定值。

目前,ScrollViewer中有一个Grid。 Grid没有定义其大小,因此它占用了所有空间。因此,如果您现在缩放网格,例如按因子2缩放,这意味着网格的内容按因子2缩放,但网格仍将占用它可以获得的所有空间。如果你将Grid的宽度指定为500,然后将其缩放为2,则会使Grid的宽度为1000.但是如果你说:Grid,你可以占用父级给你的所有空间,然后缩放Grid,它会是一样的。这意味着缩放ScrollViewer的自动调整大小的内容将不会显示滚动条。

在您的示例中,这是正确的,直到Grid的内容(第一列宽度= 150 +第二列中“右”文本的宽度)超过可用大小 - 此时网格的DesiredSize将大于ScrollViewer可以提供的大小,ScrollViewer将显示滚动条。

例如:

1)假设在启动应用程序时,scale设置为1,ScrollViewer为Grid提供500个水平点。网格显示第一列150宽度,并显示“右”文本,没有任何比例。第二列设置为填充剩余空间 - 因此:第二列使用500 - 150 = 350点。

2)现在用户将比例设置为2.网格将第一列缩放为300磅。这意味着第二列现在只能获得200个点。网格还缩放“右”文本,但内容(第一列300或文本宽度)仍然不超过ScrollViewer提供的500点。

3)用户现在将比例设置为3.现在网格内容的总宽度超过500点,这意味着ScrollViewer将显示滚动条。

因此,使用自动调整控件,ScrollViewer和缩放功能不能很好。

但是,如果您将Grid的大小固定为500,那么在缩放和使用ScrollViewer时,您将获得更可预测的结果。例如,如果你缩放10%,网格的大小将是550并且已经超过ScrollViewer的大小 - 所以ScrollViewer会显示滚动条。当你增加窗口的大小时,这也会给你预期的行为 - 网格的大小将保持不变,并且在某些时候滚动条会消失(当窗口大到足以显示窗口的整个内容时缩放网格。

总结:我的建议是将固定大小设置为ScrollViewer控件的内容。如果您有固定大小的窗口,则可以根据该大小设置宽度和高度。否则,您可以在首次加载控件时动态设置它:

您可以将内容控件的XAML更改为:

<ContentControl Name="ContentControl1" 
                ContentTemplate="{StaticResource ContentTemplate1}" 
                Margin="10" 
                Loaded="ContentControl1_OnLoaded" >

并添加ContentControl1_OnLoaded处理,只需将大小设置为初始大小:

private void ContentControl1_OnLoaded(object sender, RoutedEventArgs e)
{
    ContentControl1.Width = ContentControl1.ActualWidth;
    ContentControl1.Height = ContentControl1.ActualHeight;
}

缩放和平移似乎是一项非常简单的任务。但是我的经验表明(我是ZoomPanel控件的作者),这项任务很快就会变得非常复杂。