You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

819 lines
29 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections;
using System.Runtime.InteropServices;
using POSV.Keyboard.Touch;
namespace POSV.Keyboard
{
/// <summary>
/// Represents virtual keyboard control.
/// </summary>
[ToolboxItem(true)]
public class KeyboardControl : Control
{
private TouchHandler _TouchHandler = null;
/// <summary>
/// Creates a new VirtualKeyboard control.
/// </summary>
public KeyboardControl()
{
_ColorTable = _DefaultColorTable;
// Do not allow the control to receive focus.
SetStyle(ControlStyles.Selectable, false);
// Do not call OnPaintBackground().
SetStyle(ControlStyles.Opaque, true);
// Enable double buffering. OptimizedDoubleBuffer and AllPaintingInWmPaint are set by the DoubleBuffered property below.
//SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
//SetStyle(ControlStyles.AllPaintingInWmPaint, true);
DoubleBuffered = true;
ResizeRedraw = true;
_RepeatTimer = new Timer();
_RepeatTimer.Tick += new EventHandler(RepeatTimer_Tick);
}
// The model for the Keyboard. This object contains all the information about the keys
// and the possible layouts of the keyboard.
private Keyboard _Keyboard = null; //Keyboard.CreateDefaultKeyboard();
/// <summary>
/// Gets or sets the Keyboard object that describes the VirtualKeyboard.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Keyboard Keyboard
{
get
{
if(_Keyboard==null)
_Keyboard = Keyboard.CreateDefaultKeyboard();
return _Keyboard;
}
set { _Keyboard = value; }
}
/// <summary>
/// Default width for the keyboard in pixels.
/// </summary>
public static int DefaultWidth
{
get { return 740; }
}
/// <summary>
/// Default height for the keyboard in pixels.
/// </summary>
public static int DefaultHeight {
get { return 250; }
}
protected override Size DefaultSize
{
get
{
return Dpi.Size(new Size(DefaultWidth, DefaultHeight));
}
}
private VirtualKeyboardColorTable _DefaultColorTable = new VirtualKeyboardColorTable();
private VirtualKeyboardColorTable _ColorTable;
/// <summary>
/// Gets or set the ColorTable used to draw the keyboard.
/// </summary>
public VirtualKeyboardColorTable ColorTable
{
get { return _ColorTable; }
set
{
_ColorTable = value;
Invalidate(); // Ok to invalidate entire control, to render everything with the new colors.
}
}
private Renderer _Renderer = new FlatStyleRenderer();
/// <summary>
/// Gets or sets the Renderer used to render the keyboard.
/// </summary>
public Renderer Renderer
{
get { return _Renderer; }
set
{
if (value == null)
value = new FlatStyleRenderer();
_Renderer = value;
Invalidate(); // Ok to invalidate entire control, to redraw everything with the new renderer.
}
}
// Through this method, the keys generated by the Virtual Keyboard are sent to the target control.
// In this version, the current target control is always the control with input focus and the keyboard
// is only visible when the control (to which the keyboard was previously attached) has input focus.
// Therefore, SendKeys.Send offers all the logic needed for this version of the keyboard.
//
// For future versions, we might consider using the Win32 API SendInput function for greater flexibility.
private void SendKeysToTarget(string info)
{
KeyboardKeyCancelEventArgs cancelArgs = new KeyboardKeyCancelEventArgs(info);
OnSendingKey(cancelArgs);
if (cancelArgs.Cancel) return;
SendKeys.Send(info);
OnKeySent(new KeyboardKeyEventArgs(info));
}
#region Send repeating key if mouse is hold down
private Timer _RepeatTimer; // Used to send repeated keys to the target control.
private Key _PressedKey; // Used to keep the keys that is repeatedly sent to the target control.
private void RepeatTimer_Tick(object sender, EventArgs e)
{
if (_PressedKey != null)
{
if (_RepeatTimer.Interval == KeyboardDelay)
_RepeatTimer.Interval = KeyboardSpeed;
SendKeysToTarget(_PressedKey.Info);
}
}
private void CancelRepeatKey()
{
_PressedKey = null;
_RepeatTimer.Stop();
}
private void StartRepeatKey(Key key)
{
_PressedKey = key;
_RepeatTimer.Start();
_RepeatTimer.Interval = KeyboardDelay;
}
private static int KeyboardDelay
{
get
{
// SystemInformation.KeyboardDelay is between 0 (2.5 repetitions per second) and 31 (about 30 per second)
// but these values arre hardware dependendant, so the fomula below is a fast and accurate enough not to feel
// any difference between this and a real keyboard at the same values.
return 1000 / (SystemInformation.KeyboardDelay + 1);
}
}
private static int KeyboardSpeed
{
get
{
// See KeyboardDelay for details.
return 1000 / (SystemInformation.KeyboardSpeed + 1);
}
}
#endregion
private bool _IsTopBarVisible = true;
/// <summary>
/// Gets or sets a value indicating if the top bar of the keyboard is visible.
/// </summary>
[DefaultValue(true)]
[Description("Indicates if the top bar of the keyboard is visible.")]
[Category("Appearance")]
public bool IsTopBarVisible
{
get { return _IsTopBarVisible; }
set
{
_IsTopBarVisible = value;
this.Invalidate();
}
}
private int _CaptionHeight = 20;
/// <summary>
/// Indicates the height of the keyboard caption which hosts the close button.
/// </summary>
[DefaultValue(20), Category("Appearance"), Description("Indicates the height of the keyboard caption which hosts the close button.")]
public int CaptionHeight
{
get { return _CaptionHeight; }
set
{
if (value != _CaptionHeight)
{
int oldValue = _CaptionHeight;
_CaptionHeight = value;
this.Invalidate();
}
}
}
//private const int TopBarHeight = 20;
private int TopBarVisibleHeight
{
get { return IsTopBarVisible ? ActualCaptionHeight + Border : 0; }
}
public int Border {
get { return Dpi.Width10; }
}
private readonly Key HideKeyboardKey = new Key("Hide", info: null);
private int ActualCaptionHeight
{
get { return Dpi.Height(_CaptionHeight); }
}
private Rectangle TopBarRectangle
{
get { return new Rectangle(Border, Border, Width - 2 * Border, ActualCaptionHeight); }
}
private Rectangle CloseButtonRectangle
{
get { return new Rectangle(Width - ActualCaptionHeight - Border, Border, ActualCaptionHeight, ActualCaptionHeight); }
}
private Rectangle KeysRectangle
{
get
{
Rectangle rect = new Rectangle(Border, Border, Width - 2 * Border, Height - 2 * Border);
if (IsTopBarVisible)
{
rect.Y += ActualCaptionHeight + Border;
rect.Height -= ActualCaptionHeight + Border;
}
return rect;
}
}
protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
{
Dpi.SetScaling(factor);
base.ScaleControl(factor, specified);
}
protected override void OnPaint(PaintEventArgs e)
{
KeyboardLayout currentLayout = Keyboard.CurrentLayout;
if (currentLayout.LogicalWidth <= 0 || currentLayout.LogicalHeight <= 0)
return;
Rectangle rectKeys = KeysRectangle;
float sfx = (float)rectKeys.Width / currentLayout.LogicalWidth;
float sfy = (float)rectKeys.Height / currentLayout.LogicalHeight;
Renderer.ColorTable = ColorTable;
Renderer.BeginFrame(new BeginFrameRendererEventArgs(e.Graphics, e.ClipRectangle, rectKeys, Font, currentLayout));
Renderer.DrawBackground(new BackgroundRendererEventArgs(e.Graphics, e.ClipRectangle, new Rectangle(0, 0, Width, Height)));
if (IsTopBarVisible)
{
Renderer.DrawTopBar(new TopBarRendererEventArgs(e.Graphics, e.ClipRectangle, TopBarRectangle, Text, Font));
Renderer.DrawCloseButton(new CloseButtonRendererEventArgs(e.Graphics, _PressedKey == HideKeyboardKey, e.ClipRectangle, CloseButtonRectangle));
}
foreach (Key key in currentLayout.Keys)
{
Rectangle rectKey = new Rectangle((int)(key.Bounds.X * sfx), (int)(key.Bounds.Y * sfy),
(int)(key.Bounds.Width * sfx), (int)(key.Bounds.Height * sfy));
rectKey.Offset(rectKeys.Left, rectKeys.Top);
Renderer.DrawKey(new KeyRendererEventArgs(e.Graphics, key, key == _PressedKey, e.ClipRectangle, rectKey));
}
Renderer.EndFrame();
#if TRIAL
using (Font font = new System.Drawing.Font(this.Font.FontFamily, 7, FontStyle.Regular))
{
using (SolidBrush brush = new SolidBrush(Color.FromArgb(24, Color.White)))
e.Graphics.DrawString("DotNetBar Trial Version", font, brush, (this.ClientRectangle.Width - 80) / 2, this.ClientRectangle.Height - 32);
}
#endif
}
private void PerformMoveAction(Point location)
{
if (IsTopBarVisible && CloseButtonRectangle.Contains(location))
{
Cursor = Cursors.Hand;
}
else
{
KeyboardLayout currentLayout = Keyboard.CurrentLayout;
Rectangle rectKeys = KeysRectangle;
float fx = (float)rectKeys.Width / currentLayout.LogicalWidth;
float fy = (float)rectKeys.Height / currentLayout.LogicalHeight;
if (_PressedKey == HideKeyboardKey)
{
if (_PressedKey != null)
{
Invalidate(GetKeyPhysicalBounds(_PressedKey, fx, fy));
}
_PressedKey = null;
Invalidate(CloseButtonRectangle);
this.Update();
}
Key key = null;
if (KeysRectangle.Contains(location))
key = currentLayout.KeyHitTest((int)((location.X - rectKeys.Left) / fx), (int)((location.Y - rectKeys.Top) / fy));
// If the mouse is over a key, change the cursor to the Hand cursor, to provide
// a visual feedback for the user to know that it can interact with this key.
Key pressedKey = _PressedKey;
if (key == null)
{
Cursor = Cursors.Arrow;
CancelRepeatKey();
if (pressedKey != null)
{
Invalidate(GetKeyPhysicalBounds(pressedKey, fx, fy)); // Only invalidate area under the currently pressed key.
this.Update();
}
}
else if (key != pressedKey)
{
Cursor = Cursors.Hand;
CancelRepeatKey();
if (pressedKey != null)
{
Invalidate(GetKeyPhysicalBounds(pressedKey, fx, fy)); // Only invalidate area under the currently pressed key.
this.Update();
}
}
else
{
Cursor = Cursors.Hand;
}
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (ProcessMouseAction)
{
PerformMoveAction(e.Location);
_LastTouchAction = null;
}
base.OnMouseMove(e);
}
protected override void OnMouseLeave(EventArgs e)
{
KeyboardLayout currentLayout = Keyboard.CurrentLayout;
Rectangle rectKeys = KeysRectangle;
float fx = (float)rectKeys.Width / currentLayout.LogicalWidth;
float fy = (float)rectKeys.Height / currentLayout.LogicalHeight;
if (_PressedKey != HideKeyboardKey)
Invalidate(CloseButtonRectangle);
else if (_PressedKey != null)
{
Invalidate(GetKeyPhysicalBounds(_PressedKey, fx, fy)); // Only invalidate area under the currently pressed key.
this.Update();
}
CancelRepeatKey();
}
private bool _TouchEnabled = true;
/// <summary>
/// Indicates whether touch support is enabled when provided by hardware.
/// </summary>
[DefaultValue(true), Category("Behavior"), Description("Indicates whether touch support is enabled when provided by hardware.")]
public bool TouchEnabled
{
get { return _TouchEnabled; }
set
{
if (value != _TouchEnabled)
{
bool oldValue = _TouchEnabled;
_TouchEnabled = value;
OnTouchEnabledChanged(oldValue, value);
}
}
}
/// <summary>
/// Called when TouchEnabled property has changed.
/// </summary>
/// <param name="oldValue">Old property value</param>
/// <param name="newValue">New property value</param>
protected virtual void OnTouchEnabledChanged(bool oldValue, bool newValue)
{
//OnPropertyChanged(new PropertyChangedEventArgs("TouchEnabled"));
}
protected override void OnHandleCreated(EventArgs e)
{
if (TouchHandler.IsTouchEnabled)
{
_TouchHandler = new TouchHandler(this, eTouchHandlerType.Touch);
_TouchHandler.TouchDown += new EventHandler<TouchEventArgs>(TouchHandlerTouchDown);
_TouchHandler.TouchUp += new EventHandler<TouchEventArgs>(TouchHandlerTouchUp);
// Don't need touch move action for keyboard handling
//_TouchHandler.TouchMove += new EventHandler<TouchEventArgs>(TouchHandlerTouchMove);
}
base.OnHandleCreated(e);
}
private DateTime? _LastTouchAction = null;
//void TouchHandlerTouchMove(object sender, TouchEventArgs e)
//{
// PerformMoveAction(e.Location);
// _LastTouchAction = DateTime.Now;
//}
void TouchHandlerTouchUp(object sender, TouchEventArgs e)
{
if (_TouchEnabled)
{
// Process keybaord hiding through the MouseUp event instead of touch event since if closed using touch the mouse up will occur on control
// which is under keyboard after keyboard is hidden which is not desired
if (IsTopBarVisible && CloseButtonRectangle.Contains(e.Location))
{
_LastTouchAction = null;
return;
}
PerformUpAction(e.Location);
_LastTouchAction = DateTime.Now;
}
}
void TouchHandlerTouchDown(object sender, TouchEventArgs e)
{
if (_TouchEnabled)
{
PerformDownAction(e.Location);
_LastTouchAction = DateTime.Now;
}
}
/// <summary>
/// Returns the Key at given location. Location expected is in control coordinates.
/// </summary>
/// <param name="location">Location is control coordinates.</param>
/// <returns>Key if there is a key at given location or null/nothing if no key exists at given location.</returns>
public Key HitTest(Point location)
{
KeyboardLayout currentLayout = Keyboard.CurrentLayout;
Rectangle rectKeys = KeysRectangle;
float fx = (float)rectKeys.Width / currentLayout.LogicalWidth;
float fy = (float)rectKeys.Height / currentLayout.LogicalHeight;
Key key = null;
if (KeysRectangle.Contains(location))
key = currentLayout.KeyHitTest((int)((location.X - rectKeys.Left) / fx), (int)((location.Y - rectKeys.Top) / fy));
return key;
}
private void PerformDownAction(Point location)
{
if (_PressedKey != null) // This can happen because multi-touch can send multiple down messages
{
PerformUpAction(location);
}
if (IsTopBarVisible && CloseButtonRectangle.Contains(location))
{
_PressedKey = HideKeyboardKey;
Invalidate(CloseButtonRectangle);
}
else
{
KeyboardLayout currentLayout = Keyboard.CurrentLayout;
Rectangle rectKeys = KeysRectangle;
float fx = (float)rectKeys.Width / currentLayout.LogicalWidth;
float fy = (float)rectKeys.Height / currentLayout.LogicalHeight;
Key key = HitTest(location);
if (key != null)
{
TimeSpan ts = DateTime.Now.Subtract(_LastMouseUpEvent);
if (_LastKeyUp != null && key.Caption == _LastKeyUp.Caption && ts < TimeSpan.FromMilliseconds(SystemInformation.DoubleClickTime) && key.ChangeToLayoutEx >= 0)
{
Keyboard.CurrentLayoutIndex = key.ChangeToLayoutEx;
_LastMouseUpEvent = DateTime.MinValue;
Invalidate();
}
else
{
_PressedKey = key;
if (_PressedKey.Info != null && _PressedKey.Info != "")
{
StartRepeatKey(key);
SendKeysToTarget(key.Info);
}
}
Invalidate(GetKeyPhysicalBounds(key, fx, fy)); // Only invalidate area under the currently pressed key.
}
}
this.Update();
}
private int TouchMouseInputFilterDelay = 2000;
private bool ProcessMouseAction
{
get
{
return _LastTouchAction == null ||
(_LastTouchAction != null && DateTime.Now.Subtract(_LastTouchAction.Value).TotalMilliseconds > TouchMouseInputFilterDelay);
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (ProcessMouseAction)
{
PerformDownAction(e.Location);
_LastTouchAction = null;
}
base.OnMouseDown(e);
}
private DateTime _LastMouseUpEvent = DateTime.MinValue;
private Key _LastKeyUp = null;
private void PerformUpAction(Point location)
{
if (IsTopBarVisible && CloseButtonRectangle.Contains(location))
{
if (_PressedKey == HideKeyboardKey)
{
HideKeyboard();
_PressedKey = null;
}
CancelRepeatKey();
}
else
{
Key pressedKey = _PressedKey;
CancelRepeatKey();
if (pressedKey != null)
{
KeyboardLayout currentLayout = Keyboard.CurrentLayout;
Rectangle rectKeys = KeysRectangle;
float fx = (float)rectKeys.Width / currentLayout.LogicalWidth;
float fy = (float)rectKeys.Height / currentLayout.LogicalHeight;
_LastMouseUpEvent = DateTime.Now;
_LastKeyUp = pressedKey;
if (pressedKey.ChangeToLayout >= 0)
{
Keyboard.CurrentLayoutIndex = pressedKey.ChangeToLayout;
Invalidate(); // Ok to invalidate entire control because we need to draw a whole new layout.
}
else if (pressedKey.ChangeToLayout == KeyboardLayout.PreviousLayout)
{
Keyboard.ChangeToPreviousLayout();
Invalidate(); // Ok to invalidate entire control because we need to draw a whole new layout.
}
else
{
Invalidate(GetKeyPhysicalBounds(pressedKey, fx, fy)); // Only invalidate area under the currently pressed key.
}
}
}
this.Update();
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (ProcessMouseAction)
{
PerformUpAction(e.Location);
base.OnMouseUp(e);
}
_LastTouchAction = null;
}
private Rectangle GetKeyPhysicalBounds(Key key, float scaleFactorX, float scaleFactorY)
{
Rectangle r = key.Bounds;
r.X = (int)(r.X * scaleFactorX) + Border;
r.Y = (int)(r.Y * scaleFactorY) + Border + TopBarVisibleHeight;
r.Width = (int)(r.Width * scaleFactorX);
r.Height = (int)(r.Height * scaleFactorY);
return r;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_DefaultColorTable != null)
_DefaultColorTable.Dispose();
if (_RepeatTimer != null)
_RepeatTimer.Dispose();
}
base.Dispose(disposing);
}
/// <summary>
/// Attaches the Keyboard to the specified control. The keyboard will automatically appear when the control receives input focus.
/// </summary>
/// <param name="control">The control to which the Keyboard will be attached.</param>
public void AttachTo(Control control)
{
control.GotFocus += new EventHandler(control_GotFocus);
control.LostFocus += new EventHandler(control_LostFocus);
}
/// <summary>
/// Detaches the Keyboard from the specified control.
/// </summary>
/// <param name="control">The control from which the Keyboard will be detached.</param>
public void DetachFrom(Control control)
{
control.GotFocus -= new EventHandler(control_GotFocus);
control.LostFocus -= new EventHandler(control_LostFocus);
}
void control_GotFocus(object sender, EventArgs e)
{
Show();
}
void control_LostFocus(object sender, EventArgs e)
{
HideKeyboard();
}
private void HideKeyboard()
{
CancelEventArgs ce = new CancelEventArgs();
OnKeyboardClosing(ce);
if (ce.Cancel) return;
this.Hide();
OnKeyboardClosed(EventArgs.Empty);
}
/// <summary>
/// Occurs before the keyboard is closed and allows canceling of keyboard closing.
/// </summary>
[Description("Occurs before the keyboard is closed and allows canceling of keyboard closing.")]
public event CancelEventHandler KeyboardClosing;
/// <summary>
/// Raises KeyboardClosing event.
/// </summary>
/// <param name="e">Provides event arguments.</param>
protected virtual void OnKeyboardClosing(CancelEventArgs e)
{
CancelEventHandler handler = KeyboardClosing;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Occurs after keyboard is closed.
/// </summary>
[Description("Occurs after keyboard is closed.")]
public event EventHandler KeyboardClosed;
/// <summary>
/// Raises KeyboardClosed event.
/// </summary>
/// <param name="e">Provides event arguments.</param>
protected virtual void OnKeyboardClosed(EventArgs e)
{
EventHandler handler = KeyboardClosed;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Occurs before the key pressed on keyboard is sent to target control and allows cancellation of the message
/// </summary>
[Description("Occurs before the key pressed on keyboard is sent to target control and allows cancellation of the message.")]
public event KeyCancelEventHandler SendingKey;
/// <summary>
/// Raises SendingKey event.
/// </summary>
/// <param name="e">Provides event arguments.</param>
protected virtual void OnSendingKey(KeyboardKeyCancelEventArgs e)
{
KeyCancelEventHandler handler = SendingKey;
if (handler != null)
handler(this, e);
}
/// <summary>
/// Occurs after the key is sent to target control.
/// </summary>
[Description("Occurs after the key is sent to target control.")]
public event KeyEventHandler KeySent;
/// <summary>
/// Raises KeySent event.
/// </summary>
/// <param name="e">Provides event arguments.</param>
protected virtual void OnKeySent(KeyboardKeyEventArgs e)
{
KeyEventHandler handler = KeySent;
if (handler != null)
handler(this, e);
}
protected override void OnTextChanged(EventArgs e)
{
this.Invalidate();
base.OnTextChanged(e);
}
}
/// <summary>
/// Defines delegate for key-related events.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void KeyEventHandler(object sender, KeyboardKeyEventArgs e);
/// <summary>
/// Provides data for key related events.
/// </summary>
public class KeyboardKeyEventArgs : EventArgs
{
/// <summary>
/// Gets the key being pressed. The format of this information
/// must confirm to the description for the SendKeys.Send function. For more details, see:
/// http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.aspx
/// </summary>
public readonly string Key;
/// <summary>
/// Initializes a new instance of the KeyboardKeyEventArgs class.
/// </summary>
/// <param name="key"></param>
public KeyboardKeyEventArgs(string key)
{
Key = key;
}
}
/// <summary>
/// Defines delegate for key-related events.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void KeyCancelEventHandler(object sender, KeyboardKeyCancelEventArgs e);
/// <summary>
/// Provides data for key related events.
/// </summary>
public class KeyboardKeyCancelEventArgs : CancelEventArgs
{
/// <summary>
/// Gets the key being pressed. The format of this information
/// must confirm to the description for the SendKeys.Send function. For more details, see:
/// http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.aspx
/// </summary>
public readonly string Key;
/// <summary>
/// Initializes a new instance of the KeyCancelEventArgs class.
/// </summary>
/// <param name="key"></param>
public KeyboardKeyCancelEventArgs(string key)
{
Key = key;
}
}
}