简易的DragDropCarousel 拖拽轮播控件
上一篇文章有写到 自动轮播的控件 简易的AutoPlayCarousel 轮播控件 - 黄高林 - 博客园 (cnblogs.com)
本章是基于自动轮播的一种衍生,通过拖拽鼠标进切换
直接上代码
DragDropCarousel核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | [ContentProperty(nameof(ItemSources))] [TemplatePart(Name = "PART_StackPanel" , Type = typeof (StackPanel))] public class DragDropCarousel : Control { #region 字段 private StackPanel _stkMain; /// <summary> /// 标记是否点击 /// </summary> private bool _isMouseDown; /// <summary> /// 标记是否动画中,动画过程中不允许操作 /// </summary> private bool _isAnimating; /// <summary> /// 最后一次点击的位置 /// </summary> private Point _lastDownPoint; /// <summary> /// 移动中 /// </summary> private bool _isMoving; #endregion #region 构造 static DragDropCarousel() { DefaultStyleKeyProperty.OverrideMetadata( typeof (DragDropCarousel), new FrameworkPropertyMetadata( typeof (DragDropCarousel))); } public DragDropCarousel() { Loaded += Carousel_Loaded; SizeChanged += Carousel_SizeChanged; } #endregion #region 属性 /// <summary> /// 子控件集合 /// </summary> public ObservableCollection<FrameworkElement> ItemSources { get => (ObservableCollection<FrameworkElement>)GetValue(ItemSourcesProperty); private set => SetValue(ItemSourcesProperty, value); } public static readonly DependencyProperty ItemSourcesProperty = DependencyProperty.Register( "ItemSources" , typeof (ObservableCollection<FrameworkElement>), typeof (DragDropCarousel), new PropertyMetadata( new ObservableCollection<FrameworkElement>())); /// <summary> /// 方向 /// </summary> public Orientation Orientation { get => (Orientation)GetValue(OrientationProperty); set => SetValue(OrientationProperty, value); } public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( "Orientation" , typeof (Orientation), typeof (DragDropCarousel), new PropertyMetadata(Orientation.Horizontal)); /// <summary> /// 下标 /// </summary> public int Index { get => ( int )GetValue(IndexProperty); set => SetValue(IndexProperty, value); } public static readonly DependencyProperty IndexProperty = DependencyProperty.Register( "Index" , typeof ( int ), typeof (DragDropCarousel), new PropertyMetadata(0)); /// <summary> /// 抬起手时,滑动切页动画持续时间 /// </summary> public TimeSpan AnimateDuration { get => (TimeSpan)GetValue(AnimateDurationProperty); set => SetValue(AnimateDurationProperty, value); } public static readonly DependencyProperty AnimateDurationProperty = DependencyProperty.Register( "AnimateDuration" , typeof (TimeSpan), typeof (DragDropCarousel), new PropertyMetadata(TimeSpan.FromSeconds(0.5))); /// <summary> /// 移动阈值 /// </summary> public double MoveThreshold { get => ( double )GetValue(MoveThresholdProperty); set => SetValue(MoveThresholdProperty, value); } // Using a DependencyProperty as the backing store for MoveThreshold. This enables animation, styling, binding, etc... public static readonly DependencyProperty MoveThresholdProperty = DependencyProperty.Register( "MoveThreshold" , typeof ( double ), typeof (DragDropCarousel), new PropertyMetadata(10d)); #endregion #region 对外方法 public override void OnApplyTemplate() { base .OnApplyTemplate(); _stkMain = GetTemplateChild( "PART_StackPanel" ) as StackPanel; } #endregion #region Event Handler private void Carousel_SizeChanged( object sender, SizeChangedEventArgs e) { foreach (FrameworkElement children in ItemSources) { children.Width = ActualWidth; children.Height = ActualHeight; UnRegisterMouseEvent(children); RegisterMouseEvent(children); } } private void UnRegisterMouseEvent(FrameworkElement children) { children.MouseDown -= Children_MouseDown; children.MouseMove -= Children_MouseMove; children.MouseUp -= Children_MouseUp; } private void RegisterMouseEvent(FrameworkElement children) { children.MouseDown += Children_MouseDown; children.MouseMove += Children_MouseMove; children.MouseUp += Children_MouseUp; } private void Children_MouseUp( object sender, MouseButtonEventArgs e) { if (!_isMouseDown || _isAnimating||!_isMoving) {<em id= "__mceDel" > }<br> _isMoving= false ; _isMouseDown = false ; var targetIndex = GetTargetIndex(e, _lastDownPoint); Index = targetIndex; var newThickness = new Thickness(-1 * ActualWidth * targetIndex, 0, 0, 0); var oldThickness = _stkMain.Margin; var thicknessAnimation = new ThicknessAnimation() { From = oldThickness, To = newThickness, Duration = AnimateDuration, EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut }, FillBehavior = FillBehavior.Stop, }; thicknessAnimation.Completed += (s, args) => { _isAnimating = false ; _stkMain.Margin = newThickness; }; _isAnimating = true ; _stkMain.BeginAnimation(MarginProperty, thicknessAnimation); } private int GetTargetIndex(MouseButtonEventArgs e, Point lastDownPoint) { var position = e.GetPosition( this ); var vector = position - lastDownPoint; int targetIndex; if (vector.X < 0) { targetIndex = Index + 1 > (ItemSources.Count - 1) ? Index : Index + 1; } else { targetIndex = Index - 1 >= 0 ? Index - 1 : 0; } return targetIndex; } private void Children_MouseMove( object sender, MouseEventArgs e) { var position = e.GetPosition( this ); if (!IsCanMove(_lastDownPoint, position)) { _stkMain.Margin = new Thickness((-1 * ActualWidth * Index), 0, 0, 0); return ; } var moveLength = _lastDownPoint.X - position.X; _stkMain.Margin = new Thickness((-1 * ActualWidth * Index - moveLength), 0, 0, 0); } private bool IsCanMove(Point lastPoint, Point currentPoint) { if (!_isMouseDown) { return false ; } //以下控制不允许超出边界,想要回弹效果,可以去掉这部分代码 var vector = currentPoint - lastPoint; if (!_isMoving && Math.Abs(vector.X) <= MoveThreshold) { return false ; } _isMoving = true ; if (vector.X < 0) { return Index != ItemSources.Count - 1; } return Index != 0; } private void Children_MouseDown( object sender, MouseButtonEventArgs e) { if (!(MoveElementAttach.GetAllowMove(e.Source as UIElement) ?? false ) || _isAnimating) { _isMouseDown = false ; return ; } _isMouseDown = true ; _lastDownPoint = e.GetPosition( this ); } private void Carousel_Loaded( object sender, RoutedEventArgs e) { if (ItemSources == null ) return ; Loaded -= Carousel_Loaded; foreach (FrameworkElement child in ItemSources) { child.Width = ActualWidth; child.Height = ActualHeight; } } #endregion } </em> |
_isMoving = false;
_isMouseDown = false;
return;
一些辅助的代码
1 2 3 4 5 6 7 8 9 10 11 | public class IndexChangedEventArgs : RoutedEventArgs { public IndexChangedEventArgs( int currentIndex, RoutedEvent routedEvent) : base (routedEvent) { CurrentIndex = currentIndex; } public int CurrentIndex { get ; set ; } } public delegate void IndexChangedEventHandler( object sender, IndexChangedEventArgs e); |
1 2 3 4 5 6 7 8 9 10 11 12 | /// <summary> /// 移动附加属性 /// </summary> public class MoveElementAttach : DependencyObject { /// <summary>允许操作</summary> public static readonly DependencyProperty AllowMoveProperty = DependencyProperty.RegisterAttached( "AllowMove" , typeof ( bool ), typeof (MoveElementAttach), new PropertyMetadata((PropertyChangedCallback) null )); public static bool ? GetAllowMove(DependencyObject obj) => ( bool ?)obj?.GetValue(MoveElementAttach.AllowMoveProperty); public static void SetAllowMove(DependencyObject obj, bool ? value) => obj.SetValue(MoveElementAttach.AllowMoveProperty, ( object )value); } |
默认样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <sys:Double x:Key= "DefaultFontSize" >14</sys:Double> <sys:Boolean x:Key= "DefaultSnapsToDevicePixels" > false </sys:Boolean> <Style TargetType= "{x:Type local:DragDropCarousel}" > <Setter Property= "SnapsToDevicePixels" Value= "{StaticResource DefaultSnapsToDevicePixels}" /> <Setter Property= "FontSize" Value= "{StaticResource DefaultFontSize}" /> <Setter Property= "Template" > <Setter.Value> <ControlTemplate TargetType= "{x:Type local:DragDropCarousel}" > <StackPanel x:Name= "PART_StackPanel" Orientation= "Horizontal" > <ItemsControl x:Name= "PART_ItemsControl" ItemsSource= "{TemplateBinding ItemSources}" VerticalAlignment= "Stretch" HorizontalAlignment= "Stretch" > <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Width= "{TemplateBinding Width}" Height= "{TemplateBinding Height}" Orientation= "{Binding Orientation,RelativeSource={RelativeSource AncestorType=local:DragDropCarousel}}" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> |
界面的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <customControl:DragDropCarousel x:Name= "Carousel" Height= "1080" > <Grid Background= "Red" customControl:MoveElementAttach.AllowMove= "True" > <Grid HorizontalAlignment= "Center" VerticalAlignment= "Center" Height= "500" Width= "500" Background= "Black" > <TextBlock Text= "1" FontSize= "20" Foreground= "Wheat" HorizontalAlignment= "Center" VerticalAlignment= "Center" ></TextBlock> </Grid> </Grid> <Grid Background= "Green" customControl:MoveElementAttach.AllowMove= "True" > <Grid HorizontalAlignment= "Center" VerticalAlignment= "Center" Height= "500" Width= "500" Background= "Black" > <TextBlock Text= "2" FontSize= "20" Foreground= "Wheat" HorizontalAlignment= "Center" VerticalAlignment= "Center" ></TextBlock> </Grid> </Grid> <Grid Background= "Yellow" customControl:MoveElementAttach.AllowMove= "True" > <Grid HorizontalAlignment= "Center" VerticalAlignment= "Center" Height= "500" Width= "500" Background= "Black" > <TextBlock Text= "3" FontSize= "20" Foreground= "Wheat" HorizontalAlignment= "Center" VerticalAlignment= "Center" ></TextBlock> </Grid> </Grid> <Grid Background= "Blue" customControl:MoveElementAttach.AllowMove= "True" > <Grid HorizontalAlignment= "Center" VerticalAlignment= "Center" Height= "500" Width= "500" Background= "Black" > <TextBlock Text= "4" FontSize= "20" Foreground= "Wheat" HorizontalAlignment= "Center" VerticalAlignment= "Center" ></TextBlock> </Grid> </Grid> </customControl:DragDropCarousel> |
通过 customControl:MoveElementAttach.AllowMove="True" 来标记那些控件支持拖拽,哪些不支持拖拽,这种只是临时解决方案,其实还有其他的解决方案。后续我如果要用到再持续更新
标签:
.net、WPF
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!