上一回實現了一個寬度不均勻的Panel,這次我們編寫一個簡單的BigbangView主體。 首先創建一個模板化控制項,刪掉Themes/Generic.xaml中的<Style TargetType="BigbangView">...</Style>段。 然後打開C:\Program Files (x ...
上一回實現了一個寬度不均勻的Panel,這次我們編寫一個簡單的BigbangView主體。
首先創建一個模板化控制項,刪掉Themes/Generic.xaml中的<Style TargetType="BigbangView">...</Style>段。
然後打開C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\(SDK版本)\Generic\generic.xaml,在裡面找到
<Style TargetType="ListViewItem" x:Key="ListViewItemExpanded"> ... </Style> <Style TargetType="ListView"> ... </Style>
這兩段,複製到項目中Themes/Generic.xaml中,將TargetType="ListView"修改為TargetType="BigbangView",添加Setter:
<Setter Property="SelectionMode" Value="Multiple"></Setter> <Setter Property="HorizontalAlignment" Value="Stretch"></Setter> <Setter Property="VerticalAlignment" Value="Center"></Setter> <Setter Property="IsTabStop" Value="False" /> <Setter Property="TabNavigation" Value="Once" /> <Setter Property="IsSwipeEnabled" Value="True" /> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" /> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" /> <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled" /> <Setter Property="ScrollViewer.IsHorizontalRailEnabled" Value="False" /> <Setter Property="ScrollViewer.VerticalScrollMode" Value="Enabled" /> <Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="True" /> <Setter Property="ScrollViewer.ZoomMode" Value="Disabled" /> <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" /> <Setter Property="ScrollViewer.BringIntoViewOnFocusChange" Value="True" /> <Setter Property="UseSystemFocusVisuals" Value="True" /> <Setter Property="ItemContainerTransitions"> <Setter.Value> <TransitionCollection> <AddDeleteThemeTransition /> <ContentThemeTransition /> <ReorderThemeTransition /> <EntranceThemeTransition IsStaggeringEnabled="False" /> </TransitionCollection> </Setter.Value> </Setter> <Setter Property="ItemContainerStyle"> <Setter.Value> <Style TargetType="ListViewItem"> 前面複製的ListViewItemExpanded的內容剪貼到這裡 </Style> </Setter.Value> </Setter> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <control:BigbangPanel > <control:BigbangPanel.ChildrenTransitions> <TransitionCollection> <AddDeleteThemeTransition /> </TransitionCollection> </control:BigbangPanel.ChildrenTransitions> </control:BigbangPanel> </ItemsPanelTemplate> </Setter.Value> </Setter>View Code
其中BigbangPanel是咱們上回書寫的面板。
然後打開BigbangView.cs,修改基類:
public sealed class BigbangView : ListView { public BigbangView() { this.DefaultStyleKey = typeof(BigbangView); } }
接下來就是整個過程中最複雜,最枯燥的部分,編寫按下滑動選中。
先打開安卓版的大爆炸(不是錘子的可以拿個安卓手機下載IT之家客戶端看效果),對整個過程進行分析發現,有以下幾種狀態。
1、點擊選中;
2、Panel高度小於控制項高度,也就是ScrollViewer不啟用時,按下向四周滑動可以更改選中狀態;
3、Panel高度大於控制項高度,上下滑動可以滾動ScrollViewer,左右滑動禁用ScrollViewer的滾動並且更改選中狀態 。
這篇文章先實現滑鼠的操作。由於滑鼠在頁面上下滑動並不會觸發ScrollViewer的滾動,所以在此不考慮第三項。
首先修改Style中的Template段,
<Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="control:BigbangView"> <Border x:Name="RootBorder" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"> <ScrollViewer x:Name="ScrollViewer" TabNavigation="{TemplateBinding TabNavigation}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" AutomationProperties.AccessibilityView="Raw"> <Grid x:Name="ItemsGrid" Background="Transparent" ManipulationMode="System"> <ItemsPresenter Header="{TemplateBinding Header}" HeaderTemplate="{TemplateBinding HeaderTemplate}" HeaderTransitions="{TemplateBinding HeaderTransitions}" Footer="{TemplateBinding Footer}" FooterTemplate="{TemplateBinding FooterTemplate}" FooterTransitions="{TemplateBinding FooterTransitions}" Padding="{TemplateBinding Padding}"/> </Grid> </ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter>View Code
在後臺代碼中重載OnApplyTemplate()(實際應該做空判斷,我偷懶了)
public BigbangView() { this.DefaultStyleKey = typeof(BigbangView); this.Loaded += BigbangView_Loaded; this.Unloaded += BigbangView_Unloaded; this.SelectionChanged += BigbangView_SelectionChanged; PointerPressedHandler = new PointerEventHandler(_PointerPressed); PointerReleasedHandler = new PointerEventHandler(_PointerReleased); PointerMovedHandler = new PointerEventHandler(_PointerMoved); } protected override void OnApplyTemplate() { base.OnApplyTemplate(); RootBorder = GetTemplateChild("RootBorder") as Border; ItemsGrid = GetTemplateChild("ItemsGrid") as Grid; ScrollViewer = GetTemplateChild("ScrollViewer") as ScrollViewer; ScrollViewer.AddHandler(UIElement.PointerPressedEvent, PointerPressedHandler, true); ScrollViewer.AddHandler(UIElement.PointerReleasedEvent, PointerReleasedHandler, true); ScrollViewer.AddHandler(UIElement.PointerCanceledEvent, PointerReleasedHandler, true); ScrollViewer.AddHandler(UIElement.PointerExitedEvent, PointerReleasedHandler, true); ScrollViewer.ViewChanging += _ScrollViewer_ViewChanging; }
然後我們需要把每個子元素在Panel中的位置緩存下來,在Panel中添加屬性
private Dictionary<UIElement, Rect> _ChildrenRects; public Dictionary<UIElement, Rect> ChildrenRects { get { if (_ChildrenRects == null) _ChildrenRects = new Dictionary<UIElement, Rect>(); return _ChildrenRects; } set => _ChildrenRects = value; }
將ArrangeOverride中Children[x].Arrange(new Rect...)修改為如下
var rect = new Rect(x, y, Children[i].DesiredSize.Width, Children[i].DesiredSize.Height); Children[i].Arrange(rect); ChildrenRects[Children[i]] = rect;
編寫以下幾個輔助的方法:
//從Item獲取在Panel中的佈局信息 private Rect? GetItemRect(object Item) { var itemContainer = base.ContainerFromItem(Item) as UIElement; if (itemContainer != null && _Panel != null) { if (_Panel.ChildrenRects.ContainsKey(itemContainer)) { return _Panel.ChildrenRects[itemContainer]; } } return null; } //從容器獲取Item和Index private int GetIndexFromContainer(UIElement Container, out object Item, IList<object> SourceList = null) { Item = ItemFromContainer(Container); if (Item != null) { if (SourceList == null) SourceList = GetSourceList(); return SourceList.IndexOf(Item); } return -1; } //獲取坐標位置的Item和Index private int GetIndexFromPosition(Point Position, out object Item) { var sourceList = GetSourceList(); for (int i = 0; i < sourceList.Count; i++) { var rect = GetItemRect(sourceList[i]); if (!rect.HasValue) break; if (rect.Value.Contains(Position)) { Item = sourceList[i]; return i; } } Item = null; return -1; } //獲取源列表 private IList<object> GetSourceList() { if (ItemsSource == null) return Items.ToList(); else { var tmp = new List<object>(); foreach (var item in (IEnumerable)ItemsSource) { tmp.Add(item); } return tmp; } }
然後編寫三個狀態方法:OnSelectionStart初始化各個變數的狀態,獲取開始的點;OnSelecting更新被選中的項,OnSelectionComplate做最後的清理,還原狀態:
int StartIndex = -1; //本次選擇開始的位置 int EndIndex = -1; //本次選擇結束的位置 bool? IsFirstItemHadSelected; //本次選擇是選中後續還是取消選中後續 Point? StartPoint; //選中開始的坐標 UIElement StartContainer; //選擇開始時的容器 private void OnSelectionStarted(Point Position) { var tmpIndex = GetIndexFromPosition(Position, out var item); if (tmpIndex == -1) { return; } if (StartIndex < 0) { StartIndex = tmpIndex; IsFirstItemHadSelected = SelectedItems.Contains(item); StartContainer = ContainerFromItem(item) as UIElement; } } private void OnSelecting(UIElement Container) { if (IsFirstItemHadSelected.HasValue) { var sourceList = GetSourceList(); var tmpIndex = GetIndexFromContainer(Container, out var item, sourceList); if (tmpIndex == -1) return; if (StartIndex < 0) { StartIndex = tmpIndex; return; } else { EndIndex = tmpIndex; if (EndIndex >= 0) { for (int i = Math.Min(StartIndex, EndIndex); i <= Math.Max(StartIndex, EndIndex); i++) { if (IsFirstItemHadSelected.Value) { if (SelectedItems.Contains(sourceList[i])) SelectedItems.Remove(sourceList[i]); } else { if (!SelectedItems.Contains(sourceList[i])) SelectedItems.Add(sourceList[i]); } } } } } } private void OnSelectionComplated() { StartIndex = -1; EndIndex = -1; IsFirstItemHadSelected = null; StartPoint = null; StartContainer = null; }
然後編寫事件:
private void Container_PointerEntered(object sender, PointerRoutedEventArgs e) { if (IsFirstItemHadSelected.HasValue) { if (sender is UIElement ele && ele != StartContainer) { if (StartContainer == null) { StartContainer = ele; StartIndex = GetIndexFromContainer(StartContainer, out var item); } else { OnSelecting(ele); } } } } private void _PointerPressed(object sender, PointerRoutedEventArgs e) { StartPoint = e.GetCurrentPoint(ItemsGrid).Position; OnSelectionStarted(StartPoint.Value); IsSwipeEnable = true; } private void _PointerReleased(object sender, PointerRoutedEventArgs e) { if (IsSwipeEnable.HasValue) { OnSelectionComplated(); } }
至此,BigbangView已經可以響應滑鼠的滑動選擇。
下回預告:BigbangView響應觸摸。