1 /** DGui project file. 2 3 Copyright: Trogu Antonio Davide 2011-2013 4 5 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 7 Authors: Trogu Antonio Davide 8 */ 9 module dgui.listview; 10 11 import std.utf: toUTF8; 12 public import dgui.core.controls.ownerdrawcontrol; 13 import dgui.core.utils; 14 import dgui.imagelist; 15 16 enum ColumnTextAlign: int 17 { 18 left = LVCFMT_LEFT, 19 center = LVCFMT_CENTER, 20 right = LVCFMT_RIGHT, 21 } 22 23 enum ViewStyle: uint 24 { 25 list = LVS_LIST, 26 report = LVS_REPORT, 27 largeIcon = LVS_ICON, 28 smallIcon = LVS_SMALLICON, 29 } 30 31 enum ListViewBits: ubyte 32 { 33 none = 0, 34 gridLines = 1, 35 fullRowSelect = 2, 36 checkBoxes = 4, 37 } 38 39 class ListViewItem 40 { 41 private Collection!(ListViewItem) _subItems; 42 private bool _checked = false; 43 private ListViewItem _parentItem; 44 private ListView _owner; 45 private string _text; 46 private int _imgIdx; 47 48 mixin tagProperty; 49 50 package this(ListView owner, string txt, int imgIdx, bool check) 51 { 52 this._checked = check; 53 this._imgIdx = imgIdx; 54 this._owner = owner; 55 this._text = txt; 56 } 57 58 package this(ListView owner, ListViewItem parentItem, string txt, int imgIdx, bool check) 59 { 60 this._parentItem = parentItem; 61 this(owner, txt, imgIdx, check); 62 } 63 64 @property public final int index() 65 { 66 if(this._owner) 67 { 68 foreach(int i, ListViewItem lvi; this._owner.items) 69 { 70 if(lvi is (this._parentItem ? this._parentItem : this)) 71 { 72 return i; 73 } 74 } 75 } 76 77 return -1; 78 } 79 80 @property public final int imageIndex() 81 { 82 return this._imgIdx; 83 } 84 85 @property public final void imageIndex(int imgIdx) 86 { 87 if(this._parentItem) 88 { 89 return; 90 } 91 92 this._imgIdx = imgIdx; 93 94 if(this._owner && this._owner.created) 95 { 96 LVITEMW lvi; 97 98 lvi.mask = LVIF_IMAGE; 99 lvi.iItem = this.index; 100 lvi.iSubItem = 0; 101 lvi.iImage = imgIdx; 102 103 this._owner.sendMessage(LVM_SETITEMW, 0, cast(LPARAM)&lvi); 104 } 105 } 106 107 @property public final string text() 108 { 109 return this._text; 110 } 111 112 @property public final void text(string s) 113 { 114 this._text = s; 115 116 if(this._owner && this._owner.created) 117 { 118 LVITEMW lvi; 119 120 lvi.mask = LVIF_TEXT; 121 lvi.iItem = this.index; 122 lvi.iSubItem = !this._parentItem ? 0 : this.subitemIndex; 123 lvi.pszText = toUTFz!(wchar*)(s); 124 125 this._owner.sendMessage(LVM_SETITEMW, 0, cast(LPARAM)&lvi); 126 } 127 } 128 129 @property package bool internalChecked() 130 { 131 return this._checked; 132 } 133 134 @property public final bool checked() 135 { 136 if(this._owner && this._owner.created) 137 { 138 return cast(bool)((this._owner.sendMessage(LVM_GETITEMSTATE, this.index, LVIS_STATEIMAGEMASK) >> 12) - 1); 139 } 140 141 return this._checked; 142 } 143 144 @property public final void checked(bool b) 145 { 146 if(this._parentItem) 147 { 148 return; 149 } 150 151 this._checked = b; 152 153 if(this._owner && this._owner.created) 154 { 155 LVITEMW lvi; 156 157 lvi.mask = LVIF_STATE; 158 lvi.stateMask = LVIS_STATEIMAGEMASK; 159 lvi.state = cast(LPARAM)(b ? 2 : 1) << 12; //Checked State 160 161 this._owner.sendMessage(LVM_SETITEMSTATE, this.index, cast(LPARAM)&lvi); 162 } 163 } 164 165 public final void addSubItem(string txt) 166 { 167 if(this._parentItem) //E' un subitem, non fare niente. 168 { 169 return; 170 } 171 172 if(!this._subItems) 173 { 174 this._subItems = new Collection!(ListViewItem)(); 175 } 176 177 ListViewItem lvi = new ListViewItem(this._owner, this, txt, -1, false); 178 this._subItems.add(lvi); 179 180 if(this._owner && this._owner.created) 181 { 182 ListView.insertItem(lvi, true); 183 } 184 } 185 186 @property public final ListViewItem[] subItems() 187 { 188 if(this._subItems) 189 { 190 return this._subItems.get(); 191 } 192 193 return null; 194 } 195 196 @property public final ListView listView() 197 { 198 return this._owner; 199 } 200 201 @property package ListViewItem parentItem() 202 { 203 return this._parentItem; 204 } 205 206 package void removeSubItem(int idx) 207 { 208 this._subItems.removeAt(idx); 209 } 210 211 @property package int subitemIndex() 212 { 213 if(this._parentItem is this) 214 { 215 return 0; //Se è l'item principale ritorna 0. 216 } 217 else if(!this._parentItem.subItems) 218 { 219 return 1; //E' il primo subitem 220 } 221 else if(this._owner && this._parentItem) 222 { 223 int i = 0; 224 225 foreach(ListViewItem lvi; this._parentItem.subItems) 226 { 227 if(lvi is this) 228 { 229 return i + 1; 230 } 231 232 i++; 233 } 234 } 235 236 return -1; //Non dovrebbe mai restituire -1 237 } 238 } 239 240 class ListViewColumn 241 { 242 private ColumnTextAlign _cta; 243 private ListView _owner; 244 private string _text; 245 private int _width; 246 247 package this(ListView owner, string txt, int w, ColumnTextAlign cta) 248 { 249 this._owner = owner; 250 this._text = txt; 251 this._width = w; 252 this._cta = cta; 253 } 254 255 @property public int index() 256 { 257 if(this._owner) 258 { 259 int i = 0; 260 261 foreach(ListViewColumn lvc; this._owner.columns) 262 { 263 if(lvc is this) 264 { 265 return i; 266 } 267 268 i++; 269 } 270 } 271 272 return -1; 273 } 274 275 @property public string text() 276 { 277 return this._text; 278 } 279 280 @property public int width() 281 { 282 return this._width; 283 } 284 285 @property public ColumnTextAlign textAlign() 286 { 287 return this._cta; 288 } 289 290 @property public ListView listView() 291 { 292 return this._owner; 293 } 294 } 295 296 public alias ItemEventArgs!(ListViewItem) ListViewItemCheckedEventArgs; 297 298 class ListView: OwnerDrawControl 299 { 300 public Event!(Control, EventArgs) itemChanged; 301 public Event!(Control, ListViewItemCheckedEventArgs) itemChecked; 302 303 private Collection!(ListViewColumn) _columns; 304 private Collection!(ListViewItem) _items; 305 private ListViewBits _lBits = ListViewBits.none; 306 private ListViewItem _selectedItem; 307 private ImageList _imgList; 308 309 @property public final ImageList imageList() 310 { 311 return this._imgList; 312 } 313 314 @property public final void imageList(ImageList imgList) 315 { 316 this._imgList = imgList; 317 318 if(this.created) 319 { 320 this.sendMessage(LVM_SETIMAGELIST, LVSIL_NORMAL, cast(LPARAM)imgList.handle); 321 this.sendMessage(LVM_SETIMAGELIST, LVSIL_SMALL, cast(LPARAM)imgList.handle); 322 } 323 } 324 325 @property public final ViewStyle viewStyle() 326 { 327 if(this.getStyle() & ViewStyle.largeIcon) 328 { 329 return ViewStyle.largeIcon; 330 } 331 else if(this.getStyle() & ViewStyle.smallIcon) 332 { 333 return ViewStyle.smallIcon; 334 } 335 else if(this.getStyle() & ViewStyle.list) 336 { 337 return ViewStyle.list; 338 } 339 else if(this.getStyle() & ViewStyle.report) 340 { 341 return ViewStyle.report; 342 } 343 344 assert(false, "Unknwown ListView Style"); 345 } 346 347 @property public final void viewStyle(ViewStyle vs) 348 { 349 /* Remove flickering in Report Mode */ 350 ListView.setBit(this._cBits, ControlBits.doubleBuffered, vs is ViewStyle.report); 351 352 this.setStyle(vs, true); 353 } 354 355 @property public final bool fullRowSelect() 356 { 357 return cast(bool)(this._lBits & ListViewBits.fullRowSelect); 358 } 359 360 @property public final void fullRowSelect(bool b) 361 { 362 this._lBits |= ListViewBits.fullRowSelect; 363 364 if(this.created) 365 { 366 this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, b ? LVS_EX_FULLROWSELECT : 0); 367 } 368 } 369 370 @property public final bool gridLines() 371 { 372 return cast(bool)(this._lBits & ListViewBits.gridLines); 373 } 374 375 @property public final void gridLines(bool b) 376 { 377 this._lBits |= ListViewBits.gridLines; 378 379 if(this.created) 380 { 381 this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_GRIDLINES, b ? LVS_EX_GRIDLINES : 0); 382 } 383 } 384 385 @property public final bool checkBoxes() 386 { 387 return cast(bool)(this._lBits & ListViewBits.checkBoxes); 388 } 389 390 @property public final void checkBoxes(bool b) 391 { 392 this._lBits |= ListViewBits.checkBoxes; 393 394 if(this.created) 395 { 396 this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_CHECKBOXES, b ? LVS_EX_CHECKBOXES : 0); 397 } 398 } 399 400 @property public final ListViewItem selectedItem() 401 { 402 return this._selectedItem; 403 } 404 405 public final ListViewColumn addColumn(string txt, int w, ColumnTextAlign cta = ColumnTextAlign.left) 406 { 407 if(!this._columns) 408 { 409 this._columns = new Collection!(ListViewColumn)(); 410 } 411 412 ListViewColumn lvc = new ListViewColumn(this, txt, w, cta); 413 this._columns.add(lvc); 414 415 if(this.created) 416 { 417 ListView.insertColumn(lvc); 418 } 419 420 return lvc; 421 } 422 423 public final void removeColumn(int idx) 424 { 425 this._columns.removeAt(idx); 426 427 /* 428 * Rimuovo tutti gli items nella colonna rimossa 429 */ 430 431 if(this._items) 432 { 433 if(idx) 434 { 435 foreach(ListViewItem lvi; this._items) 436 { 437 lvi.removeSubItem(idx - 1); //Subitems iniziano da 0 nelle DGui e da 1 su Windows. 438 } 439 } 440 else 441 { 442 //TODO: Gestire caso "Rimozione colonna 0". 443 } 444 } 445 446 if(this.created) 447 { 448 this.sendMessage(LVM_DELETECOLUMN, idx, 0); 449 } 450 } 451 452 public final ListViewItem addItem(string txt, int imgIdx = -1, bool checked = false) 453 { 454 if(!this._items) 455 { 456 this._items = new Collection!(ListViewItem)(); 457 } 458 459 ListViewItem lvi = new ListViewItem(this, txt, imgIdx, checked); 460 this._items.add(lvi); 461 462 if(this.created) 463 { 464 ListView.insertItem(lvi); 465 } 466 467 return lvi; 468 } 469 470 public final void removeItem(int idx) 471 { 472 if(this._items) 473 { 474 this._items.removeAt(idx); 475 } 476 477 if(this.created) 478 { 479 this.sendMessage(LVM_DELETEITEM, idx, 0); 480 } 481 } 482 483 public final void clear() 484 { 485 if(this._items) 486 { 487 this._items.clear(); 488 } 489 490 if(this.created) 491 { 492 this.sendMessage(LVM_DELETEALLITEMS, 0, 0); 493 } 494 } 495 496 @property public final Collection!(ListViewItem) items() 497 { 498 return this._items; 499 } 500 501 @property public final Collection!(ListViewColumn) columns() 502 { 503 return this._columns; 504 } 505 506 package static void insertItem(ListViewItem item, bool subitem = false) 507 { 508 /* 509 * Item: Item (or SubItem) to insert. 510 * Subitem = Is a SubItem? 511 */ 512 513 int idx = item.index; 514 LVITEMW lvi; 515 516 lvi.mask = LVIF_TEXT | (!subitem ? (LVIF_IMAGE | LVIF_STATE | LVIF_PARAM) : 0); 517 lvi.iImage = !subitem ? item.imageIndex : -1; 518 lvi.iItem = !subitem ? idx : item.parentItem.index; 519 lvi.iSubItem = !subitem ? 0 : item.subitemIndex; //ListView's subitem starts from 1 (0 is the main item). 520 lvi.pszText = toUTFz!(wchar*)(item.text); 521 lvi.lParam = winCast!(LPARAM)(item); 522 523 item.listView.sendMessage(!subitem ? LVM_INSERTITEMW : LVM_SETITEMW, 0, cast(LPARAM)&lvi); 524 525 if(!subitem) 526 { 527 if(item.listView.checkBoxes) //LVM_INSERTITEM doesn't handle CheckBoxes, use LVM_SETITEMSTATE 528 { 529 //Recycle the variable 'lvi' 530 531 lvi.mask = LVIF_STATE; 532 lvi.stateMask = LVIS_STATEIMAGEMASK; 533 lvi.state = cast(LPARAM)(item.internalChecked ? 2 : 1) << 12; //Checked State 534 item.listView.sendMessage(LVM_SETITEMSTATE, idx, cast(LPARAM)&lvi); 535 } 536 537 ListViewItem[] subItems = item.subItems; 538 539 if(subItems) 540 { 541 foreach(ListViewItem slvi; subItems) 542 { 543 ListView.insertItem(slvi, true); 544 } 545 } 546 } 547 } 548 549 private static void insertColumn(ListViewColumn col) 550 { 551 LVCOLUMNW lvc; 552 553 lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT; 554 lvc.cx = col.width; 555 lvc.fmt = col.textAlign; 556 lvc.pszText = toUTFz!(wchar*)(col.text); 557 558 col.listView.sendMessage(LVM_INSERTCOLUMNW, col.listView._columns.length, cast(LPARAM)&lvc); 559 } 560 561 protected override void createControlParams(ref CreateControlParams ccp) 562 { 563 this.setStyle(LVS_ALIGNLEFT | LVS_ALIGNTOP | LVS_AUTOARRANGE | LVS_SHAREIMAGELISTS, true); 564 565 /* WS_CLIPSIBLINGS | WS_CLIPCHILDREN: There is a SysHeader Component inside a list view in Report Mode */ 566 if(this.getStyle() & ViewStyle.report) 567 { 568 this.setStyle(WS_CLIPSIBLINGS | WS_CLIPCHILDREN, true); 569 } 570 571 ccp.superclassName = WC_LISTVIEW; 572 ccp.className = WC_DLISTVIEW; 573 ccp.defaultBackColor = SystemColors.colorWindow; 574 575 switch(this._drawMode) 576 { 577 case OwnerDrawMode.fixed: 578 this.setStyle(LVS_OWNERDRAWFIXED, true); 579 break; 580 581 case OwnerDrawMode.variable: 582 assert(false, "ListView: Owner Draw Variable Style not allowed"); 583 584 default: 585 break; 586 } 587 588 //ListView.setBit(this._cBits, ControlBits.ORIGINAL_PAINT, true); 589 super.createControlParams(ccp); 590 } 591 592 protected override void onReflectedMessage(ref Message m) 593 { 594 switch(m.msg) 595 { 596 case WM_NOTIFY: 597 { 598 NMLISTVIEW* pNotify = cast(NMLISTVIEW*)m.lParam; 599 600 if(pNotify && pNotify.iItem != -1) 601 { 602 switch(pNotify.hdr.code) 603 { 604 case LVN_ITEMCHANGED: 605 { 606 if(pNotify.uChanged & LVIF_STATE) 607 { 608 uint changedState = pNotify.uNewState ^ pNotify.uOldState; 609 610 if(pNotify.uNewState & LVIS_SELECTED) 611 { 612 this._selectedItem = this._items[pNotify.iItem]; 613 this.onSelectedItemChanged(EventArgs.empty); 614 } 615 616 if((changedState & 0x2000) || (changedState & 0x1000)) /* IF Checked || Unchecked THEN */ 617 { 618 scope ListViewItemCheckedEventArgs e = new ListViewItemCheckedEventArgs(this._items[pNotify.iItem]); 619 this.onItemChecked(e); 620 } 621 } 622 } 623 break; 624 625 default: 626 break; 627 } 628 } 629 } 630 break; 631 632 default: 633 break; 634 } 635 636 super.onReflectedMessage(m); 637 } 638 639 protected override void onHandleCreated(EventArgs e) 640 { 641 if(this._lBits & ListViewBits.gridLines) 642 { 643 this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_GRIDLINES, LVS_EX_GRIDLINES); 644 } 645 646 if(this._lBits & ListViewBits.fullRowSelect) 647 { 648 this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); 649 } 650 651 if(this._lBits & ListViewBits.checkBoxes) 652 { 653 this.sendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES); 654 } 655 656 if(this._imgList) 657 { 658 this.sendMessage(LVM_SETIMAGELIST, LVSIL_NORMAL, cast(LPARAM)this._imgList.handle); 659 this.sendMessage(LVM_SETIMAGELIST, LVSIL_SMALL, cast(LPARAM)this._imgList.handle); 660 } 661 662 if(this.getStyle() & ViewStyle.report) 663 { 664 if(this._columns) 665 { 666 foreach(ListViewColumn lvc; this._columns) 667 { 668 ListView.insertColumn(lvc); 669 } 670 } 671 672 /* Remove flickering in Report Mode */ 673 ListView.setBit(this._cBits, ControlBits.doubleBuffered, true); 674 } 675 676 if(this._items) 677 { 678 foreach(ListViewItem lvi; this._items) 679 { 680 ListView.insertItem(lvi); 681 } 682 } 683 684 super.onHandleCreated(e); 685 } 686 687 protected void onSelectedItemChanged(EventArgs e) 688 { 689 this.itemChanged(this, e); 690 } 691 692 protected void onItemChecked(ListViewItemCheckedEventArgs e) 693 { 694 this.itemChecked(this, e); 695 } 696 }