1
2
3
4
5
6 using System;
7 using System.Collections;
8
9 namespace System.Windows
10 {
11 public class ListViewItemEventArgs
12 {
13 public ListViewItemEventArgs(ListView* view_, ListViewItem* item_) : view(view_), item(item_), location(), control(false), errorId(0)
14 {
15 }
16 public ListView* view;
17 public ListViewItem* item;
18 public Point location;
19 public bool control;
20 public int errorId;
21 }
22
23 public class ListViewColumnEventArgs
24 {
25 public ListViewColumnEventArgs(ListView* view_, ListViewColumn* column_) : view(view_), column(column_)
26 {
27 }
28 public ListView* view;
29 public ListViewColumn* column;
30 }
31
32 public class delegate void ListViewItemEventHandler(ListViewItemEventArgs& args);
33 public class delegate void ListViewColumnEventHandler(ListViewColumnEventArgs& args);
34
35 public Color DefaultListViewBackgroundColor()
36 {
37 return Color.White();
38 }
39
40 public string DefaultListViewFontFamilyName()
41 {
42 return "Segoe UI";
43 }
44
45 public float DefaultListViewFontSize()
46 {
47 return 9.000000f;
48 }
49
50 public Color DefaultListViewColumnTextColor()
51 {
52 return Color(76u, 96u, 122u);
53 }
54
55 public Color DefaultListViewItemTextColor()
56 {
57 return Color.Black();
58 }
59
60 public Color DefaultListViewDisabledItemTextColor()
61 {
62 return Color(201u, 201u, 201u);
63 }
64
65 public Color DefaultListViewColumnDividerColor()
66 {
67 return Color(229u, 229u, 229u);
68 }
69
70 public Color DefaultListViewItemSelectedColor()
71 {
72 return Color(204u, 232u, 255u);
73 }
74
75 public Padding DefaultListViewColumnHeaderPadding()
76 {
77 return Padding(4, 0, 4, 4);
78 }
79
80 public Padding DefaultListViewItemPadding()
81 {
82 return Padding(4, 0, 4, 0);
83 }
84
85 public Padding DefaultListViewItemColumnPadding()
86 {
87 return Padding(4, 0, 4, 0);
88 }
89
90 public Padding DefaultListViewColumnDividerPadding()
91 {
92 return Padding(1, 0, 1, 0);
93 }
94
95 public Padding DefaultListViewImagePadding()
96 {
97 return Padding(2, 2, 2, 2);
98 }
99
100 public ControlCreateParams& ListViewControlCreateParams(ControlCreateParams& controlCreateParams)
101 {
102 return controlCreateParams.SetWindowClassName("System.Windows.ListView").SetWindowClassStyle(DoubleClickWindowClassStyle()).
103 SetWindowClassBackgroundColor(SystemColor.COLOR_WINDOW).SetBackgroundColor(DefaultListViewBackgroundColor());
104 }
105
106 public class ListViewCreateParams
107 {
108 public ListViewCreateParams(ControlCreateParams& controlCreateParams_) :
109 controlCreateParams(controlCreateParams_),
110 allowMultiselect(false),
111 fontFamilyName(DefaultListViewFontFamilyName()),
112 fontSize(DefaultListViewFontSize()),
113 columnTextColor(DefaultListViewColumnTextColor()),
114 itemTextColor(DefaultListViewItemTextColor()),
115 disabledItemTextColor(DefaultListViewDisabledItemTextColor()),
116 columnDividerColor(DefaultListViewColumnDividerColor()),
117 itemSelectedColor(DefaultListViewItemSelectedColor()),
118 columnHeaderPadding(DefaultListViewColumnHeaderPadding()),
119 itemPadding(DefaultListViewItemPadding()),
120 itemColumnPadding(DefaultListViewItemColumnPadding()),
121 columnDividerPadding(DefaultListViewColumnDividerPadding()),
122 imagePadding(DefaultListViewImagePadding())
123 {
124 }
125 public ListViewCreateParams& Defaults()
126 {
127 return *this;
128 }
129 public ListViewCreateParams& SetAllowMultiselect(bool allowMultiselect_)
130 {
131 allowMultiselect = allowMultiselect_;
132 return *this;
133 }
134 public ListViewCreateParams& SetFontFamilyName(const string& fontFamilyName_)
135 {
136 fontFamilyName = fontFamilyName_;
137 return *this;
138 }
139 public ListViewCreateParams& SetFontSize(float fontSize_)
140 {
141 fontSize = fontSize_;
142 return *this;
143 }
144 public ListViewCreateParams& SetColumnTextColor(const Color& columnTextColor_)
145 {
146 columnTextColor = columnTextColor_;
147 return *this;
148 }
149 public ListViewCreateParams& SetItemTextColor(const Color& itemTextColor_)
150 {
151 itemTextColor = itemTextColor_;
152 return *this;
153 }
154 public ListViewCreateParams& SetDisabledItemTextColor(const Color& disabledItemTextColor_)
155 {
156 disabledItemTextColor = disabledItemTextColor_;
157 return *this;
158 }
159 public ListViewCreateParams& SetColumnDividerColor(const Color& columnDividerColor_)
160 {
161 columnDividerColor = columnDividerColor_;
162 return *this;
163 }
164 public ListViewCreateParams& SetItemSelectedColor(const Color& itemSelectedColor_)
165 {
166 itemSelectedColor = itemSelectedColor_;
167 return *this;
168 }
169 public ListViewCreateParams& SetColumnHeaderPadding(const Padding& columnHeaderPadding_)
170 {
171 columnHeaderPadding = columnHeaderPadding_;
172 return *this;
173 }
174 public ListViewCreateParams& SetItemPadding(const Padding& itemPadding_)
175 {
176 itemPadding = itemPadding_;
177 return *this;
178 }
179 public ListViewCreateParams& SetItemColumnPadding(const Padding& itemColumnPadding_)
180 {
181 itemColumnPadding = itemColumnPadding_;
182 return *this;
183 }
184 public ListViewCreateParams& SetColumnDividerPadding(const Padding& columnDividerPadding_)
185 {
186 columnDividerPadding = columnDividerPadding_;
187 return *this;
188 }
189 public ListViewCreateParams& SetImagePadding(const Padding& imagePadding_)
190 {
191 imagePadding = imagePadding_;
192 return *this;
193 }
194 public ControlCreateParams& controlCreateParams;
195 public bool allowMultiselect;
196 public string fontFamilyName;
197 public float fontSize;
198 public Color columnTextColor;
199 public Color itemTextColor;
200 public Color disabledItemTextColor;
201 public Color columnDividerColor;
202 public Color itemSelectedColor;
203 public Padding columnHeaderPadding;
204 public Padding itemPadding;
205 public Padding itemColumnPadding;
206 public Padding columnDividerPadding;
207 public Padding imagePadding;
208 }
209
210 public class ListView : Control
211 {
212 public enum Flags : int
213 {
214 none = 0, measured = 1 << 0, allowMultiselect = 1 << 1
215 }
216 public ListView(ListViewCreateParams& createParams) :
217 base(createParams.controlCreateParams),
218 flags(Flags.none), imageList(null),
219 columnHeaderTextBrush(createParams.columnTextColor),
220 itemTextBrush(createParams.itemTextColor),
221 disabledItemTextBrush(createParams.disabledItemTextColor),
222 itemSelectedBrush(createParams.itemSelectedColor),
223 columnDividerPen(createParams.columnDividerColor),
224 stringFormat(),
225 columnHeaderPadding(createParams.columnHeaderPadding),
226 itemPadding(createParams.itemPadding),
227 itemColumnPadding(createParams.itemColumnPadding),
228 columnDividerPadding(createParams.columnDividerPadding),
229 imagePadding(createParams.imagePadding),
230 charWidth(0),
231 charHeight(0),
232 columnDividerWidth(1),
233 ellipsisWidth(0),
234 mouseDownItem(null),
235 mouseEnterItem(null),
236 selectedItem(null),
237 mouseDownColumnDivider(null),
238 data(null),
239 arrowCursor(),
240 columnSizeCursor(null)
241 {
242 auto cursorResult = LoadStandardCursor(StandardCursorId.IDC_ARROW);
243 if (cursorResult.Error())
244 {
245 SetErrorId(cursorResult.GetErrorId());
246 return;
247 }
248 arrowCursor = Rvalue(cursorResult.Value());
249 auto columnSizeCursorResult = Application.GetResourceManager().GetCursor("column.size.system.windows.cursor");
250 if (columnSizeCursorResult.Error())
251 {
252 SetErrorId(columnSizeCursorResult.GetErrorId());
253 return;
254 }
255 columnSizeCursor = Rvalue(columnSizeCursorResult.Value());
256 if (createParams.allowMultiselect)
257 {
258 SetFlag(Flags.allowMultiselect);
259 }
260 SetFont(Font(FontFamily(createParams.fontFamilyName), createParams.fontSize));
261 }
262 public inline ImageList* GetImageList() const
263 {
264 return imageList;
265 }
266 public void SetImageList(ImageList* imageList_)
267 {
268 imageList = imageList_;
269 }
270 public const StringFormat& GetStringFormat() const
271 {
272 return stringFormat;
273 }
274 public const Brush& ColumnHeaderTextBrush() const
275 {
276 return columnHeaderTextBrush;
277 }
278 public const Brush* ItemTextBrush() const
279 {
280 return &itemTextBrush;
281 }
282 public const Brush* DisabledItemTextBrush() const
283 {
284 return &disabledItemTextBrush;
285 }
286 public const Brush& ItemSelectedBrush() const
287 {
288 return itemSelectedBrush;
289 }
290 public const Pen& ColumnDividerPen() const
291 {
292 return columnDividerPen;
293 }
294 public const Padding& ColumnHeaderPadding() const
295 {
296 return columnHeaderPadding;
297 }
298 public const Padding& ItemPadding() const
299 {
300 return itemPadding;
301 }
302 public const Padding& ItemColumnPadding() const
303 {
304 return itemColumnPadding;
305 }
306 public const Padding& ColumnDividerPadding() const
307 {
308 return columnDividerPadding;
309 }
310 public const Padding& ImagePadding() const
311 {
312 return imagePadding;
313 }
314 public inline bool GetFlag(Flags flag) const
315 {
316 return (flags & flag) != Flags.none;
317 }
318 public void SetFlag(Flags flag)
319 {
320 flags = cast<Flags>(flags | flag);
321 }
322 public void ResetFlag(Flags flag)
323 {
324 flags = cast<Flags>(flags & ~flag);
325 }
326 public int ColumnCount() const
327 {
328 return cast<int>(columns.Count());
329 }
330 public void AddColumn(const string& name, int width)
331 {
332 ListViewColumn* column = new ListViewColumn(this, name, width);
333 columns.Add(UniquePtr<ListViewColumn>(column));
334 columnDividers.Add(UniquePtr<ListViewColumnDivider>(new ListViewColumnDivider(this, column)));
335 }
336 [nodiscard]
337 public Result<ListViewColumn*> GetColumn(int columnIndex) const
338 {
339 if (columnIndex < 0 || columnIndex >= ColumnCount())
340 {
341 int errorId = AllocateError("invalid column index");
342 return Result<ListViewColumn*>(ErrorId(errorId));
343 }
344 return Result<ListViewColumn*>(columns[columnIndex].Get());
345 }
346 public int ItemCount() const
347 {
348 return cast<int>(items.Count());
349 }
350 public ListViewItem* AddItem()
351 {
352 ListViewItem* item = new ListViewItem(this);
353 items.Add(UniquePtr<ListViewItem>(item));
354 return item;
355 }
356 [nodiscard]
357 public Result<ListViewItem*> GetItem(int itemIndex) const
358 {
359 if (itemIndex < 0 || itemIndex >= ItemCount())
360 {
361 int errorId = AllocateError("invalid item index");
362 return Result<ListViewItem*>(ErrorId(errorId));
363 }
364 return Result<ListViewItem*>(items[itemIndex].Get());
365 }
366 public inline ListViewItem* SelectedItem() const
367 {
368 return selectedItem;
369 }
370 [nodiscard]
371 public Result<bool> SetSelectedItem(ListViewItem* selectedItem_)
372 {
373 if (GetFlag(Flags.allowMultiselect))
374 {
375 auto result = ResetSelectedItems();
376 if (result.Error()) return result;
377 }
378 if (selectedItem != selectedItem_)
379 {
380 if (selectedItem != null)
381 {
382 auto result = selectedItem->ResetSelected();
383 if (result.Error()) return result;
384 }
385 selectedItem = selectedItem_;
386 if (selectedItem != null)
387 {
388 auto result = selectedItem->SetSelected();
389 if (result.Error()) return result;
390 }
391 }
392 return Result<bool>(true);
393 }
394 public List<ListViewItem*> GetSelectedItems() const
395 {
396 List<ListViewItem*> result;
397 for (const UniquePtr<ListViewItem>& item : items)
398 {
399 if (item->IsSelected())
400 {
401 result.Add(item.Get());
402 }
403 }
404 return result;
405 }
406 [nodiscard]
407 public Result<bool> ResetSelectedItems()
408 {
409 for (const UniquePtr<ListViewItem>& item : items)
410 {
411 if (item.Get() != SelectedItem())
412 {
413 if (item->IsSelected())
414 {
415 auto result = item->ResetSelected();
416 if (result.Error()) return result;
417 }
418 }
419 }
420 return Result<bool>(true);
421 }
422 public ListViewItem* ItemAt(const Point& location) const
423 {
424 for (const UniquePtr<ListViewItem>& item : items)
425 {
426 Rect rect(item->Location(), item->GetSize());
427 if (rect.Contains(location))
428 {
429 return item.Get();
430 }
431 }
432 return null;
433 }
434 public ListViewColumnDivider* ColumnDividerAt(const Point& location) const
435 {
436 for (const UniquePtr<ListViewColumnDivider>& columnDivider : columnDividers)
437 {
438 Rect rect(columnDivider->Location(), columnDivider->GetSize());
439 if (rect.Contains(location))
440 {
441 return columnDivider.Get();
442 }
443 }
444 return null;
445 }
446 public inline float TextHeight() const
447 {
448 return charHeight;
449 }
450 public inline float ColumnDividerWidth() const
451 {
452 return columnDividerWidth;
453 }
454 public float EllipsisWidth() const
455 {
456 return ellipsisWidth;
457 }
458 public void FireColumnWidthChanged(ListViewColumn* column)
459 {
460 ListViewColumnEventArgs args(this, column);
461 OnColumnWidthChanged(args);
462 }
463 public void SetData(void* data_)
464 {
465 data = data_;
466 }
467 public inline void* Data() const
468 {
469 return data;
470 }
471 public Event<ListViewItemEventHandler, ListViewItemEventArgs>& ItemClickEvent()
472 {
473 return itemClickEvent;
474 }
475 public Event<ListViewItemEventHandler, ListViewItemEventArgs>& ItemRightClickEvent()
476 {
477 return itemRightClickEvent;
478 }
479 public Event<ListViewItemEventHandler, ListViewItemEventArgs>& ItemDoubleClickEvent()
480 {
481 return itemDoubleClickEvent;
482 }
483 public Event<ListViewItemEventHandler, ListViewItemEventArgs>& ItemEnterEvent()
484 {
485 return itemEnterEvent;
486 }
487 public Event<ListViewItemEventHandler, ListViewItemEventArgs>& ItemLeaveEvent()
488 {
489 return itemLeaveEvent;
490 }
491 public Event<ListViewColumnEventHandler, ListViewColumnEventArgs>& ColumnWidthChangedEvent()
492 {
493 return columnWidthChangedEvent;
494 }
495 [nodiscard]
496 protected override Result<bool> OnSizeChanged(SizeChangedEventArgs& args)
497 {
498 auto result = base->OnSizeChanged(args);
499 if (result.Error()) return result;
500 SetContentLocation(Point(0, 0));
501 return Result<bool>(true);
502 }
503 [nodiscard]
504 protected override Result<bool> OnPaint(PaintEventArgs& args)
505 {
506 if (!GetFlag(Flags.measured))
507 {
508 SetFlag(Flags.measured);
509 auto result = Measure(args.graphics);
510 if (result.Error())
511 {
512 return Result<bool>(ErrorId(result.GetErrorId()));
513 }
514 }
515 Size contentSize(0, 0);
516 auto result = args.graphics.Clear(BackgroundColor());
517 if (result.Error()) return result;
518 PointF origin;
519 auto measureResult = MeasureItems(args.graphics, contentSize);
520 if (measureResult.Error())
521 {
522 return Result<bool>(ErrorId(measureResult.GetErrorId()));
523 }
524 result = DrawColumnHeader(args.graphics, origin);
525 if (result.Error()) return result;
526 result = DrawItems(args.graphics, origin);
527 if (result.Error()) return result;
528 result = SetContentSize(contentSize);
529 if (result.Error()) return result;
530 return base->OnPaint(args);
531 }
532 [nodiscard]
533 protected override Result<bool> OnMouseDown(MouseEventArgs& args)
534 {
535 mouseDownItem = null;
536 ListViewItem* item = ItemAt(args.location);
537 if (item != null)
538 {
539 mouseDownItem = item;
540 }
541 else
542 {
543 auto result = SetSelectedItem(null);
544 if (result.Error()) return result;
545 }
546 if (args.buttons == MouseButtons.lbutton)
547 {
548 ListViewColumnDivider* columnDivider = ColumnDividerAt(args.location);
549 if (columnDivider != null)
550 {
551 columnDivider->OnLButtonDown(args.location);
552 mouseDownColumnDivider = columnDivider;
553 }
554 }
555 return Result<bool>(true);
556 }
557 [nodiscard]
558 protected override Result<bool> OnMouseUp(MouseEventArgs& args)
559 {
560 ListViewItem* item = ItemAt(args.location);
561 if (item == mouseDownItem)
562 {
563 ListViewItemEventArgs itemArgs(this, item);
564 if ((args.buttons & MouseButtons.lbutton) != MouseButtons.none)
565 {
566 if (GetFlag(Flags.allowMultiselect))
567 {
568 if ((args.buttons & MouseButtons.control) != MouseButtons.none)
569 {
570 itemArgs.control = true;
571 }
572 }
573 itemClickEvent.Fire(itemArgs);
574 }
575 else if (args.buttons == MouseButtons.rbutton)
576 {
577 itemArgs.location = args.location;
578 itemRightClickEvent.Fire(itemArgs);
579 }
580 if (itemArgs.errorId != 0)
581 {
582 return Result<bool>(ErrorId(itemArgs.errorId));
583 }
584 }
585 mouseDownItem = null;
586 if (args.buttons == MouseButtons.lbutton)
587 {
588 if (mouseDownColumnDivider != null)
589 {
590 mouseDownColumnDivider->OnLButtonUp();
591 mouseDownColumnDivider = null;
592 }
593 }
594 return Result<bool>(true);
595 }
596 [nodiscard]
597 protected override Result<bool> OnMouseDoubleClick(MouseEventArgs& args)
598 {
599 ListViewItem* item = ItemAt(args.location);
600 if (item != null)
601 {
602 ListViewItemEventArgs args(this, item);
603 itemDoubleClickEvent.Fire(args);
604 }
605 mouseDownItem = null;
606 return Result<bool>(true);
607 }
608 [nodiscard]
609 protected override Result<bool> OnMouseEnter(EnterLeaveEventArgs& args)
610 {
611 mouseEnterItem = null;
612 return Result<bool>(true);
613 }
614 protected override Result<bool> OnMouseLeave(EnterLeaveEventArgs& args)
615 {
616 if (mouseEnterItem != null)
617 {
618 ListViewItemEventArgs leaveItemArgs(this, mouseEnterItem);
619 itemLeaveEvent.Fire(leaveItemArgs);
620 mouseEnterItem = null;
621 }
622 return Result<bool>(true);
623 }
624 [nodiscard]
625 protected override Result<bool> OnMouseMove(MouseEventArgs& args)
626 {
627 if (mouseEnterItem == null)
628 {
629 mouseEnterItem = ItemAt(args.location);
630 if (mouseEnterItem != null)
631 {
632 ListViewItemEventArgs itemArgs(this, mouseEnterItem);
633 itemEnterEvent.Fire(itemArgs);
634 }
635 }
636 else
637 {
638 ListViewItem* item = ItemAt(args.location);
639 if (item != mouseEnterItem)
640 {
641 ListViewItemEventArgs leaveItemArgs(this, mouseEnterItem);
642 itemLeaveEvent.Fire(leaveItemArgs);
643 mouseEnterItem = item;
644 if (mouseEnterItem != null)
645 {
646 ListViewItemEventArgs enterItemArgs(this, mouseEnterItem);
647 itemEnterEvent.Fire(enterItemArgs);
648 }
649 }
650 }
651 if (args.buttons == MouseButtons.lbutton)
652 {
653 if (mouseDownColumnDivider != null)
654 {
655 if (mouseDownColumnDivider->HasMouseCapture())
656 {
657 auto result = mouseDownColumnDivider->OnMouseMove(args.location);
658 if (result.Error()) return result;
659 }
660 }
661 }
662 return Result<bool>(true);
663 }
664 protected virtual void OnColumnWidthChanged(ListViewColumnEventArgs& args)
665 {
666 columnWidthChangedEvent.Fire(args);
667 }
668 protected override Result<bool> SetCursor()
669 {
670 Point pt;
671 auto cpResult = GetCursorPos(pt.x, pt.y);
672 if (cpResult.Error()) return cpResult;
673 auto result = ScreenToClient(pt);
674 if (result.Error())
675 {
676 return Result<bool>(ErrorId(result.GetErrorId()));
677 }
678 Point cursorPos = result.Value();
679 bool cursorSet = false;
680 for (const UniquePtr<ListViewColumnDivider>& columnDivider : columnDividers)
681 {
682 Rect r(columnDivider->Location(), columnDivider->GetSize());
683 if (r.Contains(cursorPos))
684 {
685 System.Windows.SetCursor(columnSizeCursor);
686 cursorSet = true;
687 break;
688 }
689 }
690 if (!cursorSet)
691 {
692 System.Windows.SetCursor(&arrowCursor);
693 }
694 return Result<bool>(true);
695 }
696 private Result<bool> Measure(Graphics& graphics)
697 {
698 auto measureResult = graphics.MeasureStringRectF("This is test string", GetFont(), PointF(0, 0), stringFormat);
699 if (measureResult.Error())
700 {
701 return Result<bool>(ErrorId(measureResult.GetErrorId()));
702 }
703 RectF charRect = measureResult.Value();
704 charHeight = charRect.size.h;
705 charWidth = charRect.size.w;
706 SetScrollUnits(cast<int>(charHeight + 0.500000f), cast<int>(2 * (charWidth + 0.500000f)));
707 measureResult = graphics.MeasureStringRectF("...", GetFont(), PointF(0, 0), stringFormat);
708 if (measureResult.Error())
709 {
710 return Result<bool>(ErrorId(measureResult.GetErrorId()));
711 }
712 RectF ellipsisRect = measureResult.Value();
713 ellipsisWidth = ellipsisRect.size.w;
714 return Result<bool>(true);
715 }
716 private Result<bool> MeasureItems(Graphics& graphics, Size& contentSize)
717 {
718 int maxWidth = 0;
719 Point loc(itemPadding.left, cast<int>(charHeight + 0.500000f + columnHeaderPadding.Vertical()));
720 for (const UniquePtr<ListViewItem>& item : items)
721 {
722 item->SetLocation(loc);
723 auto measureResult = item->Measure(graphics);
724 if (measureResult.Error())
725 {
726 return Result<bool>(ErrorId(measureResult.GetErrorId()));
727 }
728 Size itemSize = item->GetSize();
729 loc.y = loc.y + itemSize.h;
730 maxWidth = Max(maxWidth, itemSize.w);
731 }
732 contentSize.w = Max(contentSize.w, maxWidth);
733 contentSize.h = Max(contentSize.h, loc.y);
734 return Result<bool>(true);
735 }
736 private Result<bool> DrawColumnHeader(Graphics& graphics, PointF& origin)
737 {
738 PointF headerOrigin = origin;
739 headerOrigin.x = headerOrigin.x + columnHeaderPadding.left;
740 headerOrigin.y = headerOrigin.y + columnHeaderPadding.top;
741 long n = columns.Count();
742 for (long i = 0; i < n; ++i;)
743 {
744 ListViewColumn* column = columns[i].Get();
745 auto drawResult = column->Draw(graphics, headerOrigin);
746 if (drawResult.Error())
747 {
748 return Result<bool>(ErrorId(drawResult.GetErrorId()));
749 }
750 headerOrigin.x = headerOrigin.x + column->Width() + columnHeaderPadding.Horizontal();
751 ListViewColumnDivider* divider = columnDividers[i].Get();
752 headerOrigin.x = headerOrigin.x + columnDividerPadding.left;
753 drawResult = divider->Draw(graphics, headerOrigin);
754 if (drawResult.Error())
755 {
756 return Result<bool>(ErrorId(drawResult.GetErrorId()));
757 }
758 headerOrigin.x = headerOrigin.x + columnDividerWidth + columnDividerPadding.right;
759 }
760 origin.y = origin.y + charHeight + columnHeaderPadding.Vertical();
761 return Result<bool>(true);
762 }
763 private Result<bool> DrawItems(Graphics& graphics, PointF& origin)
764 {
765 for (const UniquePtr<ListViewItem>& item : items)
766 {
767 PointF itemOrigin = origin;
768 itemOrigin.y = itemOrigin.y + itemPadding.top;
769 itemOrigin.x = itemOrigin.x + itemPadding.left;
770 auto drawResult = item->Draw(graphics, itemOrigin);
771 if (drawResult.Error())
772 {
773 return Result<bool>(ErrorId(drawResult.GetErrorId()));
774 }
775 Size sz = item->GetSize();
776 origin.y = origin.y + sz.h;
777 }
778 return Result<bool>(true);
779 }
780 private Flags flags;
781 private ImageList* imageList;
782 private SolidBrush columnHeaderTextBrush;
783 private SolidBrush itemTextBrush;
784 private SolidBrush disabledItemTextBrush;
785 private SolidBrush itemSelectedBrush;
786 private Pen columnDividerPen;
787 private StringFormat stringFormat;
788 private Padding columnHeaderPadding;
789 private Padding itemPadding;
790 private Padding itemColumnPadding;
791 private Padding columnDividerPadding;
792 private Padding imagePadding;
793 private float charWidth;
794 private float charHeight;
795 private float columnDividerWidth;
796 private float ellipsisWidth;
797 private ListViewItem* mouseDownItem;
798 private ListViewItem* mouseEnterItem;
799 private ListViewItem* selectedItem;
800 private ListViewColumnDivider* mouseDownColumnDivider;
801 private void* data;
802 private Cursor arrowCursor;
803 private Cursor* columnSizeCursor;
804 private List<UniquePtr<ListViewColumn>> columns;
805 private List<UniquePtr<ListViewColumnDivider>> columnDividers;
806 private List<UniquePtr<ListViewItem>> items;
807 private Event<ListViewItemEventHandler, ListViewItemEventArgs> itemClickEvent;
808 private Event<ListViewItemEventHandler, ListViewItemEventArgs> itemRightClickEvent;
809 private Event<ListViewItemEventHandler, ListViewItemEventArgs> itemDoubleClickEvent;
810 private Event<ListViewItemEventHandler, ListViewItemEventArgs> itemEnterEvent;
811 private Event<ListViewItemEventHandler, ListViewItemEventArgs> itemLeaveEvent;
812 private Event<ListViewColumnEventHandler, ListViewColumnEventArgs> columnWidthChangedEvent;
813 }
814
815 public class ListViewColumn
816 {
817 public ListViewColumn(ListView* view_, const string& name_, int width_) :
818 view(view_), name(name_), width(width_), minWidth(0)
819 {
820 }
821 public inline const string& Name() const
822 {
823 return name;
824 }
825 public inline int Width() const
826 {
827 return width;
828 }
829 [nodiscard]
830 public Result<bool> SetWidth(int width_)
831 {
832 if (width != width_)
833 {
834 width = width_;
835 view->FireColumnWidthChanged(this);
836 auto result = view->Invalidate();
837 if (result.Error()) return result;
838 }
839 return Result<bool>(true);
840 }
841 public inline int MinWidth() const
842 {
843 return minWidth;
844 }
845 public void SetMinWidth(int minWidth_)
846 {
847 minWidth = minWidth_;
848 }
849 [nodiscard]
850 public Result<bool> Draw(Graphics& graphics, const PointF& origin)
851 {
852 auto drawResult = graphics.DrawString(name, view->GetFont(), origin, view->ColumnHeaderTextBrush());
853 return drawResult;
854 }
855 private ListView* view;
856 private string name;
857 private int width;
858 private int minWidth;
859 }
860
861 public class ListViewColumnDivider
862 {
863 public ListViewColumnDivider(ListView* view_, ListViewColumn* column_) :
864 view(view_), column(column_), location(), startCapturePos(), startColumnWidth(0), hasMouseCapture(false)
865 {
866 }
867 public inline const Point& Location() const
868 {
869 return location;
870 }
871 public Size GetSize() const
872 {
873 return Size(cast<int>(view->ColumnDividerWidth() + 4 * view->ColumnDividerPadding().Horizontal() + 0.500000f), cast<int>(view->TextHeight() + 0.500000f));
874 }
875 public inline bool HasMouseCapture() const
876 {
877 return hasMouseCapture;
878 }
879 public void OnLButtonDown(const Point& pos)
880 {
881 WinSetCapture(view->Handle());
882 startCapturePos = pos;
883 startColumnWidth = column->Width();
884 hasMouseCapture = true;
885 }
886 [nodiscard]
887 public Result<bool> OnMouseMove(const Point& pos)
888 {
889 auto result = column->SetWidth(Max(column->MinWidth(), startColumnWidth + pos.x - startCapturePos.x));
890 if (result.Error()) return result;
891 return Result<bool>(true);
892 }
893 public void OnLButtonUp()
894 {
895 WinReleaseCapture();
896 hasMouseCapture = false;
897 }
898 [nodiscard]
899 public Result<bool> Draw(Graphics& graphics, const PointF& origin)
900 {
901 const Pen& pen = view->ColumnDividerPen();
902 Size sz = GetSize();
903 location = Point(cast<int>(origin.x - sz.w / 2), cast<int>(origin.y));
904 PointF end = origin;
905 end.y = end.y + view->TextHeight();
906 auto drawResult = graphics.DrawLine(pen, origin, end);
907 return drawResult;
908 }
909 private ListView* view;
910 private ListViewColumn* column;
911 private Point location;
912 private Point startCapturePos;
913 private int startColumnWidth;
914 private bool hasMouseCapture;
915 }
916
917 public class ListViewItem
918 {
919 public enum Flags : int
920 {
921 none = 0, disabled = 1 << 0, selected = 1 << 1
922 }
923 public enum State : int
924 {
925 enabled, disabled
926 }
927 public explicit ListViewItem(ListView* view_) : view(view_), flags(Flags.none), imageIndex(-1), disabledImageIndex(-1), data(null)
928 {
929 }
930 [nodiscard]
931 public Result<bool> SetColumnValue(int columnIndex, const string& columnValue)
932 {
933 if (columnIndex < 0)
934 {
935 int errorId = AllocateError("invalid column index");
936 return Result<bool>(ErrorId(errorId));
937 }
938 while (columnIndex >= columnValues.Count())
939 {
940 columnValues.Add(string());
941 }
942 columnValues[columnIndex] = columnValue;
943 return Result<bool>(true);
944 }
945 [nodiscard]
946 public Result<string> GetColumnValue(int columnIndex) const
947 {
948 if (columnIndex < 0)
949 {
950 int errorId = AllocateError("invalid column index");
951 return Result<string>(ErrorId(errorId));
952 }
953 if (columnIndex >= columnValues.Count())
954 {
955 return string();
956 }
957 return Result<string>(columnValues[columnIndex]);
958 }
959 [nodiscard]
960 public Result<bool> Draw(Graphics& graphics, PointF& origin)
961 {
962 if (IsSelected())
963 {
964 const Brush& selectedBrush = view->ItemSelectedBrush();
965 Rect rect(location, size);
966 auto result = graphics.FillRectangle(selectedBrush, rect);
967 if (result.Error())
968 {
969 return Result<bool>(ErrorId(result.GetErrorId()));
970 }
971 }
972 PointF itemOrigin = origin;
973 int imageSpace = 0;
974 auto result = DrawImage(graphics, itemOrigin, imageSpace);
975 if (result.Error())
976 {
977 return Result<bool>(ErrorId(result.GetErrorId()));
978 }
979 for (int index = 0; index < view->ColumnCount(); ++index;)
980 {
981 int imgSpc = 0;
982 if (index == 0)
983 {
984 imgSpc = imageSpace;
985 }
986 imgSpc = imageSpace;
987 bool clipped = false;
988 Region prevClip;
989 if (index < textWidths.Count())
990 {
991 auto getColumnResult = view->GetColumn(index);
992 if (getColumnResult.Error())
993 {
994 return Result<bool>(ErrorId(getColumnResult.GetErrorId()));
995 }
996 ListViewColumn* column = getColumnResult.Value();
997 if (textWidths[index] > column->Width())
998 {
999 auto clipResult = graphics.GetClip();
1000 if (clipResult.Error())
1001 {
1002 return Result<bool>(ErrorId(clipResult.GetErrorId()));
1003 }
1004 prevClip = clipResult.Value();
1005 clipped = true;
1006 auto columnResult = view->GetColumn(index);
1007 if (columnResult.Error())
1008 {
1009 return Result<bool>(ErrorId(columnResult.GetErrorId()));
1010 }
1011 ListViewColumn* column = columnResult.Value();
1012 Size itemSize(Size(cast<int>(column->Width() - view->EllipsisWidth() - imgSpc), cast<int>(view->TextHeight())));
1013 Rect clip(Point(cast<int>(itemOrigin.x), cast<int>(itemOrigin.y)), itemSize);
1014 auto setClipResult = graphics.SetClip(clip);
1015 if (setClipResult.Error())
1016 {
1017 return Result<bool>(ErrorId(setClipResult.GetErrorId()));
1018 }
1019 }
1020 }
1021 const Brush* brush = view->ItemTextBrush();
1022 if (GetState() == State.disabled)
1023 {
1024 brush = view->DisabledItemTextBrush();
1025 }
1026 auto columnValueResult = GetColumnValue(index);
1027 if (columnValueResult.Error())
1028 {
1029 return Result<bool>(ErrorId(columnValueResult.GetErrorId()));
1030 }
1031 string columnValue = Rvalue(columnValueResult.Value());
1032 auto result = graphics.DrawString(columnValue, view->GetFont(), itemOrigin, *brush);
1033 if (result.Error())
1034 {
1035 return Result<bool>(ErrorId(result.GetErrorId()));
1036 }
1037 auto columnResult = view->GetColumn(index);
1038 if (columnResult.Error())
1039 {
1040 return Result<bool>(ErrorId(columnResult.GetErrorId()));
1041 }
1042 ListViewColumn* column = columnResult.Value();
1043 itemOrigin.x = itemOrigin.x + column->Width();
1044 if (clipped)
1045 {
1046 auto setClipResult = graphics.SetClip(prevClip);
1047 if (setClipResult.Error())
1048 {
1049 return Result<bool>(ErrorId(setClipResult.GetErrorId()));
1050 }
1051 PointF ellipsisOrigin(itemOrigin.x - view->EllipsisWidth() - imgSpc, itemOrigin.y);
1052 auto result = graphics.DrawString("...", view->GetFont(), ellipsisOrigin, *brush);
1053 if (result.Error())
1054 {
1055 return Result<bool>(ErrorId(result.GetErrorId()));
1056 }
1057 }
1058 itemOrigin.x = itemOrigin.x + view->ColumnDividerPadding().Horizontal() + view->ColumnDividerWidth() - imgSpc;
1059 itemOrigin.x = itemOrigin.x + view->ItemColumnPadding().Horizontal();
1060 }
1061 return Result<bool>(true);
1062 }
1063 [nodiscard]
1064 public Result<bool> Measure(Graphics& graphics)
1065 {
1066 float width = 0;
1067 float height = 0;
1068 Bitmap* image = null;
1069 ImageList* imageList = view->GetImageList();
1070 if (imageList != null)
1071 {
1072 if (GetState() == State.enabled)
1073 {
1074 image = imageList->GetImage(imageIndex);
1075 }
1076 else
1077 {
1078 image = imageList->GetImage(disabledImageIndex);
1079 }
1080 }
1081 if (image != null)
1082 {
1083 int imageWidth = cast<int>(image->GetWidth());
1084 int imageHeight = cast<int>(image->GetHeight());
1085 Padding padding = view->ImagePadding();
1086 if (view->ColumnCount() > 0)
1087 {
1088 auto columnResult = view->GetColumn(0);
1089 if (columnResult.Error())
1090 {
1091 return Result<bool>(ErrorId(columnResult.GetErrorId()));
1092 }
1093 ListViewColumn* firstColumn = columnResult.Value();
1094 firstColumn->SetMinWidth(Max(firstColumn->MinWidth(), cast<int>(imageWidth + padding.Horizontal() + view->EllipsisWidth() + 0.500000f)));
1095 }
1096 height = imageHeight + padding.Vertical();
1097 }
1098 for (int index = 0; index < view->ColumnCount(); ++index;)
1099 {
1100 auto columnResult = view->GetColumn(index);
1101 if (columnResult.Error())
1102 {
1103 return Result<bool>(ErrorId(columnResult.GetErrorId()));
1104 }
1105 ListViewColumn* column = columnResult.Value();
1106 width = width + column->Width();
1107 width = width + view->ColumnDividerWidth() + view->ColumnDividerPadding().Horizontal();
1108 height = Max(height, view->TextHeight());
1109 column->SetMinWidth(Max(column->MinWidth(), cast<int>(view->EllipsisWidth() + 0.500000f)));
1110 }
1111 width = width + view->ItemPadding().Horizontal();
1112 size = Size(cast<int>(width + 0.500000f), cast<int>(height + 0.500000f));
1113 textWidths.Clear();
1114 for (const string& columnValue : columnValues)
1115 {
1116 auto measureResult = graphics.MeasureStringRectF(columnValue, view->GetFont(), PointF(0, 0), view->GetStringFormat());
1117 if (measureResult.Error())
1118 {
1119 return Result<bool>(ErrorId(measureResult.GetErrorId()));
1120 }
1121 RectF r = measureResult.Value();
1122 textWidths.Add(r.size.w);
1123 }
1124 return Result<bool>(true);
1125 }
1126 public inline const Point& Location() const
1127 {
1128 return location;
1129 }
1130 public void SetLocation(const Point& location_)
1131 {
1132 location = location_;
1133 }
1134 public inline const Size& GetSize() const
1135 {
1136 return size;
1137 }
1138 public inline bool GetFlag(Flags flag) const
1139 {
1140 return (flags & flag) != Flags.none;
1141 }
1142 public inline void SetFlag(Flags flag)
1143 {
1144 flags = cast<Flags>(flags | flag);
1145 }
1146 public inline void ResetFlag(Flags flag)
1147 {
1148 flags = cast<Flags>(flags & ~flag);
1149 }
1150 public inline bool IsSelected() const
1151 {
1152 return GetFlag(Flags.selected);
1153 }
1154 [nodiscard]
1155 public Result<bool> SetSelected()
1156 {
1157 if (!IsSelected())
1158 {
1159 SetFlag(Flags.selected);
1160 auto result = view->Invalidate();
1161 if (result.Error()) return result;
1162 }
1163 return Result<bool>(true);
1164 }
1165 [nodiscard]
1166 public Result<bool> ResetSelected()
1167 {
1168 if (IsSelected())
1169 {
1170 ResetFlag(Flags.selected);
1171 auto result = view->Invalidate();
1172 if (result.Error()) return result;
1173 }
1174 return Result<bool>(true);
1175 }
1176 public State GetState() const
1177 {
1178 if (GetFlag(Flags.disabled))
1179 {
1180 return State.disabled;
1181 }
1182 else
1183 {
1184 return State.enabled;
1185 }
1186 }
1187 [nodiscard]
1188 public Result<bool> SetState(State state)
1189 {
1190 if (state != GetState())
1191 {
1192 if (state == State.disabled)
1193 {
1194 SetFlag(Flags.disabled);
1195 }
1196 else
1197 {
1198 ResetFlag(Flags.disabled);
1199 }
1200 auto result = view->Invalidate();
1201 if (result.Error()) return result;
1202 }
1203 return Result<bool>(true);
1204 }
1205 public inline ListView* View() const
1206 {
1207 return view;
1208 }
1209 public inline void* Data() const
1210 {
1211 return data;
1212 }
1213 public void SetData(void* data_)
1214 {
1215 data = data_;
1216 }
1217 public inline int ImageIndex() const
1218 {
1219 return imageIndex;
1220 }
1221 public void SetImageIndex(int imageIndex_)
1222 {
1223 imageIndex = imageIndex_;
1224 }
1225 public inline int DisabledImageIndex() const
1226 {
1227 return disabledImageIndex;
1228 }
1229 public void SetDisabledImageIndex(int disabledImageIndex_)
1230 {
1231 disabledImageIndex = disabledImageIndex_;
1232 }
1233 private Result<bool> DrawImage(Graphics& graphics, PointF& origin, int& imageSpace)
1234 {
1235 imageSpace = 0;
1236 Bitmap* image = null;
1237 ImageList* imageList = view->GetImageList();
1238 if (imageList != null)
1239 {
1240 if (GetState() == State.enabled)
1241 {
1242 image = imageList->GetImage(imageIndex);
1243 }
1244 else
1245 {
1246 image = imageList->GetImage(disabledImageIndex);
1247 }
1248 }
1249 if (image != null)
1250 {
1251 int imageWidth = cast<int>(image->GetWidth());
1252 int imageHeight = cast<int>(image->GetHeight());
1253 Padding padding = view->ImagePadding();
1254 Point imageLoc = Point(cast<int>(origin.x), cast<int>(origin.y));
1255 imageLoc.x = imageLoc.x + padding.left;
1256 imageLoc.y = imageLoc.y + padding.top;
1257 Rect r(imageLoc, Size(imageWidth + padding.Horizontal(), imageHeight + padding.Vertical()));
1258 ImageAttributes attributes;
1259 if (attributes.Error())
1260 {
1261 return Result<bool>(ErrorId(attributes.GetErrorId()));
1262 }
1263 auto setResult = attributes.SetColorKey(Color.DefaultBitmapTransparent(), Color.DefaultBitmapTransparent(), ColorAdjustType.default_);
1264 if (setResult.Error())
1265 {
1266 return Result<bool>(ErrorId(setResult.GetErrorId()));
1267 }
1268 auto drawResult = graphics.DrawImage(*image, r, 0, 0, imageWidth + padding.Horizontal(), imageHeight + padding.Vertical(), Unit.pixel, attributes);
1269 if (drawResult.Error())
1270 {
1271 return Result<bool>(ErrorId(drawResult.GetErrorId()));
1272 }
1273 imageSpace = imageWidth + padding.Horizontal();
1274 origin.x = origin.x + imageSpace;
1275 }
1276 return Result<bool>(true);
1277 }
1278 private Flags flags;
1279 private Point location;
1280 private Size size;
1281 private ListView* view;
1282 private int imageIndex;
1283 private int disabledImageIndex;
1284 private void* data;
1285 private List<string> columnValues;
1286 private List<float> textWidths;
1287 }