UWP Composition API - New FlexGrid 锁定行列

若是以前看了 UWP Jenkins + NuGet + MSBuild 手把手教你作自动UWP Build 和 App store包html

这篇的童鞋,针对VS2017,须要对应更新一下配置,须要的童鞋点击查看一下,在文章最后。前端

 

以前写过一篇 锁定列的FlexGrid,没看过的童鞋能够去先看一下那一篇。git

先放上效果图github

制做新控件的背景是SDK升级到了14393,Composition API 有了相应的改变windows

对咱们有较大的影响就是:app

10586:
在 10586 版 SDK 中, ElementCompositionPreview.GetElementVisual 方法返回的 Visual 仅由调用者控制。经过对应的 Visual 对一个 UIElement 进行的操做纯粹只会对 XAML 施加增量影响。这是由于返回的 Visual (在底层)是 UIElement 的 Visual 做为根 Visual 的子项。ide

14393:布局

在 14332 版及后续版本中, ElementCompositionPreview.GetElementVisual 方法返回的 Visual与 XAML 布局操做的 Visual 相同。这意味着不一样于 11 月更新,如今经过对应的 Visual 对一个 UIElement 元素进行的操做会绝对地改变 XAML 布局。post

由于调用者和 XAML 都在操做同一个 Visual,XAML 有可能会覆盖调用者的赋值。如下是 XAML 可能设置的属性:flex

  • Offset
  • Size
  • Opacity
  • TransformMatrix
  • Clip
  • CompositeMode

XAML 对一个互操做 Visual 属性进行更新的规则以下:

  1. XAML 在布局过程当中会覆写互操做 Visual 的属性值。

  2. XAML 不会读回由程序代码直接对互操做 Visual 的属性赋的值。

  3. XAML 只在新值不等于上次赋的旧值时,才会对互操做 Visual 的属性赋值。亦即若是 XAML 一侧的属性值没有发生变化,则 XAML 不会去更改 Visual 一侧的属性值。

  4. XAML 一侧的上次赋值的值默认与互操做 Visual 属性的默认值一致。也就是说若是 XAML 一侧的属性值保持在默认值不变,则 XAML 不会去更新 Visual 一侧的属性值(例如 XAML 布局中的 offset,对应 Visual 中的 Visual.Offset,默认值为 [0,0])。

  5. Visual 一侧的属性值不会覆写到 XAML 一侧。

  6. UI 元素最终呈现的效果取决于最后生效的值(Visual 一侧的取值或 XAML 覆写 Visual 的值)。

总的来说就是之前Visual 是绝对由我来控制,而如今XAML也会共同影响Visual 的最终值。

 

这样一搞,宝宝就不开心了,直接把之前的项目升级到14393,FlexGrid各类问题。

秉着吐槽不如本身动手的心情,让咱们本身建立New FlexGird

首先,咱们来看一下整个New FlexGird的构成,整个控件是一个ListView,头(Column Header 和 锁定的行)都放在ListView的ScrollViewer的TopHeader里面。

下面是整个New FlexGird的模板。

 

 <ControlTemplate TargetType="local:NewFlexGrid">
                    <Border x:Name="RootBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                        <ScrollViewer x:Name="ScrollViewer" Style="{StaticResource FlexGridScrollViewerStyle}" AutomationProperties.AccessibilityView="Raw" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" TabNavigation="{TemplateBinding TabNavigation}" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}">
                            <ScrollViewer.TopHeader>
                                <StackPanel Orientation="Vertical" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
                                    <local:NewFlexGridColumnHeader x:Name="ColumnHeader" FrozenCount="{TemplateBinding ColumnHeaderFrozenCount}" SelectionMode="None" IsItemClickEnabled="True" Style="{StaticResource NoScrollViewerListViewStyle}" ItemsSource="{TemplateBinding ColumnsHeaderItemsSource}" ItemTemplate="{TemplateBinding ColumnsHeaderItemTemplate}">
                                        <local:NewFlexGridColumnHeader.ItemsPanel>
                                            <ItemsPanelTemplate>
                                                <StackPanel Orientation="Horizontal"/>
                                            </ItemsPanelTemplate>
                                        </local:NewFlexGridColumnHeader.ItemsPanel>
                                    </local:NewFlexGridColumnHeader>
                                    <local:NewFlexGridFrozenRows x:Name="FrozenRows" ItemTemplate="{TemplateBinding ItemTemplate}" ItemsSource="{TemplateBinding FrozenRowsItemsSource}" IsItemClickEnabled="True" SelectionMode="None" Style="{StaticResource NoScrollViewerListViewStyle}" ItemContainerStyle="{TemplateBinding ItemContainerStyle}">
                                        <local:NewFlexGridFrozenRows.ItemsPanel>
                                            <ItemsPanelTemplate>
                                                <StackPanel Orientation="Vertical"/>
                                            </ItemsPanelTemplate>
                                        </local:NewFlexGridFrozenRows.ItemsPanel>
                                    </local:NewFlexGridFrozenRows>
                                </StackPanel>
                            </ScrollViewer.TopHeader>

                            <ItemsPresenter HorizontalAlignment="Left"  VerticalAlignment="Top" FooterTransitions="{TemplateBinding FooterTransitions}" FooterTemplate="{TemplateBinding FooterTemplate}" Footer="{TemplateBinding Footer}"  Padding="{TemplateBinding Padding}"/>
                            <!--HeaderTemplate="{TemplateBinding HeaderTemplate}" Header="{TemplateBinding Header}" HeaderTransitions="{TemplateBinding HeaderTransitions}"-->
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>

在获取到ScrollViewer元素以及New FlexGird Loaded的事件当中,咱们须要准备Composition 的元素

        private void PrepareCompositionAnimation()
        {
            if (_scrollViewer != null)
            {
                if (_scrollerViewerManipulation == null)
                {
                    _scrollerViewerManipulation = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(_scrollViewer);

                }
                if (_offsetXAnimation == null)
                {
                    _offsetXAnimation = _scrollerViewerManipulation.Compositor.CreateExpressionAnimation("-min(0,ScrollManipulation.Translation.X)");
                    _offsetXAnimation.SetReferenceParameter("ScrollManipulation", _scrollerViewerManipulation);
                    _columnsHeader._offsetXAnimation = _offsetXAnimation;
                    _frozenRows._offsetXAnimation = _offsetXAnimation;
                }
            }
        }

看过以前几遍的Composition 相关文章的童鞋应该知道这是在作什么,不知道的童鞋请先看一下UWP Composition API - PullToRefresh

NewFlexGridColumnHeader 是一个横向的ListView,它没有ScrollViewer,经过New FlexGird中的ColumnHeaderFrozenCount/ColumnsHeaderItemsSource/ColumnsHeaderItemTemplate属性进行关联。

在NewFlexGridColumnHeader 的PrepareContainerForItemOverride方法中,咱们使用前面准备好的_offsetXAnimation,让符合条件的(具体就是第几个Column header)执行动画。

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
            int index = this.IndexFromContainer(element);
            if (index > -1 && index < FrozenCount && _offsetXAnimation != null)
            {
                Canvas.SetZIndex((element as UIElement), 10);
                var _frozenContentVisual = ElementCompositionPreview.GetElementVisual(element as UIElement);

                _frozenContentVisual.StartAnimation("Offset.X", _offsetXAnimation);
            }
        }

 

NewFlexGridFrozenRows 是一个竖向的ListView,它也是没有ScrollViewer,用于存放锁定的行,经过New FlexGird中的FrozenRowsItemsSource/ItemTemplate/ItemContainerStyle等属性关联。

在NewFlexGridFrozenRows 的PrepareContainerForItemOverride方法中,咱们主要作的是注册NewFlexGridFrozenRows_Loaded 事件,

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
            var flexGridItem = element as ListViewItem;
            flexGridItem.RightTapped -= FlexGridItem_RightTapped;
            flexGridItem.Holding -= FlexGridItem_Holding;
            flexGridItem.RightTapped += FlexGridItem_RightTapped;
            flexGridItem.Holding += FlexGridItem_Holding;
            flexGridItem.Loaded += NewFlexGridFrozenRows_Loaded;
        }

当Item Loaded的时候,咱们将以前准备好的_offsetXAnimation,经过咱们定义一个附件属性来得知是哪一个元素须要作Frozen的动画。

        private void NewFlexGridFrozenRows_Loaded(object sender, RoutedEventArgs e)
        {
            (sender as ListViewItem).Loaded -= NewFlexGridFrozenRows_Loaded;

            var templateRoot = (sender as ListViewItem).ContentTemplateRoot;

            var child = templateRoot.GetAllChildren();
            var _frozenContent = child.Where(x => FlexGridItemFrozenContent.GetIsFrozenContent(x));
            if (_frozenContent != null && _offsetXAnimation != null)
            {
                foreach (var item in _frozenContent)
                {
                    var _frozenContentVisual = ElementCompositionPreview.GetElementVisual(item);

                    _frozenContentVisual.StartAnimation("Offset.X", _offsetXAnimation);

                }
            }
        }

在咱们New FlexGird 也跟NewFlexGridFrozenRows 当中同样的操做。

在Unloaded事件中咱们要释放掉一些资源防止内存泄漏,而且在合适的时机去释放掉所有的Composition 资源(见Dispose 方法)

        private void NewFlexGrid_Unloaded(object sender, RoutedEventArgs e)
        {
            if (_offsetXAnimation != null)
            {
                _offsetXAnimation.Dispose();
                _offsetXAnimation = null;
            }

            //don't dispose at this moment,some page NavigationCacheMode is required
            //you must dispose it at page back.
            //if (_scrollerViewerManipulation != null)
            //{
            //    _scrollerViewerManipulation.Dispose();
            //    _scrollerViewerManipulation = null;
            //}
        }
        public void Dispose()
        {
            if (_offsetXAnimation != null)
            {
                _offsetXAnimation.Dispose();
                _offsetXAnimation = null;
            }

            if (_scrollerViewerManipulation != null)
            {
                _scrollerViewerManipulation.Dispose();
                _scrollerViewerManipulation = null;
            }
        }

在咱们的New FlexGird的ItemTemplate里面定义好锁定的列(蓝色部分)

   <DataTemplate x:Key="WideScreenItemTemplate">
            <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="110"/>
                    <ColumnDefinition Width="110" />
                    <ColumnDefinition Width="110" />
                    <ColumnDefinition Width="110" />
                    <ColumnDefinition Width="110" />
                    <ColumnDefinition Width="110" />
                    <ColumnDefinition Width="110" />
                </Grid.ColumnDefinitions>
                <Grid Background="Green" Width="110"  flexgrid:FlexGridItemFrozenContent.IsFrozenContent="True">
                    <TextBlock Text="{Binding Age}" />
                </Grid>
                <TextBlock Text="{Binding Name}" Grid.Column="1"/>
                <TextBlock Text="{Binding IsMale}" Grid.Column="2"/>
                <Grid Background="Yellow" Width="110"  Grid.Column="3" >
                    <TextBlock Text="{Binding Age}" />
                </Grid>
                <TextBlock Text="{Binding Name}" Grid.Column="4"/>
                <TextBlock Text="{Binding IsMale}" Grid.Column="5"/>
                <TextBlock Text="{Binding Name}" Grid.Column="6"/>
            </Grid>
        </DataTemplate>

 ok,运行起来就实现了锁定行列。

在使用当中,可能有童鞋发现,还有一些其余问题。

1.锁定的列或者行,因为是透明背景,无法盖住移动的部分,解决办法是 给你要锁定的列元素加上 Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"

2.Pointer over的样式,因为1里面加了不透明的色,这部分会挡住ListViewitem PointerOver的颜色,解决办法是
重写ListviewItem的样式,由微软文档可知道,ListViewItem有2种模板

咱们这里把第2种模板重写一下,将模板里面的PointerOverBorder元素上移动到ContentBorder之上(就是Xaml里面把它移动到ContentBorder后面),具体的模板

请查看NewFlexGridItemStyle

3.当Pointer press下去的时候,冻结的列的前端会显示出来它后面挡住的内容,解决办法是

  <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" flexgrid:FlexGridItemFrozenContent.IsFrozenContent="True">
                    <StackPanel Margin="-15,0,0,0" Padding="15,4,0,4" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" VerticalAlignment="Center">
                        <TextBlock Text="{x:Bind Name, Mode=OneWay}"}"
                               TextTrimming="CharacterEllipsis"/>
                    </StackPanel>
                </Border>

将用于挡住后面内容的色块,这里是StackPanel,加上一个 负X(-15)的Margin。那你们可能会问为何不加在Border上面,根据14393 SDK的更新中,若是做为Visual 的内容具备初始的位置属性的话,这种会影响到Visual 的最终值。你们能够试试给加了flexgrid:FlexGridItemFrozenContent.IsFrozenContent="True" 的元素加上一些影响位置的属性设置,都会致使最终动画的无效。

 

不知道Creators Update里面会不会其余变化,好像有了新的API,等我研究好了,再发给你们看看。

最后开源有益:New FlexGird,你们拿去用吧。。

相关文章
相关标签/搜索