Win forms, log all clicks? - winforms

Is there a way to log all of the clicks in a Win Forms application? I'd like to intercept clicks and record the action and the name of the control that caused it.
Is this possible?
Thanks in advance.
UPDATE: I'm looking for an application wide solution, is there no way to add a listener to the windows event queue (or what ever it is called)?

You can do this by having your app's main form implement the IMessageFilter interface. You can screen the Window messages it gets and look for clicks. For example:
public partial class Form1 : Form, IMessageFilter {
public Form1() {
InitializeComponent();
Application.AddMessageFilter(this);
this.FormClosed += (o, e) => Application.RemoveMessageFilter(this);
}
public bool PreFilterMessage(ref Message m) {
if (m.Msg == 0x201 || m.Msg == 0x203) { // Trap left click + double-click
string name = "Unknown";
Control ctl = Control.FromHandle(m.HWnd);
if (ctl != null) name = ctl.Name;
Point pos = new Point(m.LParam.ToInt32());
Console.WriteLine("Click {0} at {1}", name, pos);
}
return false;
}
}
Note that this logs all clicks in any window of your app.

You could use Spy++ or WinSpy++ to achieve this.
alt text http://www.catch22.net/sites/default/files/images/winspy1.img_assist_custom.jpg
But I'm not sure how you can achieve the same thing yourself. If it's possible you'd need to do it via a low-level Windows API hook or a message handler that gives you access to all the message in your applications queue.

Well, you could subscribe to the Click or MouseDown event of every control on the form.

use MouseEventArgs like this:
private void Form_MouseDown(object sender, System.WinForms.MouseEventArgs e)
{
switch (e.Button)
{
case MouseButtons.Left:
MessageBox.Show(this,"Left Button Click");
break;
case MouseButtons.Right:
MessageBox.Show(this,"Right Button Click" );
break;
case MouseButtons.Middle:
break;
default:
break;
}
EventLog.WriteEntry("source", e.X.ToString() + " " + e.Y.ToString()); //or your own Log function
}

The NunitForms test project has a recorder application that watches for this and many other events. The code is very clever and worth a good look. It's a ThoughtWorks project.
That's the rolls Royce solution though!...
Try recursively walking the Controls collection of the form and subscibe to the event based on the type.
PK :-)

Related

Can WebView2 currently receive Keyboard inputs

I am trying to create a software in WPF which hosts a browser (WebView2 currently 1.0.818.41) and also show a OnScreenKeyboard when there is a input field focused in the browser.
I have done this kind of stuff with CefSharp in WPF before but I cannot do it with WebView2 currently. My problem is I do not find a way to send keystrokes from the OnScreenKeyboard (or from the WPF Window) to the Browser.
In CefSharp there we have a function called ChromiumWebBrowser.GetHost().SendKeyEvent() but I cannot find something similar in WebView2.
Am I blind or is this something which is currently not implemented (or maybe not planed)?
Thank you in advance!
There is no direct way. What can be done is execute some JS, which in turn posts a message to WebView. This message can then be caught back in wv2_WebMessageReceived event.
There is extensive documentation on the interop between.NET and JS and interop between JS and .NET WPF Forms here.
A solution would be to inject a sendMessage JS function in the NavigationStarting event:
private void wv2_NavigationStarting(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args){
var sc = "function sendMessage(txt) { window.chrome.webview.postMessage(txt); }";
wv2.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(sc);
}
Now you collect input fields and add onfocus and onblur events to these input fields for example in the NavigationCompleted event like this:
private void wv2_NavigationCompleted(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs args){
string script = "const collection ="+
"document.getElementsByTagName(\"input\");" +
"for (let i = 0; i < collection.length; i++){" +
"collection[i].onfocus= ()=>{ sendMessage('onFocus('+collection[i].name')'); }; " +
"collection[i].onblur= (ev)=>{ sendMessage('onBlur('+collection[i].name')'); };"+
"}";
sender.ExecuteScriptAsync(script);
}
Now catch the message in the wv2_WebMessageReceived event:
private void wv2_WebMessageReceived(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2WebMessageReceivedEventArgs args)
{
var postMess = args.TryGetWebMessageAsString();
if (postMess == "onFocus(nameOfField)" )
{
// here activate the button(keyboard)
// store the Name on focusField variable
}
if (postMess == "onBlur" && paneShown)
{
// here deactivate the button(keyboard)
// release the focusField
}
}
Now you can send a click event to the input fields:
private void btn_Clicked(Object sender, EventArgs args)
{
var script = "var field "+
"= document.getElementsByName("+focusField+");" +
" field.value+=field.value"+args.keyValue();
wv2.CoreWebView2.ExecuteScriptAsync(script);
}
wv2 is an instance of WebView2 and the code is typed directly here and not compiled. Hope you get the idea.

Integration Testing of WPF GUI: How do I identify that current work was finished

I want to run integration UI tests on my WPF application, and I'm not sure how to detect when the current test has finished so that I can proceed to the next one.
Simplifying, suppose I have a button on my window. When the button is clicked I disable it, I modify the model, and I re-enable the button. Once it detects that the model has changed, WPF changes the view on the screen.
Now I want to run a test that simulates clicking the button again and again. To click the button I’ll use automation, as described in this SO question. But how do I know when the work is finished and the display updated, so as to "click" the button again? Do I hook the botton’s IsEnabledChanged, or is there some global indication that the current cycle of processing has finished?
Edit: What was missing in my description is that I want the user to see the interim results on the screen. For example, if the test has 10 phases I want the user to see something like a Step Counter label with values 1 .. 10 appearing on the screen, and not just the number changing immediately from 1 to 10. See my answer below.
how do I know when the work is finished and the display updated, so as to "click" the button again?
According to your description, you said When the button is clicked I disable it, I modify the model, and I re-enable the button.
Therefore, I can only assume that when the model has changed, the Button will be re-enabled. So you could either attach a handler to the model's NotifyPropertyChanged event, or as you suggested, add a handler for the IsEnabledChanged event.
Here is how I managed to get it working. This might be trivial - I'm a novice with GUI. I'm just posting it here in the hope it'll help other novices like me :)
Anyhow, what I used is a two button solutions: Test and Step. Test starts the testing sequence, Step runs each step of the tests. The Step buttons interact with an Integration Tester By Steps helper.
The helper receives an Init with the Number Of Commands as parameter, (currently the helper generates random commands by itself, so it just needs to know how many commands to generate). The helpe provides a Step method to execute the next command, and a Needs More Steps property to indicate whether testing should continue.
The helper derives form INotifyPropertyChanged and has a Counter dependency property that is displayed on the main window.
The states of the Test and Step buttons are controlled by three helper methods: SetButtonsFor_OutsideTesting, SetButtonsFor_InsideTestingOutsideAnyStep and SetButtonsFor_InsideTestingInsideAStep.
First, I verified that everything is working manually, and then I added a timer and automated the process using the Stack Overflow suggestions on how to programmatically click a button in WPF and how to make a WPF Timer Like C# Timer.
Now, here's the Main Window's code:
private void Test_Click(object sender, RoutedEventArgs e)
{
SetButtonsFor_InsideTestingOutsideAnyStep();
RunTheTestBySteps();
}
public readonly IntegrationTesterBySteps _integrationTesterBySteps =
new IntegrationTesterBySteps();
void RunTheTestBySteps()
{
SetButtonsFor_InsideTestingOutsideAnyStep();
IntegrationTesterBySteps.Init(10);
StartTheTimer();
}
private void StartTheTimer()
{
DispatcherTimer = new DispatcherTimer();
DispatcherTimer.Tick += DispatcherTimer_Tick;
DispatcherTimer.Interval = new TimeSpan(0, 0, 1);
DispatcherTimer.Start();
}
private void StopTheTimer()
{
DispatcherTimer.Stop();
DispatcherTimer.Tick -= DispatcherTimer_Tick;
}
private DispatcherTimer DispatcherTimer { get; set; }
private void DispatcherTimer_Tick(object sender, EventArgs e)
{
if (!BtnStep.IsEnabled) return;
ClickTheStepButton();
CommandManager.InvalidateRequerySuggested();
}
private void BtnStep_Click(object sender, RoutedEventArgs e)
{
SetButtonsFor_InsideTestingInsideAStep();
IntegrationTesterBySteps.Step();
if (this.IntegrationTesterBySteps.NeedsMoreSteps)
SetButtonsFor_InsideTestingOutsideAnyStep();
else
{
SetButtonsFor_OutsideTesting();
StopTheTimer();
}
}
private void ClickTheStepButton()
{
var peer = new ButtonAutomationPeer(BtnStep);
var invokeProv = peer.GetPattern(PatternInterface.Invoke)
as IInvokeProvider;
if (invokeProv != null)
invokeProv.Invoke();
}
void SetButtonsFor_InsideTestingInsideAStep()
{
BtnTest.IsEnabled = false;
BtnStep.IsEnabled = false;
}
void SetButtonsFor_InsideTestingOutsideAnyStep()
{
BtnTest.IsEnabled = false;
BtnStep.IsEnabled = true;
}
void SetButtonsFor_OutsideTesting()
{
BtnTest.IsEnabled = true;
BtnStep.IsEnabled = false;
}

Raise event when string changed

I need to create some interface to read data from iButton. Actually it work's like that: user applies little pendant with magnet to iButton device -> log in him. User take away pendant - logout.
My problem is that, I know how to relog, by simple:
public static void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (sp.ReadLine() == "logout")
{
logoutUser();
}
else
{
//put data into nonstatic textbox from another window
// or raise some event which will listen to changes in data and put string into textbox
}
}
But how can I raise event when data != "logout" (for example A0019293881, as pin code).
You can create your own event, an lauch then in the else clause, then create some method to hear the event an do what you want.
More about events here

C# checkedlistbox differentiate between ItemCheck via code and mouse click

I'm using C# VS2008, WinForm application
I have a checkedlistbox control on my form (win-form application)
In the code I check some items in checkedlistbox using the SetItemChecked(index, false) method and it raise the event ItemCheck.
I also allow the user to check items in that checkedlistbox and it also raise the event ItemCheck when the user check or uncheck an item.
How can I find in the ItemCheck event how this event occur (via code or via user keyboard/mouse input)?
Thanks.
I think that there is no a simple way to differentiate the situation using code.
The only thing that comes to mind is through the use of a global form variable:
public class Form1:Form
{
bool _isCodeClick = false;
.....
// Somewhere in your code
_isCodeClick = true;
checkedListBox1.SetItemChecked(index, true);
_isCodeClick = false;
.....
private void CheckedListBox1_ItemCheck(Object sender, ItemCheckEventArgs e)
{
if(_isCodeClick == true)
{
// Do processing for click by code
}
else
{
// Do processing for click by user
}
}
}
If you go for this solution remember to take additional steps to correctly trap exceptions that could bypass the reset of the global variable to the false state.
Probably using advanced manipulation of keyboard and mouse events you could reach a reasonable way to identify what has caused the ItemCheck event, but sometime some solutions are too complex and not worth it.
EDIT: Reviewing my answer I feel the need to add a little change to reduce the maintaining problems that this esponse implies.
The code that set the boolean variable and call the SetItemChecked should be encapsulated in a separate function like this
private void SetItemCheckedFromCode(int index, bool toSet)
{
try
{
_isCodeClick = true;
checkedListBox1.SetItemChecked(index, true);
}
finally
{
_isCodeClick = false;
}
}

How can I force my busy indicator to display? (WPF)

I've created a busy indicator - basically an animation of a logo spinning. I've added it to a login window and bound the Visibility property to my viewmodel's BusyIndicatorVisibility property.
When I click login, I want the spinner to appear whilst the login happens (it calls a web service to determine whether the login credentials are correct). However, when I set the visibility to visible, then continue with the login, the spinner doesn't appear until the login is complete. In Winforms old fashioned coding I would have added an Application.DoEvents. How can I make the spinner appear in WPF in an MVVM application?
The code is:
private bool Login()
{
BusyIndicatorVisibility = Visibility.Visible;
var result = false;
var status = GetConnectionGenerator().Connect(_model);
if (status == ConnectionStatus.Successful)
{
result = true;
}
else if (status == ConnectionStatus.LoginFailure)
{
ShowError("Login Failed");
Password = "";
}
else
{
ShowError("Unknown User");
}
BusyIndicatorVisibility = Visibility.Collapsed;
return result;
}
You have to make your login async. You can use the BackgroundWorker to do this. Something like:
BusyIndicatorVisibility = Visibility.Visible;
// Disable here also your UI to not allow the user to do things that are not allowed during login-validation
BackgroundWorker bgWorker = new BackgroundWorker() ;
bgWorker.DoWork += (s, e) => {
e.Result=Login(); // Do the login. As an example, I return the login-validation-result over e.Result.
};
bgWorker.RunWorkerCompleted += (s, e) => {
BusyIndicatorVisibility = Visibility.Collapsed;
// Enable here the UI
// You can get the login-result via the e.Result. Make sure to check also the e.Error for errors that happended during the login-operation
};
bgWorker.RunWorkerAsync();
Only for completness: There is the possibility to give the UI the time to refresh before the login takes place. This is done over the dispatcher. However this is a hack and IMO never should be used. But if you're interested in this, you can search StackOverflow for wpf doevents.
You can try to run busy indicar in a separate thread as this article explains: Creating a Busy Indicator in a separate thread in WPF
Or try running the new BusyIndicator from the Extended WPF Toolkit
But I'm pretty sure that you will be out of luck if you don't place the logic in the background thread.
Does your login code run on the UI thread? That might block databinding updates.

Resources