How to Create a Button That Can Send Keys to a Conrol Without Losing Focus - Virtual Keyboard

How to create a Button that can send keys to a conrol without losing focus - Virtual Keyboard

Non-Selectable Button to Send Key like Virtual Keyboard

It's enough to make a non-selectable button and then handle it's click event and send key. This way these buttons can work like virtual keyboard keys and the focus will remain on the control which has focus while it sends the key to the focused control.

public class ExButton:Button
{
public ExButton()
{
SetStyle(ControlStyles.Selectable, false);
}
}

Then handle click event and send key:

private void exButton1_Click(object sender, EventArgs e)
{
SendKeys.SendWait("A");
}

SetStyle extension method for a Control

Is there any way of calling SetStyle on a Control without using
inheritance?

Yes, using Reflection, there is. Create this extension method:

public static class Extensions
{
public static void SetStyle(this Control control, ControlStyles flags, bool value)
{
Type type = control.GetType();
BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
MethodInfo method = type.GetMethod("SetStyle", bindingFlags);
if (method != null)
{
object[] param = { flags, value };
method.Invoke(control, param);
}
}
}

Then use it this way, in your form Load event:

this.button1.SetStyle(ControlStyles.Selectable, false);

Then button1 will be non-selectable without inheritance.

How to `SendKeys` to the last active input TextBox after clicking a button in winforms (C#)?

Call

_recentTextbox.Select();

before you do your sendkeys. There exists another method that will work similarly ( Focus() ) but that is primarily intended for people creating custom controls

If you have many textboxes and you need to know which one recently lost the focus to your button, attach the same Leave (or LostFocus) event handler to all the textboxes:

private void Leave(object sender, EventArgs e){
_recentTextbox = (TextBox)sender; //_recentTextbox is a class wide TextBox variable
}

private void btnA_Click(object sender, EventArgs e)
{
if(_recentTextbox == null)
return;
_recentTextbox.Select();
SendKeys.Send("A");
_recentTextbox = null; //will be set again when a textbox loses focus
}

Use a virtual Keyboard on focused Textboxes and DataGridView Cells

To add text to the current ActiveControl using Buttons, these Button must not steal the focus from the ActiveControl (otherwise they become the ActiveControl).

This way, you can also avoid all those FocusedTextbox = textBox etc. and remove that code.

You just need Buttons that don't have the Selectable attribute set. You can use a Custom Control derived from Button and remove ControlStyles.Selectable in its constructor using the SetStyle method:

Public Class ButtonNoSel
Inherits Button
Public Sub New()
SetStyle(ControlStyles.Selectable, False)
End Sub
End Class

Replace your Buttons with this one (or, well, just set the Style if you're already using Custom Controls).


To replace the existing Buttons with this Custom Control:

  • Add a new class object to your Project, name it ButtonNoSel, copy all the code above inside the new class to replace the two lines of code you find there.
  • Build the Project. You can find the ButtonNoSel Control in your ToolBox now. Replace your Buttons with this one.
  • Or, open up the Form's Designer file and replace (CTRL+H) all System.Windows.Forms.Button() related to the Virtual KeyBoard with ButtonNoSel.
  • Remove the existing event handlers, these are not needed anymore.

Add the same Click event handler in the Constructor of the class that hosts those Buttons (a Form or whatever else you're using).

You can then remove all those event handlers, one for each control, that you have now; only one event handler is needed for all:

Public Sub New()
InitializeComponent()

For Each ctrl As Control In Me.Controls.OfType(Of ButtonNoSel)
AddHandler ctrl.Click, AddressOf KeyBoardButtons_Click
Next
End Sub

Of course, you also don't need to add event handlers to any other control, this is all that's required.

Now, you can filter the Control types you want your keyboard to work on, e.g., TextBoxBase Controls (TextBox and RichTextBox), DataGridView, NumericUpDown etc.

Or filter only special cases that need special treatment (e.g., MonthCalendar).

To add the char corresponding to the Button pressed, you can use SendKeys.Send(): it will insert the new char in the current insertion point, so you don't need any other code to store and reset the caret/cursor position as it happens if you set the Text property of a Control.

In this example, I'm checking whether the ActiveControl is a TextBoxBase Control, then just send the char that the clicked Button holds.

If it's a DataGridView, first send F2 to enter Edit Mode, then send the char.

You could also just send a char (so, no filter would be required), but in this case, you'll replace, not add to, the existing value of that Cell.

Private Sub KeyBoardButtons_Click(sender As Object, e As EventArgs)
Dim selectedButton = DirectCast(sender, Control)
Dim keysToSend = String.Empty

If TypeOf ActiveControl Is TextBoxBase Then
keysToSend = selectedButton.Text
ElseIf TypeOf ActiveControl Is DataGridView Then
Dim ctrl = DirectCast(ActiveControl, DataGridView)
If TypeOf ctrl.CurrentCell IsNot DataGridViewTextBoxCell Then Return
SendKeys.Send("{F2}")
keysToSend = selectedButton.Text
Else
' Whatever else
End If
If Not String.IsNullOrEmpty(keysToSend) Then
SendKeys.Send(keysToSend)
End If
End Sub

► Note that {F2} is sent just once: when the Cell enters Edit Mode, the ActiveControl is a DataGridViewTextBoxEditingControl, hence a TextBox Control, handled by the TextBoxBase filter.

This is how it works (using just the code posted here):

Virtual Keyboard SendKeys

Virtual keyboard input to data grid view

I don't think you can append text to a DataGridView Cell. I would store the current value in a string, then update that variable before setting the text to the cell.

        private void button1_Click(object sender, EventArgs e)
{
//Get the currently selected Cell
string msg = String.Format("Row: {0}, Column: {1}",
DGV.CurrentCell.RowIndex,
DGV.CurrentCell.ColumnIndex);

//Update our New Cell Value Data
newValue = newValue + "4";

//Apply the New Cell Value Data to the Selected Cell
this.DGV.CurrentCell.Value = newValue;
}

Event when form is about to get focus?

Impoerant:, I agree with Jimi's and jmcilhinney's comments and I also
believe it's not the right way of implementing an on-screen keyboard,
but this post it just trying to help you on:

  • Finding handle of the deactivated window when
    this window has been activated
    .

Find handle of the deactivated window when this window has been activated

You can use SetWinEventHook you can to listen to some events from other processes and register a WinEventProc callback method to receive the event when the event raised.

Here we are interested in EVENT_SYSTEM_FOREGROUND. Every time that we receive this event, if the activated window is not our form, we track the window which has been activated, then when our window is activated we look into the value of tracked window which is now the previous window which has lost its focus.

C#

Here is the code that I tried and worked well for me:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyForm : Form
{
public const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
public const uint EVENT_OBJECT_DESTROY = 0x8001;
public const uint WINEVENT_OUTOFCONTEXT = 0;

public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild,
uint dwEventThread, uint dwmsEventTime);

[DllImport("user32.dll")]
public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax,
IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc,
uint idProcess, uint idThread, uint dwFlags);

[DllImport("user32.dll")]
public static extern bool UnhookWinEvent(IntPtr hWinEventHook);

[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();

IntPtr hook = IntPtr.Zero;
protected override void OnLoad(EventArgs e)
{
previous = GetForegroundWindow();
hook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND,
EVENT_SYSTEM_FOREGROUND,
IntPtr.Zero, new WinEventDelegate(WinEventProc),
0, 0, WINEVENT_OUTOFCONTEXT);
base.OnLoad(e);
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
UnhookWinEvent(hook);
base.OnFormClosing(e);
}
IntPtr? previous = null;
void WinEventProc(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread,
uint dwmsEventTime)
{
if (hwnd != this.Handle)
{
previous = hwnd;
}
else
{
if (previous.HasValue)
this.Text = $"Previous window: {(int)previous:X}";
else
this.Text = $"No idea about previous window.";
}
}
}

VB.NET

Imports System.Runtime.InteropServices

Public Class MyForm
Inherits Form

Public Const EVENT_SYSTEM_FOREGROUND As UInteger = &H3
Public Const EVENT_OBJECT_DESTROY As UInteger = &H8001
Public Const WINEVENT_OUTOFCONTEXT As UInteger = 0
Public Delegate Sub WinEventDelegate(ByVal hWinEventHook As IntPtr,
ByVal eventType As UInteger,
ByVal hwnd As IntPtr,
ByVal idObject As Integer,
ByVal idChild As Integer,
ByVal dwEventThread As UInteger,
ByVal dwmsEventTime As UInteger)
<DllImport("user32.dll")>
Public Shared Function SetWinEventHook(ByVal eventMin As UInteger,
ByVal eventMax As UInteger,
ByVal hmodWinEventProc As IntPtr,
ByVal lpfnWinEventProc As WinEventDelegate,
ByVal idProcess As UInteger,
ByVal idThread As UInteger,
ByVal dwFlags As UInteger) As IntPtr
End Function
<DllImport("user32.dll")>
Public Shared Function UnhookWinEvent(ByVal hWinEventHook As IntPtr) As Boolean
End Function
<DllImport("user32.dll")>
Private Shared Function GetForegroundWindow() As IntPtr
End Function
Private hook As IntPtr = IntPtr.Zero
Protected Overrides Sub OnLoad(ByVal e As EventArgs)
previous = GetForegroundWindow()
hook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND,
EVENT_SYSTEM_FOREGROUND, IntPtr.Zero,
New WinEventDelegate(AddressOf WinEventProc),
0, 0, WINEVENT_OUTOFCONTEXT)
MyBase.OnLoad(e)
End Sub

Protected Overrides Sub OnFormClosing(ByVal e As FormClosingEventArgs)
UnhookWinEvent(hook)
MyBase.OnFormClosing(e)
End Sub

Private previous As IntPtr? = Nothing

Private Sub WinEventProc(ByVal hWinEventHook As IntPtr,
ByVal eventType As UInteger,
ByVal hwnd As IntPtr,
ByVal idObject As Integer,
ByVal idChild As Integer,
ByVal dwEventThread As UInteger,
ByVal dwmsEventTime As UInteger)
If hwnd <> Me.Handle Then
previous = hwnd
Else

If previous.HasValue Then
Me.Text = $"Previous window: {CInt(previous):X}"
Else
Me.Text = $"No idea about previous window."
End If
End If
End Sub
End Class

Retain focus on a UI element, but click a button on a different dialog. WPF C#

You could just return focus to the original window asynchronousely:

public static void BackgroundFocus(this UIElement el)    
{
Action a = () => el.Focus();
el.Dispatcher.BeginInvoke(DispatcherPriority.Background, a);
}

But this is not ideal, because the original window caption would flicker when losing focus...
Why don't you use Popup instead?



Related Topics



Leave a reply



Submit