1 // Written by Christopher E. Miller 2 // See the included license.txt for copyright and license details. 3 4 5 6 module dfl.button; 7 8 private import dfl.base, dfl.control, dfl.application, dfl.internal.winapi; 9 private import dfl.event, dfl.drawing, dfl.internal.dlib; 10 11 12 private extern(Windows) void _initButton(); 13 14 15 16 abstract class ButtonBase: ControlSuperClass { // docmain 17 18 @property void textAlign(ContentAlignment calign) { // setter 19 LONG wl = _bstyle() & ~(BS_BOTTOM | BS_CENTER | BS_TOP | BS_RIGHT | BS_LEFT | BS_VCENTER); 20 21 final switch(calign) { 22 case ContentAlignment.TOP_LEFT: 23 wl |= BS_TOP | BS_LEFT; 24 break; 25 26 case ContentAlignment.BOTTOM_CENTER: 27 wl |= BS_BOTTOM | BS_CENTER; 28 break; 29 30 case ContentAlignment.BOTTOM_LEFT: 31 wl |= BS_BOTTOM | BS_LEFT; 32 break; 33 34 case ContentAlignment.BOTTOM_RIGHT: 35 wl |= BS_BOTTOM | BS_RIGHT; 36 break; 37 38 case ContentAlignment.MIDDLE_CENTER: 39 wl |= BS_CENTER | BS_VCENTER; 40 break; 41 42 case ContentAlignment.MIDDLE_LEFT: 43 wl |= BS_VCENTER | BS_LEFT; 44 break; 45 46 case ContentAlignment.MIDDLE_RIGHT: 47 wl |= BS_VCENTER | BS_RIGHT; 48 break; 49 50 case ContentAlignment.TOP_CENTER: 51 wl |= BS_TOP | BS_CENTER; 52 break; 53 54 case ContentAlignment.TOP_RIGHT: 55 wl |= BS_TOP | BS_RIGHT; 56 break; 57 } 58 59 _bstyle(wl); 60 61 _crecreate(); 62 } 63 64 /// ditto 65 @property ContentAlignment textAlign() { // getter 66 LONG wl = _bstyle(); 67 68 if(wl & BS_VCENTER) { // Middle. 69 if(wl & BS_CENTER) { 70 return ContentAlignment.MIDDLE_CENTER; 71 } 72 if(wl & BS_RIGHT) { 73 return ContentAlignment.MIDDLE_RIGHT; 74 } 75 return ContentAlignment.MIDDLE_LEFT; 76 } else if(wl & BS_BOTTOM) { // Bottom. 77 if(wl & BS_CENTER) { 78 return ContentAlignment.BOTTOM_CENTER; 79 } 80 if(wl & BS_RIGHT) { 81 return ContentAlignment.BOTTOM_RIGHT; 82 } 83 return ContentAlignment.BOTTOM_LEFT; 84 } else { // Top. 85 if(wl & BS_CENTER) { 86 return ContentAlignment.TOP_CENTER; 87 } 88 if(wl & BS_RIGHT) { 89 return ContentAlignment.TOP_RIGHT; 90 } 91 return ContentAlignment.TOP_LEFT; 92 } 93 } 94 95 96 // Border stuff... 97 98 99 /+ 100 override void createHandle() { 101 if(isHandleCreated) { 102 return; 103 } 104 105 createClassHandle(BUTTON_CLASSNAME); 106 107 onHandleCreated(EventArgs.empty); 108 } 109 +/ 110 111 112 protected override void createParams(ref CreateParams cp) { 113 super.createParams(cp); 114 115 cp.className = BUTTON_CLASSNAME; 116 if(isdef) { 117 cp.menu = cast(HMENU)IDOK; 118 if(!(cp.style & WS_DISABLED)) { 119 cp.style |= BS_DEFPUSHBUTTON; 120 } 121 } else if(cp.style & WS_DISABLED) { 122 cp.style &= ~BS_DEFPUSHBUTTON; 123 } 124 } 125 126 127 protected override void prevWndProc(ref Message msg) { 128 //msg.result = CallWindowProcA(buttonPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam); 129 msg.result = dfl.internal.utf.callWindowProc(buttonPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam); 130 } 131 132 133 protected override void onReflectedMessage(ref Message m) { 134 super.onReflectedMessage(m); 135 136 switch(m.msg) { 137 case WM_COMMAND: 138 assert(cast(HWND)m.lParam == handle); 139 140 switch(HIWORD(m.wParam)) { 141 case BN_CLICKED: 142 onClick(EventArgs.empty); 143 break; 144 145 default: 146 } 147 break; 148 149 default: 150 } 151 } 152 153 154 protected override void wndProc(ref Message msg) { 155 switch(msg.msg) { 156 case WM_LBUTTONDOWN: 157 onMouseDown(new MouseEventArgs(MouseButtons.LEFT, 0, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0)); 158 break; 159 160 case WM_LBUTTONUP: 161 onMouseUp(new MouseEventArgs(MouseButtons.LEFT, 1, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0)); 162 break; 163 164 default: 165 super.wndProc(msg); 166 return; 167 } 168 prevWndProc(msg); 169 } 170 171 172 /+ 173 protected override void onHandleCreated(EventArgs ea) { 174 super.onHandleCreated(ea); 175 176 /+ 177 // Done in createParams() now. 178 if(isdef) { 179 SetWindowLongA(handle, GWL_ID, IDOK); 180 } 181 +/ 182 } 183 +/ 184 185 186 this() { 187 _initButton(); 188 189 wstyle |= WS_TABSTOP /+ | BS_NOTIFY +/; 190 ctrlStyle |= ControlStyles.SELECTABLE; 191 wclassStyle = buttonClassStyle; 192 } 193 194 195 protected: 196 197 198 final @property void isDefault(bool byes) { // setter 199 isdef = byes; 200 } 201 202 /// ditto 203 final @property bool isDefault() { // getter 204 //return (_bstyle() & BS_DEFPUSHBUTTON) == BS_DEFPUSHBUTTON; 205 //return GetDlgCtrlID(m.hWnd) == IDOK; 206 return isdef; 207 } 208 209 210 protected override bool processMnemonic(dchar charCode) { 211 if(canSelect) { 212 if(isMnemonic(charCode, text)) { 213 select(); 214 //Application.doEvents(); // ? 215 //performClick(); 216 onClick(EventArgs.empty); 217 return true; 218 } 219 } 220 return false; 221 } 222 223 224 225 override @property Size defaultSize() { // getter 226 return Size(75, 23); 227 } 228 229 230 private: 231 bool isdef = false; 232 233 234 package: 235 final: 236 // Automatically redraws button styles, unlike _style(). 237 // Don't use with regular window styles ? 238 void _bstyle(LONG newStyle) { 239 if(isHandleCreated) 240 //SendMessageA(handle, BM_SETSTYLE, LOWORD(newStyle), MAKELPARAM(TRUE, 0)); 241 { 242 SendMessageA(handle, BM_SETSTYLE, newStyle, MAKELPARAM(TRUE, 0)); 243 } 244 245 wstyle = newStyle; 246 //_style(newStyle); 247 } 248 249 250 LONG _bstyle() { 251 return _style(); 252 } 253 } 254 255 256 257 class Button: ButtonBase, IButtonControl { // docmain 258 this() { 259 } 260 261 262 263 @property DialogResult dialogResult() { // getter 264 return dresult; 265 } 266 267 /// ditto 268 @property void dialogResult(DialogResult dr) { // setter 269 dresult = dr; 270 } 271 272 273 274 // True if default button. 275 void notifyDefault(bool byes) { 276 isDefault = byes; 277 278 if(byes) { 279 if(enabled) { // Only show thick border if enabled. 280 _bstyle(_bstyle() | BS_DEFPUSHBUTTON); 281 } 282 } else { 283 _bstyle(_bstyle() & ~BS_DEFPUSHBUTTON); 284 } 285 } 286 287 288 289 void performClick() { 290 if(!enabled || !visible || !isHandleCreated) { // ? 291 return; // ? 292 } 293 294 // This is actually not so good because it sets focus to the control. 295 //SendMessageA(handle, BM_CLICK, 0, 0); // So that wndProc() gets it. 296 297 onClick(EventArgs.empty); 298 } 299 300 301 protected override void onClick(EventArgs ea) { 302 super.onClick(ea); 303 304 if(!(Application._compat & DflCompat.FORM_DIALOGRESULT_096)) { 305 if(DialogResult.NONE != this.dialogResult) { 306 auto xx = cast(IDialogResult)topLevelControl; 307 if(xx) { 308 xx.dialogResult = this.dialogResult; 309 } 310 } 311 } 312 } 313 314 315 protected override void wndProc(ref Message m) { 316 switch(m.msg) { 317 case WM_ENABLE: { 318 // Fixing the thick border of a default button when enabling and disabling it. 319 320 // To-do: check if correct implementation. 321 322 DWORD bst; 323 bst = _bstyle(); 324 if(bst & BS_DEFPUSHBUTTON) { 325 //_bstyle(bst); // Force the border to be updated. Only works when enabling. 326 if(!m.wParam) { 327 _bstyle(bst & ~BS_DEFPUSHBUTTON); 328 } 329 } else if(m.wParam) { 330 //if(GetDlgCtrlID(m.hWnd) == IDOK) 331 if(isdef) { 332 _bstyle(bst | BS_DEFPUSHBUTTON); 333 } 334 } 335 } 336 break; 337 338 default: 339 } 340 341 super.wndProc(m); 342 } 343 344 345 override @property void text(Dstring txt) { // setter 346 if(txt.length) { 347 assert(!this.image, "Button image with text not supported"); 348 } 349 350 super.text = txt; 351 } 352 353 alias Control.text text; // Overload. 354 355 356 357 final @property Image image() { // getter 358 return _img; 359 } 360 361 /// ditto 362 final @property void image(Image img) // setter 363 in { 364 if(img) { 365 assert(!this.text.length, "Button image with text not supported"); 366 } 367 } 368 body { 369 /+ 370 if(_picbm) { 371 _picbm.dispose(); 372 _picbm = null; 373 } 374 +/ 375 376 _img = null; // In case of exception. 377 LONG imgst = 0; 378 if(img) { 379 /+ 380 if(cast(Bitmap)img) { 381 imgst = BS_BITMAP; 382 } else if(cast(Icon)img) { 383 imgst = BS_ICON; 384 } else { 385 if(cast(Picture)img) { 386 _picbm = (cast(Picture)img).toBitmap(); 387 imgst = BS_BITMAP; 388 goto not_unsupported; 389 } 390 391 throw new DflException("Unsupported image format"); 392 not_unsupported: ; 393 } 394 +/ 395 switch(img._imgtype(null)) { 396 case 1: 397 imgst = BS_BITMAP; 398 break; 399 400 case 2: 401 imgst = BS_ICON; 402 break; 403 404 default: 405 throw new DflException("Unsupported image format"); 406 not_unsupported: ; 407 } 408 } 409 410 _img = img; 411 _style((_style() & ~(BS_BITMAP | BS_ICON)) | imgst); // Redrawn manually in setImg(). 412 if(img) { 413 if(isHandleCreated) { 414 setImg(imgst); 415 } 416 } 417 //_bstyle((_bstyle() & ~(BS_BITMAP | BS_ICON)) | imgst); 418 } 419 420 421 private void setImg(LONG bsImageStyle) 422 in { 423 assert(isHandleCreated); 424 } 425 body { 426 WPARAM wparam = 0; 427 LPARAM lparam = 0; 428 429 /+ 430 if(bsImageStyle & BS_BITMAP) { 431 wparam = IMAGE_BITMAP; 432 lparam = cast(LPARAM)(_picbm ? _picbm.handle : (cast(Bitmap)_img).handle); 433 } else if(bsImageStyle & BS_ICON) { 434 wparam = IMAGE_ICON; 435 lparam = cast(LPARAM)((cast(Icon)(_img)).handle); 436 } else 437 { 438 return; 439 } 440 +/ 441 if(!_img) { 442 return; 443 } 444 HGDIOBJ hgo; 445 switch(_img._imgtype(&hgo)) { 446 case 1: 447 wparam = IMAGE_BITMAP; 448 break; 449 450 case 2: 451 wparam = IMAGE_ICON; 452 break; 453 454 default: 455 return; 456 } 457 lparam = cast(LPARAM)hgo; 458 459 //assert(lparam); 460 SendMessageA(handle, BM_SETIMAGE, wparam, lparam); 461 invalidate(); 462 } 463 464 465 protected override void onHandleCreated(EventArgs ea) { 466 super.onHandleCreated(ea); 467 468 setImg(_bstyle()); 469 } 470 471 472 protected override void onHandleDestroyed(EventArgs ea) { 473 super.onHandleDestroyed(ea); 474 475 /+ 476 if(_picbm) { 477 _picbm.dispose(); 478 _picbm = null; 479 } 480 +/ 481 } 482 483 484 private: 485 DialogResult dresult = DialogResult.NONE; 486 Image _img = null; 487 //Bitmap _picbm = null; // If -_img- is a Picture, need to keep a separate Bitmap. 488 } 489 490 491 492 class CheckBox: ButtonBase { // docmain 493 494 final @property void appearance(Appearance ap) { // setter 495 final switch(ap) { 496 case Appearance.NORMAL: 497 _bstyle(_bstyle() & ~BS_PUSHLIKE); 498 break; 499 500 case Appearance.BUTTON: 501 _bstyle(_bstyle() | BS_PUSHLIKE); 502 break; 503 } 504 505 _crecreate(); 506 } 507 508 /// ditto 509 final @property Appearance appearance() { // getter 510 if(_bstyle() & BS_PUSHLIKE) { 511 return Appearance.BUTTON; 512 } 513 return Appearance.NORMAL; 514 } 515 516 517 518 final @property void autoCheck(bool byes) { // setter 519 if(byes) { 520 _bstyle((_bstyle() & ~BS_CHECKBOX) | BS_AUTOCHECKBOX); 521 } else { 522 _bstyle((_bstyle() & ~BS_AUTOCHECKBOX) | BS_CHECKBOX); 523 } 524 // Enabling/disabling the window before creation messes 525 // up the autocheck style flag, so handle it manually. 526 _autocheck = byes; 527 } 528 529 /// ditto 530 final @property bool autoCheck() { // getter 531 /+ 532 return (_bstyle() & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX; 533 +/ 534 return _autocheck; 535 } 536 537 538 this() { 539 wstyle |= BS_AUTOCHECKBOX | BS_LEFT | BS_VCENTER; // Auto check and MIDDLE_LEFT by default. 540 } 541 542 543 /+ 544 protected override void onClick(EventArgs ea) { 545 _updateState(); 546 547 super.onClick(ea); 548 } 549 +/ 550 551 552 553 final @property void checked(bool byes) { // setter 554 if(byes) { 555 _check = CheckState.CHECKED; 556 } else { 557 _check = CheckState.UNCHECKED; 558 } 559 560 if(isHandleCreated) { 561 SendMessageA(handle, BM_SETCHECK, cast(WPARAM)_check, 0); 562 } 563 } 564 565 /// ditto 566 // Returns true for indeterminate too. 567 final @property bool checked() { // getter 568 if(isHandleCreated) { 569 _updateState(); 570 } 571 return _check != CheckState.UNCHECKED; 572 } 573 574 575 576 final @property void checkState(CheckState st) { // setter 577 _check = st; 578 579 if(isHandleCreated) { 580 SendMessageA(handle, BM_SETCHECK, cast(WPARAM)st, 0); 581 } 582 } 583 584 /// ditto 585 final @property CheckState checkState() { // getter 586 if(isHandleCreated) { 587 _updateState(); 588 } 589 return _check; 590 } 591 592 593 protected override void onHandleCreated(EventArgs ea) { 594 super.onHandleCreated(ea); 595 596 if(_autocheck) { 597 _bstyle((_bstyle() & ~BS_CHECKBOX) | BS_AUTOCHECKBOX); 598 } else { 599 _bstyle((_bstyle() & ~BS_AUTOCHECKBOX) | BS_CHECKBOX); 600 } 601 602 SendMessageA(handle, BM_SETCHECK, cast(WPARAM)_check, 0); 603 } 604 605 606 private: 607 CheckState _check = CheckState.UNCHECKED; // Not always accurate. 608 bool _autocheck = true; 609 610 611 void _updateState() { 612 _check = cast(CheckState)SendMessageA(handle, BM_GETCHECK, 0, 0); 613 } 614 } 615 616 617 618 class RadioButton: ButtonBase { // docmain 619 620 final @property void appearance(Appearance ap) { // setter 621 final switch(ap) { 622 case Appearance.NORMAL: 623 _bstyle(_bstyle() & ~BS_PUSHLIKE); 624 break; 625 626 case Appearance.BUTTON: 627 _bstyle(_bstyle() | BS_PUSHLIKE); 628 break; 629 } 630 631 _crecreate(); 632 } 633 634 /// ditto 635 final @property Appearance appearance() { // getter 636 if(_bstyle() & BS_PUSHLIKE) { 637 return Appearance.BUTTON; 638 } 639 return Appearance.NORMAL; 640 } 641 642 643 644 final @property void autoCheck(bool byes) { // setter 645 /+ 646 if(byes) { 647 _bstyle((_bstyle() & ~BS_RADIOBUTTON) | BS_AUTORADIOBUTTON); 648 } else { 649 _bstyle((_bstyle() & ~BS_AUTORADIOBUTTON) | BS_RADIOBUTTON); 650 } 651 // Enabling/disabling the window before creation messes 652 // up the autocheck style flag, so handle it manually. 653 +/ 654 _autocheck = byes; 655 } 656 657 658 /// ditto 659 final @property bool autoCheck() { // getter 660 /+ // Also commented out when using BS_AUTORADIOBUTTON. 661 return (_bstyle() & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX; 662 +/ 663 return _autocheck; 664 } 665 666 667 this() { 668 wstyle &= ~WS_TABSTOP; 669 //wstyle |= BS_AUTORADIOBUTTON | BS_LEFT | BS_VCENTER; // MIDDLE_LEFT by default. 670 wstyle |= BS_RADIOBUTTON | BS_LEFT | BS_VCENTER; // MIDDLE_LEFT by default. 671 } 672 673 674 protected override void onClick(EventArgs ea) { 675 if(autoCheck) { 676 if(parent) { // Sanity. 677 foreach(Control ctrl; parent.controls) { 678 if(ctrl is this) { 679 continue; 680 } 681 if((ctrl._rtype() & (1 | 8)) == (1 | 8)) { // Radio button + auto check. 682 (cast(RadioButton)ctrl).checked = false; 683 } 684 } 685 } 686 checked = true; 687 } 688 689 super.onClick(ea); 690 } 691 692 693 /+ 694 protected override void onClick(EventArgs ea) { 695 _updateState(); 696 697 super.onClick(ea); 698 } 699 +/ 700 701 702 703 final @property void checked(bool byes) { // setter 704 if(byes) { 705 _check = CheckState.CHECKED; 706 } else { 707 _check = CheckState.UNCHECKED; 708 } 709 710 if(isHandleCreated) { 711 SendMessageA(handle, BM_SETCHECK, cast(WPARAM)_check, 0); 712 } 713 } 714 715 /// ditto 716 // Returns true for indeterminate too. 717 final @property bool checked() { // getter 718 if(isHandleCreated) { 719 _updateState(); 720 } 721 return _check != CheckState.UNCHECKED; 722 } 723 724 725 726 final @property void checkState(CheckState st) { // setter 727 _check = st; 728 729 if(isHandleCreated) { 730 SendMessageA(handle, BM_SETCHECK, cast(WPARAM)st, 0); 731 } 732 } 733 734 /// ditto 735 final @property CheckState checkState() { // getter 736 if(isHandleCreated) { 737 _updateState(); 738 } 739 return _check; 740 } 741 742 743 744 void performClick() { 745 //onClick(EventArgs.empty); 746 SendMessageA(handle, BM_CLICK, 0, 0); // So that wndProc() gets it. 747 } 748 749 750 protected override void onHandleCreated(EventArgs ea) { 751 super.onHandleCreated(ea); 752 753 /+ 754 if(_autocheck) { 755 _bstyle((_bstyle() & ~BS_RADIOBUTTON) | BS_AUTORADIOBUTTON); 756 } else { 757 _bstyle((_bstyle() & ~BS_AUTORADIOBUTTON) | BS_RADIOBUTTON); 758 } 759 +/ 760 761 SendMessageA(handle, BM_SETCHECK, cast(WPARAM)_check, 0); 762 } 763 764 765 /+ 766 protected override void onReflectedMessage(ref Message m) { 767 super.onReflectedMessage(m); 768 769 switch(m.msg) { 770 /+ 771 // Without this, with XP styles, the background just ends up transparent; not the requested color. 772 // This erases the text when XP styles aren't enabled. 773 case WM_CTLCOLORSTATIC: 774 case WM_CTLCOLORBTN: { 775 //if(hasVisualStyle) 776 { 777 RECT rect; 778 rect.right = width; 779 rect.bottom = height; 780 FillRect(cast(HDC)m.wParam, &rect, hbrBg); 781 } 782 } 783 break; 784 +/ 785 786 default: 787 } 788 } 789 +/ 790 791 792 /+ package +/ /+ protected +/ override int _rtype() { // package 793 if(autoCheck) { 794 return 1 | 8; // Radio button + auto check. 795 } 796 return 1; // Radio button. 797 } 798 799 800 private: 801 CheckState _check = CheckState.UNCHECKED; // Not always accurate. 802 bool _autocheck = true; 803 804 805 void _updateState() { 806 _check = cast(CheckState)SendMessageA(handle, BM_GETCHECK, 0, 0); 807 } 808 } 809