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