WPF缩放&x2B;滚动条?

WPF缩放&x2B;滚动条?,wpf,xaml,zooming,Wpf,Xaml,Zooming,我正在尝试缩放scrollviewer中的一些内容 我正在寻找的缩放行为是RenderTransform+ScaleTransform。但这不适用于ScrollViewer 使用LayoutTransform+ScaleTransform,scrollviewer确实会受到影响(仅限ContentTemplate1),但其行为不像缩放 假设不能更改ContentTemplate1/ContentTemplate2(即,第三方控件),如何让zoom与scrollviewer一起工作 <Gri

我正在尝试缩放scrollviewer中的一些内容

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

使用LayoutTransform+ScaleTransform,scrollviewer确实会受到影响(仅限ContentTemplate1),但其行为不像缩放

假设不能更改ContentTemplate1/ContentTemplate2(即,第三方控件),如何让zoom与scrollviewer一起工作

<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>

如果我理解正确:

  • 是否要使用
    缩放滑块
    进行缩放
  • 如果内容太大,无法放入其选项卡中,是否希望显示滚动条
如果是这样的话,您需要的是
layoututtransform
。这种转换是在所有元素被测量和布局之前完成的,并且
ScrollViewer
将能够判断是否需要滚动条

在我的机器上,如果您只需切换到
LayoutTransform
,则“内容1”选项卡将按预期工作(请注意,在“右”从屏幕上消失之前,您必须进行大量缩放,切换滚动条):

“内容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>
                ...

...

要使缩放的元素获得准确的
渲染转换
外观,我们不妨坚持使用
渲染转换
,而是告诉
滚动查看器
如何实现我们自己的滚动逻辑。此方法基于以下优秀教程:

我们创建了自己的自定义
“ZoomableContentControl”
,它实现了
IScrollInfo
,并告诉
ScrollViewer
从那里获取其滚动逻辑(ScrollViewer.CanContentScroll=True)。魔法发生在
ArrangeOverride()
中,我们在这里玩
extendwidth/ExtentHeight
rendertransferormorigin

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:
    //https://web.archive.org/web/20140809230047/http://tech.pro/tutorial/907/wpf-tutorial-implementing-iscrollinfo
    //(down to and including SetHorizontalOffset()/SetVerticalOffset()).
    //Note the bug reported by "Martin" in the comments.

    ...
用法:

<TabItem Header="Content 1">
    <ScrollViewer CanContentScroll="True"
                  HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
            <v:ZoomableContentControl ContentTemplate="{StaticResource ContentTemplate1}" Margin="10" >
            <FrameworkElement.RenderTransform>
                <StaticResource ResourceKey="ScaleTransform" />
            </FrameworkElement.RenderTransform>
        </v:ZoomableContentControl>
    </ScrollViewer>
</TabItem>
<TabItem Header="Content 2">
    <ScrollViewer CanContentScroll="True"
                  HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <v:ZoomableContentControl ContentTemplate="{StaticResource ContentTemplate2}" Margin="10" >
            <FrameworkElement.RenderTransform>
                <StaticResource ResourceKey="ScaleTransform" />
            </FrameworkElement.RenderTransform>
        </v:ZoomableContentControl>
    </ScrollViewer>
</TabItem>

我的第一个建议是,使用已经支持ScrollViewer并具有许多附加缩放和平移功能的商业第三方,检查您可以使用哪种缩放功能

现在来解决您的问题:

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

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

在您的示例中,直到网格的内容(第一列的宽度=150+第二列中“右”文本的宽度)超过可用的大小,网格的所需大小将大于ScrollViewer可以提供的大小,ScrollViewer将显示滚动条,这才是真的

例如:

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

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

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

因此,使用自动调整大小的控件、ScrollViewer和缩放功能并不能很好地工作

但是,如果将网格的大小固定为500,则在缩放和使用ScrollViewer时,将获得更可预测的结果。例如,如果按10%的比例缩放,网格的大小将为550,并且已经超过ScrollViewer的大小,因此ScrollViewer将显示滚动条。这也会给你预期的行为,当你
<ContentControl Name="ContentControl1" 
                ContentTemplate="{StaticResource ContentTemplate1}" 
                Margin="10" 
                Loaded="ContentControl1_OnLoaded" >
private void ContentControl1_OnLoaded(object sender, RoutedEventArgs e)
{
    ContentControl1.Width = ContentControl1.ActualWidth;
    ContentControl1.Height = ContentControl1.ActualHeight;
}