Poptip.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. using System;
  2. using System.Windows;
  3. using System.Windows.Controls;
  4. using System.Windows.Controls.Primitives;
  5. using System.Windows.Data;
  6. using System.Windows.Input;
  7. using System.Windows.Threading;
  8. using HandyControl.Data;
  9. using HandyControl.Data.Enum;
  10. using HandyControl.Expression.Drawing;
  11. using HandyControl.Tools;
  12. namespace HandyControl.Controls;
  13. public class Poptip : AdornerElement
  14. {
  15. private readonly Popup _popup;
  16. private DispatcherTimer _openTimer;
  17. public Poptip()
  18. {
  19. _popup = new Popup
  20. {
  21. AllowsTransparency = true,
  22. Child = this,
  23. Placement = PlacementMode.Relative
  24. };
  25. _popup.SetBinding(DataContextProperty, new Binding(DataContextProperty.Name) { Source = this });
  26. }
  27. public static readonly DependencyProperty HitModeProperty = DependencyProperty.RegisterAttached(
  28. "HitMode", typeof(HitMode), typeof(Poptip), new PropertyMetadata(HitMode.Hover));
  29. public static void SetHitMode(DependencyObject element, HitMode value)
  30. => element.SetValue(HitModeProperty, value);
  31. public static HitMode GetHitMode(DependencyObject element)
  32. => (HitMode) element.GetValue(HitModeProperty);
  33. public HitMode HitMode
  34. {
  35. get => (HitMode) GetValue(HitModeProperty);
  36. set => SetValue(HitModeProperty, value);
  37. }
  38. public static readonly DependencyProperty ContentProperty = DependencyProperty.RegisterAttached(
  39. "Content", typeof(object), typeof(Poptip), new PropertyMetadata(default, OnContentChanged));
  40. private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  41. {
  42. if (d is Poptip) return;
  43. if (GetInstance(d) == null)
  44. {
  45. SetInstance(d, Default);
  46. SetIsInstance(d, false);
  47. }
  48. }
  49. public static void SetContent(DependencyObject element, object value)
  50. => element.SetValue(ContentProperty, value);
  51. public static object GetContent(DependencyObject element)
  52. => element.GetValue(ContentProperty);
  53. public object Content
  54. {
  55. get => GetValue(ContentProperty);
  56. set => SetValue(ContentProperty, value);
  57. }
  58. public static readonly DependencyProperty ContentTemplateProperty = DependencyProperty.Register(
  59. nameof(ContentTemplate), typeof(DataTemplate), typeof(Poptip), new PropertyMetadata(default(DataTemplate)));
  60. public DataTemplate ContentTemplate
  61. {
  62. get => (DataTemplate) GetValue(ContentTemplateProperty);
  63. set => SetValue(ContentTemplateProperty, value);
  64. }
  65. public static readonly DependencyProperty ContentStringFormatProperty = DependencyProperty.Register(
  66. nameof(ContentStringFormat), typeof(string), typeof(Poptip), new PropertyMetadata(default(string)));
  67. public string ContentStringFormat
  68. {
  69. get => (string) GetValue(ContentStringFormatProperty);
  70. set => SetValue(ContentStringFormatProperty, value);
  71. }
  72. public static readonly DependencyProperty ContentTemplateSelectorProperty = DependencyProperty.Register(
  73. nameof(ContentTemplateSelector), typeof(DataTemplateSelector), typeof(Poptip), new PropertyMetadata(default(DataTemplateSelector)));
  74. public DataTemplateSelector ContentTemplateSelector
  75. {
  76. get => (DataTemplateSelector) GetValue(ContentTemplateSelectorProperty);
  77. set => SetValue(ContentTemplateSelectorProperty, value);
  78. }
  79. public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.RegisterAttached(
  80. "VerticalOffset", typeof(double), typeof(Poptip), new PropertyMetadata(ValueBoxes.Double0Box));
  81. public static void SetVerticalOffset(DependencyObject element, double value)
  82. {
  83. element.SetValue(VerticalOffsetProperty, value);
  84. }
  85. public static double GetVerticalOffset(DependencyObject element)
  86. {
  87. return (double) element.GetValue(VerticalOffsetProperty);
  88. }
  89. public double VerticalOffset
  90. {
  91. get => (double) GetValue(VerticalOffsetProperty);
  92. set => SetValue(VerticalOffsetProperty, value);
  93. }
  94. public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.RegisterAttached(
  95. "HorizontalOffset", typeof(double), typeof(Poptip), new PropertyMetadata(ValueBoxes.Double0Box));
  96. public static void SetHorizontalOffset(DependencyObject element, double value)
  97. {
  98. element.SetValue(HorizontalOffsetProperty, value);
  99. }
  100. public static double GetHorizontalOffset(DependencyObject element)
  101. {
  102. return (double) element.GetValue(HorizontalOffsetProperty);
  103. }
  104. public double HorizontalOffset
  105. {
  106. get => (double) GetValue(HorizontalOffsetProperty);
  107. set => SetValue(HorizontalOffsetProperty, value);
  108. }
  109. public static readonly DependencyProperty PlacementTypeProperty = DependencyProperty.RegisterAttached(
  110. "PlacementType", typeof(PlacementType), typeof(Poptip), new PropertyMetadata(PlacementType.Top));
  111. public static void SetPlacement(DependencyObject element, PlacementType value)
  112. => element.SetValue(PlacementTypeProperty, value);
  113. public static PlacementType GetPlacement(DependencyObject element)
  114. => (PlacementType) element.GetValue(PlacementTypeProperty);
  115. public PlacementType PlacementType
  116. {
  117. get => (PlacementType) GetValue(PlacementTypeProperty);
  118. set => SetValue(PlacementTypeProperty, value);
  119. }
  120. public static readonly DependencyProperty IsOpenProperty = DependencyProperty.RegisterAttached(
  121. "IsOpen", typeof(bool), typeof(Poptip), new PropertyMetadata(ValueBoxes.FalseBox, OnIsOpenChanged));
  122. private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  123. {
  124. if (d is Poptip poptip)
  125. {
  126. poptip.SwitchPoptip((bool) e.NewValue);
  127. }
  128. else
  129. {
  130. ((Poptip) GetInstance(d))?.SwitchPoptip((bool) e.NewValue);
  131. }
  132. }
  133. public static void SetIsOpen(DependencyObject element, bool value)
  134. => element.SetValue(IsOpenProperty, ValueBoxes.BooleanBox(value));
  135. public static bool GetIsOpen(DependencyObject element)
  136. => (bool) element.GetValue(IsOpenProperty);
  137. public bool IsOpen
  138. {
  139. get => (bool) GetValue(IsOpenProperty);
  140. set => SetValue(IsOpenProperty, ValueBoxes.BooleanBox(value));
  141. }
  142. public static readonly DependencyProperty DelayProperty = DependencyProperty.Register(
  143. nameof(Delay), typeof(double), typeof(Poptip), new PropertyMetadata(1000.0), ValidateHelper.IsInRangeOfPosDoubleIncludeZero);
  144. public double Delay
  145. {
  146. get => (double) GetValue(DelayProperty);
  147. set => SetValue(DelayProperty, value);
  148. }
  149. public static Poptip Default => new();
  150. protected sealed override void OnTargetChanged(FrameworkElement element, bool isNew)
  151. {
  152. base.OnTargetChanged(element, isNew);
  153. if (element == null) return;
  154. if (!isNew)
  155. {
  156. element.MouseEnter -= Element_MouseEnter;
  157. element.MouseLeave -= Element_MouseLeave;
  158. element.GotFocus -= Element_GotFocus;
  159. element.LostFocus -= Element_LostFocus;
  160. ElementTarget = null;
  161. }
  162. else
  163. {
  164. element.MouseEnter += Element_MouseEnter;
  165. element.MouseLeave += Element_MouseLeave;
  166. element.GotFocus += Element_GotFocus;
  167. element.LostFocus += Element_LostFocus;
  168. ElementTarget = element;
  169. _popup.PlacementTarget = ElementTarget;
  170. }
  171. }
  172. protected override void Dispose() => SwitchPoptip(false);
  173. private void UpdateLocation()
  174. {
  175. var targetWidth = Target.RenderSize.Width;
  176. var targetHeight = Target.RenderSize.Height;
  177. Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  178. var size = DesiredSize;
  179. var width = size.Width;
  180. var height = size.Height;
  181. var offsetX = .0;
  182. var offsetY = .0;
  183. var poptip = (Poptip) GetInstance(Target);
  184. var popupPlacement = poptip.PlacementType;
  185. var popupOffsetX = poptip.HorizontalOffset;
  186. var popupOffsetY = poptip.VerticalOffset;
  187. switch (popupPlacement)
  188. {
  189. case PlacementType.LeftTop:
  190. break;
  191. case PlacementType.Left:
  192. offsetY = -(height - targetHeight) * 0.5;
  193. break;
  194. case PlacementType.LeftBottom:
  195. offsetY = -(height - targetHeight);
  196. break;
  197. case PlacementType.TopLeft:
  198. offsetX = width;
  199. offsetY = -height;
  200. break;
  201. case PlacementType.Top:
  202. offsetX = (width + targetWidth) * 0.5;
  203. offsetY = -height;
  204. break;
  205. case PlacementType.TopRight:
  206. offsetX = targetWidth;
  207. offsetY = -height;
  208. break;
  209. case PlacementType.RightTop:
  210. offsetX = width + targetWidth;
  211. break;
  212. case PlacementType.Right:
  213. offsetX = width + targetWidth;
  214. offsetY = -(height - targetHeight) * 0.5;
  215. break;
  216. case PlacementType.RightBottom:
  217. offsetX = width + targetWidth;
  218. offsetY = -(height - targetHeight);
  219. break;
  220. case PlacementType.BottomLeft:
  221. offsetX = width;
  222. offsetY = targetHeight;
  223. break;
  224. case PlacementType.Bottom:
  225. offsetX = (width + targetWidth) * 0.5;
  226. offsetY = targetHeight;
  227. break;
  228. case PlacementType.BottomRight:
  229. offsetX = targetWidth;
  230. offsetY = targetHeight;
  231. break;
  232. default:
  233. throw new ArgumentOutOfRangeException();
  234. }
  235. _popup.HorizontalOffset = offsetX + popupOffsetX;
  236. _popup.VerticalOffset = offsetY + popupOffsetY;
  237. }
  238. private void SwitchPoptip(bool isShow)
  239. {
  240. if (isShow)
  241. {
  242. if (!GetIsInstance(Target))
  243. {
  244. SetCurrentValue(ContentProperty, GetContent(Target));
  245. SetCurrentValue(PlacementTypeProperty, GetPlacement(Target));
  246. SetCurrentValue(HitModeProperty, GetHitMode(Target));
  247. SetCurrentValue(HorizontalOffsetProperty, GetHorizontalOffset(Target));
  248. SetCurrentValue(VerticalOffsetProperty, GetVerticalOffset(Target));
  249. SetCurrentValue(IsOpenProperty, GetIsOpen(Target));
  250. }
  251. _popup.PlacementTarget = Target;
  252. UpdateLocation();
  253. }
  254. ResetTimer();
  255. var delay = Delay;
  256. if (!isShow || HitMode != HitMode.Hover || MathHelper.IsVerySmall(delay))
  257. {
  258. _popup.IsOpen = isShow;
  259. Target.SetCurrentValue(IsOpenProperty, isShow);
  260. }
  261. else
  262. {
  263. _openTimer = new DispatcherTimer
  264. {
  265. Interval = TimeSpan.FromMilliseconds(delay)
  266. };
  267. _openTimer.Tick += OpenTimer_Tick;
  268. _openTimer.Start();
  269. }
  270. }
  271. private void ResetTimer()
  272. {
  273. if (_openTimer != null)
  274. {
  275. _openTimer.Stop();
  276. _openTimer = null;
  277. }
  278. }
  279. private void OpenTimer_Tick(object sender, EventArgs e)
  280. {
  281. _popup.IsOpen = true;
  282. Target.SetCurrentValue(IsOpenProperty, true);
  283. ResetTimer();
  284. }
  285. private void Element_MouseEnter(object sender, MouseEventArgs e)
  286. {
  287. var hitMode = GetIsInstance(Target) ? HitMode : GetHitMode(Target);
  288. if (hitMode != HitMode.Hover) return;
  289. SwitchPoptip(true);
  290. }
  291. private void Element_MouseLeave(object sender, MouseEventArgs e)
  292. {
  293. var hitMode = GetIsInstance(Target) ? HitMode : GetHitMode(Target);
  294. if (hitMode != HitMode.Hover) return;
  295. SwitchPoptip(false);
  296. }
  297. private void Element_GotFocus(object sender, RoutedEventArgs e)
  298. {
  299. var hitMode = GetIsInstance(Target) ? HitMode : GetHitMode(Target);
  300. if (hitMode != HitMode.Focus) return;
  301. SwitchPoptip(true);
  302. }
  303. private void Element_LostFocus(object sender, RoutedEventArgs e)
  304. {
  305. var hitMode = GetIsInstance(Target) ? HitMode : GetHitMode(Target);
  306. if (hitMode != HitMode.Focus) return;
  307. SwitchPoptip(false);
  308. }
  309. }