zoukankan      html  css  js  c++  java
  • MSChart控件Demo中打印表格的Bug

    最近因为项目中使用到了图形及图形打印功能,因此想到使用.NET平台下的Chart控件,在其给出的Demo中,有非常丰富的示例可以满足实际开发要求。由于项目中有一个要求是在根据数据源画出柱状图形的同时,显示柱状图所对应的表格(数据)。发现在其ChartFeatures/Customization and Events下的Custom Painting a DataTable示例与要求比较相似。其显示如下:

    1

    图(1) 柱状图及表格显示效果

    图形下面是数据源,上面是根据数据源画出的柱状图形,比较美观。但是在打印时,却出现了问题,其打印效果如下:

    2

    图(2) 打印时的呈现效果

    数据表格在打印的时候,显示不完整。为了找到原因,先分析上面图形是如何形成的。

    对于.NET提供的控件,是由其OnPaint事件完成绘制工作的。而此Chart控件在OnPaint时,只绘制了柱状图形部分,其数据表格部分,是在其PostPaint事件时绘制的。Chart控件其自身的打印方法,提供了图形打印功能,即可以只调用Chart.Printing.Print()函数,完成整个图形的打印。当然,在打印的过程中,会产生图形的重绘,引发OnPiant事件和PostPaint事件。为了找到产生此种错误的原因,只有仔细读取代码,可是代码中也没有找到明显的错误,花了几乎一天的时候,最后才想到,问题可能就出在打印时的重绘。因为打印时使用的坐标是设备坐标,而显示的时候则使用的是页面坐标。在两个坐标转换过程中,由于PageUnit的不同而导致显示的不正常。

         坐标系类型,在GDI时代分为逻辑坐标和物理坐标,而GDI+中分别为:

    1. 世界坐标(World Coordinate):要测量的点距离文档区域左上角的位置(以像素为单位);
    2. 页面坐标(Page Coordinate):要测量的点距离客户区域左上角的位置(以像素为单位);
    3. 设备坐标(Device Coordinate):它类似于页面坐标,但是其测量单位不一定是像素,而是用户通过调用PageUnit属性,指定它的单位。它除了默认的像素外,还包括英寸和毫米。如果我们为某个图形设定了其PageUnit为英寸或毫米,那么不管它在哪种设备上显示的大小都是一样的,不会因为分辨率的不同而大小不一致。

    在跟踪代码调试过程中发现,图形显示时,使用的是PageUnit.Dispaly枚举值“指定显示设备的度量单位。 通常,视频显示使用的单位是像素;打印机使用的单位是 1/100 英寸。”,而在打印的时候,会引发两次Paint事件,一次是在将显示内容重绘到打印机上,一次是因为输出到打印机的窗口显示时,引发了当前图形显示窗口的重绘。在输出到打印机时,Graphics.PageUnit值为Pixel。与OnPaint事件中使用的PageUnit.Display不同,似乎是找到了问题的根本所在。

    但是,从图(2)中可以看出,打印时,每个单元格宽度的计算是正确的,而高度的计算则不完全准确。如果是因为PageUint的原因,则应该整个表格显示都不正确。看来,问题还得继续找。

    既然打印和显示都与像素有关系,那么就说说像素。

    /*
    对于计算机的屏幕设备而言,像素(Pixel)或者说px是一个最基本的单位,就是一个点。
    其它所有的单位,都和像素成一个固定的比例换算关系。
    所有的长度单位基于屏幕进行显示的时候,都统一先换算成为像素的多少,然后进行显示。
    所以,就计算机的屏幕而言,相对长度和绝对长度没有本质差别。
    任何单位其实都是像素,差别只是比例不同。
    */

    对于像素的直接反映:“点”,就是要显示的内容,但是对于一个点占居多大的尺寸,不同的设备有不同的标准。显示器和打印机,其分辨率是不一样的,即DPI(每英寸显示的点数)可能不一样,如果使用PageUnit.Pixel时,在转换过程中则可能出现问题。

    继续调试代码。

    分别查看显示和打印时,Graphics.Dpix的值,分别为96和300。问题终于找到了。

    原来在打印的时候,Graphics会根据当前的输出设备,自动计算显示宽度与高度。但是,在PostPaint事件中,当画表格矩形框的时候,使用的宽度为axisFont.Height,而不是axisFontSize.Height。前面指定的字体大小是一定的,而后面的Size大小,则是经过Graphics根据当前的PageUnit和Dpix自动换算得到的。通过调试,计算得到两者的比正好是96:300。因此,只需要将PaintDataTable函数中使用axisFont.Height的地方,用axisFontSize.Height代替就可以了。

    3

    图(3) 代码更改后的打印效果

    当然也有其它方法,如可能通过判断当前使用的PageUnit,而确定需要不需要对高度进行调整与改变。

    此外,对此Demo中的两个建议:

    • 指定了接受的Chart控件的ChartArea.Name必须为”Default”,这样,就不方便使用。可以将“Default”更改为area.Name。
    • 在表格打印的开头,首先打印出来显示的颜色,此处的宽度指定为10,则在显示与打印的时候,其中间还是相差96:300的比例。因此,可在函数开始判断一下,如果当前的Graphics的PageUnit为Pixel,则将宽度指定为10*300/96;

    至此,困扰了我一两天的问题解决了。由于之前没有接触过打印及图形显示方面的技术,解决起来很费劲,而且Chart控件是在.NET3.5SP1之后才有的,用的人不是太多,有些问题,在网上找也找不到。现在贴出来,但愿会对碰到此问题的朋友有所帮助,也希望大家拍砖指教。

    待决问题:仔细看图1,图2,图3,会发现图3下面没有“This is the x Axis”。这个是我在调试过程中,发现高度调整的时候,会部分掩盖AxisX.Title,一时没有找到好的解决办法。不知各位有什么好方法,还请指点。

    PS:下面把相关的代码贴出,注意此函数在\WinSamples2010\WinSamples\Utilities\ChartDataTableHelper\ChartDataTableHelper.cs中。

      1:  /// <summary>
      2:          /// This method does all the work for the painting of the data table.
      3:          /// </summary>
      4:          private void PaintDataTable(System.Windows.Forms.DataVisualization.Charting.ChartPaintEventArgs e)
      5:          {
      6:              ChartArea area = (ChartArea) e.ChartElement; 
      7:   
      8:              // get the rect of the chart area
      9:              RectangleF rect = e.ChartGraphics.GetAbsoluteRectangle( area.Position.ToRectangleF() );
     10:   
     11:              // get the inner plot position
     12:              ElementPosition elemPos = area.InnerPlotPosition;
     13:   
     14:              // find the coordinates of the inner plot position
     15:              float x = rect.X + (rect.Width / 100 * elemPos.X);
     16:              float y = rect.Y + (rect.Height / 100 * elemPos.Y);
     17:              float ChartAreaBottomY = rect.Y + rect.Height;
     18:   
     19:              // offset the bottom by the width+1 of the scrollbar if it is visible
     20:              if(area.AxisX.ScrollBar.IsVisible && !area.AxisX.ScrollBar.IsPositionedInside)
     21:                  ChartAreaBottomY -= ((float)area.AxisX.ScrollBar.Size+1);
     22:   
     23:              float width = (rect.Width / 100 * elemPos.Width);
     24:              float height = (rect.Height / 100 * elemPos.Height);
     25:   
     26:              // find the height of the font that will be used
     27:              Font axisFont = area.AxisX.LabelStyle.Font;
     28:              //axisFont = new Font(axisFont.FontFamily, axisFont.Size, axisFont.Style,GraphicsUnit.Point);
     29:              float tempHeight = axisFont.Height;
     30:              float tempWidth = 20;
     31:              if (e.ChartGraphics.Graphics.PageUnit == GraphicsUnit.Pixel)
     32:              { 
     33:                  tempHeight = (float)(axisFont.Height * 3.15);
     34:                  tempWidth =(float) (20 * 3.125);
     35:              }
     36:              string testString = "ForFontHeight";
     37:              SizeF axisFontSize = e.ChartGraphics.Graphics.MeasureString(testString, axisFont);
     38:   
     39:              // find the height of the font that will be used
     40:              Font titleFont = area.AxisX.TitleFont;
     41:              testString = area.AxisX.Title;
     42:              SizeF titleFontSize = e.ChartGraphics.Graphics.MeasureString(testString, titleFont);
     43:   
     44:              int seriesCount = 0;
     45:   
     46:              // for each series that is attached to the chart area,
     47:              // draw some boxes around the labels in the color provided
     48:              for(int i = e.Chart.Series.Count-1; i >= 0; i--)
     49:              {
     50:                  if(area.Name == e.Chart.Series[i].ChartArea)
     51:                  {
     52:                      seriesCount++;
     53:                  }
     54:              }
     55:   
     56:              // now, if a box was actually drawn, then draw 
     57:              // the verticle lines to separate the columns of the table.
     58:              if(seriesCount > 0)
     59:              {
     60:                  for(int i = 0; i < e.Chart.Series.Count; i++)
     61:                  {
     62:                      if(area.Name == e.Chart.Series[i].ChartArea)
     63:                      {
     64:                          double min = area.AxisX.Minimum;
     65:                          double max = area.AxisX.Maximum;
     66:   
     67:                          // modify the min value for the current axis view
     68:                          if(area.AxisX.ScaleView.Position-1 > min)
     69:                              min = area.AxisX.ScaleView.Position-1;
     70:   
     71:                          // modify the max value for the currect axis view
     72:                          if( (area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5) < max)
     73:                              max = area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5;
     74:   
     75:   
     76:                          // find the starting point that will be display.
     77:                          // this is dependent on the current axis view.
     78:                          // this sample assumes the same number of points in each
     79:                          // series so always take from the zeroth series
     80:                          int pointIndex = 0;
     81:                          foreach(DataPoint pt in ChartObj.Series[0].Points)
     82:                          {
     83:                              if(pt.XValue > min)
     84:                                  break;
     85:   
     86:                              pointIndex++;
     87:                          }
     88:   
     89:                          bool TableLegendDrawn = false;
     90:   
     91:                          for(double AxisValue = min; AxisValue < max; AxisValue++)
     92:                          {
     93:                              float pixelX = (float)e.ChartGraphics.GetPositionFromAxis(area.Name, AxisName.X, AxisValue);
     94:                              float nextPixelX = (float)e.ChartGraphics.GetPositionFromAxis(area.Name, AxisName.X, AxisValue + 1);
     95:                              float pixelY = ChartAreaBottomY - titleFontSize.Height - (seriesCount * axisFontSize.Height);
     96:   
     97:                              PointF point1 = PointF.Empty;
     98:                              PointF point2 = PointF.Empty;
     99:   
    100:                              // Set Maximum and minimum points
    101:                              point1.X = pixelX;
    102:                              point1.Y = 0;
    103:   
    104:                              // Convert relative coordinates to absolute coordinates.
    105:                              point1 = e.ChartGraphics.GetAbsolutePoint(point1);
    106:                              point2.X = point1.X;
    107:                              point2.Y = ChartAreaBottomY - titleFontSize.Height;
    108:                              point1.Y = pixelY;
    109:   
    110:                              // Draw connection line
    111:                              e.ChartGraphics.Graphics.DrawLine(new Pen(borderColor), point1,point2);
    112:   
    113:   
    114:                              point2.X = nextPixelX;
    115:                              point2.Y = 0;
    116:                              point2 = e.ChartGraphics.GetAbsolutePoint(point2);
    117:   
    118:                              StringFormat format = new StringFormat();
    119:                              format.Alignment = StringAlignment.Center;
    120:                              format.LineAlignment = StringAlignment.Center;
    121:   
    122:                              // for each series draw one value in the column
    123:                              int row = 0;
    124:                              foreach(Series ser in ChartObj.Series)
    125:                              {
    126:                                  if(area.Name == ser.ChartArea)
    127:                                  {
    128:                                      if(!TableLegendDrawn)
    129:                                      {
    130:                                          // draw the series color box 
    131:                                          e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(ser.Color),
    132:                                              x - tempWidth, row * (tempHeight) + (point1.Y), tempWidth, axisFontSize.Height);
    133:   
    134:                                          e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor),
    135:                                              x - tempWidth, row * (tempHeight) + (point1.Y), tempWidth, axisFontSize.Height);
    136:   
    137:                                          e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(tableColor),
    138:                                              x,
    139:                                              row * (tempHeight) + (point1.Y),
    140:                                              width, 
    141:                                              axisFontSize.Height); 
    142:   
    143:                                          e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor), 
    144:                                              x,
    145:                                              row * (tempHeight) + (point1.Y),
    146:                                              width, 
    147:                                              axisFontSize.Height); 
    148:   
    149:                                      }
    150:   
    151:                                      if(pointIndex < ser.Points.Count)
    152:                                      {
    153:                                          string label = ser.Points[pointIndex].YValues[0].ToString();
    154:                                          RectangleF textRect = new RectangleF(point1.X, row * (axisFontSize.Height) + (point1.Y + 1), point2.X - point1.X, axisFontSize.Height);
    155:                                          e.ChartGraphics.Graphics.DrawString(label, axisFont, new SolidBrush(area.AxisX.LabelStyle.ForeColor), textRect, format);
    156:                                      }
    157:   
    158:                                      row++;
    159:   
    160:                                  }
    161:                              }
    162:   
    163:                              TableLegendDrawn = true;
    164:   
    165:                              pointIndex++;
    166:                          }
    167:   
    168:                          // do this only once so break!
    169:                          break;
    170:                      }
    171:                  }
    172:              }
    173:          }
    174:   
    175:   
    176:   
  • 相关阅读:
    JSP简单访问数据库
    解析数据存储MySQL
    学习SSH框架
    JavaWEB中读取配置信息
    Eclipse中将Java项目转换成Web项目的方法
    JavaWEB入门
    万能数据库连接类-Oracle、DB2 、Access 、Sql Server
    小米3 打开开发者选项
    coolpad 5879logcat不能输入日志解决办法
    实用开发之-oracle表回滚到一个指定时间的操作语句
  • 原文地址:https://www.cnblogs.com/RitchieChen/p/1788636.html
Copyright © 2011-2022 走看看