Carousel.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.Specialized;
  5. using System.ComponentModel;
  6. using System.Linq;
  7. using System.Windows;
  8. using System.Windows.Controls;
  9. using System.Windows.Controls.Primitives;
  10. using System.Windows.Input;
  11. using System.Windows.Markup;
  12. using System.Windows.Threading;
  13. using HandyControl.Data;
  14. using HandyControl.Interactivity;
  15. using HandyControl.Tools;
  16. namespace HandyControl.Controls;
  17. /// <summary>
  18. /// 轮播控件
  19. /// </summary>
  20. [DefaultProperty("Items")]
  21. [ContentProperty("Items")]
  22. [TemplatePart(Name = ElementPanelPage, Type = typeof(Panel))]
  23. public class Carousel : SimpleItemsControl, IDisposable
  24. {
  25. #region Constants
  26. private const string ElementPanelPage = "PART_PanelPage";
  27. #endregion Constants
  28. #region Data
  29. private bool _isDisposed;
  30. private Panel _panelPage;
  31. private bool _appliedTemplate;
  32. private int _pageIndex = -1;
  33. private RadioButton _selectedButton;
  34. private DispatcherTimer _updateTimer;
  35. private readonly List<double> _widthList = new();
  36. private readonly Dictionary<object, CarouselItem> _entryDic = new();
  37. private bool _isRefresh;
  38. private IEnumerable _itemsSourceInternal;
  39. #endregion Data
  40. public override void OnApplyTemplate()
  41. {
  42. _appliedTemplate = false;
  43. _panelPage?.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ButtonPages_OnClick));
  44. base.OnApplyTemplate();
  45. _panelPage = GetTemplateChild(ElementPanelPage) as Panel;
  46. if (!CheckNull()) return;
  47. _panelPage.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ButtonPages_OnClick));
  48. _appliedTemplate = true;
  49. Update();
  50. }
  51. private void Update()
  52. {
  53. TimerSwitch(AutoRun);
  54. UpdatePageButtons(_pageIndex);
  55. }
  56. private bool CheckNull() => _panelPage != null;
  57. public static readonly DependencyProperty AutoRunProperty = DependencyProperty.Register(
  58. nameof(AutoRun), typeof(bool), typeof(Carousel), new PropertyMetadata(ValueBoxes.FalseBox, (o, args) =>
  59. {
  60. var ctl = (Carousel) o;
  61. ctl.TimerSwitch((bool) args.NewValue);
  62. }));
  63. public static readonly DependencyProperty IntervalProperty = DependencyProperty.Register(
  64. nameof(Interval), typeof(TimeSpan), typeof(Carousel), new PropertyMetadata(TimeSpan.FromSeconds(2)));
  65. public static readonly DependencyProperty ExtendWidthProperty = DependencyProperty.Register(
  66. nameof(ExtendWidth), typeof(double), typeof(Carousel), new PropertyMetadata(ValueBoxes.Double0Box));
  67. public double ExtendWidth
  68. {
  69. get => (double) GetValue(ExtendWidthProperty);
  70. set => SetValue(ExtendWidthProperty, value);
  71. }
  72. public static readonly DependencyProperty IsCenterProperty = DependencyProperty.Register(
  73. nameof(IsCenter), typeof(bool), typeof(Carousel), new PropertyMetadata(ValueBoxes.FalseBox));
  74. public bool IsCenter
  75. {
  76. get => (bool) GetValue(IsCenterProperty);
  77. set => SetValue(IsCenterProperty, ValueBoxes.BooleanBox(value));
  78. }
  79. public static readonly DependencyProperty PageButtonStyleProperty = DependencyProperty.Register(
  80. nameof(PageButtonStyle), typeof(Style), typeof(Carousel), new PropertyMetadata(default(Style)));
  81. public Style PageButtonStyle
  82. {
  83. get => (Style) GetValue(PageButtonStyleProperty);
  84. set => SetValue(PageButtonStyleProperty, value);
  85. }
  86. public Carousel()
  87. {
  88. CommandBindings.Add(new CommandBinding(ControlCommands.Prev, ButtonPrev_OnClick));
  89. CommandBindings.Add(new CommandBinding(ControlCommands.Next, ButtonNext_OnClick));
  90. CommandBindings.Add(new CommandBinding(ControlCommands.Selected, ButtonPages_OnClick));
  91. Loaded += (s, e) => UpdatePageButtons();
  92. IsVisibleChanged += Carousel_IsVisibleChanged;
  93. }
  94. ~Carousel() => Dispose();
  95. public void Dispose()
  96. {
  97. if (_isDisposed) return;
  98. IsVisibleChanged -= Carousel_IsVisibleChanged;
  99. _updateTimer?.Stop();
  100. _isDisposed = true;
  101. GC.SuppressFinalize(this);
  102. }
  103. private void Carousel_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
  104. {
  105. if (_updateTimer == null) return;
  106. if (IsVisible)
  107. {
  108. _updateTimer.Tick += UpdateTimer_Tick;
  109. _updateTimer.Start();
  110. }
  111. else
  112. {
  113. _updateTimer.Stop();
  114. _updateTimer.Tick -= UpdateTimer_Tick;
  115. }
  116. }
  117. /// <summary>
  118. /// 是否自动跳转
  119. /// </summary>
  120. public bool AutoRun
  121. {
  122. get => (bool) GetValue(AutoRunProperty);
  123. set => SetValue(AutoRunProperty, ValueBoxes.BooleanBox(value));
  124. }
  125. /// <summary>
  126. /// 跳转时间间隔
  127. /// </summary>
  128. public TimeSpan Interval
  129. {
  130. get => (TimeSpan) GetValue(IntervalProperty);
  131. set => SetValue(IntervalProperty, value);
  132. }
  133. /// <summary>
  134. /// 页码
  135. /// </summary>
  136. public int PageIndex
  137. {
  138. get => _pageIndex;
  139. set
  140. {
  141. if (Items.Count == 0) return;
  142. if (_pageIndex == value) return;
  143. if (value < 0)
  144. _pageIndex = Items.Count - 1;
  145. else if (value >= Items.Count)
  146. _pageIndex = 0;
  147. else
  148. _pageIndex = value;
  149. UpdatePageButtons(_pageIndex);
  150. }
  151. }
  152. /// <summary>
  153. /// 计时器开关
  154. /// </summary>
  155. private void TimerSwitch(bool run)
  156. {
  157. if (!_appliedTemplate) return;
  158. if (_updateTimer != null)
  159. {
  160. _updateTimer.Tick -= UpdateTimer_Tick;
  161. _updateTimer.Stop();
  162. _updateTimer = null;
  163. }
  164. if (!run) return;
  165. _updateTimer = new DispatcherTimer
  166. {
  167. Interval = Interval
  168. };
  169. _updateTimer.Tick += UpdateTimer_Tick;
  170. _updateTimer.Start();
  171. }
  172. private void UpdateTimer_Tick(object sender, EventArgs e)
  173. {
  174. if (IsMouseOver) return;
  175. PageIndex++;
  176. }
  177. /// <summary>
  178. /// 更新页按钮
  179. /// </summary>
  180. public void UpdatePageButtons(int index = -1)
  181. {
  182. if (!CheckNull()) return;
  183. if (!_appliedTemplate) return;
  184. var count = Items.Count;
  185. _widthList.Clear();
  186. _widthList.Add(0);
  187. var width = .0;
  188. foreach (FrameworkElement item in ItemsHost.Children)
  189. {
  190. item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  191. width += item.DesiredSize.Width;
  192. _widthList.Add(width);
  193. }
  194. ItemsHost.Width = _widthList.Last() + ExtendWidth;
  195. _panelPage.Children.Clear();
  196. for (var i = 0; i < count; i++)
  197. {
  198. _panelPage.Children.Add(new RadioButton
  199. {
  200. Style = PageButtonStyle
  201. });
  202. }
  203. if (index == -1 && count > 0) index = 0;
  204. if (index >= 0 && index < count)
  205. {
  206. if (_panelPage.Children[index] is RadioButton button)
  207. {
  208. button.IsChecked = true;
  209. button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
  210. UpdateItemsPosition();
  211. }
  212. }
  213. }
  214. /// <summary>
  215. /// 更新项的位置
  216. /// </summary>
  217. private void UpdateItemsPosition()
  218. {
  219. if (!CheckNull()) return;
  220. if (!_appliedTemplate) return;
  221. if (Items.Count == 0) return;
  222. if (!IsCenter)
  223. {
  224. ItemsHost.BeginAnimation(MarginProperty,
  225. AnimationHelper.CreateAnimation(new Thickness(-_widthList[PageIndex], 0, 0, 0)));
  226. }
  227. else
  228. {
  229. var ctl = (FrameworkElement) ItemsHost.Children[PageIndex];
  230. var ctlWidth = ctl.DesiredSize.Width;
  231. ItemsHost.BeginAnimation(MarginProperty,
  232. AnimationHelper.CreateAnimation(
  233. new Thickness(-_widthList[PageIndex] + (ActualWidth - ctlWidth) / 2, 0, 0, 0)));
  234. }
  235. }
  236. protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
  237. {
  238. base.OnRenderSizeChanged(sizeInfo);
  239. UpdateItemsPosition();
  240. }
  241. private void ButtonPages_OnClick(object sender, RoutedEventArgs e)
  242. {
  243. if (!CheckNull()) return;
  244. _selectedButton = e.OriginalSource as RadioButton;
  245. var index = _panelPage.Children.IndexOf(_selectedButton);
  246. if (index != -1)
  247. {
  248. PageIndex = index;
  249. }
  250. }
  251. private void ButtonPrev_OnClick(object sender, RoutedEventArgs e) => PageIndex--;
  252. private void ButtonNext_OnClick(object sender, RoutedEventArgs e) => PageIndex++;
  253. protected override DependencyObject GetContainerForItemOverride() => new CarouselItem();
  254. protected override bool IsItemItsOwnContainerOverride(object item) => item is CarouselItem;
  255. private void ClearItems()
  256. {
  257. ItemsHost?.Children.Clear();
  258. _entryDic.Clear();
  259. }
  260. private void RemoveItem(object item)
  261. {
  262. if (_entryDic.TryGetValue(item, out var entry))
  263. {
  264. ItemsHost.Children.Remove(entry);
  265. Items.Remove(item);
  266. _entryDic.Remove(item);
  267. }
  268. }
  269. protected override void Refresh()
  270. {
  271. if (ItemsHost == null) return;
  272. _entryDic.Clear();
  273. _isRefresh = true;
  274. foreach (var item in Items)
  275. {
  276. AddItem(item);
  277. }
  278. _isRefresh = false;
  279. }
  280. private void AddItem(object item) => InsertItem(_entryDic.Count, item);
  281. private void InsertItem(int index, object item)
  282. {
  283. if (ItemsHost == null)
  284. {
  285. Items.Insert(index, item);
  286. _entryDic.Add(item, null);
  287. }
  288. else
  289. {
  290. DependencyObject container;
  291. if (IsItemItsOwnContainerOverride(item))
  292. {
  293. container = item as DependencyObject;
  294. }
  295. else
  296. {
  297. container = GetContainerForItemOverride();
  298. PrepareContainerForItemOverride(container, item);
  299. }
  300. if (container is CarouselItem element)
  301. {
  302. element.Style = ItemContainerStyle;
  303. _entryDic[item] = element;
  304. ItemsHost.Children.Insert(index, element);
  305. if (IsLoaded && !_isRefresh && _itemsSourceInternal != null)
  306. {
  307. Items.Insert(index, item);
  308. }
  309. }
  310. }
  311. }
  312. protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
  313. {
  314. if (_itemsSourceInternal != null)
  315. {
  316. if (_itemsSourceInternal is INotifyCollectionChanged s)
  317. {
  318. s.CollectionChanged -= InternalCollectionChanged;
  319. }
  320. Items.Clear();
  321. ClearItems();
  322. }
  323. _itemsSourceInternal = newValue;
  324. if (_itemsSourceInternal != null)
  325. {
  326. if (_itemsSourceInternal is INotifyCollectionChanged s)
  327. {
  328. s.CollectionChanged += InternalCollectionChanged;
  329. }
  330. foreach (var item in _itemsSourceInternal)
  331. {
  332. AddItem(item);
  333. }
  334. }
  335. }
  336. private void InternalCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  337. {
  338. if (ItemsHost == null) return;
  339. if (e.Action == NotifyCollectionChangedAction.Reset)
  340. {
  341. if (_entryDic.Count == 0) return;
  342. ClearItems();
  343. Items.Clear();
  344. return;
  345. }
  346. if (e.OldItems != null)
  347. {
  348. foreach (var item in e.OldItems)
  349. {
  350. RemoveItem(item);
  351. }
  352. }
  353. if (e.NewItems != null)
  354. {
  355. var count = 0;
  356. foreach (var item in e.NewItems)
  357. {
  358. var index = e.NewStartingIndex + count++;
  359. InsertItem(index, item);
  360. }
  361. }
  362. }
  363. protected override void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
  364. {
  365. if (_itemsSourceInternal != null) return;
  366. InternalCollectionChanged(sender, e);
  367. }
  368. protected override void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e)
  369. {
  370. }
  371. protected override void OnItemContainerStyleChanged(DependencyPropertyChangedEventArgs e)
  372. {
  373. }
  374. }