我的开发助手之时间线控件

数年前因为某个原因,开始编写的我的开发助手,一路艰辛,一路坚持,目前仍不断完善之中,项目地址:https://gitee.com/sqlorm/DevelopAssistant 欢迎大家点赞和支持。

今天想和大家分享一下其中的时间线控件,这是一个通过GDI绘制和对原有事件重写来实现的用户自定义控件,界面还算美观,操作也很简捷,喜欢的同学不妨收下。

控件是这样子的:

?

没有内容时界面

?

管理界面带编辑功能界面

下面就来介绍一下关于这个控件的开发:

第一步、我们创建一个类继承 UserControl

控件主要对 OnPaint ,OnMouseClick ,OnMouseMove,OnSizeChanged,OnMouseWheel 方法进行重写,其中 OnPaint 方法用户界面元素的绘制,并在该方法里面计算控件元素的绘制区域,以便在OnMouseClick 重写方法里实现元素的点击事件,OnMouseClick 方法就是实现控件元素的点击事件,OnMouseMove 主要实体一些鼠标特效,例如滑动鼠标改变背景色,改变控件默认光标形状等,OnSizeChanged 方法主要实现当改变控件大小时控件控件滚动条相关属性的计算和触发控件元素重绘及事件区域范围Rectangle的计算,通过判断鼠标点击的位置属于哪个元素的区域范围来确定触发哪个元素的相关事件,OnMouseWheel  鼠标滚轮滚动时发生。其实winform下自定义控件特别是通过GDI绘制来实现的一类基本上都是实现上述几个事件方法来实现,可以用一张图来概括:

?

第二步、定义控件的内部元素

控件主要涉及到 月份对象元素: MonthItem ,日期对象元素:DateItem ,时间对象元素:DateTimeItem 他们都继承自公共对象元素:TimelineItem 他们都有公同的属性Id (与数据库表主键做关联),Name 名称,Tag 其它数据相关绑定的标签。其次MonthItem和 DateItem 都有 Bound 属于,用户保存该元素在控件中的绘制区域。下面贴出这三个元素实体类的代码:

MonthItem:

    [Serializable]
    public class MonthItem : TimelineItem
    {
        public DateTime Date { get; set; }
        public string DateLabel { get; set; }
        public List<DateItem> List { get; set; }

        internal Size Size { get; set; }
        internal Rectangle Bound { get; set; }
    }

     DateItem:

    [Serializable]
    public class DateItem : TimelineItem
    {
        public DateTime Date { get; set; }
        public List<DateTimeItem> List { get; set; }

        internal Size Size { get; set; }
        internal Rectangle Bound { get; set; }
        internal Rectangle AddRect { get; set; }
        internal Rectangle ClickRect { get; set; }
        public bool Selected { get; set; }

        private string _tag;
        public override string Tag
        {
            get
            {
                if (string.IsNullOrEmpty(_tag))
                {
                    _tag = Date.ToString("yyyyMMdd");
                }
                return _tag;
            }
        }

    }

    DateTimeItem :

    [Serializable]
    public class DateTimeItem : TimelineItem
    {
        public Image Icon { get; set; }
        public string Title { get; set; }
        public string Summary { get; set; }
        public string Description { get; set; }
        public string ToolTip { get; set; }
        public string PersonName { get; set; }
        public DateTime DateTime { get; set; }
        public ImportantLevel Level { get; set; }
        public Timeliness Timeliness { get; set; }
        public string ResponsiblePerson { get; set; }

        internal Rectangle EditRect { get; set; }
        internal Rectangle DeleteRect { get; set; }
        /// <summary>
        /// 0 :默认 1:修改  2:删除
        /// </summary>
        internal int ButtonState { get; set; }
        public Rectangle ClickRect { get; set; }
        public bool Selected { get; set; }

        private string _tag;
        public override string Tag
        {
            get
            {
                if (string.IsNullOrEmpty(_tag))
                {
                    _tag = DateTime.ToString("yyyyMMddHHmmss");
                }
                return _tag;
            }
        }

    }

第三步、绘制控件内部的元素

绘制控件内部的元素主要分为绘制 TimelineItem 一类(包括 MonthItem ,DateItem 和 DateTimeItem)和 控件的滚动条,一般来讲winform 控件自带的滚动条都由系统绘制,往往和操作系统息息相关,这里我们的时间线控件要适合开发助手相关的主题,所以我们采用在内部自己绘制滚动条,通过主要对OnMouseMove,OnMouseWheel两相事件方法进行重写来实现滚动条的控件。

这里对TimelineItem 一类的元素绘制主要贴出以下代码:

/// <summary>
        /// 计算 TimelineItem 绘制区域 通过对 MonthItem 子元素递规循环计算 整个 MonthItem 元素的绘制区域
        /// </summary>
        /// <param name="g"></param>
        /// <param name="index"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        private Rectangle MeasureItemBound(Graphics g, int index, MonthItem item)
        {
            int itemHeight = 46;
            if (item.List != null)
            {
                foreach (DateItem subItem in item.List)
                {
                    if (subItem.List != null)
                    {
                        foreach(DateTimeItem subsubItem in subItem.List)
                        {
                            itemHeight = itemHeight + 32;
                            if (!string.IsNullOrEmpty(subsubItem.Summary))
                            {
                                itemHeight = itemHeight + 26;
                            }
                        }
                    }
                    itemHeight = itemHeight + 32;
                }
            }
            Rectangle rect = new Rectangle(drawPositionOffset.X + padding.Left, drawPositionOffset.Y + position, this.Width - padding.Left - padding.Right - (scrollerBarVisable ? 0 : scrollerBarWidth), itemHeight);
            position = position + itemHeight;
            return rect;
        }

/// <summary>
        /// 绘制 MonthItem 元素,包括下面的  DateItem 和  DateTimeItem 子元素
        /// </summary>
        /// <param name="g"></param>
        /// <param name="index"></param>
        /// <param name="item"></param>
        private void DrawTimelineItem(Graphics g, int index, MonthItem item)
        {
            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Center;
            sf.LineAlignment = StringAlignment.Center;

            // margin
            Rectangle bound = item.Bound;

            //g.DrawRectangle(new Pen(SystemColors.ControlDark), bound);
            g.DrawLine(new Pen(SystemColors.ControlLight) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dot }, 5, bound.Top + 23, bound.Width - 10, bound.Top + 23);

            Point start = new Point(5 + 18, bound.Top + (index > 0 ? 0 : 5));
            Point end = new Point(5 + 18, bound.Bottom - (index < this.DataList.Count - 1 ? 0 : 5));
            g.DrawLine(new Pen(SystemColors.ControlLight), start, end);

            Rectangle iconRect = new Rectangle(5, bound.Top + 5, 36, 36);
            g.FillEllipse(Brushes.Orange, iconRect);
            g.DrawString(item.DateLabel, this.Font, Brushes.White, iconRect, sf);

            if (item.List != null)
            {
                StringFormat subSf = new StringFormat();
                subSf.LineAlignment = StringAlignment.Center;
                Font subTitleFont = new Font("仿宋", 12, FontStyle.Bold | FontStyle.Italic) { };

                int top = bound.Top + 15;
                for (int i = 0; i < item.List.Count; i++)
                {
                    top = top + 32;
                    DateItem subItem = item.List[i];

                    Rectangle subIconRect = new Rectangle(5 + 12, top + 9, 12, 12);
                    g.FillEllipse(Brushes.Orange, subIconRect);
                    //g.DrawEllipse(new Pen(Color.Orange) { Width=2.0f }, subIconRect);
                    //g.DrawString((i + 1).ToString(), this.Font, Brushes.White, subIconRect, sf);
                    subIconRect.Inflate(-2, -2);
                    g.FillEllipse(Brushes.White, subIconRect);

                    Rectangle subRect = new Rectangle(56, top, bound.Width - 64, 32);
                    if (subItem.Selected)
                    {
                        using (var roundedRectanglePath = CreateRoundedRectanglePath(subRect, 2))
                        {
                            g.FillPath(new SolidBrush(Color.FromArgb(240, 245, 249)), roundedRectanglePath);
                        }
                    }

                    Rectangle subTitleRect = new Rectangle(56, top, bound.Width - 64 - 30, 32);
                    //g.DrawRectangle(new Pen(Color.Orange), subTitleRect);
                    //g.DrawString((i + 1) + "、" + subItem.Title, this.Font, Brushes.Red, subTitleRect, subSf);

                    Brush subTitleBrush = Brushes.Black;
                    g.DrawString(subItem.Date.ToString("yyyy-MM-dd"), subTitleFont, subTitleBrush, subTitleRect, subSf);
                    //g.FillRectangle(Brushes.Red, subTitleRect);

                    Rectangle subOptionRect = new Rectangle(bound.X + bound.Width - 34 + 4, top + 8, 16, 16);
                    //g.FillRectangle(Brushes.Yellow, subOptionRect);
                    g.DrawImage(this.TimeLineIcons.Images[2], subOptionRect);
                    subItem.AddRect = subOptionRect;
                    subItem.ClickRect = subRect;

                    if (subItem.List != null)
                    {
                        for (int j = 0; j < subItem.List.Count; j++)
                        {
                            top = top + 32;
                            DateTimeItem subsubItem = subItem.List[j];

                            //Rectangle subsubIconRect = new Rectangle(5 + 14, top + 10, 8, 8);
                            //g.FillEllipse(Brushes.Orange, subsubIconRect);
                            //subsubIconRect.Inflate(-2, -2);
                            //g.FillEllipse(Brushes.White, subsubIconRect);

                            Rectangle DateTimeItemClickRect = new Rectangle(56, top + 2, bound.Width - 64, 28);

                            if (!string.IsNullOrEmpty(subsubItem.Summary))
                                DateTimeItemClickRect = new Rectangle(DateTimeItemClickRect.X, DateTimeItemClickRect.Y, DateTimeItemClickRect.Width, DateTimeItemClickRect.Height + 32);

                            if (subsubItem.Selected)
                            {
                                using (var roundedRectanglePath = CreateRoundedRectanglePath(DateTimeItemClickRect, 2))
                                {
                                    g.FillPath(new SolidBrush(Color.FromArgb(240, 245, 249)), roundedRectanglePath);
                                }
                            }

                            Brush drawTitleBrush = Brushes.Black;
                            if (subsubItem.Selected)
                                drawTitleBrush = Brushes.Blue;

                            Color drawTitleColor = Color.Black;
                            if (subsubItem.Selected)
                                drawTitleColor = Color.Blue;

                            if (!subsubItem.Selected)
                            {
                                switch (subsubItem.Level)
                                {
                                    case ImportantLevel.Important:
                                        drawTitleColor = Color.Orange;
                                        break;
                                    case ImportantLevel.MoreImportant:
                                        drawTitleColor = Color.Brown;
                                        break;
                                    case ImportantLevel.MostImportant:
                                        drawTitleColor = Color.Red;
                                        break;
                                }
                            }

                            //Rectangle subsubImgRect = new Rectangle(56 + 0, top + 7, 16, 16);
                            ////g.FillEllipse(Brushes.Red, subsubImgRect);
                            //g.DrawImage(this.TimeLineIcons.Images[2], subsubImgRect);

                            Brush itemIconBrush = Brushes.Red;
                            switch (subsubItem.Timeliness)
                            {
                                case Timeliness.Normal:
                                    itemIconBrush = Brushes.Green;
                                    break;
                                case Timeliness.Yellow:
                                    itemIconBrush = Brushes.Yellow;
                                    break;
                                case Timeliness.Orange:
                                    itemIconBrush = Brushes.Orange;
                                    break;
                                case Timeliness.Red:
                                    itemIconBrush = Brushes.Red;
                                    break;
                                case Timeliness.Dark:
                                    itemIconBrush = Brushes.Gray;
                                    break;
                                case Timeliness.Black:
                                    itemIconBrush = Brushes.Black;
                                    break;
                            }

                            int m = 20;
                            Rectangle subsubImgRect = Rectangle.Empty;
                            if (subsubItem.Icon != null)
                            {
                                if (subsubItem.Icon.Height == 16)
                                {
                                    m = 20;
                                    subsubImgRect = new Rectangle(56 + 0, top + 3, 16, 16);
                                }
                                if (subsubItem.Icon.Height == 24)
                                {
                                    m = 28;
                                    subsubImgRect = new Rectangle(56 + 0, top + 3, 24, 24);
                                }
                                else
                                {
                                    throw new Exception("只支持16*16、24*24大小的图标");
                                }
                                g.DrawImage(subsubItem.Icon, subsubImgRect);
                            }
                            else
                            {
                                if(!string.IsNullOrEmpty(subsubItem.PersonName))
                                {
                                    m = 28;
                                    subsubImgRect = new Rectangle(56 + 0, top + 3, 24, 24);
                                }
                                else
                                {
                                    m = 20;
                                    subsubImgRect = new Rectangle(56 + 0, top + 7, 16, 16);
                                }
                                g.FillEllipse(itemIconBrush, subsubImgRect);

                                if (!string.IsNullOrEmpty(subsubItem.PersonName))
                                {
                                    Brush showNameBrush = Brushes.White;
                                    Font showNameFont = new Font("微软雅黑", 8, FontStyle.Bold);

                                    if (itemIconBrush == Brushes.Red ||
                                        itemIconBrush == Brushes.Yellow)
                                    {
                                        showNameBrush = Brushes.Black;
                                    }

                                    g.DrawString(subsubItem.PersonName, showNameFont, showNameBrush, subsubImgRect, sf);
                                }

                            }

                            //Rectangle subsubTitleRect = new Rectangle(56 + 20, top, bound.Width - 84 - 60, 32);
                            Rectangle subsubTitleRect = new Rectangle(56 + m, top, bound.Width - 84 - 60, 32);
                            //g.DrawRectangle(new Pen(Color.Orange), subsubTitleRect);
                            //g.DrawString((i + 1) + "、" + subItem.Title, this.Font, Brushes.Red, subTitleRect, subSf);
                            //g.DrawString(subsubItem.Title, this.Font, drawTitleBrush, subsubTitleRect, subSf);
                            TextRenderer.DrawText(g, subsubItem.Title, this.Font, subsubTitleRect, drawTitleColor, TextFormatFlags.Left | TextFormatFlags.WordEllipsis | TextFormatFlags.VerticalCenter);

                            if (_isEditModel && subsubItem.Selected)
                            {
                                //绘制删除按扭
                                Size subsubTitleSize = TextRenderer.MeasureText(g, subsubItem.Title, this.Font);
                                subsubItem.EditRect = new Rectangle(subsubTitleRect.X + subsubTitleSize.Width + 2, top + 8, 16, 16);
                                subsubItem.DeleteRect = new Rectangle(subsubTitleRect.X + subsubTitleSize.Width + 2 + 16 + 4, top + 8, 16, 16);
                            }

                            Rectangle subsubTimeRect = new Rectangle(bound.Width - 64, top, 56, 32);
                            //g.FillRectangle(Brushes.Green, subsubTimeRect);
                            //g.DrawString((i + 1) + "、" + subItem.Title, this.Font, Brushes.Red, subTitleRect, subSf);
                            g.DrawString(subsubItem.DateTime.ToString("HH:mm:ss"), this.Font, drawTitleBrush, subsubTimeRect, subSf);

                            if (!string.IsNullOrEmpty(subsubItem.Summary))
                            {
                                Font drawSummaryFont = this.Font;
                                Brush drawSummaryBrush = Brushes.Gray;
                                Color drawSummaryColor = Color.Gray;

                                if (subsubItem.Selected)
                                {
                                    drawSummaryBrush = new SolidBrush(SystemColors.ControlDark);
                                    //drawSummaryFont = new Font(this.Font, FontStyle.Italic);
                                    drawSummaryColor = SystemColors.ControlDark;
                                }

                                top = top + 32;
                                Rectangle subsubSummaryRect = new Rectangle(56, top, bound.Width - 64, 26);
                                //g.DrawRectangle(Pens.Red, subsubSummaryRect);
                                //g.DrawString(subsubItem.Summary, drawSummaryFont, drawSummaryBrush, subsubSummaryRect, subSf);
                                TextRenderer.DrawText(g, subsubItem.Summary, drawSummaryFont, subsubSummaryRect, drawSummaryColor, TextFormatFlags.Left | TextFormatFlags.WordEllipsis | TextFormatFlags.VerticalCenter);

                            }

                            subsubItem.ClickRect = DateTimeItemClickRect;

                            g.DrawLine(new Pen(SystemColors.ControlLight) { DashStyle = System.Drawing.Drawing2D.DashStyle.Dot }, 56, top + 32, bound.Width - 10, top + 32);

                            if (_isEditModel && subsubItem.Selected)
                            {
                                //switch (subsubItem.ButtonState)
                                //{
                                //    case 1:
                                //        g.FillRectangle(new SolidBrush(Color.Orange), subsubItem.EditRect);
                                //        g.FillRectangle(new SolidBrush(Color.FromArgb(240, 245, 249)), subsubItem.DeleteRect);
                                //        break;
                                //    case 2:
                                //        g.FillRectangle(new SolidBrush(Color.FromArgb(240, 245, 249)), subsubItem.EditRect);
                                //        g.FillRectangle(new SolidBrush(Color.Orange), subsubItem.DeleteRect);
                                //        break;
                                //    default:
                                //        g.FillRectangle(new SolidBrush(Color.FromArgb(240, 245, 249)), subsubItem.EditRect);
                                //        g.FillRectangle(new SolidBrush(Color.FromArgb(240, 245, 249)), subsubItem.DeleteRect);
                                //        break;
                                //}

                                g.FillRectangle(new SolidBrush(Color.FromArgb(240, 245, 249)), subsubItem.EditRect);
                                g.FillRectangle(new SolidBrush(Color.FromArgb(240, 245, 249)), subsubItem.DeleteRect);

                                //绘制编辑按扭
                                g.DrawImage(this.TimeLineIcons.Images[0], subsubItem.EditRect);
                                //绘制删除按扭
                                g.DrawImage(this.TimeLineIcons.Images[1], subsubItem.DeleteRect);
                            }                            

                        }
                    }                   

                }

            }

            StringFormat sf2 = new StringFormat();
            sf2.LineAlignment = StringAlignment.Center;
            Rectangle itemTitleRect = new Rectangle(56, bound.Top + 5, bound.Width - 64, 36);
            //g.DrawRectangle(new Pen(Color.Orange), itemTitleRect);
            //g.DrawString("共计 " + (item.List == null ? 0 : item.List.Count()) + " 个事项", this.Font, Brushes.Black, itemTitleRect, sf2);

        }

绘制滚动条这里先贴一张不带上下箭头的滚动条截图

?

主要涉及到的计算如下:

 /// <summary>
        /// 计算 Thumb 的高
        /// </summary>
        /// <returns></returns>
        private int GetThumbHeight()
        {
            int disHeight = this.BorderStyle == NBorderStyle.None ? this.Height : this.Height - 2;
            if (MaxnumHeight == 0 || MaxnumHeight <= disHeight) return disHeight;
            int thumbHeight = (int)(disHeight * 1.0d / MaxnumHeight * disHeight);
            if (thumbHeight < 20) thumbHeight = 20;
            largeChange = DisplayRectangle.Height - thumbHeight;
            return thumbHeight;
        }

        /// <summary>
        /// 绘制 Thumb 及计算 Thumb 的位置
        /// </summary>
        /// <param name="g"></param>
        private void DrawScrollThumb(Graphics g)
        {
            int thumbOffsetY = (int)(scrollerBarValue * 1.0 / scrollerBarMaxnum * (scrollerRect.Height - thumbRect.Height));
            thumbRect = new Rectangle(scrollerRect.X, scrollerRect.Y + thumbOffsetY, scrollerRect.Width, scrollerThumbHeight);
            g.FillRectangle(Brushes.Gray, thumbRect);
        }

        /// <summary>
        /// 计算所以元素 累加起来总的高度(包话不可见部分)
        /// </summary>
        private int MaxnumHeight
        {
            get
            {
                int maxnum = 0;
                Graphics g = null;
                if (this.DataList != null)
                {
                    for (int i = 0; i < this.DataList.Count; i++)
                    {
                        var item = this.DataList[i];
                        maxnum += MeasureItemBound(g, i, item).Height;
                    }
                }
                return maxnum;
            }
        }

第四步、处理控件中的事件

首先我们重写一个Click事件对外开放,usercontrol 自控件本身就有一个click事件,这里我们在定义事件的属性前面添加 new 关键字表达用新的事件属性来替换掉原有的click事件。代码如下:

 private static readonly object itemEventObject = new object();
        /// <summary>
        /// 重写一个Click事件对外开放
        /// </summary>
        public new event EventHandler<TimeLineEventArgs> Click
        {
            add { Events.AddHandler(itemEventObject, value); }
            remove { Events.RemoveHandler(itemEventObject, value); }
        }

这里可以看到我们定义了一个 TimeLineEventArgs 实体类,里面主要有 Command 命令 Data 数据两个属性,在控件内类我们通过判断点击鼠标的位置来判断触发哪个元素,哪种操作类型的事件,控件在编辑模式下有 添加,编辑,删除三种事件类型通过 Command 传递给外部调用,实现代码如下:

 protected override void OnMouseClick(MouseEventArgs e)
        {
            base.OnMouseClick(e);

            if (!newItemModel)
            {
                //是否继续 当选择项为 DateItem 时 就跳过 DateTimeItem 循环
                bool isChidrenContinueForeach = true;

                foreach(MonthItem monthItem in DataList)
                {
                    foreach (DateItem dateItem in monthItem.List)
                    {
                        if (dateItem.ClickRect.Contains(e.Location))
                        {
                            var eventArg = new TimeLineEventArgs(dateItem);
                            if (_isEditModel && dateItem.AddRect != Rectangle.Empty && dateItem.AddRect.Contains(e.Location))
                            {
                                eventArg.Command = "new";
                            }
                            DoItemClick(eventArg);
                            isChidrenContinueForeach = false;
                        }
                        foreach (DateTimeItem dateTimeItem in dateItem.List)
                        {
                            if (isChidrenContinueForeach &&
                                dateTimeItem.ClickRect.Contains(e.Location))
                            {
                                var eventArg = new TimeLineEventArgs(dateTimeItem);
                                eventArg.Command = "detail";
                                if (_isEditModel && dateTimeItem.EditRect != Rectangle.Empty && dateTimeItem.EditRect.Contains(e.Location))
                                {
                                    eventArg.Command = "edit";
                                }
                                if (_isEditModel && dateTimeItem.DeleteRect != Rectangle.Empty && dateTimeItem.DeleteRect.Contains(e.Location))
                                {
                                    eventArg.Command = "delete";
                                }
                                DoItemClick(eventArg);
                                isChidrenContinueForeach = false;
                            }

                            if (!isChidrenContinueForeach)
                                break;
                        }

                        if (!isChidrenContinueForeach)
                            break;

                    }
                }

            }
            else
            {
                if (newItemRect.Contains(e.Location))
                {
                    var eventArg = new TimeLineEventArgs(null);
                    eventArg.Command = "new";
                    DoItemClick(eventArg);
                }
            }
        }

控件中处理滚动条事件代码如下:

/// <summary>
        /// 滑动滚动条时  由 OnMouseMove 触发
        /// </summary>
        /// <param name="e"></param>
        private void DoMouseScrolling(MouseEventArgs e)
        {
            if (!scrollerBarVisable)
                return;

            int d = mouseDownOffset.Y + e.Location.Y - mouseDownPos.Y;
            scrollerBarValue = (int)(d * 1.0 / (scrollerRect.Height - thumbRect.Height) * scrollerBarMaxnum);

            if (scrollerBarValue < 0)
            {
                scrollerBarValue = 0;
            }
            if (scrollerBarValue > 100)
            {
                scrollerBarValue = 100;
            }

            int drawOffsetY = (int)(-scrollerBarValue * 1.0 / scrollerBarMaxnum * (MaxnumHeight + smallChange - this.Height));
            drawPositionOffset = new Point(0, drawOffsetY);

            this.Invalidate();

        }

        /// <summary>
        /// 处理鼠标滚轮事件 由 OnMouseWheel 触发
        /// </summary>
        /// <param name="e"></param>
        private void DoMouseWheel(MouseEventArgs e)
        {
            if (!scrollerBarVisable)
                return;

            int olePositionOffsetY = drawPositionOffset.Y;

            int mouseWheelScrollLines = e.Delta / NativeMethods.WHEEL_DELTA;

            int newPositionOffsetY = drawPositionOffset.Y + mouseWheelScrollLines * smallChange;

            int d = newPositionOffsetY - olePositionOffsetY;
            scrollerBarValue += (int)(-d * 1.0 / (MaxnumHeight + smallChange - this.Height) * scrollerBarMaxnum);

            if (scrollerBarValue < 0)
            {
                scrollerBarValue = 0;
            }
            if (scrollerBarValue > 100)
            {
                scrollerBarValue = 100;
            }

            int drawOffsetY = (int)(-scrollerBarValue * 1.0 / scrollerBarMaxnum * (MaxnumHeight + smallChange - this.Height));
            drawPositionOffset = new Point(0, drawOffsetY);

            this.Invalidate();
        }

第五步、控件性能优化

关于控件性能的优化主要体现在判断元素是否处于可视区域,如果不在可视区域则不绘制该元素及其子元素,特别是针对控件拥有上百上千甚至上万元素时,绘制所以元素系统开销很大(内存占用和CPU计算),往往一个控件在可视区域能展现出来的元素个数是有限的。

 /// <summary>
        /// 判断是否在可视区域的方法
        /// </summary>
        /// <param name="bound"></param>
        /// <returns></returns>
        private bool IsRectangleVisible(Rectangle bound)
        {
            bool isItemDrawModel = true;
            if (bound.Bottom < this.DisplayRectangle.Top ||
                        bound.Top > this.DisplayRectangle.Bottom)
            {
                isItemDrawModel = false;
            }
            return isItemDrawModel;
        }

        /// <summary>
        /// 在绘制元素添加判断,不在可视范围内跳过不进行绘制
        /// </summary>
        /// <param name="g"></param>
        private void DrawTimelineItems(Graphics g)
        {
            position = 0;
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

            if (DataList != null)
            {
                for (int index = 0; index < DataList.Count; index++)
                {
                    MonthItem item = DataList[index];
                    item.Bound = MeasureItemBound(g, index, item);
                    if (IsRectangleVisible(item.Bound))
                    {
                        DrawTimelineItem(g, index, item);
                    }
                }
            }         

            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default;
        }

以上就是时间控件的开发过程,其中界面设计参照了一下开源中国码云的个人主页动态:

?

完整代码可以点击上面的地址进入链接后下载,觉得不错希望可以给个赞,谢谢!

原文地址:https://www.cnblogs.com/wxdongtt2007/p/11421494.html

时间: 2024-10-06 06:06:29

我的开发助手之时间线控件的相关文章

visual studio开发工具的C#主流控件属性一览表

visual studio开发工具的C#主流控件属性一览表 详细的介绍了各控制属性的详细中文介绍 C#控件及常用设计整理 1.窗体 1.常用属性 (1)Name属性:用来获取或设置窗体的名称,在应用程序中可通过Name属性来引用窗体. (2) WindowState属性: 用来获取或设置窗体的窗口状态. 取值有三种: Normal (窗体正常显示). Minimized(窗体以最小化形式显示)和 Maximized(窗体以最大化形式显示). (3)StartPosition属性:用来获取或设置运

iOS开发基础篇-手写控件

一.手写控件的步骤 1)使用相应的控件类创建控件对象: 2)设置该控件的各种属性: 3)添加空间到视图中: 4)如果是 UIButton 等控件,还需考虑控件的单击事件等: 二.添加 UIButton 单击事件  [topbtn addTarget:self action:@selector(move:) forControlEvents:UIControlEventTouchUpInside]; 1) addTarget:forControlEvents: 方法定义在 UIControl 类中

iOS开发UI篇—手写控件,frame,center和bounds属性

iOS开发UI基础—手写控件,frame,center和bounds属性 一.手写控件 1.手写控件的步骤 (1)使用相应的控件类创建控件对象 (2)设置该控件的各种属性 (3)添加控件到视图中 (4)如果是button等控件,还需考虑控件的单击事件等 (5)注意:View Contollor和view的关系 2.注意点 在OC开发中,Storyboard中的所有操作都可以通过代码实现,程序员一定要熟练掌握代码布局界面的能力! 设置控件监听方法的示例代码如下: [btn addTarget:se

Qt开发环境中使用报表控件FastReport遇到的一些问题(二)

上一节中谈到的那个问题:传递的变量内容如果是纯英文,报表报错.经笔者反复测试,找到了解决办法:代码中第5行替换为以下 params<<"my_var"<<"\"xyz\""; 在内容前后用\"把内容括起来,在把报表设计器中Code页的语言设置为非PascalScript就好了. Qt开发环境中使用报表控件FastReport遇到的一些问题(二),布布扣,bubuko.com

IOS开发基础篇--手写控件,frame,center和bounds属性

iOS开发UI基础—手写控件,frame,center和bounds属性 一.手写控件 1.手写控件的步骤 (1)使用相应的控件类创建控件对象 (2)设置该控件的各种属性 (3)添加控件到视图中 (4)如果是button等控件,还需考虑控件的单击事件等 (5)注意:View Contollor和view的关系 2.注意点 在OC开发中,Storyboard中的所有操作都可以通过代码实现,程序员一定要熟练掌握代码布局界面的能力! 设置控件监听方法的示例代码如下: [btn addTarget:se

OS开发UI基础—手写控件,frame,center和bounds属性

OS开发UI基础—手写控件,frame,center和bounds属性 一.手写控件 1.手写控件的步骤 (1)使用相应的控件类创建控件对象 (2)设置该控件的各种属性 (3)添加控件到视图中 (4)如果是button等控件,还需考虑控件的单击事件等 (5)注意:View Contollor和view的关系 2.注意点 在OC开发中,Storyboard中的所有操作都可以通过代码实现,程序员一定要熟练掌握代码布局界面的能力! 设置控件监听方法的示例代码如下: [btn addTarget:sel

C#实现窗体拖动时各个控件同比自动放缩大小

实现方式主要是利用panel控件为主题,对于每个控件的大小位置和字体这几个属性进行记录, 然后根据窗体改变的大小同时放缩. 简要步骤如下: 1 创建C#窗体程序项目. 2  Panel放置到窗体. 3  设置属性dock为fill. 4  注意MinnumSize不能设置为0, 改成大于0都行. public partial class FrmDemo : Form { double dFrmWidth; double dFrmHeight; double dZoomHorizon; doubl

iOS开发项目篇—43子控件的细节处理

iOS开发项目篇—43子控件的细节处理 一.升级UI 把之前的UI图片删除,换上新的图片(图片命名一致,规范)没有其他的影响. 删除之后,添加. 替换之后,做一次clear操作. 建议把沙盒中的包删除,删除之后再做一次clear操作. 二.调整转发(模块) 1.设置背景(使用提供的素材图片进行平铺) 为转发微博部分设置背景,考虑到这个部分整体上是一个UIView,可以尝试以下设置. 第一种尝试: 但是这样设置,因为图片是平铺的,所以整个背景会出现线条效果,影响显示,不可行. 第二种尝试: 注意:

js组件开发-移动端地区选择控件mobile-select-area

移动端地区选择控件mobile-select-area 由于之前的[js开源组件开发]js手机联动选择地区仿ios 开源git 很受欢迎,于是我又对其进行了一些优化,包括可选的范围变大了,添加了默认空首地址的功能,也添加了更多api参数,首先我们先来看下这次的效果图. 它的github地址请点击https://github.com/tianxiangbing/mobile-select-area 它的demo演示请点击 http://www.lovewebgames.com/jsmodule/m