FIX: TextBox is not accepted RETURN and TAB correctly in form that has default button.

- General
In order to resolve this, keyboard input process was changed similar to WinForms.
A part of role of FormMessageFilter class was moved to each control's wndProc(), because TextBox class is must to know that parent form has default button and not.
And also remove to hack of ControlStyles enum.

- Others
Add TextBox example code.
Separate TextBoxBase class from textbox.d to textboxbase.d file.
Separate SharedControl class from control.d to sharedcontrol.d file.
Fix some old alias sytax.
Add vscode workspace file.
This commit is contained in:
haru-s 2022-12-24 10:58:18 +09:00
parent a7275028ca
commit abdb70f691
14 changed files with 2146 additions and 1315 deletions

8
dfl.code-workspace Normal file
View file

@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

16
examples/textbox/.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
.dub
docs.json
__dummy.html
docs/
/hello_dfl
hello_dfl.so
hello_dfl.dylib
hello_dfl.dll
hello_dfl.a
hello_dfl.lib
hello_dfl-test-*
*.exe
*.pdb
*.o
*.obj
*.lst

16
examples/textbox/dub.json Normal file
View file

@ -0,0 +1,16 @@
{
"authors": ["haru-s"],
"copyright": "Copyright (C) 2022 haru-s",
"description": "DFL sample code.",
"name": "textbox_sample",
"targetType": "executable",
"targetPath": "bin",
"dependencies": {
"dfl": {
"path": "../../../dfl"
}
},
"lflags-windows-x86_omf-dmd": ["/exet:nt/su:windows:6.0"],
"lflags-windows-x86_mscoff-dmd": ["/SUBSYSTEM:WINDOWS", "/ENTRY:mainCRTStartup"],
"lflags-windows-x86_64-dmd": ["/SUBSYSTEM:WINDOWS", "/ENTRY:mainCRTStartup"]
}

View file

@ -0,0 +1,7 @@
{
"fileVersion": 1,
"versions": {
"dfl": {"path":"../.."},
"undead": "1.1.8"
}
}

View file

@ -0,0 +1,3 @@
set dmd_path=c:\d\dmd2\windows
set dmc_path=c:\dmc\dm
cmd

View file

@ -0,0 +1,127 @@
import dfl;
version(Have_dfl) // For DUB.
{
}
else
{
pragma(lib, "dfl.lib");
}
class MainForm : Form
{
private TextBox _textbox1;
private TextBox _textbox2;
private TextBox _textbox3;
private TextBox _textbox4;
private TextBox _textbox5;
private Button _button1;
private Button _button2;
this()
{
// Form setting
text = "TextBox sample";
size = Size(500,400);
resizeRedraw = true;
// Button setting (Default button)
_button1 = new Button();
_button1.location = Point(10,10);
_button1.text = "OK";
_button1.click ~= (Control c, EventArgs e)
{
this.text = this.text ~ "+";
};
_button1.parent = this;
// Button setting (Default button's enable/disable is switched).
_button2 = new Button();
_button2.location = Point(100,10);
_button2.text = "On/Off";
_button2.click ~= (Control c, EventArgs e)
{
if(acceptButton)
{
acceptButton = null; // Default button is now none.
_button1.notifyDefault(false);
_textbox1.text = "Now Off";
}
else
{
acceptButton = _button1; // Set default button.
_button1.notifyDefault(true);
_textbox1.text = "Now On";
}
};
_button2.parent = this;
// TextBox setting (Single line)
_textbox1 = new TextBox();
_textbox1.location = Point(200,10);
_textbox1.size = Size(100,30);
_textbox1.multiline = false; // false: One line textbox.
_textbox1.scrollBars = ScrollBars.NONE; // NONE: Without scroll bar.
_textbox1.acceptsReturn = false; // false: Disables RETURN key.
_textbox1.acceptsTab = false; // false: Disables TAB key.
// true: Enables TAB key. But be changed focus only
// because one line textbox is not able to input TAB char.
_textbox1.wordWrap = false; // false: Do not send words that span the right edge to the next line.
_button1.notifyDefault(false); // false: Do not set as default button
_textbox1.text = "Default Off";
_textbox1.parent = this;
// TextBox setting (return:yes, tab:yes)
_textbox2 = new TextBox();
_textbox2.location = Point(10,60);
_textbox2.size = Size(450,50);
_textbox2.multiline = true; // true: Multi line textbox.
_textbox2.scrollBars = ScrollBars.VERTICAL; // VERTICAL: within vertical scroll bar.
_textbox2.acceptsReturn = true; // true: Enables RETURN key.
_textbox2.acceptsTab = true; // true: Enables TAB key.
_textbox2.wordWrap = true; // true: Send words that span the right edge to the next line.
_textbox2.text = "return:yes, tab:yes";
_textbox2.parent = this;
// TextBox setting (return:yes, tab:no)
_textbox3 = new TextBox();
_textbox3.location = Point(10,130);
_textbox3.size = Size(450,50);
_textbox3.multiline = true; // false: One line textbox.
_textbox3.scrollBars = ScrollBars.VERTICAL; // VERTICAL: within vertical scroll bar.
_textbox3.acceptsReturn = true; // true: Enables RETURN key.
_textbox3.acceptsTab = false; // false: Disables TAB key.
_textbox3.wordWrap = true; // true: Send words that span the right edge to the next line.
_textbox3.text = "return:yes, tab:no";
_textbox3.parent = this;
// TextBox setting (return:no, tab:yes)
_textbox4 = new TextBox();
_textbox4.location = Point(10,190);
_textbox4.size = Size(450,50);
_textbox4.multiline = true; // true: Multi line textbox.
_textbox4.scrollBars = ScrollBars.VERTICAL; // VERTICAL: within vertical scroll bar.
_textbox4.acceptsReturn = false; // false: Disables RETURN key.
_textbox4.acceptsTab = true; // true: Enables TAB key.
_textbox4.wordWrap = true; // true: Send words that span the right edge to the next line.
_textbox4.text = "return:no, tab:yes";
_textbox4.parent = this;
// TextBox setting (return:no, tab:no)
_textbox5 = new TextBox();
_textbox5.location = Point(10,250);
_textbox5.size = Size(450,50);
_textbox5.multiline = true; // true: Multi line textbox.
_textbox5.scrollBars = ScrollBars.VERTICAL; // VERTICAL: within vertical scroll bar.
_textbox5.acceptsReturn = false; // false: Disables RETURN key.
_textbox5.acceptsTab = false; // false: Disables TAB key.
_textbox5.wordWrap = true; // true: Send words that span the right edge to the next line.
_textbox5.text = "return:no, tab:no";
_textbox5.parent = this;
}
}
void main()
{
Application.run(new MainForm());
}

View file

@ -2540,7 +2540,7 @@ extern(Windows)
}
WNDPROC _superClass(HINSTANCE hinst, Dstring className, Dstring newClassName, out WNDCLASSA getInfo) // deprecated
deprecated WNDPROC _superClass(HINSTANCE hinst, Dstring className, Dstring newClassName, out WNDCLASSA getInfo)
{
WNDPROC wndProc;

View file

@ -5,13 +5,16 @@
///
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 dfl.application, dfl.event, dfl.label;
private import dfl.collections;
private import core.memory;
private import dfl.internal.dlib, dfl.internal.clib;
private import dfl.internal.winapi, dfl.internal.wincom;
private import dfl.internal.utf;
private import dfl.internal.com;
version(NO_DRAG_DROP)
version = DFL_NO_DRAG_DROP;
@ -178,9 +181,6 @@ enum ControlStyles: uint
//DOUBLE_BUFFER = 0x10000, // TODO: implement.
//OPTIMIZED_DOUBLE_BUFFER = 0x20000, // TODO: implement.
USE_TEXT_FOR_ACCESSIBILITY = 0x40000, /// ditto
WANT_TAB_KEY = 0x01000000,
WANT_ALL_KEYS = 0x02000000,
}
@ -3704,11 +3704,92 @@ class Control: DObject, IWindow // docmain
}
///
// Return true if processed.
/// Process shortcut key (ctrl+A etc).
// Returns false when parent is none, otherwise call parent form's one.
protected bool processCmdKey(ref Message msg, Keys keyData)
{
if (parent)
return parent.processCmdKey(msg, keyData);
else
return false;
}
/// Process dialog key (TAB, RETURN, ESC, UP, DOWN, LEFT, RIGHT and so on).
// Returns false when parent is none, otherwise call parent form's one.
protected bool processDialogKey(Keys keyData)
{
if (parent)
return parent.processDialogKey(keyData);
else
return false;
}
/// Process mnemonic (access key) such as Alt+T.
// Returns false when parent is none, otherwise call parent form's one.
protected bool processDialogChar(char charCode)
{
if (parent)
return parent.processDialogChar(charCode);
else
return false;
}
/// Pre-process keybord message
// This function called from Application.DflWndProc().
// If return true, processed message,
// then wndProc() be not called after preProcessMessage().
// If return false, wndProc() be called after preProcessMessage().
bool preProcessMessage(ref Message msg)
{
return false;
bool result/+ = false+/;
if (msg.msg == WM_KEYDOWN || msg.msg == WM_SYSKEYDOWN)
{
// TODO: Implement
// if (!getExtendedState(ExtendedStates.UiCues))
// {
// processUICues(msg);
// }
Keys keyData = cast(Keys)msg.wParam | modifierKeys;
// processCmdKey returns true when keyData is a shortcut key (ctrl+C etc).
if (processCmdKey(msg, keyData))
{
result = true;
}
// isInputKey returns true when keyData is a regular input key.
else if (isInputKey(keyData))
{
// TODO: Implement
// SetExtendedState(ExtendedStates.InputKey, true);
result = false; // End preprocessing and call wndProc().
}
else
{
// Process dialog keys such as TAB, RETURN, ESC, UP, DOWN, RIGHT, LEFT.
result = processDialogKey(keyData);
}
}
else if (msg.msg == WM_CHAR || msg.msg == WM_SYSCHAR)
{
if (msg.msg == WM_CHAR && isInputChar(cast(char)msg.wParam))
{
// TODO: Implement
// setExtendedState(ExtendedStates.InputChar, true);
result = false;
}
else
{
result = processDialogChar(cast(char)msg.wParam);
}
}
else
{
result = false;
}
return result;
}
@ -3909,7 +3990,7 @@ class Control: DObject, IWindow // docmain
}
package static void _dlgselnext(Form dlg, HWND hwcursel, bool forward,
package static bool _dlgselnext(Form dlg, HWND hwcursel, bool forward,
bool tabStopOnly = true, bool selectableOnly = false,
bool nested = true, bool wrap = true,
HWND hwchildrenof = null)
@ -3960,6 +4041,7 @@ class Control: DObject, IWindow // docmain
{
//DefDlgProcA(dlg.handle, WM_NEXTDLGCTL, cast(WPARAM)hwfirst, MAKELPARAM(true, 0));
dlg._selectChild(hwfirst);
return true;
}
}
}
@ -3990,22 +4072,26 @@ class Control: DObject, IWindow // docmain
}, 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);
return true;
}
}
return false;
}
package final void _selectNextControl(Form ctrltoplevel,
package final bool _selectNextControl(Form ctrltoplevel,
Control ctrl, bool forward, bool tabStopOnly, bool nested, bool wrap)
{
if(!created)
return;
return false;
assert(ctrltoplevel !is null);
assert(ctrltoplevel.isHandleCreated);
_dlgselnext(ctrltoplevel,
return _dlgselnext(ctrltoplevel,
(ctrl && ctrl.isHandleCreated) ? ctrl.handle : null,
forward, tabStopOnly, !tabStopOnly, nested, wrap,
this.handle);
@ -4019,14 +4105,16 @@ class Control: DObject, IWindow // docmain
// Only considers child controls of this control.
final void selectNextControl(Control ctrl, bool forward, bool tabStopOnly, bool nested, bool wrap)
final bool selectNextControl(Control ctrl, bool forward, bool tabStopOnly, bool nested, bool wrap)
{
if(!created)
return;
return false;
auto ctrltoplevel = findForm();
if(ctrltoplevel)
return _selectNextControl(ctrltoplevel, ctrl, forward, tabStopOnly, nested, wrap);
return false;
}
@ -4594,7 +4682,26 @@ class Control: DObject, IWindow // docmain
DWORD ldlgcode = 0;
}
///
protected bool processKeyPreview(ref Message m)
{
return parent && parent.processKeyPreview(m);
}
///
// Returns true in order to break in wndProc(), because processed.
protected final bool processKeyMessage(ref Message m)
{
if ( parent && parent.processKeyPreview(m))
{
return true;
}
return processKeyEventArgs(m);
}
///
protected void wndProc(ref Message msg)
{
@ -4845,7 +4952,7 @@ class Control: DObject, IWindow // docmain
case WM_KEYUP:
case WM_CHAR:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_SYSKEYUP: // TODO: Is it correct?
case WM_SYSCHAR:
//case WM_IMECHAR:
/+
@ -4858,7 +4965,14 @@ class Control: DObject, IWindow // docmain
msg.result = 1; // The key was not processed.
break;
+/
msg.result = !processKeyEventArgs(msg);
if (processKeyMessage(msg))
{
//msg.result = 0;
return;
}
defWndProc(msg);
//msg.result = !processKeyEventArgs(msg);
return;
case WM_MOUSEWHEEL: // Requires Windows 98 or NT4.
@ -5450,9 +5564,6 @@ class Control: DObject, IWindow // docmain
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;
@ -6802,7 +6913,6 @@ class Control: DObject, IWindow // docmain
cbits &= ~CBits.IN_LAYOUT;
}
/+
// Not sure what to do here.
deprecated bool isInputChar(char charCode)
@ -6810,8 +6920,58 @@ class Control: DObject, IWindow // docmain
return false;
}
+/
protected bool isInputChar(char charCode)
{
int mask = 0;
if (charCode == Keys.TAB)
{
mask = (DLGC_WANTCHARS | DLGC_WANTALLKEYS | DLGC_WANTTAB);
}
else
{
mask = (DLGC_WANTCHARS | DLGC_WANTALLKEYS);
}
return (SendMessage(hwnd, WM_GETDLGCODE, 0, 0) & mask) != 0;
}
/// isInputKey returns true when keyData is a regular input key.
// If keyData is input key, then window message is sended to wndProc()
// such as WM_KEYDOWN, WM_KEYUP, WM_CHAR and so on.
protected bool isInputKey(Keys keyData)
{
if ((keyData & Keys.ALT) == Keys.ALT)
{
return false;
}
auto mask = DLGC_WANTALLKEYS;
switch (keyData & Keys.KEY_CODE)
{
case Keys.TAB:
mask = DLGC_WANTALLKEYS | DLGC_WANTTAB;
break;
case Keys.LEFT:
case Keys.RIGHT:
case Keys.UP:
case Keys.DOWN:
mask = DLGC_WANTALLKEYS | DLGC_WANTARROWS;
break;
default:
// Nothing
}
// return isHandleCreated
// && (SendMessage(hwnd, WM_GETDLGCODE, 0, 0) & mask) != 0;
if (isHandleCreated)
{
LRESULT r = SendMessage(hwnd, WM_GETDLGCODE, 0, 0);
return (r & mask) != 0;
}
return false;
}
///
void setVisibleCore(bool byes)
{
@ -6847,9 +7007,11 @@ class Control: DObject, IWindow // docmain
package final bool _wantTabKey()
{
if(ctrlStyle & ControlStyles.WANT_TAB_KEY)
// if(ctrlStyle & ControlStyles.WANT_TAB_KEY)
if((DLGC_WANTTAB & sendMessage(hwnd, WM_GETDLGCODE, 0, 0)) != 0)
return true;
return false;
else
return false;
}
@ -6857,12 +7019,22 @@ class Control: DObject, IWindow // docmain
// Return true if processed.
protected bool processKeyEventArgs(ref Message msg)
{
// TODO: implement more (IME etc...)
switch(msg.msg)
{
case WM_CHAR:
case WM_SYSCHAR:
{
scope KeyPressEventArgs kpea = new KeyPressEventArgs(cast(dchar)msg.wParam, modifierKeys);
onKeyPress(kpea);
return kpea.handled;
}
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
{
scope KeyEventArgs kea = new KeyEventArgs(cast(Keys)(msg.wParam | modifierKeys));
ushort repeat = msg.lParam & 0xFFFF; // First 16 bits.
for(; repeat; repeat--)
{
@ -6870,35 +7042,21 @@ class Control: DObject, IWindow // docmain
onKeyDown(kea);
}
if(kea.handled)
return true;
return kea.handled;
}
break;
case WM_KEYUP:
case WM_SYSKEYUP: // TODO: Is it correct?
{
// 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;
return kea.handled;
}
break;
case WM_CHAR:
{
scope KeyPressEventArgs kpea = new KeyPressEventArgs(cast(dchar)msg.wParam, modifierKeys);
onKeyPress(kpea);
if(kpea.handled)
return true;
}
break;
default:
assert(0); // Does not reached here, if WM is traped in Control.wndProc() correctly.
}
defWndProc(msg);
return !msg.result;
}
@ -6907,25 +7065,7 @@ class Control: DObject, IWindow // docmain
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)
{
@ -8197,182 +8337,24 @@ class ContainerControl: ScrollableControl, IContainerControl // docmain
}
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)
protected bool processTabKey(bool forward)
{
if(isHandleCreated)
{
//SendMessageA(hwnd, WM_NEXTDLGCTL, !forward, 0);
//return true;
select(true, forward);
return selectNextControl(activeControl, forward, tabStop, true, false);
}
return false;
}
+/
}
import std.traits, std.typecons;
private template hasLocalAliasing(T...)
{
import std.traits, std.typecons;
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);
}
}

View file

@ -6,10 +6,13 @@
module dfl.form;
private import dfl.internal.dlib;
private import dfl.internal.winapi;
private import dfl.internal.utf;
private import dfl.control, dfl.internal.winapi, dfl.event, dfl.drawing;
private import dfl.application, dfl.base, dfl.internal.utf;
private import dfl.control, dfl.event, dfl.drawing;
private import dfl.application, dfl.base;
private import dfl.collections;
debug(APP_PRINT)
{
private import dfl.internal.clib;
@ -129,10 +132,10 @@ class Form: ContainerControl, IDialogResult // docmain
///
final @property void acceptButton(IButtonControl btn) // setter
{
if(acceptBtn)
acceptBtn.notifyDefault(false);
if(_acceptButton)
_acceptButton.notifyDefault(false);
acceptBtn = btn;
_acceptButton = btn;
if(btn)
btn.notifyDefault(true);
@ -141,14 +144,14 @@ class Form: ContainerControl, IDialogResult // docmain
/// ditto
final @property IButtonControl acceptButton() // getter
{
return acceptBtn;
return _acceptButton;
}
///
final @property void cancelButton(IButtonControl btn) // setter
{
cancelBtn = btn;
_cancelButton = btn;
if(btn)
{
@ -162,7 +165,7 @@ class Form: ContainerControl, IDialogResult // docmain
/// ditto
final @property IButtonControl cancelButton() // getter
{
return cancelBtn;
return _cancelButton;
}
@ -462,7 +465,8 @@ class Form: ContainerControl, IDialogResult // docmain
debug
{
static import std.string;
er = std.string.format("CreateWindowEx failed {className=%s;exStyle=0x%X;style=0x%X;parent=0x%X;menu=0x%X;inst=0x%X;}",
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;
@ -918,10 +922,29 @@ class Form: ContainerControl, IDialogResult // docmain
return wicon;
}
// TODO: implement.
// keyPreview
/// keyPreview
final @property bool keyPreview() // getter
{
return _keyPreview;
}
/// ditto
final @property void keyPreview(bool v) // setter
{
_keyPreview = v;
}
///
protected override bool processKeyPreview(ref Message m)
{
if(_keyPreview && processKeyEventArgs(m))
{
// Returns true for call onKeyPress(), onKeyDown() or onKeyUp() on this form.
return true;
}
return super.processKeyPreview(m);
}
///
final @property bool isMdiChild() // getter
@ -2273,20 +2296,20 @@ class Form: ContainerControl, IDialogResult // docmain
switch(LOWORD(msg.wParam))
{
case IDOK:
if(acceptBtn)
if(_acceptButton)
{
if(HIWORD(msg.wParam) == BN_CLICKED)
acceptBtn.performClick();
_acceptButton.performClick();
return;
}
break;
//return;
case IDCANCEL:
if(cancelBtn)
if(_cancelButton)
{
if(HIWORD(msg.wParam) == BN_CLICKED)
cancelBtn.performClick();
_cancelButton.performClick();
return;
}
break;
@ -2742,8 +2765,139 @@ class Form: ContainerControl, IDialogResult // docmain
}
}
/// Process shortcut key (ctrl+A etc).
// Returns true when processed, false when not.
protected override bool processCmdKey(ref Message msg, Keys keyData)
{
if (super.processCmdKey(msg, keyData))
{
return true;
}
// Process MDI accelerator keys.
bool retValue = false;
MSG win32Message = msg._winMsg;
if ((_mdiClient !is null) && (_mdiClient.handle !is/+ != +/ null) &&
TranslateMDISysAccel(_mdiClient.hwnd, &win32Message))
{
retValue = true;
}
msg.msg = win32Message.message;
msg.wParam = win32Message.wParam;
msg.lParam = win32Message.lParam;
msg.hWnd = win32Message.hwnd;
return retValue;
}
package alias dfl.internal.utf.defDlgProc _defFormProc;
/// Process dialog key (TAB, RETURN, ESC, UP, DOWN, LEFT, RIGHT and so on).
// Returns true when processed, false when called super class.
protected override bool processDialogKey(Keys keyData)
{
if ((keyData & (Keys.ALT | Keys.CONTROL)) == Keys.NONE)
{
Keys keyCode = keyData & Keys.KEY_CODE;
IButtonControl button;
switch (keyCode)
{
case Keys.RETURN:
// button = cast(IButtonControl)GetObject(propDefaultButton);
button = _acceptButton;
if (button)
{
//PerformClick now checks for validationcancelled...
//if (button is Control)
{
button.performClick();
}
return true;
}
break;
case Keys.ESCAPE:
// button = cast(IButtonControl)GetObject(propCancelButton);
button = _cancelButton;
if (button)
{
// In order to keep the behavior in sync with native
// and MFC dialogs, we want to not give the cancel button
// the focus on Escape. If we do, we end up with giving it
// the focus when we reshow the dialog.
//if (button is Control) {
// ((Control)button).Focus();
//}
button.performClick();
return true;
}
break;
default:
// Fall through
}
}
return super.processDialogKey(keyData);
}
/// Process mnemonic (access key) such as Alt+T.
// Returns true when processed, otherwise call super class.
protected override bool processDialogChar(char charCode)
{
// If we're the top-level form or control, we need to do the mnemonic handling
//
if (isMdiChild && charCode != ' ')
{
if (processMnemonic(charCode))
{
return true;
}
// ContainerControl calls ProcessMnemonic starting from the active MdiChild form (this)
// so let's flag it as processed.
_mnemonicProcessed = true;
try
{
return super.processDialogChar(charCode);
}
finally
{
_mnemonicProcessed = false;
}
}
// Non-MdiChild form, just pass the call to ContainerControl.
return super.processDialogChar(charCode);
}
/// Changes forcus by TAB
// Returns true when changed forcus, otherwise returns false.
protected override bool processTabKey(bool forward)
{
if (selectNextControl(activeControl, forward, true, true, true))
{
return true;
}
// I've added a special case for UserControls because they shouldn't cycle back to the
// beginning if they don't have a parent form, such as when they're on an ActiveXBridge.
if (isMdiChild || !parentForm)
{
bool selected = selectNextControl(null, forward, true, true, false);
if (selected)
{
return true;
}
}
return false;
}
package alias _defFormProc = dfl.internal.utf.defDlgProc;
protected override void defWndProc(ref Message msg)
{
@ -2990,7 +3144,7 @@ class Form: ContainerControl, IDialogResult // docmain
private:
IButtonControl acceptBtn, cancelBtn;
IButtonControl _acceptButton, _cancelButton;
bool autoscale = true;
Size autoscaleBase;
DialogResult fresult = DialogResult.NONE;
@ -3018,7 +3172,10 @@ class Form: ContainerControl, IDialogResult // docmain
Form wowner = null, wmdiparent = null;
//bool _closingvisible;
bool nofilter = false;
bool _keyPreview = false; // Gets or sets a value indicating whether the form will receive key events
// before the event is passed to the control that has focus.
bool _mnemonicProcessed = false;
version(NO_MDI) {} else
{
MdiClient _mdiClient = null; // null == not MDI container.
@ -3027,13 +3184,15 @@ class Form: ContainerControl, IDialogResult // docmain
package static bool wantsAllKeys(HWND hwnd)
{
return (SendMessageA(hwnd, WM_GETDLGCODE, 0, 0) &
DLGC_WANTALLKEYS) != 0;
return (SendMessageA(hwnd, WM_GETDLGCODE, 0, 0) & DLGC_WANTALLKEYS) != 0;
}
private static class FormMessageFilter: IMessageFilter
{
/// Filters out a message before it is dispatched.
// Returns: true when filter the message and stop it from being dispatched;
// false when allow the message and continue to the next filter or control.
protected bool preFilterMessage(ref Message m)
{
version(NO_MDI)
@ -3086,13 +3245,11 @@ class Form: ContainerControl, IDialogResult // docmain
case WM_CHAR:
switch(cast(Keys)m.wParam)
{
case Keys.ENTER:
if(form.acceptButton)
{
dfl.internal.utf.isDialogMessage(form.handle, &m._winMsg);
return true; // Prevent.
}
return false;
// case Keys.ENTER:
// This case has been moved to wndProc().
// If parent form does not have a default button,
// input RETURN to TextBox regardless of
// the value of TextBox.acceptsReturn.
case Keys.ESCAPE:
if(form.cancelButton)
@ -3145,22 +3302,28 @@ class Form: ContainerControl, IDialogResult // docmain
case Keys.TAB:
{
LRESULT dlgc;
Control cc;
dlgc = SendMessageA(m.hWnd, WM_GETDLGCODE, 0, 0);
cc = fromHandle(m.hWnd);
// When window message is WM_KEYDOWN, WM_KEYUP and WM_CHAR,
// wParam is not contain modifier keys such as CONTROL, SHIFT.
// So uses GetKeyState().
LRESULT dlgc = SendMessageA(m.hWnd, WM_GETDLGCODE, 0, 0);
Control cc = fromHandle(m.hWnd);
bool isPressingControl = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
if(cc)
{
if(cc._wantTabKey())
// When CONTROL is pressing, do not input TAB but change forcus.
if(cc._wantTabKey() && !isPressingControl)
return false; // Continue.
}
else
{
if(dlgc & DLGC_WANTALLKEYS)
// When CONTROL is pressing, do not input TAB but change forcus.
if((dlgc & DLGC_WANTALLKEYS) && !isPressingControl)
return false; // Continue.
}
//if(dlgc & (DLGC_WANTTAB | DLGC_WANTALLKEYS))
if(dlgc & DLGC_WANTTAB)
// When CONTROL is pressing, do not input TAB but change forcus.
if((dlgc & DLGC_WANTTAB) && !isPressingControl)
return false; // Continue.
if(WM_KEYDOWN == m.msg)
{
@ -3302,9 +3465,9 @@ class Form: ContainerControl, IDialogResult // docmain
/+
package final bool _dlgescape()
{
if(cancelBtn)
if(_cancelButton)
{
cancelBtn.performClick();
_cancelButton.performClick();
return true;
}
return false;

View file

@ -8,10 +8,10 @@ module dfl;
public import dfl.base, dfl.menu, dfl.control, dfl.usercontrol,
dfl.form, dfl.drawing, dfl.panel, dfl.event,
dfl.application, dfl.button, dfl.socket,
dfl.timer, dfl.environment, dfl.label, dfl.textbox,
dfl.timer, dfl.environment, dfl.label, dfl.textboxbase, dfl.textbox,
dfl.listbox, dfl.splitter, dfl.groupbox, dfl.messagebox,
dfl.registry, dfl.notifyicon, dfl.collections, dfl.data,
dfl.clipboard, dfl.commondialog, dfl.richtextbox, dfl.tooltip,
dfl.combobox, dfl.treeview, dfl.picturebox, dfl.tabcontrol,
dfl.listview, dfl.statusbar, dfl.progressbar, dfl.resources,
dfl.imagelist, dfl.toolbar, dfl.trackbar;
dfl.imagelist, dfl.toolbar, dfl.trackbar, dfl.sharedcontrol;

View file

@ -5,9 +5,14 @@
///
module dfl.richtextbox;
private import dfl.textbox, dfl.internal.winapi, dfl.event, dfl.application;
private import dfl.base, dfl.drawing, dfl.data;
private import dfl.control, dfl.internal.utf, dfl.internal.dlib;
private import dfl.textboxbase;
private import dfl.base, dfl.application;
private import dfl.event, dfl.drawing, dfl.data;
private import dfl.control;
private import dfl.internal.winapi;
private import dfl.internal.utf;
private import dfl.internal.dlib;
version(DFL_NO_MENUS)
{
@ -118,7 +123,7 @@ class RichTextBox: TextBoxBase // docmain
return wcurs; // Do return null and don't inherit.
}
alias TextBoxBase.cursor cursor; // Overload.
alias cursor = TextBoxBase.cursor; // Overload.
override @property Dstring selectedText() // getter
@ -138,7 +143,7 @@ class RichTextBox: TextBoxBase // docmain
return null;
}
alias TextBoxBase.selectedText selectedText; // Overload.
alias selectedText = TextBoxBase.selectedText; // Overload.
override @property void selectionLength(uint len) // setter
@ -200,13 +205,13 @@ class RichTextBox: TextBoxBase // docmain
override @property void maxLength(uint len) // setter
{
lim = len;
_lim = len;
if(created)
SendMessageA(handle, EM_EXLIMITTEXT, 0, cast(LPARAM)len);
}
alias TextBoxBase.maxLength maxLength; // Overload.
alias maxLength = TextBoxBase.maxLength; // Overload.
override @property Size defaultSize() // getter
@ -233,7 +238,7 @@ class RichTextBox: TextBoxBase // docmain
super.backColor(c);
}
alias TextBoxBase.backColor backColor; // Overload.
alias backColor = TextBoxBase.backColor; // Overload.
private void _setfc(Color c)
@ -260,7 +265,7 @@ class RichTextBox: TextBoxBase // docmain
super.foreColor(c);
}
alias TextBoxBase.foreColor foreColor; // Overload.
alias foreColor = TextBoxBase.foreColor; // Overload.
///
@ -303,7 +308,7 @@ class RichTextBox: TextBoxBase // docmain
}
}
alias TextBoxBase.paste paste; // Overload.
alias paste = TextBoxBase.paste; // Overload.
///

152
source/dfl/sharedcontrol.d Normal file
View file

@ -0,0 +1,152 @@
// Written by Christopher E. Miller
// See the included license.txt for copyright and license details.
///
module dfl.sharedcontrol;
private import dfl.control;
private import dfl.application;
private import dfl.internal.winapi;
private import dfl.internal.dlib;
private import dfl.internal.clib;
///
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);
}
}

File diff suppressed because it is too large Load diff

1214
source/dfl/textboxbase.d Normal file

File diff suppressed because it is too large Load diff