I'm writing a .exe that is supposed to run as a scheduled task to check if I have required IE windows open running .Xbaps on specific monitors. I have code that checks what URL is supposed to run, if it's not I use this code to launch it, then move it to the correct monitor:
Process myProcess = Process.Start("iexplore.exe", "-new -k " + "http://server01:123/software.client.xbap");
myProcess.WaitForInputIdle();
Thread.Sleep(500);
MoveWindowToMonitor(myProcess.MainWindowHandle, 1);
Window Moving code:
private static void MoveWindowToMonitor(IntPtr windowHandler, int monitor)
{
RECT windowRec = new RECT();
GetWindowRect(windowHandler, ref windowRec);
int width = windowRec.Right - windowRec.Left;
int height = windowRec.Top - windowRec.Bottom;
if (width < 0)
width = width * -1;
if (height < 0)
height = height * -1;
SetWindowPos(windowHandler, (IntPtr)SpecialWindowHandles.HWND_TOP, Screen.AllScreens[monitor].WorkingArea.Left,
Screen.AllScreens[monitor].WorkingArea.Top, width, height, SetWindowPosFlags.SWP_SHOWWINDOW);
}
Running a quick test version of this gets the first IE window open, Xbap launched, and then quickly moves it over to my other monitor. When I run it a second time, without closing the first IE window, I always get InvalidOperationException
"Process has exited, so the requested information is not available."
I've checked my Task Manager as this is happening and I actually get two iexplore.exe items under details the first time I run the task, and only one additional iexplorer.exe for each subsequent execution of the task. I also get one PresentationHost.exe per xbap launched.
Anyone have any idea what I'm doing wrong or a better way to do this?
My end goal is to be able to do this:
Launch IE in Kiosk Mode on monitor 1 with specific url X:
Launch IE in Kiosk Mode on monitor 2 with specific url Y:
After you launch an IE process, it does some funny stuff and that process you have launched can occasionally end right away as another takes over the window.
What I would do is, using the methods below, is EnumTheWindows which would step through every visible window running and look for Internet Explorer or my baseURL. Then I would pass that Window Handle to GetURL and get the specific URL that IE window was running. This allowed me to use ConfirmProcessIsOnProperMonitor() and MoveWindowToMonitor() to get the windows on the proper monitor.
Important stuff:
private static bool ConfirmProcessIsOnProperMonitor(IntPtr windowHandler, int monitor)
{
//make sure you don't go to an incorrect monitor
if (monitor >= Screen.AllScreens.Count()) monitor = Screen.AllScreens.Count() - 1;
RECT windowRec = new RECT();
GetWindowRect(windowHandler, ref windowRec);
if (windowRec.Left != Screen.AllScreens[monitor].WorkingArea.Left || windowRec.Top != Screen.AllScreens[monitor].WorkingArea.Top)
return false;
else
return true;
}
private static void MoveWindowToMonitor(IntPtr windowHandler, int monitor)
{
//make sure you don't go to an incorrect monitor
if (monitor >= Screen.AllScreens.Count()) monitor = Screen.AllScreens.Count() - 1;
RECT windowRec = new RECT();
GetWindowRect(windowHandler, ref windowRec);
int width = windowRec.Right - windowRec.Left;
int height = windowRec.Top - windowRec.Bottom;
if (width < 0)
width = width * -1;
if (height < 0)
height = height * -1;
SetWindowPos(windowHandler, (IntPtr)SpecialWindowHandles.HWND_TOP, Screen.AllScreens[monitor].WorkingArea.Left,
Screen.AllScreens[monitor].WorkingArea.Top, width, height, SetWindowPosFlags.SWP_SHOWWINDOW);
}
protected static bool EnumTheWindows(IntPtr hWnd, IntPtr lParam)
{
int size = GetWindowTextLength(hWnd);
if (size++ > 0 && IsWindowVisible(hWnd))
{
StringBuilder sb = new StringBuilder(size);
GetWindowText(hWnd, sb, size);
string windowText = sb.ToString();
if (windowText.ToLower().Contains(_baseURL) || windowText.ToLower().Contains("internet explorer"))
{
string url = GetURL(hWnd);
_windowhandles.Add(hWnd, url);
}
}
return true;
}
private static string GetURL(IntPtr intPtr)
{
foreach (InternetExplorer ie in new ShellWindows())
{
if (ie.HWND == intPtr.ToInt32())
{
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(ie.FullName);
if ((fileNameWithoutExtension != null) && fileNameWithoutExtension.ToLower().Equals("iexplore"))
{
return ie.LocationURL;
}
else
{
return null;
}
}
}
return null;
}
Hard to read windows API code:
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, StringBuilder msgbody);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
protected delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
protected static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
protected static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
protected static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
[DllImport("user32.dll")]
protected static extern bool IsWindowVisible(IntPtr hWnd);
Related
I wrote this code to test the Inject mouse method but it is not working for me. The test is supposed to click in the google text box search area, but the box never gets highlighted. Any idea why?
Google's page does load. The code runs (confirmed through break points), but nothing happens.
public partial class Form1 : Form
{
private IWebView webView;
public Form1()
{
InitializeComponent();
initiate();
}
private void button1_Click(object sender, EventArgs e)
{
click(650, 405);
}
private async void initiate()
{
WebSession session = WebCore.CreateWebSession(
#"C:\SessionDataPath", WebPreferences.Default);
webView = WebCore.CreateWebView(
this.ClientSize.Width,
this.ClientSize.Height, session, WebViewType.Window
);
webView.ParentWindow = this.Handle;
webView.Source = new Uri("http://www.google.com");
await Task.Delay(30000);
click(650, 405);
}
public void click(int x, int y)
{
webView.InjectMouseMove(x, y);
webView.InjectMouseDown(MouseButton.Left);
webView.InjectMouseUp(MouseButton.Left);
}
}
I tried to get this code to work with chromium handle by looking at the proper chromium class but it didn't work
private async Task<bool> clickCoorindate(Point point)
{
webView.FocusView();
int x = point.X; // X coordinate of the click
int y = point.Y; // Y coordinate of the click
IntPtr handle = webView.ProcessHandle;
StringBuilder className = new StringBuilder(100);
while (className.ToString() != "Chrome_RenderWidgetHostHWND") // The class control for the browser
{
handle = GetWindow(handle, 5); // Get a handle to the child window
GetClassName(handle, className, className.Capacity);
if (className.ToString() == "Chrome_RenderWidgetHostHWND")
handle = Handle;
}
IntPtr lParam = (IntPtr)((y << 16) | x); // The coordinates
IntPtr wParam = IntPtr.Zero; // Additional parameters for the click (e.g. Ctrl)
const uint downCode = 0x201; // Left click down code
const uint upCode = 0x202; // Left click up code
const uint moveCode = 0x200;
SendMessage(handle, downCode, wParam, lParam); // Mouse button down
SendMessage(handle, upCode, wParam, lParam); // Mouse button up
Thread.Sleep(20);
SendMessage(handle, downCode, wParam, lParam); // Mouse button down
SendMessage(handle, upCode, wParam, lParam); // Mouse button up
return true;
}
As mentioned in the documentation (see: WebViewType), a windowed view captures all input itself and you cannot inject input programmatically using Awesomium API (you could do this as you tried, by sending native Windows messages to the appropriate HWND but it's not suggested and straightforward procedure).
To be able to inject input programmatically using the InjectXXX methods, make sure your view is of type offscreen.
in my XNA game i use a lot Awesomium and this is my InputSystem i've implemented in my awesomium component, it works very well.
note that this is just a part of my class, so some methods aren't here but they are not needed to understand the process
basically in my thread. basically i hook to the messages in my form and relay them to the WebView. Hope this helps
public partial class BasicAwesomiumComponent : DrawableGameComponent {
private delegate Int32 ProcessMessagesDelegate(Int32 code, Int32 wParam, ref Message lParam);
private static class User32 {
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr SetWindowsHookEx(Int32 windowsHookId, ProcessMessagesDelegate function, IntPtr mod, Int32 threadId);
[DllImport("user32.dll", SetLastError = true)]
internal static extern Int32 UnhookWindowsHookEx(IntPtr hook);
[DllImport("user32.dll", SetLastError = true)]
internal static extern Int32 CallNextHookEx(IntPtr hook, Int32 code, Int32 wParam, ref Message lParam);
[DllImport("user32.dll", SetLastError = true)]
internal static extern Boolean TranslateMessage(ref Message message);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr FindWindow(String className, String windowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern int RegisterWindowMessage(String msg);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(HandleRef hWnd, Int32 msg, Int32 wParam, Int32 lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool SystemParametersInfo(Int32 nAction, Int32 nParam, ref Int32 value, Int32 ignore);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern int GetSystemMetrics(Int32 nIndex);
}
private static class Kernel32 {
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern Int32 GetCurrentThreadId();
}
private static class SystemMetrics {
internal static Int32 MouseWheelScrollDelta {
get {
return 120;
}
}
internal static Int32 MouseWheelScrollLines {
get {
var scrollLines = 0;
if (User32.GetSystemMetrics(75) == 0) {
var hwnd = User32.FindWindow("MouseZ", "Magellan MSWHEEL");
if (hwnd != IntPtr.Zero) {
var windowMessage = User32.RegisterWindowMessage("MSH_SCROLL_LINES_MSG");
scrollLines = (Int32)User32.SendMessage(new HandleRef(null, hwnd), windowMessage, 0, 0);
if (scrollLines != 0) {
return scrollLines;
}
}
return 3;
}
User32.SystemParametersInfo(104, 0, ref scrollLines, 0);
return scrollLines;
}
}
}
private enum WindowsMessage {
KeyDown = 0x0100,
KeyUp = 0x0101,
Char = 0x0102,
MouseMove = 0x0200,
LeftButtonDown = 0x0201,
LeftButtonUp = 0x0202,
LeftButtonDoubleClick = 0x0203,
RightButtonDown = 0x0204,
RightButtonUp = 0x0205,
RightButtonDoubleClick = 0x0206,
MiddleButtonDown = 0x0207,
MiddleButtonUp = 0x0208,
MiddleButtonDoubleClick = 0x0209,
MouseWheel = 0x020A,
}
private struct Message {
internal IntPtr HWnd;
internal Int32 Msg;
internal IntPtr WParam;
internal IntPtr LParam;
internal IntPtr Result;
}
private IntPtr hookHandle;
private ProcessMessagesDelegate processMessages;
private Int32 ProcessMessages(Int32 code, Int32 wParam, ref Message lParam) {
if (this.Enabled && code == 0 && wParam == 1) {
bool processed = false;
switch ((WindowsMessage)lParam.Msg) {
case WindowsMessage.KeyDown:
case WindowsMessage.KeyUp:
case WindowsMessage.Char:
WebKeyboardEvent keyboardEvent = new WebKeyboardEvent((uint)lParam.Msg, lParam.WParam, lParam.LParam, 0);
awesomiumContext.Post(state => {
if (!WebView.IsLive) return;
WebView.InjectKeyboardEvent(keyboardEvent);
}, null);
processed = true;
break;
case WindowsMessage.MouseWheel:
var delta = (((Int32)lParam.WParam) >> 16);
awesomiumContext.Post(state => {
if (!WebView.IsLive) return;
WebView.InjectMouseWheel(delta / SystemMetrics.MouseWheelScrollDelta * 16 * SystemMetrics.MouseWheelScrollLines, 0);
}, null);
processed = true;
break;
}
if (!processed) {
WindowsMessage message = (WindowsMessage)lParam.Msg;
awesomiumContext.Post(state => {
if (!WebView.IsLive) return;
switch (message) {
case WindowsMessage.MouseMove:
var mouse = Mouse.GetState();
WebView.InjectMouseMove(mouse.X - area.X, mouse.Y - area.Y);
break;
case WindowsMessage.LeftButtonDown:
WebView.InjectMouseDown(MouseButton.Left);
break;
case WindowsMessage.LeftButtonUp:
WebView.InjectMouseUp(MouseButton.Left);
break;
case WindowsMessage.LeftButtonDoubleClick:
WebView.InjectMouseDown(MouseButton.Left);
break;
case WindowsMessage.RightButtonDown:
WebView.InjectMouseDown(MouseButton.Right);
break;
case WindowsMessage.RightButtonUp:
WebView.InjectMouseUp(MouseButton.Right);
break;
case WindowsMessage.RightButtonDoubleClick:
WebView.InjectMouseDown(MouseButton.Right);
break;
case WindowsMessage.MiddleButtonDown:
WebView.InjectMouseDown(MouseButton.Middle);
break;
case WindowsMessage.MiddleButtonUp:
WebView.InjectMouseUp(MouseButton.Middle);
break;
case WindowsMessage.MiddleButtonDoubleClick:
WebView.InjectMouseDown(MouseButton.Middle);
break;
}
}, null);
}
User32.TranslateMessage(ref lParam);
}
return User32.CallNextHookEx(IntPtr.Zero, code, wParam, ref lParam);
}
}
update:
note that in my component, to hook the message pump, i use
int currentThread = Kernel32.GetCurrentThreadId();
// Create the message hook.
hookHandle = User32.SetWindowsHookEx(3, ProcessMessages, IntPtr.Zero, currentThread);
my surface in an Offscreen webview so the more complex, this should work for you too
i posted a separated answer to give another direction:
take a look at this gist: https://gist.github.com/robertkhrona/918109
it seems to suggest to do
webView.InjectMouseMove(x,y);
webView.InjectMouseDown(MouseButton.Left);
webView.InjectMouseMove(x,y);
webView.InjectMouseUp(MouseButton.Left);
so moving (to the same position) between the two mousedown/up event
btw i think this shouldn't be needed tho
which version of awesomium are you running?
update:
Remember to set the focus on your WebView before injecting inputs
webView.Focus();
I set the viewtype to offscreen and the injectclick worked fine, when set to window it doesn't work. I don't know why, but I can work with that.
I am developing an application in c#.net,and for that i am writing code to display icon in a system tray,and whenever a new message arrives the balloon tooltip will be shown there,which has click event which will open new message arrived,everything works fine,but the problem is i am getting multiple numbers of icon generated in system tray,which shuld be only one,how can i prevent it?i found on internet how to dispose them,but couldn't find way to prevent more than one.or is there any better way to show notifications for newly received message..please help me if you know the solution..
There are better custom solutions available see here and here for some examples.
However, System Tray doesn't refresh automatically. If you show/hide multiple system tray icons, it can mess the tray up. Normally all disposed icons will disappear when you mouse hover. However, there is a way to refresh the system tray programmatically. Reference here.
Note : SendMessage function, sends the specified message to a window or windows. The SendMessage function calls the window procedure for the specified window and does not return until the window procedure has processed the message.
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll")]
public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
public void RefreshTrayArea()
{
IntPtr systemTrayContainerHandle = FindWindow("Shell_TrayWnd", null);
IntPtr systemTrayHandle = FindWindowEx(systemTrayContainerHandle, IntPtr.Zero, "TrayNotifyWnd", null);
IntPtr sysPagerHandle = FindWindowEx(systemTrayHandle, IntPtr.Zero, "SysPager", null);
IntPtr notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "Notification Area");
if (notificationAreaHandle == IntPtr.Zero)
{
notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "User Promoted Notification Area");
IntPtr notifyIconOverflowWindowHandle = FindWindow("NotifyIconOverflowWindow", null);
IntPtr overflowNotificationAreaHandle = FindWindowEx(notifyIconOverflowWindowHandle, IntPtr.Zero, "ToolbarWindow32", "Overflow Notification Area");
RefreshTrayArea(overflowNotificationAreaHandle);
}
RefreshTrayArea(notificationAreaHandle);
}
private static void RefreshTrayArea(IntPtr windowHandle)
{
const uint wmMousemove = 0x0200;
RECT rect;
GetClientRect(windowHandle, out rect);
for (var x = 0; x < rect.right; x += 5)
for (var y = 0; y < rect.bottom; y += 5)
SendMessage(windowHandle, wmMousemove, 0, (y << 16) + x);
}
I've created some global Hot keys for my application and I want them to work only if my application is active. (It should not work if my application is not the active form).
So how can I check if my C# winform application is the active form among all the other windows applications?
I tried
if(this.Focused)
//Do somthing
But it's not working
Try this:
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
public static bool Activates()
{
var x = GetForegroundWindow();
if (x == IntPtr.Zero) {
return false;
}
var y = Process.GetCurrentProcess().Id;
int i;
GetWindowThreadProcessId(x, out i);
return i == y;
}
You can also refer: C#: Detecting which application has focus
You can use Windows API function GetForegroundWindow and GetWindowText.
GetForegroundWindow :
The GetForegroundWindow function returns a handle to the window with which the user is currently working.
GetWindowText:
The GetWindowText function copies the text of the specified window's title bar (if it has one) into a buffer.
Add below code to declare API functions :
[ DllImport("user32.dll") ]
static extern int GetForegroundWindow();
[ DllImport("user32.dll") ]
static extern int GetWindowText(int hWnd, StringBuilder text, int count);
Start a timer :
private void timer1_Tick(object sender, System.EventArgs e)
{
GetActiveWindow();
}
Active window function :
private void GetActiveWindow()
{
const int nChars = 256;
int handle = 0;
StringBuilder Buff = new StringBuilder(nChars);
handle = GetForegroundWindow();
if ( GetWindowText(handle, Buff, nChars) > 0 )
{
this.captionWindowLabel.Text = Buff.ToString();
this.IDWindowLabel.Text = handle.ToString();
}
}
I have to show information to the user that updates every 100 milliseconds, this means the contents of the textbox I show it in are constantly being changed and if they are scrolling through them when they are being changed the update will cause them to loose their scroll bar position
How do I prevent this? I've reduced the effect a lot by adding all text at once.
Current code:
string textboxStr = "";
foreach (string debugItem in debugItems)
{
textboxStr += debugItem + Environment.NewLine;
}
debugForm.Controls[0].Text = textboxStr;
Update 1:
Used the solution provided below and it's not working, the scroll bar still resets to its default position meaning you loose your position and your pointer resets too.
Implementation:
In class:
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool LockWindowUpdate(IntPtr hWndLock);
In function:
var originalPosition = ((TextBox)debugForm.Controls[0]).SelectionStart;
LockWindowUpdate(((TextBox)debugForm.Controls[0]).Handle);
debugForm.Controls[0].Text = textboxStr;
((TextBox)debugForm.Controls[0]).SelectionStart = originalPosition;
((TextBox)debugForm.Controls[0]).ScrollToCaret();
LockWindowUpdate(IntPtr.Zero);
Update 2:
Used the 2nd solution provided below and it's not working. The scroll bar still jumps to the top even mid way while scrolling. Then sometimes when you're not even on it the scroll bar will start jumping up and down (Every 100 ms, when it updates the text).
Implementation:
In class:
[DllImport("user32.dll")]
static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
private const int SB_VERT = 0x1;
private const int SB_THUMBPOSITION = 4;
private const int WM_VSCROLL = 0x115;
In function:
var currentPosition = GetScrollPos(debugForm.Controls[0].Handle, SB_VERT);
debugForm.Controls[0].Text = textboxStr;
SetScrollPos(debugForm.Controls[0].Handle, SB_VERT, currentPosition, false);
PostMessageA(debugForm.Controls[0].Handle, WM_VSCROLL, SB_THUMBPOSITION + 65535 * currentPosition, 0);
Example text:
Active Scene: Level0
--------------------------------------------------
Settings
Fps: 60
GameSize: {Width=600, Height=600}
FreezeOnFocusLost: False
ShowCursor: False
StaysOnTop: False
EscClose: True
Title:
Debug: True
DebugInterval: 100
--------------------------------------------------
Entities
Entity Name: Player
moveSpeed: 10
jumpSpeed: 8
ID: 0
Type: 0
Gravity: 1
Vspeed: 1
Hspeed: 0
X: 20
Y: 361
Z: 0
Sprites: System.Collections.Generic.List`1[GameEngine.Sprite]
SpriteIndex: 0
SpriteSpeed: 0
FramesSinceChange: 0
CollisionHandlers: System.Collections.Generic.List`1[GameEngine.CollisionHandler]
--------------------------------------------------
Key Events
Key: Left
State: DOWN
Key: Left
State: UP
Key: Right
State: DOWN
Key: Right
State: UP
Key: Up
State: DOWN
Key: Up
State: UP
You can store the SelectionStart then use ScrollToCaret after you update. Use LockWindowUpdate to stop the flicker. Something like this:
[DllImport("user32.dll")]
public static extern bool LockWindowUpdate(IntPtr hWndLock);
var originalPosition = textBox.SelectionStart;
LockWindowUpdate(textBox.Handle);
// ---- do the update here ----
textBox.SelectionStart = originalPosition;
textBox.ScrollToCaret();
LockWindowUpdate(IntPtr.Zero);
As long as the textbox doesn't change size (which it doesn't sound like it will) this should work fine. The other option is to use EM_LINESCROLL to store and set the scrollbar value for the textbox.. but that's more involved.
EDIT:
Since that didn't work.. here's another option.
First, some Windows APIs:
[DllImport("user32.dll")]
static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
private static extern bool PostMessageA(IntPtr hWnd, int nBar, int wParam, int lParam);
..and some values:
private const int SB_VERT = 0x1;
private const int SB_THUMBPOSITION = 4;
private const int WM_VSCROLL = 0x115;
You can now do this:
var currentPosition = GetScrollPos(textBox.Handle, SB_VERT);
// ---- update the text here ----
SetScrollPos(textBox.Handle, SB_VERT, currentPosition, false);
PostMessageA(textBox.Handle, WM_VSCROLL, SB_THUMBPOSITION + 65535 * currentPosition, 0);
This works perfectly for me. The only problem I have is that it jumps around sometimes purely because my randomly generated string of characters has widely varying widths. As long as yours is roughly similar after each update, it should be fine.
After searching and never finding a legitimate solution that works with and without focus as well as horizontally and vertically, I stumbled across an API solution that works (at least for my platform - Win7 / .Net4 WinForms).
using System;
using System.Runtime.InteropServices;
namespace WindowsNative
{
/// <summary>
/// Provides scroll commands for things like Multiline Textboxes, UserControls, etc.
/// </summary>
public static class ScrollAPIs
{
[DllImport("user32.dll")]
internal static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
internal static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
[DllImport("user32.dll")]
internal static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
public enum ScrollbarDirection
{
Horizontal = 0,
Vertical = 1,
}
private enum Messages
{
WM_HSCROLL = 0x0114,
WM_VSCROLL = 0x0115
}
public static int GetScrollPosition(IntPtr hWnd, ScrollbarDirection direction)
{
return GetScrollPos(hWnd, (int)direction);
}
public static void GetScrollPosition(IntPtr hWnd, out int horizontalPosition, out int verticalPosition)
{
horizontalPosition = GetScrollPos(hWnd, (int)ScrollbarDirection.Horizontal);
verticalPosition = GetScrollPos(hWnd, (int)ScrollbarDirection.Vertical);
}
public static void SetScrollPosition(IntPtr hwnd, int hozizontalPosition, int verticalPosition)
{
SetScrollPosition(hwnd, ScrollbarDirection.Horizontal, hozizontalPosition);
SetScrollPosition(hwnd, ScrollbarDirection.Vertical, verticalPosition);
}
public static void SetScrollPosition(IntPtr hwnd, ScrollbarDirection direction, int position)
{
//move the scroll bar
SetScrollPos(hwnd, (int)direction, position, true);
//convert the position to the windows message equivalent
IntPtr msgPosition = new IntPtr((position << 16) + 4);
Messages msg = (direction == ScrollbarDirection.Horizontal) ? Messages.WM_HSCROLL : Messages.WM_VSCROLL;
SendMessage(hwnd, (int)msg, msgPosition, IntPtr.Zero);
}
}
}
With a multiline textbox (tbx_main) use like:
int horzPos, vertPos;
ScrollAPIs.GetScrollPosition(tbx_main.Handle, out horzPos, out vertPos);
//make your changes
//i did something like the following where m_buffer is a string[]
tbx_main.Text = string.Join(Environment.NewLine, m_buffer);
tbx_main.SelectionStart = 0;
tbx_main.SelectionLength = 0;
ScrollAPIs.SetScrollPosition(tbx_main.Handle, horzPos, vertPos);
I have a problem with getting scrollbar positions. Is it possible to get the scrollbar position of another process for example Notepad. I wrote small app where i tested and always get 0 0 as a position of scrollbars.
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool MoveWindow(IntPtr hwnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("user32.dll")]
static extern IntPtr SetActiveWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
private void Form1_Load(object sender, EventArgs e)
{
this.SuspendLayout();
Process notepad = new Process();
ProcessStartInfo psi = new ProcessStartInfo(#"c:\list1.txt");
psi.WindowStyle = ProcessWindowStyle.Normal;
notepad.StartInfo = psi;
notepad.Start();
this.ResumeLayout();
notepad.WaitForInputIdle(3000);
IntPtr old = SetParent(notepad.MainWindowHandle, this.Handle);
SetWindowLong(notepad.MainWindowHandle, GWL_STYLE, WS_VISIBLE + WS_MAXIMIZE);
MoveWindow(notepad.MainWindowHandle, 100, 100, 400, 400, true);
SetActiveWindow(notepad.MainWindowHandle);
SwitchToThisWindow(notepad.MainWindowHandle, true);
}
I have button which send PGDN event to notepad and it works great but after pgdn event position of scrollbar also is 0 0
private void PGDN_Click(object sender, EventArgs e)
{
Process[] procs = Process.GetProcessesByName("Notepad");
IntPtr hwnd = procs[0].MainWindowHandle;
SetActiveWindow(hwnd);
SwitchToThisWindow(hwnd, true);
Thread.Sleep(2000);
SendKeys.SendWait("{PGDN}");
Thread.Sleep(2000);
label1.Text = "OK";
label1.Text = "";
label1.Text = HScrollPos().ToString() + " " + VScrollPos().ToString(); }
Here are the HScrollPos and VScrollPos functions :
public int VScrollPos()
{
Process[] procs = Process.GetProcessesByName("Notepad");
IntPtr hwnd = procs[0].MainWindowHandle;
if (procs.Length != 0)
{
return GetScrollPos(hwnd , SB_VERT);
}
else
{
MessageBox.Show("Notepad Nor Running");
return 0;
}
}
public int HScrollPos()
{
Process[] procs = Process.GetProcessesByName("Notepad");
IntPtr hwnd = procs[0].MainWindowHandle;
if (procs.Length != 0)
{
return GetScrollPos(hwnd , SB_HORZ);
}
else
{
MessageBox.Show("Notepad Nor Running");
return 0;
}
}
Maybe there is another way to get Scrollbar Position of another process/window in windows? Please Help. Thx for granted.
And here is the Working Code based on Answer. Thx
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
private void button4_Click(object sender, EventArgs e)
{
string lpszParentClass = "Notepad";
string lpszParentWindow = "Untitled - Notepad";
string lpszClass = "Edit";
IntPtr ParenthWnd = new IntPtr(0);
IntPtr hWnd = new IntPtr(0);
ParenthWnd = FindWindow(lpszParentClass, lpszParentWindow);
if (ParenthWnd.Equals(IntPtr.Zero))
MessageBox.Show("Notepad Not Running");
else
{
hWnd = FindWindowEx(ParenthWnd, hWnd, lpszClass, "");
if (hWnd.Equals(IntPtr.Zero))
MessageBox.Show("Notepad doesn't have an edit component ?");
else
{
MessageBox.Show("Notepad Window: " + ParenthWnd.ToString());
MessageBox.Show("Edit Control: " + hWnd.ToString());
}
}
SetActiveWindow(ParenthWnd);
label5.Text = GetScrollPos(hWnd, SB_VERT) + " " + GetScrollPos(hWnd, SB_HORZ);
}
I suspect the problem is that you are using the main window handle, you should be using the handle of the Edit control, which is a child of the main window.
Using the main window hwnd you can enumrate the child windows to find the hWnd of the edit control and then use that hWnd in your call to get the scroll bar position.
SendKeys is working because it is sending the key stroke to the window that has input focus which in this case is the Edit control.
Here is an answer to a question I provided sometime back that will help with the interop for EnumChildWindows if you need, there is a lot more there but it might help.