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