mirror of
https://github.com/Rayerd/dfl.git
synced 2025-04-25 20:49:58 +03:00
8360 lines
163 KiB
D
8360 lines
163 KiB
D
// 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);
|
|
}
|
|
}
|