// Written by Christopher E. Miller
// See the included license.txt for copyright and license details.


///
module dfl.listbox;

private import dfl.internal.dlib;

private import dfl.internal.winapi, dfl.control, dfl.base, dfl.application;
private import dfl.drawing, dfl.event, dfl.collections;

static import dfl.internal.utf;

private import std.algorithm;

private extern(C) void* memmove(void*, void*, size_t len);

private extern(Windows) void _initListbox();


alias StringObject ListString;


///
abstract class ListControl: ControlSuperClass // docmain
{
	///
	final Dstring getItemText(Object item)
	{
		return getObjectString(item);
	}
	
	
	//EventHandler selectedValueChanged;
	Event!(ListControl, EventArgs) selectedValueChanged; ///
	
	
	///
	abstract @property void selectedIndex(int idx); // setter
	/// ditto
	abstract @property int selectedIndex(); // getter
	
	///
	abstract @property void selectedValue(Object val); // setter
	/// ditto
	
	///
	abstract @property void selectedValue(Dstring str); // setter
	/// ditto
	abstract @property Object selectedValue(); // getter
	
	
	static @property Color defaultBackColor() // getter
	{
		return SystemColors.window;
	}
	
	
	override @property Color backColor() // getter
	{
		if(Color.empty == backc)
			return defaultBackColor;
		return backc;
	}
	
	alias Control.backColor backColor; // Overload.
	
	
	static @property Color defaultForeColor() //getter
	{
		return SystemColors.windowText;
	}
	
	
	override @property Color foreColor() // getter
	{
		if(Color.empty == forec)
			return defaultForeColor;
		return forec;
	}
	
	alias Control.foreColor foreColor; // Overload.
	
	
	this()
	{
	}
	
	
	protected:
	
	///
	void onSelectedValueChanged(EventArgs ea)
	{
		selectedValueChanged(this, ea);
	}
	
	
	///
	// Index change causes the value to be changed.
	void onSelectedIndexChanged(EventArgs ea)
	{
		onSelectedValueChanged(ea); // This appears to be correct.
	}
}


///
enum SelectionMode: ubyte
{
	ONE, ///
	NONE, /// ditto
	MULTI_SIMPLE, /// ditto
	MULTI_EXTENDED, /// ditto
}


///
class ListBox: ListControl // docmain
{
	///
	static class SelectedIndexCollection
	{
		deprecated alias length count;
		
		@property int length() // getter
		{
			if(!lbox.isHandleCreated)
				return 0;
			
			if(lbox.isMultSel())
			{
				return lbox.prevwproc(LB_GETSELCOUNT, 0, 0).toI32;
			}
			else
			{
				return (lbox.selectedIndex == -1) ? 0 : 1;
			}
		}
		
		
		int opIndex(int idx)
		{
			foreach(int onidx; this)
			{
				if(!idx)
					return onidx;
				idx--;
			}
			
			// If it's not found it's out of bounds and bad things happen.
			assert(0);
		}
		
		
		bool contains(int idx)
		{
			return indexOf(idx) != -1;
		}
		
		
		int indexOf(int idx)
		{
			int i = 0;
			foreach(int onidx; this)
			{
				if(onidx == idx)
					return i;
				i++;
			}
			return -1;
		}
		
		
		int opApply(int delegate(ref int) dg)
		{
			int result = 0;
			
			if(lbox.isMultSel())
			{
				int[] items;
				items = new int[length];
				if(items.length != lbox.prevwproc(LB_GETSELITEMS, items.length, cast(LPARAM)cast(int*)items))
					throw new DflException("Unable to enumerate selected list items");
				foreach(int _idx; items)
				{
					int idx = _idx; // Prevent ref.
					result = dg(idx);
					if(result)
						break;
				}
			}
			else
			{
				int idx;
				idx = lbox.selectedIndex;
				if(-1 != idx)
					result = dg(idx);
			}
			return result;
		}
		
		mixin OpApplyAddIndex!(opApply, int);
		
		
		protected this(ListBox lb)
		{
			lbox = lb;
		}
		
		
		package:
		ListBox lbox;
	}
	
	
	///
	static class SelectedObjectCollection
	{
		deprecated alias length count;
		
		@property int length() // getter
		{
			if(!lbox.isHandleCreated)
				return 0;
			
			if(lbox.isMultSel())
			{
				return lbox.prevwproc(LB_GETSELCOUNT, 0, 0).toI32;
			}
			else
			{
				return (lbox.selectedIndex == -1) ? 0 : 1;
			}
		}
		
		
		Object opIndex(int idx)
		{
			foreach(Object obj; this)
			{
				if(!idx)
					return obj;
				idx--;
			}
			
			// If it's not found it's out of bounds and bad things happen.
			assert(0);
		}
		
		
		bool contains(Object obj)
		{
			return indexOf(obj) != -1;
		}
		
		
		bool contains(Dstring str)
		{
			return indexOf(str) != -1;
		}
		
		
		int indexOf(Object obj)
		{
			int idx = 0;
			foreach(Object onobj; this)
			{
				if(onobj == obj) // Not using is.
					return idx;
				idx++;
			}
			return -1;
		}
		
		
		int indexOf(Dstring str)
		{
			int idx = 0;
			foreach(Object onobj; this)
			{
				//if(getObjectString(onobj) is str && getObjectString(onobj).length == str.length)
				if(getObjectString(onobj) == str)
					return idx;
				idx++;
			}
			return -1;
		}
		
		
		// Used internally.
		int _opApply(int delegate(ref Object) dg) // package
		{
			int result = 0;
			
			if(lbox.isMultSel())
			{
				int[] items;
				items = new int[length];
				if(items.length != lbox.prevwproc(LB_GETSELITEMS, items.length, cast(LPARAM)cast(int*)items))
					throw new DflException("Unable to enumerate selected list items");
				foreach(int idx; items)
				{
					Object obj;
					obj = lbox.items[idx];
					result = dg(obj);
					if(result)
						break;
				}
			}
			else
			{
				Object obj;
				obj = lbox.selectedItem;
				if(obj)
					result = dg(obj);
			}
			return result;
		}
		
		
		// Used internally.
		int _opApply(int delegate(ref Dstring) dg) // package
		{
			int result = 0;
			
			if(lbox.isMultSel())
			{
				int[] items;
				items = new int[length];
				if(items.length != lbox.prevwproc(LB_GETSELITEMS, items.length, cast(LPARAM)cast(int*)items))
					throw new DflException("Unable to enumerate selected list items");
				foreach(int idx; items)
				{
					Dstring str;
					str = getObjectString(lbox.items[idx]);
					result = dg(str);
					if(result)
						break;
				}
			}
			else
			{
				Object obj;
				Dstring str;
				obj = lbox.selectedItem;
				if(obj)
				{
					str = getObjectString(obj);
					result = dg(str);
				}
			}
			return result;
		}
		
		mixin OpApplyAddIndex!(_opApply, Dstring);
		
		mixin OpApplyAddIndex!(_opApply, Object);
		
		// Had to do it this way because: DMD 1.028: -H is broken for mixin identifiers
		// Note that this way probably prevents opApply from being overridden.
		alias _opApply opApply;
		
		
		protected this(ListBox lb)
		{
			lbox = lb;
		}
		
		
		package:
		ListBox lbox;
	}
	
	
	///
	enum int DEFAULT_ITEM_HEIGHT = 13;
	///
	enum int NO_MATCHES = LB_ERR;
	
	
	protected override @property Size defaultSize() // getter
	{
		return Size(120, 95);
	}
	
	
	///
	@property void borderStyle(BorderStyle bs) // setter
	{
		final switch(bs)
		{
			case BorderStyle.FIXED_3D:
				_style(_style() & ~WS_BORDER);
				_exStyle(_exStyle() | WS_EX_CLIENTEDGE);
				break;
				
			case BorderStyle.FIXED_SINGLE:
				_exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
				_style(_style() | WS_BORDER);
				break;
				
			case BorderStyle.NONE:
				_style(_style() & ~WS_BORDER);
				_exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
				break;
		}
		
		if(isHandleCreated)
		{
			redrawEntire();
		}
	}
	
	/// ditto
	@property BorderStyle borderStyle() // getter
	{
		if(_exStyle() & WS_EX_CLIENTEDGE)
			return BorderStyle.FIXED_3D;
		else if(_style() & WS_BORDER)
			return BorderStyle.FIXED_SINGLE;
		return BorderStyle.NONE;
	}
	
	
	///
	@property void drawMode(DrawMode dm) // setter
	{
		LONG wl = _style() & ~(LBS_OWNERDRAWVARIABLE | LBS_OWNERDRAWFIXED);
		
		final switch(dm)
		{
			case DrawMode.OWNER_DRAW_VARIABLE:
				wl |= LBS_OWNERDRAWVARIABLE;
				break;
			
			case DrawMode.OWNER_DRAW_FIXED:
				wl |= LBS_OWNERDRAWFIXED;
				break;
			
			case DrawMode.NORMAL:
				break;
		}
		
		_style(wl);
		
		_crecreate();
	}
	
	/// ditto
	@property DrawMode drawMode() // getter
	{
		LONG wl = _style();
		
		if(wl & LBS_OWNERDRAWVARIABLE)
			return DrawMode.OWNER_DRAW_VARIABLE;
		if(wl & LBS_OWNERDRAWFIXED)
			return DrawMode.OWNER_DRAW_FIXED;
		return DrawMode.NORMAL;
	}
	
	
	///
	final @property void horizontalExtent(int he) // setter
	{
		if(isHandleCreated)
			prevwproc(LB_SETHORIZONTALEXTENT, he, 0);
		
		hextent = he;
	}
	
	/// ditto
	final @property int horizontalExtent() // getter
	{
		if(isHandleCreated)
			hextent = cast(int)prevwproc(LB_GETHORIZONTALEXTENT, 0, 0);
		return hextent;
	}
	
	
	///
	final @property void horizontalScrollbar(bool byes) // setter
	{
		if(byes)
			_style(_style() | WS_HSCROLL);
		else
			_style(_style() & ~WS_HSCROLL);
		
		_crecreate();
	}
	
	/// ditto
	final @property bool horizontalScrollbar() // getter
	{
		return (_style() & WS_HSCROLL) != 0;
	}
	
	
	///
	final @property void integralHeight(bool byes) //setter
	{
		if(byes)
			_style(_style() & ~LBS_NOINTEGRALHEIGHT);
		else
			_style(_style() | LBS_NOINTEGRALHEIGHT);
		
		_crecreate();
	}
	
	/// ditto
	final @property bool integralHeight() // getter
	{
		return (_style() & LBS_NOINTEGRALHEIGHT) == 0;
	}
	
	
	///
	// This function has no effect if the drawMode is OWNER_DRAW_VARIABLE.
	final @property void itemHeight(int h) // setter
	{
		if(drawMode == DrawMode.OWNER_DRAW_VARIABLE)
			return;
		
		iheight = h;
		
		if(isHandleCreated)
			prevwproc(LB_SETITEMHEIGHT, 0, MAKELPARAM(h, 0));
	}
	
	/// ditto
	// Return value is meaningless when drawMode is OWNER_DRAW_VARIABLE.
	final @property int itemHeight() // getter
	{
		// Requesting it like this when owner draw variable doesn't work.
		/+
		if(!isHandleCreated)
			return iheight;
		
		int result = prevwproc(LB_GETITEMHEIGHT, 0, 0);
		if(result == LB_ERR)
			result = iheight; // ?
		else
			iheight = result;
		
		return result;
		+/
		
		return iheight;
	}
	
	
	///
	final @property ObjectCollection items() // getter
	{
		return icollection;
	}
	
	
	///
	final @property void multiColumn(bool byes) // setter
	{
		// TODO: is this the correct implementation?
		
		if(byes)
			_style(_style() | LBS_MULTICOLUMN | WS_HSCROLL);
		else
			_style(_style() & ~(LBS_MULTICOLUMN | WS_HSCROLL));
		
		_crecreate();
	}
	
	/// ditto
	final @property bool multiColumn() // getter
	{
		return (_style() & LBS_MULTICOLUMN) != 0;
	}
	
	
	///
	final @property void scrollAlwaysVisible(bool byes) // setter
	{
		if(byes)
			_style(_style() | LBS_DISABLENOSCROLL);
		else
			_style(_style() & ~LBS_DISABLENOSCROLL);
		
		_crecreate();
	}
	
	/// ditto
	final @property bool scrollAlwaysVisible() // getter
	{
		return (_style() & LBS_DISABLENOSCROLL) != 0;
	}
	
	
	override @property void selectedIndex(int idx) // setter
	{
		if(isHandleCreated)
		{
			if(isMultSel())
			{
				if(idx == -1)
				{
					// Remove all selection.
					
					// Not working right.
					//prevwproc(LB_SELITEMRANGE, false, MAKELPARAM(0, ushort.max));
					
					// Get the indices directly because deselecting them during
					// selidxcollection.foreach could screw it up.
					
					int[] items;
					
					items = new int[selidxcollection.length];
					if(items.length != prevwproc(LB_GETSELITEMS, items.length, cast(LPARAM)cast(int*)items))
						throw new DflException("Unable to clear selected list items");
					
					foreach(int _idx; items)
					{
						prevwproc(LB_SETSEL, false, _idx);
					}
				}
				else
				{
					// ?
					prevwproc(LB_SETSEL, true, idx);
				}
			}
			else
			{
				prevwproc(LB_SETCURSEL, idx, 0);
			}
		}
	}
	
	override @property int selectedIndex() // getter
	{
		if(isHandleCreated)
		{
			if(isMultSel())
			{
				if(selidxcollection.length)
					return selidxcollection[0];
			}
			else
			{
				LRESULT result;
				result = prevwproc(LB_GETCURSEL, 0, 0);
				if(LB_ERR != result) // Redundant.
					return cast(int)result;
			}
		}
		return -1;
	}
	
	
	///
	final @property void selectedItem(Object o) // setter
	{
		int i;
		i = items.indexOf(o);
		if(i != -1)
			selectedIndex = i;
	}
	
	/// ditto
	final @property void selectedItem(Dstring str) // setter
	{
		int i;
		i = items.indexOf(str);
		if(i != -1)
			selectedIndex = i;
	}
	
	
	final @property Object selectedItem() // getter
	{
		int idx;
		idx = selectedIndex;
		if(idx == -1)
			return null;
		return items[idx];
	}
	
	
	override @property void selectedValue(Object val) // setter
	{
		selectedItem = val;
	}
	
	override @property void selectedValue(Dstring str) // setter
	{
		selectedItem = str;
	}
	
	override @property Object selectedValue() // getter
	{
		return selectedItem;
	}
	
	
	///
	final @property SelectedIndexCollection selectedIndices() // getter
	{
		return selidxcollection;
	}
	
	
	///
	final @property SelectedObjectCollection selectedItems() // getter
	{
		return selobjcollection;
	}
	
	
	///
	@property void selectionMode(SelectionMode selmode) // setter
	{
		LONG wl = _style() & ~(LBS_NOSEL | LBS_EXTENDEDSEL | LBS_MULTIPLESEL);
		
		final switch(selmode)
		{
			case SelectionMode.ONE:
				break;
			
			case SelectionMode.MULTI_SIMPLE:
				wl |= LBS_MULTIPLESEL;
				break;
			
			case SelectionMode.MULTI_EXTENDED:
				wl |= LBS_EXTENDEDSEL;
				break;
			
			case SelectionMode.NONE:
				wl |= LBS_NOSEL;
				break;
		}
		
		_style(wl);
		
		_crecreate();
	}
	
	/// ditto
	@property SelectionMode selectionMode() // getter
	{
		LONG wl = _style();
		
		if(wl & LBS_NOSEL)
			return SelectionMode.NONE;
		if(wl & LBS_EXTENDEDSEL)
			return SelectionMode.MULTI_EXTENDED;
		if(wl & LBS_MULTIPLESEL)
			return SelectionMode.MULTI_SIMPLE;
		return SelectionMode.ONE;
	}
	
	
	///
	final @property void sorted(bool byes) // setter
	{
		/+
		if(byes)
			_style(_style() | LBS_SORT);
		else
			_style(_style() & ~LBS_SORT);
		+/
		_sorting = byes;
	}
	
	/// ditto
	final @property bool sorted() // getter
	{
		//return (_style() & LBS_SORT) != 0;
		return _sorting;
	}
	
	
	///
	final @property void topIndex(int idx) // setter
	{
		if(isHandleCreated)
			prevwproc(LB_SETTOPINDEX, idx, 0);
	}
	
	/// ditto
	final @property int topIndex() // getter
	{
		if(isHandleCreated)
			return prevwproc(LB_GETTOPINDEX, 0, 0).toI32;
		return 0;
	}
	
	
	///
	final @property void useTabStops(bool byes) // setter
	{
		if(byes)
			_style(_style() | LBS_USETABSTOPS);
		else
			_style(_style() & ~LBS_USETABSTOPS);
		
		_crecreate();
	}
	
	/// ditto
	final @property bool useTabStops() // getter
	{
		return (_style() & LBS_USETABSTOPS) != 0;
	}
	
	
	///
	final void beginUpdate()
	{
		prevwproc(WM_SETREDRAW, false, 0);
	}
	
	/// ditto
	final void endUpdate()
	{
		prevwproc(WM_SETREDRAW, true, 0);
		invalidate(true); // Show updates.
	}
	
	
	package final bool isMultSel()
	{
		return (_style() & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL)) != 0;
	}
	
	
	///
	final void clearSelected()
	{
		if(created)
			selectedIndex = -1;
	}
	
	
	///
	final int findString(Dstring str, int startIndex)
	{
		// TODO: find string if control not created ?
		
		int result = NO_MATCHES;
		
		if(created)
		{
			if(dfl.internal.utf.useUnicode)
				result = prevwproc(LB_FINDSTRING, startIndex, cast(LPARAM)dfl.internal.utf.toUnicodez(str)).toI32;
			else
				result = prevwproc(LB_FINDSTRING, startIndex, cast(LPARAM)dfl.internal.utf.unsafeAnsiz(str)).toI32;
			if(result == LB_ERR) // Redundant.
				result = NO_MATCHES;
		}
		
		return result;
	}
	
	/// ditto
	final int findString(Dstring str)
	{
		return findString(str, -1); // Start at beginning.
	}
	
	
	///
	final int findStringExact(Dstring str, int startIndex)
	{
		// TODO: find string if control not created ?
		
		int result = NO_MATCHES;
		
		if(created)
		{
			if(dfl.internal.utf.useUnicode)
				result = prevwproc(LB_FINDSTRINGEXACT, startIndex, cast(LPARAM)dfl.internal.utf.toUnicodez(str)).toI32;
			else
				result = prevwproc(LB_FINDSTRINGEXACT, startIndex, cast(LPARAM)dfl.internal.utf.unsafeAnsiz(str)).toI32;
			if(result == LB_ERR) // Redundant.
				result = NO_MATCHES;
		}
		
		return result;
	}
	
	/// ditto
	final int findStringExact(Dstring str)
	{
		return findStringExact(str, -1); // Start at beginning.
	}
	
	
	///
	final int getItemHeight(int idx)
	{
		int result = prevwproc(LB_GETITEMHEIGHT, idx, 0).toI32;
		if(LB_ERR == result)
			throw new DflException("Unable to obtain item height");
		return result;
	}
	
	
	///
	final Rect getItemRectangle(int idx)
	{
		RECT rect;
		if(LB_ERR == prevwproc(LB_GETITEMRECT, idx, cast(LPARAM)&rect))
		{
			//if(idx >= 0 && idx < items.length)
				return Rect(0, 0, 0, 0); // ?
			//throw new DflException("Unable to obtain item rectangle");
		}
		return Rect(&rect);
	}
	
	
	///
	final bool getSelected(int idx)
	{
		return prevwproc(LB_GETSEL, idx, 0) > 0;
	}
	
	
	///
	final int indexFromPoint(int x, int y)
	{
		// LB_ITEMFROMPOINT is "nearest", so also check with the item rectangle to
		// see if the point is directly in the item.
		
		// Maybe use LBItemFromPt() from common controls.
		
		int result = NO_MATCHES;
		
		if(created)
		{
			result = prevwproc(LB_ITEMFROMPOINT, 0, MAKELPARAM(x, y)).toI32;
			if(!HIWORD(result)) // In client area
			{
				//result = LOWORD(result); // High word already 0.
				if(result < 0 || !getItemRectangle(result).contains(x, y))
					result = NO_MATCHES;
			}
			else // Outside client area.
			{
				result = NO_MATCHES;
			}
		}
		
		return result;
	}
	
	/// ditto
	final int indexFromPoint(Point pt)
	{
		return indexFromPoint(pt.x, pt.y);
	}
	
	
	///
	final void setSelected(int idx, bool byes)
	{
		if(created)
			prevwproc(LB_SETSEL, byes, idx);
	}
	
	
	///
	protected ObjectCollection createItemCollection()
	{
		return new ObjectCollection(this);
	}
	
	
	///
	void sort()
	{
		if(icollection._items.length)
		{
			Object[] itemscopy;
			itemscopy = icollection._items.dup;
			std.algorithm.sort(itemscopy);
			
			items.clear();
			
			beginUpdate();
			scope(exit)
				endUpdate();
			
			foreach(i, Object o; itemscopy)
			{
				if (i > int.max)
					throw new DflException("sort() failure");
				items.insert(cast(int)i, o);
			}
		}
	}
	
	
	///
	static class ObjectCollection
	{
		protected this(ListBox lbox)
		{
			this.lbox = lbox;
		}
		
		
		protected this(ListBox lbox, Object[] range)
		{
			this.lbox = lbox;
			addRange(range);
		}
		
		
		protected this(ListBox lbox, Dstring[] range)
		{
			this.lbox = lbox;
			addRange(range);
		}
		
		
		/+
		protected this(ListBox lbox, ObjectCollection range)
		{
			this.lbox = lbox;
			addRange(range);
		}
		+/
		
		
		void add(Object value)
		{
			add2(value);
		}
		
		
		void add(Dstring value)
		{
			add(new ListString(value));
		}
		
		
		void addRange(Object[] range)
		{
			if(lbox.sorted)
			{
				foreach(Object value; range)
				{
					add(value);
				}
			}
			else
			{
				_wraparray.addRange(range);
			}
		}
		
		
		void addRange(Dstring[] range)
		{
			foreach(Dstring value; range)
			{
				add(value);
			}
		}
		
		
		private:
		
		ListBox lbox;
		Object[] _items;
		
		
		LRESULT insert2(WPARAM idx, Dstring val)
		{
			insert(idx.toI32, val);
			return idx;
		}
		
		
		LRESULT add2(Object val)
		{
			int i;
			if(lbox.sorted)
			{
				for(i = 0; i != _items.length.toI32; i++)
				{
					if(val < _items[i])
						break;
				}
			}
			else
			{
				i = _items.length.toI32;
			}
			
			insert(i, val);
			
			return i;
		}
		
		
		LRESULT add2(Dstring val)
		{
			return add2(new ListString(val));
		}
		
		
		void _added(size_t idx, Object val)
		{
			if(lbox.created)
			{
				if(dfl.internal.utf.useUnicode)
					lbox.prevwproc(LB_INSERTSTRING, idx, cast(LPARAM)dfl.internal.utf.toUnicodez(getObjectString(val)));
				else
					lbox.prevwproc(LB_INSERTSTRING, idx, cast(LPARAM)dfl.internal.utf.toAnsiz(getObjectString(val))); // Can this be unsafeAnsiz()?
			}
		}
		
		
		void _removed(size_t idx, Object val)
		{
			if(size_t.max == idx) // Clear all.
			{
				if(lbox.created)
				{
					lbox.prevwproc(LB_RESETCONTENT, 0, 0);
				}
			}
			else
			{
				if(lbox.created)
				{
					lbox.prevwproc(LB_DELETESTRING, cast(WPARAM)idx, 0);
				}
			}
		}
		
		
		public:
		
		mixin ListWrapArray!(Object, _items,
			_blankListCallback!(Object), _added,
			_blankListCallback!(Object), _removed,
			true, false, false) _wraparray;
	}
	
	
	this()
	{
		_initListbox();
		
		// Default useTabStops and vertical scrolling.
		wstyle |= WS_TABSTOP | LBS_USETABSTOPS | LBS_HASSTRINGS | WS_VSCROLL | LBS_NOTIFY;
		wexstyle |= WS_EX_CLIENTEDGE;
		ctrlStyle |= ControlStyles.SELECTABLE;
		wclassStyle = listboxClassStyle;
		
		icollection = createItemCollection();
		selidxcollection = new SelectedIndexCollection(this);
		selobjcollection = new SelectedObjectCollection(this);
	}
	
	
	protected override void onHandleCreated(EventArgs ea)
	{
		super.onHandleCreated(ea);
		
		// Set the Ctrl ID to the HWND so that it is unique
		// and WM_MEASUREITEM will work properly.
		SetWindowLongPtrA(hwnd, GWL_ID, cast(LONG_PTR)hwnd);
		
		if(hextent != 0)
			prevwproc(LB_SETHORIZONTALEXTENT, hextent, 0);
		
		if(iheight != DEFAULT_ITEM_HEIGHT)
			prevwproc(LB_SETITEMHEIGHT, 0, MAKELPARAM(iheight, 0));
		
		Message m;
		m.hWnd = handle;
		m.msg = LB_INSERTSTRING;
		// NOTE: duplicate code.
		if(dfl.internal.utf.useUnicode)
		{
			foreach(i, Object obj; icollection._items)
			{
				m.wParam = i;
				m.lParam = cast(LPARAM)dfl.internal.utf.toUnicodez(getObjectString(obj)); // <--
				
				prevWndProc(m);
				//if(LB_ERR == m.result || LB_ERRSPACE == m.result)
				if(m.result < 0)
					throw new DflException("Unable to add list item");
				
				//prevwproc(LB_SETITEMDATA, m.result, cast(LPARAM)cast(void*)obj);
			}
		}
		else
		{
			foreach(i, Object obj; icollection._items)
			{
				m.wParam = i;
				m.lParam = cast(LPARAM)dfl.internal.utf.toAnsiz(getObjectString(obj)); // Can this be unsafeAnsiz? // <--
				
				prevWndProc(m);
				//if(LB_ERR == m.result || LB_ERRSPACE == m.result)
				if(m.result < 0)
					throw new DflException("Unable to add list item");
				
				//prevwproc(LB_SETITEMDATA, m.result, cast(LPARAM)cast(void*)obj);
			}
		}
		
		//redrawEntire();
	}
	
	
	/+
	override void createHandle()
	{
		if(isHandleCreated)
			return;
		
		createClassHandle(LISTBOX_CLASSNAME);
		
		onHandleCreated(EventArgs.empty);
	}
	+/
	
	
	protected override void createParams(ref CreateParams cp)
	{
		super.createParams(cp);
		
		cp.className = LISTBOX_CLASSNAME;
	}
	
	
	//DrawItemEventHandler drawItem;
	Event!(ListBox, DrawItemEventArgs) drawItem; ///
	//MeasureItemEventHandler measureItem;
	Event!(ListBox, MeasureItemEventArgs) measureItem; ///
	
	
	protected:
	
	///
	void onDrawItem(DrawItemEventArgs dieh)
	{
		drawItem(this, dieh);
	}
	
	
	///
	void onMeasureItem(MeasureItemEventArgs miea)
	{
		measureItem(this, miea);
	}
	
	
	package final void _WmDrawItem(DRAWITEMSTRUCT* dis)
	in
	{
		assert(dis.hwndItem == handle);
		assert(dis.CtlType == ODT_LISTBOX);
	}
	do
	{
		DrawItemState state;
		state = cast(DrawItemState)dis.itemState;
		
		if(dis.itemID == -1)
		{
			FillRect(dis.hDC, &dis.rcItem, hbrBg);
			if(state & DrawItemState.FOCUS)
				DrawFocusRect(dis.hDC, &dis.rcItem);
		}
		else
		{
			DrawItemEventArgs diea;
			Color bc, fc;
			
			if(state & DrawItemState.SELECTED)
			{
				bc = Color.systemColor(COLOR_HIGHLIGHT);
				fc = Color.systemColor(COLOR_HIGHLIGHTTEXT);
			}
			else
			{
				bc = backColor;
				fc = foreColor;
			}
			
			prepareDc(dis.hDC);
			diea = new DrawItemEventArgs(new Graphics(dis.hDC, false), wfont,
				Rect(&dis.rcItem), dis.itemID, state, fc, bc);
			
			onDrawItem(diea);
		}
	}
	
	
	package final void _WmMeasureItem(MEASUREITEMSTRUCT* mis)
	in
	{
		assert(mis.CtlType == ODT_LISTBOX);
	}
	do
	{
		MeasureItemEventArgs miea;
		scope Graphics gpx = new CommonGraphics(handle(), GetDC(handle));
		miea = new MeasureItemEventArgs(gpx, mis.itemID, /+ mis.itemHeight +/ iheight);
		miea.itemWidth = mis.itemWidth;
		
		onMeasureItem(miea);
		
		mis.itemHeight = miea.itemHeight;
		mis.itemWidth = miea.itemWidth;
	}
	
	
	override void prevWndProc(ref Message msg)
	{
		//msg.result = CallWindowProcA(listboxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
		msg.result = dfl.internal.utf.callWindowProc(listboxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
	}
	
	
	protected override void onReflectedMessage(ref Message m)
	{
		super.onReflectedMessage(m);
		
		switch(m.msg)
		{
			case WM_DRAWITEM:
				_WmDrawItem(cast(DRAWITEMSTRUCT*)m.lParam);
				m.result = 1;
				break;
			
			case WM_MEASUREITEM:
				_WmMeasureItem(cast(MEASUREITEMSTRUCT*)m.lParam);
				m.result = 1;
				break;
			
			case WM_COMMAND:
				assert(cast(HWND)m.lParam == handle);
				switch(HIWORD(m.wParam))
				{
					case LBN_SELCHANGE:
						onSelectedIndexChanged(EventArgs.empty);
						break;
					
					case LBN_SELCANCEL:
						onSelectedIndexChanged(EventArgs.empty);
						break;
					
					default:
				}
				break;
			
			default:
		}
	}
	
	
	override void wndProc(ref Message msg)
	{
		switch(msg.msg)
		{
			case LB_ADDSTRING:
				//msg.result = icollection.add2(stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix.
				//msg.result = icollection.add2(stringFromStringz(cast(char*)msg.lParam).idup); // TODO: fix. // Needed in D2. Doesn't work in D1.
				msg.result = icollection.add2(cast(Dstring)stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix. // Needed in D2.
				return;
			
			case LB_INSERTSTRING:
				//msg.result = icollection.insert2(msg.wParam, stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix.
				//msg.result = icollection.insert2(msg.wParam, stringFromStringz(cast(char*)msg.lParam).idup); // TODO: fix. // Needed in D2. Doesn't work in D1.
				msg.result = icollection.insert2(msg.wParam, cast(Dstring)stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix. // Needed in D2.
				return;
			
			case LB_DELETESTRING:
				icollection.removeAt(msg.wParam.toI32);
				msg.result = icollection.length;
				return;
			
			case LB_RESETCONTENT:
				icollection.clear();
				return;
			
			case LB_SETITEMDATA:
				// Cannot set item data from outside DFL.
				msg.result = LB_ERR;
				return;
			
			case LB_ADDFILE:
				msg.result = LB_ERR;
				return;
			
			case LB_DIR:
				msg.result = LB_ERR;
				return;
			
			default:
		}
		super.wndProc(msg);
	}
	
	
	private:
	int hextent = 0;
	int iheight = DEFAULT_ITEM_HEIGHT;
	ObjectCollection icollection;
	SelectedIndexCollection selidxcollection;
	SelectedObjectCollection selobjcollection;
	bool _sorting = false;
	
	
	package:
	final:
	LRESULT prevwproc(UINT msg, WPARAM wparam, LPARAM lparam)
	{
		//return CallWindowProcA(listviewPrevWndProc, hwnd, msg, wparam, lparam);
		return dfl.internal.utf.callWindowProc(listboxPrevWndProc, hwnd, msg, wparam, lparam);
	}
}

