我的開發助手之時間線控件


    數年前因為某個原因,開始編寫的我的開發助手,一路艱辛,一路堅持,目前仍不斷完善之中,項目地址: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;
        }

  以上就是時間控件的開發過程,其中界面設計參照了一下開源中國碼雲的個人主頁動態:

     完整代碼可以點擊上面的地址進入鏈接后下載,覺得不錯希望可以給個贊,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM