// Written by Christopher E. Miller // See the included license.txt for copyright and license details. /// module dfl.control; private import dfl.internal.dlib, dfl.internal.clib; private import dfl.base, dfl.form, dfl.drawing; private import dfl.internal.winapi, dfl.application, dfl.event, dfl.label; private import dfl.internal.wincom, dfl.internal.utf, dfl.collections, dfl.internal.com; private import core.memory; version(NO_DRAG_DROP) version = DFL_NO_DRAG_DROP; version(DFL_NO_DRAG_DROP) { } else { private import dfl.data; } version(DFL_NO_MENUS) { } else { private import dfl.menu; } //version = RADIO_GROUP_LAYOUT; version = DFL_NO_ZOMBIE_FORM; /// enum AnchorStyles: ubyte { NONE = 0, /// TOP = 1, /// ditto BOTTOM = 2, /// ditto LEFT = 4, /// ditto RIGHT = 8, /// ditto /+ // Extras: VERTICAL = TOP | BOTTOM, HORIZONTAL = LEFT | RIGHT, ALL = TOP | BOTTOM | LEFT | RIGHT, DEFAULT = TOP | LEFT, TOP_LEFT = TOP | LEFT, TOP_RIGHT = TOP | RIGHT, BOTTOM_LEFT = BOTTOM | LEFT, BOTTOM_RIGHT = BOTTOM | RIGHT, +/ } /// Flags for setting control bounds. enum BoundsSpecified: ubyte { NONE = 0, /// X = 1, /// ditto Y = 2, /// ditto LOCATION = 1 | 2, /// ditto WIDTH = 4, /// ditto HEIGHT = 8, /// ditto SIZE = 4 | 8, /// ditto ALL = 1 | 2 | 4 | 8, /// ditto } /// Layout docking style. enum DockStyle: ubyte { NONE, /// BOTTOM, /// FILL, /// LEFT, /// RIGHT, /// TOP, /// } private { struct GetZIndex { Control find; int index = -1; private int _tmp = 0; } extern(Windows) BOOL getZIndexCallback(HWND hwnd, LPARAM lparam) { GetZIndex* gzi = cast(GetZIndex*)lparam; if(hwnd == gzi.find.hwnd) { gzi.index = gzi._tmp; return FALSE; // Stop, found it. } Control ctrl; ctrl = Control.fromHandle(hwnd); if(ctrl && ctrl.parent is gzi.find.parent) { gzi._tmp++; } return TRUE; // Keep looking. } } /// Effect flags for drag/drop operations. enum DragDropEffects: DWORD { NONE = 0, /// COPY = 1, /// ditto MOVE = 2, /// ditto LINK = 4, /// ditto SCROLL = 0x80000000, /// ditto ALL = COPY | MOVE | LINK | SCROLL, /// ditto } /// Drag/drop action. enum DragAction: HRESULT { CONTINUE = S_OK, /// CANCEL = DRAGDROP_S_CANCEL, /// ditto DROP = DRAGDROP_S_DROP, /// ditto } // Flags. deprecated enum UICues: uint { NONE = 0, SHOW_FOCUS = 1, SHOW_KEYBOARD = 2, SHOWN = SHOW_FOCUS | SHOW_KEYBOARD, CHANGE_FOCUS = 4, CHANGE_KEYBOARD = 8, // Key mnemonic underline cues are on. CHANGED = CHANGE_FOCUS | CHANGE_KEYBOARD, } // May be OR'ed together. /// Style flags of a control. enum ControlStyles: uint { NONE = 0, /// CONTAINER_CONTROL = 0x1, /// ditto // TODO: implement. USER_PAINT = 0x2, /// ditto OPAQUE = 0x4, /// ditto RESIZE_REDRAW = 0x10, /// ditto //FIXED_WIDTH = 0x20, // TODO: implement. //FIXED_HEIGHT = 0x40, // TODO: implement. STANDARD_CLICK = 0x100, /// ditto SELECTABLE = 0x200, /// ditto // TODO: implement. USER_MOUSE = 0x400, /// ditto //SUPPORTS_TRANSPARENT_BACK_COLOR = 0x800, // Only if USER_PAINT and parent is derived from Control. TODO: implement. STANDARD_DOUBLE_CLICK = 0x1000, /// ditto ALL_PAINTING_IN_WM_PAINT = 0x2000, /// ditto CACHE_TEXT = 0x4000, /// ditto ENABLE_NOTIFY_MESSAGE = 0x8000, // deprecated. Calls onNotifyMessage() for every message. //DOUBLE_BUFFER = 0x10000, // TODO: implement. WANT_TAB_KEY = 0x01000000, WANT_ALL_KEYS = 0x02000000, } /// Control creation parameters. struct CreateParams { Dstring className; /// Dstring caption; /// ditto void* param; /// ditto HWND parent; /// ditto HMENU menu; /// ditto HINSTANCE inst; /// ditto int x; /// ditto int y; /// ditto int width; /// ditto int height; /// ditto DWORD classStyle; /// ditto DWORD exStyle; /// ditto DWORD style; /// ditto } deprecated class UICuesEventArgs: EventArgs { deprecated: this(UICues uic) { chg = uic; } final UICues changed() // getter { return chg; } final bool changeFocus() { return (chg & UICues.CHANGE_FOCUS) != 0; } final bool changeKeyboard() { return (chg & UICues.CHANGE_KEYBOARD) != 0; } final bool showFocus() { return (chg & UICues.SHOW_FOCUS) != 0; } final bool showKeyboard() { return (chg & UICues.SHOW_KEYBOARD) != 0; } private: UICues chg; } /// class ControlEventArgs: EventArgs { /// this(Control ctrl) { this.ctrl = ctrl; } /// final @property Control control() // getter { return ctrl; } private: Control ctrl; } /// class HelpEventArgs: EventArgs { /// this(Point mousePos) { mpos = mousePos; } /// final @property void handled(bool byes) // setter { hand = byes; } /// ditto final @property bool handled() // getter { return hand; } /// final @property Point mousePos() // getter { return mpos; } private: Point mpos; bool hand = false; } /// class InvalidateEventArgs: EventArgs { /// this(Rect invalidRect) { ir = invalidRect; } /// final @property Rect invalidRect() // getter { return ir; } private: Rect ir; } // /// // New dimensions before resizing. deprecated class BeforeResizeEventArgs: EventArgs { deprecated: /// this(int width, int height) { this.w = width; this.h = height; } /// void width(int cx) // setter { w = cx; } /// ditto int width() // getter { return w; } /// void height(int cy) // setter { h = cy; } /// ditto int height() // getter { return h; } private: int w, h; } /// class LayoutEventArgs: EventArgs { /// this(Control affectedControl) { ac = affectedControl; } /// final @property Control affectedControl() // getter { return ac; } private: Control ac; } version(DFL_NO_DRAG_DROP) {} else { /// class DragEventArgs: EventArgs { /// this(dfl.data.IDataObject dataObj, int keyState, int x, int y, DragDropEffects allowedEffect, DragDropEffects effect) { _dobj = dataObj; _keyState = keyState; _x = x; _y = y; _allowedEffect = allowedEffect; _effect = effect; } /// final @property DragDropEffects allowedEffect() // getter { return _allowedEffect; } /// final @property void effect(DragDropEffects newEffect) // setter { _effect = newEffect; } /// ditto final @property DragDropEffects effect() // getter { return _effect; } /// final @property dfl.data.IDataObject data() // getter { return _dobj; } /// // State of ctrl, alt, shift, and mouse buttons. final @property int keyState() // getter { return _keyState; } /// final @property int x() // getter { return _x; } /// final @property int y() // getter { return _y; } private: dfl.data.IDataObject _dobj; int _keyState; int _x, _y; DragDropEffects _allowedEffect, _effect; } /// class GiveFeedbackEventArgs: EventArgs { /// this(DragDropEffects effect, bool useDefaultCursors) { _effect = effect; udefcurs = useDefaultCursors; } /// final @property DragDropEffects effect() // getter { return _effect; } /// final @property void useDefaultCursors(bool byes) // setter { udefcurs = byes; } /// ditto final @property bool useDefaultCursors() // getter { return udefcurs; } private: DragDropEffects _effect; bool udefcurs; } /// class QueryContinueDragEventArgs: EventArgs { /// this(int keyState, bool escapePressed, DragAction action) { _keyState = keyState; escp = escapePressed; _action = action; } /// final @property void action(DragAction newAction) // setter { _action = newAction; } /// ditto final @property DragAction action() // getter { return _action; } /// final @property bool escapePressed() // getter { return escp; } /// // State of ctrl, alt and shift. final @property int keyState() // getter { return _keyState; } private: int _keyState; bool escp; DragAction _action; } } version(NO_WINDOWS_HUNG_WORKAROUND) { } else { version = WINDOWS_HUNG_WORKAROUND; } debug { version=_DFL_WINDOWS_HUNG_WORKAROUND; } version(WINDOWS_HUNG_WORKAROUND) { version=_DFL_WINDOWS_HUNG_WORKAROUND; } version(_DFL_WINDOWS_HUNG_WORKAROUND) { class WindowsHungDflException: DflException { this(Dstring msg) { super(msg); } } } alias BOOL delegate(HWND) EnumWindowsCallback; package struct EnumWindowsCallbackData { EnumWindowsCallback callback; DThrowable exception; } // Callback for EnumWindows() and EnumChildWindows(). private extern(Windows) BOOL enumingWindows(HWND hwnd, LPARAM lparam) nothrow { auto cbd = *(cast(EnumWindowsCallbackData*)lparam); try { return cbd.callback(hwnd); } catch (DThrowable e) { cbd.exception = e; return FALSE; } assert(0); } private struct Efi { HWND hwParent; EnumWindowsCallbackData cbd; } // Callback for EnumChildWindows(). -lparam- = pointer to Efi; private extern(Windows) BOOL enumingFirstWindows(HWND hwnd, LPARAM lparam) nothrow { auto efi = cast(Efi*)lparam; if(efi.hwParent == GetParent(hwnd)) { try { return efi.cbd.callback(hwnd); } catch (DThrowable e) { efi.cbd.exception = e; return FALSE; } } return TRUE; } package BOOL enumWindows(EnumWindowsCallback dg) { EnumWindowsCallbackData cbd; cbd.callback = dg; scope (exit) if (cbd.exception) throw cbd.exception; static assert((&cbd).sizeof <= LPARAM.sizeof); return EnumWindows(&enumingWindows, cast(LPARAM)&cbd); } package BOOL enumChildWindows(HWND hwParent, EnumWindowsCallback dg) { EnumWindowsCallbackData cbd; cbd.callback = dg; scope (exit) if (cbd.exception) throw cbd.exception; static assert((&cbd).sizeof <= LPARAM.sizeof); return EnumChildWindows(hwParent, &enumingWindows, cast(LPARAM)&cbd); } // Only the parent's children, not its children. package BOOL enumFirstChildWindows(HWND hwParent, EnumWindowsCallback dg) { Efi efi; efi.hwParent = hwParent; efi.cbd.callback = dg; scope (exit) if (efi.cbd.exception) throw efi.cbd.exception; return EnumChildWindows(hwParent, &enumingFirstWindows, cast(LPARAM)&efi); } /// enum ControlFont: ubyte { COMPATIBLE, /// OLD, /// ditto NATIVE, /// ditto } debug { import std.string; } /// Control class. class Control: DObject, IWindow // docmain { /// static class ControlCollection { protected this(Control owner) { _owner = owner; } deprecated alias length count; /// @property int length() // getter { if(_owner.isHandleCreated) { // Inefficient :( uint len = 0; foreach(Control ctrl; this) { len++; } return len; } else { return children.length.toI32; } } /// @property Control opIndex(int i) // getter { if(_owner.isHandleCreated) { int oni = 0; foreach(Control ctrl; this) { if(oni == i) return ctrl; oni++; } // Index out of bounds, bad things happen. assert(0); } else { return children[i]; } } /// void add(Control ctrl) { ctrl.parent = _owner; } /// // opIn ? bool contains(Control ctrl) { return indexOf(ctrl) != -1; } /// int indexOf(Control ctrl) { if(_owner.isHandleCreated) { int i = 0; int foundi = -1; BOOL enuming(HWND hwnd) { if(hwnd == ctrl.handle) { foundi = i; return false; // Stop. } i++; return true; // Continue. } enumFirstChildWindows(_owner.handle, &enuming); return foundi; } else { foreach(int i, Control onCtrl; children) { if(onCtrl == ctrl) return i; } return -1; } } /// void remove(Control ctrl) { if(_owner.isHandleCreated) { _removeCreated(ctrl.handle); } else { int i = indexOf(ctrl); if(i != -1) _removeNotCreated(i); } } private void _removeCreated(HWND hwnd) { DestroyWindow(hwnd); // ? } package void _removeNotCreated(int i) { if(!i) children = children[1 .. children.length]; else if(i == children.length - 1) children = children[0 .. i]; else children = children[0 .. i] ~ children[i + 1 .. children.length]; } /// void removeAt(int i) { if(_owner.isHandleCreated) { int ith = 0; HWND hwndith; BOOL enuming(HWND hwnd) { if(ith == i) { hwndith = hwnd; return false; // Stop. } ith++; return true; // Continue. } enumFirstChildWindows(_owner.handle, &enuming); if(hwndith) _removeCreated(hwndith); } else { _removeNotCreated(i); } } protected final @property Control owner() // getter { return _owner; } /// int opApply(int delegate(ref Control) dg) { int result = 0; if(_owner.isHandleCreated) { BOOL enuming(HWND hwnd) { Control ctrl = fromHandle(hwnd); if(ctrl) { result = dg(ctrl); if(result) return false; // Stop. } return true; // Continue. } enumFirstChildWindows(_owner.handle, &enuming); } else { foreach(Control ctrl; children) { result = dg(ctrl); if(result) break; } } return result; } mixin OpApplyAddIndex!(opApply, Control); package: Control _owner; Control[] children; // Only valid if -owner- isn't created yet (or is recreating). /+ final void _array_swap(int ifrom, int ito) { if(ifrom == ito || ifrom < 0 || ito < 0 || ifrom >= length || ito >= length) return; Control cto; cto = children[ito]; children[ito] = children[ifrom]; children[ifrom] = cto; } +/ final void _simple_front_one(int i) { if(i < 0 || i >= length - 1) return; children = children[0 .. i] ~ children[i + 1 .. i + 2] ~ children[i .. i + 1] ~ children[i + 2 .. children.length]; } final void _simple_front_one(Control c) { return _simple_front_one(indexOf(c)); } final void _simple_back_one(int i) { if(i <= 0 || i >= length) return; children = children[0 .. i - 1] ~ children[i + 1 .. i + 2] ~ children[i .. i + 1] ~ children[i + 2 .. children.length]; } final void _simple_back_one(Control c) { return _simple_back_one(indexOf(c)); } final void _simple_back(int i) { if(i <= 0 || i >= length) return; children = children[i .. i + 1] ~ children[0 .. i] ~ children[i + 1 .. children.length]; } final void _simple_back(Control c) { return _simple_back(indexOf(c)); } final void _simple_front(int i) { if(i < 0 || i >= length - 1) return; children = children[0 .. i] ~ children[i + 1 .. children.length] ~ children[i .. i + 1]; } final void _simple_front(Control c) { return _simple_front(indexOf(c)); } } private void _ctrladded(ControlEventArgs cea) { if(Application._compat & DflCompat.CONTROL_PARENT_096) { if(!(_exStyle() & WS_EX_CONTROLPARENT)) { if(!(cbits & CBits.FORM)) { //if((cea.control._style() & WS_TABSTOP) || (cea.control._exStyle() & WS_EX_CONTROLPARENT)) _exStyle(_exStyle() | WS_EX_CONTROLPARENT); } } } else { assert(getStyle(ControlStyles.CONTAINER_CONTROL), "Control added to non-container parent"); } onControlAdded(cea); } private void _ctrlremoved(ControlEventArgs cea) { alayout(cea.control); onControlRemoved(cea); } /// protected void onControlAdded(ControlEventArgs cea) { controlAdded(this, cea); } /// protected void onControlRemoved(ControlEventArgs cea) { controlRemoved(this, cea); } /// @property final HWindow handle() // IWindow getter { if(!isHandleCreated) { debug(APP_PRINT) cprintf("Control created due to handle request.\n"); createHandle(); } return hwnd; } version(DFL_NO_DRAG_DROP) {} else { /// @property void allowDrop(bool byes) // setter { /+ if(dyes) _exStyle(_exStyle() | WS_EX_ACCEPTFILES); else _exStyle(_exStyle() & ~WS_EX_ACCEPTFILES); +/ if(byes) { if(!droptarget) { droptarget = new DropTarget(this); if(isHandleCreated) { switch(RegisterDragDrop(hwnd, droptarget)) { case S_OK: case DRAGDROP_E_ALREADYREGISTERED: // Hmm. break; default: droptarget = null; throw new DflException("Unable to register drag-drop"); } } } } else { destroy(droptarget); // delete is deprecated. RevokeDragDrop(hwnd); } } /// ditto @property bool allowDrop() // getter { /+ return (_exStyle() & WS_EX_ACCEPTFILES) != 0; +/ return droptarget !is null; } } /+ deprecated void anchor(AnchorStyles a) // setter { /+ anch = a; if(!(anch & (AnchorStyles.LEFT | AnchorStyles.RIGHT))) anch |= AnchorStyles.LEFT; if(!(anch & (AnchorStyles.TOP | AnchorStyles.BOTTOM))) anch |= AnchorStyles.TOP; +/ sdock = DockStyle.NONE; // Can't be set at the same time. } deprecated AnchorStyles anchor() // getter { //return anch; return cast(AnchorStyles)(AnchorStyles.LEFT | AnchorStyles.TOP); } +/ private void _propagateBackColorAmbience() { Color bc; bc = backColor; void pa(Control pc) { foreach(Control ctrl; pc.ccollection) { if(Color.empty == ctrl.backc) // If default. { if(bc == ctrl.backColor) // If same default. { ctrl.deleteThisBackgroundBrush(); // Needs to be recreated with new color. ctrl.onBackColorChanged(EventArgs.empty); pa(ctrl); // Recursive. } } } } pa(this); } /// protected void onBackColorChanged(EventArgs ea) { debug(EVENT_PRINT) { cprintf("{ Event: onBackColorChanged - Control %.*s }\n", name); } backColorChanged(this, ea); } /// @property void backColor(Color c) // setter { if(backc == c) return; deleteThisBackgroundBrush(); // Needs to be recreated with new color. backc = c; onBackColorChanged(EventArgs.empty); _propagateBackColorAmbience(); if(isHandleCreated) invalidate(true); // Redraw! } /// ditto @property Color backColor() // getter { if(Color.empty == backc) { if(parent) { return parent.backColor; } return defaultBackColor; } return backc; } /// final @property int bottom() // getter { return wrect.bottom; } /// final @property void bounds(Rect r) // setter { setBoundsCore(r.x, r.y, r.width, r.height, BoundsSpecified.ALL); } /// ditto final @property Rect bounds() // getter { return wrect; } /+ final @property Rect originalBounds() // getter package { return oldwrect; } +/ /// protected void setBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) { // Make sure at least one flag is set. //if(!(specified & BoundsSpecified.ALL)) if(!specified) return; if(isHandleCreated) { UINT swpf = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE; if(specified & BoundsSpecified.X) { if(!(specified & BoundsSpecified.Y)) y = this.top(); swpf &= ~SWP_NOMOVE; } else if(specified & BoundsSpecified.Y) { x = this.left(); swpf &= ~SWP_NOMOVE; } if(specified & BoundsSpecified.WIDTH) { if(!(specified & BoundsSpecified.HEIGHT)) height = this.height(); swpf &= ~SWP_NOSIZE; } else if(specified & BoundsSpecified.HEIGHT) { width = this.width(); swpf &= ~SWP_NOSIZE; } SetWindowPos(hwnd, HWND.init, x, y, width, height, swpf); // Window events will update -wrect-. } else { if(specified & BoundsSpecified.X) wrect.x = x; if(specified & BoundsSpecified.Y) wrect.y = y; if(specified & BoundsSpecified.WIDTH) { if(width < 0) width = 0; wrect.width = width; wclientsz.width = width; } if(specified & BoundsSpecified.HEIGHT) { if(height < 0) height = 0; wrect.height = height; wclientsz.height = height; } //oldwrect = wrect; } } /// final @property bool canFocus() // getter { /+ LONG wl = _style(); return /+ hwnd && +/ (wl & WS_VISIBLE) && !(wl & WS_DISABLED); +/ //return visible && enabled; // Don't need to check -isHandleCreated- because IsWindowVisible() will fail from a null HWND. return /+ isHandleCreated && +/ IsWindowVisible(hwnd) && IsWindowEnabled(hwnd); } /// final @property bool canSelect() // getter out(result) { if(result) { assert(isHandleCreated); } } do { // All parent controls need to be visible and enabled, too. // Don't need to check -isHandleCreated- because IsWindowVisible() will fail from a null HWND. return /+ isHandleCreated && +/ (ctrlStyle & ControlStyles.SELECTABLE) && IsWindowVisible(hwnd) && IsWindowEnabled(hwnd); } package final bool _hasSelStyle() { return getStyle(ControlStyles.SELECTABLE); } /// // Returns true if this control has the mouse capture. final @property bool capture() // getter { return isHandleCreated && hwnd == GetCapture(); } /// ditto final @property void capture(bool cyes) // setter { if(cyes) SetCapture(hwnd); else ReleaseCapture(); } // When true, validating and validated events are fired when the control // receives focus. Typically set to false for controls such as a Help button. // Default is true. deprecated final bool causesValidation() // getter { //return cvalidation; return false; } deprecated protected void onCausesValidationChanged(EventArgs ea) { //causesValidationChanged(this, ea); } deprecated final void causesValidation(bool vyes) // setter { /+ if(cvalidation == vyes) return; cvalidation = vyes; onCausesValidationChanged(EventArgs.empty); +/ } /// final @property Rect clientRectangle() // getter { return Rect(Point(0, 0), wclientsz); } /// final bool contains(Control ctrl) { //return ccollection.contains(ctrl); return ctrl && ctrl.parent is this; } /// final @property Size clientSize() // getter { return wclientsz; } /// ditto final @property void clientSize(Size sz) // setter { setClientSizeCore(sz.width, sz.height); } /// protected void setClientSizeCore(int width, int height) { /+ if(isHandleCreated) setBoundsCore(0, 0, width, height, BoundsSpecified.SIZE); //wclientsz = Size(width, height); +/ RECT r; r.left = 0; r.top = 0; r.right = width; r.bottom = height; AdjustWindowRectEx(&r, _style(), FALSE, _exStyle()); setBoundsCore(0, 0, r.right - r.left, r.bottom - r.top, BoundsSpecified.SIZE); } /// // This window or one of its children has focus. final @property bool containsFocus() // getter { if(!isHandleCreated) return false; HWND hwfocus = GetFocus(); return hwfocus == hwnd || IsChild(hwnd, hwfocus); } version(DFL_NO_MENUS) { } else { /// protected void onContextMenuChanged(EventArgs ea) { contextMenuChanged(this, ea); } /// @property void contextMenu(ContextMenu menu) // setter { if(cmenu is menu) return; cmenu = menu; if(isHandleCreated) { onContextMenuChanged(EventArgs.empty); } } /// ditto @property ContextMenu contextMenu() // getter { return cmenu; } } /// final @property ControlCollection controls() // getter { //return new ControlCollection(this); return ccollection; } /// final @property bool created() // getter { // To-do: only return true when createHandle finishes. // Will also need to update uses of created/isHandleCreated. // Return false again when disposing/killing. //return isHandleCreated; return isHandleCreated || recreatingHandle; } private void _propagateCursorAmbience() { Cursor cur; cur = cursor; void pa(Control pc) { foreach(Control ctrl; pc.ccollection) { if(ctrl.wcurs is null) // If default. { if(cur is ctrl.cursor) // If same default. { ctrl.onCursorChanged(EventArgs.empty); pa(ctrl); // Recursive. } } } } pa(this); } /// protected void onCursorChanged(EventArgs ea) { /+ debug(EVENT_PRINT) { cprintf("{ Event: onCursorChanged - Control %.*s }\n", name); } +/ if(isHandleCreated) { if(visible && enabled) { Point curpt = Cursor.position; if(hwnd == WindowFromPoint(curpt.point)) { SendMessageA(hwnd, WM_SETCURSOR, cast(WPARAM)hwnd, MAKELPARAM( SendMessageA(hwnd, WM_NCHITTEST, 0, MAKELPARAM(curpt.x, curpt.y)).toI32, WM_MOUSEMOVE) ); } } } cursorChanged(this, ea); } /// @property void cursor(Cursor cur) // setter { if(cur is wcurs) return; wcurs = cur; onCursorChanged(EventArgs.empty); _propagateCursorAmbience(); } /// ditto @property Cursor cursor() // getter { if(!wcurs) { if(parent) { return parent.cursor; } return _defaultCursor; } return wcurs; } /// static @property Color defaultBackColor() // getter { return Color.systemColor(COLOR_BTNFACE); } /// static @property Color defaultForeColor() //getter { return Color.systemColor(COLOR_BTNTEXT); } private static Font _deffont = null; private static Font _createOldFont() { return new Font(cast(HFONT)GetStockObject(DEFAULT_GUI_FONT), false); } private static Font _createCompatibleFont() { Font result; result = _createOldFont(); try { OSVERSIONINFOA osi; osi.dwOSVersionInfoSize = osi.sizeof; if(GetVersionExA(&osi) && osi.dwMajorVersion >= 5) { // "MS Shell Dlg" / "MS Shell Dlg 2" not always supported. result = new Font("MS Shell Dlg 2", result.getSize(GraphicsUnit.POINT), GraphicsUnit.POINT); } } catch(Exception) { } //if(!result) // result = _createOldFont(); assert(result !is null); return result; } private static Font _createNativeFont() { Font result; NONCLIENTMETRICSA ncm; ncm.cbSize = ncm.sizeof; if(!SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, ncm.sizeof, &ncm, 0)) { result = _createCompatibleFont(); } else { result = new Font(&ncm.lfMessageFont, true); } return result; } private static void _setDeffont(ControlFont cf) { synchronized { assert(_deffont is null); switch(cf) { case ControlFont.COMPATIBLE: _deffont = _createCompatibleFont(); break; case ControlFont.NATIVE: _deffont = _createNativeFont(); break; case ControlFont.OLD: _deffont = _createOldFont(); break; default: assert(0); } } } deprecated alias defaultFont controlFont; /// static @property void defaultFont(ControlFont cf) // setter { if(_deffont) throw new DflException("Control font already selected"); _setDeffont(cf); } /// ditto static @property void defaultFont(Font f) // setter { if(_deffont) throw new DflException("Control font already selected"); _deffont = f; } /// ditto static @property Font defaultFont() // getter { if(!_deffont) { _setDeffont(ControlFont.COMPATIBLE); } return _deffont; } package static class SafeCursor: Cursor { this(HCURSOR hcur) { super(hcur, false); } override void dispose() { } /+ ~this() { super.dispose(); } +/ } package static @property Cursor _defaultCursor() // getter { static Cursor def = null; if(!def) { synchronized { if(!def) def = new SafeCursor(LoadCursor(HINSTANCE.init, IDC_ARROW)); } } return def; } /// @property Rect displayRectangle() // getter { return clientRectangle; } /// //protected void onDockChanged(EventArgs ea) protected void onHasLayoutChanged(EventArgs ea) { if(parent) parent.alayout(this); //dockChanged(this, ea); hasLayoutChanged(this, ea); } alias onHasLayoutChanged onDockChanged; private final void _alreadyLayout() { throw new DflException("Control already has a layout"); } /// @property DockStyle dock() // getter { return sdock; } /// ditto @property void dock(DockStyle ds) // setter { if(ds == sdock) return; DockStyle _olddock = sdock; sdock = ds; /+ anch = AnchorStyles.NONE; // Can't be set at the same time. +/ if(DockStyle.NONE == ds) { if(DockStyle.NONE != _olddock) // If it was even docking before; don't unset hasLayout for something else. hasLayout = false; } else { // Ensure not replacing some other layout, but OK if replacing another dock. if(DockStyle.NONE == _olddock) { if(hasLayout) _alreadyLayout(); } hasLayout = true; } /+ // Called by hasLayout. if(isHandleCreated) { onDockChanged(EventArgs.empty); } +/ } /// Get or set whether or not this control currently has its bounds managed. Fires onHasLayoutChanged as needed. final @property bool hasLayout() // getter { if(cbits & CBits.HAS_LAYOUT) return true; return false; } /// ditto final @property void hasLayout(bool byes) // setter { //if(byes == hasLayout) // return; // No! setting this property again must trigger onHasLayoutChanged again. if(byes) cbits |= CBits.HAS_LAYOUT; else cbits &= ~CBits.HAS_LAYOUT; if(byes) // No need if layout is removed. { if(isHandleCreated) { onHasLayoutChanged(EventArgs.empty); } } } package final void _venabled(bool byes) { if(isHandleCreated) { EnableWindow(hwnd, byes); // Window events will update -wstyle-. } else { if(byes) wstyle &= ~WS_DISABLED; else wstyle |= WS_DISABLED; } } /// final @property void enabled(bool byes) // setter { if(byes) cbits |= CBits.ENABLED; else cbits &= ~CBits.ENABLED; /+ if(!byes) { _venabled(false); } else { if(!parent || parent.enabled) _venabled(true); } _propagateEnabledAmbience(); +/ _venabled(byes); } /// final @property bool enabled() // getter { /* return IsWindowEnabled(hwnd) ? true : false; */ return (wstyle & WS_DISABLED) == 0; } private void _propagateEnabledAmbience() { /+ // Isn't working... if(cbits & CBits.FORM) return; bool en = enabled; void pa(Control pc) { foreach(Control ctrl; pc.ccollection) { if(ctrl.cbits & CBits.ENABLED) { _venabled(en); pa(ctrl); } } } pa(this); +/ } /// final void enable() { enabled = true; } /// ditto final void disable() { enabled = false; } /// @property bool focused() // getter { //return isHandleCreated && hwnd == GetFocus(); return created && fromChildHandle(GetFocus()) is this; } /// @property void font(Font f) // setter { if(wfont is f) return; wfont = f; if(isHandleCreated) SendMessageA(hwnd, WM_SETFONT, cast(WPARAM)wfont.handle, MAKELPARAM(true, 0)); onFontChanged(EventArgs.empty); _propagateFontAmbience(); } /// ditto @property Font font() // getter { if(!wfont) { if(parent) { return parent.font; } return defaultFont; } return wfont; } private void _propagateForeColorAmbience() { Color fc; fc = foreColor; void pa(Control pc) { foreach(Control ctrl; pc.ccollection) { if(Color.empty == ctrl.forec) // If default. { if(fc == ctrl.foreColor) // If same default. { ctrl.onForeColorChanged(EventArgs.empty); pa(ctrl); // Recursive. } } } } pa(this); } /// protected void onForeColorChanged(EventArgs ea) { debug(EVENT_PRINT) { cprintf("{ Event: onForeColorChanged - Control %.*s }\n", name); } foreColorChanged(this, ea); } /// @property void foreColor(Color c) // setter { if(c == forec) return; forec = c; onForeColorChanged(EventArgs.empty); _propagateForeColorAmbience(); if(isHandleCreated) invalidate(true); // Redraw! } /// ditto @property Color foreColor() // getter { if(Color.empty == forec) { if(parent) { return parent.foreColor; } return defaultForeColor; } return forec; } /// // Doesn't cause a ControlCollection to be constructed so // it could improve performance when walking through children. final @property bool hasChildren() // getter { //return isHandleCreated && GetWindow(hwnd, GW_CHILD) != HWND.init; if(isHandleCreated) { return GetWindow(hwnd, GW_CHILD) != HWND.init; } else { return ccollection.children.length != 0; } } /// final @property void height(int h) // setter { /* RECT rect; GetWindowRect(hwnd, &rect); SetWindowPos(hwnd, HWND.init, 0, 0, rect.right - rect.left, h, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE); */ setBoundsCore(0, 0, 0, h, BoundsSpecified.HEIGHT); } /// ditto final @property int height() // getter { return wrect.height; } /// final @property bool isHandleCreated() // getter { return hwnd != HWND.init; } /// final @property void left(int l) // setter { /* RECT rect; GetWindowRect(hwnd, &rect); SetWindowPos(hwnd, HWND.init, l, rect.top, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE); */ setBoundsCore(l, 0, 0, 0, BoundsSpecified.X); } /// ditto final @property int left() // getter { return wrect.x; } /// Property: get or set the X and Y location of the control. final @property void location(Point pt) // setter { /* SetWindowPos(hwnd, HWND.init, pt.x, pt.y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE); */ setBoundsCore(pt.x, pt.y, 0, 0, BoundsSpecified.LOCATION); } /// ditto final @property Point location() // getter { return wrect.location; } /// Currently depressed modifier keys. static @property Keys modifierKeys() // getter { // Is there a better way to do this? Keys ks = Keys.NONE; if(GetAsyncKeyState(VK_SHIFT) & 0x8000) ks |= Keys.SHIFT; if(GetAsyncKeyState(VK_MENU) & 0x8000) ks |= Keys.ALT; if(GetAsyncKeyState(VK_CONTROL) & 0x8000) ks|= Keys.CONTROL; return ks; } /// Currently depressed mouse buttons. static @property MouseButtons mouseButtons() // getter { MouseButtons result; result = MouseButtons.NONE; if(GetSystemMetrics(SM_SWAPBUTTON)) { if(GetAsyncKeyState(VK_LBUTTON) & 0x8000) result |= MouseButtons.RIGHT; // Swapped. if(GetAsyncKeyState(VK_RBUTTON) & 0x8000) result |= MouseButtons.LEFT; // Swapped. } else { if(GetAsyncKeyState(VK_LBUTTON) & 0x8000) result |= MouseButtons.LEFT; if(GetAsyncKeyState(VK_RBUTTON) & 0x8000) result |= MouseButtons.RIGHT; } if(GetAsyncKeyState(VK_MBUTTON) & 0x8000) result |= MouseButtons.MIDDLE; return result; } /// static @property Point mousePosition() // getter { Point pt; GetCursorPos(&pt.point); return pt; } /// Property: get or set the name of this control used in code. final @property void name(Dstring txt) // setter { _ctrlname = txt; } /// ditto final @property Dstring name() // getter { return _ctrlname; } /// protected void onParentChanged(EventArgs ea) { debug(EVENT_PRINT) { cprintf("{ Event: onParentChanged - Control %.*s }\n", name); } parentChanged(this, ea); } /+ /// // ea is the new parent. protected void onParentChanging(ControlEventArgs ea) { } +/ /// final Form findForm() { Form f; Control c; c = this; for(;;) { f = cast(Form)c; if(f) break; c = c.parent; if(!c) return null; } return f; } /// final @property void parent(Control c) // setter { if(c is wparent) return; if(!(_style() & WS_CHILD) || (_exStyle() & WS_EX_MDICHILD)) throw new DflException("Cannot add a top level control to a control"); //scope ControlEventArgs pcea = new ControlEventArgs(c); //onParentChanging(pcea); Control oldparent; _FixAmbientOld oldinfo; oldparent = wparent; if(oldparent) { oldinfo.set(oldparent); if(!oldparent.isHandleCreated) { int oi = oldparent.controls.indexOf(this); //assert(-1 != oi); // Fails if the parent (and thus this) handles destroyed. if(-1 != oi) oldparent.controls._removeNotCreated(oi); } } else { oldinfo.set(this); } scope ControlEventArgs cea = new ControlEventArgs(this); if(c) { wparent = c; // I want the destroy notification. Don't need it anymore. //c._exStyle(c._exStyle() & ~WS_EX_NOPARENTNOTIFY); if(c.isHandleCreated) { cbits &= ~CBits.NEED_INIT_LAYOUT; //if(created) if(isHandleCreated) { SetParent(hwnd, c.hwnd); } else { // If the parent is created, create me! createControl(); } onParentChanged(EventArgs.empty); if(oldparent) oldparent._ctrlremoved(cea); c._ctrladded(cea); _fixAmbient(&oldinfo); initLayout(); } else { // If the parent exists and isn't created, need to add // -this- to its children array. c.ccollection.children ~= this; onParentChanged(EventArgs.empty); if(oldparent) oldparent._ctrlremoved(cea); c._ctrladded(cea); _fixAmbient(&oldinfo); cbits |= CBits.NEED_INIT_LAYOUT; } } else { assert(c is null); //wparent = c; wparent = null; if(isHandleCreated) SetParent(hwnd, HWND.init); onParentChanged(EventArgs.empty); assert(oldparent !is null); oldparent._ctrlremoved(cea); _fixAmbient(&oldinfo); } } /// ditto final @property Control parent() // getter { return wparent; } private final Control _fetchParent() { HWND hwParent = GetParent(hwnd); return fromHandle(hwParent); } // TODO: check implementation. private static HRGN dupHrgn(HRGN hrgn) { HRGN rdup = CreateRectRgn(0, 0, 1, 1); CombineRgn(rdup, hrgn, HRGN.init, RGN_COPY); return rdup; } /// final @property void region(Region rgn) // setter { if(isHandleCreated) { // Need to make a copy of the region. SetWindowRgn(hwnd, dupHrgn(rgn.handle), true); } wregion = rgn; } /// ditto final @property Region region() // getter { return wregion; } private final Region _fetchRegion() { HRGN hrgn = CreateRectRgn(0, 0, 1, 1); GetWindowRgn(hwnd, hrgn); return new Region(hrgn); // Owned because GetWindowRgn() gives a copy. } /// final @property int right() // getter { return wrect.right; } /+ @property void rightToLeft(bool byes) // setter { LONG wl = _exStyle(); if(byes) wl |= WS_EX_RTLREADING; else wl &= ~WS_EX_RTLREADING; _exStyle(wl); } @property bool rightToLeft() // getter { return (_exStyle() & WS_EX_RTLREADING) != 0; } +/ deprecated @property void rightToLeft(bool byes) // setter { rightToLeft = byes ? RightToLeft.YES : RightToLeft.NO; } package final void _fixRtol(RightToLeft val) { switch(val) { case RightToLeft.INHERIT: if(parent && parent.rightToLeft == RightToLeft.YES) { goto case RightToLeft.YES; } goto case RightToLeft.NO; case RightToLeft.YES: _exStyle(_exStyle() | WS_EX_RTLREADING); break; case RightToLeft.NO: _exStyle(_exStyle() & ~WS_EX_RTLREADING); break; default: assert(0); } //invalidate(true); // Children too in case they inherit. invalidate(false); // Since children are enumerated. } private void _propagateRtolAmbience() { RightToLeft rl; rl = rightToLeft; void pa(Control pc) { if(RightToLeft.INHERIT == pc.rtol) { //pc._fixRtol(rtol); pc._fixRtol(rl); // Set the specific parent value so it doesn't have to look up the chain. foreach(Control ctrl; pc.ccollection) { ctrl.onRightToLeftChanged(EventArgs.empty); pa(ctrl); } } } pa(this); } /// @property void rightToLeft(RightToLeft val) // setter { if(rtol != val) { rtol = val; onRightToLeftChanged(EventArgs.empty); _propagateRtolAmbience(); // Also sets the class style and invalidates. } } /// ditto // Returns YES or NO; if inherited, returns parent's setting. @property RightToLeft rightToLeft() // getter { if(RightToLeft.INHERIT == rtol) { return parent ? parent.rightToLeft : RightToLeft.NO; } return rtol; } package struct _FixAmbientOld { Font font; Cursor cursor; Color backColor; Color foreColor; RightToLeft rightToLeft; //CBits cbits; bool enabled; void set(Control ctrl) { if(ctrl) { font = ctrl.font; cursor = ctrl.cursor; backColor = ctrl.backColor; foreColor = ctrl.foreColor; rightToLeft = ctrl.rightToLeft; //cbits = ctrl.cbits; enabled = ctrl.enabled; } /+else { font = null; cursor = null; backColor = Color.empty; foreColor = Color.empty; rightToLeft = RightToLeft.INHERIT; //cbits = CBits.init; enabled = true; }+/ } } // This is called when the inherited ambience changes. package final void _fixAmbient(_FixAmbientOld* oldinfo) { // Note: exception will screw things up. _FixAmbientOld newinfo; if(parent) newinfo.set(parent); else newinfo.set(this); if(RightToLeft.INHERIT == rtol) { if(newinfo.rightToLeft !is oldinfo.rightToLeft) { onRightToLeftChanged(EventArgs.empty); _propagateRtolAmbience(); } } if(Color.empty == backc) { if(newinfo.backColor !is oldinfo.backColor) { onBackColorChanged(EventArgs.empty); _propagateBackColorAmbience(); } } if(Color.empty == forec) { if(newinfo.foreColor !is oldinfo.foreColor) { onForeColorChanged(EventArgs.empty); _propagateForeColorAmbience(); } } if(!wfont) { if(newinfo.font !is oldinfo.font) { onFontChanged(EventArgs.empty); _propagateFontAmbience(); } } if(!wcurs) { if(newinfo.cursor !is oldinfo.cursor) { onCursorChanged(EventArgs.empty); _propagateCursorAmbience(); } } /+ if(newinfo.enabled != oldinfo.enabled) { if(cbits & CBits.ENABLED) { _venabled(newinfo.enabled); _propagateEnabledAmbience(); } } +/ } /+ package final void _fixAmbientChildren() { foreach(Control ctrl; ccollection.children) { ctrl._fixAmbient(); } } +/ /// final @property void size(Size sz) // setter { /* SetWindowPos(hwnd, HWND.init, 0, 0, sz.width, sz.height, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE); */ setBoundsCore(0, 0, sz.width, sz.height, BoundsSpecified.SIZE); } /// ditto final @property Size size() // getter { return wrect.size; // struct Size, not sizeof. } /+ final @property void tabIndex(int i) // setter { // TODO: ? } final @property int tabIndex() // getter { return tabidx; } +/ // Use -zIndex- instead. // -tabIndex- may return different values in the future. deprecated int tabIndex() // getter { return zIndex; } /// final @property int zIndex() // getter out(result) { assert(result >= 0); } do { if(!parent) return 0; if(isHandleCreated) { GetZIndex gzi; gzi.find = this; int index; int tmp; BOOL getZIndexCallback(HWND hWnd) { if(hWnd is hwnd) { index = tmp; return FALSE; // Stop, found it. } auto ctrl = Control.fromHandle(hWnd); if(ctrl && ctrl.parent is parent) { tmp++; } return TRUE; // Keep looking. } enumChildWindows(parent.hwnd, &getZIndexCallback); return index; } else { return parent.controls.indexOf(this); } } /// // True if control can be tabbed to. final @property void tabStop(bool byes) // setter { LONG wl = _style(); if(byes) wl |= WS_TABSTOP; else wl &= ~WS_TABSTOP; _style(wl); } /// ditto final @property bool tabStop() // getter { return (_style() & WS_TABSTOP) != 0; } /// Property: get or set additional data tagged onto the control. final @property void tag(Object o) // setter { otag = o; } /// ditto final @property Object tag() // getter { return otag; } private final Dstring _fetchText() { return dfl.internal.utf.getWindowText(hwnd); } /// @property void text(Dstring txt) // setter { if(isHandleCreated) { if(ctrlStyle & ControlStyles.CACHE_TEXT) { //if(wtext == txt) // return; wtext = txt; } dfl.internal.utf.setWindowText(hwnd, txt); } else { wtext = txt; } } /// ditto @property Dstring text() // getter { if(isHandleCreated) { if(ctrlStyle & ControlStyles.CACHE_TEXT) return wtext; return _fetchText(); } else { return wtext; } } /// final @property void top(int t) // setter { setBoundsCore(0, t, 0, 0, BoundsSpecified.Y); } /// ditto final @property int top() // getter { return wrect.y; } /// Returns the topmost Control related to this control. // Returns the owner control that has no parent. // Returns this Control if no owner ? final @property Control topLevelControl() // getter { if(isHandleCreated) { HWND hwCurrent = hwnd; HWND hwParent; for(;;) { hwParent = GetParent(hwCurrent); // This gets the top-level one, whereas the previous code jumped owners. if(!hwParent) break; hwCurrent = hwParent; } return fromHandle(hwCurrent); } else { Control ctrl; ctrl = this; while(ctrl.parent) { ctrl = ctrl.parent; // This shouldn't jump owners.. } return ctrl; } } /+ private DWORD _fetchVisible() { //return IsWindowVisible(hwnd) != FALSE; wstyle = GetWindowLongPtrA(hwnd, GWL_STYLE); return wstyle & WS_VISIBLE; } +/ /// final @property void visible(bool byes) // setter { setVisibleCore(byes); } /// ditto final @property bool visible() // getter { //if(isHandleCreated) // wstyle = GetWindowLongPtrA(hwnd, GWL_STYLE); // ... //return (wstyle & WS_VISIBLE) != 0; return (cbits & CBits.VISIBLE) != 0; } /// final @property void width(int w) // setter { setBoundsCore(0, 0, w, 0, BoundsSpecified.WIDTH); } /// ditto final @property int width() // getter { return wrect.width; } /// final void sendToBack() { if(!isHandleCreated) { if(parent) parent.ccollection._simple_front(this); return; } SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } /// final void bringToFront() { if(!isHandleCreated) { if(parent) parent.ccollection._simple_back(this); return; } SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); //BringWindowToTop(hwnd); } deprecated alias bringUpOne zIndexUp; /// // Move up one. final void bringUpOne() { if(!isHandleCreated) { if(parent) parent.ccollection._simple_front_one(this); return; } HWND hw; // Need to move back twice because the previous one already precedes this one. hw = GetWindow(hwnd, GW_HWNDPREV); if(!hw) { hw = HWND_TOP; } else { hw = GetWindow(hw, GW_HWNDPREV); if(!hw) hw = HWND_TOP; } SetWindowPos(hwnd, hw, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } deprecated alias sendBackOne zIndexDown; /// // Move back one. final void sendBackOne() { if(!isHandleCreated) { if(parent) parent.ccollection._simple_back_one(this); return; } HWND hw; hw = GetWindow(hwnd, GW_HWNDNEXT); if(!hw) hw = HWND_BOTTOM; SetWindowPos(hwnd, hw, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } // Note: true if no children, even if this not created. package final @property bool areChildrenCreated() // getter { return !ccollection.children.length; } package final void createChildren() { assert(isHandleCreated); Control[] ctrls; ctrls = ccollection.children; ccollection.children = null; foreach(Control ctrl; ctrls) { assert(ctrl.parent is this); assert(!(ctrl is null)); assert(ctrl); ctrl.createControl(); } } /// // Force creation of the window and its child controls. final void createControl() { createHandle(); // Called in WM_CREATE also. createChildren(); } /// Returns a new Graphics object for this control, creating the control handle if necessary. final Graphics createGraphics() { HDC hdc = GetDC(handle); // Create handle as necessary. SetTextColor(hdc, foreColor.toRgb()); return new CommonGraphics(hwnd, hdc); } version(DFL_NO_DRAG_DROP) {} else { private static class DropTarget: DflComObject, IDropTarget { this(Control ctrl) { this.ctrl = ctrl; } ~this() { if (dataObj) { GC.removeRoot(cast(void*)dataObj); destroy(dataObj); } } extern(Windows): override HRESULT QueryInterface(IID* riid, void** ppv) { if(*riid == _IID_IDropTarget) { *ppv = cast(void*)cast(IDropTarget)this; AddRef(); return S_OK; } else if(*riid == _IID_IUnknown) { *ppv = cast(void*)cast(IUnknown)this; AddRef(); return S_OK; } else { *ppv = null; return E_NOINTERFACE; } } HRESULT DragEnter(dfl.internal.wincom.IDataObject pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { HRESULT result; try { //dataObj = new ComToDdataObject(pDataObject); ensureDataObj(pDataObject); scope DragEventArgs ea = new DragEventArgs(dataObj, cast(int)grfKeyState, pt.x, pt.y, cast(DragDropEffects)*pdwEffect, DragDropEffects.NONE); // ? ctrl.onDragEnter(ea); *pdwEffect = ea.effect; result = S_OK; } catch(DThrowable e) { Application.onThreadException(e); result = E_UNEXPECTED; } return result; } HRESULT DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { HRESULT result; try { assert(dataObj !is null); scope DragEventArgs ea = new DragEventArgs(dataObj, cast(int)grfKeyState, pt.x, pt.y, cast(DragDropEffects)*pdwEffect, DragDropEffects.NONE); // ? ctrl.onDragOver(ea); *pdwEffect = ea.effect; result = S_OK; } catch(DThrowable e) { Application.onThreadException(e); result = E_UNEXPECTED; } return result; } HRESULT DragLeave() { HRESULT result; try { ctrl.onDragLeave(EventArgs.empty); killDataObj(); result = S_OK; } catch(DThrowable e) { Application.onThreadException(e); result = E_UNEXPECTED; } return result; } HRESULT Drop(dfl.internal.wincom.IDataObject pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { HRESULT result; try { //assert(dataObj !is null); ensureDataObj(pDataObject); scope DragEventArgs ea = new DragEventArgs(dataObj, cast(int)grfKeyState, pt.x, pt.y, cast(DragDropEffects)*pdwEffect, DragDropEffects.NONE); // ? ctrl.onDragDrop(ea); *pdwEffect = ea.effect; result = S_OK; } catch(DThrowable e) { Application.onThreadException(e); result = E_UNEXPECTED; } return result; } private: Control ctrl; //dfl.data.IDataObject dataObj; ComToDdataObject dataObj; void ensureDataObj(dfl.internal.wincom.IDataObject pDataObject) { if(!dataObj) { dataObj = new ComToDdataObject(pDataObject); GC.addRoot(cast(void*)dataObj); } else if (!dataObj.isSameDataObject(pDataObject)) { GC.removeRoot(cast(void*)dataObj); dataObj = new ComToDdataObject(pDataObject); GC.addRoot(cast(void*)dataObj); } } void killDataObj() { // Can't do this because the COM object might still need to be released elsewhere. //delete dataObj; //dataObj = null; } } /// protected void onDragLeave(EventArgs ea) { dragLeave(this, ea); } /// protected void onDragEnter(DragEventArgs ea) { dragEnter(this, ea); } /// protected void onDragOver(DragEventArgs ea) { dragOver(this, ea); } /// protected void onDragDrop(DragEventArgs ea) { dragDrop(this, ea); } private static class DropSource: DflComObject, IDropSource { this(Control ctrl) { this.ctrl = ctrl; mbtns = Control.mouseButtons; } extern(Windows): override HRESULT QueryInterface(IID* riid, void** ppv) { if(*riid == _IID_IDropSource) { *ppv = cast(void*)cast(IDropSource)this; AddRef(); return S_OK; } else if(*riid == _IID_IUnknown) { *ppv = cast(void*)cast(IUnknown)this; AddRef(); return S_OK; } else { *ppv = null; return E_NOINTERFACE; } } HRESULT QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) { HRESULT result; try { DragAction act; if(fEscapePressed) { act = cast(DragAction)DragAction.CANCEL; } else { if(mbtns & MouseButtons.LEFT) { if(!(grfKeyState & MK_LBUTTON)) { act = cast(DragAction)DragAction.DROP; goto qdoit; } } else { if(grfKeyState & MK_LBUTTON) { act = cast(DragAction)DragAction.CANCEL; goto qdoit; } } if(mbtns & MouseButtons.RIGHT) { if(!(grfKeyState & MK_RBUTTON)) { act = cast(DragAction)DragAction.DROP; goto qdoit; } } else { if(grfKeyState & MK_RBUTTON) { act = cast(DragAction)DragAction.CANCEL; goto qdoit; } } if(mbtns & MouseButtons.MIDDLE) { if(!(grfKeyState & MK_MBUTTON)) { act = cast(DragAction)DragAction.DROP; goto qdoit; } } else { if(grfKeyState & MK_MBUTTON) { act = cast(DragAction)DragAction.CANCEL; goto qdoit; } } act = cast(DragAction)DragAction.CONTINUE; } qdoit: scope QueryContinueDragEventArgs ea = new QueryContinueDragEventArgs(cast(int)grfKeyState, fEscapePressed != FALSE, act); // ? ctrl.onQueryContinueDrag(ea); result = cast(HRESULT)ea.action; } catch(DThrowable e) { Application.onThreadException(e); result = E_UNEXPECTED; } return result; } HRESULT GiveFeedback(DWORD dwEffect) { HRESULT result; try { scope GiveFeedbackEventArgs ea = new GiveFeedbackEventArgs(cast(DragDropEffects)dwEffect, true); ctrl.onGiveFeedback(ea); result = ea.useDefaultCursors ? DRAGDROP_S_USEDEFAULTCURSORS : S_OK; } catch(DThrowable e) { Application.onThreadException(e); result = E_UNEXPECTED; } return result; } private: Control ctrl; MouseButtons mbtns; } /// protected void onQueryContinueDrag(QueryContinueDragEventArgs ea) { queryContinueDrag(this, ea); } /// protected void onGiveFeedback(GiveFeedbackEventArgs ea) { giveFeedback(this, ea); } /// Perform a drag/drop operation. final DragDropEffects doDragDrop(dfl.data.IDataObject dataObj, DragDropEffects allowedEffects) { Object foo = cast(Object)dataObj; // Hold a reference to the Object... DWORD effect; DropSource dropsrc; dfl.internal.wincom.IDataObject dropdata; dropsrc = new DropSource(this); dropdata = new DtoComDataObject(dataObj); // dataObj seems to be killed too early. switch(DoDragDrop(dropdata, dropsrc, cast(DWORD)allowedEffects, &effect)) { case DRAGDROP_S_DROP: // All good. break; case DRAGDROP_S_CANCEL: return DragDropEffects.NONE; // ? default: throw new DflException("Unable to complete drag-drop operation"); } return cast(DragDropEffects)effect; } /// ditto final DragDropEffects doDragDrop(Data obj, DragDropEffects allowedEffects) { dfl.data.IDataObject dd; dd = new DataObject; dd.setData(obj); return doDragDrop(dd, allowedEffects); } } override Dequ opEquals(Object o) { Control ctrl = cast(Control)o; if(!ctrl) return 0; // Not equal. return opEquals(ctrl); } Dequ opEquals(Control ctrl) { if(!isHandleCreated) return super.opEquals(ctrl); return hwnd == ctrl.hwnd; } override int opCmp(Object o) { Control ctrl = cast(Control)o; if(!ctrl) return -1; return opCmp(ctrl); } int opCmp(Control ctrl) { if(!isHandleCreated || hwnd != ctrl.hwnd) return super.opCmp(ctrl); return 0; } /// final bool focus() { return SetFocus(hwnd) != HWND.init; } /// Returns the Control instance from one of its window handles, or null if none. // Finds controls that own more than one handle. // A combo box has several HWNDs, this would return the // correct combo box control if any of those handles are // provided. static Control fromChildHandle(HWND hwChild) { Control result; for(;;) { if(!hwChild) return null; result = fromHandle(hwChild); if(result) return result; hwChild = GetParent(hwChild); } } /// Returns the Control instance from its window handle, or null if none. static Control fromHandle(HWND hw) { return Application.lookupHwnd(hw); } /// final Control getChildAtPoint(Point pt) { HWND hwChild; hwChild = ChildWindowFromPoint(hwnd, pt.point); if(!hwChild) return null; return fromChildHandle(hwChild); } /// final void hide() { setVisibleCore(false); } /// ditto final void show() { /* ShowWindow(hwnd, SW_SHOW); doShow(); */ setVisibleCore(true); } package final void redrawEntire() { if(hwnd) { SetWindowPos(hwnd, HWND.init, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } } package final void recalcEntire() { if(hwnd) { SetWindowPos(hwnd, HWND.init, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } } /// final void invalidate() { if(!hwnd) return; RedrawWindow(hwnd, null, HRGN.init, RDW_ERASE | RDW_INVALIDATE | RDW_NOCHILDREN); } /// ditto final void invalidate(bool andChildren) { if(!hwnd) return; RedrawWindow(hwnd, null, HRGN.init, RDW_ERASE | RDW_INVALIDATE | (andChildren ? RDW_ALLCHILDREN : RDW_NOCHILDREN)); } /// ditto final void invalidate(Rect r) { if(!hwnd) return; RECT rect; r.getRect(&rect); RedrawWindow(hwnd, &rect, HRGN.init, RDW_ERASE | RDW_INVALIDATE | RDW_NOCHILDREN); } /// ditto final void invalidate(Rect r, bool andChildren) { if(!hwnd) return; RECT rect; r.getRect(&rect); RedrawWindow(hwnd, &rect, HRGN.init, RDW_ERASE | RDW_INVALIDATE | (andChildren ? RDW_ALLCHILDREN : RDW_NOCHILDREN)); } /// ditto final void invalidate(Region rgn) { if(!hwnd) return; RedrawWindow(hwnd, null, rgn.handle, RDW_ERASE | RDW_INVALIDATE | RDW_NOCHILDREN); } /// ditto final void invalidate(Region rgn, bool andChildren) { if(!hwnd) return; RedrawWindow(hwnd, null, rgn.handle, RDW_ERASE | RDW_INVALIDATE | (andChildren ? RDW_ALLCHILDREN : RDW_NOCHILDREN)); } /// // Redraws the entire control, including nonclient area. final void redraw() { if(!hwnd) return; RedrawWindow(hwnd, null, HRGN.init, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME); } /// Returns true if the window does not belong to the current thread. @property bool invokeRequired() // getter { DWORD tid = GetWindowThreadProcessId(hwnd, null); return tid != GetCurrentThreadId(); } private static void badInvokeHandle() { //throw new DflException("Must invoke after creating handle"); throw new DflException("Must invoke with created handle"); } /// Synchronously calls a delegate in this Control's thread. This function is thread safe and exceptions are propagated to the caller. // Exceptions are propagated back to the caller of invoke(). final Object invoke(Object delegate(Object[]) dg, Object[] args ...) { if(!hwnd) badInvokeHandle(); InvokeData inv; inv.dg = dg; inv.args = args; if(LRESULT_DFL_INVOKE != SendMessageA(hwnd, wmDfl, WPARAM_DFL_INVOKE, cast(LRESULT)&inv)) throw new DflException("Invoke failure"); if(inv.exception) throw inv.exception; return inv.result; } /// ditto final void invoke(void delegate() dg) { if(!hwnd) badInvokeHandle(); InvokeSimpleData inv; inv.dg = dg; if(LRESULT_DFL_INVOKE != SendMessageA(hwnd, wmDfl, WPARAM_DFL_INVOKE_SIMPLE, cast(LRESULT)&inv)) throw new DflException("Invoke failure"); if(inv.exception) throw inv.exception; } /** Asynchronously calls a function after the window message queue processes its current messages. It is generally not safe to pass references to the delayed function. Exceptions are not propagated to the caller. **/ // Extra. // Exceptions will be passed to Application.onThreadException() and // trigger the threadException event or the default exception dialog. final void delayInvoke(void function() fn) { if(!hwnd) badInvokeHandle(); assert(!invokeRequired); static assert(fn.sizeof <= LPARAM.sizeof); PostMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE, cast(LPARAM)fn); } /// ditto // Extra. // Exceptions will be passed to Application.onThreadException() and // trigger the threadException event or the default exception dialog. // Copy of params are passed to fn, they do not exist after it returns. // It is unsafe to pass references to a delayed function. final void delayInvoke(void function(Control, size_t[]) fn, size_t[] params ...) { if(!hwnd) badInvokeHandle(); assert(!invokeRequired); static assert((DflInvokeParam*).sizeof <= LPARAM.sizeof); DflInvokeParam* p; p = cast(DflInvokeParam*)dfl.internal.clib.malloc( (DflInvokeParam.sizeof - size_t.sizeof) + params.length * size_t.sizeof); if(!p) throw new OomException(); p.fp = fn; p.nparams = params.length; p.params.ptr[0 .. params.length] = params[]; PostMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, cast(LPARAM)p); } deprecated alias delayInvoke beginInvoke; /// static bool isMnemonic(dchar charCode, Dstring text) { size_t ui; for(ui = 0; ui != text.length; ui++) { if('&' == text[ui]) { if(++ui == text.length) break; if('&' == text[ui]) // && means literal & so skip it. continue; dchar dch; dch = utf8stringGetUtf32char(text, ui); return utf32charToLower(charCode) == utf32charToLower(dch); } } return false; } /// Converts a screen Point to a client Point. final Point pointToClient(Point pt) { ScreenToClient(hwnd, &pt.point); return pt; } /// Converts a client Point to a screen Point. final Point pointToScreen(Point pt) { ClientToScreen(hwnd, &pt.point); return pt; } /// Converts a screen Rectangle to a client Rectangle. final Rect rectangleToClient(Rect r) { RECT rect; r.getRect(&rect); MapWindowPoints(HWND.init, hwnd, cast(POINT*)&rect, 2); return Rect(&rect); } /// Converts a client Rectangle to a screen Rectangle. final Rect rectangleToScreen(Rect r) { RECT rect; r.getRect(&rect); MapWindowPoints(hwnd, HWND.init, cast(POINT*)&rect, 2); return Rect(&rect); } /// // Return true if processed. bool preProcessMessage(ref Message msg) { return false; } /// final Size getAutoScaleSize(Font f) { Size result; Graphics g; g = createGraphics(); result = g.getScaleSize(f); g.dispose(); return result; } /// ditto final Size getAutoScaleSize() { return getAutoScaleSize(font); } /// void refresh() { invalidate(true); } /// void resetBackColor() { //backColor = defaultBackColor; backColor = Color.empty; } /// void resetCursor() { //cursor = new Cursor(LoadCursorA(HINSTANCE.init, IDC_ARROW), false); cursor = null; } /// void resetFont() { //font = defaultFont; font = null; } /// void resetForeColor() { //foreColor = defaultForeColor; foreColor = Color.empty; } /// void resetRightToLeft() { //rightToLeft = false; rightToLeft = RightToLeft.INHERIT; } /// void resetText() { //text = ""; text = null; } /// // Just allow layout recalc, but don't do it right now. final void resumeLayout() { //_allowLayout = true; if(_disallowLayout) _disallowLayout--; } /// ditto // Allow layout recalc, only do it now if -byes- is true. final void resumeLayout(bool byes) { if(_disallowLayout) _disallowLayout--; // This is correct. if(byes) { if(!_disallowLayout) alayout(null); } } /// final void suspendLayout() { //_allowLayout = false; _disallowLayout++; } final void performLayout(Control affectedControl) { alayout(affectedControl, false); } final void performLayout() { return performLayout(this); } /+ // TODO: implement. // Scale both height and width to -ratio-. final void scale(float ratio) { scaleCore(ratio, ratio); } // Scale -width- and -height- ratios. final void scale(float width, float height) { scaleCore(width, height); } // Also scales child controls recursively. protected void scaleCore(float width, float height) { suspendLayout(); // ... resumeLayout(); } +/ private static bool _eachild(HWND hw, bool delegate(HWND hw) callback, ref size_t xiter, bool nested) { for(; hw; hw = GetWindow(hw, GW_HWNDNEXT)) { if(!xiter) return false; xiter--; LONG st = GetWindowLongPtrA(hw, GWL_STYLE).toI32; if(!(st & WS_VISIBLE)) continue; if(st & WS_DISABLED) continue; if(!callback(hw)) return false; if(nested) { //LONG exst = GetWindowLongPtrA(hw, GWL_EXSTYLE); //if(exst & WS_EX_CONTROLPARENT) // It's no longer added. { HWND hwc = GetWindow(hw, GW_CHILD); if(hwc) { //if(!_eachild(hwc, callback, xiter, nested)) if(!_eachild(hwc, callback, xiter, true)) return false; } } } } return true; } package static void eachGoodChildHandle(HWND hwparent, bool delegate(HWND hw) callback, bool nested = true) { HWND hw = GetWindow(hwparent, GW_CHILD); size_t xiter = 2000; _eachild(hw, callback, xiter, nested); } private static bool _isHwndControlSel(HWND hw) { Control c = Control.fromHandle(hw); return c && c.getStyle(ControlStyles.SELECTABLE); } package static void _dlgselnext(Form dlg, HWND hwcursel, bool forward, bool tabStopOnly = true, bool selectableOnly = false, bool nested = true, bool wrap = true, HWND hwchildrenof = null) { //assert(cast(Form)Control.fromHandle(hwdlg) !is null); if(!hwchildrenof) hwchildrenof = dlg.handle; if(forward) { bool foundthis = false, tdone = false; HWND hwfirst; eachGoodChildHandle(hwchildrenof, (HWND hw) { assert(!tdone); if(hw == hwcursel) { foundthis = true; } else { if(!tabStopOnly || (GetWindowLongPtrA(hw, GWL_STYLE) & WS_TABSTOP)) { if(!selectableOnly || _isHwndControlSel(hw)) { if(foundthis) { //DefDlgProcA(dlg.handle, WM_NEXTDLGCTL, cast(WPARAM)hw, MAKELPARAM(true, 0)); dlg._selectChild(hw); tdone = true; return false; // Break. } else { if(HWND.init == hwfirst) hwfirst = hw; } } } } return true; // Continue. }, nested); if(!tdone && HWND.init != hwfirst) { // If it falls through without finding hwcursel, let it select the first one, even if not wrapping. if(wrap || !foundthis) { //DefDlgProcA(dlg.handle, WM_NEXTDLGCTL, cast(WPARAM)hwfirst, MAKELPARAM(true, 0)); dlg._selectChild(hwfirst); } } } else { HWND hwprev; eachGoodChildHandle(hwchildrenof, (HWND hw) { if(hw == hwcursel) { if(HWND.init != hwprev) // Otherwise, keep looping and get last one. return false; // Break. if(!wrap) // No wrapping, so don't get last one. { assert(HWND.init == hwprev); return false; // Break. } } if(!tabStopOnly || (GetWindowLongPtrA(hw, GWL_STYLE) & WS_TABSTOP)) { if(!selectableOnly || _isHwndControlSel(hw)) { hwprev = hw; } } return true; // Continue. }, nested); // If it falls through without finding hwcursel, let it select the last one, even if not wrapping. if(HWND.init != hwprev) //DefDlgProcA(dlg.handle, WM_NEXTDLGCTL, cast(WPARAM)hwprev, MAKELPARAM(true, 0)); dlg._selectChild(hwprev); } } package final void _selectNextControl(Form ctrltoplevel, Control ctrl, bool forward, bool tabStopOnly, bool nested, bool wrap) { if(!created) return; assert(ctrltoplevel !is null); assert(ctrltoplevel.isHandleCreated); _dlgselnext(ctrltoplevel, (ctrl && ctrl.isHandleCreated) ? ctrl.handle : null, forward, tabStopOnly, !tabStopOnly, nested, wrap, this.handle); } package final void _selectThisControl() { } // Only considers child controls of this control. final void selectNextControl(Control ctrl, bool forward, bool tabStopOnly, bool nested, bool wrap) { if(!created) return; auto ctrltoplevel = findForm(); if(ctrltoplevel) return _selectNextControl(ctrltoplevel, ctrl, forward, tabStopOnly, nested, wrap); } /// final void select() { select(false, false); } /// ditto // If -directed- is true, -forward- is used; otherwise, selects this control. // If -forward- is true, the next control in the tab order is selected, // otherwise the previous control in the tab order is selected. // Controls without style ControlStyles.SELECTABLE are skipped. void select(bool directed, bool forward) { if(!created) return; auto ctrltoplevel = findForm(); if(ctrltoplevel && ctrltoplevel !is this) { /+ // Old... // Even if directed, ensure THIS one is selected first. if(!directed || hwnd != GetFocus()) { DefDlgProcA(ctrltoplevel.handle, WM_NEXTDLGCTL, cast(WPARAM)hwnd, MAKELPARAM(true, 0)); } if(directed) { DefDlgProcA(ctrltoplevel.handle, WM_NEXTDLGCTL, !forward, MAKELPARAM(false, 0)); } +/ if(directed) { _dlgselnext(ctrltoplevel, this.handle, forward); } else { ctrltoplevel._selectChild(this); } } else { focus(); // This must be a form so just focus it ? } } /// final void setBounds(int x, int y, int width, int height) { setBoundsCore(x, y, width, height, BoundsSpecified.ALL); } /// ditto final void setBounds(int x, int y, int width, int height, BoundsSpecified specified) { setBoundsCore(x, y, width, height, specified); } override Dstring toString() { return text; } /// final void update() { if(!created) return; UpdateWindow(hwnd); } /// // If mouseEnter, mouseHover and mouseLeave events are supported. // Returns true on Windows 95 with IE 5.5, Windows 98+ or Windows NT 4.0+. static @property bool supportsMouseTracking() // getter { return trackMouseEvent != null; } package final Rect _fetchBounds() { RECT r; GetWindowRect(hwnd, &r); HWND hwParent = GetParent(hwnd); if(hwParent && (_style() & WS_CHILD)) MapWindowPoints(HWND.init, hwParent, cast(POINT*)&r, 2); return Rect(&r); } package final Size _fetchClientSize() { RECT r; GetClientRect(hwnd, &r); return Size(r.right, r.bottom); } deprecated protected void onInvalidated(InvalidateEventArgs iea) { //invalidated(this, iea); } /// protected void onPaint(PaintEventArgs pea) { paint(this, pea); } protected void onMoving(MovingEventArgs cea) { moving(this, cea); } /// protected void onMove(EventArgs ea) { move(this, ea); } /+ protected void onLocationChanged(EventArgs ea) { locationChanged(this, ea); } +/ alias onMove onLocationChanged; protected void onSizing(SizingEventArgs cea) { sizing(this, cea); } /// protected void onResize(EventArgs ea) { resize(this, ea); } /+ protected void onSizeChanged(EventArgs ea) { sizeChanged(this, ea); } +/ alias onResize onSizeChanged; /+ // /// // Allows comparing before and after dimensions, and also allows modifying the new dimensions. deprecated protected void onBeforeResize(BeforeResizeEventArgs ea) { } +/ /// protected void onMouseEnter(MouseEventArgs mea) { mouseEnter(this, mea); } /// protected void onMouseMove(MouseEventArgs mea) { mouseMove(this, mea); } /// protected void onKeyDown(KeyEventArgs kea) { keyDown(this, kea); } /// protected void onKeyPress(KeyPressEventArgs kea) { keyPress(this, kea); } /// protected void onKeyUp(KeyEventArgs kea) { keyUp(this, kea); } /// protected void onMouseWheel(MouseEventArgs mea) { mouseWheel(this, mea); } /// protected void onMouseHover(MouseEventArgs mea) { mouseHover(this, mea); } /// protected void onMouseLeave(MouseEventArgs mea) { mouseLeave(this, mea); } /// protected void onMouseDown(MouseEventArgs mea) { mouseDown(this, mea); } /// protected void onMouseUp(MouseEventArgs mea) { mouseUp(this, mea); } /// protected void onClick(EventArgs ea) { click(this, ea); } /// protected void onDoubleClick(EventArgs ea) { doubleClick(this, ea); } /// protected void onGotFocus(EventArgs ea) { gotFocus(this, ea); } /+ deprecated protected void onEnter(EventArgs ea) { //enter(this, ea); } deprecated protected void onLeave(EventArgs ea) { //leave(this, ea); } deprecated protected void onValidated(EventArgs ea) { //validated(this, ea); } deprecated protected void onValidating(CancelEventArgs cea) { /+ foreach(CancelEventHandler.Handler handler; validating.handlers()) { handler(this, cea); if(cea.cancel) return; // Not validated. } onValidated(EventArgs.empty); +/ } +/ /// protected void onLostFocus(EventArgs ea) { lostFocus(this, ea); } /// protected void onEnabledChanged(EventArgs ea) { enabledChanged(this, ea); } /// protected void onTextChanged(EventArgs ea) { textChanged(this, ea); } private void _propagateFontAmbience() { Font fon; fon = font; void pa(Control pc) { foreach(Control ctrl; pc.ccollection) { if(!ctrl.wfont) // If default. { if(fon is ctrl.font) // If same default. { if(ctrl.isHandleCreated) SendMessageA(ctrl.hwnd, WM_SETFONT, cast(WPARAM)fon.handle, MAKELPARAM(true, 0)); ctrl.onFontChanged(EventArgs.empty); pa(ctrl); // Recursive. } } } } pa(this); } /// protected void onFontChanged(EventArgs ea) { debug(EVENT_PRINT) { cprintf("{ Event: onFontChanged - Control %.*s }\n", name); } fontChanged(this, ea); } /// protected void onRightToLeftChanged(EventArgs ea) { debug(EVENT_PRINT) { cprintf("{ Event: onRightToLeftChanged - Control %.*s }\n", name); } rightToLeftChanged(this, ea); } /// protected void onVisibleChanged(EventArgs ea) { if(wparent) { wparent.vchanged(); suspendLayout(); // Note: exception could cause failure to restore. wparent.alayout(this); resumeLayout(false); } if(visible) alayout(this); visibleChanged(this, ea); if(visible) { // If no focus or the focused control is hidden, try to select something... HWND hwfocus = GetFocus(); if(!hwfocus || (hwfocus == hwnd && !getStyle(ControlStyles.SELECTABLE)) || !IsWindowVisible(hwfocus)) { selectNextControl(null, true, true, true, false); } } } /// protected void onHelpRequested(HelpEventArgs hea) { debug(EVENT_PRINT) { cprintf("{ Event: onHelpRequested - Control %.*s }\n", name); } helpRequested(this, hea); } /// protected void onSystemColorsChanged(EventArgs ea) { debug(EVENT_PRINT) { cprintf("{ Event: onSystemColorsChanged - Control %.*s }\n", name); } systemColorsChanged(this, ea); } /// protected void onHandleCreated(EventArgs ea) { if(!(cbits & CBits.VSTYLE)) _disableVisualStyle(); Font fon; fon = font; if(fon) SendMessageA(hwnd, WM_SETFONT, cast(WPARAM)fon.handle, 0); if(wregion) { // Need to make a copy of the region. SetWindowRgn(hwnd, dupHrgn(wregion.handle), true); } version(DFL_NO_DRAG_DROP) {} else { if(droptarget) { if(S_OK != RegisterDragDrop(hwnd, droptarget)) { droptarget = null; throw new DflException("Unable to register drag-drop"); } } } debug { _handlecreated = true; } } /// protected void onHandleDestroyed(EventArgs ea) { handleDestroyed(this, ea); } /// protected void onPaintBackground(PaintEventArgs pea) { RECT rect; pea.clipRectangle.getRect(&rect); FillRect(pea.graphics.handle, &rect, hbrBg); } private static MouseButtons wparamMouseButtons(WPARAM wparam) { MouseButtons result; if(wparam & MK_LBUTTON) result |= MouseButtons.LEFT; if(wparam & MK_RBUTTON) result |= MouseButtons.RIGHT; if(wparam & MK_MBUTTON) result |= MouseButtons.MIDDLE; return result; } package final void prepareDc(HDC hdc) { //SetBkMode(hdc, TRANSPARENT); // ? //SetBkMode(hdc, OPAQUE); // ? SetBkColor(hdc, backColor.toRgb()); SetTextColor(hdc, foreColor.toRgb()); } // Message copy so it cannot be modified. deprecated protected void onNotifyMessage(Message msg) { } /+ /+package+/ LRESULT customMsg(ref CustomMsg msg) // package { return 0; } +/ /// protected void onReflectedMessage(ref Message m) { switch(m.msg) { case WM_CTLCOLORSTATIC: case WM_CTLCOLORLISTBOX: case WM_CTLCOLOREDIT: case WM_CTLCOLORSCROLLBAR: case WM_CTLCOLORBTN: //case WM_CTLCOLORDLG: // ? //case 0x0019: //WM_CTLCOLOR; obsolete. prepareDc(cast(HDC)m.wParam); //assert(GetObjectA(hbrBg, 0, null)); m.result = cast(LRESULT)hbrBg; break; default: } } // ChildWindowFromPoint includes both hidden and disabled. // This includes disabled windows, but not hidden. // Here is a point in this control, see if it's over a visible child. // Returns null if not even in this control's client. final HWND pointOverVisibleChild(Point pt) // package { if(pt.x < 0 || pt.y < 0) return HWND.init; if(pt.x > wclientsz.width || pt.y > wclientsz.height) return HWND.init; // Note: doesn't include non-DFL windows... TO-DO: fix. foreach(Control ctrl; ccollection) { if(!ctrl.visible) continue; if(!ctrl.isHandleCreated) // Shouldn't.. continue; if(ctrl.bounds.contains(pt)) return ctrl.hwnd; } return hwnd; // Just over this control. } version(_DFL_WINDOWS_HUNG_WORKAROUND) { DWORD ldlgcode = 0; } /// protected void wndProc(ref Message msg) { //if(ctrlStyle & ControlStyles.ENABLE_NOTIFY_MESSAGE) // onNotifyMessage(msg); switch(msg.msg) { case WM_PAINT: { // This can't be done in BeginPaint() becuase part might get // validated during this event ? //RECT uprect; //GetUpdateRect(hwnd, &uprect, true); //onInvalidated(new InvalidateEventArgs(Rect(&uprect))); PAINTSTRUCT ps; BeginPaint(msg.hWnd, &ps); try { //onInvalidated(new InvalidateEventArgs(Rect(&uprect))); scope PaintEventArgs pea = new PaintEventArgs(new Graphics(ps.hdc, false), Rect(&ps.rcPaint)); // Probably because ControlStyles.ALL_PAINTING_IN_WM_PAINT. if(ps.fErase) { prepareDc(ps.hdc); onPaintBackground(pea); } prepareDc(ps.hdc); onPaint(pea); } finally { EndPaint(hwnd, &ps); } } return; case WM_ERASEBKGND: if(ctrlStyle & ControlStyles.OPAQUE) { msg.result = 1; // Erased. } else if(!(ctrlStyle & ControlStyles.ALL_PAINTING_IN_WM_PAINT)) { RECT uprect; /+ GetUpdateRect(hwnd, &uprect, false); +/ uprect.left = 0; uprect.top = 0; uprect.right = clientSize.width; uprect.bottom = clientSize.height; prepareDc(cast(HDC)msg.wParam); scope PaintEventArgs pea = new PaintEventArgs(new Graphics(cast(HDC)msg.wParam, false), Rect(&uprect)); onPaintBackground(pea); msg.result = 1; // Erased. } return; case WM_PRINTCLIENT: prepareDc(cast(HDC)msg.wParam); scope PaintEventArgs pea = new PaintEventArgs(new Graphics(cast(HDC)msg.wParam, false), Rect(Point(0, 0), wclientsz)); onPaint(pea); return; case WM_CTLCOLORSTATIC: case WM_CTLCOLORLISTBOX: case WM_CTLCOLOREDIT: case WM_CTLCOLORSCROLLBAR: case WM_CTLCOLORBTN: //case WM_CTLCOLORDLG: // ? //case 0x0019: //WM_CTLCOLOR; obsolete. { Control ctrl = fromChildHandle(cast(HWND)msg.lParam); if(ctrl) { //ctrl.prepareDc(cast(HDC)msg.wParam); //msg.result = cast(LRESULT)ctrl.hbrBg; ctrl.onReflectedMessage(msg); return; } } break; case WM_WINDOWPOSCHANGED: { WINDOWPOS* wp = cast(WINDOWPOS*)msg.lParam; bool needLayout = false; //if(!wp.hwndInsertAfter) // wp.flags |= SWP_NOZORDER; // ? bool didvis = false; if(wp.flags & (SWP_HIDEWINDOW | SWP_SHOWWINDOW)) { needLayout = true; // Only if not didvis / if not recreating. if(!recreatingHandle) // Note: suppresses onVisibleChanged { if(wp.flags & SWP_HIDEWINDOW) // Hiding. _clicking = false; onVisibleChanged(EventArgs.empty); didvis = true; //break; // Showing min/max includes other flags. } } if(!(wp.flags & SWP_NOZORDER) /+ || (wp.flags & SWP_SHOWWINDOW) +/) { if(wparent) wparent.vchanged(); } if(!(wp.flags & SWP_NOMOVE)) { onMove(EventArgs.empty); } if(!(wp.flags & SWP_NOSIZE)) { if(szdraw) invalidate(true); onResize(EventArgs.empty); needLayout = true; } // Frame change results in a new client size. if(wp.flags & SWP_FRAMECHANGED) { if(szdraw) invalidate(true); needLayout = true; } if(!didvis) // onVisibleChanged already triggers layout. { if(/+ (wp.flags & SWP_SHOWWINDOW) || +/ !(wp.flags & SWP_NOSIZE) || !(wp.flags & SWP_NOZORDER)) // z-order determines what is positioned first. { suspendLayout(); // Note: exception could cause failure to restore. if(wparent) wparent.alayout(this); resumeLayout(false); needLayout = true; } if(needLayout) { alayout(this); } } } break; case WM_WINDOWPOSCHANGING: { WINDOWPOS* wp = cast(WINDOWPOS*)msg.lParam; if (!(wp.flags & SWP_NOMOVE) && (location.x != wp.x || location.y != wp.y)) { scope e = new MovingEventArgs(Point(wp.x, wp.y)); onMoving(e); wp.x = e.x; wp.y = e.y; } if (!(wp.flags & SWP_NOSIZE) && (width != wp.cx || height != wp.cy)) { scope e = new SizingEventArgs(Size(wp.cx, wp.cy)); onSizing(e); wp.cx = e.width; wp.cy = e.height; } } break; case WM_MOUSEMOVE: if(_clicking) { if(!(msg.wParam & MK_LBUTTON)) _clicking = false; } if(trackMouseEvent) // Requires Windows 95 with IE 5.5, 98 or NT4. { if(!menter) { menter = true; POINT pt; GetCursorPos(&pt); MapWindowPoints(HWND.init, hwnd, &pt, 1); scope MouseEventArgs mea = new MouseEventArgs(wparamMouseButtons(msg.wParam), 0, pt.x, pt.y, 0); onMouseEnter(mea); TRACKMOUSEEVENT tme; tme.cbSize = TRACKMOUSEEVENT.sizeof; tme.dwFlags = TME_HOVER | TME_LEAVE; tme.hwndTrack = msg.hWnd; tme.dwHoverTime = HOVER_DEFAULT; trackMouseEvent(&tme); } } onMouseMove(new MouseEventArgs(wparamMouseButtons(msg.wParam), 0, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0)); break; case WM_SETCURSOR: // Just update it so that Control.defWndProc() can set it correctly. if(cast(HWND)msg.wParam == hwnd) { Cursor cur; cur = cursor; if(cur) { if(cast(HCURSOR)GetClassLongPtrA(hwnd, GCL_HCURSOR) != cur.handle) SetClassLongPtrA(hwnd, GCL_HCURSOR, cast(LONG_PTR)cur.handle); } else { if(cast(HCURSOR)GetClassLongPtrA(hwnd, GCL_HCURSOR) != HCURSOR.init) SetClassLongPtrA(hwnd, GCL_HCURSOR, cast(LONG_PTR)cast(HCURSOR)null); } Control.defWndProc(msg); return; } break; /+ case WM_NEXTDLGCTL: if(!LOWORD(msg.lParam)) { select(true, msg.wParam != 0); return; } break; +/ case WM_KEYDOWN: case WM_KEYUP: case WM_CHAR: case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_SYSCHAR: //case WM_IMECHAR: /+ if(processKeyEventArgs(msg)) { // The key was processed. msg.result = 0; return; } msg.result = 1; // The key was not processed. break; +/ msg.result = !processKeyEventArgs(msg); return; case WM_MOUSEWHEEL: // Requires Windows 98 or NT4. { scope MouseEventArgs mea = new MouseEventArgs(wparamMouseButtons(LOWORD(msg.wParam)), 0, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), cast(short)HIWORD(msg.wParam)); onMouseWheel(mea); } break; case WM_MOUSEHOVER: // Requires Windows 95 with IE 5.5, 98 or NT4. { scope MouseEventArgs mea = new MouseEventArgs(wparamMouseButtons(msg.wParam), 0, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseHover(mea); } break; case WM_MOUSELEAVE: // Requires Windows 95 with IE 5.5, 98 or NT4. { menter = false; POINT pt; GetCursorPos(&pt); MapWindowPoints(HWND.init, hwnd, &pt, 1); scope MouseEventArgs mea = new MouseEventArgs(wparamMouseButtons(msg.wParam), 0, pt.x, pt.y, 0); onMouseLeave(mea); } break; case WM_LBUTTONDOWN: { _clicking = true; scope MouseEventArgs mea = new MouseEventArgs(MouseButtons.LEFT, 1, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseDown(mea); //if(ctrlStyle & ControlStyles.SELECTABLE) // SetFocus(hwnd); // No, this goofs up stuff, including the ComboBox dropdown. } break; case WM_RBUTTONDOWN: { scope MouseEventArgs mea = new MouseEventArgs(MouseButtons.RIGHT, 1, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseDown(mea); } break; case WM_MBUTTONDOWN: { scope MouseEventArgs mea = new MouseEventArgs(MouseButtons.MIDDLE, 1, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseDown(mea); } break; case WM_LBUTTONUP: { if(msg.lParam == -1) break; // Use temp in case of exception. bool wasClicking = _clicking; _clicking = false; scope MouseEventArgs mea = new MouseEventArgs(MouseButtons.LEFT, 1, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseUp(mea); if(wasClicking && (ctrlStyle & ControlStyles.STANDARD_CLICK)) { // See if the mouse up was over the control. if(Rect(0, 0, wclientsz.width, wclientsz.height).contains(mea.x, mea.y)) { // Now make sure there's no child in the way. //if(ChildWindowFromPoint(hwnd, Point(mea.x, mea.y).point) == hwnd) // Includes hidden windows. if(pointOverVisibleChild(Point(mea.x, mea.y)) == hwnd) onClick(EventArgs.empty); } } } break; version(CUSTOM_MSG_HOOK) {} else { case WM_DRAWITEM: { Control ctrl; DRAWITEMSTRUCT* dis = cast(DRAWITEMSTRUCT*)msg.lParam; if(dis.CtlType == ODT_MENU) { // dis.hwndItem is the HMENU. } else { ctrl = Control.fromChildHandle(dis.hwndItem); if(ctrl) { //msg.result = ctrl.customMsg(*(cast(CustomMsg*)&msg)); ctrl.onReflectedMessage(msg); return; } } } break; case WM_MEASUREITEM: { Control ctrl; MEASUREITEMSTRUCT* mis = cast(MEASUREITEMSTRUCT*)msg.lParam; if(!(mis.CtlType == ODT_MENU)) { ctrl = Control.fromChildHandle(cast(HWND)mis.CtlID); if(ctrl) { //msg.result = ctrl.customMsg(*(cast(CustomMsg*)&msg)); ctrl.onReflectedMessage(msg); return; } } } break; case WM_COMMAND: { /+ switch(LOWORD(msg.wParam)) { case IDOK: case IDCANCEL: if(parent) { parent.wndProc(msg); } //break; return; // ? default: } +/ Control ctrl; ctrl = Control.fromChildHandle(cast(HWND)msg.lParam); if(ctrl) { //msg.result = ctrl.customMsg(*(cast(CustomMsg*)&msg)); ctrl.onReflectedMessage(msg); return; } else { version(DFL_NO_MENUS) { } else { MenuItem m; m = cast(MenuItem)Application.lookupMenuID(LOWORD(msg.wParam)); if(m) { //msg.result = m.customMsg(*(cast(CustomMsg*)&msg)); m._reflectMenu(msg); //return; // ? } } } } break; case WM_NOTIFY: { Control ctrl; NMHDR* nmh; nmh = cast(NMHDR*)msg.lParam; ctrl = Control.fromChildHandle(nmh.hwndFrom); if(ctrl) { //msg.result = ctrl.customMsg(*(cast(CustomMsg*)&msg)); ctrl.onReflectedMessage(msg); return; } } break; version(DFL_NO_MENUS) { } else { case WM_MENUSELECT: { UINT mflags; UINT uitem; int mid; MenuItem m; mflags = HIWORD(msg.wParam); uitem = LOWORD(msg.wParam); // Depends on the flags. if(mflags & MF_SYSMENU) break; if(mflags & MF_POPUP) { // -uitem- is an index. mid = GetMenuItemID(cast(HMENU)msg.lParam, uitem); } else { // -uitem- is the item identifier. mid = uitem; } m = cast(MenuItem)Application.lookupMenuID(mid); if(m) { //msg.result = m.customMsg(*(cast(CustomMsg*)&msg)); m._reflectMenu(msg); //return; } } break; case WM_INITMENUPOPUP: if(HIWORD(msg.lParam)) { // System menu. } else { MenuItem m; //m = cast(MenuItem)Application.lookupMenuID(GetMenuItemID(cast(HMENU)msg.wParam, LOWORD(msg.lParam))); m = cast(MenuItem)Application.lookupMenu(cast(HMENU)msg.wParam); if(m) { //msg.result = m.customMsg(*(cast(CustomMsg*)&msg)); m._reflectMenu(msg); //return; } } break; case WM_INITMENU: { ContextMenu m; m = cast(ContextMenu)Application.lookupMenu(cast(HMENU)msg.wParam); if(m) { //msg.result = m.customMsg(*(cast(CustomMsg*)&msg)); m._reflectMenu(msg); //return; } } break; } } case WM_RBUTTONUP: { scope MouseEventArgs mea = new MouseEventArgs(MouseButtons.RIGHT, 1, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseUp(mea); } break; case WM_MBUTTONUP: { scope MouseEventArgs mea = new MouseEventArgs(MouseButtons.MIDDLE, 1, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseUp(mea); } break; case WM_LBUTTONDBLCLK: { scope MouseEventArgs mea = new MouseEventArgs(MouseButtons.LEFT, 2, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseDown(mea); if((ctrlStyle & (ControlStyles.STANDARD_CLICK | ControlStyles.STANDARD_DOUBLE_CLICK)) == (ControlStyles.STANDARD_CLICK | ControlStyles.STANDARD_DOUBLE_CLICK)) { onDoubleClick(EventArgs.empty); } } break; case WM_RBUTTONDBLCLK: { scope MouseEventArgs mea = new MouseEventArgs(MouseButtons.RIGHT, 2, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseDown(mea); } break; case WM_MBUTTONDBLCLK: { scope MouseEventArgs mea = new MouseEventArgs(MouseButtons.MIDDLE, 2, cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam), 0); onMouseDown(mea); } break; case WM_SETFOCUS: _wmSetFocus(); // defWndProc* Form focuses a child. break; case WM_KILLFOCUS: _wmKillFocus(); break; case WM_ENABLE: onEnabledChanged(EventArgs.empty); // defWndProc* break; /+ case WM_NEXTDLGCTL: if(msg.wParam && !LOWORD(msg.lParam)) { HWND hwf; hwf = GetFocus(); if(hwf) { Control hwc; hwc = Control.fromHandle(hwf); if(hwc) { if(hwc._rtype() & 0x20) // TabControl { hwf = GetWindow(hwf, GW_CHILD); if(hwf) { // Can't do this because it could be modifying someone else's memory. //msg.wParam = cast(WPARAM)hwf; //msg.lParam = MAKELPARAM(1, 0); msg.result = DefWindowProcA(msg.hWnd, WM_NEXTDLGCTL, cast(WPARAM)hwf, MAKELPARAM(TRUE, 0)); return; } } } } } break; +/ case WM_SETTEXT: defWndProc(msg); // Need to fetch it because cast(char*)lparam isn't always accessible ? // Should this go in _wndProc()? Need to defWndProc() first ? if(ctrlStyle & ControlStyles.CACHE_TEXT) wtext = _fetchText(); onTextChanged(EventArgs.empty); return; case WM_SETFONT: // Don't replace -wfont- if it's the same one, beacuse the old Font // object will get garbage collected and probably delete the HFONT. //onFontChanged(EventArgs.empty); // defWndProc* return; /+ case WM_STYLECHANGED: { //defWndProc(msg); STYLESTRUCT* ss = cast(STYLESTRUCT*)msg.lParam; DWORD changed = ss.styleOld ^ ss.styleNew; if(msg.wParam == GWL_EXSTYLE) { //if(changed & WS_EX_RTLREADING) // onRightToLeftChanged(EventArgs.empty); } } break; +/ case WM_ACTIVATE: switch(LOWORD(msg.wParam)) { case WA_INACTIVE: _clicking = false; break; default: } break; version(DFL_NO_MENUS) { } else { case WM_CONTEXTMENU: if(hwnd == cast(HWND)msg.wParam) { if(cmenu) { // Shift+F10 causes xPos and yPos to be -1. Point point; if(msg.lParam == -1) point = pointToScreen(Point(0, 0)); else point = Point(cast(short)LOWORD(msg.lParam), cast(short)HIWORD(msg.lParam)); SetFocus(handle); // ? cmenu.show(this, point); return; } } break; } case WM_HELP: { HELPINFO* hi = cast(HELPINFO*)msg.lParam; scope HelpEventArgs hea = new HelpEventArgs(Point(hi.MousePos.x, hi.MousePos.y)); onHelpRequested(hea); if(hea.handled) { msg.result = TRUE; return; } } break; case WM_SYSCOLORCHANGE: onSystemColorsChanged(EventArgs.empty); // Need to send the message to children for some common controls to update properly. foreach(Control ctrl; ccollection) { SendMessageA(ctrl.handle, WM_SYSCOLORCHANGE, msg.wParam, msg.lParam); } break; case WM_SETTINGCHANGE: // Send the message to children. foreach(Control ctrl; ccollection) { SendMessageA(ctrl.handle, WM_SETTINGCHANGE, msg.wParam, msg.lParam); } break; case WM_PALETTECHANGED: /+ if(cast(HWND)msg.wParam != hwnd) { // Realize palette. } +/ // Send the message to children. foreach(Control ctrl; ccollection) { SendMessageA(ctrl.handle, WM_PALETTECHANGED, msg.wParam, msg.lParam); } break; //case WM_QUERYNEWPALETTE: // Send this message to children ? /+ // Moved this stuff to -parent-. case WM_PARENTNOTIFY: switch(LOWORD(msg.wParam)) { case WM_DESTROY: Control ctrl = fromChildHandle(cast(HWND)msg.lParam); if(ctrl) { _ctrlremoved(new ControlEventArgs(ctrl)); // ? vchanged(); //alayout(ctrl); // This is already being called from somewhere else.. } break; /+ case WM_CREATE: initLayout(); break; +/ default: } break; +/ case WM_CREATE: /+ if(wparent) initLayout(); // ? +/ if(cbits & CBits.NEED_INIT_LAYOUT) { if(visible) { if(wparent) { wparent.vchanged(); suspendLayout(); // Note: exception could cause failure to restore. wparent.alayout(this); resumeLayout(false); } alayout(this); } } break; case WM_DESTROY: onHandleDestroyed(EventArgs.empty); break; case WM_GETDLGCODE: { version(_DFL_WINDOWS_HUNG_WORKAROUND) { /+ if(ctrlStyle & ControlStyles.CONTAINER_CONTROL) { if(!(_exStyle & WS_EX_CONTROLPARENT)) assert(0); } +/ DWORD dw; dw = GetTickCount(); if(ldlgcode < dw - 1020) { ldlgcode = dw - 1000; } else { ldlgcode += 50; if(ldlgcode > dw) { // Probably a problem with WS_EX_CONTROLPARENT and WS_TABSTOP. if(ldlgcode >= ldlgcode.max - 10_000) { ldlgcode = 0; throw new WindowsHungDflException("Windows hung"); } //msg.result |= 0x0004 | 0x0002 | 0x0001; //DLGC_WANTALLKEYS | DLGC_WANTTAB | DLGC_WANTARROWS; ldlgcode = ldlgcode.max - 10_000; return; } } } /+ if(msg.lParam) { Message m; m._winMsg = *cast(MSG*)msg.lParam; if(processKeyEventArgs(m)) return; } +/ defWndProc(msg); if(ctrlStyle & ControlStyles.WANT_ALL_KEYS) msg.result |= DLGC_WANTALLKEYS; // Only want chars if ALT isn't down, because it would break mnemonics. if(!(GetKeyState(VK_MENU) & 0x8000)) msg.result |= DLGC_WANTCHARS; } return; case WM_CLOSE: /+{ if(parent) { Message mp; mp = msg; mp.hWnd = parent.handle; parent.wndProc(mp); // Pass to parent so it can decide what to do. } }+/ return; // Prevent defWndProc from destroying the window! case 0: // WM_NULL // Don't confuse with failed RegisterWindowMessage(). break; default: //defWndProc(msg); version(DFL_NO_WM_GETCONTROLNAME) { } else { if(msg.msg == wmGetControlName) { //cprintf("WM_GETCONTROLNAME: %.*s; wparam: %d\n", cast(uint)name.length, name.ptr, msg.wParam); if(msg.wParam && this.name.length) { OSVERSIONINFOA osver; osver.dwOSVersionInfoSize = OSVERSIONINFOA.sizeof; if(GetVersionExA(&osver)) { try { if(osver.dwPlatformId <= VER_PLATFORM_WIN32_WINDOWS) { if(dfl.internal.utf.useUnicode) { } else { // ANSI. Dstring ansi; ansi = dfl.internal.utf.toAnsi(this.name); if(msg.wParam <= ansi.length) ansi = ansi[0 .. msg.wParam - 1]; (cast(char*)msg.lParam)[0 .. ansi.length] = ansi[]; (cast(char*)msg.lParam)[ansi.length] = 0; msg.result = ansi.length + 1; } } else { // Unicode. Dwstring uni; uni = dfl.internal.utf.toUnicode(this.name); if(msg.wParam <= uni.length) uni = uni[0 .. msg.wParam - 1]; (cast(wchar*)msg.lParam)[0 .. uni.length] = uni[]; (cast(wchar*)msg.lParam)[uni.length] = 0; msg.result = uni.length + 1; } } catch(Exception) { } return; } } } } } defWndProc(msg); if(msg.msg == WM_CREATE) { EventArgs ea; ea = EventArgs.empty; onHandleCreated(ea); debug { assert(_handlecreated, "If overriding onHandleCreated(), be sure to call super.onHandleCreated()!"); } handleCreated(this, ea); debug { _handlecreated = false; // Reset. } } } package final void _wmSetFocus() { //onEnter(EventArgs.empty); onGotFocus(EventArgs.empty); // defWndProc* Form focuses a child. } package final void _wmKillFocus() { _clicking = false; //onLeave(EventArgs.empty); //if(cvalidation) // onValidating(new CancelEventArgs); onLostFocus(EventArgs.empty); } /// protected void defWndProc(ref Message msg) { //msg.result = DefWindowProcA(msg.hWnd, msg.msg, msg.wParam, msg.lParam); msg.result = dfl.internal.utf.defWindowProc(msg.hWnd, msg.msg, msg.wParam, msg.lParam); } // Always called right when destroyed, before doing anything else. // hwnd is cleared after this step. void _destroying() // package { //wparent = null; // ? } // This function must be called FIRST for EVERY message to this // window in order to keep the correct window state. // This function must not throw exceptions. package final void mustWndProc(ref Message msg) { if(needCalcSize) { needCalcSize = false; RECT crect; GetClientRect(msg.hWnd, &crect); wclientsz.width = crect.right; wclientsz.height = crect.bottom; } switch(msg.msg) { case WM_NCCALCSIZE: needCalcSize = true; break; case WM_WINDOWPOSCHANGED: { WINDOWPOS* wp = cast(WINDOWPOS*)msg.lParam; if(!recreatingHandle) { //wstyle = GetWindowLongPtrA(hwnd, GWL_STYLE); // ..WM_SHOWWINDOW. if(wp.flags & (SWP_HIDEWINDOW | SWP_SHOWWINDOW)) { //wstyle = GetWindowLongPtrA(hwnd, GWL_STYLE); cbits |= CBits.VISIBLE; wstyle |= WS_VISIBLE; if(wp.flags & SWP_HIDEWINDOW) // Hiding. { cbits &= ~CBits.VISIBLE; wstyle &= ~WS_VISIBLE; } //break; // Showing min/max includes other flags. } } //if(!(wp.flags & SWP_NOMOVE)) // wrect.location = Point(wp.x, wp.y); if(!(wp.flags & SWP_NOSIZE) || !(wp.flags & SWP_NOMOVE) || (wp.flags & SWP_FRAMECHANGED)) { //wrect = _fetchBounds(); wrect = Rect(wp.x, wp.y, wp.cx, wp.cy); wclientsz = _fetchClientSize(); } if((wp.flags & (SWP_SHOWWINDOW | SWP_HIDEWINDOW)) || !(wp.flags & SWP_NOSIZE)) { DWORD rstyle; rstyle = GetWindowLongPtrA(msg.hWnd, GWL_STYLE).toI32; rstyle &= WS_MAXIMIZE | WS_MINIMIZE; wstyle &= ~(WS_MAXIMIZE | WS_MINIMIZE); wstyle |= rstyle; } } break; /+ case WM_WINDOWPOSCHANGING: //oldwrect = wrect; break; +/ /+ case WM_SETFONT: //wfont = _fetchFont(); break; +/ case WM_STYLECHANGED: { STYLESTRUCT* ss = cast(STYLESTRUCT*)msg.lParam; if(msg.wParam == GWL_STYLE) wstyle = ss.styleNew; else if(msg.wParam == GWL_EXSTYLE) wexstyle = ss.styleNew; /+ wrect = _fetchBounds(); wclientsz = _fetchClientSize(); +/ } break; /+ // NOTE: this is sent even if the parent is shown. case WM_SHOWWINDOW: if(!msg.lParam) { /+ { cbits &= ~(CBits.SW_SHOWN | CBits.SW_HIDDEN); DWORD rstyle; rstyle = GetWindowLongPtrA(msg.hWnd, GWL_STYLE); if(cast(BOOL)msg.wParam) { //wstyle |= WS_VISIBLE; if(!(WS_VISIBLE & wstyle) && (WS_VISIBLE & rstyle)) { wstyle = rstyle; cbits |= CBits.SW_SHOWN; try { createChildren(); // Might throw. } catch(DThrowable e) { Application.onThreadException(e); } } wstyle = rstyle; } else { //wstyle &= ~WS_VISIBLE; if((WS_VISIBLE & wstyle) && !(WS_VISIBLE & rstyle)) { wstyle = rstyle; cbits |= CBits.SW_HIDDEN; } wstyle = rstyle; } } +/ wstyle = GetWindowLongPtrA(msg.hWnd, GWL_STYLE); //if(cbits & CBits.FVISIBLE) // wstyle |= WS_VISIBLE; } break; +/ case WM_ENABLE: /+ //if(IsWindowEnabled(hwnd)) if(cast(BOOL)msg.wParam) wstyle &= ~WS_DISABLED; else wstyle |= WS_DISABLED; +/ wstyle = GetWindowLongPtrA(hwnd, GWL_STYLE).toI32; break; /+ case WM_PARENTNOTIFY: switch(LOWORD(msg.wParam)) { case WM_DESTROY: // ... break; default: } break; +/ case WM_NCCREATE: { //hwnd = msg.hWnd; /+ // Not using CREATESTRUCT for window bounds because it can contain // CW_USEDEFAULT and other magic values. CREATESTRUCTA* cs; cs = cast(CREATESTRUCTA*)msg.lParam; //wrect = Rect(cs.x, cs.y, cs.cx, cs.cy); +/ wrect = _fetchBounds(); //oldwrect = wrect; wclientsz = _fetchClientSize(); } break; case WM_CREATE: try { cbits |= CBits.CREATED; //hwnd = msg.hWnd; CREATESTRUCTA* cs; cs = cast(CREATESTRUCTA*)msg.lParam; /+ // Done in WM_NCCREATE now. //wrect = _fetchBounds(); wrect = Rect(cs.x, cs.y, cs.cx, cs.cy); wclientsz = _fetchClientSize(); +/ // If class style was changed, update. if(_fetchClassLongPtr() != wclassStyle) SetClassLongPtrA(hwnd, GCL_STYLE, wclassStyle); // Need to update clientSize in case of styles in createParams(). wclientsz = _fetchClientSize(); //finishCreating(msg.hWnd); if(!(ctrlStyle & ControlStyles.CACHE_TEXT)) wtext = null; /+ // Gets created on demand instead. if(Color.empty != backc) { hbrBg = backc.createBrush(); } +/ /+ // ? wstyle = cs.style; wexstyle = cs.dwExStyle; +/ createChildren(); // Might throw. Used to be commented-out. if(recreatingHandle) { // After existing messages and functions are done. delayInvoke(function(Control cthis, size_t[] params){ cthis.cbits &= ~CBits.RECREATING; }); } } catch(DThrowable e) { Application.onThreadException(e); } break; case WM_DESTROY: cbits &= ~CBits.CREATED; if(!recreatingHandle) cbits &= ~CBits.FORMLOADED; _destroying(); //if(!killing) if(recreatingHandle) fillRecreationData(); break; case WM_NCDESTROY: Application.removeHwnd(hwnd); hwnd = HWND.init; break; default: /+ if(msg.msg == wmDfl) { switch(msg.wParam) { case WPARAM_DFL_: default: } } +/ } } package final void _wndProc(ref Message msg) { //mustWndProc(msg); // Done in dflWndProc() now. wndProc(msg); } package final void _defWndProc(ref Message msg) { defWndProc(msg); } package final void doShow() { if(wparent) // Exclude owner. { SetWindowPos(hwnd, HWND.init, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOZORDER); } else { SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW); } } package final void doHide() { SetWindowPos(hwnd, HWND.init, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOZORDER); } //EventHandler backColorChanged; Event!(Control, EventArgs) backColorChanged; /// // EventHandler backgroundImageChanged; /+ deprecated EventHandler causesValidationChanged; deprecated InvalidateEventHandler invalidated; deprecated EventHandler validated; deprecated CancelEventHandler validating; // Once cancel is true, remaining events are suppressed (including validated). deprecated EventHandler enter; // Cascades up. TODO: fix implementation. deprecated EventHandler leave; // Cascades down. TODO: fix implementation. deprecated UICuesEventHandler changeUICues; // TODO: properly fire. +/ //EventHandler click; Event!(Control, EventArgs) click; /// version(DFL_NO_MENUS) { } else { //EventHandler contextMenuChanged; Event!(Control, EventArgs) contextMenuChanged; /// } //ControlEventHandler controlAdded; Event!(Control, ControlEventArgs) controlAdded; /// //ControlEventHandler controlRemoved; Event!(Control, ControlEventArgs) controlRemoved; /// //EventHandler cursorChanged; Event!(Control, EventArgs) cursorChanged; /// //EventHandler disposed; Event!(Control, EventArgs) disposed; /// //EventHandler dockChanged; //Event!(Control, EventArgs) dockChanged; /// Event!(Control, EventArgs) hasLayoutChanged; /// alias hasLayoutChanged dockChanged; //EventHandler doubleClick; Event!(Control, EventArgs) doubleClick; /// //EventHandler enabledChanged; Event!(Control, EventArgs) enabledChanged; /// //EventHandler fontChanged; Event!(Control, EventArgs) fontChanged; /// //EventHandler foreColorChanged; Event!(Control, EventArgs) foreColorChanged; /// //EventHandler gotFocus; // After enter. Event!(Control, EventArgs) gotFocus; /// //EventHandler handleCreated; Event!(Control, EventArgs) handleCreated; /// //EventHandler handleDestroyed; Event!(Control, EventArgs) handleDestroyed; /// //HelpEventHandler helpRequested; Event!(Control, HelpEventArgs) helpRequested; /// //KeyEventHandler keyDown; Event!(Control, KeyEventArgs) keyDown; /// //KeyEventHandler keyPress; Event!(Control, KeyPressEventArgs) keyPress; /// //KeyEventHandler keyUp; Event!(Control, KeyEventArgs) keyUp; /// //LayoutEventHandler layout; Event!(Control, LayoutEventArgs) layout; /// //EventHandler lostFocus; Event!(Control, EventArgs) lostFocus; /// //MouseEventHandler mouseDown; Event!(Control, MouseEventArgs) mouseDown; /// //MouseEventHandler mouseEnter; Event!(Control, MouseEventArgs) mouseEnter; /// //MouseEventHandler mouseHover; Event!(Control, MouseEventArgs) mouseHover; /// //MouseEventHandler mouseLeave; Event!(Control, MouseEventArgs) mouseLeave; /// //MouseEventHandler mouseMove; Event!(Control, MouseEventArgs) mouseMove; /// //MouseEventHandler mouseUp; Event!(Control, MouseEventArgs) mouseUp; /// //MouseEventHandler mouseWheel; Event!(Control, MouseEventArgs) mouseWheel; /// //EventHandler moving; Event!(Control, MovingEventArgs) moving; /// //EventHandler move; Event!(Control, EventArgs) move; /// //EventHandler locationChanged; alias move locationChanged; //PaintEventHandler paint; Event!(Control, PaintEventArgs) paint; /// //EventHandler parentChanged; Event!(Control, EventArgs) parentChanged; /// //EventHandler sizing; Event!(Control, SizingEventArgs) sizing; /// //EventHandler resize; Event!(Control, EventArgs) resize; /// //EventHandler sizeChanged; alias resize sizeChanged; //EventHandler rightToLeftChanged; Event!(Control, EventArgs) rightToLeftChanged; /// // EventHandler styleChanged; //EventHandler systemColorsChanged; Event!(Control, EventArgs) systemColorsChanged; /// // EventHandler tabIndexChanged; // EventHandler tabStopChanged; //EventHandler textChanged; Event!(Control, EventArgs) textChanged; /// //EventHandler visibleChanged; Event!(Control, EventArgs) visibleChanged; /// version(DFL_NO_DRAG_DROP) {} else { //DragEventHandler dragDrop; Event!(Control, DragEventArgs) dragDrop; /// //DragEventHandler dragEnter; Event!(Control, DragEventArgs) dragEnter; /// //EventHandler dragLeave; Event!(Control, EventArgs) dragLeave; /// //DragEventHandler dragOver; Event!(Control, DragEventArgs) dragOver; /// //GiveFeedbackEventHandler giveFeedback; Event!(Control, GiveFeedbackEventArgs) giveFeedback; /// //QueryContinueDragEventHandler queryContinueDrag; Event!(Control, QueryContinueDragEventArgs) queryContinueDrag; /// } /// Construct a new Control instance. this() { //name = DObject.toString(); // ? wrect.size = defaultSize; //oldwrect = wrect; /+ backc = defaultBackColor; forec = defaultForeColor; wfont = defaultFont; wcurs = new Cursor(LoadCursorA(HINSTANCE.init, IDC_ARROW), false); +/ backc = Color.empty; forec = Color.empty; wfont = null; wcurs = null; ccollection = createControlsInstance(); } /// ditto this(Dstring text) { this(); wtext = text; ccollection = createControlsInstance(); } /// ditto this(Control cparent, Dstring text) { this(); wtext = text; parent = cparent; ccollection = createControlsInstance(); } /// ditto this(Dstring text, int left, int top, int width, int height) { this(); wtext = text; wrect = Rect(left, top, width, height); ccollection = createControlsInstance(); } /// ditto this(Control cparent, Dstring text, int left, int top, int width, int height) { this(); wtext = text; wrect = Rect(left, top, width, height); parent = cparent; ccollection = createControlsInstance(); } /+ // Used internally. this(HWND hwnd) in { assert(hwnd); } do { this.hwnd = hwnd; owned = false; ccollection = new ControlCollection(this); } +/ ~this() { debug(APP_PRINT) cprintf("~Control %p\n", cast(void*)this); version(DFL_NO_ZOMBIE_FORM) { } else { Application.zombieKill(this); // Does nothing if not zombie. } //dispose(false); destroyHandle(); deleteThisBackgroundBrush(); } /+ package +/ /+ protected +/ int _rtype() // package { return 0; } /// void dispose() { dispose(true); } /// ditto protected void dispose(bool disposing) { if(disposing) { killing = true; version(DFL_NO_MENUS) { } else { cmenu = cmenu.init; } _ctrlname = _ctrlname.init; otag = otag.init; wcurs = wcurs.init; wfont = wfont.init; wparent = wparent.init; wregion = wregion.init; wtext = wtext.init; deleteThisBackgroundBrush(); //ccollection.children = null; // Not GC-safe in dtor. //ccollection = null; // ? Causes bad things. Leaving it will do just fine. } if(!isHandleCreated) return; destroyHandle(); /+ //assert(hwnd == HWND.init); // Zombie trips this. (Not anymore with the hwnd-prop) if(hwnd) { assert(!IsWindow(hwnd)); hwnd = HWND.init; } +/ assert(hwnd == HWND.init); onDisposed(EventArgs.empty); } protected: /// @property Size defaultSize() // getter { return Size(0, 0); } /+ // TODO: implement. @property EventHandlerList events() // getter { } +/ /+ // TODO: implement. Is this worth implementing? // Set to -1 to reset cache. final @property void fontHeight(int fh) // setter { } final @property int fontHeight() // getter { return fonth; } +/ /// //final void resizeRedraw(bool byes) // setter public final @property void resizeRedraw(bool byes) // setter { /+ // These class styles get lost sometimes so don't rely on them. LONG cl = _classStyle(); if(byes) cl |= CS_HREDRAW | CS_VREDRAW; else cl &= ~(CS_HREDRAW | CS_VREDRAW); _classStyle(cl); +/ szdraw = byes; } /// ditto final @property bool resizeRedraw() // getter { //return (_classStyle() & (CS_HREDRAW | CS_VREDRAW)) != 0; return szdraw; } /+ // /// // I don't think this is reliable. final bool hasVisualStyle() // getter { bool result = false; HWND hw = handle; // Always reference handle. HMODULE huxtheme = GetModuleHandleA("uxtheme.dll"); //HMODULE huxtheme = LoadLibraryA("uxtheme.dll"); if(huxtheme) { auto getwintheme = cast(typeof(&GetWindowTheme))GetProcAddress(huxtheme, "GetWindowTheme"); if(getwintheme) { result = getwintheme(hw) != null; } //FreeLibrary(huxtheme); } return result; } +/ package final void _disableVisualStyle() { assert(isHandleCreated); HMODULE hmuxt; hmuxt = GetModuleHandleA("uxtheme.dll"); if(hmuxt) { auto setWinTheme = cast(typeof(&SetWindowTheme))GetProcAddress(hmuxt, "SetWindowTheme"); if(setWinTheme) { setWinTheme(hwnd, " "w.ptr, " "w.ptr); // Clear the theme. } } } /// public final void disableVisualStyle(bool byes = true) { if(!byes) { if(cbits & CBits.VSTYLE) return; cbits |= CBits.VSTYLE; if(isHandleCreated) { _crecreate(); } } else { if(!(cbits & CBits.VSTYLE)) return; cbits &= ~CBits.VSTYLE; if(isHandleCreated) _disableVisualStyle(); } } deprecated public final void enableVisualStyle(bool byes = true) { return disableVisualStyle(!byes); } /// ControlCollection createControlsInstance() { return new ControlCollection(this); } deprecated package final void createClassHandle(Dstring className) { if(!wparent || !wparent.handle || killing) { create_err: throw new DflException("Control creation failure"); } // This is here because referencing wparent.handle might create me. //if(created) if(isHandleCreated) return; Application.creatingControl(this); hwnd = dfl.internal.utf.createWindowEx(wexstyle, className, wtext, wstyle, wrect.x, wrect.y, wrect.width, wrect.height, wparent.handle, HMENU.init, Application.getInstance(), null); if(!hwnd) goto create_err; } /// // Override to change the creation parameters. // Be sure to call super.createParams() or all the create params will need to be filled. protected void createParams(ref CreateParams cp) { with(cp) { className = CONTROL_CLASSNAME; caption = wtext; param = null; //parent = wparent.handle; parent = wparent ? wparent.handle : HWND.init; menu = HMENU.init; inst = Application.getInstance(); x = wrect.x; y = wrect.y; width = wrect.width; height = wrect.height; classStyle = wclassStyle; exStyle = wexstyle; wstyle |= WS_VISIBLE; if(!(cbits & CBits.VISIBLE)) wstyle &= ~WS_VISIBLE; style = wstyle; } } /// protected void createHandle() { // Note: if modified, Form.createHandle() should be modified as well. if(isHandleCreated) return; //createClassHandle(CONTROL_CLASSNAME); /+ if(!wparent || !wparent.handle || killing) { create_err: //throw new DflException("Control creation failure"); throw new DflException(Object.toString() ~ " creation failure"); // ? } +/ debug { Dstring er; } if(killing) { debug { er = "the control is being disposed"; } debug(APP_PRINT) { cprintf("Creating Control handle while disposing.\n"); } create_err: Dstring kmsg = "Control creation failure"; if(name.length) kmsg ~= " (" ~ name ~ ")"; debug { if(er.length) kmsg ~= " - " ~ er; } throw new DflException(kmsg); //throw new DflException(Object.toString() ~ " creation failure"); // ? } // Need the parent's handle to exist. if(wparent) wparent.createHandle(); // This is here because wparent.createHandle() might create me. //if(created) if(isHandleCreated) return; CreateParams cp; /+ DWORD prevClassStyle; prevClassStyle = wclassStyle; +/ createParams(cp); assert(!isHandleCreated); // Make sure the handle wasn't created in createParams(). with(cp) { wtext = caption; //wrect = Rect(x, y, width, height); // This gets updated in WM_CREATE. wclassStyle = classStyle; wexstyle = exStyle; wstyle = style; //if(style & WS_CHILD) // Breaks context-help. if((ctrlStyle & ControlStyles.CONTAINER_CONTROL) && (style & WS_CHILD)) { exStyle |= WS_EX_CONTROLPARENT; } bool vis = (style & WS_VISIBLE) != 0; Application.creatingControl(this); hwnd = dfl.internal.utf.createWindowEx(exStyle, className, caption, (style & ~WS_VISIBLE), x, y, width, height, parent, menu, inst, param); if(!hwnd) { debug(APP_PRINT) { cprintf("CreateWindowEx failed." ~" (exStyle=0x%X, className=`%.*s`, caption=`%.*s`, style=0x%X, x=%d, y=%d, width=%d, height=%d," ~" parent=0x%X, menu=0x%X, inst=0x%X, param=0x%X)\n", exStyle, className.ptr, caption.ptr, style, x, y, width, height, parent, menu, inst, param); } debug { er = std.string.format("CreateWindowEx failed {className=%s;exStyle=0x%X;style=0x%X;parent=0x%X;menu=0x%X;inst=0x%X;}", className, exStyle, style, cast(void*)parent, cast(void*)menu, cast(void*)inst); } goto create_err; } if(vis) doShow(); // Properly fires onVisibleChanged. } //onHandleCreated(EventArgs.empty); // Called in WM_CREATE now. } package final void _createHandle() { createHandle(); } /// public final @property bool recreatingHandle() // getter { if(cbits & CBits.RECREATING) return true; return false; } private void _setAllRecreating() { cbits |= CBits.RECREATING; foreach(Control cc; controls) { cc._setAllRecreating(); } } /// protected void recreateHandle() in { assert(!recreatingHandle); } do { if(!isHandleCreated) return; if(recreatingHandle) return; bool hfocus = focused; HWND prevHwnd = GetWindow(hwnd, GW_HWNDPREV); _setAllRecreating(); //scope(exit) // cbits &= ~CBits.RECREATING; // Now done from WM_CREATE. destroyHandle(); createHandle(); if(prevHwnd) SetWindowPos(hwnd, prevHwnd, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); else SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); if(hfocus) select(); } /// void destroyHandle() { if(!isHandleCreated) return; DestroyWindow(hwnd); // This stuff is done in WM_DESTROY because DestroyWindow() could be called elsewhere.. //hwnd = HWND.init; // Done in WM_DESTROY. //onHandleDestroyed(EventArgs.empty); // Done in WM_DESTROY. } private final void fillRecreationData() { //cprintf(" { fillRecreationData %.*s }\n", name); if(!(ctrlStyle & ControlStyles.CACHE_TEXT)) wtext = _fetchText(); //wclassStyle = _fetchClassLongPtr(); // ? // Fetch children. Control[] ccs; foreach(Control cc; controls) { ccs ~= cc; } ccollection.children = ccs; } /// protected void onDisposed(EventArgs ea) { disposed(this, ea); } /// protected final bool getStyle(ControlStyles flag) { return (ctrlStyle & flag) != 0; } /// ditto protected final void setStyle(ControlStyles flag, bool value) { if(flag & ControlStyles.CACHE_TEXT) { if(value) wtext = _fetchText(); else wtext = null; } if(value) ctrlStyle |= flag; else ctrlStyle &= ~flag; } /// // Only for setStyle() styles that are part of hwnd and wndclass styles. protected final void updateStyles() { LONG newClassStyles = _classStyle(); LONG newWndStyles = _style(); if(ctrlStyle & ControlStyles.STANDARD_DOUBLE_CLICK) newClassStyles |= CS_DBLCLKS; else newClassStyles &= ~CS_DBLCLKS; /+ if(ctrlStyle & ControlStyles.RESIZE_REDRAW) newClassStyles |= CS_HREDRAW | CS_VREDRAW; else newClassStyles &= ~(CS_HREDRAW | CS_VREDRAW); +/ /+ if(ctrlStyle & ControlStyles.SELECTABLE) newWndStyles |= WS_TABSTOP; else newWndStyles &= ~WS_TABSTOP; +/ _classStyle(newClassStyles); _style(newWndStyles); } /// final bool getTopLevel() { // return GetParent(hwnd) == HWND.init; return wparent is null; } package final void alayout(Control ctrl, bool vcheck = true) { if(vcheck && !visible) return; if(cbits & CBits.IN_LAYOUT) return; //if(_allowLayout) if(!_disallowLayout) { //cprintf("alayout\n"); scope LayoutEventArgs lea = new LayoutEventArgs(ctrl); onLayout(lea); } } // Z-order of controls has changed. package final void vchanged() { // Z-order can't change if it's not created or invisible. //if(!isHandleCreated || !visible) // return; version(RADIO_GROUP_LAYOUT) { //cprintf("vchanged\n"); bool foundRadio = false; foreach(Control ctrl; ccollection) { if(!ctrl.visible) continue; if(ctrl._rtype() & 1) // Radio type. { LONG wlg; wlg = ctrl._style(); if(foundRadio) { if(wlg & WS_GROUP) //ctrl._style(wlg & ~WS_GROUP); ctrl._style(wlg & ~(WS_GROUP | WS_TABSTOP)); } else { foundRadio = true; if(!(wlg & WS_GROUP)) //ctrl._style(wlg | WS_GROUP); ctrl._style(wlg | WS_GROUP | WS_TABSTOP); } } else { // Found non-radio so reset group. // Update: only reset group if found ctrl with WS_EX_CONTROLPARENT. // TODO: check if correct implementation. if(ctrl._exStyle() & WS_EX_CONTROLPARENT) foundRadio = false; } } } } /// // Called after adding the control to a container. protected void initLayout() { assert(wparent !is null); if(visible && created) // ? { wparent.vchanged(); wparent.alayout(this); } } /// protected void onLayout(LayoutEventArgs lea) { // Note: exception could cause failure to restore. //suspendLayout(); cbits |= CBits.IN_LAYOUT; debug(EVENT_PRINT) { cprintf("{ Event: onLayout - Control %.*s }\n", name); } Rect area; area = displayRectangle; foreach(Control ctrl; ccollection) { if(!ctrl.visible || !ctrl.created) continue; if(ctrl._rtype() & (2 | 4)) // Mdichild | Tabpage continue; //Rect prevctrlbounds; //prevctrlbounds = ctrl.bounds; //ctrl.suspendLayout(); // Note: exception could cause failure to restore. switch(ctrl.sdock) { case DockStyle.NONE: /+ if(ctrl.anch & (AnchorStyles.RIGHT | AnchorStyles.BOTTOM)) // If none of these are set, no point in doing any anchor code. { Rect newb; newb = ctrl.bounds; if(ctrl.anch & AnchorStyles.RIGHT) { if(ctrl.anch & AnchorStyles.LEFT) newb.width += bounds.width - originalBounds.width; else newb.x += bounds.width - originalBounds.width; } if(ctrl.anch & AnchorStyles.BOTTOM) { if(ctrl.anch & AnchorStyles.LEFT) newb.height += bounds.height - originalBounds.height; else newb.y += bounds.height - originalBounds.height; } if(newb != ctrl.bounds) ctrl.bounds = newb; } +/ break; case DockStyle.LEFT: ctrl.setBoundsCore(area.x, area.y, 0, area.height, cast(BoundsSpecified)(BoundsSpecified.LOCATION | BoundsSpecified.HEIGHT)); area.x = area.x + ctrl.width; area.width = area.width - ctrl.width; break; case DockStyle.TOP: ctrl.setBoundsCore(area.x, area.y, area.width, 0, cast(BoundsSpecified)(BoundsSpecified.LOCATION | BoundsSpecified.WIDTH)); area.y = area.y + ctrl.height; area.height = area.height - ctrl.height; break; case DockStyle.FILL: //ctrl.bounds(Rect(area.x, area.y, area.width, area.height)); ctrl.bounds = area; // area = ? break; case DockStyle.BOTTOM: ctrl.setBoundsCore(area.x, area.bottom - ctrl.height, area.width, 0, cast(BoundsSpecified)(BoundsSpecified.LOCATION | BoundsSpecified.WIDTH)); area.height = area.height - ctrl.height; break; case DockStyle.RIGHT: ctrl.setBoundsCore(area.right - ctrl.width, area.y, 0, area.height, cast(BoundsSpecified)(BoundsSpecified.LOCATION | BoundsSpecified.HEIGHT)); area.width = area.width - ctrl.width; break; default: assert(0); } //ctrl.resumeLayout(true); //ctrl.resumeLayout(prevctrlbounds != ctrl.bounds); } layout(this, lea); //resumeLayout(false); cbits &= ~CBits.IN_LAYOUT; } /+ // Not sure what to do here. deprecated bool isInputChar(char charCode) { return false; } +/ /// void setVisibleCore(bool byes) { if(isHandleCreated) { //wstyle = GetWindowLongPtrA(hwnd, GWL_STYLE); if(visible == byes) return; //ShowWindow(hwnd, byes ? SW_SHOW : SW_HIDE); if(byes) doShow(); else doHide(); } else { if(byes) { cbits |= CBits.VISIBLE; wstyle |= WS_VISIBLE; createControl(); } else { cbits &= ~CBits.VISIBLE; wstyle &= ~WS_VISIBLE; return; // Not created and being hidden.. } } } package final bool _wantTabKey() { if(ctrlStyle & ControlStyles.WANT_TAB_KEY) return true; return false; } /// // Return true if processed. protected bool processKeyEventArgs(ref Message msg) { switch(msg.msg) { case WM_KEYDOWN: { scope KeyEventArgs kea = new KeyEventArgs(cast(Keys)(msg.wParam | modifierKeys)); ushort repeat = msg.lParam & 0xFFFF; // First 16 bits. for(; repeat; repeat--) { //kea.handled = false; onKeyDown(kea); } if(kea.handled) return true; } break; case WM_KEYUP: { // Repeat count is always 1 for key up. scope KeyEventArgs kea = new KeyEventArgs(cast(Keys)(msg.wParam | modifierKeys)); onKeyUp(kea); if(kea.handled) return true; } break; case WM_CHAR: { scope KeyPressEventArgs kpea = new KeyPressEventArgs(cast(dchar)msg.wParam, modifierKeys); onKeyPress(kpea); if(kpea.handled) return true; } break; default: } defWndProc(msg); return !msg.result; } package final bool _processKeyEventArgs(ref Message msg) { return processKeyEventArgs(msg); } /+ bool processKeyPreview(ref Message m) { if(wparent) return wparent.processKeyPreview(m); return false; } protected bool processDialogChar(dchar charCode) { if(wparent) return wparent.processDialogChar(charCode); return false; } +/ /// protected bool processMnemonic(dchar charCode) { return false; } package bool _processMnemonic(dchar charCode) { return processMnemonic(charCode); } // Retain DFL 0.9.5 compatibility. public deprecated void setDFL095() { version(SET_DFL_095) { pragma(msg, "DFL: DFL 0.9.5 compatibility set at compile time"); } else { //_compat = CCompat.DFL095; Application.setCompat(DflCompat.CONTROL_RECREATE_095); } } package enum CCompat: ubyte { NONE = 0, DFL095 = 1, } version(SET_DFL_095) package enum _compat = CCompat.DFL095; else version(DFL_NO_COMPAT) package enum _compat = CCompat.NONE; else package @property CCompat _compat() // getter { if(Application._compat & DflCompat.CONTROL_RECREATE_095) return CCompat.DFL095; return CCompat.NONE; } package final void _crecreate() { if(CCompat.DFL095 != _compat) { if(!recreatingHandle) recreateHandle(); } } package: HWND hwnd; //AnchorStyles anch = cast(AnchorStyles)(AnchorStyles.TOP | AnchorStyles.LEFT); //bool cvalidation = true; version(DFL_NO_MENUS) { } else { ContextMenu cmenu; } DockStyle sdock = DockStyle.NONE; Dstring _ctrlname; Object otag; Color backc, forec; Rect wrect; //Rect oldwrect; Size wclientsz; Cursor wcurs; Font wfont; Control wparent; Region wregion; ControlCollection ccollection; Dstring wtext; // After creation, this isn't used unless ControlStyles.CACHE_TEXT. ControlStyles ctrlStyle = ControlStyles.STANDARD_CLICK | ControlStyles.STANDARD_DOUBLE_CLICK /+ | ControlStyles.RESIZE_REDRAW +/ ; HBRUSH _hbrBg; RightToLeft rtol = RightToLeft.INHERIT; uint _disallowLayout = 0; version(DFL_NO_DRAG_DROP) {} else { DropTarget droptarget = null; } // Note: WS_VISIBLE is not reliable. LONG wstyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; // Child, visible and enabled by default. LONG wexstyle; LONG wclassStyle = WNDCLASS_STYLE; enum CBits: uint { NONE = 0x0, MENTER = 0x1, // Is mouse entered? Only valid if -trackMouseEvent- is non-null. KILLING = 0x2, OWNED = 0x4, //ALLOW_LAYOUT = 0x8, CLICKING = 0x10, NEED_CALC_SIZE = 0x20, SZDRAW = 0x40, OWNEDBG = 0x80, HANDLE_CREATED = 0x100, // debug only SW_SHOWN = 0x200, SW_HIDDEN = 0x400, CREATED = 0x800, NEED_INIT_LAYOUT = 0x1000, IN_LAYOUT = 0x2000, FVISIBLE = 0x4000, VISIBLE = 0x8000, NOCLOSING = 0x10000, ASCROLL = 0x20000, ASCALE = 0x40000, FORM = 0x80000, RECREATING = 0x100000, HAS_LAYOUT = 0x200000, VSTYLE = 0x400000, // If not forced off. FORMLOADED = 0x800000, // If not forced off. ENABLED = 0x1000000, // Enabled state, not considering the parent. } //CBits cbits = CBits.ALLOW_LAYOUT; //CBits cbits = CBits.NONE; CBits cbits = CBits.VISIBLE | CBits.VSTYLE | CBits.ENABLED; final: @property void menter(bool byes) // setter { if(byes) cbits |= CBits.MENTER; else cbits &= ~CBits.MENTER; } @property bool menter() // getter { return (cbits & CBits.MENTER) != 0; } @property void killing(bool byes) // setter //{ if(byes) cbits |= CBits.KILLING; else cbits &= ~CBits.KILLING; } { assert(byes); if(byes) cbits |= CBits.KILLING; } @property bool killing() // getter { return (cbits & CBits.KILLING) != 0; } @property void owned(bool byes) // setter { if(byes) cbits |= CBits.OWNED; else cbits &= ~CBits.OWNED; } @property bool owned() // getter { return (cbits & CBits.OWNED) != 0; } /+ void _allowLayout(bool byes) // setter { if(byes) cbits |= CBits.ALLOW_LAYOUT; else cbits &= ~CBits.ALLOW_LAYOUT; } bool _allowLayout() // getter { return (cbits & CBits.ALLOW_LAYOUT) != 0; } +/ @property void _clicking(bool byes) // setter { if(byes) cbits |= CBits.CLICKING; else cbits &= ~CBits.CLICKING; } @property bool _clicking() // getter { return (cbits & CBits.CLICKING) != 0; } @property void needCalcSize(bool byes) // setter { if(byes) cbits |= CBits.NEED_CALC_SIZE; else cbits &= ~CBits.NEED_CALC_SIZE; } @property bool needCalcSize() // getter { return (cbits & CBits.NEED_CALC_SIZE) != 0; } @property void szdraw(bool byes) // setter { if(byes) cbits |= CBits.SZDRAW; else cbits &= ~CBits.SZDRAW; } @property bool szdraw() // getter { return (cbits & CBits.SZDRAW) != 0; } @property void ownedbg(bool byes) // setter { if(byes) cbits |= CBits.OWNEDBG; else cbits &= ~CBits.OWNEDBG; } @property bool ownedbg() // getter { return (cbits & CBits.OWNEDBG) != 0; } debug { @property void _handlecreated(bool byes) // setter { if(byes) cbits |= CBits.HANDLE_CREATED; else cbits &= ~CBits.HANDLE_CREATED; } @property bool _handlecreated() // getter { return (cbits & CBits.HANDLE_CREATED) != 0; } } @property LONG _exStyle() { // return GetWindowLongPtrA(hwnd, GWL_EXSTYLE); return wexstyle; } @property void _exStyle(LONG wl) { if(isHandleCreated) { SetWindowLongPtrA(hwnd, GWL_EXSTYLE, wl); } wexstyle = wl; } @property LONG _style() { // return GetWindowLongPtrA(hwnd, GWL_STYLE); return wstyle; } @property void _style(LONG wl) { if(isHandleCreated) { SetWindowLongPtrA(hwnd, GWL_STYLE, wl); } wstyle = wl; } @property HBRUSH hbrBg() // getter { if(_hbrBg) return _hbrBg; if(backc == Color.empty && parent && backColor == parent.backColor) { ownedbg = false; _hbrBg = parent.hbrBg; return _hbrBg; } hbrBg = backColor.createBrush(); // Call hbrBg's setter and set ownedbg. return _hbrBg; } @property void hbrBg(HBRUSH hbr) // setter in { if(hbr) { assert(!_hbrBg); } } do { _hbrBg = hbr; ownedbg = true; } void deleteThisBackgroundBrush() { if(_hbrBg) { if(ownedbg) DeleteObject(_hbrBg); _hbrBg = HBRUSH.init; } } LRESULT defwproc(UINT msg, WPARAM wparam, LPARAM lparam) { //return DefWindowProcA(hwnd, msg, wparam, lparam); return dfl.internal.utf.defWindowProc(hwnd, msg, wparam, lparam); } LONG_PTR _fetchClassLongPtr() { return GetClassLongPtrA(hwnd, GCL_STYLE); } LONG _classStyle() { // return GetClassLongPtrA(hwnd, GCL_STYLE); // return wclassStyle; if(isHandleCreated) { // Always fetch because it's not guaranteed to be accurate. wclassStyle = _fetchClassLongPtr().toI32; } return wclassStyle; } package void _classStyle(LONG cl) { if(isHandleCreated) { SetClassLongPtrA(hwnd, GCL_STYLE, cl); } wclassStyle = cl; } } package abstract class ControlSuperClass: Control // dapi.d { // Call previous wndProc(). abstract protected void prevWndProc(ref Message msg); protected override void wndProc(ref Message msg) { switch(msg.msg) { case WM_PAINT: { RECT uprect; //GetUpdateRect(hwnd, &uprect, true); //onInvalidated(new InvalidateEventArgs(Rect(&uprect))); //if(!msg.wParam) GetUpdateRect(hwnd, &uprect, false); // Preserve. prevWndProc(msg); // Now fake a normal paint event... scope Graphics gpx = new CommonGraphics(hwnd, GetDC(hwnd)); //scope Graphics gpx = new CommonGraphics(hwnd, msg.wParam ? cast(HDC)msg.wParam : GetDC(hwnd), msg.wParam ? false : true); HRGN hrgn; hrgn = CreateRectRgnIndirect(&uprect); SelectClipRgn(gpx.handle, hrgn); DeleteObject(hrgn); scope PaintEventArgs pea = new PaintEventArgs(gpx, Rect(&uprect)); // Can't erase the background now, Windows just painted.. //if(ps.fErase) //{ // prepareDc(gpx.handle); // onPaintBackground(pea); //} prepareDc(gpx.handle); onPaint(pea); } break; case WM_PRINTCLIENT: { prevWndProc(msg); scope Graphics gpx = new CommonGraphics(hwnd, GetDC(hwnd)); scope PaintEventArgs pea = new PaintEventArgs(gpx, Rect(Point(0, 0), wclientsz)); prepareDc(pea.graphics.handle); onPaint(pea); } break; case WM_PRINT: Control.defWndProc(msg); break; case WM_ERASEBKGND: Control.wndProc(msg); break; case WM_NCACTIVATE: case WM_NCCALCSIZE: case WM_NCCREATE: case WM_NCPAINT: prevWndProc(msg); break; case WM_KEYDOWN: case WM_KEYUP: case WM_CHAR: case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_SYSCHAR: //case WM_IMECHAR: super.wndProc(msg); return; default: prevWndProc(msg); super.wndProc(msg); } } override void defWndProc(ref Message m) { switch(m.msg) { case WM_KEYDOWN: case WM_KEYUP: case WM_CHAR: case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_SYSCHAR: //case WM_IMECHAR: // ? prevWndProc(m); break; default: } } protected override void onPaintBackground(PaintEventArgs pea) { Message msg; msg.hWnd = handle; msg.msg = WM_ERASEBKGND; msg.wParam = cast(WPARAM)pea.graphics.handle; prevWndProc(msg); // Don't paint the background twice. //super.onPaintBackground(pea); // Event ? //paintBackground(this, pea); } } /// class ScrollableControl: Control // docmain { // /// deprecated void autoScroll(bool byes) // setter { if(byes) cbits |= CBits.ASCROLL; else cbits &= ~CBits.ASCROLL; } // /// ditto deprecated bool autoScroll() // getter { return (cbits & CBits.ASCROLL) == CBits.ASCROLL; } // /// deprecated final void autoScrollMargin(Size sz) // setter { //scrollmargin = sz; } // /// ditto deprecated final Size autoScrollMargin() // getter { //return scrollmargin; return Size(0, 0); } // /// deprecated final void autoScrollMinSize(Size sz) // setter { //scrollmin = sz; } // /// ditto deprecated final Size autoScrollMinSize() // getter { //return scrollmin; return Size(0, 0); } // /// deprecated final void autoScrollPosition(Point pt) // setter { //autoscrollpos = pt; } // /// ditto deprecated final Point autoScrollPosition() // getter { //return autoscrollpos; return Point(0, 0); } /// final @property Size autoScaleBaseSize() // getter { return autossz; } /// ditto final @property void autoScaleBaseSize(Size newSize) // setter in { assert(newSize.width > 0); assert(newSize.height > 0); } do { autossz = newSize; } /// final @property void autoScale(bool byes) // setter { if(byes) cbits |= CBits.ASCALE; else cbits &= ~CBits.ASCALE; } /// ditto final @property bool autoScale() // getter { return (cbits & CBits.ASCALE) == CBits.ASCALE; } final @property Point scrollPosition() // getter { return Point(xspos, yspos); } static Size calcScale(Size area, Size toScale, Size fromScale) // package in { assert(fromScale.width); assert(fromScale.height); } do { area.width = cast(int)(cast(float)area.width / cast(float)fromScale.width * cast(float)toScale.width); area.height = cast(int)(cast(float)area.height / cast(float)fromScale.height * cast(float)toScale.height); return area; } Size calcScale(Size area, Size toScale) // package { return calcScale(area, toScale, DEFAULT_SCALE); } final void _scale(Size toScale) // package { bool first = true; // Note: doesn't get to-scale for nested scrollable-controls. void xscale(Control c, Size fromScale) { c.suspendLayout(); if(first) { first = false; c.size = calcScale(c.size, toScale, fromScale); } else { Point pt; Size sz; sz = calcScale(Size(c.left, c.top), toScale, fromScale); pt = Point(sz.width, sz.height); sz = calcScale(c.size, toScale, fromScale); c.bounds = Rect(pt, sz); } if(c.hasChildren) { ScrollableControl scc; foreach(Control cc; c.controls) { scc = cast(ScrollableControl)cc; if(scc) { if(scc.autoScale) // ? { xscale(scc, scc.autoScaleBaseSize); scc.autoScaleBaseSize = toScale; } } else { xscale(cc, fromScale); } } } //c.resumeLayout(true); c.resumeLayout(false); // Should still be perfectly proportionate if it was properly laid out before scaling. } xscale(this, autoScaleBaseSize); autoScaleBaseSize = toScale; } final void _scale() // package { return _scale(getAutoScaleSize()); } protected override void onControlAdded(ControlEventArgs ea) { super.onControlAdded(ea); if(created) // ? if(isHandleCreated) { auto sc = cast(ScrollableControl)ea.control; if(sc) { if(sc.autoScale) sc._scale(); } else { if(autoScale) _scale(); } } } //override final Rect displayRectangle() // getter override @property Rect displayRectangle() // getter { Rect result = clientRectangle; // Subtract dock padding. result.x = result.x + dpad.left; result.width = result.width - dpad.right - dpad.left; result.y = result.y + dpad.top; result.height = result.height - dpad.bottom - dpad.top; // Add scroll width. if(scrollSize.width > clientSize.width) result.width = result.width + (scrollSize.width - clientSize.width); if(scrollSize.height > clientSize.height) result.height = result.height + (scrollSize.height - clientSize.height); // Adjust scroll position. result.location = Point(result.location.x - scrollPosition.x, result.location.y - scrollPosition.y); return result; } /// final @property void scrollSize(Size sz) // setter { scrollsz = sz; _fixScrollBounds(); // Implies _adjustScrollSize(). } /// ditto final @property Size scrollSize() // getter { return scrollsz; } /// class DockPaddingEdges { private: int _left, _top, _right, _bottom; int _all; //package void delegate() changed; final: void changed() { dpadChanged(); } public: /// @property void all(int x) // setter { _bottom = _right = _top = _left = _all = x; changed(); } /// ditto final @property int all() // getter { return _all; } /// ditto @property void left(int x) // setter { _left = x; changed(); } /// ditto @property int left() // getter { return _left; } /// ditto @property void top(int x) // setter { _top = x; changed(); } /// ditto @property int top() // getter { return _top; } /// ditto @property void right(int x) // setter { _right = x; changed(); } /// ditto @property int right() // getter { return _right; } /// ditto @property void bottom(int x) // setter { _bottom = x; changed(); } /// ditto @property int bottom() // getter { return _bottom; } } /// final @property DockPaddingEdges dockPadding() // getter { return dpad; } deprecated final void setAutoScrollMargin(int x, int y) { // } this() { super(); _init(); } enum DEFAULT_SCALE = Size(5, 13); /// final @property void hScroll(bool byes) // setter { LONG wl = _style(); if(byes) wl |= WS_HSCROLL; else wl &= ~WS_HSCROLL; _style(wl); if(isHandleCreated) redrawEntire(); } /// ditto final @property bool hScroll() // getter { return (_style() & WS_HSCROLL) != 0; } /// final @property void vScroll(bool byes) // setter { LONG wl = _style(); if(byes) wl |= WS_VSCROLL; else wl &= ~WS_VSCROLL; _style(wl); if(isHandleCreated) redrawEntire(); } /// ditto final @property bool vScroll() // getter { return (_style() & WS_VSCROLL) != 0; } protected: /+ override void onLayout(LayoutEventArgs lea) { // ... super.onLayout(lea); } +/ /+ override void scaleCore(float width, float height) { // Might not want to call super.scaleCore(). } +/ override void wndProc(ref Message m) { switch(m.msg) { case WM_VSCROLL: { SCROLLINFO si = void; si.cbSize = SCROLLINFO.sizeof; si.fMask = SIF_ALL; if(GetScrollInfo(m.hWnd, SB_VERT, &si)) { int delta, maxp; maxp = scrollSize.height - clientSize.height; switch(LOWORD(m.wParam)) { case SB_LINEDOWN: if(yspos >= maxp) return; delta = maxp - yspos; if(autossz.height < delta) delta = autossz.height; break; case SB_LINEUP: if(yspos <= 0) return; delta = yspos; if(autossz.height < delta) delta = autossz.height; delta = -delta; break; case SB_PAGEDOWN: if(yspos >= maxp) return; if(yspos >= maxp) return; delta = maxp - yspos; if(clientSize.height < delta) delta = clientSize.height; break; case SB_PAGEUP: if(yspos <= 0) return; delta = yspos; if(clientSize.height < delta) delta = clientSize.height; delta = -delta; break; case SB_THUMBTRACK: case SB_THUMBPOSITION: //delta = cast(int)HIWORD(m.wParam) - yspos; // Limited to 16-bits. delta = si.nTrackPos - yspos; break; case SB_BOTTOM: delta = maxp - yspos; break; case SB_TOP: delta = -yspos; break; default: } yspos += delta; SetScrollPos(m.hWnd, SB_VERT, yspos, TRUE); ScrollWindow(m.hWnd, 0, -delta, null, null); } } break; case WM_HSCROLL: { SCROLLINFO si = void; si.cbSize = SCROLLINFO.sizeof; si.fMask = SIF_ALL; if(GetScrollInfo(m.hWnd, SB_HORZ, &si)) { int delta, maxp; maxp = scrollSize.width - clientSize.width; switch(LOWORD(m.wParam)) { case SB_LINERIGHT: if(xspos >= maxp) return; delta = maxp - xspos; if(autossz.width < delta) delta = autossz.width; break; case SB_LINELEFT: if(xspos <= 0) return; delta = xspos; if(autossz.width < delta) delta = autossz.width; delta = -delta; break; case SB_PAGERIGHT: if(xspos >= maxp) return; if(xspos >= maxp) return; delta = maxp - xspos; if(clientSize.width < delta) delta = clientSize.width; break; case SB_PAGELEFT: if(xspos <= 0) return; delta = xspos; if(clientSize.width < delta) delta = clientSize.width; delta = -delta; break; case SB_THUMBTRACK: case SB_THUMBPOSITION: //delta = cast(int)HIWORD(m.wParam) - xspos; // Limited to 16-bits. delta = si.nTrackPos - xspos; break; case SB_RIGHT: delta = maxp - xspos; break; case SB_LEFT: delta = -xspos; break; default: } xspos += delta; SetScrollPos(m.hWnd, SB_HORZ, xspos, TRUE); ScrollWindow(m.hWnd, -delta, 0, null, null); } } break; default: } super.wndProc(m); } override void onMouseWheel(MouseEventArgs ea) { int maxp = scrollSize.height - clientSize.height; int delta; UINT wlines; if(!SystemParametersInfoA(SPI_GETWHEELSCROLLLINES, 0, &wlines, 0)) wlines = 3; if(ea.delta < 0) { if(yspos < maxp) { delta = maxp - yspos; if(autossz.height * wlines < delta) delta = autossz.height * wlines; yspos += delta; SetScrollPos(hwnd, SB_VERT, yspos, TRUE); ScrollWindow(hwnd, 0, -delta, null, null); } } else { if(yspos > 0) { delta = yspos; if(autossz.height * wlines < delta) delta = autossz.height * wlines; delta = -delta; yspos += delta; SetScrollPos(hwnd, SB_VERT, yspos, TRUE); ScrollWindow(hwnd, 0, -delta, null, null); } } super.onMouseWheel(ea); } override void onHandleCreated(EventArgs ea) { xspos = 0; yspos = 0; super.onHandleCreated(ea); //_adjustScrollSize(FALSE); if(hScroll || vScroll) { _adjustScrollSize(FALSE); recalcEntire(); // Need to recalc frame. } } override void onVisibleChanged(EventArgs ea) { if(visible) _adjustScrollSize(FALSE); super.onVisibleChanged(ea); } private void _fixScrollBounds() { if(hScroll || vScroll) { int ydiff = 0, xdiff = 0; if(yspos > scrollSize.height - clientSize.height) { ydiff = (clientSize.height + yspos) - scrollSize.height; yspos -= ydiff; if(yspos < 0) { ydiff += yspos; yspos = 0; } } if(xspos > scrollSize.width - clientSize.width) { xdiff = (clientSize.width + xspos) - scrollSize.width; xspos -= xdiff; if(xspos < 0) { xdiff += xspos; xspos = 0; } } if(isHandleCreated) { if(xdiff || ydiff) ScrollWindow(hwnd, xdiff, ydiff, null, null); _adjustScrollSize(); } } } override void onResize(EventArgs ea) { super.onResize(ea); _fixScrollBounds(); } private: //Size scrollmargin, scrollmin; //Point autoscrollpos; DockPaddingEdges dpad; Size autossz = DEFAULT_SCALE; Size scrollsz = Size(0, 0); int xspos = 0, yspos = 0; void _init() { dpad = new DockPaddingEdges; //dpad.changed = &dpadChanged; } void dpadChanged() { alayout(this); } void _adjustScrollSize(BOOL fRedraw = TRUE) { assert(isHandleCreated); if(!hScroll && !vScroll) return; SCROLLINFO si; //if(vScroll) { si.cbSize = SCROLLINFO.sizeof; si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; si.nPos = yspos; si.nMin = 0; si.nMax = clientSize.height; si.nPage = clientSize.height; if(scrollSize.height > clientSize.height) si.nMax = scrollSize.height; if(si.nMax) si.nMax--; SetScrollInfo(hwnd, SB_VERT, &si, fRedraw); } //if(hScroll) { si.cbSize = SCROLLINFO.sizeof; si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; si.nPos = xspos; si.nMin = 0; si.nMax = clientSize.width; si.nPage = clientSize.width; if(scrollSize.width > clientSize.width) si.nMax = scrollSize.width; if(si.nMax) si.nMax--; SetScrollInfo(hwnd, SB_HORZ, &si, fRedraw); } } } /// interface IContainerControl // docmain { /// @property Control activeControl(); // getter deprecated void activeControl(Control); // setter deprecated bool activateControl(Control); } /// class ContainerControl: ScrollableControl, IContainerControl // docmain { /// @property Control activeControl() // getter { /+ HWND hwfocus, hw; hw = hwfocus = GetFocus(); while(hw) { if(hw == this.hwnd) return Control.fromChildHandle(hwfocus); hw = GetParent(hw); } return null; +/ Control ctrlfocus, ctrl; ctrl = ctrlfocus = Control.fromChildHandle(GetFocus()); while(ctrl) { if(ctrl is this) return ctrlfocus; ctrl = ctrl.parent; } return null; } /// ditto @property void activeControl(Control ctrl) // setter { if(!activateControl(ctrl)) throw new DflException("Unable to activate control"); } /// // Returns true if successfully activated. final bool activateControl(Control ctrl) { // Not sure if this is correct. if(!ctrl.canSelect) return false; //if(!SetActiveWindow(ctrl.handle)) // return false; ctrl.select(); return true; } /// final @property Form parentForm() // getter { Control par; Form f; for(par = parent; par; par = par.parent) { f = cast(Form)par; if(f) return f; } return null; } /+ final bool validate() { // ... } +/ this() { super(); _init(); } /+ // Used internally. this(HWND hwnd) { super(hwnd); _init(); } +/ private void _init() { //wexstyle |= WS_EX_CONTROLPARENT; ctrlStyle |= ControlStyles.CONTAINER_CONTROL; } protected: /+ override bool processDialogChar(char charCode) { // Not sure if this is correct. return false; } +/ /+ deprecated protected override bool processMnemonic(dchar charCode) { return false; } bool processTabKey(bool forward) { if(isHandleCreated) { //SendMessageA(hwnd, WM_NEXTDLGCTL, !forward, 0); //return true; select(true, forward); } return false; } +/ } import std.traits, std.typecons; private template hasLocalAliasing(T...) { static if( !T.length ) enum hasLocalAliasing = false; else enum hasLocalAliasing = std.traits.hasLocalAliasing!(T[0]) || dfl.control.hasLocalAliasing!(T[1 .. $]); } /// shared class SharedControl { private: Control _ctrl; LPARAM makeParam(ARGS...)(void function(Control, ARGS) fn, Tuple!(ARGS)* args) if (ARGS.length) { static assert((DflInvokeParam*).sizeof <= LPARAM.sizeof); static struct InvokeParam { void function(Control, ARGS) fn; ARGS args; } alias dfl.internal.clib.malloc malloc; alias dfl.internal.clib.free free; auto param = cast(InvokeParam*)malloc(InvokeParam.sizeof); param.fn = fn; param.args = args.field; if (!param) throw new OomException(); auto p = cast(DflInvokeParam*)malloc(DflInvokeParam.sizeof); if (!p) throw new OomException(); static void fnentry(Control c, size_t[] p) { auto param = cast(InvokeParam*)p[0]; param.fn(c, param.args); free(param); } p.fp = &fnentry; p.nparams = 1; p.params[0] = cast(size_t)param; return cast(LPARAM)p; } LPARAM makeParamNoneArgs(void function(Control) fn) { static assert((DflInvokeParam*).sizeof <= LPARAM.sizeof); alias dfl.internal.clib.malloc malloc; alias dfl.internal.clib.free free; auto p = cast(DflInvokeParam*)malloc(DflInvokeParam.sizeof); if (!p) throw new OomException(); static void fnentry(Control c, size_t[] p) { auto fn = cast(void function(Control))p[0]; fn(c); } p.fp = &fnentry; p.nparams = 1; p.params[0] = cast(size_t)fn; return cast(LPARAM)p; } public: /// this(Control ctrl) { assert(ctrl); _ctrl = cast(shared)ctrl; } /// void invoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) if (ARGS.length && !hasLocalAliasing!(ARGS)) { auto ctrl = cast(Control)_ctrl; auto hwnd = ctrl.handle; if(!hwnd) Control.badInvokeHandle(); auto t = tuple(args); auto p = makeParam(fn, &t); SendMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); } /// void invoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) if (!ARGS.length) { auto ctrl = cast(Control)_ctrl; auto hwnd = ctrl.handle; if(!hwnd) Control.badInvokeHandle(); auto p = makeParamNoneArgs(fn); SendMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); } /// void delayInvoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) if (ARGS.length && !hasLocalAliasing!(ARGS)) { auto ctrl = cast(Control)_ctrl; auto hwnd = ctrl.handle; if(!hwnd) Control.badInvokeHandle(); auto t = tuple(args); auto p = makeParam(fn, &t); PostMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); } /// void delayInvoke(ARGS...)(void function(Control, ARGS) fn, ARGS args) if (!ARGS.length) { auto ctrl = cast(Control)_ctrl; auto hwnd = ctrl.handle; if(!hwnd) Control.badInvokeHandle(); auto p = makeParamNoneArgs(fn); PostMessageA(hwnd, wmDfl, WPARAM_DFL_DELAY_INVOKE_PARAMS, p); } }