Get selected text from another app into own C# app - c#

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.

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.

From CTRL+"something" to a string with a code sample

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.

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.

How can you programmatically detect if javascript is enabled/disabled in a Windows Desktop Application? (WebBrowser Control)

I have an application which writes HTML to a WebBrowser control in a .NET winforms application.
I want to detect somehow programatically if the Internet Settings have Javascript (or Active Scripting rather) disabled.
I'm guessing you need to use something like WMI to query the IE Security Settings.
EDIT #1: It's important I know if javascript is enabled prior to displaying the HTML so solutions which modify the DOM to display a warning or that use tags are not applicable in my particular case. In my case, if javascript isn't available i'll display content in a native RichTextBox instead and I also want to report whether JS is enabled back to the server application so I can tell the admin who sends alerts that 5% or 75% of users have JS enabled or not.
Thanks to #Kickaha's suggestion. Here's a simple method which checks the registry to see if it's set. Probably some cases where this could throw an exception so be sure to handle them.
const string DWORD_FOR_ACTIVE_SCRIPTING = "1400";
const string VALUE_FOR_DISABLED = "3";
const string VALUE_FOR_ENABLED = "0";
public static bool IsJavascriptEnabled( )
{
bool retVal = true;
//get the registry key for Zone 3(Internet Zone)
Microsoft.Win32.RegistryKey key = Registry.CurrentUser.OpenSubKey(#"Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\3", true);
if (key != null)
{
Object value = key.GetValue(DWORD_FOR_ACTIVE_SCRIPTING, VALUE_FOR_ENABLED);
if( value.ToString().Equals(VALUE_FOR_DISABLED) )
{
retVal = false;
}
}
return retVal;
}
Note: in the interest of keep this code sample short (and because I only cared about the Internet Zone) - this method only checks the internet zone. You can modify the 3 at end of OpenSubKey line to change the zone.
If you are having troubles with popups popping up, i've included a solution for you, and if you want to disable/enable javascript on th client machine (or even just read/query if it is enabled/disabled) ive included that answer for you as well, here we go:
Which popup message do you want to disable? If it's the alert message, try this, obviously resolving the window or frame object to your particular needs, I’ve just assumed top-level document, but if you need an iframe you can access it using window.frames(0). for the first frame and so on... (re the JavaScript part)... here is some code, assuming WB is your webbrowser control...
WB.Document.parentWindow.execScript "window.alert = function () { };", "JScript"
You must run the above code only after the entire page is done loading, i understand this is very difficult to do (and a full-proof version hasn't been published yet) however I have been doing it (full proof) for some time now, and you can gather hints on how to do this accurately if you read some of my previous answers labelled "webbrowser" and "webbrowser-control", but getting back to the question at hand, if you want to cancel the .confirm JavaScript message, just replace window.alert with window.confirm (of course, qualifying your window. object with the correct object to reach the document hierarchy you are working with). You can also disable the .print method with the above technique and the new IE9 .prompt method as well.
If you want to disable JavaScript entirely, you can use the registry to do this, and you must make the registry change before the webbrowser control loads into memory, and every time you change it (on & off) you must reload the webbrowser control out and into memory (or just restart your application).
The registry key is \HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\ - the keyname is 1400 and the value to disable it is 3, and to enable it is 0.
Of course, because there are 5 zones under the Zones key, you need to either change it for the active zone or for all zones to be sure. However, you really don't need to do this if all you want to do is supress js dialog popup messages.
Let me know how you go, and if I can help further.
Here is a suggestion - Encode the warning into your webpage as default. Create a javascript that runs on page load which removes that element. The warning will be there when ever javascript is not allowed to run.
It's a long while since I coded client side HTML javascript to interact with the DOM so I may be a little out of date. i.e. you will need to fix details, but I hope I get the general idea across.
<script>
document.getElemntByID("noJavascriptWarning").innerHTML="";
</script>
and in your HTML body
<DIV id="noJavascriptWarning" name="noJavaScriptWarning">Please enable Javascript</DIV>

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