基于这个问题,给我们的开发造成了很多的不便,因为我们很多时候需要对一些控件进行修改,比如这次我就想在ListView的项目中实现一个滚动条的效果。经过查阅,解决的方式有以下几种:1:可以试用控件嵌入的方法,通过使用C++中的钩子,来截获消息,并实现两个控件之间的统一行为 2:直接继承自Control类,重画。
OwnerDrawnList控件设计
在OpennetCF上发现了一篇好文章,有关sdf中的ownerDrawnList的创建及应用,翻译下来,一起共享
原文链接: http://www.opennetcf.com/Default.aspx?tabid=120
介绍:
在桌面平台.NET Framwork下,ListBox,ComoBox和Menu提供了修改其显示项的能力,我们可以响应这些控件中的DrawItem事件横容易的实现修改字体,背景,还有每一项的颜色等,但这些都不适用与.net cf,因为cf下没有暴露DrawItem这个事件。主要原因是它所依赖的操作系统Windows CE 3.0没有提供创建windows自有控件的能力。
然而,.net CF提供了一些相关的快速绘制的素材和一个便捷的从零做起来创建一个自呈现基础控件的方法。所以我决定来创建一个可以响应DrawItem事件的可继承的基础控件,这样提供简易可扩展的自定义绘制列表。下面就是创建过程
第一步:
首先我们确定要实现的基础控件叫做 OwnerDrawnList ,第一步为此类添加DrawItemEventHandler 委托方法和 DrawItemEventArgs 类,而且尽量把实现的方法和属性与桌面的.net framework相匹配。
//可以处理DrawItem事件的方法
public delegate void DrawItemEventHandler(object sender, DrawItemEventArgs e);
//描述了被画项的状态
public enum DrawItemState
{
None = 0,
Selected = 1,
Disabled = 4,
Focus = 16
}
///
/// 为DrawItem事件提供数据
///
public class DrawItemEventArgs : System.EventArgs
{
//private members
private Color backColor;
private Color foreColor;
private Font font;
private int index;
private Graphics graphics;
private Rectangle rect;
private DrawItemState state;
///
/// 初始化一个DrawItemEventArgs的新实例
///
public DrawItemEventArgs(Graphics graphics, Font font, Rectangle rect, int index, DrawItemState state,Color foreColor, Color backColor )
{
//initialize private members
this.graphics = graphics;
this.font = font;
. . .
}
///
/// 使用合适的颜色在DrawEventArgs所定义的区域内画背景
///
public virtual void DrawBackground()
{
Brush brush;
if (state == DrawItemState.Selected)
{
brush = new SolidBrush(SystemColors.Highlight);
}
else
{
brush = new SolidBrush(backColor);
}
graphics.FillRectangle(brush, rect);
}
. . .
下一步我们创建OwnerDrawnList 这个类, 它直接继承自System.Windows.Forms.Control:
public abstract class OwnerDrawnList : Control
{
// 在这里插入实现代码
}
添加DrawItem 事件,当需要绘制的时候触发该事件
//DrawItem event
public event DrawItemEventHandler DrawItem;
///
/// 响应DrawItem事件
///
protected virtual void OnDrawItem(object sender, DrawItemEventArgs e)
{
if (this.DrawItem != null)
this.DrawItem(sender, e);
}
我们将要显示的项目保存在一个私有的 listItems 的可变 ArrayList 中
private ArrayList listItems;
///
/// ListItems属性器
///
public ArrayList Items
{
get
{
return listItems;
}
}
OwnerDrawnList应该可以显示一个可滚动的数据视图,在这里我们将添加一个垂直的滚动条到我们的控件
//Vertical scroll
private ScrollBar vScroll;
//默认构造函数
public OwnerDrawnList()
{
listItems = new ArrayList();
//创建滚动条实例
vScroll = new VScrollBar();
//隐藏
vScroll.Hide();
//绑定只更改事件,通过委托处理该事件
vScroll.ValueChanged+=new EventHandler(vScrollcroll_ValueChanged);
//在控件容器中添加滚动条
this.Controls.Add(vScroll);
SCROLL_WIDTH = vScroll.Width;
}
可通过ShowScrollbar属性来设置是否使用滚动条:
///
/// 滚动条显示属性器
///
public bool ShowScrollbar
{
get
{
return showScrollbar;
}
set
{
showScrollbar = value;
vScroll.Visible = showScrollbar;
}
}
//设置Item的高度
protected virtual int ItemHeight
{
get
{
return this.itemHeight;
}
set
{
this.itemHeight = value;
}
}
因为 OwnerDrawnList是一个自绘制控件,所以它的核心应该都在OnPaint 中:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e )
{
//创建一个缓冲bmp的Graphics
Graphics gxOff = Graphics.FromImage(m_bmpOffscreen);
//清除
gxOff.Clear(this.BackColor);
int itemStart = 0;
int ipos = 0;
int drawCount = 0;
//得到第一个可见的Item
if (showScrollbar)
itemStart = vScroll.Value;
//得到要显示的Item数目
if (showScrollbar)
drawCount = DrawCount;
else
drawCount = listItems.Count;
//对整个Items进行遍历
for(int index=itemStart;index< span>
{
//项目矩形框
Rectangle itemRect = new Rectangle(0, ipos*itemHeight, this.ClientRectangle.Width, itemHeight);
DrawItemState state;
//设置相应的状态
if (index == this.SelectedIndex)
state = DrawItemState.Selected;
else
state = DrawItemState.None;
//绘制参数
DrawItemEventArgs drawArgs = new DrawItemEventArgs(gxOff, this.Font, itemRect, index, state);
//为继承者触发相应的绘制函数
OnDrawItem(this, drawArgs);
//移到下一项
ipos++;
}
//Rectangle for the border
Rectangle rc = this.ClientRectangle;
rc.Height--;
rc.Width--; OwnerDrawnList控件设计
//绘制bmp
gxOff.DrawRectangle(new Pen(Color.Black), rc);
//绘制整个控件
e.Graphics.DrawImage(m_bmpOffscreen, 0, 0);
gxOff.Dispose();
}
在上面的代码中我们在绘制过程中实现了双缓冲,在每次绘制的过程中,计算出需要被绘制的项目并在遍历将其绘制. 并在遍历过程中我们已经预备生成了DrawItemArgs和触发了OnDrawItem事件
选中某一项我们需要触发OnMouseDown 事件:
protected override void OnMouseDown(MouseEventArgs e)
{
this.SelectedIndex = this.vScroll.Value + (e.Y / this.ItemHeight);
// 将控件设为无效,重新绘制会对其刷新
this.Invalidate();
}
有关OwnerDrawnList 的其他代码在这里就不一一详述了,可以通过sample获得整个代码,对于滚动条,还可以根据listView的item数量来设置其Max值
对OwnerDrawnList继承示例
创建了OwnerDrawnList之后,那我们开始应用它的灵活和便利. 在这里我通过继承生成了两个实例:CustomListBox和MenuBox,首先我们看CustomListBox
public class CustomListBox : OwnerDrawnList
{
const int DRAW_OFFSET = 4;
//在构造器中初始化属性
public CustomListBox()
{
this.ShowScrollbar = true;
this.ForeColor = Color.Black;
//计算每项高度
Graphics g = this.CreateGraphics();
this.ItemHeight = 2 * Math.Max((int)(g.MeasureString("A", this.Font).Height), this.ItemHeight) + 2;
//释放掉绘图设备文
g.Dispose();
}
}
现在我们重写OnDrawnItem,在这里可以加入我们自己设计的如何绘制每项的逻辑
protected override void OnDrawItem(object sender, DrawItemEventArgs e)
{
Brush textBrush; //文字画刷
Rectangle rc = e.Bounds;
rc.X += DRAW_OFFSET;
//检查此项是否已经被选中了
if (e.State == DrawItemState.Selected)
{
//如果被选中就显示高亮
e.DrawBackground();
textBrush = new SolidBrush(SystemColors.HighlightText);
}
else
{
//更改每项的背景色
if ((e.Index % 2) == 0)
{
e.DrawBackground(Color.LightBlue);
}
textBrush = new SolidBrush(this.ForeColor);
}
//绘制字符串
e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font, textBrush, rc);
//绘制每项的分割条
e.Graphics.DrawLine(new Pen(Color.Black), 0, e.Bounds.Bottom, e.Bounds.Width, e.Bounds.Bottom);
//调用基类绘制函数
base.OnDrawItem (sender, e);
}
下面图例是显示效果
下面的代码是如何在使用我们继承过来的CustListBox
custListBox = new CustomListBox();
custListBox.Width = 170;
custListBox.Height = 200;
custListBox.Location = new Point(25, 10);
//设置字体
custListBox.Font = new Font("Frutiger Linotype", 8F, FontStyle.Regular);
//填充项目
custListBox.Items.Add("alex@mail.com 8:10 AM 1K Send me some files");
custListBox.Items.Add("bill@house.net 8:12 AM 1K Interested in your..");
custListBox.Items.Add("seth@compf.net 9:12 AM 1K Breaking the barier");
custListBox.Items.Add("craig@blue.com 11:10 AM 1K Performance imrovement..");
custListBox.Items.Add("admin@unmos.com 11:22 AM 1K Audit log report..");
custListBox.Items.Add("hjudy@ipcons.net 12:15 AM 1K Tentative meeting morrow");
custListBox.Items.Add("pass@mtrdrop.com 7:32 PM 1K Organizational announcements");
custListBox.Items.Add("bill@house.net 8:44 PM 1K User maintanence app...");
//将控件加入换题
this.Controls.Add(custListBox);
“But what if I want to show icons or images for my items in the list?” I hear you ask. To achieve this, some straightforward modifications to our CustomList are required. First we will add a ListItem class:
//ListItem类
public class ListItem
{
//私有成员
private string text = "";
private int imageIndex = -1;
public string Text
{
get
{
return text;
}
set
{
text = value;
}
}
public int ImageIndex
{
get
{
return imageIndex;
}
set
{
imageIndex = value;
}
}
}
添加ImageList属性
private ImageList imageList = null;
public ImageList ImageList
{
get
{
return imageList;
}
set
{
imageList = value;
}
}
现在我们修改 OnDrawItem的实现
//得到 ListItem
ListItem item = (ListItem)this.Items[e.Index];
//检查一项是否有图片
if (item.ImageIndex > -1)
{
Image img = imageList.Images[item.ImageIndex];
if (img != null)
{
ImageAttributes imageAttr = new ImageAttributes();
//设置transparency,一下代码是设置图片的透明效果
imageAttr.SetColorKey(BackgroundImageColor(img), BackgroundImageColor(img));
Rectangle imgRect = new Rectangle(2, rc.Y + 1, img.Width, img.Height);
e.Graphics.DrawImage(img, imgRect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imageAttr);
//将文字移动
rc.X+=img.Width + 2;
}
}
设置图片透明,就需要获得背景图片的颜色
private Color BackgroundImageColor(Image image)
{
Bitmap bmp = new Bitmap(image);
Color ret = bmp.GetPixel(0, 0);
return ret;
}
一下为添加设置控件的项目
//声明ImageList
custListBox.ImageList = imageList1;
//扩充项目
ListItem item1 = new ListItem();
item1.Text = "Meeting notes";
item1.ImageIndex = 0;
custListBox.Items.Add(item1);
ListItem item2 = new ListItem();
item2.Text = "Interested in your..";
item2.ImageIndex = 1;
custListBox.Items.Add(item2);
...
以下是效果图
我们实现了一个灵活易用的基础类,这是一个很强大的工具,我们可以通过它来创建自己的应用。