UWP Composition API - New FlexGrid 锁定行列

原文:UWP Composition API - New FlexGrid 锁定行列

如果之前看了 UWP Jenkins + NuGet + MSBuild 手把手教你做自动UWP Build 和 App store包

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

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

先放上效果图

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

对我们有较大的影响就是:

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

14393:

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

因为调用者和 XAML 都在操作同一个 Visual,XAML 有可能会覆盖调用者的赋值。以下是 XAML 可能设置的属性:

  • 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,大家拿去用吧。。

原文地址:https://www.cnblogs.com/lonelyxmas/p/8548561.html

时间: 2024-11-06 09:31:48

UWP Composition API - New FlexGrid 锁定行列的相关文章

UWP Composition API - GroupListView(一)

需求: 光看标题大家肯定不知道是什么东西,先上效果图: 这不就是ListView的Group效果吗?? 看上去是的.但是请听完需求.1.Group中的集合需要支持增量加载ISupportIncrementalLoading 2.支持UI Virtualization oh,no.ListView 自带的Group都不支持这2个需求.好吧,只有靠自己撸Code了.. 实现前思考: 仔细想了下,其实要解决的主要问题有2个数据源的处理 和 GroupHeader的UI的处理 1.数据源的处理  因为之

UWP Composition API - GroupListView(二)

还是先上效果图: 看完了上一篇UWP Composition API - GroupListView(一)的童鞋会问,这不是跟上一篇一样的吗??? 骗点击的?? No,No,其实相对上一个有更简单粗暴的方案,因为上篇是为了研究Composition API,所以含着泪都要做完(有没有被骗的赶脚)..( ╯□╰ ) 那是有没有简单点的方法呢?? 嗯,看到这篇,那答案肯定是Yes. 我再啰嗦下需求: 1.Group中的集合需要支持增量加载ISupportIncrementalLoading 2.支持

UWP Composition API - RadialMenu

原文:UWP Composition API - RadialMenu 用Windows 8.1的童鞋应该知道OneNote里面有一个RadialMenu.如下图,下图是WIn10应用Drawboard PDF的RadialMenu,Win8.1的机器不好找了.哈哈,由于整个文章比较长,大家可以放<给我一首歌的时间> 边听边看.<滑稽> 从设计到开发包括修复一些bug,大概用了不连续的2个月,想看源代码的童鞋可以先到 RadialMenu 查看效果和代码. 先放上项目里面的最终效果

UWP Composition API - PullToRefresh

背景: 之前用ScrollViewer 来做过 PullToRefresh的控件,在项目一些特殊的条件下总有一些问题,比如ScrollViewer不会及时到达指定位置.于是便有了使用Composition API来重新实现PullToRefresh控件.本控件的难点不是实现,而是对Composition API的一些探索. 本文的一些观点或者说结论不一定是全对的,都是通过实验得到的,Composition API 可用的资料实在是太少了. 成品效果图: 资料: Composition API 资

UWP中使用Composition API实现吸顶(2)

原文:UWP中使用Composition API实现吸顶(2) 在上一篇中我们讨论了不涉及Pivot的吸顶操作,但是一般来说,吸顶的部分都是Pivot的Header,所以在此我们将讨论关于Pivot多个Item关联同一个Header的情况. 老样子,先做一个简单的页面,页面有一个Grid当Header,一个去掉了头部的Pivot,Pivot内有三个ListView,ListView设置了和页面Header高度一致的空白Header. <Page x:Class="TestListViewH

Vue3.0基于Proxy 实现的数据更变检测 支持Composition API和Options API,以及typings

Composition API 纯函数式 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"></div> <script src="vue

Delphi Windows API判断文件共享锁定状态

一.概述 锁是操作系统为实现数据共享而提供的一种安全机制,它使得不同的应用程序,不同的计算机之间可以安全有效地共享和交换数据.要保证安全有效地操作共享数据,必须在相应的操作前判断锁的类型,然后才能确定数据是否可读或可写,从而为开发出健壮的程序提供切实依据.   同样,在Windows中,文件可以共享模式打开,它也涉及到锁的操作问题.根据Windows中文件共享时加锁范围的大小,锁可分为全局锁和局部锁:全局锁以锁定文件全部内容为特征,而局部锁以锁定文件的局部内容为特征,且文件的锁定区域不可重复.根

【Win 10 应用开发】UI Composition 札记(四):绘制图形

使用 Win 2D 组件,就可以很轻松地绘制各种图形,哪怕你没有 D2D 相关基础,也不必写很复杂的 C++ 代码. 先来说说如何获取 Win 2D 组件.很简单,创建 UWP 应用项目后,你打开“解决方案资源管理器”窗口,然后在[引用]节点上右击,从快捷菜单中选择[管理 Nuget 程序包]命令,在打开的窗口中搜索“Win 2D”,然后安装带有 uwp 标识的那个就可以了. 顺便说一下,nuget 的包缓存在你的用户文件夹下,就是系统盘下的 \users\xxx,xxx是你登录系统的用户名,在

【Win 10 应用开发】UI Composition 札记(一):视图框架的实现

在开始今天的内容之前,老周先说一个问题,这个问题记得以前有人提过的. 设置 Windows.ApplicationModel.Core.CoreApplicationView.TitleBar.ExtendViewIntoTitleBar 属性可以让应用窗口中的内容扩展到标题栏.简单地说,就是你的UI区域可以扩大,并填充到标题栏,这在开发自定义标题栏或弄个什么毛玻璃效果时很有用. 不过,这个 ExtendViewIntoTitleBar 属性有个“八阿哥”,一旦你设置之后,系统会对其进行记录,很