CalendarControl.cs 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631
  1. using InABox.Core;
  2. using InABox.WPF;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Collections.Specialized;
  7. using System.ComponentModel;
  8. using System.Diagnostics.CodeAnalysis;
  9. using System.Linq;
  10. using System.Printing;
  11. using System.Reactive.Linq;
  12. using System.Runtime.CompilerServices;
  13. using System.Text;
  14. using System.Threading;
  15. using System.Threading.Tasks;
  16. using System.Windows;
  17. using System.Windows.Controls;
  18. using System.Windows.Controls.Primitives;
  19. using System.Windows.Data;
  20. using System.Windows.Input;
  21. using System.Windows.Media;
  22. using System.Windows.Media.Animation;
  23. using System.Windows.Shapes;
  24. namespace InABox.Wpf;
  25. public class CalendarBlockEventArgs(object? value, object column, DateTime date, TimeSpan start, TimeSpan end, Point point) : EventArgs
  26. {
  27. public object? Value { get; set; } = value;
  28. public DateTime Date { get; set; } = date;
  29. public object Column { get; set; } = column;
  30. public TimeSpan Start { get; set; } = start;
  31. public TimeSpan End { get; set; } = end;
  32. public Point Point { get; set; } = point;
  33. private ContextMenu? _menu;
  34. public ContextMenu Menu
  35. {
  36. get
  37. {
  38. _menu ??= new ContextMenu();
  39. return _menu;
  40. }
  41. }
  42. public ContextMenu? GetContextMenu()
  43. {
  44. return _menu;
  45. }
  46. }
  47. public class CalendarRegion : INotifyPropertyChanged
  48. {
  49. private DateTime _date;
  50. public DateTime Date
  51. {
  52. get => _date;
  53. set
  54. {
  55. _date = value;
  56. OnPropertyChanged();
  57. }
  58. }
  59. private object? _column;
  60. public object? Column
  61. {
  62. get => _column;
  63. set
  64. {
  65. _column = value;
  66. OnPropertyChanged();
  67. }
  68. }
  69. private TimeSpan _start;
  70. public TimeSpan Start
  71. {
  72. get => _start;
  73. set
  74. {
  75. _start = value;
  76. OnPropertyChanged();
  77. }
  78. }
  79. private TimeSpan _end;
  80. public TimeSpan End
  81. {
  82. get => _end;
  83. set
  84. {
  85. _end = value;
  86. OnPropertyChanged();
  87. }
  88. }
  89. private Brush? _background;
  90. public Brush? Background
  91. {
  92. get => _background;
  93. set
  94. {
  95. _background = value;
  96. OnPropertyChanged();
  97. }
  98. }
  99. public event PropertyChangedEventHandler? PropertyChanged;
  100. private void OnPropertyChanged([CallerMemberName] string? name = null)
  101. {
  102. PropertyChanged?.Invoke(this, new(name));
  103. }
  104. }
  105. public enum CalendarColumnWidthMode
  106. {
  107. ConstantColumns,
  108. ConstantSubColumns
  109. }
  110. public class CalendarControl : ContentControl
  111. {
  112. public static readonly DependencyProperty StartHourProperty =
  113. DependencyProperty.Register(nameof(StartHour), typeof(TimeSpan), typeof(CalendarControl), new(TimeSpan.Zero, Render_Changed));
  114. public static readonly DependencyProperty EndHourProperty =
  115. DependencyProperty.Register(nameof(EndHour), typeof(TimeSpan), typeof(CalendarControl), new(TimeSpan.FromHours(24), Render_Changed));
  116. public static readonly DependencyProperty RowHeightProperty =
  117. DependencyProperty.Register(nameof(RowHeight), typeof(double), typeof(CalendarControl), new(100.0, Render_Changed));
  118. public static readonly DependencyProperty ZoomProperty =
  119. DependencyProperty.Register(nameof(Zoom), typeof(double), typeof(CalendarControl), new(1.0, Render_Changed));
  120. public static readonly DependencyProperty MinimumColumnWidthProperty =
  121. DependencyProperty.Register(nameof(MinimumColumnWidth), typeof(double), typeof(CalendarControl), new(50.0, Render_Changed));
  122. public static readonly DependencyProperty ColumnWidthModeProperty =
  123. DependencyProperty.Register(nameof(ColumnWidthMode), typeof(CalendarColumnWidthMode), typeof(CalendarControl), new(CalendarColumnWidthMode.ConstantSubColumns, Render_Changed));
  124. public static readonly DependencyProperty MinimumBlockHeightProperty =
  125. DependencyProperty.Register(nameof(MinimumBlockHeight), typeof(double), typeof(CalendarControl), new(5.0, Render_Changed));
  126. public static readonly DependencyProperty RowIntervalProperty =
  127. DependencyProperty.Register(nameof(RowInterval), typeof(TimeSpan), typeof(CalendarControl), new(TimeSpan.FromHours(1), Render_Changed));
  128. public static readonly DependencyProperty ItemsSourceProperty =
  129. DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(CalendarControl), new(ItemsSource_Changed));
  130. public static readonly DependencyProperty ItemTemplateProperty =
  131. DependencyProperty.Register(nameof(ItemTemplate), typeof(DataTemplate), typeof(CalendarControl));
  132. public static readonly DependencyProperty DateTemplateProperty =
  133. DependencyProperty.Register(nameof(DateTemplate), typeof(DataTemplate), typeof(CalendarControl));
  134. public static readonly DependencyProperty HeaderTemplateProperty =
  135. DependencyProperty.Register(nameof(HeaderTemplate), typeof(DataTemplate), typeof(CalendarControl));
  136. public static readonly DependencyProperty ColumnsProperty =
  137. DependencyProperty.Register(nameof(Columns), typeof(IEnumerable), typeof(CalendarControl), new(Columns_Changed));
  138. public static readonly DependencyProperty DatesProperty =
  139. DependencyProperty.Register(nameof(Dates), typeof(IEnumerable<DateTime>), typeof(CalendarControl), new(Dates_Changed));
  140. public static readonly DependencyProperty RegionsProperty =
  141. DependencyProperty.Register(nameof(Regions), typeof(IEnumerable<CalendarRegion>), typeof(CalendarControl), new(Regions_Changed));
  142. public TimeSpan StartHour
  143. {
  144. get => (TimeSpan)GetValue(StartHourProperty);
  145. set => SetValue(StartHourProperty, value);
  146. }
  147. public TimeSpan EndHour
  148. {
  149. get => (TimeSpan)GetValue(EndHourProperty);
  150. set => SetValue(EndHourProperty, value);
  151. }
  152. public double MinimumColumnWidth
  153. {
  154. get => (double)GetValue(MinimumColumnWidthProperty);
  155. set => SetValue(MinimumColumnWidthProperty, value);
  156. }
  157. public CalendarColumnWidthMode ColumnWidthMode
  158. {
  159. get => (CalendarColumnWidthMode)GetValue(ColumnWidthModeProperty);
  160. set => SetValue(ColumnWidthModeProperty, value);
  161. }
  162. public double MinimumBlockHeight
  163. {
  164. get => (double)GetValue(MinimumBlockHeightProperty);
  165. set => SetValue(MinimumBlockHeightProperty, value);
  166. }
  167. public double RowHeight
  168. {
  169. get => (double)GetValue(RowHeightProperty);
  170. set => SetValue(RowHeightProperty, value);
  171. }
  172. /// <summary>
  173. /// Zoom level for the calendar; the calculated row height will be multiplied by this number to get the rendered row height.
  174. /// </summary>
  175. public double Zoom
  176. {
  177. get => (double)GetValue(ZoomProperty);
  178. set => SetValue(ZoomProperty, value);
  179. }
  180. public TimeSpan RowInterval
  181. {
  182. get => (TimeSpan)GetValue(RowIntervalProperty);
  183. set => SetValue(RowIntervalProperty, value);
  184. }
  185. public BindingBase? DateMapping { get; set; }
  186. public BindingBase? ColumnMapping { get; set; }
  187. public BindingBase? StartTimeMapping { get; set; }
  188. public BindingBase? EndTimeMapping { get; set; }
  189. public IEnumerable? ItemsSource
  190. {
  191. get => GetValue(ItemsSourceProperty) as IEnumerable;
  192. set => SetValue(ItemsSourceProperty, value);
  193. }
  194. public DataTemplate? ItemTemplate
  195. {
  196. get => GetValue(ItemTemplateProperty) as DataTemplate;
  197. set => SetValue(ItemTemplateProperty, value);
  198. }
  199. public DataTemplate? DateTemplate
  200. {
  201. get => GetValue(DateTemplateProperty) as DataTemplate;
  202. set => SetValue(DateTemplateProperty, value);
  203. }
  204. public DataTemplate? HeaderTemplate
  205. {
  206. get => GetValue(HeaderTemplateProperty) as DataTemplate;
  207. set => SetValue(HeaderTemplateProperty, value);
  208. }
  209. public IEnumerable? Columns
  210. {
  211. get => GetValue(ColumnsProperty) as IEnumerable;
  212. set => SetValue(ColumnsProperty, value);
  213. }
  214. public IEnumerable<DateTime>? Dates
  215. {
  216. get => GetValue(DatesProperty) as IEnumerable<DateTime>;
  217. set => SetValue(DatesProperty, value);
  218. }
  219. public IEnumerable<CalendarRegion>? Regions
  220. {
  221. get => GetValue(RegionsProperty) as IEnumerable<CalendarRegion>;
  222. set => SetValue(RegionsProperty, value);
  223. }
  224. public event EventHandler<CalendarBlockEventArgs>? BlockClicked;
  225. public event EventHandler<CalendarBlockEventArgs>? BlockRightClicked;
  226. public event EventHandler<CalendarBlockEventArgs>? BlockHeld;
  227. public delegate TimeSpan? GetFillBlockHandler(DateTime date, object? column, TimeSpan time);
  228. public GetFillBlockHandler? GetFillBlockStart;
  229. public GetFillBlockHandler? GetFillBlockEnd;
  230. private static void Columns_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  231. {
  232. if (d is not CalendarControl calendar) return;
  233. if(e.OldValue is INotifyCollectionChanged oldNotify)
  234. {
  235. oldNotify.CollectionChanged -= calendar.ColumnsCollection_Changed;
  236. }
  237. calendar.Render(columnsChanged: true);
  238. if(e.NewValue is INotifyCollectionChanged notify)
  239. {
  240. notify.CollectionChanged += calendar.ColumnsCollection_Changed;
  241. }
  242. }
  243. private static void Dates_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  244. {
  245. if (d is not CalendarControl calendar) return;
  246. if(e.OldValue is INotifyCollectionChanged oldNotify)
  247. {
  248. oldNotify.CollectionChanged -= calendar.DatesCollection_Changed;
  249. }
  250. calendar.Render(columnsChanged: true);
  251. if(e.NewValue is INotifyCollectionChanged notify)
  252. {
  253. notify.CollectionChanged += calendar.DatesCollection_Changed;
  254. }
  255. }
  256. private static void Regions_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  257. {
  258. if (d is not CalendarControl calendar) return;
  259. if(e.OldValue is IEnumerable<CalendarRegion> regions)
  260. {
  261. foreach(var item in regions)
  262. {
  263. item.PropertyChanged -= calendar.Region_PropertyChanged;
  264. }
  265. }
  266. if(e.OldValue is INotifyCollectionChanged oldNotify)
  267. {
  268. oldNotify.CollectionChanged -= calendar.RegionsCollection_Changed;
  269. }
  270. calendar.UpdateRegionBinding();
  271. calendar.Render();
  272. if(e.NewValue is INotifyCollectionChanged notify)
  273. {
  274. notify.CollectionChanged += calendar.RegionsCollection_Changed;
  275. }
  276. }
  277. private void UpdateRegionBinding()
  278. {
  279. if(Regions is null)
  280. {
  281. return;
  282. }
  283. foreach(var region in Regions)
  284. {
  285. region.PropertyChanged -= Region_PropertyChanged;
  286. region.PropertyChanged += Region_PropertyChanged;
  287. }
  288. }
  289. private void Region_PropertyChanged(object? sender, PropertyChangedEventArgs e)
  290. {
  291. if(e.PropertyName == nameof(CalendarRegion.Start)
  292. || e.PropertyName == nameof(CalendarRegion.End)
  293. || e.PropertyName == nameof(CalendarRegion.Date)
  294. || e.PropertyName == nameof(CalendarRegion.Column))
  295. {
  296. Render();
  297. }
  298. }
  299. private void RegionsCollection_Changed(object? sender, NotifyCollectionChangedEventArgs e)
  300. {
  301. UpdateRegionBinding();
  302. Render();
  303. }
  304. private void DatesCollection_Changed(object? sender, NotifyCollectionChangedEventArgs e)
  305. {
  306. Render(columnsChanged: true);
  307. }
  308. private void ColumnsCollection_Changed(object? sender, NotifyCollectionChangedEventArgs e)
  309. {
  310. Render(columnsChanged: true);
  311. }
  312. private static void Render_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  313. {
  314. if (d is not CalendarControl calendar) return;
  315. calendar.Render();
  316. }
  317. private ScrollViewer DateScroll;
  318. private ScrollViewer HeaderScroll;
  319. private ScrollViewer LabelScroll;
  320. private ScrollViewer MainScroll;
  321. private ScrollBar VerticalScroll;
  322. private ScrollBar HorizontalScroll;
  323. private Canvas DateCanvas;
  324. private Canvas HeaderCanvas;
  325. private Canvas LabelCanvas;
  326. private Canvas MainCanvas;
  327. private Border HeaderBorder;
  328. public CalendarControl()
  329. {
  330. var grid = new Grid();
  331. grid.AddRow(GridUnitType.Auto); // Date
  332. grid.AddRow(GridUnitType.Auto); // Column
  333. grid.AddRow(GridUnitType.Star);
  334. grid.AddRow(GridUnitType.Auto); // Scroll Bar
  335. grid.AddColumn(GridUnitType.Auto); // Times
  336. grid.AddColumn(GridUnitType.Star);
  337. grid.AddColumn(GridUnitType.Auto); // ScrollBar
  338. DateScroll = new ScrollViewer
  339. {
  340. HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
  341. VerticalScrollBarVisibility = ScrollBarVisibility.Disabled
  342. };
  343. DateCanvas = new Canvas
  344. {
  345. };
  346. DateScroll.Content = DateCanvas;
  347. HeaderScroll = new ScrollViewer
  348. {
  349. HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
  350. VerticalScrollBarVisibility = ScrollBarVisibility.Disabled
  351. };
  352. HeaderCanvas = new Canvas
  353. {
  354. };
  355. HeaderScroll.Content = HeaderCanvas;
  356. LabelScroll = new ScrollViewer
  357. {
  358. VerticalScrollBarVisibility = ScrollBarVisibility.Hidden,
  359. HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled
  360. };
  361. LabelCanvas = new Canvas
  362. {
  363. Margin = new(2, 0, 2, 0)
  364. };
  365. LabelScroll.Content = LabelCanvas;
  366. MainScroll = new ScrollViewer
  367. {
  368. HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
  369. VerticalScrollBarVisibility = ScrollBarVisibility.Hidden
  370. };
  371. MainCanvas = new Canvas
  372. {
  373. };
  374. MainScroll.Content = MainCanvas;
  375. MainScroll.SizeChanged += MainScroll_SizeChanged;
  376. MainScroll.ScrollChanged += MainScroll_ScrollChanged;
  377. MainScroll.PreviewMouseWheel += MainScroll_PreviewMouseWheel;
  378. grid.AddChild(
  379. new Border
  380. {
  381. BorderBrush = Colors.LightGray.ToBrush(),
  382. BorderThickness = new(0, 0, 1, 0)
  383. },
  384. row: 0, rowSpan: 2,
  385. column: 0);
  386. grid.AddChild(
  387. new Border
  388. {
  389. BorderBrush = Colors.LightGray.ToBrush(),
  390. BorderThickness = new(0, 0, 0, 1),
  391. Child = DateScroll
  392. },
  393. row: 0,
  394. column: 1, colSpan: 2);
  395. HeaderBorder = new Border
  396. {
  397. BorderBrush = Colors.LightGray.ToBrush(),
  398. BorderThickness = new(0, 0, 0, 1),
  399. Child = HeaderScroll
  400. };
  401. grid.AddChild(
  402. HeaderBorder,
  403. row: 1,
  404. column: 1, colSpan: 2);
  405. grid.AddChild(
  406. new Border
  407. {
  408. BorderBrush = Colors.LightGray.ToBrush(),
  409. BorderThickness = new(0, 0, 1, 0),
  410. Child = LabelScroll
  411. },
  412. row: 2, rowSpan: 2,
  413. column: 0);
  414. grid.AddChild(
  415. new Border
  416. {
  417. Child = MainScroll
  418. },
  419. row: 2, rowSpan: 2,
  420. column: 1, colSpan: 2);
  421. VerticalScroll = new ScrollBar();
  422. VerticalScroll.Scroll += VerticalScroll_Scroll;
  423. VerticalScroll.Opacity = 0.5;
  424. VerticalScroll.MouseEnter += Scroll_MouseEnter;
  425. VerticalScroll.MouseLeave += Scroll_MouseLeave;
  426. HorizontalScroll = new ScrollBar
  427. {
  428. Orientation = Orientation.Horizontal
  429. };
  430. HorizontalScroll.Scroll += HorizontalScroll_Scroll;
  431. HorizontalScroll.Opacity = 0.5;
  432. HorizontalScroll.MouseEnter += Scroll_MouseEnter;
  433. HorizontalScroll.MouseLeave += Scroll_MouseLeave;
  434. grid.AddChild(VerticalScroll, 2, 2);
  435. grid.AddChild(HorizontalScroll, 3, 1);
  436. Content = grid;
  437. this.PreviewMouseLeftButtonDown += CalendarControl_PreviewMouseLeftButtonDown;
  438. }
  439. private void CalendarControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  440. {
  441. ClearSelectedRectangle();
  442. }
  443. private (double x, double y) _currentScroll;
  444. private void Scroll_MouseLeave(object sender, MouseEventArgs e)
  445. {
  446. if(sender is ScrollBar bar)
  447. {
  448. bar.Opacity = 0.5;
  449. }
  450. }
  451. private void Scroll_MouseEnter(object sender, MouseEventArgs e)
  452. {
  453. if(sender is ScrollBar bar)
  454. {
  455. bar.Opacity = 1;
  456. }
  457. }
  458. private void MainScroll_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
  459. {
  460. if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
  461. {
  462. MainScroll.ScrollToHorizontalOffset(MainScroll.HorizontalOffset - e.Delta);
  463. e.Handled = true;
  464. }
  465. }
  466. private void HorizontalScroll_Scroll(object sender, ScrollEventArgs e)
  467. {
  468. MainScroll.ScrollToHorizontalOffset(e.NewValue);
  469. }
  470. private void VerticalScroll_Scroll(object sender, ScrollEventArgs e)
  471. {
  472. MainScroll.ScrollToVerticalOffset(e.NewValue);
  473. }
  474. private void MainScroll_ScrollChanged(object sender, ScrollChangedEventArgs e)
  475. {
  476. LabelScroll.ScrollToVerticalOffset(e.VerticalOffset);
  477. HeaderScroll.ScrollToHorizontalOffset(e.HorizontalOffset);
  478. DateScroll.ScrollToHorizontalOffset(e.HorizontalOffset);
  479. HorizontalScroll.Value = e.HorizontalOffset;
  480. VerticalScroll.Value = e.VerticalOffset;
  481. _currentScroll = (e.HorizontalOffset, e.VerticalOffset);
  482. }
  483. private static void ItemsSource_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
  484. {
  485. if (d is not CalendarControl calendar) return;
  486. if(e.OldValue is INotifyCollectionChanged oldNotify)
  487. {
  488. oldNotify.CollectionChanged -= calendar.Collection_Changed;
  489. }
  490. calendar.Render(itemsChanged: true);
  491. if(calendar.ItemsSource is INotifyCollectionChanged notify)
  492. {
  493. notify.CollectionChanged += calendar.Collection_Changed;
  494. }
  495. }
  496. private void Collection_Changed(object? sender, NotifyCollectionChangedEventArgs e)
  497. {
  498. Render(itemsChanged: true);
  499. }
  500. private void MainScroll_SizeChanged(object sender, SizeChangedEventArgs e)
  501. {
  502. Render();
  503. }
  504. private class Block : Border, INotifyPropertyChanged
  505. {
  506. public static readonly DependencyProperty ColumnProperty =
  507. DependencyProperty.Register(nameof(Column), typeof(object), typeof(Block), new(OnPropertyChangedHandler));
  508. public static readonly DependencyProperty DateProperty =
  509. DependencyProperty.Register(nameof(Date), typeof(DateTime), typeof(Block), new(OnPropertyChangedHandler));
  510. public static readonly DependencyProperty StartTimeProperty =
  511. DependencyProperty.Register(nameof(StartTime), typeof(TimeSpan), typeof(Block), new(OnPropertyChangedHandler));
  512. public static readonly DependencyProperty EndTimeProperty =
  513. DependencyProperty.Register(nameof(EndTime), typeof(TimeSpan), typeof(Block), new(OnPropertyChangedHandler));
  514. public int ColumnIndex { get; set; } = -1;
  515. public int NColumns { get; set; } = -1;
  516. public object Column
  517. {
  518. get => GetValue(ColumnProperty);
  519. set => SetValue(ColumnProperty, value);
  520. }
  521. public DateTime Date
  522. {
  523. get => (DateTime)GetValue(DateProperty);
  524. set => SetValue(DateProperty, value);
  525. }
  526. public TimeSpan StartTime
  527. {
  528. get => (TimeSpan)GetValue(StartTimeProperty);
  529. set => SetValue(StartTimeProperty, value);
  530. }
  531. public TimeSpan EndTime
  532. {
  533. get => (TimeSpan)GetValue(EndTimeProperty);
  534. set => SetValue(EndTimeProperty, value);
  535. }
  536. private ContentControl _contentControl;
  537. public ContentControl ContentControl => _contentControl;
  538. public object? Content => _contentControl.Content;
  539. public override string ToString()
  540. {
  541. return $"Block({Column}: {StartTime:hh\\:mm} - {EndTime:hh\\:mm})";
  542. }
  543. public Block(CalendarControl parent, object content)
  544. {
  545. _contentControl = new ContentControl
  546. {
  547. Content = content
  548. };
  549. _contentControl.Bind(ContentControl.ContentTemplateProperty, parent, x => x.ItemTemplate);
  550. Child = _contentControl;
  551. }
  552. public event PropertyChangedEventHandler? PropertyChanged;
  553. private static void OnPropertyChangedHandler(DependencyObject d, DependencyPropertyChangedEventArgs e)
  554. {
  555. if (d is not Block block) return;
  556. block.PropertyChanged?.Invoke(block, new(e.Property.Name));
  557. }
  558. }
  559. private class Column
  560. {
  561. public List<Block> Blocks { get; set; } = new();
  562. public List<List<Block>>? Columns { get; set; } = null;
  563. }
  564. private List<Block> _blockList = new();
  565. private Dictionary<DateTime, Dictionary<object, Column>> _blocks = new();
  566. private List<IDisposable> _oldSubscriptions = new();
  567. private List<object> _columns = new();
  568. private List<(DateTime, object)> _columnList = new();
  569. private IEnumerable<KeyValuePair<(DateTime, object), Column>> _allColumns =>
  570. _blocks.SelectMany(x => x.Value.Select(y => new KeyValuePair<(DateTime, object), Column>((x.Key, y.Key), y.Value)));
  571. private class ActionDisposable(Action onDispose) : IDisposable
  572. {
  573. public void Dispose()
  574. {
  575. onDispose();
  576. }
  577. }
  578. private bool RecreateBlocksList()
  579. {
  580. if (ItemsSource is null) return false;
  581. var columnBinding = ColumnMapping;
  582. var dateBinding = DateMapping;
  583. var startBinding = StartTimeMapping;
  584. var endBinding = EndTimeMapping;
  585. if(columnBinding is null || dateBinding is null || startBinding is null || endBinding is null)
  586. {
  587. return false;
  588. }
  589. foreach(var subscription in _oldSubscriptions)
  590. {
  591. subscription.Dispose();
  592. }
  593. _oldSubscriptions.Clear();
  594. _blockList.Clear();
  595. foreach(var item in ItemsSource)
  596. {
  597. if (item is null) continue;
  598. var block = new Block(this, item);
  599. block.SetBinding(Block.ColumnProperty, columnBinding);
  600. block.SetBinding(Block.StartTimeProperty, startBinding);
  601. block.SetBinding(Block.EndTimeProperty, endBinding);
  602. block.SetBinding(Block.DateProperty, dateBinding);
  603. block.DataContext = item;
  604. block.Background = Colors.Transparent.ToBrush();
  605. block.PropertyChanged += Block_PropertyChanged;
  606. _oldSubscriptions.Add(new ActionDisposable(() => block.PropertyChanged -= Block_PropertyChanged));
  607. block.ContentControl.MouseLeftButtonDown += (o, e) =>
  608. {
  609. Block_MouseLeftButtonDown(block, e);
  610. };
  611. block.ContentControl.MouseLeftButtonUp += (o, e) =>
  612. {
  613. Block_MouseLeftButtonUp(block, e);
  614. };
  615. block.ContentControl.MouseRightButtonUp += (o, e) =>
  616. {
  617. Block_MouseRightButtonUp(block, e);
  618. };
  619. _blockList.Add(block);
  620. }
  621. return true;
  622. }
  623. private void RefreshColumns(bool itemsChanged = false)
  624. {
  625. if (itemsChanged)
  626. {
  627. if (!RecreateBlocksList()) return;
  628. }
  629. _blocks.Clear();
  630. _columns.Clear();
  631. var autoGenerateColumns = true;
  632. if(Columns is not null)
  633. {
  634. autoGenerateColumns = false;
  635. foreach(var column in Columns)
  636. {
  637. if(column is null) continue;
  638. _columns.Add(column);
  639. }
  640. }
  641. var autoGenerateDates = true;
  642. if(Dates is not null)
  643. {
  644. autoGenerateDates = false;
  645. foreach(var date in Dates)
  646. {
  647. if(!_blocks.TryGetValue(date, out var dateBlocks))
  648. {
  649. dateBlocks = new();
  650. _blocks.Add(date, dateBlocks);
  651. if (!autoGenerateColumns)
  652. {
  653. foreach(var col in _columns)
  654. {
  655. if(!dateBlocks.TryAdd(col, new()))
  656. {
  657. throw new Exception($"Duplicate column {col} in Calendar");
  658. }
  659. }
  660. }
  661. }
  662. }
  663. }
  664. foreach(var block in _blockList)
  665. {
  666. var column = block.Column;
  667. var date = block.Date;
  668. if(column is null)
  669. {
  670. continue;
  671. }
  672. if(!_blocks.TryGetValue(date, out var dateBlocks))
  673. {
  674. if (!autoGenerateDates) continue;
  675. dateBlocks = new();
  676. _blocks.Add(date, dateBlocks);
  677. if (!autoGenerateColumns)
  678. {
  679. foreach(var col in _columns)
  680. {
  681. if(!dateBlocks.TryAdd(col, new()))
  682. {
  683. throw new Exception($"Duplicate column {col} in Calendar");
  684. }
  685. }
  686. }
  687. }
  688. if(!dateBlocks.TryGetValue(column, out var columnBlocks))
  689. {
  690. if (!autoGenerateColumns) continue;
  691. columnBlocks = new();
  692. dateBlocks.Add(column, columnBlocks);
  693. _columns.Add(column);
  694. }
  695. columnBlocks.Blocks.Add(block);
  696. }
  697. HeaderBorder.Visibility = _columns.Count <= 1 ? Visibility.Collapsed : Visibility.Visible;
  698. }
  699. private void Block_PropertyChanged(object? sender, PropertyChangedEventArgs e)
  700. {
  701. if (sender is not Block block) return;
  702. if(e.PropertyName == nameof(Block.Column)
  703. || e.PropertyName == nameof(Block.Date))
  704. {
  705. Render(columnsChanged: true);
  706. }
  707. else if(e.PropertyName == nameof(Block.StartTime)
  708. || e.PropertyName == nameof(Block.EndTime))
  709. {
  710. UpdateBlock(block);
  711. }
  712. }
  713. private double _colWidth;
  714. private double _colSpace;
  715. private double _rowHeight;
  716. private bool _columnsChanged = false;
  717. private bool _itemsChanged = false;
  718. private bool _recalculatePositions = false;
  719. private bool _rerendering = false;
  720. private void Render(bool columnsChanged = false, bool itemsChanged = false, bool recalculatePositions = false)
  721. {
  722. _columnsChanged = _columnsChanged || columnsChanged;
  723. _itemsChanged = _itemsChanged || itemsChanged;
  724. _recalculatePositions = _recalculatePositions || recalculatePositions;
  725. if (!_rerendering)
  726. {
  727. _rerendering = true;
  728. Dispatcher.BeginInvoke(DoRender);
  729. }
  730. }
  731. private double _lastColWidth;
  732. private CalendarColumnWidthMode _lastColumnWidthMode;
  733. private void DoRender()
  734. {
  735. _rerendering = false;
  736. var itemsChanged = _itemsChanged;
  737. var columnsChanged = _columnsChanged;
  738. var recalculatePositions = _recalculatePositions;
  739. _columnsChanged = false;
  740. _itemsChanged = false;
  741. _recalculatePositions = false;
  742. if (itemsChanged || columnsChanged)
  743. {
  744. RefreshColumns(itemsChanged: itemsChanged);
  745. }
  746. if (recalculatePositions)
  747. {
  748. foreach(var (column, columnBlocks) in _allColumns)
  749. {
  750. columnBlocks.Columns = null;
  751. }
  752. }
  753. var nRows = ((EndHour - StartHour).TotalHours / RowInterval.TotalHours);
  754. var rowHeight = Math.Max(RowHeight, MainScroll.ActualHeight / nRows) * Zoom;
  755. MainCanvas.Children.Clear();
  756. MainCanvas.Height = rowHeight * nRows;
  757. var minColWidth = MinimumColumnWidth;
  758. var colSpace = 1;
  759. var nColumns = 0;
  760. var nSizingColumns = 0;
  761. foreach (var (column, columnBlocks) in _allColumns)
  762. {
  763. columnBlocks.Columns ??= RecalculateBlockPositionsForDay(columnBlocks.Blocks);
  764. nColumns += columnBlocks.Columns.Count;
  765. nSizingColumns += ColumnWidthMode switch
  766. {
  767. CalendarColumnWidthMode.ConstantSubColumns => columnBlocks.Columns.Count,
  768. CalendarColumnWidthMode.ConstantColumns or _ => 1,
  769. };
  770. }
  771. var colWidth = (Math.Max((MainScroll.ActualWidth - colSpace * (_blocks.Sum(x => x.Value.Count) - 1)) / nSizingColumns, minColWidth));
  772. var lastColWidth = _lastColWidth;
  773. _lastColWidth = colWidth;
  774. var lastColWidthMode = _lastColumnWidthMode;
  775. _lastColumnWidthMode = ColumnWidthMode;
  776. var updateHeaders = colWidth != lastColWidth || lastColWidthMode != ColumnWidthMode || columnsChanged || itemsChanged;
  777. if (updateHeaders)
  778. {
  779. HeaderCanvas.Children.Clear();
  780. DateCanvas.Children.Clear();
  781. }
  782. ClearHeldSelection();
  783. _rowHeight = rowHeight;
  784. _colWidth = colWidth;
  785. _colSpace = colSpace;
  786. var minY = double.MaxValue;
  787. var colX = 0.0;
  788. var dates = _blocks.Keys.ToArray();
  789. Array.Sort(dates);
  790. var regions = (Regions ?? Enumerable.Empty<CalendarRegion>())
  791. .GroupByDictionary(x => (x.Date, x.Column));
  792. _columnList.Clear();
  793. var columnIdx = 0;
  794. foreach(var date in dates)
  795. {
  796. if (!_blocks.TryGetValue(date, out var dateBlocks)) continue;
  797. if (updateHeaders)
  798. {
  799. var nDateColumns = ColumnWidthMode switch
  800. {
  801. CalendarColumnWidthMode.ConstantSubColumns => dateBlocks.Sum(x => x.Value.Columns!.Count),
  802. CalendarColumnWidthMode.ConstantColumns or _ => dateBlocks.Count,
  803. };
  804. var dateHeader = new ContentControl
  805. {
  806. Content = date,
  807. Width = colWidth * nDateColumns + colSpace * (nDateColumns - 1)
  808. };
  809. dateHeader.Bind(ContentControl.ContentTemplateProperty, this, x => x.DateTemplate);
  810. Canvas.SetLeft(dateHeader, colX);
  811. DateCanvas.Children.Add(dateHeader);
  812. dateHeader.SizeChanged += DateHeader_SizeChanged;
  813. }
  814. var dateColumnIndex = 0;
  815. foreach(var columnKey in _columns)
  816. {
  817. if(!dateBlocks.TryGetValue(columnKey, out var columnBlocks))
  818. {
  819. continue;
  820. }
  821. _columnList.Add((date, columnKey));
  822. var (nCols, subColWidth) = ColumnWidthMode switch
  823. {
  824. CalendarColumnWidthMode.ConstantSubColumns => (columnBlocks.Columns!.Count, colWidth),
  825. CalendarColumnWidthMode.ConstantColumns or _ => (1, colWidth / columnBlocks.Columns!.Count),
  826. };
  827. if (updateHeaders)
  828. {
  829. var columnHeader = new ContentControl
  830. {
  831. Content = columnKey,
  832. Width = colWidth * nCols,
  833. };
  834. columnHeader.Bind(ContentControl.ContentTemplateProperty, this, x => x.HeaderTemplate);
  835. Canvas.SetLeft(columnHeader, colX);
  836. HeaderCanvas.Children.Add(columnHeader);
  837. columnHeader.SizeChanged += ColumnHeader_SizeChanged;
  838. }
  839. // Add regions
  840. if(regions.TryGetValue((date, columnKey), out var columnRegions))
  841. {
  842. foreach(var region in columnRegions)
  843. {
  844. var rectangle = new Rectangle
  845. {
  846. Width = colWidth * nCols,
  847. Height = ((region.End - region.Start).TotalHours / RowInterval.TotalHours) * rowHeight,
  848. };
  849. rectangle.Bind(Rectangle.FillProperty, region, x => x.Background);
  850. Canvas.SetLeft(rectangle, colX);
  851. Canvas.SetTop(rectangle, ((region.Start - StartHour).TotalHours / RowInterval.TotalHours) * rowHeight);
  852. MainCanvas.Children.Add(rectangle);
  853. }
  854. }
  855. // Add cell placeholders
  856. var rowIdx = 0;
  857. for(var time = StartHour; time < EndHour; time += RowInterval)
  858. {
  859. var rectangle = new Rectangle
  860. {
  861. Width = colWidth * nCols,
  862. Height = rowHeight,
  863. Fill = new SolidColorBrush(Colors.Transparent),
  864. };
  865. rectangle.MouseEnter += (o, e) =>
  866. {
  867. if(rectangle != _selectedRectangle)
  868. {
  869. rectangle.Fill = Colors.LightBlue.ToBrush(0.5);
  870. }
  871. };
  872. rectangle.MouseLeave += (o, e) =>
  873. {
  874. if(rectangle != _selectedRectangle)
  875. {
  876. rectangle.Fill = Colors.Transparent.ToBrush();
  877. }
  878. };
  879. rectangle.MouseLeftButtonDown += Rectangle_MouseLeftButtonDown;
  880. rectangle.MouseLeftButtonUp += Rectangle_MouseLeftButtonUp;
  881. rectangle.MouseRightButtonDown += Rectangle_MouseRightButtonDown;
  882. rectangle.MouseRightButtonUp += Rectangle_MouseRightButtonUp;
  883. Canvas.SetLeft(rectangle, colX);
  884. Canvas.SetTop(rectangle, rowIdx * rowHeight);
  885. MainCanvas.Children.Add(rectangle);
  886. ++rowIdx;
  887. }
  888. var startColX = colX;
  889. foreach(var column in columnBlocks.Columns!)
  890. {
  891. foreach(var block in column)
  892. {
  893. var blockY = GetRow(block.StartTime) * rowHeight;
  894. minY = Math.Min(blockY, minY);
  895. Canvas.SetTop(block, blockY);
  896. Canvas.SetLeft(block, colX);
  897. block.Height = Math.Max((GetRow(block.EndTime) - GetRow(block.StartTime)) * rowHeight, MinimumBlockHeight);
  898. block.Width = subColWidth * block.NColumns;
  899. MainCanvas.Children.Add(block);
  900. }
  901. if(ColumnWidthMode == CalendarColumnWidthMode.ConstantSubColumns)
  902. {
  903. colX += colWidth;
  904. }
  905. else
  906. {
  907. colX += subColWidth;
  908. }
  909. }
  910. if(ColumnWidthMode == CalendarColumnWidthMode.ConstantColumns)
  911. {
  912. colX = startColX + colWidth;
  913. }
  914. // Add Header separators
  915. if(columnIdx < nColumns - 1)
  916. {
  917. var rectangle = new Rectangle
  918. {
  919. Width = 0.75,
  920. Height = MainCanvas.Height,
  921. Fill = new SolidColorBrush(Colors.LightGray)
  922. };
  923. Canvas.SetLeft(rectangle, colX);
  924. MainCanvas.Children.Add(rectangle);
  925. if (updateHeaders)
  926. {
  927. var headRectangle = new Rectangle
  928. {
  929. Width = 0.75,
  930. Fill = new SolidColorBrush(Colors.LightGray)
  931. };
  932. headRectangle.Bind(Rectangle.HeightProperty, HeaderCanvas, x => x.ActualHeight);
  933. Canvas.SetLeft(headRectangle, colX);
  934. HeaderCanvas.Children.Add(headRectangle);
  935. if(dateColumnIndex == dateBlocks.Count - 1)
  936. {
  937. var dateRectangle = new Rectangle
  938. {
  939. Width = 0.75,
  940. Fill = new SolidColorBrush(Colors.LightGray)
  941. };
  942. dateRectangle.Bind(Rectangle.HeightProperty, DateCanvas, x => x.ActualHeight);
  943. Canvas.SetLeft(dateRectangle, colX);
  944. DateCanvas.Children.Add(dateRectangle);
  945. }
  946. }
  947. colX += colSpace;
  948. }
  949. ++dateColumnIndex;
  950. ++columnIdx;
  951. }
  952. }
  953. MainCanvas.Width = Math.Floor(colX);
  954. HeaderCanvas.Width = Math.Floor(colX);
  955. DateCanvas.Width = Math.Floor(colX);
  956. VerticalScroll.Minimum = 0;
  957. VerticalScroll.Maximum = MainCanvas.Height - MainScroll.ActualHeight;
  958. VerticalScroll.ViewportSize = MainScroll.ActualHeight;
  959. VerticalScroll.Visibility = VerticalScroll.Maximum < 1 ? Visibility.Collapsed : Visibility.Visible;
  960. HorizontalScroll.Minimum = 0;
  961. HorizontalScroll.Maximum = MainCanvas.Width - MainScroll.ActualWidth;
  962. HorizontalScroll.ViewportSize = MainScroll.ActualWidth;
  963. HorizontalScroll.Visibility = HorizontalScroll.Maximum < 1 ? Visibility.Collapsed : Visibility.Visible;
  964. if(minY == double.MaxValue)
  965. {
  966. MainScroll.ScrollToHorizontalOffset(_currentScroll.x);
  967. MainScroll.ScrollToVerticalOffset(_currentScroll.y);
  968. }
  969. else
  970. {
  971. MainScroll.ScrollToHorizontalOffset(_currentScroll.x);
  972. MainScroll.ScrollToVerticalOffset(Math.Max(minY - rowHeight / 2, _currentScroll.y));
  973. }
  974. var lines = new List<FrameworkElement>();
  975. LabelCanvas.Children.Clear();
  976. LabelCanvas.Height = MainCanvas.Height;
  977. var y = rowHeight;
  978. for(var time = StartHour + RowInterval; time < EndHour; time += RowInterval)
  979. {
  980. var rectangle = new Rectangle
  981. {
  982. Width = MainCanvas.Width,
  983. Height = 0.75,
  984. Fill = new SolidColorBrush(Colors.LightGray)
  985. };
  986. Canvas.SetLeft(rectangle, 0);
  987. Canvas.SetTop(rectangle, y);
  988. lines.Add(rectangle);
  989. var block = new TextBlock
  990. {
  991. Text = time.ToString("hh\\:mm"),
  992. Margin = new(0, -5, 0, 0),
  993. FontSize = 10
  994. };
  995. block.SizeChanged += Block_SizeChanged;
  996. Canvas.SetTop(block, y);
  997. LabelCanvas.Children.Add(block);
  998. y += rowHeight;
  999. }
  1000. for(var i = 0; i < lines.Count; ++i)
  1001. {
  1002. MainCanvas.Children.Insert(i, lines[i]);
  1003. }
  1004. }
  1005. private bool TryGetBlockFromPosition(Point point, out DateTime blockDate, [NotNullWhen(true)] out object? column, out TimeSpan start, out TimeSpan end, out int index)
  1006. {
  1007. var rowIdx = (int)Math.Floor(point.Y / _rowHeight);
  1008. start = StartHour + RowInterval * rowIdx;
  1009. end = StartHour + RowInterval * (rowIdx + 1);
  1010. if(start < StartHour)
  1011. {
  1012. start = StartHour;
  1013. }
  1014. if(end > EndHour)
  1015. {
  1016. end = EndHour;
  1017. }
  1018. column = null;
  1019. index = -1;
  1020. blockDate = DateTime.MinValue;
  1021. var x = point.X;
  1022. foreach(var (date, columnKey) in _columnList)
  1023. {
  1024. if (!_blocks.TryGetValue(date, out var dateBlocks)
  1025. || !dateBlocks.TryGetValue(columnKey, out var columnBlocks)) continue;
  1026. var (nCols, subColWidth) = ColumnWidthMode switch
  1027. {
  1028. CalendarColumnWidthMode.ConstantSubColumns => (columnBlocks.Columns!.Count, _colWidth),
  1029. CalendarColumnWidthMode.ConstantColumns => (1, _colWidth / columnBlocks.Columns!.Count),
  1030. _ => throw new InvalidEnumException<CalendarColumnWidthMode>(ColumnWidthMode)
  1031. };
  1032. var colWidth = nCols * _colWidth + _colSpace;
  1033. if(x < colWidth)
  1034. {
  1035. column = columnKey;
  1036. index = Math.Min((int)Math.Floor(x / subColWidth), columnBlocks.Columns!.Count - 1);
  1037. blockDate = date;
  1038. break;
  1039. }
  1040. else
  1041. {
  1042. x -= colWidth;
  1043. }
  1044. }
  1045. return column is not null;
  1046. }
  1047. private bool TryGetBlockFromPosition(MouseEventArgs e, out DateTime blockDate, [NotNullWhen(true)] out object? column, out TimeSpan start, out TimeSpan end, out int index)
  1048. {
  1049. return TryGetBlockFromPosition(e.GetPosition(MainCanvas), out blockDate, out column, out start, out end, out index);
  1050. }
  1051. private CancellationTokenSource? cts = null;
  1052. private void PressedAction(Action onHeld)
  1053. {
  1054. cts?.Cancel();
  1055. cts = new();
  1056. Task.Delay(500).ContinueWith(task =>
  1057. {
  1058. cts = null;
  1059. onHeld();
  1060. }, cts.Token, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
  1061. }
  1062. private void ReleasedAction(Action onRelease)
  1063. {
  1064. if(cts is not null)
  1065. {
  1066. cts.Cancel();
  1067. onRelease();
  1068. }
  1069. }
  1070. private void CallBlockEvent(EventHandler<CalendarBlockEventArgs>? e, UIElement? parent, CalendarBlockEventArgs args)
  1071. {
  1072. e?.Invoke(this, args);
  1073. if(args.GetContextMenu() is ContextMenu menu && menu.Items.Count > 0)
  1074. {
  1075. menu.PlacementTarget = parent;
  1076. menu.IsOpen = true;
  1077. }
  1078. }
  1079. private void CallBlockEvent(EventHandler<CalendarBlockEventArgs>? e, Block block, Point point)
  1080. {
  1081. CallBlockEvent(e, block, new CalendarBlockEventArgs(block.Content, block.Column, block.Date, block.StartTime, block.EndTime, point));
  1082. }
  1083. private void CallBlockEvent(EventHandler<CalendarBlockEventArgs>? e, Block block, MouseEventArgs args)
  1084. {
  1085. CallBlockEvent(e, block, args.GetPosition(MainCanvas));
  1086. }
  1087. private void Block_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
  1088. {
  1089. if (sender is not Block block) return;
  1090. e.Handled = true;
  1091. PressedAction(() => CallBlockEvent(BlockHeld, block, e));
  1092. }
  1093. private void Block_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
  1094. {
  1095. if (sender is not Block block) return;
  1096. e.Handled = true;
  1097. ReleasedAction(() => CallBlockEvent(BlockClicked, block, e));
  1098. }
  1099. private void Block_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
  1100. {
  1101. if (sender is not Block block) return;
  1102. e.Handled = true;
  1103. CallBlockEvent(BlockRightClicked, block, e);
  1104. }
  1105. private Border? _heldSelection;
  1106. public void ClearHeldSelection()
  1107. {
  1108. MainCanvas.Children.Remove(_heldSelection);
  1109. _heldSelection = null;
  1110. }
  1111. public class Region(TimeSpan? start, TimeSpan? end, double x, double width)
  1112. {
  1113. public TimeSpan? Start { get; } = start;
  1114. public TimeSpan? End { get; } = end;
  1115. public double X { get; } = x;
  1116. public double Width { get; } = width;
  1117. }
  1118. private Region GetEmptySpace(DateTime date, object? column, int subColumnIndex, TimeSpan time, IEnumerable<object?>? exclude = null)
  1119. {
  1120. TimeSpan? blockStart = null;
  1121. TimeSpan? blockEnd = null;
  1122. var start = time;
  1123. var width = 0.0;
  1124. var excludedItems = exclude?.ToHashSet() ?? [];
  1125. var x = 0.0;
  1126. foreach(var (columnDate, columnKey) in _columnList)
  1127. {
  1128. if (!_blocks.TryGetValue(columnDate, out var dateBlocks)
  1129. || !dateBlocks.TryGetValue(columnKey, out var columnBlocks)) continue;
  1130. var (nCols, subColWidth) = ColumnWidthMode switch
  1131. {
  1132. CalendarColumnWidthMode.ConstantSubColumns => (columnBlocks.Columns!.Count, _colWidth),
  1133. CalendarColumnWidthMode.ConstantColumns => (1, _colWidth / columnBlocks.Columns!.Count),
  1134. _ => throw new InvalidEnumException<CalendarColumnWidthMode>(ColumnWidthMode)
  1135. };
  1136. var colWidth = nCols * _colWidth + _colSpace;
  1137. if(date == columnDate && column == columnKey)
  1138. {
  1139. x += subColumnIndex * subColWidth;
  1140. var list = columnBlocks.Blocks.Where(x =>
  1141. {
  1142. return !excludedItems.Contains(x.Content) && x.ColumnIndex <= subColumnIndex && subColumnIndex < x.ColumnIndex + x.NColumns;
  1143. }).ToList();
  1144. list.SortBy(x => x.StartTime);
  1145. for(int i = 0; i < list.Count; ++i)
  1146. {
  1147. if(start < list[i].StartTime)
  1148. {
  1149. blockEnd = list[i].StartTime;
  1150. break;
  1151. }
  1152. else
  1153. {
  1154. blockStart = list[i].EndTime;
  1155. }
  1156. }
  1157. width = subColWidth;
  1158. break;
  1159. }
  1160. x += colWidth;
  1161. }
  1162. return new(blockStart, blockEnd, x, width);
  1163. }
  1164. public TimeSpan GetTime(Point point)
  1165. {
  1166. return StartHour + (point.Y / _rowHeight) * RowInterval;
  1167. }
  1168. public bool GetEmptySpace(Point pos, out TimeSpan? start, out TimeSpan? end, TimeSpan? time = null, IEnumerable<object?>? exclude = null)
  1169. {
  1170. if (!TryGetBlockFromPosition(pos, out var date, out var column, out var _start, out var _end, out var index))
  1171. {
  1172. start = default;
  1173. end = default;
  1174. return false;
  1175. }
  1176. var region = GetEmptySpace(date, column, index, time ?? (StartHour + (pos.Y / _rowHeight) * RowInterval), exclude);
  1177. start = region.Start;
  1178. end = region.End;
  1179. return true;
  1180. }
  1181. private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  1182. {
  1183. if (sender is not UIElement element) return;
  1184. if (!TryGetBlockFromPosition(e, out var date, out var column, out var start, out var end, out var index)) return;
  1185. var pos = e.GetPosition(MainCanvas);
  1186. ClearHeldSelection();
  1187. PressedAction(() =>
  1188. {
  1189. if(_heldSelection is null)
  1190. {
  1191. _heldSelection = new Border
  1192. {
  1193. Background = Colors.LightBlue.ToBrush(0.7)
  1194. };
  1195. }
  1196. else
  1197. {
  1198. MainCanvas.Children.Remove(_heldSelection);
  1199. }
  1200. var element = MainCanvas.Children.OfType<Block>().FirstOrDefault(x => x.Date == date && x.Column == column);
  1201. if(element is not null)
  1202. {
  1203. var idx = MainCanvas.Children.IndexOf(element);
  1204. MainCanvas.Children.Insert(idx + 1, _heldSelection);
  1205. }
  1206. else
  1207. {
  1208. MainCanvas.Children.Add(_heldSelection);
  1209. }
  1210. var mouseTime = StartHour + (pos.Y / _rowHeight) * RowInterval;
  1211. var region = GetEmptySpace(date, column, index, mouseTime);
  1212. var blockStart = region.Start ?? GetFillBlockStart?.Invoke(date, column, mouseTime) ?? start;
  1213. var blockEnd = region.End ?? GetFillBlockEnd?.Invoke(date, column, mouseTime) ?? end;
  1214. var width = region.Width;
  1215. var top = ((blockStart - StartHour).TotalHours / RowInterval.TotalHours) * _rowHeight;
  1216. var height = ((blockEnd - blockStart).TotalHours / RowInterval.TotalHours) * _rowHeight;
  1217. var duration = TimeSpan.FromSeconds(0.2);
  1218. var leftAnimation = new DoubleAnimation
  1219. {
  1220. From = pos.X,
  1221. To = region.X,
  1222. Duration = duration
  1223. };
  1224. Storyboard.SetTarget(leftAnimation, _heldSelection);
  1225. Storyboard.SetTargetProperty(leftAnimation, new PropertyPath("(Canvas.Left)"));
  1226. var widthAnimation = new DoubleAnimation
  1227. {
  1228. From = 0,
  1229. To = width,
  1230. Duration = duration
  1231. };
  1232. Storyboard.SetTarget(widthAnimation, _heldSelection);
  1233. Storyboard.SetTargetProperty(widthAnimation, new PropertyPath("Width"));
  1234. var topAnimation = new DoubleAnimation
  1235. {
  1236. From = pos.Y,
  1237. To = top,
  1238. Duration = duration
  1239. };
  1240. Storyboard.SetTarget(topAnimation, _heldSelection);
  1241. Storyboard.SetTargetProperty(topAnimation, new PropertyPath("(Canvas.Top)"));
  1242. var heightAnimation = new DoubleAnimation
  1243. {
  1244. From = 0,
  1245. To = height,
  1246. Duration = duration
  1247. };
  1248. Storyboard.SetTarget(heightAnimation, _heldSelection);
  1249. Storyboard.SetTargetProperty(heightAnimation, new PropertyPath("Height"));
  1250. var storyBoard = new Storyboard();
  1251. storyBoard.Children.Add(leftAnimation);
  1252. storyBoard.Children.Add(topAnimation);
  1253. storyBoard.Children.Add(widthAnimation);
  1254. storyBoard.Children.Add(heightAnimation);
  1255. storyBoard.Completed += (o, _) =>
  1256. {
  1257. CallBlockEvent(BlockHeld, element, new CalendarBlockEventArgs(null, column, date, blockStart, blockEnd, pos));
  1258. };
  1259. storyBoard.Begin();
  1260. });
  1261. }
  1262. private Rectangle? _selectedRectangle;
  1263. private CellPosition? _selectedRectanglePosition;
  1264. private class CellPosition(DateTime date, object? column, TimeSpan start, TimeSpan end)
  1265. {
  1266. public DateTime Date { get; } = date;
  1267. public object? Column { get; } = column;
  1268. public TimeSpan Start { get; } = start;
  1269. public TimeSpan End { get; } = end;
  1270. public bool Equals(DateTime date, object? column, TimeSpan start, TimeSpan end)
  1271. {
  1272. return date == Date && Equals(column, Column) && start == Start && end == End;
  1273. }
  1274. }
  1275. private void ClearSelectedRectangle()
  1276. {
  1277. if(_selectedRectangle is not null)
  1278. {
  1279. _selectedRectangle.Fill = _selectedRectangle.IsMouseOver ? Colors.LightBlue.ToBrush(0.5) : Colors.Transparent.ToBrush();
  1280. _selectedRectangle = null;
  1281. }
  1282. }
  1283. private void Rectangle_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
  1284. {
  1285. if (!TryGetBlockFromPosition(e, out var date, out var column, out var start, out var end, out var index)) return;
  1286. if(_selectedRectanglePosition?.Equals(date, column, start, end) == false)
  1287. {
  1288. ClearSelectedRectangle();
  1289. }
  1290. }
  1291. private void Rectangle_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
  1292. {
  1293. if (!TryGetBlockFromPosition(e, out var date, out var column, out var start, out var end, out var index)) return;
  1294. var pos = e.GetPosition(MainCanvas);
  1295. ClearSelectedRectangle();
  1296. if(sender is Rectangle rect)
  1297. {
  1298. _selectedRectangle = rect;
  1299. _selectedRectanglePosition = new(date, column, start, end);
  1300. rect.Fill = Colors.LightBlue.ToBrush(0.7);
  1301. }
  1302. CallBlockEvent(BlockRightClicked, sender as UIElement, new CalendarBlockEventArgs(null, column, date, start, end, pos));
  1303. }
  1304. private void Rectangle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
  1305. {
  1306. if (!TryGetBlockFromPosition(e, out var date, out var column, out var start, out var end, out var index)) return;
  1307. var pos = e.GetPosition(MainCanvas);
  1308. ReleasedAction(() =>
  1309. {
  1310. ClearSelectedRectangle();
  1311. if(sender is Rectangle rect)
  1312. {
  1313. _selectedRectangle = rect;
  1314. _selectedRectanglePosition = new(date, column, start, end);
  1315. rect.Fill = Colors.LightBlue.ToBrush(0.7);
  1316. }
  1317. CallBlockEvent(BlockClicked, sender as UIElement, new CalendarBlockEventArgs(null, column, date, start, end, pos));
  1318. });
  1319. }
  1320. private void ColumnHeader_SizeChanged(object? sender, SizeChangedEventArgs e)
  1321. {
  1322. HeaderCanvas.Height = HeaderCanvas.Children.OfType<FrameworkElement>().Select(x => x.ActualHeight).Max();
  1323. }
  1324. private void DateHeader_SizeChanged(object sender, SizeChangedEventArgs e)
  1325. {
  1326. DateCanvas.Height = DateCanvas.Children.OfType<FrameworkElement>().Select(x => x.ActualHeight).Max();
  1327. }
  1328. private void Block_SizeChanged(object? sender, SizeChangedEventArgs e)
  1329. {
  1330. LabelCanvas.Width = LabelCanvas.Children.OfType<FrameworkElement>().Select(x => x.ActualWidth).Max();
  1331. }
  1332. private double GetRow(TimeSpan time)
  1333. {
  1334. return (time - StartHour).TotalHours / RowInterval.TotalHours;
  1335. }
  1336. private static List<List<Block>> RecalculateBlockPositionsForDay(List<Block> dayBlocks)
  1337. {
  1338. dayBlocks.SortBy(x => x.StartTime);
  1339. var columns = new List<List<Block>>();
  1340. var remainingBlocks = dayBlocks;
  1341. while(remainingBlocks.Count > 0)
  1342. {
  1343. // At least one block will be moved, so we can use 1 less than the remaining as capacity.
  1344. var tempRemainingBlocks = new List<Block>(remainingBlocks.Count - 1);
  1345. var newBlocks = new List<Block>(remainingBlocks.Count);
  1346. var curTime = TimeSpan.MinValue;
  1347. Block? curBlock = null;
  1348. foreach(var block in remainingBlocks)
  1349. {
  1350. if(curBlock is not null && block.StartTime < curTime)
  1351. {
  1352. tempRemainingBlocks.Add(block);
  1353. }
  1354. else
  1355. {
  1356. newBlocks.Add(block);
  1357. curTime = block.EndTime;
  1358. curBlock = block;
  1359. }
  1360. }
  1361. columns.Add(newBlocks);
  1362. remainingBlocks = tempRemainingBlocks;
  1363. }
  1364. for(int i = 0; i < columns.Count; ++i)
  1365. {
  1366. foreach(var block in columns[i])
  1367. {
  1368. var nColumns = -1;
  1369. for(int j = i + 1; j < columns.Count; ++j)
  1370. {
  1371. foreach(var block2 in columns[j])
  1372. {
  1373. if(block.StartTime < block2.EndTime && block.EndTime > block2.StartTime)
  1374. {
  1375. nColumns = j - i;
  1376. break;
  1377. }
  1378. }
  1379. if(nColumns > -1)
  1380. {
  1381. break;
  1382. }
  1383. }
  1384. block.NColumns = nColumns > -1 ? nColumns : columns.Count - i;
  1385. block.ColumnIndex = i;
  1386. }
  1387. }
  1388. if(columns.Count == 0)
  1389. {
  1390. columns.Add(new());
  1391. }
  1392. return columns;
  1393. }
  1394. private void UpdateBlock(Block block)
  1395. {
  1396. Render(recalculatePositions: true);
  1397. }
  1398. }