/// <summary> /// 筛选出没有在当前 RectanglesCanvas 面板中显示的、日期更久远的记录, /// 然后根据这些记录在 StackCanvas 面板中生成新的方块面板,接着在这些 /// 新生成的面板中着色日期更久远的记录。 /// </summary> private void ExtendStackCanvasByFilterOldRecorders(List <StatistTotalByDateTime> oldRecorders, Rectangle earliestRectangle, int canvasOrdinal = 1) { if (oldRecorders != null && oldRecorders.Count > 0) { Canvas oldRectanglesCanvas = new Canvas() { Name = $"OldRectanglesCanvas_{canvasOrdinal}" }; Rectangle oldRect = this.RectanglesLayout(oldRectanglesCanvas, DatetimeParser.ParseExpressToDateTime(earliestRectangle.Name, DateMode.DateWithSlash).AddDays(-1)); oldRectanglesCanvas.Loaded += (object sender, RoutedEventArgs e) => { Canvas canvas = sender as Canvas; foreach (var item in canvas.Children) { if (item is TextBlock tag) { Debug.WriteLine($"tag.ActualWidth: {tag.ActualWidth}"); } } }; this.StackCanvas.Children.Insert(0, oldRectanglesCanvas); List <StatistTotalByDateTime> newOldRecorders = EarlierThanEarliestRectangle(oldRecorders, oldRect); ExtendStackCanvasByFilterOldRecorders(newOldRecorders, oldRect, canvasOrdinal + 1); } // 扩展结束后,给每个 RectanglesCanvas 附加进度条 else { foreach (Canvas canvas in this.StackCanvas.Children) { ProgressBoard.SlideOn(canvas, new ProgressBoard()); } } }
/// <summary> /// 返回所有的事件记录中含有比 rect 的日期还要早的记录。 /// </summary> /// <param name="entries">存储事件链的模型类</param> /// <param name="rect">用于日期比较的目标方块</param> /// <returns></returns> private static List <StatistTotalByDateTime> EarlierThanEarliestRectangle(List <StatistTotalByDateTime> entries, Rectangle rect) { List <StatistTotalByDateTime> earlierThanEarliestRectangleLik = new List <StatistTotalByDateTime>(); foreach (var item in entries) { if (item.DateTime < DatetimeParser.ParseExpressToDateTime(rect.Name, DateMode.DateWithSlash)) { earlierThanEarliestRectangleLik.Add(item); } } return(earlierThanEarliestRectangleLik); }
/// <summary> /// 给相应的 Rectangle 标记上日期和星期数 /// </summary> /// <param name="canvas"></param> private static void DateTag(Canvas canvas) { /* * 下面的查询语句用来提取画布中最左侧的一列方块(res1)和最上侧的一行方块(res2), * 然后将这些方块标记上星期数和月份 */ IEnumerable <IGrouping <double, UIElement> > leftGroup = from rect in canvas.Children group rect by Canvas.GetLeft(rect); IEnumerable <IGrouping <double, UIElement> > topGroup = from rect in canvas.Children group rect by Canvas.GetTop(rect); double minLeftGroupKey = leftGroup.Min(group => group.Key); double minTopGroupKey = topGroup.Min(group => group.Key); IGrouping <double, UIElement> res1 = (from g in leftGroup where g.Key == minLeftGroupKey select g).First(); IGrouping <double, UIElement> res2 = (from g in topGroup where g.Key == minTopGroupKey select g).First(); /* * 给周一、周三、周五的方块打上标记 Mon、Wed、Fri */ foreach (Rectangle rect in res1) { if (DatetimeParser.ParseExpressToDateTime((rect as Rectangle).Name, DateMode.DateWithSlash).DayOfWeek == DayOfWeek.Monday || DatetimeParser.ParseExpressToDateTime((rect as Rectangle).Name, DateMode.DateWithSlash).DayOfWeek == DayOfWeek.Wednesday || DatetimeParser.ParseExpressToDateTime((rect as Rectangle).Name, DateMode.DateWithSlash).DayOfWeek == DayOfWeek.Friday) { var tbx = new TextBlock() { Text = DatetimeParser.ParseExpressToDateTime((rect as Rectangle).Name, DateMode.DateWithSlash).DayOfWeek.ToString().Substring(0, 3), FontSize = 10, Foreground = new SolidColorBrush(Windows.UI.Colors.Gray) }; Canvas.SetLeft(tbx, Canvas.GetLeft(rect) - 30); Canvas.SetTop(tbx, Canvas.GetTop(rect)); canvas.Children.Add(tbx); } } /* * 给每个月份开头的方块打上标记,从 Jan 到 Dec */ Rectangle previousRect = null; // 每个标记创建完成后会同对应的方块一起写入该列表 List <(TextBlock Tag, Rectangle TagedRect)> tagList = new List <(TextBlock Tag, Rectangle TagedRect)>(); foreach (Rectangle rect in res2.Reverse()) { void setTopTag(string text) { var tag = new TextBlock() { Text = text, FontSize = 10, Foreground = new SolidColorBrush(Windows.UI.Colors.Gray) }; Canvas.SetLeft(tag, Canvas.GetLeft(rect)); Canvas.SetTop(tag, Canvas.GetTop(rect) - 15); tagList.Add((tag, rect)); /* * 采用下面的设计是因为 TextBlock.ActualWidth 只有在控件加载完成后(Loaded)才会产生有效值, * TextBlock.ActualWidth 是检测两个 tag 是否重叠的关键参数,检测公式为: * Canvas.GetLeft(tagList[i].Tag) - Canvas.GetLeft(tagList[i - 1].Tag) - tagList[i - 1].Tag.ActualWidth <= 0 * 当该表达式为 true,表明 tagList 中某两个相邻的 tag 发生重叠,需要将右边的 tag 往右移动一个方块的距离避免重叠。 * 当 today 为一年中的某些天时,RectanglesCanvas 的第一列和第二列会是两个相邻的月份,如此紧凑的距离会导致他们顶部 * 的标记重叠,一个例子是 today 为 10/12/2019,当输入的纪录中含有 2017 年的记录时,那么渲染 2017 年对应的 RectanglesCanvas * 会导致该 Canvas 的第一列和第二列的顶部标记重叠。 * * tag.Loaded 事件函数的作用是对 tagList 中的 tag 进行距离检测,当所有的 tag 在 setTopTag 函数中 * 创建完毕后,UI 开始加载 Canvas 中的 UIElement,这个过程会触发每个 tag 的 Loaded 事件,通过 Loaded * 事件对每个 tag 的距离进行轮询。 */ tag.Loaded += (object sender, RoutedEventArgs e) => { for (int i = 1; i < tagList.Count - 1; i++) { if (Canvas.GetLeft(tagList[i].Tag) - Canvas.GetLeft(tagList[i - 1].Tag) - tagList[i - 1].Tag.ActualWidth <= 0) { Debug.WriteLine($"Canvas.GetLeft(list[{i}].Tag) - Canvas.GetLeft(list[{i - 1}].Tag) - list[{i - 1}].Tag.ActualWidth = {Canvas.GetLeft(tagList[i].Tag)} - {Canvas.GetLeft(tagList[i - 1].Tag)} - {tagList[i - 1].Tag.ActualWidth} = {Canvas.GetLeft(tagList[i].Tag) - Canvas.GetLeft(tagList[i - 1].Tag) - tagList[i - 1].Tag.ActualWidth}"); Canvas.SetLeft(tagList[i].Tag, Canvas.GetLeft(tagList[i].Tag) + tagList[i].TagedRect.Width); } } }; canvas.Children.Add(tag); } if (previousRect == null) { setTopTag(DatetimeParser.NumberToMonth(DatetimeParser.ParseExpressToDateTime(rect.Name, DateMode.DateWithSlash).Month)); } else { int monthOfPreviousRectangle = DatetimeParser.ParseExpressToDateTime(previousRect.Name, DateMode.DateWithSlash).Month; int monthOfCurrentRectangle = DatetimeParser.ParseExpressToDateTime(rect.Name, DateMode.DateWithSlash).Month; if (monthOfCurrentRectangle != monthOfPreviousRectangle) { setTopTag(DatetimeParser.NumberToMonth(DatetimeParser.ParseExpressToDateTime(rect.Name, DateMode.DateWithSlash).Month)); } } previousRect = rect; } }