概述 前面 New UWP Community Toolkit 文章中,我們對 V2.2.0 版本的重要更新做了簡單回顧,其中簡單介紹了 RangeSelector,本篇我們結合代碼詳細講解一下 RangeSelector 相關功能。 RangeSelector 是一種範圍選擇控制項,有兩個滑塊控制項, ...
概述
前面 New UWP Community Toolkit 文章中,我們對 V2.2.0 版本的重要更新做了簡單回顧,其中簡單介紹了 RangeSelector,本篇我們結合代碼詳細講解一下 RangeSelector 相關功能。
RangeSelector 是一種範圍選擇控制項,有兩個滑塊控制項,允許用戶在控制項的取值範圍內選擇一個子區間範圍。在實際應用開發中 RangeSelector 也有著非常廣泛的應用,例如篩選時的價格區間選擇等等。我們來看一下官方示例中的展示:
Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/rangeselector
Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;
開發過程
代碼分析
先來看看 RangeSelector 的結構組成:
- RangeChangedEventArgs.cs - 範圍改變處理事件傳入的參數類,包含了 oldValue,newValue 和 ChangedRangeProperty(標誌 min 和 max 兩個區間值是否改變)
- RangeSelector.cs - RangeSelector 的控制項定義和事件處理類
- RangeSelector.xaml - RangeSelector 的樣式文件
下麵來看一下幾個主要類中的主要代碼實現,因為篇幅關係,我們只摘錄部分關鍵代碼實現:
1. RangeSelector.xaml
RangeSelector.xaml 是 RangeSelector 控制項的樣式文件,我們看到 Template 部分,由一個背景 Border OutOfRangeContentContainer,兩個選擇滑塊 MinThumb 和 MaxThumb,以及顯示當前範圍的矩形 ActiveRectangle 組成;再看 VisualStateManager,我們截取了一部分,在 MinPressed 發生時,MinThumb 被高亮顯示,同理其他狀態發生時也會有對應的視覺狀態發生。
<Style TargetType="controls:RangeSelector"> ... <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="controls:RangeSelector"> <Grid x:Name="ControlGrid" Height="24" > <Grid.Resources> <Style x:Key="SliderThumbStyle" TargetType="Thumb"> ... </Style> </Grid.Resources> <Border x:Name="OutOfRangeContentContainer" Background="Transparent"> ... </Border> <Canvas x:Name="ContainerCanvas" Margin="0,0,8,0" Background="Transparent"> <Rectangle x:Name="ActiveRectangle" Height="2" VerticalAlignment="Center" Fill="{TemplateBinding Foreground}" /> <Thumb x:Name="MinThumb" AutomationProperties.Name="Min thumb" IsTabStop="True" Style="{StaticResource SliderThumbStyle}" TabIndex="0" /> <Thumb x:Name="MaxThumb" AutomationProperties.Name="Max thumb" IsTabStop="True" Style="{StaticResource SliderThumbStyle}" TabIndex="1" /> </Canvas> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="MinPressed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MinThumb" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightChromeHighBrush}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <!-- other visual state --> ... </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
2. RangeSelector.cs
我們先看看 RangeSelector 類的組成:
先來看看類中的依賴屬性:
- Minimum - 控制項允許選擇範圍的最小值,預設是 0.0,修改時觸發 MinimumChangedCallback
- Maximum - 控制項允許選擇範圍的最大值,預設是 1.0,修改時觸發 MaximumChangedCallback
- RangeMin - 控制項實際選擇範圍的最小值,預設是 0.0,修改時觸發 RangeMinChangedCallback
- RangeMax - 控制項實際選擇範圍的最大值,預設是 1.0,修改時觸發 RangeMaxChangedCallback
- IsTouchOptimized - 觸摸優化的標誌,預設是 false,修改時觸發 IsTouchOptimizedChangedCallback
- StepFrequency - 每次調整範圍時的步長,預設是 1.0
我們在其中挑出有代表性的方法詳細看一下:
① MinimumChangedCallback(d, e)
允許範圍最小值調整的回調方法,最大值對應的方法功能類似;當最小值調整後的 newValue 大於等於舊的最大值時,對最大值重新設置為 newValue + 0.01;當 newValue 大於等於實際範圍最小值時,把實際最小值設置為 newValue,當 newValue 大於等於實際範圍最大值時,把實際最大值也設置為 newValue;最後如果 newValue 小於 oldValue 時,需要同步滑塊的位置;
private static void MinimumChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var rangeSelector = d as RangeSelector; if (rangeSelector == null || !rangeSelector._valuesAssigned) { return; } var newValue = (double)e.NewValue; var oldValue = (double)e.OldValue; if (rangeSelector.Maximum < newValue) { rangeSelector.Maximum = newValue + Epsilon; } if (rangeSelector.RangeMin < newValue) { rangeSelector.RangeMin = newValue; } if (rangeSelector.RangeMax < newValue) { rangeSelector.RangeMax = newValue; } if (newValue < oldValue) { rangeSelector.SyncThumbs(); } }
② RangeMinChangedCallback(d, e)
實際範圍最小值調整的回調方法,最大值對應的方法功能類似;根據步長來對 newValue 做矯正,比如 oldValue = 0.0,newValue = 0.11,步長 0.1,那麼 newValue 會調整為 0.1;然後是對 newValue 超出允許選擇範圍時的邊界處理;最後實際選擇範圍修改時,需要同步調整顯示實際範圍的矩形控制項的狀態;
private static void RangeMinChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { var rangeSelector = d as RangeSelector; if (rangeSelector == null) { return; } rangeSelector._minSet = true; if (!rangeSelector._valuesAssigned) { return; } var newValue = (double)e.NewValue; rangeSelector.RangeMinToStepFrequency(); if (rangeSelector._valuesAssigned) { if (newValue < rangeSelector.Minimum) { rangeSelector.RangeMin = rangeSelector.Minimum; return; } if (newValue > rangeSelector.Maximum) { rangeSelector.RangeMin = rangeSelector.Maximum; return; } rangeSelector.SyncActiveRectangle(); if (newValue > rangeSelector.RangeMax) { rangeSelector.RangeMax = newValue; } } else { rangeSelector.SyncActiveRectangle(); } }
③ IsTouchOptimizedChangedCallback(d, e)
當觸摸優化變化時,控制項也會做出變化,實際處理方法是 ArrangeForTouch();我們看到,在觸摸優化後,滑塊的寬高被設置為 44,對應的範圍顯示也會變大;而在非觸摸優化時,控制項整體會變小,變為滑鼠點擊時的樣式;因為實現了觸摸優化,所以我們可以根據當前設備是否是平板模式,來決定控制項的顯示狀態,非常有用。
private void ArrangeForTouch() { if (_containerCanvas == null) { return; } if (IsTouchOptimized) { ... if (_minThumb != null) { _minThumb.Width = _minThumb.Height = 44; _minThumb.Margin = new Thickness(-20, 0, 0, 0); } ... } else { ... if (_minThumb != null) { _minThumb.Width = 8; _minThumb.Height = 24; _minThumb.Margin = new Thickness(-8, 0, 0, 0); } ... } }
下麵的幾個方法,主要處理的是 rangeMin 和 rangeMax 兩個滑塊的拖拽事件,我們還是只看 min 對應的處理,max 處理類似:
根據當前滑塊拖拽後的位置,來修改實際範圍最小值;計算方式就是根據允許範圍區間,控制項實際寬度,以及當前位置距離最小值的距離,來計算出比例;
private void MinThumb_DragDelta(object sender, DragDeltaEventArgs e) { _absolutePosition += e.HorizontalChange; RangeMin = DragThumb(_minThumb, 0, Canvas.GetLeft(_maxThumb), _absolutePosition); } private double DragThumb(Thumb thumb, double min, double max, double nextPos) { nextPos = Math.Max(min, nextPos); nextPos = Math.Min(max, nextPos); Canvas.SetLeft(thumb, nextPos); return Minimum + ((nextPos / _containerCanvas.ActualWidth) * (Maximum - Minimum)); }
而在滑塊拖拽開始和結束時,以及可用狀態變化時,也會觸發對應的 VisualStateManager 的 state 來調整控制項視覺顯示狀態;
調用示例
我們定義了一個 RangeSelector 控制項,在左右兩側顯示當前選擇範圍的最小值和最大值,而控制項的可選範圍區間是 0~100,可以看到示例運行圖的顯示:
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"/> <ColumnDefinition/> <ColumnDefinition Width="50"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="Black" Text="{Binding RangeMin, ElementName=RangeSelectorControl}" /> <controls:RangeSelector Grid.Column="1" x:Name="RangeSelectorControl" Minimum="0" Maximum="100" StepFrequency="1"/> <TextBlock Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="Black" Text="{Binding RangeMax, ElementName=RangeSelectorControl}" /> </Grid>
總結
到這裡我們就把 UWP Community Toolkit 中的 RangeSelector 控制項的源代碼實現過程和簡單的調用示例講解完成了,希望能對大家更好的理解和使用這個控制項有所幫助,大家也可以在實際應用中,編寫更豐富的控制項樣式,或者更特殊的範圍選擇,比如環形等。歡迎大家多多交流,謝謝!
最後,再跟大家安利一下 UWPCommunityToolkit 的官方微博:https://weibo.com/u/6506046490, 大家可以通過微博關註最新動態。
衷心感謝 UWPCommunityToolkit 的作者們傑出的工作,Thank you so much, UWPCommunityToolkit authors!!!