若是以前看了 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
XAML 對一個互操做 Visual 屬性進行更新的規則以下:
XAML 在佈局過程當中會覆寫互操做 Visual 的屬性值。
XAML 不會讀回由程序代碼直接對互操做 Visual 的屬性賦的值。
XAML 只在新值不等於上次賦的舊值時,纔會對互操做 Visual 的屬性賦值。亦即若是 XAML 一側的屬性值沒有發生變化,則 XAML 不會去更改 Visual 一側的屬性值。
XAML 一側的上次賦值的值默認與互操做 Visual 屬性的默認值一致。也就是說若是 XAML 一側的屬性值保持在默認值不變,則 XAML 不會去更新 Visual 一側的屬性值(例如 XAML 佈局中的 offset,對應 Visual 中的 Visual.Offset,默認值爲 [0,0])。
Visual 一側的屬性值不會覆寫到 XAML 一側。
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,你們拿去用吧。。