1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 
5 ///
6 module dfl.listbox;
7 
8 static import std.algorithm;
9 
10 private import dfl.internal.dlib;
11 
12 private import dfl.internal.winapi, dfl.control, dfl.base, dfl.application;
13 private import dfl.drawing, dfl.event, dfl.collections;
14 
15 
16 private extern(C) void* memmove(void*, void*, size_t len);
17 
18 private extern(Windows) void _initListbox();
19 
20 
21 alias StringObject ListString;
22 
23 
24 ///
25 abstract class ListControl: ControlSuperClass // docmain
26 {
27 	///
28 	final Dstring getItemText(Object item)
29 	{
30 		return getObjectString(item);
31 	}
32 	
33 	
34 	//EventHandler selectedValueChanged;
35 	Event!(ListControl, EventArgs) selectedValueChanged; ///
36 	
37 	
38 	///
39 	abstract @property void selectedIndex(int idx); // setter
40 	/// ditto
41 	abstract @property int selectedIndex(); // getter
42 	
43 	///
44 	abstract @property void selectedValue(Object val); // setter
45 	/// ditto
46 	
47 	///
48 	abstract @property void selectedValue(Dstring str); // setter
49 	/// ditto
50 	abstract @property Object selectedValue(); // getter
51 	
52 	
53 	static @property Color defaultBackColor() // getter
54 	{
55 		return SystemColors.window;
56 	}
57 	
58 	
59 	override @property Color backColor() // getter
60 	{
61 		if(Color.empty == backc)
62 			return defaultBackColor;
63 		return backc;
64 	}
65 	
66 	alias Control.backColor backColor; // Overload.
67 	
68 	
69 	static @property Color defaultForeColor() //getter
70 	{
71 		return SystemColors.windowText;
72 	}
73 	
74 	
75 	override @property Color foreColor() // getter
76 	{
77 		if(Color.empty == forec)
78 			return defaultForeColor;
79 		return forec;
80 	}
81 	
82 	alias Control.foreColor foreColor; // Overload.
83 	
84 	
85 	this()
86 	{
87 	}
88 	
89 	
90 	protected:
91 	
92 	///
93 	void onSelectedValueChanged(EventArgs ea)
94 	{
95 		selectedValueChanged(this, ea);
96 	}
97 	
98 	
99 	///
100 	// Index change causes the value to be changed.
101 	void onSelectedIndexChanged(EventArgs ea)
102 	{
103 		onSelectedValueChanged(ea); // This appears to be correct.
104 	}
105 }
106 
107 
108 ///
109 enum SelectionMode: ubyte
110 {
111 	ONE, ///
112 	NONE, /// ditto
113 	MULTI_SIMPLE, /// ditto
114 	MULTI_EXTENDED, /// ditto
115 }
116 
117 
118 ///
119 class ListBox: ListControl // docmain
120 {
121 	///
122 	static class SelectedIndexCollection
123 	{
124 		deprecated alias length count;
125 		
126 		@property int length() // getter
127 		{
128 			if(!lbox.isHandleCreated)
129 				return 0;
130 			
131 			if(lbox.isMultSel())
132 			{
133 				return cast(int)lbox.prevwproc(LB_GETSELCOUNT, 0, 0);
134 			}
135 			else
136 			{
137 				return (lbox.selectedIndex == -1) ? 0 : 1;
138 			}
139 		}
140 		
141 		
142 		int opIndex(int idx)
143 		{
144 			foreach(int onidx; this)
145 			{
146 				if(!idx)
147 					return onidx;
148 				idx--;
149 			}
150 			
151 			// If it's not found it's out of bounds and bad things happen.
152 			assert(0);
153 		}
154 		
155 		
156 		bool contains(int idx)
157 		{
158 			return indexOf(idx) != -1;
159 		}
160 		
161 		
162 		int indexOf(int idx)
163 		{
164 			int i = 0;
165 			foreach(int onidx; this)
166 			{
167 				if(onidx == idx)
168 					return i;
169 				i++;
170 			}
171 			return -1;
172 		}
173 		
174 		
175 		int opApply(int delegate(ref int) dg)
176 		{
177 			int result = 0;
178 			
179 			if(lbox.isMultSel())
180 			{
181 				int[] items;
182 				items = new int[length];
183 				if(items.length != lbox.prevwproc(LB_GETSELITEMS, items.length, cast(LPARAM)cast(int*)items))
184 					throw new DflException("Unable to enumerate selected list items");
185 				foreach(int _idx; items)
186 				{
187 					int idx = _idx; // Prevent ref.
188 					result = dg(idx);
189 					if(result)
190 						break;
191 				}
192 			}
193 			else
194 			{
195 				int idx;
196 				idx = lbox.selectedIndex;
197 				if(-1 != idx)
198 					result = dg(idx);
199 			}
200 			return result;
201 		}
202 		
203 		mixin OpApplyAddIndex!(opApply, int);
204 		
205 		
206 		protected this(ListBox lb)
207 		{
208 			lbox = lb;
209 		}
210 		
211 		
212 		package:
213 		ListBox lbox;
214 	}
215 	
216 	
217 	///
218 	static class SelectedObjectCollection
219 	{
220 		deprecated alias length count;
221 		
222 		@property int length() // getter
223 		{
224 			if(!lbox.isHandleCreated)
225 				return 0;
226 			
227 			if(lbox.isMultSel())
228 			{
229 				return cast(int)lbox.prevwproc(LB_GETSELCOUNT, 0, 0);
230 			}
231 			else
232 			{
233 				return (lbox.selectedIndex == -1) ? 0 : 1;
234 			}
235 		}
236 		
237 		
238 		Object opIndex(int idx)
239 		{
240 			foreach(Object obj; this)
241 			{
242 				if(!idx)
243 					return obj;
244 				idx--;
245 			}
246 			
247 			// If it's not found it's out of bounds and bad things happen.
248 			assert(0);
249 		}
250 		
251 		
252 		bool contains(Object obj)
253 		{
254 			return indexOf(obj) != -1;
255 		}
256 		
257 		
258 		bool contains(Dstring str)
259 		{
260 			return indexOf(str) != -1;
261 		}
262 		
263 		
264 		int indexOf(Object obj)
265 		{
266 			int idx = 0;
267 			foreach(Object onobj; this)
268 			{
269 				if(onobj == obj) // Not using is.
270 					return idx;
271 				idx++;
272 			}
273 			return -1;
274 		}
275 		
276 		
277 		int indexOf(Dstring str)
278 		{
279 			int idx = 0;
280 			foreach(Object onobj; this)
281 			{
282 				//if(getObjectString(onobj) is str && getObjectString(onobj).length == str.length)
283 				if(getObjectString(onobj) == str)
284 					return idx;
285 				idx++;
286 			}
287 			return -1;
288 		}
289 		
290 		
291 		// Used internally.
292 		int _opApply(int delegate(ref Object) dg) // package
293 		{
294 			int result = 0;
295 			
296 			if(lbox.isMultSel())
297 			{
298 				int[] items;
299 				items = new int[length];
300 				if(items.length != lbox.prevwproc(LB_GETSELITEMS, items.length, cast(LPARAM)cast(int*)items))
301 					throw new DflException("Unable to enumerate selected list items");
302 				foreach(int idx; items)
303 				{
304 					Object obj;
305 					obj = lbox.items[idx];
306 					result = dg(obj);
307 					if(result)
308 						break;
309 				}
310 			}
311 			else
312 			{
313 				Object obj;
314 				obj = lbox.selectedItem;
315 				if(obj)
316 					result = dg(obj);
317 			}
318 			return result;
319 		}
320 		
321 		
322 		// Used internally.
323 		int _opApply(int delegate(ref Dstring) dg) // package
324 		{
325 			int result = 0;
326 			
327 			if(lbox.isMultSel())
328 			{
329 				int[] items;
330 				items = new int[length];
331 				if(items.length != lbox.prevwproc(LB_GETSELITEMS, items.length, cast(LPARAM)cast(int*)items))
332 					throw new DflException("Unable to enumerate selected list items");
333 				foreach(int idx; items)
334 				{
335 					Dstring str;
336 					str = getObjectString(lbox.items[idx]);
337 					result = dg(str);
338 					if(result)
339 						break;
340 				}
341 			}
342 			else
343 			{
344 				Object obj;
345 				Dstring str;
346 				obj = lbox.selectedItem;
347 				if(obj)
348 				{
349 					str = getObjectString(obj);
350 					result = dg(str);
351 				}
352 			}
353 			return result;
354 		}
355 		
356 		mixin OpApplyAddIndex!(_opApply, Dstring);
357 		
358 		mixin OpApplyAddIndex!(_opApply, Object);
359 		
360 		// Had to do it this way because: DMD 1.028: -H is broken for mixin identifiers
361 		// Note that this way probably prevents opApply from being overridden.
362 		alias _opApply opApply;
363 		
364 		
365 		protected this(ListBox lb)
366 		{
367 			lbox = lb;
368 		}
369 		
370 		
371 		package:
372 		ListBox lbox;
373 	}
374 	
375 	
376 	///
377 	enum int DEFAULT_ITEM_HEIGHT = 13;
378 	///
379 	enum int NO_MATCHES = LB_ERR;
380 	
381 	
382 	protected override @property Size defaultSize() // getter
383 	{
384 		return Size(120, 95);
385 	}
386 	
387 	
388 	///
389 	@property void borderStyle(BorderStyle bs) // setter
390 	{
391 		final switch(bs)
392 		{
393 			case BorderStyle.FIXED_3D:
394 				_style(_style() & ~WS_BORDER);
395 				_exStyle(_exStyle() | WS_EX_CLIENTEDGE);
396 				break;
397 				
398 			case BorderStyle.FIXED_SINGLE:
399 				_exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
400 				_style(_style() | WS_BORDER);
401 				break;
402 				
403 			case BorderStyle.NONE:
404 				_style(_style() & ~WS_BORDER);
405 				_exStyle(_exStyle() & ~WS_EX_CLIENTEDGE);
406 				break;
407 		}
408 		
409 		if(isHandleCreated)
410 		{
411 			redrawEntire();
412 		}
413 	}
414 	
415 	/// ditto
416 	@property BorderStyle borderStyle() // getter
417 	{
418 		if(_exStyle() & WS_EX_CLIENTEDGE)
419 			return BorderStyle.FIXED_3D;
420 		else if(_style() & WS_BORDER)
421 			return BorderStyle.FIXED_SINGLE;
422 		return BorderStyle.NONE;
423 	}
424 	
425 	
426 	///
427 	@property void drawMode(DrawMode dm) // setter
428 	{
429 		LONG wl = _style() & ~(LBS_OWNERDRAWVARIABLE | LBS_OWNERDRAWFIXED);
430 		
431 		final switch(dm)
432 		{
433 			case DrawMode.OWNER_DRAW_VARIABLE:
434 				wl |= LBS_OWNERDRAWVARIABLE;
435 				break;
436 			
437 			case DrawMode.OWNER_DRAW_FIXED:
438 				wl |= LBS_OWNERDRAWFIXED;
439 				break;
440 			
441 			case DrawMode.NORMAL:
442 				break;
443 		}
444 		
445 		_style(wl);
446 		
447 		_crecreate();
448 	}
449 	
450 	/// ditto
451 	@property DrawMode drawMode() // getter
452 	{
453 		LONG wl = _style();
454 		
455 		if(wl & LBS_OWNERDRAWVARIABLE)
456 			return DrawMode.OWNER_DRAW_VARIABLE;
457 		if(wl & LBS_OWNERDRAWFIXED)
458 			return DrawMode.OWNER_DRAW_FIXED;
459 		return DrawMode.NORMAL;
460 	}
461 	
462 	
463 	///
464 	final @property void horizontalExtent(int he) // setter
465 	{
466 		if(isHandleCreated)
467 			prevwproc(LB_SETHORIZONTALEXTENT, he, 0);
468 		
469 		hextent = he;
470 	}
471 	
472 	/// ditto
473 	final @property int horizontalExtent() // getter
474 	{
475 		if(isHandleCreated)
476 			hextent = cast(int)prevwproc(LB_GETHORIZONTALEXTENT, 0, 0);
477 		return hextent;
478 	}
479 	
480 	
481 	///
482 	final @property void horizontalScrollbar(bool byes) // setter
483 	{
484 		if(byes)
485 			_style(_style() | WS_HSCROLL);
486 		else
487 			_style(_style() & ~WS_HSCROLL);
488 		
489 		_crecreate();
490 	}
491 	
492 	/// ditto
493 	final @property bool horizontalScrollbar() // getter
494 	{
495 		return (_style() & WS_HSCROLL) != 0;
496 	}
497 	
498 	
499 	///
500 	final @property void integralHeight(bool byes) //setter
501 	{
502 		if(byes)
503 			_style(_style() & ~LBS_NOINTEGRALHEIGHT);
504 		else
505 			_style(_style() | LBS_NOINTEGRALHEIGHT);
506 		
507 		_crecreate();
508 	}
509 	
510 	/// ditto
511 	final @property bool integralHeight() // getter
512 	{
513 		return (_style() & LBS_NOINTEGRALHEIGHT) == 0;
514 	}
515 	
516 	
517 	///
518 	// This function has no effect if the drawMode is OWNER_DRAW_VARIABLE.
519 	final @property void itemHeight(int h) // setter
520 	{
521 		if(drawMode == DrawMode.OWNER_DRAW_VARIABLE)
522 			return;
523 		
524 		iheight = h;
525 		
526 		if(isHandleCreated)
527 			prevwproc(LB_SETITEMHEIGHT, 0, MAKELPARAM(h, 0));
528 	}
529 	
530 	/// ditto
531 	// Return value is meaningless when drawMode is OWNER_DRAW_VARIABLE.
532 	final @property int itemHeight() // getter
533 	{
534 		// Requesting it like this when owner draw variable doesn't work.
535 		/+
536 		if(!isHandleCreated)
537 			return iheight;
538 		
539 		int result = prevwproc(LB_GETITEMHEIGHT, 0, 0);
540 		if(result == LB_ERR)
541 			result = iheight; // ?
542 		else
543 			iheight = result;
544 		
545 		return result;
546 		+/
547 		
548 		return iheight;
549 	}
550 	
551 	
552 	///
553 	final @property ObjectCollection items() // getter
554 	{
555 		return icollection;
556 	}
557 	
558 	
559 	///
560 	final @property void multiColumn(bool byes) // setter
561 	{
562 		// TODO: is this the correct implementation?
563 		
564 		if(byes)
565 			_style(_style() | LBS_MULTICOLUMN | WS_HSCROLL);
566 		else
567 			_style(_style() & ~(LBS_MULTICOLUMN | WS_HSCROLL));
568 		
569 		_crecreate();
570 	}
571 	
572 	/// ditto
573 	final @property bool multiColumn() // getter
574 	{
575 		return (_style() & LBS_MULTICOLUMN) != 0;
576 	}
577 	
578 	
579 	///
580 	final @property void scrollAlwaysVisible(bool byes) // setter
581 	{
582 		if(byes)
583 			_style(_style() | LBS_DISABLENOSCROLL);
584 		else
585 			_style(_style() & ~LBS_DISABLENOSCROLL);
586 		
587 		_crecreate();
588 	}
589 	
590 	/// ditto
591 	final @property bool scrollAlwaysVisible() // getter
592 	{
593 		return (_style() & LBS_DISABLENOSCROLL) != 0;
594 	}
595 	
596 	
597 	override @property void selectedIndex(int idx) // setter
598 	{
599 		if(isHandleCreated)
600 		{
601 			if(isMultSel())
602 			{
603 				if(idx == -1)
604 				{
605 					// Remove all selection.
606 					
607 					// Not working right.
608 					//prevwproc(LB_SELITEMRANGE, false, MAKELPARAM(0, ushort.max));
609 					
610 					// Get the indices directly because deselecting them during
611 					// selidxcollection.foreach could screw it up.
612 					
613 					int[] items;
614 					
615 					items = new int[selidxcollection.length];
616 					if(items.length != prevwproc(LB_GETSELITEMS, items.length, cast(LPARAM)cast(int*)items))
617 						throw new DflException("Unable to clear selected list items");
618 					
619 					foreach(int _idx; items)
620 					{
621 						prevwproc(LB_SETSEL, false, _idx);
622 					}
623 				}
624 				else
625 				{
626 					// ?
627 					prevwproc(LB_SETSEL, true, idx);
628 				}
629 			}
630 			else
631 			{
632 				prevwproc(LB_SETCURSEL, idx, 0);
633 			}
634 		}
635 	}
636 	
637 	override @property int selectedIndex() // getter
638 	{
639 		if(isHandleCreated)
640 		{
641 			if(isMultSel())
642 			{
643 				if(selidxcollection.length)
644 					return selidxcollection[0];
645 			}
646 			else
647 			{
648 				LRESULT result;
649 				result = prevwproc(LB_GETCURSEL, 0, 0);
650 				if(LB_ERR != result) // Redundant.
651 					return cast(int)result;
652 			}
653 		}
654 		return -1;
655 	}
656 	
657 	
658 	///
659 	final @property void selectedItem(Object o) // setter
660 	{
661 		int i;
662 		i = items.indexOf(o);
663 		if(i != -1)
664 			selectedIndex = i;
665 	}
666 	
667 	/// ditto
668 	final @property void selectedItem(Dstring str) // setter
669 	{
670 		int i;
671 		i = items.indexOf(str);
672 		if(i != -1)
673 			selectedIndex = i;
674 	}
675 	
676 	
677 	final @property Object selectedItem() // getter
678 	{
679 		int idx;
680 		idx = selectedIndex;
681 		if(idx == -1)
682 			return null;
683 		return items[idx];
684 	}
685 	
686 	
687 	override @property void selectedValue(Object val) // setter
688 	{
689 		selectedItem = val;
690 	}
691 	
692 	override @property void selectedValue(Dstring str) // setter
693 	{
694 		selectedItem = str;
695 	}
696 	
697 	override @property Object selectedValue() // getter
698 	{
699 		return selectedItem;
700 	}
701 	
702 	
703 	///
704 	final @property SelectedIndexCollection selectedIndices() // getter
705 	{
706 		return selidxcollection;
707 	}
708 	
709 	
710 	///
711 	final @property SelectedObjectCollection selectedItems() // getter
712 	{
713 		return selobjcollection;
714 	}
715 	
716 	
717 	///
718 	@property void selectionMode(SelectionMode selmode) // setter
719 	{
720 		LONG wl = _style() & ~(LBS_NOSEL | LBS_EXTENDEDSEL | LBS_MULTIPLESEL);
721 		
722 		final switch(selmode)
723 		{
724 			case SelectionMode.ONE:
725 				break;
726 			
727 			case SelectionMode.MULTI_SIMPLE:
728 				wl |= LBS_MULTIPLESEL;
729 				break;
730 			
731 			case SelectionMode.MULTI_EXTENDED:
732 				wl |= LBS_EXTENDEDSEL;
733 				break;
734 			
735 			case SelectionMode.NONE:
736 				wl |= LBS_NOSEL;
737 				break;
738 		}
739 		
740 		_style(wl);
741 		
742 		_crecreate();
743 	}
744 	
745 	/// ditto
746 	@property SelectionMode selectionMode() // getter
747 	{
748 		LONG wl = _style();
749 		
750 		if(wl & LBS_NOSEL)
751 			return SelectionMode.NONE;
752 		if(wl & LBS_EXTENDEDSEL)
753 			return SelectionMode.MULTI_EXTENDED;
754 		if(wl & LBS_MULTIPLESEL)
755 			return SelectionMode.MULTI_SIMPLE;
756 		return SelectionMode.ONE;
757 	}
758 	
759 	
760 	///
761 	final @property void sorted(bool byes) // setter
762 	{
763 		/+
764 		if(byes)
765 			_style(_style() | LBS_SORT);
766 		else
767 			_style(_style() & ~LBS_SORT);
768 		+/
769 		_sorting = byes;
770 	}
771 	
772 	/// ditto
773 	final @property bool sorted() // getter
774 	{
775 		//return (_style() & LBS_SORT) != 0;
776 		return _sorting;
777 	}
778 	
779 	
780 	///
781 	final @property void topIndex(int idx) // setter
782 	{
783 		if(isHandleCreated)
784 			prevwproc(LB_SETTOPINDEX, idx, 0);
785 	}
786 	
787 	/// ditto
788 	final @property int topIndex() // getter
789 	{
790 		if(isHandleCreated)
791 			return cast(int)prevwproc(LB_GETTOPINDEX, 0, 0);
792 		return 0;
793 	}
794 	
795 	
796 	///
797 	final @property void useTabStops(bool byes) // setter
798 	{
799 		if(byes)
800 			_style(_style() | LBS_USETABSTOPS);
801 		else
802 			_style(_style() & ~LBS_USETABSTOPS);
803 		
804 		_crecreate();
805 	}
806 	
807 	/// ditto
808 	final @property bool useTabStops() // getter
809 	{
810 		return (_style() & LBS_USETABSTOPS) != 0;
811 	}
812 	
813 	
814 	///
815 	final void beginUpdate()
816 	{
817 		prevwproc(WM_SETREDRAW, false, 0);
818 	}
819 	
820 	/// ditto
821 	final void endUpdate()
822 	{
823 		prevwproc(WM_SETREDRAW, true, 0);
824 		invalidate(true); // Show updates.
825 	}
826 	
827 	
828 	package final bool isMultSel()
829 	{
830 		return (_style() & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL)) != 0;
831 	}
832 	
833 	
834 	///
835 	final void clearSelected()
836 	{
837 		if(created)
838 			selectedIndex = -1;
839 	}
840 	
841 	
842 	///
843 	final int findString(Dstring str, int startIndex)
844 	{
845 		// TODO: find string if control not created ?
846 		
847 		int result = NO_MATCHES;
848 		
849 		if(created)
850 		{
851 			if(dfl.internal.utf.useUnicode)
852 				result = cast(int)prevwproc(LB_FINDSTRING, startIndex, cast(LPARAM)dfl.internal.utf.toUnicodez(str));
853 			else
854 				result = cast(int)prevwproc(LB_FINDSTRING, startIndex, cast(LPARAM)dfl.internal.utf.unsafeAnsiz(str));
855 			if(result == LB_ERR) // Redundant.
856 				result = NO_MATCHES;
857 		}
858 		
859 		return result;
860 	}
861 	
862 	/// ditto
863 	final int findString(Dstring str)
864 	{
865 		return findString(str, -1); // Start at beginning.
866 	}
867 	
868 	
869 	///
870 	final int findStringExact(Dstring str, int startIndex)
871 	{
872 		// TODO: find string if control not created ?
873 		
874 		int result = NO_MATCHES;
875 		
876 		if(created)
877 		{
878 			if(dfl.internal.utf.useUnicode)
879 				result = cast(int)prevwproc(LB_FINDSTRINGEXACT, startIndex, cast(LPARAM)dfl.internal.utf.toUnicodez(str));
880 			else
881 				result = cast(int)prevwproc(LB_FINDSTRINGEXACT, startIndex, cast(LPARAM)dfl.internal.utf.unsafeAnsiz(str));
882 			if(result == LB_ERR) // Redundant.
883 				result = NO_MATCHES;
884 		}
885 		
886 		return result;
887 	}
888 	
889 	/// ditto
890 	final int findStringExact(Dstring str)
891 	{
892 		return findStringExact(str, -1); // Start at beginning.
893 	}
894 	
895 	
896 	///
897 	final int getItemHeight(int idx)
898 	{
899 		int result = cast(int)prevwproc(LB_GETITEMHEIGHT, idx, 0);
900 		if(LB_ERR == result)
901 			throw new DflException("Unable to obtain item height");
902 		return result;
903 	}
904 	
905 	
906 	///
907 	final Rect getItemRectangle(int idx)
908 	{
909 		RECT rect;
910 		if(LB_ERR == cast(int)prevwproc(LB_GETITEMRECT, idx, cast(LPARAM)&rect))
911 		{
912 			//if(idx >= 0 && idx < items.length)
913 				return Rect(0, 0, 0, 0); // ?
914 			//throw new DflException("Unable to obtain item rectangle");
915 		}
916 		return Rect(&rect);
917 	}
918 	
919 	
920 	///
921 	final bool getSelected(int idx)
922 	{
923 		return prevwproc(LB_GETSEL, idx, 0) > 0;
924 	}
925 	
926 	
927 	///
928 	final int indexFromPoint(int x, int y)
929 	{
930 		// LB_ITEMFROMPOINT is "nearest", so also check with the item rectangle to
931 		// see if the point is directly in the item.
932 		
933 		// Maybe use LBItemFromPt() from common controls.
934 		
935 		int result = NO_MATCHES;
936 		
937 		if(created)
938 		{
939 			result = cast(int)prevwproc(LB_ITEMFROMPOINT, 0, MAKELPARAM(x, y));
940 			if(!HIWORD(result)) // In client area
941 			{
942 				//result = LOWORD(result); // High word already 0.
943 				if(result < 0 || !getItemRectangle(result).contains(x, y))
944 					result = NO_MATCHES;
945 			}
946 			else // Outside client area.
947 			{
948 				result = NO_MATCHES;
949 			}
950 		}
951 		
952 		return result;
953 	}
954 	
955 	/// ditto
956 	final int indexFromPoint(Point pt)
957 	{
958 		return indexFromPoint(pt.x, pt.y);
959 	}
960 	
961 	
962 	///
963 	final void setSelected(int idx, bool byes)
964 	{
965 		if(created)
966 			 prevwproc(LB_SETSEL, byes, idx);
967 	}
968 	
969 	
970 	///
971 	protected ObjectCollection createItemCollection()
972 	{
973 		return new ObjectCollection(this);
974 	}
975 	
976 	
977 	///
978 	void sort()
979 	{
980 		if(icollection._items.length)
981 		{
982 			Object[] itemscopy;
983 			itemscopy = icollection._items.dup;
984 			std.algorithm.sort( itemscopy );
985 			
986 			items.clear();
987 			
988 			beginUpdate();
989 			scope(exit)
990 				endUpdate();
991 			
992 			foreach(int i, Object o; itemscopy)
993 			{
994 				items.insert(i, o);
995 			}
996 		}
997 	}
998 	
999 	
1000 	///
1001 	static class ObjectCollection
1002 	{
1003 		protected this(ListBox lbox)
1004 		{
1005 			this.lbox = lbox;
1006 		}
1007 		
1008 		
1009 		protected this(ListBox lbox, Object[] range)
1010 		{
1011 			this.lbox = lbox;
1012 			addRange(range);
1013 		}
1014 		
1015 		
1016 		protected this(ListBox lbox, Dstring[] range)
1017 		{
1018 			this.lbox = lbox;
1019 			addRange(range);
1020 		}
1021 		
1022 		
1023 		/+
1024 		protected this(ListBox lbox, ObjectCollection range)
1025 		{
1026 			this.lbox = lbox;
1027 			addRange(range);
1028 		}
1029 		+/
1030 		
1031 		
1032 		void add(Object value)
1033 		{
1034 			add2(value);
1035 		}
1036 		
1037 		
1038 		void add(Dstring value)
1039 		{
1040 			add(new ListString(value));
1041 		}
1042 		
1043 		
1044 		void addRange(Object[] range)
1045 		{
1046 			if(lbox.sorted)
1047 			{
1048 				foreach(Object value; range)
1049 				{
1050 					add(value);
1051 				}
1052 			}
1053 			else
1054 			{
1055 				_wraparray.addRange(range);
1056 			}
1057 		}
1058 		
1059 		
1060 		void addRange(Dstring[] range)
1061 		{
1062 			foreach(Dstring value; range)
1063 			{
1064 				add(value);
1065 			}
1066 		}
1067 		
1068 		
1069 		private:
1070 		
1071 		ListBox lbox;
1072 		Object[] _items;
1073 		
1074 		
1075 		LRESULT insert2(WPARAM idx, Dstring val)
1076 		{
1077 			insert(cast(int)idx, val);
1078 			return idx;
1079 		}
1080 		
1081 		
1082 		LRESULT add2(Object val)
1083 		{
1084 			int i;
1085 			if(lbox.sorted)
1086 			{
1087 				for(i = 0; i != _items.length; i++)
1088 				{
1089 					if(val < _items[i])
1090 						break;
1091 				}
1092 			}
1093 			else
1094 			{
1095 				i = cast(int)_items.length;
1096 			}
1097 			
1098 			insert(i, val);
1099 			
1100 			return i;
1101 		}
1102 		
1103 		
1104 		LRESULT add2(Dstring val)
1105 		{
1106 			return add2(new ListString(val));
1107 		}
1108 		
1109 		
1110 		void _added(size_t idx, Object val)
1111 		{
1112 			if(lbox.created)
1113 			{
1114 				if(dfl.internal.utf.useUnicode)
1115 					lbox.prevwproc(LB_INSERTSTRING, idx, cast(LPARAM)dfl.internal.utf.toUnicodez(getObjectString(val)));
1116 				else
1117 					lbox.prevwproc(LB_INSERTSTRING, idx, cast(LPARAM)dfl.internal.utf.toAnsiz(getObjectString(val))); // Can this be unsafeAnsiz()?
1118 			}
1119 		}
1120 		
1121 		
1122 		void _removed(size_t idx, Object val)
1123 		{
1124 			if(size_t.max == idx) // Clear all.
1125 			{
1126 				if(lbox.created)
1127 				{
1128 					lbox.prevwproc(LB_RESETCONTENT, 0, 0);
1129 				}
1130 			}
1131 			else
1132 			{
1133 				if(lbox.created)
1134 				{
1135 					lbox.prevwproc(LB_DELETESTRING, cast(WPARAM)idx, 0);
1136 				}
1137 			}
1138 		}
1139 		
1140 		
1141 		public:
1142 		
1143 		mixin ListWrapArray!(Object, _items,
1144 			_blankListCallback!(Object), _added,
1145 			_blankListCallback!(Object), _removed,
1146 			true, false, false) _wraparray;
1147 	}
1148 	
1149 	
1150 	this()
1151 	{
1152 		_initListbox();
1153 		
1154 		// Default useTabStops and vertical scrolling.
1155 		wstyle |= WS_TABSTOP | LBS_USETABSTOPS | LBS_HASSTRINGS | WS_VSCROLL | LBS_NOTIFY;
1156 		wexstyle |= WS_EX_CLIENTEDGE;
1157 		ctrlStyle |= ControlStyles.SELECTABLE;
1158 		wclassStyle = listboxClassStyle;
1159 		
1160 		icollection = createItemCollection();
1161 		selidxcollection = new SelectedIndexCollection(this);
1162 		selobjcollection = new SelectedObjectCollection(this);
1163 	}
1164 	
1165 	
1166 	protected override void onHandleCreated(EventArgs ea)
1167 	{
1168 		super.onHandleCreated(ea);
1169 		
1170 		// Set the Ctrl ID to the HWND so that it is unique
1171 		// and WM_MEASUREITEM will work properly.
1172 		SetWindowLongA(hwnd, GWL_ID, cast(LONG)hwnd);
1173 		
1174 		if(hextent != 0)
1175 			prevwproc(LB_SETHORIZONTALEXTENT, hextent, 0);
1176 		
1177 		if(iheight != DEFAULT_ITEM_HEIGHT)
1178 			prevwproc(LB_SETITEMHEIGHT, 0, MAKELPARAM(iheight, 0));
1179 		
1180 		Message m;
1181 		m.hWnd = handle;
1182 		m.msg = LB_INSERTSTRING;
1183 		// Note: duplicate code.
1184 		if(dfl.internal.utf.useUnicode)
1185 		{
1186 			foreach(int i, Object obj; icollection._items)
1187 			{
1188 				m.wParam = i;
1189 				m.lParam = cast(LPARAM)dfl.internal.utf.toUnicodez(getObjectString(obj)); // <--
1190 				
1191 				prevWndProc(m);
1192 				//if(LB_ERR == m.result || LB_ERRSPACE == m.result)
1193 				if(m.result < 0)
1194 					throw new DflException("Unable to add list item");
1195 				
1196 				//prevwproc(LB_SETITEMDATA, m.result, cast(LPARAM)cast(void*)obj);
1197 			}
1198 		}
1199 		else
1200 		{
1201 			foreach(int i, Object obj; icollection._items)
1202 			{
1203 				m.wParam = i;
1204 				m.lParam = cast(LPARAM)dfl.internal.utf.toAnsiz(getObjectString(obj)); // Can this be unsafeAnsiz? // <--
1205 				
1206 				prevWndProc(m);
1207 				//if(LB_ERR == m.result || LB_ERRSPACE == m.result)
1208 				if(m.result < 0)
1209 					throw new DflException("Unable to add list item");
1210 				
1211 				//prevwproc(LB_SETITEMDATA, m.result, cast(LPARAM)cast(void*)obj);
1212 			}
1213 		}
1214 		
1215 		//redrawEntire();
1216 	}
1217 	
1218 	
1219 	/+
1220 	override void createHandle()
1221 	{
1222 		if(isHandleCreated)
1223 			return;
1224 		
1225 		createClassHandle(LISTBOX_CLASSNAME);
1226 		
1227 		onHandleCreated(EventArgs.empty);
1228 	}
1229 	+/
1230 	
1231 	
1232 	protected override void createParams(ref CreateParams cp)
1233 	{
1234 		super.createParams(cp);
1235 		
1236 		cp.className = LISTBOX_CLASSNAME;
1237 	}
1238 	
1239 	
1240 	//DrawItemEventHandler drawItem;
1241 	Event!(ListBox, DrawItemEventArgs) drawItem; ///
1242 	//MeasureItemEventHandler measureItem;
1243 	Event!(ListBox, MeasureItemEventArgs) measureItem; ///
1244 	
1245 	
1246 	protected:
1247 	
1248 	///
1249 	void onDrawItem(DrawItemEventArgs dieh)
1250 	{
1251 		drawItem(this, dieh);
1252 	}
1253 	
1254 	
1255 	///
1256 	void onMeasureItem(MeasureItemEventArgs miea)
1257 	{
1258 		measureItem(this, miea);
1259 	}
1260 	
1261 	
1262 	package final void _WmDrawItem(DRAWITEMSTRUCT* dis)
1263 	in
1264 	{
1265 		assert(dis.hwndItem == handle);
1266 		assert(dis.CtlType == ODT_LISTBOX);
1267 	}
1268 	body
1269 	{
1270 		DrawItemState state;
1271 		state = cast(DrawItemState)dis.itemState;
1272 		
1273 		if(dis.itemID == -1)
1274 		{
1275 			FillRect(dis.hDC, &dis.rcItem, hbrBg);
1276 			if(state & DrawItemState.FOCUS)
1277 				DrawFocusRect(dis.hDC, &dis.rcItem);
1278 		}
1279 		else
1280 		{
1281 			DrawItemEventArgs diea;
1282 			Color bc, fc;
1283 			
1284 			if(state & DrawItemState.SELECTED)
1285 			{
1286 				bc = Color.systemColor(COLOR_HIGHLIGHT);
1287 				fc = Color.systemColor(COLOR_HIGHLIGHTTEXT);
1288 			}
1289 			else
1290 			{
1291 				bc = backColor;
1292 				fc = foreColor;
1293 			}
1294 			
1295 			prepareDc(dis.hDC);
1296 			diea = new DrawItemEventArgs(new Graphics(dis.hDC, false), wfont,
1297 				Rect(&dis.rcItem), dis.itemID, state, fc, bc);
1298 			
1299 			onDrawItem(diea);
1300 		}
1301 	}
1302 	
1303 	
1304 	package final void _WmMeasureItem(MEASUREITEMSTRUCT* mis)
1305 	in
1306 	{
1307 		assert(mis.CtlType == ODT_LISTBOX);
1308 	}
1309 	body
1310 	{
1311 		MeasureItemEventArgs miea;
1312 		scope Graphics gpx = new CommonGraphics(handle(), GetDC(handle));
1313 		miea = new MeasureItemEventArgs(gpx, mis.itemID, /+ mis.itemHeight +/ iheight);
1314 		miea.itemWidth = mis.itemWidth;
1315 		
1316 		onMeasureItem(miea);
1317 		
1318 		mis.itemHeight = miea.itemHeight;
1319 		mis.itemWidth = miea.itemWidth;
1320 	}
1321 	
1322 	
1323 	override void prevWndProc(ref Message msg)
1324 	{
1325 		//msg.result = CallWindowProcA(listboxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
1326 		msg.result = dfl.internal.utf.callWindowProc(listboxPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
1327 	}
1328 	
1329 	
1330 	protected override void onReflectedMessage(ref Message m)
1331 	{
1332 		super.onReflectedMessage(m);
1333 		
1334 		switch(m.msg)
1335 		{
1336 			case WM_DRAWITEM:
1337 				_WmDrawItem(cast(DRAWITEMSTRUCT*)m.lParam);
1338 				m.result = 1;
1339 				break;
1340 			
1341 			case WM_MEASUREITEM:
1342 				_WmMeasureItem(cast(MEASUREITEMSTRUCT*)m.lParam);
1343 				m.result = 1;
1344 				break;
1345 			
1346 			case WM_COMMAND:
1347 				assert(cast(HWND)m.lParam == handle);
1348 				switch(HIWORD(m.wParam))
1349 				{
1350 					case LBN_SELCHANGE:
1351 						onSelectedIndexChanged(EventArgs.empty);
1352 						break;
1353 					
1354 					case LBN_SELCANCEL:
1355 						onSelectedIndexChanged(EventArgs.empty);
1356 						break;
1357 					
1358 					default:
1359 				}
1360 				break;
1361 			
1362 			default:
1363 		}
1364 	}
1365 	
1366 	
1367 	override void wndProc(ref Message msg)
1368 	{
1369 		switch(msg.msg)
1370 		{
1371 			case LB_ADDSTRING:
1372 				//msg.result = icollection.add2(stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix.
1373 				//msg.result = icollection.add2(stringFromStringz(cast(char*)msg.lParam).idup); // TODO: fix. // Needed in D2. Doesn't work in D1.
1374 				msg.result = icollection.add2(cast(Dstring)stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix. // Needed in D2.
1375 				return;
1376 			
1377 			case LB_INSERTSTRING:
1378 				//msg.result = icollection.insert2(msg.wParam, stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix.
1379 				//msg.result = icollection.insert2(msg.wParam, stringFromStringz(cast(char*)msg.lParam).idup); // TODO: fix. // Needed in D2. Doesn't work in D1.
1380 				msg.result = icollection.insert2(msg.wParam, cast(Dstring)stringFromStringz(cast(char*)msg.lParam).dup); // TODO: fix. // Needed in D2.
1381 				return;
1382 			
1383 			case LB_DELETESTRING:
1384 				icollection.removeAt(cast(int)msg.wParam);
1385 				msg.result = icollection.length;
1386 				return;
1387 			
1388 			case LB_RESETCONTENT:
1389 				icollection.clear();
1390 				return;
1391 			
1392 			case LB_SETITEMDATA:
1393 				// Cannot set item data from outside DFL.
1394 				msg.result = LB_ERR;
1395 				return;
1396 			
1397 			case LB_ADDFILE:
1398 				msg.result = LB_ERR;
1399 				return;
1400 			
1401 			case LB_DIR:
1402 				msg.result = LB_ERR;
1403 				return;
1404 			
1405 			default:
1406 		}
1407 		super.wndProc(msg);
1408 	}
1409 	
1410 	
1411 	private:
1412 	int hextent = 0;
1413 	int iheight = DEFAULT_ITEM_HEIGHT;
1414 	ObjectCollection icollection;
1415 	SelectedIndexCollection selidxcollection;
1416 	SelectedObjectCollection selobjcollection;
1417 	bool _sorting = false;
1418 	
1419 	
1420 	package:
1421 	final:
1422 	LRESULT prevwproc(UINT msg, WPARAM wparam, LPARAM lparam)
1423 	{
1424 		//return CallWindowProcA(listviewPrevWndProc, hwnd, msg, wparam, lparam);
1425 		return dfl.internal.utf.callWindowProc(listboxPrevWndProc, hwnd, msg, wparam, lparam);
1426 	}
1427 }
1428