#region Using directives
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.IO;
#endregion
namespace SmartInstitute.Client.Modules
{
public partial class OfferedClassListBox : ListBox
{
private class Consts
{
// cell space in each row
public const int Buffer = 4;
// where to draw the second column of information (from right side)
public const int RightBuffer = 120;
// don't draw the second column if the control is only this wide
public const int DrawRightSideWidth = 210;
}
// internal members
private Font m_fontHeader;
private StringFormat m_format;
// maintain offscreen bitmap to prevent flickering
private Bitmap m_bmpItem;
private Graphics m_graphics;
public OfferedClassListBox()
{
// owner draw, variable height so can draw the header
// with a different height
DrawMode = DrawMode.OwnerDrawVariable;
// font used when drawing the header
m_fontHeader = new Font("tahoma", Font.Size, FontStyle.Bold);
// format used when drawing comments
m_format = new StringFormat();
m_format.Trimming = StringTrimming.EllipsisCharacter;
InitializeComponent();
}
/// <summary>
/// Call this method when the control is fully loaded in order to
/// load the class list
/// </summary>
public void LoadClassList()
{
this.Clear();
this.BeginUpdate();
this.PopulateDummyData();
this.EndUpdate();
this.Invalidate();
}
// remove all items from the list
public void Clear()
{
// delete all items
Items.Clear();
Invalidate();
}
// calculate the height of the row
protected override void OnMeasureItem( MeasureItemEventArgs e )
{
base.OnMeasureItem(e);
if (e.Index < 0 || e.Index >= Items.Count)
return;
if (base.Items.Count > 0)
{
object item = base.Items[e.Index];
// Course rows take only one line as it is just the header
if (item is CourseNode)
e.ItemHeight = (Font.Height) + (Consts.Buffer * 5);
else
e.ItemHeight = (Font.Height * 3) + (Consts.Buffer * 5);
// Section nodes are multiple lines
}
}
// a row needs to be drawn
protected override void OnDrawItem( DrawItemEventArgs e )
{
//base.OnDrawItem(e);
if (e.Index < 0 || e.Index >= Items.Count)
return;
// each section is drawn on an offscreen bitmap to prevent flickering
// the bitmap is only created when necessary (when the row size changes)
//CreateOffscreenGraphics(e.Index);
//m_graphics.Clear(SystemColors.Window);
if (base.Items.Count > 0)
{
e.DrawBackground();
if (e.State == DrawItemState.Focus)
e.DrawFocusRectangle();
object item = base.Items[e.Index];
// Course rows take only one line as it is just the header
if (item is CourseNode)
DrawCourse(item as CourseNode, e.Graphics, e.Bounds);
else
DrawSection(item as SectionNode, e.Graphics, e.Bounds);
//blit the offscreen bitmap to the screen
//e.Graphics.DrawImage(m_bmpItem, e.Bounds.Left, e.Bounds.Top, m_bmpItem.Width, m_bmpItem.Height);
}
}
// need to redraw rows when resize
protected override void OnResize( EventArgs e )
{
base.OnResize(e);
Invalidate();
}
// draw the header
private void DrawCourse( CourseNode node, Graphics g, Rectangle bounds )
{
// left label
g.DrawString(node.Title, m_fontHeader, SystemBrushes.ControlText, bounds.Left + Consts.Buffer, bounds.Top + (bounds.Height - m_fontHeader.Height - Consts.Buffer));
// separator
g.DrawLine(Pens.LightGray, bounds.Left, bounds.Bottom - 1, bounds.Right, bounds.Bottom - 1);
}
// draw data item
private void DrawSection( SectionNode node, Graphics g, Rectangle bounds )
{
//display name
g.DrawString(node.Title, this.Font, SystemBrushes.ControlText, bounds.Left + Consts.Buffer, bounds.Top + Consts.Buffer);
//calculate the comment area
RectangleF boundsComment = new RectangleF(bounds.Left + Consts.Buffer,
bounds.Top + this.Font.Height + (Consts.Buffer * 3),
bounds.Width - (Consts.Buffer * 2), this.Font.Height * 2);
//comment
g.DrawString(node.Routine, this.Font, SystemBrushes.ControlText, boundsComment, m_format);
//separator
g.DrawLine(Pens.LightGray, bounds.Left, bounds.Bottom - 1, Bounds.Right, bounds.Bottom - 1);
}
// maintain an offscreen bitmap that is used to draw each
// item in the listbox, use the same bitmap if possible, only
// create a new one when the size changes
private void CreateOffscreenGraphics( int index )
{
if (index >= 0 && index < Items.Count)
{
// see if need to create the offscreen bitmap
Rectangle bounds = GetItemRectangle(index);
if (m_bmpItem == null || m_bmpItem.Width != bounds.Width || m_bmpItem.Height != bounds.Height)
{
// size changed, create the bitmap and graphics
if (m_bmpItem != null)
{
m_bmpItem.Dispose();
}
m_bmpItem = new Bitmap(bounds.Width, bounds.Height);
if (m_graphics != null)
{
m_graphics.Dispose();
}
m_graphics = Graphics.FromImage(m_bmpItem);
}
}
}
// paint the background part of the control that does not contain any items
protected override void WndProc( ref Message m )
{
const int WM_ERASEBKGND = 20;
// look for background erase message
if (m.Msg == WM_ERASEBKGND)
{
// let Windows handle the message if the list is empty
if (Items.Count > 0)
{
// calculate the area between the bottom of the last
// item and the bottom of the list box control
Rectangle rc = GetItemRectangle(Items.Count - 1);
// fill in the bottom area
if (rc.Bottom != 0 && rc.Bottom < DisplayRectangle.Bottom)
{
Graphics g = Graphics.FromHwnd(Handle);
g.FillRectangle(SystemBrushes.Window, DisplayRectangle.Left, rc.Bottom, DisplayRectangle.Width, DisplayRectangle.Bottom - rc.Bottom);
g.Dispose();
}
}
}
else
{
base.WndProc(ref m);
}
}
private void PopulateDummyData()
{
StreamReader reader = new StreamReader(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Courses.csv"));
string lastOfferedCourseTitle = string.Empty;
CourseNode courseNode = null;
int lastCourseID = 0;
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
if (null != line)
{
string[] tokens = line.Split(';');
// Check if from this row a new offered course begins
int offeredCourseID = int.Parse(tokens[0]);
string offeredCourseTitle = tokens[2];
// if this is a new course, add a new course node
if (lastCourseID != offeredCourseID)
{
courseNode = new CourseNode(offeredCourseTitle);
base.Items.Add(courseNode);
lastCourseID = offeredCourseID;
}
// Add the current section node under the course node
SectionNode sectionNode =
new SectionNode
(
offeredCourseID, offeredCourseTitle,
tokens[3], int.Parse(tokens[4]), int.Parse(tokens[5]),
true,
"Sat 8:00AM-9:00AM CL5 Sun 9:00AM-10:00AM CL4 Thu 10:00AM-12:00AM 411"
);
base.Items.Add(sectionNode);
}
}
}
class CourseNode
{
public string Title;
public CourseNode( string title )
{
this.Title = title;
}
}
class SectionNode
{
public string Title;
public int Count;
public int Status;
public bool IsAllowed;
public int OfferedCourseID;
public string CourseTitle;
public string Routine;
public SectionNode(
int courseID,
string courseTitle,
string sectionTitle,
int count,
int status,
bool isAllowed,
string routine)
{
this.OfferedCourseID = courseID;
this.CourseTitle = courseTitle;
this.Title = sectionTitle;
this.Count = count;
this.Status = status;
this.IsAllowed = isAllowed;
this.Routine = routine;
}
}
private StringBuilder _KeyStack = new StringBuilder(10);
private DateTime lastKeyStroke = DateTime.Now;
private void OfferedClassListBox_KeyPress(object sender, KeyPressEventArgs e)
{
// If this key storke is after a while, clear the key buffer and start from
// scratch
if ((DateTime.Now - lastKeyStroke).TotalSeconds > 2)
_KeyStack.Length = 0;
lastKeyStroke = DateTime.Now;
if (e.KeyChar == '\b')
{
if (_KeyStack.Length > 0)
_KeyStack.Length--;
}
else
{
if (char.IsLetterOrDigit(e.KeyChar))
{
_KeyStack.Append(e.KeyChar);
}
else
{
_KeyStack.Length = 0;
return;
}
}
string match = _KeyStack.ToString();
for( int i = 0; i < base.Items.Count; i ++ )
{
object item = base.Items[i];
if (item is CourseNode)
{
CourseNode course = item as CourseNode;
if (course.Title.Length > match.Length)
{
if (course.Title.Substring(0, match.Length).ToLower() == match)
{
base.SelectedIndex = i;
break;
}
}
}
else
{
SectionNode section = item as SectionNode;
if (section.Title.Length > match.Length)
{
if (section.Title.Substring(0, match.Length).ToLower() == match)
{
base.SelectedIndex = i;
break;
}
}
}
}
//this.Invalidate();
}
protected override void OnPaint( PaintEventArgs e )
{
base.OnPaint(e);
if (0 == _KeyStack.Length)
return;
string match = _KeyStack.ToString();
SizeF size = e.Graphics.MeasureString(match, base.Font);
ControlPaint.DrawButton(e.Graphics, 0, 30, (int)size.Width, (int)size.Height, ButtonState.Flat);
e.Graphics.DrawString(match, base.Font, Brushes.Black, 0, 30);
}
}
}