From CTRL+"something" to a string with a code sample - c#

public virtual void Send(string keysToType, ActionListener actionListener)
{
if (heldKeys.Count > 0) keysToType = keysToType.ToLower();
CapsLockOn = false;
foreach (char c in keysToType)
{
short key = VkKeyScan(c);
if (c.Equals('\r')) continue;
if (ShiftKeyIsNeeded(key)) SendKeyDown((short) KeyboardInput.SpecialKeys.SHIFT, false);
if (CtrlKeyIsNeeded(key)) SendKeyDown((short) KeyboardInput.SpecialKeys.CONTROL, false);
if (AltKeyIsNeeded(key)) SendKeyDown((short) KeyboardInput.SpecialKeys.ALT, false);
Press(key, false);
if (ShiftKeyIsNeeded(key)) SendKeyUp((short) KeyboardInput.SpecialKeys.SHIFT, false);
if (CtrlKeyIsNeeded(key)) SendKeyUp((short) KeyboardInput.SpecialKeys.CONTROL, false);
if (AltKeyIsNeeded(key)) SendKeyUp((short) KeyboardInput.SpecialKeys.ALT, false);
}
actionListener.ActionPerformed(Action.WindowMessage);
}
I need to send a list of keyboard shortcuts to that method
CTRL + A, CTRL + End, etc.
But I don't know how to build such a string.
This far I wrote this:
string shortcuts;
// shortcuts = "\CTRL + A" + "\CTRL + End";
Send(shortcuts, myactionlistener)

Perhaps a 'normal' string is not the best format to do this?
If the string contains 'characters that need to be sent', perhaps a list of KeyEventArgs or some custom built data class could be sent to your Send method. Then you can loop over the list and execute one by one. If you need combinations (like CTRL+K + CTRL+K (=toggle bookmark in Visual Studio)) you might need a composite.
Another option is to create your own DSL.

Too late for Serge but for all the googlers out there...
Serge is referring to the Keyboard class in TestStack.White. The Send function can only be used to send strings literally to another window. You cannot specify control keys here. They are just needed internally sometimes. E.g. Send("{") on my keyboard layout would internally translate to "AltKeyIsNeeded" and "7".
You can send keyboard shortcuts CTRL+A, CTRL+End like this:
myWindow.Keyboard.HoldKey(KeyboardInput.SpecialKeys.CONTROL);
myWindow.Keyboard.Enter("A");
myWindow.Keyboard.LeaveKey(KeyboardInput.SpecialKeys.CONTROL);
myWindow.Keyboard.HoldKey(KeyboardInput.SpecialKeys.CONTROL);
myWindow.Keyboard.HoldKey(KeyboardInput.SpecialKeys.END);
myWindow.Keyboard.LeaveKey(KeyboardInput.SpecialKeys.END);
myWindow.Keyboard.LeaveKey(KeyboardInput.SpecialKeys.CONTROL);

I've done something similar in an application, with the Windows key' shortcuts. I'm used a WH_KEYBOARD_LL hook and when I've got a specific shortcut I call the method. Maybe this could help you.

In my experience of UI automation there were some cases where solution of #m3tikn0b didn't work for me, particularly ALT+DOWN and ALT+UP, not sure why. To make them work I had to use WinForm's SendKeys, for example for ALT+DOWN it is:
SendKeys.SendWait("%({DOWN})");
It has pretty rich syntax described in docs for Send method. However it was not suitable in all my cases as well, so I had to use TestStack.White for the most cases and SendKeys for some rare exclusions.

Related

c# customizing controls on a save dialog -- how to disable parent folder button?

I am working from the sample project here: http://www.codeproject.com/Articles/8086/Extending-the-save-file-dialog-class-in-NET
I have hidden the address/location bar at the top and made other modifications but I can't for the life of me manage to disable the button that lets you go up to the parent folder. Ist is in the ToolbarWindow32 class which is the problem. This is what I have at the moment but it is not working:
int parentFolderWindow = GetDlgItem(parent, 0x440);
//Doesn't work
//ShowWindow((IntPtr)parentFolderWindow, SW_HIDE);
//40961 gathered from Spy++ watching messages when clicking on the control
// doesn't work
//SendMessage(parentFolderWindow, TB_ENABLEBUTTON, 40961, 0);
// doesn't work
//SendMessage(parentFolderWindow, TB_SETSTATE, 40961, 0);
//Comes back as '{static}', am I working with the wrong control maybe?
GetClassName((IntPtr)parentFolderWindow, lpClassName, (int)nLength);
Alternatively, if they do use the parent folder button and go where I don't want them to, I'm able to look at the new directory they land in, is there a way I can force the navigation to go back?
Edit: Added screenshot
//Comes back as '{static}', am I working with the wrong control maybe?
You know you are using the wrong control, you expected to see "ToolbarWindow32" back. A very significant problem, a common one for Codeproject.com code, is that this code cannot work anymore as posted. Windows has changed too much since 2004. Vista was the first version since then that added a completely new set of shell dialogs, they are based on IFileDialog. Much improved over its predecessor, in particular customizing the dialog is a lot cleaner through the IFileDialogCustomize interface. Not actually what you want to do, and customizations do not include tinkering with the navigation bar.
The IFileDialogEvents interface delivers events, the one you are looking for is the OnFolderChanging event. Designed to stop the user from navigating away from the current folder, the thing you really want to do.
While this looks good on paper, I should caution you about actually trying to use these interfaces. A common problem with anything related to the Windows shell is that they only made it easy to use from C++. The COM interfaces are the "unfriendly" kind, interfaces based on IUnknown without a type library you can use the easily add a reference to your C# or VB.NET project. Microsoft published the "Vista bridge" to make these interfaces usable from C# as well, it looks like this. Yes, yuck. Double yuck when you discover you have to do this twice, this only works on later Windows versions and there's a strong hint that you are trying to do this on XP (judging from the control ID you found).
This is simply not something you want to have to support. Since the alternative is so simple, use the supported .NET FileOk event instead. A Winforms example:
private void SaveButton_Click(object sender, EventArgs e) {
string requiredDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
using (var dlg = new SaveFileDialog()) {
dlg.InitialDirectory = requiredDir;
dlg.FileOk += (s, cea) => {
string selectedDir = System.IO.Path.GetDirectoryName(dlg.FileName);
if (string.Compare(requiredDir, selectedDir, StringComparison.OrdinalIgnoreCase) != 0) {
string msg = string.Format("Sorry, you cannot save to this directory.\r\nPlease select '{0}' instead", requiredDir);
MessageBox.Show(msg, "Invalid folder selection");
cea.Cancel = true;
}
};
if (dlg.ShowDialog() == DialogResult.OK) {
// etc...
}
}
}
I don't this is going to work. Even if you disable the button they can type ..\ and click save and it will take them up one level. You can't exactly disable the file name text box and maintain the functionality of the dialog.
You'd be better off either using the FolderBrowserDialog and setting it's RootFolder property and asking the user to type the filename in or auto generating it.
If the folder you are wanting to restrict the users to isn't an Environment.SpecialFolder Then you'll need to do some work to make the call to SHBrowseForFolder Manually using ILCreateFromPath to get a PIDLIST_ABSOLUTE for your path to pass to the BROWSEINFO.pidlRoot
You can reflect FolderBrowserDialog.RunDialog to see how to make that call.
Since you want such custom behaviors instead of developing low level code (that is likely yo break in the next versions of windows) you can try to develop your file picker form.
Basically it is a simple treeview + list view. Microsoft has a walk-through .
It will take you half a day but once you have your custom form you can define all behaviors you need without tricks and limits.

Get selected text from another app into own C# app

I've a C# app that responds correctly to a global hotkey. I want to get the selected text from another app which has the focus if the global hotkey is pressed.
I tested native Win32 Api, then SendKeys (CTRL + C, Clipboard,...) and now Microsoft UI Automation! The problem is, this only works in Notepad, but not in Internet Explorer or Word or other apps.
I think there must be a better solution than the code I have. I read that sending CTRL + C should work fine, but that only works in Notepad too.
Here's the method I call when the global hotkey is fired:
public String GetSelectedTextFromApp()
{
String output = "";
AutomationElement focused = AutomationElement.FocusedElement;
object pattern;
TextPatternRange[] trs;
if (focused.TryGetCurrentPattern(TextPattern.Pattern, out pattern))
{
TextPattern tp = (TextPattern)pattern;
trs = tp.GetSelection();
output = trs[0].GetText(-1);
}
return output;
}
The control you're trying to automate might not be implementing TextPattern. It might only implement ValuePattern. Also, it's possible that the control you're focused on isn't the control which actually contains the text, but rather a wrapping control under which you'll find an AutomationElement implementing the above mentioned patterns. Another possibility is, like Andrii said, is that the control doesn't support UIA at all (though I find it hard to believe in case of MS Word).
In such cases, it's worth working with UISpy.exe which will help you see the visual tree of the application you're trying to automate. You'll also be able to see the supported Patterns for each AutomationElement. You can also call GetSupportedPatterns() on every AutomationElement, to see which Patterns are currently supported.

An error occurred executing the click atom

Using C# and Selenium, I am building an automated script where I, amongst other things, try to select a certain value from a droplist (value being specified in a .csv-file). I get the error;
"An error occurred executing the click atom (WARNING: The server did not provide any stacktrace information)"
I have no idea what a click atom is, much less how to fix it... Any help is appreciated greatly!
thanks in advance
this is the code for the droplist:
public bool isellHOSelectAdultsDroplist(string adults)
{
writeToLog(String.Format("Selecting adults from drop list"), this.GetType().Name);
String xpathString = HO_ADULT_SELECTION;
if(GpoExplicitWaitXpathElement(xpathString, 3, 5))
{
IWebElement dropListObjects = webDriver.FindElement(By.XPath(xpathString));
writeToLog(String.Format("DEBUG: Trying to click on appropriate number of adults..."), this.GetType().Name);
selectValueFromAdultDropList(dropListObjects, adults);
return true;
}
else
{
return false;
}
}
//...and this is my select-method
private void selectValueFromAdultDropList(IWebElement dropListObjects, string adults)
{
SelectElement manipulateDroplistObject = new SelectElement(dropListObjects);
manipulateDroplistObject.SelectByValue(adults);
String selection = manipulateDroplistObject.SelectedOption.Text;
int numberOfElements = manipulateDroplistObject.Options.Count;
writeToLog("Number of elements in Adult Droplist: " + numberOfElements, this.GetType().Name);
writeToLog("Selection from adult droplist: " + selection, this.GetType().Name);
}
I'll answer the specific question you asked, which is, "What is a click atom?" There is quite a bit of functionality in the IE driver, and the implementation of this functionality rests on three pillars.
First is IE's COM interfaces. These are the objects and methods that have been used to automate various parts of IE for more than a decade.
The second technology is so-called "native events." That is, using OS-level mechanisms to perform user interactions, like key presses and mouse clicks. On Windows, that means using the Windows SendMessage API. Almost anytime you're using the keyboard or the mouse with the IE driver, you're using native events by default.
Finally, a good portion of the IE driver functionality is implemented using JavaScript functions, which are shared by all of the browsers. These functions are known as "automation atoms".
One of the very few exceptions to using native events for mouse operations is in selecting an <option> element from a <select> element. Since IE doesn't give discoverable dimensions to <option> elements, the IE driver is forced to simulate the click action via JavaScript. This means using the automation atom for the click action. In your case, something must've gone wrong executing that JavaScript, which was faithfully reported as a "failure to execute the click atom." Without more detail, including sample HTML pages to reproduce the issue, it will be exceedingly difficult to diagnose the root cause of the issue.
It's at this point I will echo the call to update to the latest IE driver. Some of the code in this area has been overhauled, and at the least, it should be possible to extract more precise errors from failure cases with a more recent driver.

Drag & drop of a dynamically created shortcut

I have a C# application that creates shortcuts to launch other programs with specific arguments and initial directories. I would like the user to be able to drag a shortcut from the Windows form and drop it anywhere relevant like the desktop, the start menu, and so on but I don't really know how to handle that, could anyone point me in the right direction?
I have seen a few samples using PInvoke and IShellLink like this one, or read answers on SO like here, which already help create shortcuts and save them in a .lnk file. I assume I have to hand over data in a DoDragDrop() call when the user initiates a drag operation, for example by handling a MouseDown signal. That's as far as I got, I suppose I need to know exactly which type the target is expecting to accept the drop, and how to serialize the shortcut, but couldn't find any information on that part.
Perhaps another option would be to get the location of the drop, and manage that from my application, but there again I'm a bit clueless as how to do that.
The framework version is currently 3.5, and I'm only considering Windows platforms.
Thanks in advance for your help!
Update/Solution:
Using the ShellLink code mentioned above to create a temporary shortcut file, I simply used DataObject for the drag and drop, like in the following example:
private void picShortcut_MouseDown(object sender, MouseEventArgs e)
{
ShellLink link = new ShellLink();
// Creates the shortcut:
link.Target = txtTarget.Text;
link.Arguments = txtArguments.Text;
link.Description = txtDescription.Text;
link.IconPath = txtIconFile.Text;
link.IconIndex = (txtIconIndex.Text.Length > 0 ?
System.Int32.Parse(txtIconIndex.Text) : 0);
link.Save("tmp.lnk");
// Starts the drag-and-drop operation:
DataObject shortcut = new DataObject();
StringCollection files = new StringCollection();
files.Add(Path.GetFullPath("tmp.lnk"));
shortcut.SetFileDropList(files);
picShortcut.DoDragDrop(shortcut, DragDropEffects.Copy);
}
Quite complicated if you consider the PInvoke code (not shown here), and I still need to create this temporary file with the target name. If anyone knows a... erm, shortcut, it's welcome! Perhaps by porting the code for which John Knoeller gave a link (thanks!).
Raymond Chen did a whole article on this very topic on his blog check out dragging a virtual file
I answered a question sort of similar to this on a previous thread. This might be a starting point for you.
Drag and Drop link

In C#, is there a way to consistently be able to get the selected text contents of currently focused window?

In my c# .Net application, I've been trying to be able to retrieve the currently selected text in the currently focused window. (Note that it can be any window open in windows, such as word, or safari).
I'm able to retrieve the handle to the currently focused control. (Using a couple of interop calls to user32.dll, and kernel32.dll).
However, I've been unable to consistently be able to get back the selected text.
I've tried using SENDMESSAGE and GET_TEXT. However this only seems to work for some applications (works for simple applications like wordpad, doesn't work for more complex applications like firefox, or word).
I've tried using SENDMESSAGE and WM_COPY. However, again this only seems to work on some controls. (I would think that WM_COPY, would cause the exact same behaviour as manually pressing CTRL-C, but it doesn't).
I've tried using SENDMESSAGE and WM_KEYUP+WM_KEYDOWN to manually stimulate a copy command. BUt this doesn't constantly work either. (Perhaps of an overlap with the actual hotkey pressed by a user to invoke my applications).
Any ideas on consistently being able to retrieve the currently selected text ? (on any application).
I got this working by a combination of a couple of things. So:
Wait for whatever modifiers are currently held down to be released.
Send control+c (using this answer Trigger OS to copy (ctrl+c or Ctrl-x) programmatically)
bool stillHeld = true;
int timeSlept = 0;
do
{
// wait until our hotkey is released
if ((Keyboard.Modifiers & ModifierKeys.Control) > 0 ||
(Keyboard.Modifiers & ModifierKeys.Alt) > 0 ||
(Keyboard.Modifiers & ModifierKeys.Shift) > 0)
{
timeSlept += 50;
System.Threading.Thread.Sleep(timeSlept);
}
else
{
stillHeld = false;
}
} while (stillHeld && timeSlept < 1000);
Keyboard.SimulateKeyStroke('c', ctrl: true);
I'm using WPF so Keyboard.Modifiers is System.Windows.Input.Keyboard, whereas Keyboard.SimulateKeyStroke is from Chris Schmick's answer.
Note, timeSlept is my max time to wait for the user to let go of the key before continuing on its merry way.
I managed to get text for wordpad/notepad and anything that supports UI automation.
The code below may work for you in some cases. I'm going to get a start on using Reflector to see how windows does it for textboxes in the TextBoxBase.SelectedText property.
public static string SelectedText
{
get
{
AutomationElement focusedElement = AutomationElement.FocusedElement;
object currentPattern = null;
if (focusedElement.TryGetCurrentPattern(TextPattern.Pattern, out currentPattern))
{
TextPattern textPattern = (TextPattern)currentPattern;
TextPatternRange[] textPatternRanges = textPattern.GetSelection();
if (textPatternRanges.Length > 0)
{
string textSelection = textPatternRanges[0].GetText(-1);
return textSelection;
}
}
return string.Empty;
}
set
{
AutomationElement focusedElement = AutomationElement.FocusedElement;
IntPtr windowHandle = new IntPtr(focusedElement.Current.NativeWindowHandle);
NativeMethods.SendMessage(windowHandle, NativeMethods.EM_REPLACESEL, true, value);
}
}
I don't believe that it is possible, the currently focused may not contain any selected text. (It may not even contain any text at all). Or the current selection could be an icon, or an image.
Perhaps requiring the user to copy the selected text to the clipboard first may be a solution.
I've possibly misunderstood the question, but could you just send Ctrl+c? If you know the window is always foremost and the text to be copied is selected?
SendKeys.SendWait("^c");
Once copied to the clipboard, it's not tricky to programatically retrieve the contents (you could even check it's actually text at that point).
I think creating clipboard monitor is a good option. I suggest you to look at Klipper (KDE clipboard module), it copied everything you select in the clipboard list, either content is file, folder or some text.
More details can be found here.
Hm... you find it to be easy? How come?
The best alternative to SendKeys.SendWait("^c"); I found was this one:
http://www.c-sharpcorner.com/Forums/ShowMessages.aspx?ThreadID=46203
However, it only works for a few apps, like notepad. For web browsers, it just crashes.
Anyone got anything better?
Try the GetWindowText() API on controls for which the other methods do not work.

Categories

Resources