Rating.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. // (c) Copyright Microsoft Corporation.
  2. // This source is subject to the Microsoft Public License (Ms-PL).
  3. // Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details.
  4. // All other rights reserved.
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Collections.Specialized;
  8. using System.ComponentModel;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Linq;
  11. using System.Windows.Automation;
  12. using System.Windows.Automation.Peers;
  13. using System.Windows.Controls.Primitives;
  14. using System.Windows.Data;
  15. using System.Windows.Input;
  16. using System.Windows.Media.Animation;
  17. namespace System.Windows.Controls
  18. {
  19. /// <summary>
  20. /// A control that has a rating.
  21. /// </summary>
  22. /// <QualityBand>Preview</QualityBand>
  23. [TemplateVisualState(Name = VisualStates.StateNormal, GroupName = VisualStates.GroupCommon)]
  24. [TemplateVisualState(Name = VisualStates.StateMouseOver, GroupName = VisualStates.GroupCommon)]
  25. [TemplateVisualState(Name = VisualStates.StatePressed, GroupName = VisualStates.GroupCommon)]
  26. [TemplateVisualState(Name = VisualStates.StateDisabled, GroupName = VisualStates.GroupCommon)]
  27. [TemplateVisualState(Name = VisualStates.StateReadOnly, GroupName = VisualStates.GroupCommon)]
  28. [TemplateVisualState(Name = VisualStates.StateFocused, GroupName = VisualStates.GroupFocus)]
  29. [TemplateVisualState(Name = VisualStates.StateUnfocused, GroupName = VisualStates.GroupFocus)]
  30. [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(RatingItem))]
  31. public class Rating : ItemsControl, IUpdateVisualState
  32. {
  33. #region protected double DisplayValue
  34. /// <summary>
  35. /// Gets or sets the actual value of the Rating control.
  36. /// </summary>
  37. protected double DisplayValue
  38. {
  39. get { return (double)GetValue(DisplayValueProperty); }
  40. set { SetValue(DisplayValueProperty, value); }
  41. }
  42. /// <summary>
  43. /// Identifies the DisplayValue dependency property.
  44. /// </summary>
  45. protected static readonly DependencyProperty DisplayValueProperty =
  46. DependencyProperty.Register(
  47. "DisplayValue",
  48. typeof(double),
  49. typeof(Rating),
  50. new PropertyMetadata(0.0, OnDisplayValueChanged));
  51. /// <summary>
  52. /// DisplayValueProperty property changed handler.
  53. /// </summary>
  54. /// <param name="dependencyObject">Rating that changed its DisplayValue.</param>
  55. /// <param name="eventArgs">Event arguments.</param>
  56. private static void OnDisplayValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
  57. {
  58. Rating source = (Rating)dependencyObject;
  59. source.OnDisplayValueChanged();
  60. }
  61. /// <summary>
  62. /// DisplayValueProperty property changed handler.
  63. /// </summary>
  64. private void OnDisplayValueChanged()
  65. {
  66. UpdateDisplayValues();
  67. }
  68. #endregion protected double DisplayValue
  69. /// <summary>
  70. /// Gets or sets the rating item hovered over.
  71. /// </summary>
  72. private RatingItem HoveredRatingItem { get; set; }
  73. /// <summary>
  74. /// Gets the helper that provides all of the standard
  75. /// interaction functionality.
  76. /// </summary>
  77. internal InteractionHelper Interaction { get; private set; }
  78. #if SILVERLIGHT
  79. /// <summary>
  80. /// Gets or sets the items control helper class.
  81. /// </summary>
  82. private ItemsControlHelper ItemsControlHelper { get; set; }
  83. #endif
  84. #region public int ItemCount
  85. /// <summary>
  86. /// Gets or sets the number of rating items.
  87. /// </summary>
  88. public int ItemCount
  89. {
  90. get { return (int)GetValue(ItemCountProperty); }
  91. set { SetValue(ItemCountProperty, value); }
  92. }
  93. /// <summary>
  94. /// Identifies the ItemCount dependency property.
  95. /// </summary>
  96. public static readonly DependencyProperty ItemCountProperty =
  97. DependencyProperty.Register(
  98. "ItemCount",
  99. typeof(int),
  100. typeof(Rating),
  101. new PropertyMetadata(0, OnItemCountChanged));
  102. /// <summary>
  103. /// ItemCountProperty property changed handler.
  104. /// </summary>
  105. /// <param name="d">Rating that changed its ItemCount.</param>
  106. /// <param name="e">Event arguments.</param>
  107. private static void OnItemCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  108. {
  109. Rating source = d as Rating;
  110. int value = (int)e.NewValue;
  111. source.OnItemCountChanged(value);
  112. }
  113. /// <summary>
  114. /// This method is invoked when the items count property is changed.
  115. /// </summary>
  116. /// <param name="newValue">The new value.</param>
  117. private void OnItemCountChanged(int newValue)
  118. {
  119. if (newValue < 0)
  120. {
  121. throw new ArgumentException(Muchinfo.WPF.Controls.Properties.Resources.Rating_SetItemCount_ItemCountMustBeLargerThanOrEqualToZero);
  122. }
  123. int amountToAdd = newValue - this.Items.Count;
  124. if (amountToAdd > 0)
  125. {
  126. for (int cnt = 0; cnt < amountToAdd; cnt++)
  127. {
  128. this.Items.Add(new RatingItem());
  129. }
  130. }
  131. else if (amountToAdd < 0)
  132. {
  133. for (int cnt = 0; cnt < Math.Abs(amountToAdd); cnt++)
  134. {
  135. this.Items.RemoveAt(this.Items.Count - 1);
  136. }
  137. }
  138. }
  139. #endregion public int ItemCount
  140. #region public bool IsReadOnly
  141. /// <summary>
  142. /// Gets or sets a value indicating whether the Rating is read-only.
  143. /// </summary>
  144. public bool IsReadOnly
  145. {
  146. get { return (bool)GetValue(IsReadOnlyProperty); }
  147. set { SetValue(IsReadOnlyProperty, value); }
  148. }
  149. /// <summary>
  150. /// Identifies the IsReadOnly dependency property.
  151. /// </summary>
  152. public static readonly DependencyProperty IsReadOnlyProperty =
  153. DependencyProperty.Register(
  154. "IsReadOnly",
  155. typeof(bool),
  156. typeof(Rating),
  157. new PropertyMetadata(false, OnIsReadOnlyChanged));
  158. /// <summary>
  159. /// IsReadOnlyProperty property changed handler.
  160. /// </summary>
  161. /// <param name="d">Rating that changed its IsReadOnly.</param>
  162. /// <param name="e">Event arguments.</param>
  163. private static void OnIsReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  164. {
  165. Rating source = (Rating)d;
  166. bool oldValue = (bool)e.OldValue;
  167. bool newValue = (bool)e.NewValue;
  168. source.OnIsReadOnlyChanged(oldValue, newValue);
  169. }
  170. /// <summary>
  171. /// IsReadOnlyProperty property changed handler.
  172. /// </summary>
  173. /// <param name="oldValue">Old value.</param>
  174. /// <param name="newValue">New value.</param>
  175. protected virtual void OnIsReadOnlyChanged(bool oldValue, bool newValue)
  176. {
  177. Interaction.OnIsReadOnlyChanged(newValue);
  178. foreach (RatingItem ratingItem in GetRatingItems())
  179. {
  180. ratingItem.IsReadOnly = newValue;
  181. }
  182. UpdateHoverStates();
  183. }
  184. #endregion public bool IsReadOnly
  185. #if SILVERLIGHT
  186. #region public Style ItemContainerStyle
  187. /// <summary>
  188. /// Gets or sets the item container style.
  189. /// </summary>
  190. public Style ItemContainerStyle
  191. {
  192. get { return GetValue(ItemContainerStyleProperty) as Style; }
  193. set { SetValue(ItemContainerStyleProperty, value); }
  194. }
  195. /// <summary>
  196. /// Identifies the ItemContainerStyle dependency property.
  197. /// </summary>
  198. public static readonly DependencyProperty ItemContainerStyleProperty =
  199. DependencyProperty.Register(
  200. "ItemContainerStyle",
  201. typeof(Style),
  202. typeof(Rating),
  203. new PropertyMetadata(null, OnItemContainerStyleChanged));
  204. /// <summary>
  205. /// ItemContainerStyleProperty property changed handler.
  206. /// </summary>
  207. /// <param name="d">Rating that changed its ItemContainerStyle.</param>
  208. /// <param name="e">Event arguments.</param>
  209. private static void OnItemContainerStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  210. {
  211. Rating source = (Rating)d;
  212. Style newValue = (Style)e.NewValue;
  213. source.OnItemContainerStyleChanged(newValue);
  214. }
  215. /// <summary>
  216. /// ItemContainerStyleProperty property changed handler.
  217. /// </summary>
  218. /// <param name="newValue">New value.</param>
  219. protected virtual void OnItemContainerStyleChanged(Style newValue)
  220. {
  221. ItemsControlHelper.UpdateItemContainerStyle(newValue);
  222. }
  223. #endregion public Style ItemContainerStyle
  224. #endif
  225. #region public RatingSelectionMode SelectionMode
  226. /// <summary>
  227. /// Gets or sets the selection mode.
  228. /// </summary>
  229. public RatingSelectionMode SelectionMode
  230. {
  231. get { return (RatingSelectionMode) GetValue(SelectionModeProperty); }
  232. set { SetValue(SelectionModeProperty, value); }
  233. }
  234. /// <summary>
  235. /// Identifies the SelectionMode dependency property.
  236. /// </summary>
  237. public static readonly DependencyProperty SelectionModeProperty =
  238. DependencyProperty.Register(
  239. "SelectionMode",
  240. typeof(RatingSelectionMode),
  241. typeof(Rating),
  242. new PropertyMetadata(RatingSelectionMode.Continuous, OnSelectionModeChanged));
  243. /// <summary>
  244. /// SelectionModeProperty property changed handler.
  245. /// </summary>
  246. /// <param name="d">Rating that changed its SelectionMode.</param>
  247. /// <param name="e">Event arguments.</param>
  248. private static void OnSelectionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  249. {
  250. Rating source = (Rating)d;
  251. RatingSelectionMode oldValue = (RatingSelectionMode)e.OldValue;
  252. RatingSelectionMode newValue = (RatingSelectionMode)e.NewValue;
  253. source.OnSelectionModeChanged(oldValue, newValue);
  254. }
  255. /// <summary>
  256. /// SelectionModeProperty property changed handler.
  257. /// </summary>
  258. /// <param name="oldValue">Old value.</param>
  259. /// <param name="newValue">New value.</param>
  260. protected virtual void OnSelectionModeChanged(RatingSelectionMode oldValue, RatingSelectionMode newValue)
  261. {
  262. UpdateDisplayValues();
  263. }
  264. #endregion public RatingSelectionMode SelectionMode
  265. #region public double? Value
  266. /// <summary>
  267. /// Gets or sets the rating value.
  268. /// </summary>
  269. [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Value is the logical name for this property.")]
  270. [TypeConverter(typeof(NullableConverter<double>))]
  271. public double? Value
  272. {
  273. get { return (double?)GetValue(ValueProperty); }
  274. set { SetValue(ValueProperty, value); }
  275. }
  276. /// <summary>
  277. /// Identifies the Value dependency property.
  278. /// </summary>
  279. public static readonly DependencyProperty ValueProperty =
  280. DependencyProperty.Register(
  281. "Value",
  282. typeof(double?),
  283. typeof(Rating),
  284. new PropertyMetadata(new double?(), OnValueChanged));
  285. /// <summary>
  286. /// ValueProperty property changed handler.
  287. /// </summary>
  288. /// <param name="d">Rating that changed its Value.</param>
  289. /// <param name="e">Event arguments.</param>
  290. private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  291. {
  292. Rating source = (Rating)d;
  293. double? oldValue = (double?)e.OldValue;
  294. double? newValue = (double?)e.NewValue;
  295. source.OnValueChanged(oldValue, newValue);
  296. }
  297. /// <summary>
  298. /// ValueProperty property changed handler.
  299. /// </summary>
  300. /// <param name="oldValue">Old value.</param>
  301. /// <param name="newValue">New value.</param>
  302. protected virtual void OnValueChanged(double? oldValue, double? newValue)
  303. {
  304. UpdateValues();
  305. #if SILVERLIGHT
  306. RoutedPropertyChangedEventHandler<double?> handler = ValueChanged;
  307. if (handler != null)
  308. {
  309. handler(this, new RoutedPropertyChangedEventArgs<double?>(oldValue, newValue));
  310. }
  311. #else
  312. RaiseEvent(new RoutedPropertyChangedEventArgs<double?>(oldValue, newValue, ValueChangedEvent));
  313. #endif
  314. }
  315. /// <summary>
  316. /// Updates the control when the items change.
  317. /// </summary>
  318. /// <param name="e">Information about the event.</param>
  319. protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
  320. {
  321. EventHandler layoutUpdated = null;
  322. layoutUpdated =
  323. delegate
  324. {
  325. this.LayoutUpdated -= layoutUpdated;
  326. UpdateValues();
  327. UpdateDisplayValues();
  328. };
  329. this.LayoutUpdated += layoutUpdated;
  330. this.ItemCount = this.Items.Count;
  331. base.OnItemsChanged(e);
  332. }
  333. /// <summary>
  334. /// This event is raised when the value of the rating is changed.
  335. /// </summary>
  336. #if SILVERLIGHT
  337. public event RoutedPropertyChangedEventHandler<double?> ValueChanged;
  338. #else
  339. public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<double?>), typeof(Rating));
  340. /// <summary>
  341. /// This event is raised when the value of the rating is changed.
  342. /// </summary>
  343. public event RoutedPropertyChangedEventHandler<double?> ValueChanged
  344. {
  345. add { AddHandler(ValueChangedEvent, value); }
  346. remove { RemoveHandler(ValueChangedEvent, value); }
  347. }
  348. #endif
  349. #endregion public double? Value
  350. #if !SILVERLIGHT
  351. /// <summary>
  352. /// Initializes the static members of the ColumnDataPoint class.
  353. /// </summary>
  354. static Rating()
  355. {
  356. DefaultStyleKeyProperty.OverrideMetadata(typeof(Rating), new FrameworkPropertyMetadata(typeof(Rating)));
  357. }
  358. #endif
  359. /// <summary>
  360. /// Initializes a new instance of the Rating control.
  361. /// </summary>
  362. public Rating()
  363. {
  364. this.Interaction = new InteractionHelper(this);
  365. #if SILVERLIGHT
  366. this.DefaultStyleKey = typeof(Rating);
  367. this.ItemsControlHelper = new ItemsControlHelper(this);
  368. }
  369. /// <summary>
  370. /// Applies control template to the items control.
  371. /// </summary>
  372. public override void OnApplyTemplate()
  373. {
  374. ItemsControlHelper.OnApplyTemplate();
  375. base.OnApplyTemplate();
  376. #endif
  377. }
  378. /// <summary>
  379. /// This method is invoked when the mouse enters the rating item.
  380. /// </summary>
  381. /// <param name="e">Information about the event.</param>
  382. protected override void OnMouseEnter(MouseEventArgs e)
  383. {
  384. if (Interaction.AllowMouseEnter(e))
  385. {
  386. Interaction.UpdateVisualStateBase(true);
  387. }
  388. base.OnMouseEnter(e);
  389. }
  390. /// <summary>
  391. /// This method is invoked when the mouse leaves the rating item.
  392. /// </summary>
  393. /// <param name="e">Information about the event.</param>
  394. protected override void OnMouseLeave(MouseEventArgs e)
  395. {
  396. if (Interaction.AllowMouseLeave(e))
  397. {
  398. Interaction.UpdateVisualStateBase(true);
  399. }
  400. base.OnMouseLeave(e);
  401. }
  402. /// <summary>
  403. /// Provides handling for the Rating's MouseLeftButtonDown event.
  404. /// </summary>
  405. /// <param name="e">Event arguments.</param>
  406. protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
  407. {
  408. if (Interaction.AllowMouseLeftButtonDown(e))
  409. {
  410. Interaction.OnMouseLeftButtonDownBase();
  411. }
  412. base.OnMouseLeftButtonDown(e);
  413. }
  414. /// <summary>
  415. /// Provides handling for the Rating's MouseLeftButtonUp event.
  416. /// </summary>
  417. /// <param name="e">Event arguments.</param>
  418. protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
  419. {
  420. if (Interaction.AllowMouseLeftButtonUp(e))
  421. {
  422. Interaction.OnMouseLeftButtonUpBase();
  423. }
  424. base.OnMouseLeftButtonUp(e);
  425. }
  426. /// <summary>
  427. /// Updates the values of the rating items.
  428. /// </summary>
  429. private void UpdateValues()
  430. {
  431. IList<RatingItem> ratingItems = GetRatingItems().ToList();
  432. RatingItem oldSelectedItem = this.GetSelectedRatingItem();
  433. IEnumerable<Tuple<RatingItem, double>> itemAndWeights =
  434. EnumerableFunctions
  435. .Zip(
  436. ratingItems,
  437. ratingItems
  438. .Select(ratingItem => 1.0)
  439. .GetWeightedValues(Value.GetValueOrDefault()),
  440. (item, percent) => Tuple.Create(item, percent));
  441. foreach (Tuple<RatingItem, double> itemAndWeight in itemAndWeights)
  442. {
  443. itemAndWeight.First.Value = itemAndWeight.Second;
  444. }
  445. RatingItem newSelectedItem = this.GetSelectedRatingItem();
  446. // Notify when the selection changes
  447. if (oldSelectedItem != newSelectedItem)
  448. {
  449. if (newSelectedItem != null && AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementSelected))
  450. {
  451. AutomationPeer peer = FrameworkElementAutomationPeer.CreatePeerForElement(newSelectedItem);
  452. if (peer != null)
  453. {
  454. peer.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementSelected);
  455. }
  456. }
  457. if (oldSelectedItem != null && AutomationPeer.ListenerExists(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection))
  458. {
  459. AutomationPeer peer = FrameworkElementAutomationPeer.CreatePeerForElement(oldSelectedItem);
  460. if (peer != null)
  461. {
  462. peer.RaiseAutomationEvent(AutomationEvents.SelectionItemPatternOnElementRemovedFromSelection);
  463. }
  464. }
  465. }
  466. if (HoveredRatingItem == null)
  467. {
  468. DisplayValue = Value.GetValueOrDefault();
  469. }
  470. }
  471. /// <summary>
  472. /// Updates the value and actual value of the rating items.
  473. /// </summary>
  474. private void UpdateDisplayValues()
  475. {
  476. IList<RatingItem> ratingItems = GetRatingItems().ToList();
  477. IEnumerable<Tuple<RatingItem, double>> itemAndWeights =
  478. EnumerableFunctions
  479. .Zip(
  480. ratingItems,
  481. ratingItems
  482. .Select(ratingItem => 1.0)
  483. .GetWeightedValues(DisplayValue),
  484. (item, percent) => Tuple.Create(item, percent));
  485. RatingItem selectedItem = null;
  486. Tuple<RatingItem, double> selectedItemAndWeight = itemAndWeights.LastOrDefault(i => i.Second > 0.0);
  487. if (selectedItemAndWeight != null)
  488. {
  489. selectedItem = selectedItemAndWeight.First;
  490. }
  491. else
  492. {
  493. selectedItem = GetSelectedRatingItem();
  494. }
  495. foreach (Tuple<RatingItem, double> itemAndWeight in itemAndWeights)
  496. {
  497. if (SelectionMode == RatingSelectionMode.Individual && itemAndWeight.First != selectedItem)
  498. {
  499. itemAndWeight.First.DisplayValue = 0.0;
  500. }
  501. else
  502. {
  503. itemAndWeight.First.DisplayValue = itemAndWeight.Second;
  504. }
  505. }
  506. }
  507. /// <summary>
  508. /// Updates the hover states of the rating items.
  509. /// </summary>
  510. private void UpdateHoverStates()
  511. {
  512. if (HoveredRatingItem != null && !IsReadOnly)
  513. {
  514. IList<RatingItem> ratingItems = GetRatingItems().ToList();
  515. int indexOfItem = ratingItems.IndexOf(HoveredRatingItem);
  516. double total = ratingItems.Count();
  517. double filled = indexOfItem + 1;
  518. this.DisplayValue = filled / total;
  519. for (int cnt = 0; cnt < ratingItems.Count; cnt++)
  520. {
  521. RatingItem ratingItem = ratingItems[cnt];
  522. if (cnt <= indexOfItem && this.SelectionMode == RatingSelectionMode.Continuous)
  523. {
  524. VisualStates.GoToState(ratingItem, true, VisualStates.StateMouseOver);
  525. }
  526. else
  527. {
  528. IUpdateVisualState updateVisualState = (IUpdateVisualState) ratingItem;
  529. updateVisualState.UpdateVisualState(true);
  530. }
  531. }
  532. }
  533. else
  534. {
  535. this.DisplayValue = this.Value.GetValueOrDefault();
  536. foreach (IUpdateVisualState updateVisualState in GetRatingItems().OfType<IUpdateVisualState>())
  537. {
  538. updateVisualState.UpdateVisualState(true);
  539. }
  540. }
  541. }
  542. /// <summary>
  543. /// This method returns a container for the item.
  544. /// </summary>
  545. /// <returns>A container for the item.</returns>
  546. protected override DependencyObject GetContainerForItemOverride()
  547. {
  548. return new RatingItem();
  549. }
  550. /// <summary>
  551. /// Gets a value indicating whether the item is its own container.
  552. /// </summary>
  553. /// <param name="item">The item which may be a container.</param>
  554. /// <returns>A value indicating whether the item is its own container.
  555. /// </returns>
  556. protected override bool IsItemItsOwnContainerOverride(object item)
  557. {
  558. return item is RatingItem;
  559. }
  560. /// <summary>
  561. /// This method prepares a container to host an item.
  562. /// </summary>
  563. /// <param name="element">The container.</param>
  564. /// <param name="item">The item hosted in the container.</param>
  565. protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
  566. {
  567. RatingItem ratingItem = (RatingItem)element;
  568. object defaultForegroundValue = ratingItem.ReadLocalValue(Control.ForegroundProperty);
  569. if (defaultForegroundValue == DependencyProperty.UnsetValue)
  570. {
  571. ratingItem.SetBinding(Control.ForegroundProperty, new Binding("Foreground") { Source = this });
  572. }
  573. ratingItem.IsReadOnly = this.IsReadOnly;
  574. if (ratingItem.Style == null)
  575. {
  576. ratingItem.Style = this.ItemContainerStyle;
  577. }
  578. ratingItem.Click += RatingItemClick;
  579. ratingItem.MouseEnter += RatingItemMouseEnter;
  580. ratingItem.MouseLeave += RatingItemMouseLeave;
  581. ratingItem.ParentRating = this;
  582. base.PrepareContainerForItemOverride(element, item);
  583. }
  584. /// <summary>
  585. /// This method clears a container used to host an item.
  586. /// </summary>
  587. /// <param name="element">The container that hosts the item.</param>
  588. /// <param name="item">The item hosted in the container.</param>
  589. protected override void ClearContainerForItemOverride(DependencyObject element, object item)
  590. {
  591. RatingItem ratingItem = (RatingItem)element;
  592. ratingItem.Click -= RatingItemClick;
  593. ratingItem.MouseEnter -= RatingItemMouseEnter;
  594. ratingItem.MouseLeave -= RatingItemMouseLeave;
  595. ratingItem.ParentRating = null;
  596. if (ratingItem == HoveredRatingItem)
  597. {
  598. HoveredRatingItem = null;
  599. UpdateDisplayValues();
  600. UpdateHoverStates();
  601. }
  602. base.ClearContainerForItemOverride(element, item);
  603. }
  604. /// <summary>
  605. /// This method is invoked when a rating item's mouse enter event is
  606. /// invoked.
  607. /// </summary>
  608. /// <param name="sender">The source of the event.</param>
  609. /// <param name="e">Information about the event.</param>
  610. private void RatingItemMouseEnter(object sender, MouseEventArgs e)
  611. {
  612. HoveredRatingItem = (RatingItem) sender;
  613. UpdateHoverStates();
  614. }
  615. /// <summary>
  616. /// This method is invoked when a rating item's mouse leave event is
  617. /// invoked.
  618. /// </summary>
  619. /// <param name="sender">The source of the event.</param>
  620. /// <param name="e">Information about the event.</param>
  621. private void RatingItemMouseLeave(object sender, MouseEventArgs e)
  622. {
  623. HoveredRatingItem = null;
  624. UpdateDisplayValues();
  625. UpdateHoverStates();
  626. }
  627. /// <summary>
  628. /// Returns a sequence of rating items.
  629. /// </summary>
  630. /// <returns>A sequence of rating items.</returns>
  631. internal IEnumerable<RatingItem> GetRatingItems()
  632. {
  633. #if SILVERLIGHT
  634. return
  635. Enumerable
  636. .Range(0, this.Items.Count)
  637. .Select(index => (RatingItem)ItemContainerGenerator.ContainerFromIndex(index))
  638. .Where(ratingItem => ratingItem != null);
  639. #else
  640. // The query above returns null in WPF
  641. // Either way, WPF will already contain the RatingItem objects in the Items collection.
  642. return this.Items.Cast<RatingItem>();
  643. #endif
  644. }
  645. /// <summary>
  646. /// Selects a rating item.
  647. /// </summary>
  648. /// <param name="selectedRatingItem">The selected rating item.</param>
  649. internal void SelectRatingItem(RatingItem selectedRatingItem)
  650. {
  651. if (!this.IsReadOnly)
  652. {
  653. IList<RatingItem> ratingItems = GetRatingItems().ToList();
  654. IEnumerable<double> weights = ratingItems.Select(ratingItem => 1.0);
  655. double total = ratingItems.Count();
  656. double percent;
  657. if (total != 0)
  658. {
  659. percent = weights.Take(ratingItems.IndexOf(selectedRatingItem) + 1).Sum() / total;
  660. this.Value = percent;
  661. }
  662. }
  663. }
  664. /// <summary>
  665. /// This method is raised when a rating item value is selected.
  666. /// </summary>
  667. /// <param name="sender">The source of the event.</param>
  668. /// <param name="e">Information about the event.</param>
  669. private void RatingItemClick(object sender, RoutedEventArgs e)
  670. {
  671. if (!this.IsReadOnly)
  672. {
  673. RatingItem item = (RatingItem)sender;
  674. OnRatingItemValueSelected(item, 1.0);
  675. }
  676. }
  677. /// <summary>
  678. /// Returns the selected rating item.
  679. /// </summary>
  680. /// <returns>The selected rating item.</returns>
  681. private RatingItem GetSelectedRatingItem()
  682. {
  683. return this.GetRatingItems().LastOrDefault(ratingItem => ratingItem.Value > 0.0);
  684. }
  685. /// <summary>
  686. /// This method is invoked when the rating item value is changed.
  687. /// </summary>
  688. /// <param name="ratingItem">The rating item that has changed.</param>
  689. /// <param name="newValue">The new value.</param>
  690. protected virtual void OnRatingItemValueSelected(RatingItem ratingItem, double newValue)
  691. {
  692. List<RatingItem> ratingItems = GetRatingItems().ToList();
  693. double total = ratingItems.Count();
  694. double value =
  695. (ratingItems
  696. .Take(ratingItems.IndexOf(ratingItem))
  697. .Count() + newValue) / total;
  698. this.Value = value;
  699. }
  700. /// <summary>
  701. /// Returns a RatingItemAutomationPeer for use by the Silverlight
  702. /// automation infrastructure.
  703. /// </summary>
  704. /// <returns>A RatingItemAutomationPeer object for the RatingItem.</returns>
  705. protected override AutomationPeer OnCreateAutomationPeer()
  706. {
  707. return new RatingAutomationPeer(this);
  708. }
  709. /// <summary>
  710. /// Provides handling for the
  711. /// <see cref="E:System.Windows.UIElement.KeyDown" /> event when a key
  712. /// is pressed while the control has focus.
  713. /// </summary>
  714. /// <param name="e">
  715. /// A <see cref="T:System.Windows.Input.KeyEventArgs" /> that contains
  716. /// the event data.
  717. /// </param>
  718. /// <exception cref="T:System.ArgumentNullException">
  719. /// <paramref name="e " />is null.
  720. /// </exception>
  721. [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Complexity metric is inflated by the switch statements")]
  722. protected override void OnKeyDown(KeyEventArgs e)
  723. {
  724. if (!Interaction.AllowKeyDown(e))
  725. {
  726. return;
  727. }
  728. base.OnKeyDown(e);
  729. if (e.Handled)
  730. {
  731. return;
  732. }
  733. switch (e.Key)
  734. {
  735. case Key.Left:
  736. {
  737. #if SILVERLIGHT
  738. RatingItem ratingItem = FocusManager.GetFocusedElement() as RatingItem;
  739. #else
  740. RatingItem ratingItem = FocusManager.GetFocusedElement(Application.Current.MainWindow) as RatingItem;
  741. #endif
  742. if (ratingItem != null)
  743. {
  744. ratingItem = GetRatingItemAtOffsetFrom(ratingItem, -1);
  745. }
  746. else
  747. {
  748. ratingItem = GetRatingItems().FirstOrDefault();
  749. }
  750. if (ratingItem != null)
  751. {
  752. if (ratingItem.Focus())
  753. {
  754. e.Handled = true;
  755. }
  756. }
  757. }
  758. break;
  759. case Key.Right:
  760. {
  761. #if SILVERLIGHT
  762. RatingItem ratingItem = FocusManager.GetFocusedElement() as RatingItem;
  763. #else
  764. RatingItem ratingItem = FocusManager.GetFocusedElement(Application.Current.MainWindow) as RatingItem;
  765. #endif
  766. if (ratingItem != null)
  767. {
  768. ratingItem = GetRatingItemAtOffsetFrom(ratingItem, 1);
  769. }
  770. else
  771. {
  772. ratingItem = GetRatingItems().FirstOrDefault();
  773. }
  774. if (ratingItem != null)
  775. {
  776. if (ratingItem.Focus())
  777. {
  778. e.Handled = true;
  779. }
  780. }
  781. }
  782. break;
  783. case Key.Add:
  784. {
  785. if (!this.IsReadOnly)
  786. {
  787. RatingItem ratingItem = GetSelectedRatingItem();
  788. if (ratingItem != null)
  789. {
  790. ratingItem = GetRatingItemAtOffsetFrom(ratingItem, 1);
  791. }
  792. else
  793. {
  794. ratingItem = GetRatingItems().FirstOrDefault();
  795. }
  796. if (ratingItem != null)
  797. {
  798. ratingItem.SelectValue();
  799. e.Handled = true;
  800. }
  801. }
  802. }
  803. break;
  804. case Key.Subtract:
  805. {
  806. if (!this.IsReadOnly)
  807. {
  808. RatingItem ratingItem = GetSelectedRatingItem();
  809. if (ratingItem != null)
  810. {
  811. ratingItem = GetRatingItemAtOffsetFrom(ratingItem, -1);
  812. }
  813. if (ratingItem != null)
  814. {
  815. ratingItem.SelectValue();
  816. e.Handled = true;
  817. }
  818. }
  819. }
  820. break;
  821. }
  822. }
  823. /// <summary>
  824. /// Gets a rating item at a certain index offset from another
  825. /// rating item.
  826. /// </summary>
  827. /// <param name="ratingItem">The rating item.</param>
  828. /// <param name="offset">The rating item at an offset from the
  829. /// index of the rating item.</param>
  830. /// <returns>The rating item at the offset.</returns>
  831. private RatingItem GetRatingItemAtOffsetFrom(RatingItem ratingItem, int offset)
  832. {
  833. IList<RatingItem> ratingItems = GetRatingItems().ToList();
  834. int index = ratingItems.IndexOf(ratingItem);
  835. if (index == -1)
  836. {
  837. return null;
  838. }
  839. index += offset;
  840. if (index >= 0 && index < ratingItems.Count)
  841. {
  842. ratingItem = ratingItems[index];
  843. }
  844. else
  845. {
  846. ratingItem = null;
  847. }
  848. return ratingItem;
  849. }
  850. /// <summary>
  851. /// Updates the visual state.
  852. /// </summary>
  853. /// <param name="useTransitions">A value indicating whether to use transitions.</param>
  854. void IUpdateVisualState.UpdateVisualState(bool useTransitions)
  855. {
  856. Interaction.UpdateVisualStateBase(useTransitions);
  857. }
  858. }
  859. }