Search

Friday, November 13, 2009

Owner-Drawn ListViews or custom list view in windows mobile .netcf?

every developer knows the listview provided by .net compact framework most of the time will not serve the purpose consider if we want a list view some thing like native inbox mail list the generic .netcf tool box does not provide any such options in such cases we can take over the paint method and draw our own items.below is the implemention of an owner-drawn ListView i came across some chinese site it worked.


//This is the basic custom list view control
using System;
using System.Windows.Forms;
using System.Collections;
using System.Drawing;
using System.Data;
using System.Reflection;

using System.Drawing.Imaging;

namespace SmartUI {
enum MailIcon {
unopened,
opened
}

/// <summary>
/// simple MailItem class for each ListView item.
/// </summary>
class MailItem {
public MailIcon icon;
public string sender;
public string subject;

public MailItem(MailIcon Icon, string Sender, string Subject)
{
this.icon = Icon;
this.sender = Sender;
this.subject = Subject;
}
}

/// <summary>
/// Provide a line listview
/// </summary>
class MyListView : OwnerDrawnList {
constint Column1Left = 5;
const int Column2Left = 25;

public MyListView() {
// We need a total of 5 rows, so the height is 180/5=36
Graphics g = this.CreateGraphics();
Font font = new Font(this.Font.Name, 10, FontStyle.Bold);
// Calc line height to be height of letter A plus 10%
int fontHeight = (int)(g.MeasureString("A", font).Height*1.1);
this.ItemHeight = fontHeight*2;
g.Dispose();
}


protected override void OnKeyDown(KeyEventArgs e) {
switch(e.KeyCode) {
case Keys.Return:
MessageBox.Show("You selected item " + this.SelectedIndex.ToString(),
"Item selected",
MessageBoxButtons.OK,
MessageBoxIcon.Asterisk,
MessageBoxDefaultButton.Button1);
break;
}

base.OnKeyDown(e);
}



protected override void OnPaint(PaintEventArgs e) {

// Declare vars
Font font;
Color fontColor;
string bmpName;


Graphics gOffScreen = Graphics.FromImage(this.OffScreen);
gOffScreen.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);
int itemTop = 0;

for(int n = this.VScrollBar.Value; n <= this.VScrollBar.Value + DrawCount; n++)
{



if(n == this.SelectedIndex) //check whether if mail item is selected if so highlight the background color
{
//paint the selected rectangle with system background color
gOffScreen.FillRectangle(new SolidBrush(SystemColors.Highlight),
0,
itemTop,
this.ClientSize.Width - (this.VScrollBar.Visible ? this.VScrollBar.Width : 0),
this.ItemHeight);
fontColor = CalcTextColor(SystemColors.Highlight);
}
else
fontColor = this.ForeColor;


gOffScreen.DrawLine(new Pen(Color.DarkGray),
1,
itemTop+this.ItemHeight,
this.ClientSize.Width - (this.VScrollBar.Visible ? this.VScrollBar.Width : 2),
itemTop+this.ItemHeight);


MailItem lvi = (MailItem)this.Items[n];//paint appropriate mail item to background


if (lvi.icon == MailIcon.unopened) {
font = new Font(this.Font.Name, 10, FontStyle.Bold);
bmpName = "SmartUI.unread.bmp";
}
else {
font = new Font(this.Font.Name, 10, FontStyle.Regular);
bmpName = "SmartUI.read.bmp";
}


Bitmap bmp = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream(bmpName)); //make sure to give correct path else it may crash here

// To draw a transparent image, we need to set the transparent
ImageAttributes ia = new ImageAttributes();
ia.SetColorKey(Color.Red, Color.Red);

// Set the image rectangle
Rectangle imgRect = new Rectangle(Column1Left, itemTop+3, bmp.Width, bmp.Height);

// Draw the image
gOffScreen.DrawImage(bmp, imgRect, 0, 0, bmp.Width, bmp.Height, GraphicsUnit.Pixel, ia);
// Draw the mail sender
gOffScreen.DrawString(lvi.sender, font, new SolidBrush(fontColor), Column2Left, itemTop);
// Draw the mail subject
gOffScreen.DrawString(lvi.subject, font, new SolidBrush(fontColor), Column2Left, itemTop + (ItemHeight/2));


font.Dispose();
bmp.Dispose();


itemTop += this.ItemHeight;
}


e.Graphics.DrawImage(this.OffScreen, 0, 0);

gOffScreen.Dispose();
}

// Draws the external border around the control.
protected override void OnPaintBackground(PaintEventArgs e) {
// e.Graphics.DrawRectangle(new Pen(Color.Black), 0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1);
}
}


class OwnerDrawnList : Control {
int scrollWidth;
int itemHeight = -1;
int selectedIndex = -1;

Bitmap offScreen;
VScrollBar vs;
ArrayList items;
public OwnerDrawnList() {
this.vs = new VScrollBar();
scrollWidth = this.vs.Width;
this.vs.Parent = this;
this.vs.Visible = false;
this.vs.SmallChange = 1;
this.vs.ValueChanged += new EventHandler(this.ScrollValueChanged);

// Items to draw
this.items = new ArrayList();
}
/// <summary>
///
/// </summary>
public ArrayList Items {
get { return this.items;}
}

protected Bitmap OffScreen {
get {return this.offScreen;}
}

protected VScrollBar VScrollBar {
get {return this.vs;}
}

public event EventHandler SelectedIndexChanged;

// Raise the SelectedIndexChanged event
protected virtual void OnSelectedIndexChanged(EventArgs e) {
if(this.SelectedIndexChanged != null)
this.SelectedIndexChanged(this, e);
}


public int SelectedIndex {
get {return this.selectedIndex;}
set {
this.selectedIndex = value;
if (this.SelectedIndexChanged != null)
this.SelectedIndexChanged(this, EventArgs.Empty);
}
}

protected void ScrollValueChanged(object o, EventArgs e) {
this.Refresh();
}

protected virtual int ItemHeight {
get {return this.itemHeight;}
set {this.itemHeight = value;}
}

// If the requested index is before the first visible index then set the
// first item to be the requested index. If it is after the last visible
// index, then set the last visible index to be the requested index.
public void EnsureVisible(int index) {
if(index < this.vs.Value) {
this.vs.Value = index;
this.Refresh();
}
else if(index >= this.vs.Value + this.DrawCount) {
this.vs.Value = index - this.DrawCount + 1;
this.Refresh();
}
}

//on key press event
protected override void OnKeyDown(KeyEventArgs e) {
switch(e.KeyCode) {
case Keys.Down:
if(this.SelectedIndex < this.vs.Maximum) {
EnsureVisible(++this.SelectedIndex);
this.Refresh();
}
break;
case Keys.Up:
if(this.SelectedIndex > this.vs.Minimum) {
EnsureVisible(--this.SelectedIndex);
this.Refresh();
}
break;
}

base.OnKeyDown(e);
}


protected int DrawCount {
get {
if(this.vs.Value + this.vs.LargeChange > this.vs.Maximum)
return this.vs.Maximum - this.vs.Value + 1;
else
return this.vs.LargeChange;
}
}

protected override void OnResize(EventArgs e) {
int viewableItemCount = this.ClientSize.Height / this.ItemHeight;
this.vs.Bounds = new Rectangle(this.ClientSize.Width - scrollWidth,
0,
scrollWidth,
this.ClientSize.Height);


if(this.items.Count > viewableItemCount) {
this.vs.Visible = true;
this.vs.LargeChange = viewableItemCount;

this.offScreen = new Bitmap(this.ClientSize.Width - scrollWidth, this.ClientSize.Height);
}
else {
this.vs.Visible = false;
this.vs.LargeChange = this.items.Count;

this.offScreen = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
}

this.vs.Maximum = this.items.Count - 1;
}


protected Color CalcTextColor(Color backgroundColor) {
if(backgroundColor.Equals(Color.Empty)) 
return Color.Black;

int sum = backgroundColor.R + backgroundColor.G + backgroundColor.B;

if(sum > 256)
return Color.Black;
else
return Color.White;
}

}
}



//Call below code from any window form

public CustomListView()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//

olv = new MyListView();
olv.Parent = this;
// Set the bounds of the listview.
olv.Bounds = new Rectangle(0,0, this.ClientSize.Width, this.ClientSize.Height);

// Add data items to listview
for (int i=0; i<10; i++) {
olv.Items.Add(new MailItem(MailIcon.unopened, "Ceveni Com", "re: Custom ListView"));
olv.Items.Add(new MailItem(MailIcon.opened, "James Bond", "Custom ListView"));
}

olv.SelectedIndex = 0;
olv.EnsureVisible(olv.SelectedIndex);

}


if there is any problem in the below code or if there is still shortest method of doing it or any free third party control readily available share with us.

18 comments:

Tam Vo Minh said...

Thanks so much for your tutorial. It's so great ^_^;

Hikari said...

Manjunath, do you have any working code for this (sample project)?

Hikari said...

The problem is that I'm struggling to make it work. Either I have:
- compilattion errors (solved for now)
- display error (vertical scrollbar is too narrow)
- selection problems (I cannot select any items)

Working example would help a lot :)

Manjunath said...

@Hikari

Thanx for leaving comment...

the above code first part of the code write it as the control and

second part is used to update and call the list view what i gave is the generic Mail list view body, you can write your own code and modify according to your needs and improve it by adding

1. on touch events
2. On click multiple select

adding above is very simple let me know if you need any help

Hikari said...

Thank you for prompt reply.

OK, I'll try to analyze the code line by line (now I just copied it, removed some compilation errors and run as is).
I will write another post if help is needed.

Regards
Hikari

Anonymous said...

Manjunath

Sorry for bothering you but I gave up.

The problems:
1. I can't get access to bitmap files (but this issue is not directly related to the listview itself, but rather my poor knowledge). You will get compilation error, but you can ignore it.
2. Listview changes selction when I arrows, but now when I click it. I assume that some event is missing.
3. There is an error when you try to scroll to the very bottom (tested on WVGA emulator).

The project: http://cid-beeaa7a407c674bf.skydrive.live.com/self.aspx/.Public/C%5E3%20for%20Windows%20Mobile/SmartDeviceTest.zip

I would be grateful for some help/hints on errors 2 and 3.

Regards
Hikari

Manjunath said...

@hikari

I took a look at the uploaded file here is the suggestion for quick fix

1. first of all you did not written the first part of the code as the control i clearly mentioned it the first part of the code should be a written as control but you have directly integrated it in the form.

2. secondly, you should embedd the pictures in resource file and use it.

3. if you write it has a seperate control and then call it from the class the scroll problem gets eliminated.

and between if you are new to graphics programming or designing custom controls it takes time to do all these, if you are working on deadlines assign this task to some one who has experince in custom controls hope this helps. if you further face any problem please feel free to contact me via email: feedback AT ceveni.com

Hikari said...

Hello Manjunath,

Thanks for reply on your blog. Please find a CustomListView project after small amendments: http://cid-beeaa7a407c674bf.skydrive.live.com/self.aspx/.Public/C%5E3%20for%20Windows%20Mobile/SmartDeviceTest.zip

1) I put the listview code into a custom class. Last time indeed it was a part of the form code, but just for simplicity (first make it work and then I will.

2) I embedded the bitmaps as a resource, but it didn’t help.

3) The scroll problem is still there.

4) Maybe you can give me some hints on how to implement click event to select listview item?

The code I’m struggling to prepare is for my own freeware application. There is no deadline, no budget, etc. I’m just trying to make is useful as standard controls are ugly and obsolete. Between all the lines I hope that SDK for WP7S will solve the issue by providing new flexible controls.

P.S.
Unfortunately it seems feedback(at)ceveni.com does not work.

Regards
Hikari

Manjunath said...

@hikari

i found out the problem below points will solve your problem

1. First of all in resource file image properties make image as embedded resource

2. secondly to use image from the resource use below statement

Bitmap bmp = new Bitmap(SmartDeviceTest.Properties.Resources.Read);

and for the scroll error where u get crash at the increment "n" in customlistview.cs class change drawcount variable return statement from

return this.vs.Maximum - this.vs.Value + 1;

to

return this.vs.Maximum - this.vs.Value;

hope this will fix your problem

Hikari said...

Manjunath,

I promise this is the last thing :)

Could you give me some hint on how to override OnClick event? The hing I'm fighting with is how to determine which item was actually clicked (rest should be rather easy).

Regards
Hikari

Hikari said...

Seems I have a solution!

protected override void OnMouseUp(MouseEventArgs e)
{
double onScreenItem = e.Y / ItemHeight;
int index = (int)Math.Round(onScreenItem, 0) + this.VScrollBar.Value;
//this.EnsureVisible(index); why it does not work?
this.SelectedIndex = index;
this.Refresh();
}

MrJabber said...

This code helps a lot. Exactly what I was looking for. Good Job!

Unknown said...

Is there any way to clear OwnerDrawnList. When I'm creating new items list, new adds to previous one:/

Manju said...

just call...

olv.clear() and start adding new one

Unknown said...

I suppose you meant olv.Items.Clear(), because olv doesn't have this method itself.
After all, that's the way I was trying to clear olv, but after generating another one items list and after touching the scrollbar, some error occurs.
And what's wrong, I have no idea?

Anonymous said...

can you upload the sample project?

Anonymous said...

Is not working for me, it doesns't fire the events. just shows a blank page

needle said...

Hi m8,

i´m new to c# cf and this is what i would like to add int my first project....

could you please provide a sample project ?

thx

Post a Comment

Other Interesting Articles



Related Article Widget by Yogith

Search Powered by Google