I would like to know if there was any way to lock onto a Flash window and post a message to it? Another person here had the answer to it, his name is Spencer K. His question was:
Sending simulated click via WebBrowser in C# to flash object embedded in HTML
Unfortunately, Mr. K wasn't very specific, and all he left behind for people reading his question was that he "got the handle and then iterated through the handles." I'm not extremely sure what he meant by that. I iterated through all visible handles using EnumWindows to no avail, as that did not return a window that was a flash window.
I hope somebody here could tell me, as it's been driving me mad for the past few days.
EDIT: I've just settled on inserting an SWF Object into my form and posting messages to the handle of that.
Actually flash window has its own handle too. To get it you have to get the class names of the controls it is embedded in from Spy++, then you can reach it like this:
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle);
public IntPtr Flash()
{
IntPtr pControl;
pControl = FindWindowEx(webBrowser1.Handle, IntPtr.Zero, "Shell Embedding", IntPtr.Zero);
pControl = FindWindowEx(pControl, IntPtr.Zero, "Shell DocObject View", IntPtr.Zero);
pControl = FindWindowEx(pControl, IntPtr.Zero, "Internet Explorer_Server", IntPtr.Zero);
pControl = FindWindowEx(pControl, IntPtr.Zero, "MacromediaFlashPlayerActiveX", IntPtr.Zero);
return pControl;
}
When you get the handle, you can post the clicks:
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
public enum WMessages : int
{
WM_LBUTTONDOWN = 0x201,
WM_LBUTTONUP = 0x202
}
private int MAKELPARAM(int p, int p_2)
{
return ((p_2 << 16) | (p & 0xFFFF));
}
public void DoMouseLeftClick(IntPtr handle, Point x)
{
PostMessage(handle, (uint)WMessages.WM_LBUTTONDOWN, 0, MAKELPARAM(x.X, x.Y));
PostMessage(handle, (uint)WMessages.WM_LBUTTONUP, 0, MAKELPARAM(x.X, x.Y));
}
The points will be relative to the client, so when you save them, you should save it like this:
List<Point> plist = new List<Point>();
private void webBrowser1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
switch (e.KeyCode)
{
case Keys.C:
plist.Add(webBrowser1.PointToClient(Cursor.Position));
break;
default:
break;
}
}
Hope this was helpful
You can do it via javascript.
Import this:
import flash.external.ExternalInterface;
Ad this to your AS code:
if (ExternalInterface.available) {
// add external interface
ExternalInterface.addCallback("jsFunction", asFunction);
}
public static function asFunction(message:String):void {
}
On your JS object of the flash object you can call this function:
jsObject.jsFunction("message");
This is the function to get the js object of the flash object:
var InternetExplorer = navigator.appName.indexOf("Microsoft") != -1;
jsObject = InternetExplorer ? window.jsObjectName: window.document.jsObjectName;
I did not test this code, I just copied it out of a project.
edit: added js function to get js object
The Flash window will probably not have its own handle, since it is embedded in a webpage. You would want to post the message to the web browser window (that's what Mr. K did).
If that doesn't work for you, the only other option that I'm aware of is to gain control of the browser via WebDriver or WatiN and interact with the flash object using javascript.
for calling a function in flash object u can use this code
swfobject.CallFunction(
"<invoke name=\"yourfunction\" returntype=\"xml\">" +
" <arguments><number>" yourvalue "</number></arguments> </invoke> ");
for more information follow this link:communicate-betwen-c-and-an-embeded-flash-application
i try it for a flash object in my form application and it work,but i did not use it for webbrowser
Related
I am implementing a plugin for a product called AmiBroker in C#.
AmiBroker is a trading software it has exposed a few functions which can be used by 3rd party vendors to pass the stock data to solution. So, we can create a plugin in C# which can be recognized by AmiBroker.
In my scenario I am getting a handler of Main Window of AmiBroker [Note : AmiBroker is fully written in C++] In C# we can retrieve the handler of Main Window, so using this handle can I read the data of the window e.g. Child windows, Panels showing stock lists or things which are visible to the User, and if so, how would I go about doing this?
You can, but it's messy. I literally just worked on something very similar. Pinvoke.net is great for this stuff, but I'll show you some examples of how I'd find controls. If AmiBroker has any documentation for control names or AccessibleNames or anything that allows you to find the exact controls you're looking for, that'd be killer. Because if they're ambiguously named, you're gonna have a helluva time finding the ones you're specifically looking for. But basically, what you'll want to do is EnumChildWindows on the handle you have, iterate through them and look for a unique property to allow you to find the control you want. Then you'll need to execute a specific SendMessage to get the text off of a control (GetWindowText or whatever it's called only works for labels). Code as follows, adapted or swiped from Pinvoke.net at some point (great starting point):
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
public static extern uint GetClassName(IntPtr handle, StringBuilder name, int maxLength);
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
private static List<IntPtr> GetChildWindows(IntPtr parent)
{
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
return result;
}
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
GCHandle gch = GCHandle.FromIntPtr(pointer);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
list.Add(handle);
// You can modify this to check to see if you want to cancel the operation, then return a null here
return true;
}
//THIS IS THE ONE YOU'LL CALL!
public static IntPtr GetWindowByClass(IntPtr mainWindow, string name)
{
List<IntPtr> windows = GetChildWindows(mainWindow);
foreach (IntPtr window in windows)
{
StringBuilder response = new StringBuilder();
response.Capacity = 500;
if (GetClassName(window, response, response.Capacity) > 0)
if (response.ToString() == name)
return window;
}
return IntPtr.Zero;
}
So basically it iterates through a whole set of child windows for the handle you have on the app, sees if the class name matches a control you're looking for, then returns it. There are thousands of ways to improve it (search for all the ones you want in a single shot, FindWindow MAY work by class name, etc.) but I wanted to show you more how it's done, not declare this is how it should be done. Finally, the call to get the text from the window/control is as follows (also adapted from pinvoke.net: look under User32.dll for all this stuff):
public static string GetText(IntPtr control)
{
StringBuilder builder = new StringBuilder(40);
IntPtr result = IntPtr.Zero;
uint response = SendMessageTimeoutText(control, 0xd, 40, builder, APITypes.SendMessageTimeoutFlags.SMTO_NORMAL, 2000, out result);
return builder.ToString();
}
[DllImport("user32.dll", EntryPoint = "SendMessageTimeout", SetLastError = true, CharSet = CharSet.Auto)]
public static extern uint SendMessageTimeoutText(
IntPtr hWnd,
int Msg, // Use WM_GETTEXT
int countOfChars,
StringBuilder text,
APITypes.SendMessageTimeoutFlags flags,
uint uTImeoutj,
out IntPtr result);
[Flags]
public enum SendMessageTimeoutFlags : uint
{
SMTO_NORMAL = 0x0,
SMTO_BLOCK = 0x1,
SMTO_ABORTIFHUNG = 0x2,
SMTO_NOTIMEOUTIFNOTHUNG = 0x8
}
EDIT: An addendum: the application I worked on to access another form like this actually didn't have unique control names, so I ended up using Spy++ to determine its place in the window hierarchy and pulling the children and selecting each child in turn. God help if you have to go that route, especially because it may not be consistent at all, especially if what you need is on a form that isn't created, or it's hidden behind another one that jumped it in the Z-Order (breaking your hierarchical list you're searching from). That said, you should know that EnumChildWindows will always enum ALL CHILD WINDOWS for a given window, no matter where they are in the hierarchy. If you really have to drill down and search for each control by its parent and its parent's parent, you'll need to use FindWindowEx, and declare the last child you looked at (or IntPtr.Zero if you want the first child):
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
What you are asking for seems counter intuitive if the software you are writing the plug-in for provides an API. You should really be using that.
While it is possible to use the Win32 API to enumerate child windows of the main window given the handle and then use more Win32 API functions to determine the state of the UI (i.e. "read the data") it's going to be very tedious and error-prone.
Here's a link to MSDN for EnumChildWindows which will allow you to enumerate child windows for the main window given the handle.
If you want to go down that rabbit hole you might also find SendMessage and GetWindowText useful. And most definitely you should check out pinvoke.net if you are going to be using these Win32 APIs from C#.
In Visual C#, I'm trying to take text from multiple textboxes (one at a time) and paste them into Notepad. I do this by copying to the clipboard, alt-tabbing, and then pasting into notepad...then again for the other textboxes. This code represents this idea:
subBox1.SelectAll();
subBox1.Copy();
SendKeys.Send("%{TAB}"); // alt+tab
SendKeys.Send("^v"); // paste
SendKeys.Send("{TAB}"); // tab
subBox2.SelectAll();
subBox2.Copy();
SendKeys.Send("^v");
SendKeys.Send("{TAB}");
subBox3.SelectAll();
subBox3.Copy();
SendKeys.Send("^v");
SendKeys.Send("{TAB}");
As you can see, this copies and pastes from three textboxes (named subBox1, 2, and 3). But, for some reason, only the last textbox's contents are getting copied over. This also happens if I comment out the third box...in that case, only the second textbox's content gets copied over. I've tried using the SelectAll() and Copy() as you see here, as well as the Clipboard class. Both have the same issue.
For example, if the textbox contents are "asdf", "qwer", and "zxcv" respectively, all I see is "zxcv" three times.
Any idea why this is happening? I've been stuck on this for about an hour now and have no idea what's going on.
Thanks a ton!
SendKeys doesn't wait for the other application to process the keys you send, so by the time notepad gets around to processing your keypresses, your program has already copied subBox3's text over the top of the other texts.
You need to use SendWait instead.
As well, instead of sending Alt+Tab, you could use something like this:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
// ...
SetForegroundWindow(FindWindow(null, "Untitled - Notepad"));
I'd use SendMessage for more accurate results. To use SendMessage, you first need a valid window handle to the text area of Notepad. This can be done in a variety of ways, but I prefer just using my simple child lookup function.
You will need the following namespace imports and PInvoke declarations:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
//pinvoke
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
[return:MarshalAs(UnmanagedType.Bool)]
private static extern bool GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
private static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
[return:MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr hParent, delChildWndProc callback, IntPtr lpParam);
//delegate callback for EnumChildWindows:
[return:MarshalAs(UnmanagedType.Bool)]
private delegate bool delChildWndProc(IntPtr hWnd, IntPtr lParam);
Now, onto the child window lookup. Basically works similar to FindWindowEx, but I wanted to write my own, and it retrieves multiple windows which can be nice. It uses the following wrapper class to describe information between calls:
private class WindowLookup
{
public string LookupName { get; private set; }
public List<IntPtr> MatchedChildren { get; private set; }
public int Depth { get; set; }
public int MaxDepth { get; set; }
public WindowLookup(string lookup, int maxdepth)
{
this.MatchedChildren = new List<IntPtr>();
this.LookupName = lookup;
this.MaxDepth = maxdepth;
if (this.MaxDepth > 0)
this.MaxDepth++; //account for the depth past the parent control.
this.Depth = 0;
}
}
And then the following functions do all the work:
private static List<IntPtr> FindAllWindows(IntPtr hParent, string className, int maxdepth = 0)
{
var lookup = new WindowLookup(className, maxdepth);
var gcAlloc = GCHandle.Alloc(lookup);
try
{
LookupChildProc(hParent, GCHandle.ToIntPtr(gcAlloc));
}
finally
{
if (gcAlloc.IsAllocated)
gcAlloc.Free();
}
return lookup.MatchedChildren;
}
private static bool LookupChildProc(IntPtr hChild, IntPtr lParam)
{
var handle = GCHandle.FromIntPtr(lParam);
WindowLookup lookup = null;
if (handle.IsAllocated && (lookup = handle.Target as WindowLookup) != null)
{
if (lookup.Depth < lookup.MaxDepth || lookup.MaxDepth == 0)
{
lookup.Depth++;
var builder = new StringBuilder(256);
if (GetClassName(hChild, builder, builder.Capacity) && builder.ToString().ToLower() == lookup.LookupName.ToLower())
lookup.MatchedChildren.Add(hChild);
EnumChildWindows(hChild, LookupChildProc, lParam);
}
}
return true;
}
You don't need to worry about the implementation of these functions too much, they'll work as-is. The key thing is that using these functions, you can find the handle to notepad's Edit window (the text area you type in) very easily.
var notepads = Process.GetProcessesByName("notepad");
if (notepads.Length > 0)
{
foreach(var notepad in notepads) //iterate through all the running notepad processes. Of course, you can filter this by processId or whatever.
{
foreach(var edit in FindAllWindows(notepad.MainWindowHandle, "Edit"))
{
//next part of the code will go here, read on.
}
}
}
Now, where I left the code was in the middle of a loop through the "Edit" windows of each notepad process running at the time. Now that we have a valid window handle, we can use SendMessage to send stuff to it. In particular, appending text. I wrote the following function to handle appending text to a remote control:
private static void AppendWindowText(IntPtr hWnd, string text)
{
if (hWnd != IntPtr.Zero)
{
//for your reference, 0x0E (WM_GETTEXTLENGTH), 0xB1 (EM_SETSEL), 0xC2 (EM_REPLACESEL)
int len = SendMessage(hWnd, 0x000E, IntPtr.Zero, IntPtr.Zero).ToInt32();
var unmanaged = Marshal.StringToHGlobalAuto(text);
SendMessage(hWnd, 0x00B1, new IntPtr(len), new IntPtr(len));
SendMessage(hWnd, 0x00C2, IntPtr.Zero, unmanaged);
Marshal.FreeHGlobal(unmanaged);
}
}
Now that we have our AppendWindowText function, you can add a function call to it in the nested loop above (where I put the comment):
AppendWindowText(edit, "Some text here");
And there you have it. It's a bit of a wordy response, but in the end this method is far more reliable than using SendKeys and focusing the window etc. You never need to lose focus of your own application.
If you have any questions, feel free to comment and I'll answer as best I can.
Cheers,
J
EDIT: Some references:
SendMessage function (MSDN)
EnumChildWindows function (MSDN)
Appending text using SendMessage
I have a desktop application installed on my machine. When I start a program some kind of window gets open. let's say, something like this (just example):
So, I want to write an application in C# that will find this window and capture some data from it.
What tools should I look at? I want to go with a path of least resistance.
I need to capture images, text from textboxes, and also find controls by text and click on them.
I suggest you use the cool but little-known UI Automation API for this work.
For this, the first thing to test is launch the associated UISpy tool. It will display a tree of all accessible windows on screen. It also is able to run some actions like pressing a menu, selecting an item, etc. This is using what's called UI Automation Control Patterns, which provide a way to categorize and expose a control's functionality independent of the control type or the appearance of the control.
So, if you can automate this application with UI Spy, you also can do the exact same thing using .NET code (UISpy is itself simply using the underlying API).
Here is an interesting tutorial article about UI automation programming: The Microsoft UI Automation Library
You should start enumerating handles of all windows for that process :
https://stackoverflow.com/a/2584672/351383
Then for each handle get information about text and position, with position infomation you can take screenshots of desktop on that position to get images AFAIK there is no other way to get images from a window of running application.
When you got screen positions of the controls then use from link below to simulate left mouse click, search windows for some text and then click on some point inside control, here is the method that will click a point :
https://stackoverflow.com/a/10355905/351383
I put toghether quick class to gather that data for process :
public static class ProcessSpy
{
public static List<ProcessSpyData> GetDataForProcess(string processName)
{
var result = new List<ProcessSpyData>();
Process myProc = Process.GetProcessesByName(processName).FirstOrDefault();
if (myProc != null)
{
var myHandles = EnumerateProcessWindowHandles(myProc);
foreach (IntPtr wndHandle in myHandles)
{
result.Add(new ProcessSpyData(wndHandle));
}
}
return result;
}
delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
static IEnumerable<IntPtr> EnumerateProcessWindowHandles(Process prc)
{
var handles = new List<IntPtr>();
foreach (ProcessThread thread in prc.Threads)
EnumThreadWindows(thread.Id, (hWnd, lParam) => { handles.Add(hWnd); return true; }, IntPtr.Zero);
return handles;
}
}
public class ProcessSpyData
{
private const uint WM_GETTEXT = 0x000D;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);
[DllImport("user32.dll")]
private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
int left, top, right, bottom;
public Rectangle ToRectangle()
{
return new Rectangle(left, top, right - left, bottom - top);
}
}
[DllImport("user32.dll")]
static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);
public IntPtr WindowHandle { get; private set; }
public string WindowText { get; private set; }
public Rectangle ClientRect { get; private set; }
public Rectangle ScreenPos { get; private set; }
public ProcessSpyData(IntPtr windowHandle)
{
this.WindowHandle = windowHandle;
GetWindowText();
GetWindowSize();
}
private void GetWindowText()
{
StringBuilder message = new StringBuilder(1024);
SendMessage(this.WindowHandle, WM_GETTEXT, message.Capacity, message);
this.WindowText = message.ToString();
}
private void GetWindowSize()
{
var nativeRect = new RECT();
GetClientRect(this.WindowHandle, out nativeRect);
this.ClientRect = nativeRect.ToRectangle();
Point loc = this.ClientRect.Location;
ClientToScreen(this.WindowHandle, ref loc);
this.ScreenPos = new Rectangle(loc, this.ClientRect.Size);
}
}
That should get you started, but you have to be aware if app is using non standard controls then there is no way to get text out of it with this method, and for images maybe you will get better results looking at executable resources.
UPDATE
Geting controls text for various control types (MFC, winforms, Delphi VCL etc.) would be very hard task, but for winforms see excelent Managed Windows API, they even have some sort of spy application in tools, look at that.
What kind of data are you trying to capture?
You may try listening to windows messages or reading the memory.
Depending on how much of these type of tasks you are going to be doing in the future (or how important this one is) you could try investing in something like Ranorex Spy (Ranorex studio is ott).
Link: http://www.ranorex.com/product/tools/ranorex-spy.html
there is no other way than to inject the application you want to inspect. This is how UISpy actually runs. This is also why UISpy should be run with Administrative credential.
I would like to simulate keystrokes within an embedded System.Windows.Controls.WebBrowser. Various techniques for simulating keystrokes are documented already here on StackOverflow, however they do not seem to work for the WebBrowser control.
Knowing that the control wraps another window/hwnd, I would have expected the following to work however it's not:
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
...
SendMessage(myWebBrowser.Handle, WM_CHAR, key, 0);
I am already using SendMessage to forward simulated keystrokes to other parts of the WPF application, and would prefer a consistent solution; however this is failing for the WebBrowser.
How can I forward simulated keystrokes to WebBrowser?
My solution was to use SendInput() instead of SendMessage().
The import:
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, User32.Input[] pInputs, int cbSize);
For the additional types and constants see here: http://pinvoke.net/default.aspx/user32/SendInput.html
For the expected behavior see here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx.
My virtual keypress method:
private void VirtualKeypress(Key keyCode, bool shift, char keyChar)
{
User32.Input[] inputSequence;
if (keyChar == '\0' && keyCode == Key.None)
{
throw new ArgumentException("Expected a key code or key char, not both.");
}
else if (keyChar != '\0')
{
inputSequence = KeyboardUtils.ConvertCharToInputArray(keyChar);
}
else
{
inputSequence = KeyboardUtils.ConvertKeyToInputArray(keyCode, shift);
}
User32.SendInput(
(uint)inputSequence.Length,
inputSequence,
Marshal.SizeOf(typeof(User32.Input))
);
}
I have two helper methods, ConvertCharToInputArray() and ConvertKeyToInputArray(), which return an array of length 2 or 4 depending if we need to tell windows that the shift key is depressed. For example:
'A' -> [] { shift down, A down, A up, shift up }
while just
'a' -> [] { A down, A up }
.
You were so close! The handle reported by WebBrowser.Handle is the outter most handle, while all of the input is directed to the inner most handle:
var hwnd = _browser.Handle;
hwnd = FindWindowEx(hwnd, IntPtr.Zero, "Shell Embedding", null);
hwnd = FindWindowEx(hwnd, IntPtr.Zero, "Shell DocObject View", null);
hwnd = FindWindowEx(hwnd, IntPtr.Zero, "Internet Explorer_Server", null);
SendMessage(hwnd, WM_CHAR, new IntPtr(0x0D), IntPtr.Zero);
FindWindowEx definition from pinvoke.net:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
Highlighted is the WebBrowser control:
Well i'm only used to using this in VB6 but Try sending to myWebBrowser.object.Handle or myWebBrowser.object.HWND is what i see in VB6, but you probably have .Handle in your .net version.
try the .object and let me know how it goes!!
I found I could do movement around an HTML form (Chrome Browser) from within a C# program by using sendmessage to the process #.
However, I couldn't insert text into an input field. Tried most everything (from pure C#).
While hacking, I noticed I could pop-up a context editing menu while the cursor was on the input I was trying to set, and one of the items on the menu was paste! WhatDoYouKnow! I could interact with that!
Here are the codes I used, once I had tabbed to the input I wanted to set:
Clipboard.SetText("52118"); // from C#, put the input value onto the clipboard
chrome.SendKey((char)93); // char 93 opens pop-up menu that includes paste
System.Threading.Thread.Sleep(30);
chrome.SendKey((char)0x28); // down to the first menu item
System.Threading.Thread.Sleep(30);
chrome.SendKey((char)0x28); // down to the second menu item (paste)
System.Threading.Thread.Sleep(100);
chrome.SendKey((char)0x0D); // fire the paste
Check here for the code used for the ChromeWrapper (Thanks for that!):
Sending keyboard key to browser in C# using sendkey function
You have have to use PostMessage instead of SendMessage, then it should work.
PS: 9 years late I know
I am creating a program that will send media key inputs (such as MediaPlayPause, MediaNextTrack, etc) to an application that I have the IntPtr of. Sort of like a virtual remote control.
So after researching I found this, which almost tells me exactly how to solve my problem.
However, there are three problems with the approach mentioned in the link.
I cannot set the application as foreground window as I need my application to be focused.
They use the SendKeys function which requires the target window to be focused, goes against problem 1.
From what I know, SendKeys cannot send keyboard buttons such as the keyboard Play/Pause button.
In the end, I am rather confused on what I have to use (SendInput?, SendMessage?).
Any help would be appreciated.
EDIT
Using the answer I received, I hacked together the sample code below.
Theoretically, it is supposed to find notepad and insert the letter "L" into it.
However nothing shows up on notepad, nor does the application crash. Is there an obvious error I am missing?
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(int ZeroOnly, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, IntPtr lParam);
const int WM_KEYDOWN = 0x100;
//const int WM_KEYUP = 0x101;
const int L_KEY = 0x4C;
private void button1_Click(object sender, EventArgs e)
{
IntPtr ip = FindWindowByCaption(0, "Untitled - Notepad");
SendMessage(ip, WM_KEYDOWN, L_KEY, IntPtr.Zero);
//SendMessage(ip, WM_KEYUP, L_KEY, IntPtr.Zero);
}
Most of these keys are translated to WM_APPCOMMAND* messages... so you can try SendMessage, the other option being SendInput (if the application is DirectInput-based)...
Check out the links in Windows API for common media player functions? - perhaps there is some information you can use...
As for the focus problem - there is no 100% reliable solution (see How do I send key strokes to a window without having to activate it using Windows API?)... best you can achieve with 100% reliability is to focus the application, send the keys, refocus your application... except you would write some sort of device driver (kernel mode)...
To send multimedia keys, including Play/Pause, NextTrack, PrevTrack, etc, you can use keybd_event:
public class Program
{
public const int KEYEVENTF_EXTENTEDKEY = 1;
public const int KEYEVENTF_KEYUP = 0;
public const int VK_MEDIA_NEXT_TRACK = 0xB0;
public const int VK_MEDIA_PLAY_PAUSE = 0xB3;
public const int VK_MEDIA_PREV_TRACK = 0xB1;
[DllImport("user32.dll")]
public static extern void keybd_event(byte virtualKey, byte scanCode, uint flags, IntPtr extraInfo);
public static void Main(string[] args)
{
keybd_event(VK_MEDIA_PLAY_PAUSE, 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero); // Play/Pause
//keybd_event(VK_MEDIA_PREV_TRACK, 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero); // PrevTrack
//keybd_event(VK_MEDIA_NEXT_TRACK, 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero); // NextTrack
}
Here is a list to the supported key codes that this windows api can handle:
https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
The SendKeys class is very nice, but it's also limited. The approach above sends the key command directly to Windows OS.