自定义数据邦定控件:
不需要以来任何框架内置控件的数据邦定控件。
目的:创建一个数据邦定控件,输出柱状图来帮助客户作出决策。每一行数据表示一个实体,我们用一个派生与Control类的BarChartControl控件来表示。创建一个用户控件DataBoundBarChartControl,将输出图形的功能封装进BarChartControl里面。BarChartControl控件的大多实现都是平面几何的绘制
效果如下:
其代码详细如下:
1、 先编写BarChartControl类的数据对象ChartPair类,代码如下:
using System;
using System.Collections.Generic;
using System.Text;
namespace CustomControls
{
class ChartPair
{
private double m_value;
public double Value
{
get { return m_value; }
set { m_value = value; }
}
private string m_label;
public string Label
{
get { return m_label; }
set { m_label = value; }
}
public ChartPair(double val, string label)
{
this.m_value = val;
this.m_label = label;
}
}
}
2、 BarChartControl类的具体实现如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
namespace CustomControls
{
internal delegate void BarValueChangedEventHandler(object sender,int barIndex);
[ToolboxItem(false)]
internal class BarCharControl : Control,IDisposable
{
private BindingList<ChartPair> m_Data = new BindingList<ChartPair>();
private List<Rectangle> m_barRects = new List<Rectangle>();
private List<Rectangle> m_labelRects = new List<Rectangle>();
private List<Color> m_colorList = new List<Color>();
public event BarValueChangedEventHandler BarValueChanged;
public IList<ChartPair> Data
{
get
{
return m_Data;
}
}
public bool AllowEdit
{
get
{
return m_Data.AllowEdit;
}
set
{
m_Data.AllowEdit = value;
}
}
private Font m_labelFont = new Font("Vardana", 12, FontStyle.Bold);
public BarCharControl()
{
m_Data.ListChanged += new ListChangedEventHandler(m_Data_ListChanged);
m_colorList.Add(Color.Red);
m_colorList.Add(Color.Yellow);
m_colorList.Add(Color.Blue);
m_colorList.Add(Color.Green);
m_colorList.Add(Color.Purple);
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
SetSampleData();
}
void m_Data_ListChanged(object sender, ListChangedEventArgs e)
{
UpdateCoordinate();/*这里调用这个很重要,每次集合里面的数据项发生改变都回通知控件实例去重新计算各个矩形的区域*/
if (BarValueChanged!=null && e.ListChangedType== ListChangedType.ItemChanged)
{
BarValueChanged(this, e.NewIndex);
}
}
private void SetSampleData()
{
m_Data.Add(new ChartPair(2.3,"东北"));
m_Data.Add(new ChartPair(4.5,"西北"));
m_Data.Add(new ChartPair(7.8,"东南"));
m_Data.Add(new ChartPair(5.6,"西南"));
}
//通过SizeChanged事件来触发控件的计算及重绘
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
//进行几何图形的计算等
UpdateCoordinate();
}
private void UpdateCoordinate()
{
//先预防
if (m_Data.Count==0)
{
return;
}
m_barRects.Clear();
m_labelRects.Clear();
int index = 0;
//获取一些和计算平面图形有关的数据
const int barPadding = 5;//偏移量
int sliceWidth = ClientSize.Width / m_Data.Count;//每个柱状图所占水平长度
double maxData=0,minData=0;
foreach(ChartPair pair in m_Data)
{
if(pair.Value>maxData) maxData=pair.Value;
if(pair.Value<minData) minData=pair.Value;
}
double scaleHeight=(ClientSize.Height-m_labelFont.Height)/(maxData-minData);//每个柱状体的高度分量
foreach (ChartPair pair in m_Data)
{//每个ChartPair对象,我们都给它一个柱状图和下面的说明
int xLeft = index * sliceWidth + barPadding;
int xRight = (index + 1) * sliceWidth - barPadding;
int barHeight = (int)(pair.Value * scaleHeight);
int barTop = ClientSize.Height - barHeight - m_labelFont.Height;
int barWidth = xRight - xLeft;
m_barRects.Add(new Rectangle(xLeft, barTop, barWidth, barHeight));
m_labelRects.Add(new Rectangle(xLeft,ClientSize.Height-m_labelFont.Height,barWidth,m_labelFont.Height));
index++;
}
Invalidate();//强迫控件重绘
}
protected override void OnPaint(PaintEventArgs e)
{
//base.OnPaint(e);
//也是先预防
using(Brush labelBrush=new SolidBrush(Color.Black))
{
if (m_Data.Count==0 || m_barRects.Count==0 || m_labelRects.Count==0)
{
e.Graphics.DrawString("BarChartControl没有数据项可绘制", m_labelFont, labelBrush, ClientRectangle);
return;
}
foreach (ChartPair pair in m_Data)
{
int index = m_Data.IndexOf(pair);
Rectangle barRect = m_barRects[index];
Rectangle labelRect = m_labelRects[index];
int colorIndex = index % m_colorList.Count;
using (Brush barBrush=new SolidBrush(m_colorList[colorIndex]))
{
e.Graphics.FillRectangle(barBrush, barRect);
}
//柱状图的数字表示,绘在柱状图的最上面
Rectangle barTextRect = new Rectangle(barRect.Left, barRect.Top, barRect.Width, m_labelFont.Height);
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
e.Graphics.DrawString(pair.Value.ToString("f1"), m_labelFont, labelBrush, barTextRect);
e.Graphics.DrawString(pair.Label, m_labelFont, labelBrush, labelRect);
}
}
}
//鼠标点击发生数据修改事件
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (!m_Data.AllowEdit)
{
return;
}
foreach (ChartPair pair in m_Data)
{
Rectangle barRect = m_barRects[m_Data.IndexOf(pair)];
if (barRect.Contains(e.Location))
{
if (e.Button== MouseButtons.Left)
{
pair.Value = pair.Value + pair.Value * 0.1;
}
else if (e.Button == MouseButtons.Right)
{
pair.Value = pair.Value - pair.Value * 0.1;
}
//数据更改,要重新计算图形并重绘之
UpdateCoordinate();
break;
}
}
}
protected override void Dispose(bool disposing)
{
try
{
m_labelFont.Dispose();
}
finally
{
base.Dispose(disposing);
}
}
~BarCharControl()
{
m_labelFont.Dispose();//幸亏小菜我还看过一点C++的书,要//不然还不知道这个符号干啥用的,
//说不定有要求助于MSDN了
}
}
}
3、最后编写BarChartControl类的容器控件DataBoundBarChartControl
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Collections;
namespace CustomControls
{
[ToolboxBitmap(typeof(DataBoundBarChartControl), "shiboys.BMP")]
public partial class DataBoundBarChartControl : UserControl,ISupportInitialize
{
public DataBoundBarChartControl()
{
InitializeComponent();
}
private object m_dataSource;
[AttributeProvider(typeof(IListSource))]
public object DataSource
{
get { return m_dataSource; }
set {
m_dataSource = value;
UpdateDataBindings();
}
}
private string m_DataMember;
[Editor("System.Windows.Forms.Design.DataMemberListEditor,System.Designs,Version=2.0.0.0,"+
" Culture=neutral,PublicKeyToken=null",typeof(System.Drawing.Design.UITypeEditor))]
public string DataMember
{
get { return m_DataMember; }
set { m_DataMember = value;
UpdateDataBindings();
}
}
private string m_rowIdMember;
public string RowIdMember
{
get { return m_rowIdMember; }
set {
m_rowIdMember = value;
UpdateDataBindings();
}
}
private bool m_Initializing = false;
void ISupportInitialize.BeginInit()
{
m_Initializing = true;
}
void ISupportInitialize.EndInit()
{
m_Initializing = false;
UpdateDataBindings();
}
private bool m_UpdateDataSource = false;
private IList m_bindingList = null;
//为AddBarCharControl方法添加一个HashTable成员变量
Hashtable m_BarIndexContainer = new Hashtable();
private void UpdateDataBindings()
{
//先预防
if (m_Initializing || m_dataSource==null || m_rowIdMember==null)
{
return;
}
IList list = ListBindingHelper.GetList(m_dataSource, m_DataMember) as IList;
/*
ListBindingHelper :提供用于发现可绑定列表的功能,当该列表中包含的项的属性
与这些项要绑定到的对象的公共属性不同时,还提供相应的功能用于发现这些项的属性。
C#
public static Object GetList (
Object dataSource,
string dataMember
)
通过对指定数据源和可选数据成员进行求值来返回一个对象(通常为列表)。
参数
dataSource
要从其中查找列表的数据源。
dataMember
包含列表的数据源属性的名称。它可以是空引用(在 Visual Basic 中为 Nothing)。
返回值
如果已找到基础列表,则为表示该基础列表的 Object,否则为 dataSource。
异常
*/
if (list==null || list.Count==0)
{
return;
}
m_bindingList = list;
m_UpdateDataSource = list.IsReadOnly;
IBindingList blist = list as IBindingList;
if (blist!=null)
{//取得编辑更细的粒度
m_UpdateDataSource = blist.AllowEdit;
}
int verPos = 0;//每个BarCharControl实例的ClientSize.Height高度属性设置
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(list[0]);
foreach (object dataItem in list)
{
PropertyDescriptor propDesc = props.Find(m_rowIdMember, true);
if (propDesc==null)
{
continue;//这里表示如果用户没有设定DataBoundBarChartControl.RowIdMember属性
//则不予绘制该数据项
}
//如果有该属性,则我们用一个Label控件来表示(绘制)出来
Label label = new Label();
label.Location = new Point(0, verPos);
label.Width = 150;
label.Font = new Font("Arial", 16, FontStyle.Bold);
label.Text = propDesc.GetValue(dataItem).ToString();
Controls.Add(label);
AddBarChartControl(list.IndexOf(dataItem), 150, verPos, dataItem, props);
verPos += 150;
}
}
private void AddBarChartControl(int index, int xPos, int yPos, object dataItem, PropertyDescriptorCollection props)
{
BarCharControl bar = new BarCharControl();
m_BarIndexContainer.Add(bar,index);
bar.BarValueChanged += new BarValueChangedEventHandler(bar_BarValueChanged);
bar.AllowEdit = m_UpdateDataSource;
ChartPair pair = bar.Data[index];
bar.Location = new Point(xPos, yPos);
bar.Size = new Size(ClientSize.Width - 200, 150);
Controls.Add(bar);
bar.Data.Clear();
//因为用户有了自己的数据,所以就要先清空BarCharControl中构造函数创建的
// 示例数据(SampleData),然后再把用户自己的数据添加上来
foreach (PropertyDescriptor propDesc in props)
{
Type type = propDesc.PropertyType;
string propName = propDesc.DisplayName;/*Name属性获取此成员的名称,DisplayName
获取可以显示在属性窗口的名称*/
if (propName==m_rowIdMember)
{
continue;//BarChartControl控件只管理ChartPair数据,对于RowIDMember(比如说年份)
//不管理,因为其调用者UpdateDataBindings已经处理了RowIDMember
}
double propValue;//用来设置用户给ChartPair对象的Value属性传入的值
if (
type==typeof(int)|| type == typeof(short) || type == typeof(long) || type == typeof(float)|| type == typeof(double)
)
{
propValue = Convert.ToDouble(propDesc.GetValue(dataItem));
}
if (type==typeof(string))//这个条件是说如果碰到了ChariPair的Label属性,可是后面是这样
//添加的,我都有点不懂了,调试下看看吧!后来看了其XML文档知道了
//其XML文档如下:
/*
<Sales>
<Year>1999</Year>
<NW>23.4</NW>
<NE>75.0684</NE>
<SW>22.2198471</SW>
<SE>65.3</SE>
</Sales>
*/
{
bool succeed = false;
succeed = double.TryParse(Convert.ToString(propDesc.GetValue(dataItem)), out propValue);
if (!succeed)
{
continue;
}
}
else
{
continue;
}
//剩下的过滤后的只有显示为数据属性的被添加来
bar.Data.Add(new ChartPair(propValue, propName));
}
}
void bar_BarValueChanged(object sender, int barIndex)
{
BarCharControl bar = sender as BarCharControl;
//从HashTable中获取对象的索引号
int index = (int)m_BarIndexContainer[sender];
//从数据集合中获取相对应的数据项
object dataItem = m_bindingList[index];
//获取改变后的数据值
ChartPair pair = bar.Data[index];
//查找该数据项上的所有属性
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(dataItem);
PropertyDescriptor propDesc = props.Find(pair.Label, true);
if (propDesc ==null)
{
throw new ArgumentException("在改变的数据项上未能发现" + pair.Label + "属性");
}
if (propDesc.PropertyType==typeof(double))
{
propDesc.SetValue(dataItem, pair.Value);
}
if (propDesc.PropertyType== typeof(int))
{
propDesc.SetValue(dataItem, (int)pair.Value);
}
if (propDesc.PropertyType== typeof(float))
{
propDesc.SetValue(dataItem, (float)pair.Value);
}
if (propDesc.PropertyType== typeof(long))
{
propDesc.SetValue(dataItem, (long)pair.Value);
}
if (propDesc.PropertyType==typeof(short))
{
propDesc.SetValue(dataItem, (short)pair.Value);
}
if (propDesc.PropertyType==typeof(string))
{
propDesc.SetValue(dataItem,pair.Value.ToString());
}
else
{
throw new InvalidCastException("未能转换成类型:" + propDesc.PropertyType.ToString());
}
}
}
}
4、 最后附上测试代码和测试用的XML
DataSet ds = new DataSet();
ds.ReadXml("tempdata.xml");
dataBoundBarChartControl1.DataSource = ds;// SetSampleData();
dataBoundBarChartControl1.DataMember = "Sales";
dataBoundBarChartControl1.RowIdMember = "Year";
<?xml version="1.0" standalone="yes"?>
<NewDataSet>
<Sales>
<Year>1999</Year>
<NW>23.4</NW>
<NE>75.0684</NE>
<SW>22.2198471</SW>
<SE>65.3</SE>
</Sales>
<Sales>
<Year>2000</Year>
<NW>33.1</NW>
<NE>43.2</NE>
<SW>69.9931596265854</SW>
<SE>77.1</SE>
</Sales>
<Sales>
<Year>2001</Year>
<NW>47.9</NW>
<NE>23.2</NE>
<SW>33.1</SW>
<SE>55.8</SE>
</Sales>
<Sales>
<Year>2002</Year>
<NW>31.9</NW>
<NE>67.9</NE>
<SW>12.1</SW>
<SE>33.3</SE>
</Sales>
</NewDataSet>
浙公网安备 33010602011771号