首页 > 代码库 > 分析Devexress类库的绘制过程

分析Devexress类库的绘制过程

devExpress的Winform库,是采用的GDI的方式来绘制的.

如果编过WIN32或是MFC的GDI程序,应当一看就明白是怎么回事.

 

但说归说,做归做.

今天有会闲工夫,分析了一下devExpress 的Scheduler控件,是如何完成绘制的。结果,还是让我比较吃惊。

有思路。

 

以前虽然,我在仪表行业干过几年,编过许多类似示波器那种的实时图形,如何避免屏闪,双buffer之类的,但直到后来,我才稍稍有了点真的懂了,然后转行了。

所以,今天看了人家的实现,不免还是从心里往外佩服——虽然实现得简单直白,但总觉得很美。

 

所以,这里作个记录。

首先声明,还没有完全看懂。

 

这里就是个初稿。分析的这程,最好有两个显示器。

 

先来看看我们的目标:分析一下,每个Appointment的上面的那条线是如何画出来的。

 

 

首先,我们找到这个风格所在的位置

 

 

this.schedulerControl.Views.TimelineView.AppointmentDisplayOptions.StatusDisplayType = DevExpress.XtraScheduler.AppointmentStatusDisplayType.Time; 

 

        protected internal virtual ViewInfoItemCollection CreateStatusItems(AppointmentViewInfo aptViewInfo) {
            switch (aptViewInfo.Options.StatusDisplayType) {
                case AppointmentStatusDisplayType.Never:
                default:
                    return new ViewInfoItemCollection();
                case AppointmentStatusDisplayType.Bounds:
                    return CreateStatusItemsCore(aptViewInfoaptViewInfo.Interval);
                case AppointmentStatusDisplayType.Time:
                    TimeInterval aptInterval = aptViewInfo.AppointmentInterval;
                    return CreateStatusItemsCore(aptViewInfoaptInterval);
            }

        }  

 

本来想要跟一跟绘制的过程,后来,找到绘制过程的主入口:

Sources\DevExpress.XtraScheduler\DevExpress.XtraScheduler\SchedulerControl.cs

 

protected override void OnPaint(PaintEventArgs e) {
            if (InnerIsDisposing || InnerIsDisposed)
                return;
            GraphicsInfoArgs info = new GraphicsInfoArgs(new GraphicsCache(e), this.Bounds);
            GraphicsCache cache = info.Cache;
            try {
                BorderPainter borderPainter = PaintStyle.CreateBorderPainter(BorderStyle);
                borderPainter.DrawObject(new BorderObjectInfoArgs(cacheClientRectanglenullObjectState.Normal));
                ViewPainterBase painter = CreateActiveViewPainter();
                painter.DrawViewAndScrollBarSeparator(cachethis.ViewAndDateTimeScrollBarSeparatorBounds);
                painter.DrawViewAndScrollBarSeparator(cachethis.ViewAndResourceNavigatorSeparatorBounds);
                using (IntersectClipper clipper = new IntersectClipper(cacheViewBounds)) {
                    painter.Draw(infothis.ActiveView.ViewInfo);
                }
            }
            finally {
                cache.Dispose();
            }

            base  

 

在这里,有这样一句:

GraphicsInfoArgs info = new GraphicsInfoArgs(new GraphicsCache(e), this.Bounds);

            GraphicsCache cache = info.Cache;

 

看到这时在,就很头痛。

因为,devExpress采用了缓存的机制。

这样一来,没办法在另一台显示器上,直接单步跟踪,看画到哪里了。

 

只好,一点点的用跳过一行的办法,来分析,跳过的那行画的是什么。

 

        public virtual void Draw(GraphicsInfoArgs eSchedulerViewInfoBase viewInfo) {
            GraphicsCache cache = e.Cache;
            DrawBorders(cacheviewInfo);
            DrawCellContainers(cacheviewInfo);
            DrawHeaders(cacheviewInfo);
            SchedulerControl control = viewInfo.View.Control;
            DrawAppointments(cache, viewInfo);
            DrawMoreButtons(viewInfocache);
            DrawNavigationButtons(cacheviewInfo.NavigationButtonscontrol);
        }
        protected virtual internal void DrawAppointments(GraphicsCache cacheSchedulerViewInfoBase viewInfo) {
            AppointmentPainter.DrawAppointments(cacheviewInfo);

        } 

 

AppointmentPainter 类中的
 
        protected internal virtual void DrawAppointments(GraphicsCache cacheSchedulerViewInfoBase viewInfoProcessAppointmentType selection) {
            SchedulerViewCellContainerCollection containers = GetScrollContainers(viewInfo);
            for (int i = 0; i < containers.Counti++) {
                SchedulerViewCellContainer container = containers[i];
                AppointmentViewInfoCollection appointments = GetAppointments(viewInfocontainerselection);
                if (appointments.Count > 0) {
                    int scrollOffset = container.CalculateScrollOffset();
                    DrawContainerAppointments(cacheappointmentsviewInfo.View.ControlcontainerscrollOffset);
                }
            }
        }  

 

 

        protected internal virtual void DrawContainerAppointments(GraphicsCache cacheAppointmentViewInfoCollection appointmentsISupportCustomDraw customDrawProviderSchedulerViewCellContainer containerint scrollOffset) {
            PrepareAppointmentDrawing(cachescrollOffset);
            try {
                Rectangle clipBounds = CalculateContainerClipBounds(container);
                clipBounds.Offset(0, scrollOffset);
                using (IntersectClipper clipper = new IntersectClipper(cacheclipBoundsIntersectClipperOptions.SkipCurrentClip)) {
                    DrawAppointmentsCore(cacheappointmentscustomDrawProvider);
                }
            } finally {
                RestoreAppointmentDrawing(cache);
            }

        }  

 

 

        protected virtual void DrawForeground(GraphicsCache cacheAppointmentViewInfo viewInfoRectangle viewClipBoundsISupportCustomDraw customDrawProvider) {
            DefaultDrawDelegate defaultDraw = delegate() { DrawForegroundCore(cacheviewInfoviewClipBoundscustomDrawProvider); };
            if (RaiseCustomDrawAppointment(cacheviewInfocustomDrawProviderdefaultDraw))
                return;
            defaultDraw();
        }
 
-----------------------------------
  if (RaiseCustomDrawAppointment(cacheviewInfocustomDrawProviderdefaultDraw))
这里,激发了schedulerControl_CustomDrawAppointment事件。
给用户一次机会来处理。
-----------------------------------
        protected virtual void DrawForegroundCore(GraphicsCache cacheAppointmentViewInfo viewInfoRectangle viewClipBoundsISupportCustomDraw customDrawProvider) {
            DrawAppointmentItems(cacheviewInfo.Items);
            DrawAppointmentItems(cacheviewInfo.StatusItems);
            if (viewInfo.Selected)
                DrawSelection(cacheviewInfoviewClipBoundscustomDrawProvider);

        } 

然而,到了这里,发现不好办了:

 

Sources\DevExpress.XtraScheduler\DevExpress.XtraScheduler\BasePainter.cs

        protected virtual void DrawAppointmentItems(GraphicsCache cacheViewInfoItemCollection items) {
            int count = items.Count;
            for (int i = 0; i < counti++)
                items[i].Draw(cachethis);
        } 


Sources\DevExpress.XtraScheduler\DevExpress.XtraScheduler\AppointmentContentLayoutCalculator.cs

AppointmentViewInfoStatusItem类中的:

 

  public override void Draw(GraphicsCache cache, IViewInfoItemPainter painter) {
   AppointmentPainter aptPainter = (AppointmentPainter)painter;
   aptPainter.DrawStatusItem(cache, this);
  }

然后

		protected internal override void DrawStatusItem(GraphicsCache cache, AppointmentViewInfoStatusItem item) {
			Image image = item.CachedImage;
			if (image != null)
				cache.Graphics.DrawImage(image, item.Bounds, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
		}


这里我们看到,代码的做法是直接往cache中贴了张图。

当然,我们自己编gid程序时,也常这么用,但这里看到,还是不免有些泄气。

 

还需要重头分析。

我们得找出来,什么时候,Appointment,给自己画好了图。这是我想写本文的原因。

这里的思想特别好。

 

目前来看,有两个思想,devExpress用到了,一个是双buffer,

另一个就是小的组件,先在内存中画好,需要的时候,把这个图直接贴上。这个主意太好了。

 

然后就找到这两个函数。

		protected internal override void CacheStatusImage(AppointmentViewInfoStatusItem statusItem) {
			Bitmap statusBitmap = CreateUnclippedStatusBitmap(statusItem);
			AppointmentStatusImageCalculator calc = statusImageCalculatorFactory.GetStatusImageCalculator(statusItem.SkinElementName);
			calc.MaskStatusBitmap(statusBitmap, statusItem);
			statusItem.CachedImage = statusBitmap;
		}
		protected internal virtual Bitmap CreateUnclippedStatusBitmap(AppointmentViewInfoStatusItem statusItem) {
			Rectangle bounds = statusItem.Bounds;
			Bitmap result = new Bitmap(Math.Max(1, bounds.Width), Math.Max(1, bounds.Height));
			using (Graphics gr = Graphics.FromImage(result)) {
				gr.TranslateTransform(-bounds.X, -bounds.Y);
				using (GraphicsCache cache = new GraphicsCache(gr)) {
					base.DrawStatusItem(cache, statusItem);
				}
			}
			return result;
		}


 

		protected internal virtual void DrawStatusItem(GraphicsCache cache, AppointmentViewInfoStatusItem item) {
			statusPainter.DrawRectangleStatus(cache, item.BackgroundViewInfo);
			if (item.ForegroundViewInfo.Interval.Duration == TimeSpan.Zero)
				statusPainter.DrawTriangleStatus(cache, item.ForegroundViewInfo, item.Alignment);
			else
				statusPainter.DrawRectangleStatus(cache, item.ForegroundViewInfo);
		}


DevExpress.XtraScheduler\DevExpress.XtraScheduler\BasePainter.cs

		protected internal virtual void DrawRectangleStatus(GraphicsCache cache, AppointmentStatusViewInfo statusViewInfo) {
			cache.FillRectangle(statusViewInfo.Brush, statusViewInfo.Bounds);
			statusViewInfo.CalcBorderBounds(this);
			DrawBorders(cache, statusViewInfo);
		}


 

先写到这里。

虽然,找到了位置,但还是有些遗憾。

还没有想出办法,如何在调试的时修,关掉缓存。

 

在注掉一句话后,我们看到了预期的效果:

注掉的那句:
Sources\DevExpress.XtraScheduler\DevExpress.XtraScheduler\BasePainter.cs

 

 

代码是11.2的。

因为刚试用过最新的2014,winform这里没有本质的改动。

11.2的最大的好处是,可以用vs 2008编译过。我的电脑,虽然还可以,但vs2008还是比较快一些。

 

 

分析Devexress类库的绘制过程