diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs
index 96f0b8f233..91dfe3eef2 100644
--- a/src/Gtk/Perspex.Gtk/WindowImpl.cs
+++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs
@@ -4,6 +4,9 @@
//
// -----------------------------------------------------------------------
+using Gdk;
+using Perspex.Input;
+
namespace Perspex.Gtk
{
using System;
@@ -22,25 +25,33 @@ namespace Perspex.Gtk
private Size clientSize;
+ private Gtk.IMContext imContext;
+
+ private uint lastKeyEventTimestamp;
+
public WindowImpl()
: base(Gtk.WindowType.Toplevel)
{
this.DefaultSize = new Gdk.Size(640, 480);
- this.Events = Gdk.EventMask.PointerMotionMask |
- Gdk.EventMask.ButtonPressMask |
- Gdk.EventMask.ButtonReleaseMask;
- this.windowHandle = new PlatformHandle(this.Handle, "GtkWindow");
+ Init();
}
public WindowImpl(Gtk.WindowType type)
: base(type)
+ {
+ Init();
+ }
+
+ private void Init()
{
this.Events = Gdk.EventMask.PointerMotionMask |
- Gdk.EventMask.ButtonPressMask |
- Gdk.EventMask.ButtonReleaseMask;
+ Gdk.EventMask.ButtonPressMask |
+ Gdk.EventMask.ButtonReleaseMask;
this.windowHandle = new PlatformHandle(this.Handle, "GtkWindow");
+ this.imContext = new Gtk.IMMulticontext();
+ this.imContext.Commit += ImContext_Commit;
}
-
+
public Size ClientSize
{
get;
@@ -148,19 +159,29 @@ namespace Perspex.Gtk
this.Closed();
}
- protected override bool OnKeyPressEvent(Gdk.EventKey evnt)
+ private bool ProcessKeyEvent(Gdk.EventKey evnt)
{
- var keyChar = (char)Gdk.Keyval.ToUnicode ((uint)evnt.Key);
- var keyText = keyChar == 0 ? string.Empty : new string (keyChar, 1);
+ this.lastKeyEventTimestamp = evnt.Time;
+ if (this.imContext.FilterKeypress(evnt))
+ return true;
var e = new RawKeyEventArgs(
GtkKeyboardDevice.Instance,
evnt.Time,
- RawKeyEventType.KeyDown,
- GtkKeyboardDevice.ConvertKey(evnt.Key), keyText);
+ evnt.Type == EventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
+ GtkKeyboardDevice.ConvertKey(evnt.Key));
this.Input(e);
return true;
}
+ protected override bool OnKeyPressEvent(Gdk.EventKey evnt) => ProcessKeyEvent(evnt);
+
+ protected override bool OnKeyReleaseEvent(EventKey evnt) => ProcessKeyEvent(evnt);
+
+ private void ImContext_Commit(object o, Gtk.CommitArgs args)
+ {
+ this.Input(new RawTextInputEventArgs(GtkKeyboardDevice.Instance, this.lastKeyEventTimestamp, args.Str));
+ }
+
protected override bool OnExposeEvent(Gdk.EventExpose evnt)
{
this.Paint(evnt.Area.ToPerspex(), this.GetHandle(evnt.Window));
diff --git a/src/Perspex.Controls/MenuItemAccessKeyHandler.cs b/src/Perspex.Controls/MenuItemAccessKeyHandler.cs
index f4bdec0164..6387c7c651 100644
--- a/src/Perspex.Controls/MenuItemAccessKeyHandler.cs
+++ b/src/Perspex.Controls/MenuItemAccessKeyHandler.cs
@@ -53,7 +53,7 @@ namespace Perspex.Controls
this.owner = owner;
- this.owner.AddHandler(InputElement.KeyDownEvent, this.OnKeyDown);
+ this.owner.AddHandler(InputElement.TextInputEvent, this.OnTextInput);
}
///
@@ -90,7 +90,7 @@ namespace Perspex.Controls
///
/// The event sender.
/// The event args.
- protected virtual void OnKeyDown(object sender, KeyEventArgs e)
+ protected virtual void OnTextInput(object sender, TextInputEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.Text))
{
diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs
index 3b410b361f..30efed5129 100644
--- a/src/Perspex.Controls/TextBox.cs
+++ b/src/Perspex.Controls/TextBox.cs
@@ -128,12 +128,27 @@ namespace Perspex.Controls
this.presenter.HideCaret();
}
+ protected override void OnTextInput(TextInputEventArgs e)
+ {
+ string text = this.Text ?? string.Empty;
+ int caretIndex = this.CaretIndex;
+ if (!string.IsNullOrEmpty(e.Text))
+ {
+ this.DeleteSelection();
+ caretIndex = this.CaretIndex;
+ text = this.Text ?? string.Empty;
+ this.Text = text.Substring(0, caretIndex) + e.Text + text.Substring(caretIndex);
+ ++this.CaretIndex;
+ this.SelectionStart = this.SelectionEnd = this.CaretIndex;
+ }
+ base.OnTextInput(e);
+ }
+
protected override void OnKeyDown(KeyEventArgs e)
{
string text = this.Text ?? string.Empty;
int caretIndex = this.CaretIndex;
bool movement = false;
- bool textEntered = false;
bool handled = true;
var modifiers = e.Device.Modifiers;
@@ -220,16 +235,6 @@ namespace Perspex.Controls
break;
default:
- if (!string.IsNullOrEmpty(e.Text) && !modifiers.HasFlag(ModifierKeys.Control) && !modifiers.HasFlag(ModifierKeys.Alt))
- {
- this.DeleteSelection();
- caretIndex = this.CaretIndex;
- text = this.Text ?? string.Empty;
- this.Text = text.Substring(0, caretIndex) + e.Text + text.Substring(caretIndex);
- ++this.CaretIndex;
- textEntered = true;
- }
-
break;
}
@@ -237,7 +242,7 @@ namespace Perspex.Controls
{
this.SelectionEnd = this.CaretIndex;
}
- else if (movement || textEntered)
+ else if (movement)
{
this.SelectionStart = this.SelectionEnd = this.CaretIndex;
}
diff --git a/src/Perspex.Input/AccessKeyHandler.cs b/src/Perspex.Input/AccessKeyHandler.cs
index 6df206bc67..ddb508dda2 100644
--- a/src/Perspex.Input/AccessKeyHandler.cs
+++ b/src/Perspex.Input/AccessKeyHandler.cs
@@ -150,7 +150,7 @@ namespace Perspex.Input
{
// If any other key is pressed with the Alt key held down, or the main menu is open,
// find all controls who have registered that access key.
- var text = e.Text.ToUpper();
+ var text = e.Key.ToString().ToUpper();
var matches = this.registered
.Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible)
.Select(x => x.Item2);
diff --git a/src/Perspex.Input/IInputElement.cs b/src/Perspex.Input/IInputElement.cs
index fcbaf5eed5..3ce3c5d9d0 100644
--- a/src/Perspex.Input/IInputElement.cs
+++ b/src/Perspex.Input/IInputElement.cs
@@ -34,6 +34,11 @@ namespace Perspex.Input
///
event EventHandler KeyUp;
+ ///
+ /// Occurs when a user typed some text while the control has focus.
+ ///
+ event EventHandler TextInput;
+
///
/// Occurs when the pointer enters the control.
///
diff --git a/src/Perspex.Input/InputElement.cs b/src/Perspex.Input/InputElement.cs
index 1743926e0f..154f71b32a 100644
--- a/src/Perspex.Input/InputElement.cs
+++ b/src/Perspex.Input/InputElement.cs
@@ -81,6 +81,14 @@ namespace Perspex.Input
"KeyUp",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent TextInputEvent =
+ RoutedEvent.Register(
+ "TextInput",
+ RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
+
///
/// Defines the event.
///
@@ -136,6 +144,7 @@ namespace Perspex.Input
LostFocusEvent.AddClassHandler(x => x.OnLostFocus);
KeyDownEvent.AddClassHandler(x => x.OnKeyDown);
KeyUpEvent.AddClassHandler(x => x.OnKeyUp);
+ TextInputEvent.AddClassHandler(x => x.OnTextInput);
PointerEnterEvent.AddClassHandler(x => x.OnPointerEnter);
PointerLeaveEvent.AddClassHandler(x => x.OnPointerLeave);
PointerMovedEvent.AddClassHandler(x => x.OnPointerMoved);
@@ -180,6 +189,15 @@ namespace Perspex.Input
remove { this.RemoveHandler(KeyUpEvent, value); }
}
+ ///
+ /// Occurs when a user typed some text while the control has focus.
+ ///
+ public event EventHandler TextInput
+ {
+ add { this.AddHandler(TextInputEvent, value); }
+ remove { this.RemoveHandler(TextInputEvent, value); }
+ }
+
///
/// Occurs when the pointer enters the control.
///
@@ -377,6 +395,14 @@ namespace Perspex.Input
{
}
+ ///
+ /// Called before the event occurs.
+ ///
+ /// The event args.
+ protected virtual void OnTextInput(TextInputEventArgs e)
+ {
+ }
+
///
/// Called before the event occurs.
///
diff --git a/src/Perspex.Input/KeyEventArgs.cs b/src/Perspex.Input/KeyEventArgs.cs
index 7ae86d4777..299bb015ef 100644
--- a/src/Perspex.Input/KeyEventArgs.cs
+++ b/src/Perspex.Input/KeyEventArgs.cs
@@ -14,7 +14,5 @@ namespace Perspex.Input
public IKeyboardDevice Device { get; set; }
public Key Key { get; set; }
-
- public string Text { get; set; }
}
}
diff --git a/src/Perspex.Input/KeyboardDevice.cs b/src/Perspex.Input/KeyboardDevice.cs
index d9f7c2c9f9..2e19cbbd19 100644
--- a/src/Perspex.Input/KeyboardDevice.cs
+++ b/src/Perspex.Input/KeyboardDevice.cs
@@ -22,7 +22,7 @@ namespace Perspex.Input
public KeyboardDevice()
{
this.InputManager.RawEventReceived
- .OfType()
+ .OfType()
.Where(x => x.Device == this)
.Subscribe(this.ProcessRawEvent);
}
@@ -93,30 +93,45 @@ namespace Perspex.Input
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
- private void ProcessRawEvent(RawKeyEventArgs e)
+ private void ProcessRawEvent(RawInputEventArgs e)
{
IInputElement element = this.FocusedElement;
if (element != null)
{
- switch (e.Type)
+ var keyInput = e as RawKeyEventArgs;
+ if (keyInput != null)
{
- case RawKeyEventType.KeyDown:
- case RawKeyEventType.KeyUp:
- var routedEvent = e.Type == RawKeyEventType.KeyDown ?
- InputElement.KeyDownEvent : InputElement.KeyUpEvent;
-
- KeyEventArgs ev = new KeyEventArgs
- {
- RoutedEvent = routedEvent,
- Device = this,
- Key = e.Key,
- Text = e.Text,
- Source = element,
- };
-
- element.RaiseEvent(ev);
- break;
+ switch (keyInput.Type)
+ {
+ case RawKeyEventType.KeyDown:
+ case RawKeyEventType.KeyUp:
+ var routedEvent = keyInput.Type == RawKeyEventType.KeyDown
+ ? InputElement.KeyDownEvent
+ : InputElement.KeyUpEvent;
+
+ KeyEventArgs ev = new KeyEventArgs
+ {
+ RoutedEvent = routedEvent,
+ Device = this,
+ Key = keyInput.Key,
+ Source = element,
+ };
+
+ element.RaiseEvent(ev);
+ break;
+ }
+ }
+ var text = e as RawTextInputEventArgs;
+ if (text != null)
+ {
+ element.RaiseEvent(new TextInputEventArgs()
+ {
+ Device = this,
+ Text = text.Text,
+ Source = element,
+ RoutedEvent = InputElement.TextInputEvent
+ });
}
}
}
diff --git a/src/Perspex.Input/Perspex.Input.csproj b/src/Perspex.Input/Perspex.Input.csproj
index f93f2a331e..cc8c4195f4 100644
--- a/src/Perspex.Input/Perspex.Input.csproj
+++ b/src/Perspex.Input/Perspex.Input.csproj
@@ -66,6 +66,7 @@
+
@@ -100,6 +101,7 @@
+
diff --git a/src/Perspex.Input/Raw/RawKeyEventArgs.cs b/src/Perspex.Input/Raw/RawKeyEventArgs.cs
index 8f17c14410..4f2bccc780 100644
--- a/src/Perspex.Input/Raw/RawKeyEventArgs.cs
+++ b/src/Perspex.Input/Raw/RawKeyEventArgs.cs
@@ -18,19 +18,15 @@ namespace Perspex.Input.Raw
IKeyboardDevice device,
uint timestamp,
RawKeyEventType type,
- Key key,
- string text)
+ Key key)
: base(device, timestamp)
{
this.Key = key;
this.Type = type;
- this.Text = text;
}
public Key Key { get; set; }
-
- public string Text { get; set; }
-
+
public RawKeyEventType Type { get; set; }
}
}
diff --git a/src/Perspex.Input/Raw/RawTextInputEventArgs.cs b/src/Perspex.Input/Raw/RawTextInputEventArgs.cs
new file mode 100644
index 0000000000..6739bd718d
--- /dev/null
+++ b/src/Perspex.Input/Raw/RawTextInputEventArgs.cs
@@ -0,0 +1,13 @@
+namespace Perspex.Input.Raw
+{
+ public class RawTextInputEventArgs : RawInputEventArgs
+ {
+
+ public string Text { get; set; }
+
+ public RawTextInputEventArgs(IKeyboardDevice device, uint timestamp, string text) : base(device, timestamp)
+ {
+ Text = text;
+ }
+ }
+}
diff --git a/src/Perspex.Input/TextInputEventArgs.cs b/src/Perspex.Input/TextInputEventArgs.cs
new file mode 100644
index 0000000000..0639e1d1cf
--- /dev/null
+++ b/src/Perspex.Input/TextInputEventArgs.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Perspex.Interactivity;
+
+namespace Perspex.Input
+{
+ public class TextInputEventArgs : RoutedEventArgs
+ {
+ public IKeyboardDevice Device { get; set; }
+
+ public string Text { get; set; }
+ }
+}
diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs
index 0ffa2fbf5f..ff8792ccc2 100644
--- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs
@@ -508,10 +508,10 @@ namespace Perspex.Win32.Interop
IntPtr hInstance,
IntPtr lpParam);
- [DllImport("user32.dll")]
+ [DllImport("user32.dll", EntryPoint = "DefWindowProcW")]
public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
- [DllImport("user32.dll")]
+ [DllImport("user32.dll", EntryPoint = "DispatchMessageW")]
public static extern IntPtr DispatchMessage(ref MSG lpmsg);
[DllImport("user32.dll", SetLastError = true)]
@@ -538,7 +538,7 @@ namespace Perspex.Win32.Interop
[DllImport("user32.dll")]
public static extern bool GetKeyboardState(byte[] lpKeyState);
- [DllImport("user32.dll")]
+ [DllImport("user32.dll", EntryPoint = "GetMessageW")]
public static extern sbyte GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll")]
@@ -565,6 +565,9 @@ namespace Perspex.Win32.Interop
[DllImport("user32.dll")]
public static extern bool IsWindowEnabled(IntPtr hWnd);
+ [DllImport("user32.dll")]
+ public static extern bool IsWindowUnicode(IntPtr hWnd);
+
[DllImport("user32.dll")]
public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);
@@ -577,7 +580,7 @@ namespace Perspex.Win32.Interop
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
- [DllImport("user32.dll", SetLastError = true)]
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")]
public static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs
index f4e8051590..e1ca8042b7 100644
--- a/src/Windows/Perspex.Win32/WindowImpl.cs
+++ b/src/Windows/Perspex.Win32/WindowImpl.cs
@@ -4,6 +4,8 @@
//
// -----------------------------------------------------------------------
+using Perspex.Input;
+
namespace Perspex.Win32
{
using System;
@@ -203,6 +205,8 @@ namespace Perspex.Win32
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
+ bool unicode = UnmanagedMethods.IsWindowUnicode(hWnd);
+
const double WheelDelta = 120.0;
uint timestamp = unchecked((uint)UnmanagedMethods.GetMessageTime());
@@ -252,8 +256,7 @@ namespace Perspex.Win32
WindowsKeyboardDevice.Instance,
timestamp,
RawKeyEventType.KeyDown,
- KeyInterop.KeyFromVirtualKey((int)wParam),
- WindowsKeyboardDevice.Instance.StringFromVirtualKey((uint)wParam));
+ KeyInterop.KeyFromVirtualKey((int)wParam));
break;
case UnmanagedMethods.WindowsMessage.WM_KEYUP:
@@ -263,10 +266,12 @@ namespace Perspex.Win32
WindowsKeyboardDevice.Instance,
timestamp,
RawKeyEventType.KeyUp,
- KeyInterop.KeyFromVirtualKey((int)wParam),
- WindowsKeyboardDevice.Instance.StringFromVirtualKey((uint)wParam));
+ KeyInterop.KeyFromVirtualKey((int)wParam));
+ break;
+ case UnmanagedMethods.WindowsMessage.WM_CHAR:
+ e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp,
+ new string((char) wParam.ToInt32(), 1));
break;
-
case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN:
e = new RawMouseEventArgs(
WindowsMouseDevice.Instance,
diff --git a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs
index 0f59636292..d105dd9fb3 100644
--- a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs
+++ b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs
@@ -300,8 +300,7 @@ namespace Perspex.Controls.UnitTests
new Mock().Object,
0,
RawKeyEventType.KeyDown,
- Key.A,
- "A");
+ Key.A);
impl.Object.Input(input);
var inputManagerMock = Mock.Get(InputManager.Instance);