I am using Windows Forms and I am attempting to use SendMessage to get the ComboBox dropdown rectangle. However I can't seem to find the correct parameter combination that will allow the code to compile.
I have tried copying examples I found, but nothing seems to compile.
Here are some examples of lines that do not compile:
var z1 = SendMessage(hWnd, CB_GETDROPPEDCONTROLRECT, (IntPtr)1, (IntPtr)0); // The best overloaded match has some invalid arguments.
var z2 = SendMessage(hWnd, 0x0152, (IntPtr)1, (IntPtr)0);
var z3 = SendMessage(hWnd, CB_GETDROPPEDCONTROLRECT, 1, 0);
var z4 = SendMessage(hWnd, 0x0152, 1, 0);
Thanks in advance to anyone who has any ideas to make this work.
Here is my complete code:
public partial class Form1 : Form
{
[DllImport("user32.dll")]
public static extern int SendMessage(
int hWnd, // handle to destination window
uint Msg, // message
long wParam, // first message parameter
long lParam // second message parameter
);
public Form1()
{
InitializeComponent();
List<string> itms = new List<string>();
itms.Add("Choice 1");
itms.Add("Choice 2");
itms.Add("Choice 3");
itms.Add("Choice 4");
itms.Add("Choice 5");
this.comboBox1.Items.AddRange(itms.ToArray());
}
private void comboBox1_DropDown(object sender, EventArgs e)
{
const int CB_GETDROPPEDCONTROLRECT = 0x0152;
IntPtr hWnd = comboBox1.Handle;
var z = SendMessage(hWnd, CB_GETDROPPEDCONTROLRECT, (IntPtr)1, (IntPtr)0); // The best overloaded match has some invalid arguments.
var z1 = SendMessage(hWnd, 0x0152, (IntPtr)1, (IntPtr)0);
}
}
To get the dropdown rectangle of a combobox you can do this:
First, declare the RECT struct:
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
Note: the Microsoft documentation states these fields should be long, but I tested it and for some strange reason SendMessage answers with int's here.
Second, the correct SendMessage declaration: For this special case you can now use a ref RECT parameter. Note that in your versions there are mistakes: hWnd needs to be an IntPtr while wParam is only int and not long:
[DllImport("user32.dll")]
public static extern int SendMessage(
IntPtr hWnd, // handle to destination window (combobox in this case)
int Msg, // message
int wParam, // first message parameter
ref RECT lParam // second message parameter
);
Third, the usage:
RECT rect = default;
int result = SendMessage(comboBox1.Handle, 0x0152, 1, ref rect);
Where comboBox1 is of course your ComboBox. If result is zero, the call failed, otherwise it succeeded and rect should contain the desired values.
Related
I made a code that synchronize a scroll of two RichTextBox.
Hope this works without a matter of line numbers.
but when the line of RichTextBox gets large (around 2000+), System.OverflowException occurs at SendMessage method.
Covering SendMessage with try/catch does not make it work.
Is there any way to handle IntPtr with a number which is greater than Int.MaxValue?
This is my code.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
for (int a = 0; a < 4000; a++)
{
RTB1.Text += a + "\n";
RTB2.Text += a + "\n";
}
}
[DllImport("User32.dll")]
public extern static int GetScrollPos(IntPtr hWnd, int nBar);
[DllImport("User32.dll")]
public extern static int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
private void RTB1_VScroll(object sender, EventArgs e)
{
int nPos = GetScrollPos(RTB1.Handle, (int)ScrollBarType.SbVert);
nPos <<= 16;
uint wParam = (uint)ScrollBarCommands.SB_THUMBPOSITION | (uint)nPos;
SendMessage(RTB2.Handle, (int)Message.WM_VSCROLL, new IntPtr(wParam), new IntPtr(0)); //Error occurs here.
}
public enum ScrollBarType : uint
{
SbHorz = 0,
SbVert = 1,
SbCtl = 2,
SbBoth = 3
}
public enum Message : uint
{
WM_VSCROLL = 0x0115
}
public enum ScrollBarCommands : uint
{
SB_THUMBPOSITION = 4
}
}
Looks like your application is running as 32 bit and you're getting an Overflow because UInt can have a value which can't be fit in 32 bit signed int.
For instance, running your application as 64 bit should just work fine.
That said, you don't need that. You can simply avoid using uint and just use int which will work just fine.
int wParam = (int)ScrollBarCommands.SB_THUMBPOSITION | (int)nPos;
In my windows.forms c# application, I have a multi-line textbox with WordWrap = true. After I set Text property to a long string, I need to get all lines produced by wrapping. It is not the same as Lines[] property, because my text does not include new line characters.
I have found solutions using graphics MeasureString function but it seems a little bit extra work considering that the textbox control already did the wrapping - why should I do the same work again?
Is there any way to get the lines into which the textbox wraps the text?
Thank you
Can you check the below solution,
public Form1()
{
InitializeComponent();
textBox1.Text = "This is my text where I want to check how I can get wrapped content as seperate lines automatically !! This is my text which I want to check how I can get wrapped content as seperate lines automatically !!";
}
private void button1_Click(object sender, EventArgs e)
{
bool continueProcess = true;
int i = 1; //Zero Based So Start from 1
int j = 0;
List<string> lines = new List<string>();
while (continueProcess)
{
var index = textBox1.GetFirstCharIndexFromLine(i);
if (index != -1)
{
lines.Add(textBox1.Text.Substring(j, index - j));
j = index;
i++;
}
else
{
lines.Add(textBox1.Text.Substring(j, textBox1.Text.Length - j));
continueProcess = false;
}
}
foreach(var item in lines)
{
MessageBox.Show(item);
}
}
GetFirstCharIndexFromLine Reference
Line numbering in the text box starts at zero. If the lineNumber
parameter is greater than the last line in the text box,
GetFirstCharIndexFromLine returns -1.
GetFirstCharIndexFromLine returns the first character index of a
physical line. The physical line is the displayed line, not the
assigned line. The number of displayed lines can be greater than the
number of assigned lines due to word wrap. For example, if you assign
two long lines to a RichTextBox control and set Multiline and WordWrap
to true, the two long assigned lines result in four physical (or
displayed lines).
A little pinvoking would work:
private const UInt32 EM_GETLINECOUNT = 0xba;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private void button1_Click(object sender, EventArgs e) {
int numLines = SendMessage(textBox1.Handle,
EM_GETLINECOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32()
MessageBox.Show(numLines.ToString());
}
REVISED ANSWER
I checked the Win32 APIs again and realized it could be done easily. I wrote an extension method so you can do it even easier:
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class TextBoxExtensions
{
private const uint EM_FMTLINES = 0x00C8;
private const uint WM_GETTEXT = 0x000D;
private const uint WM_GETTEXTLENGTH = 0x000E;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);
public static string[] GetWrappedLines(this TextBox textBox)
{
var handle = textBox.Handle;
SendMessage(handle, EM_FMTLINES, 1, IntPtr.Zero);
var size = SendMessage(handle, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero).ToInt32();
if (size > 0)
{
var builder = new StringBuilder(size + 1);
SendMessage(handle, WM_GETTEXT, builder.Capacity, builder);
return builder.ToString().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
}
return new string[0];
}
}
}
usage:
var lines = textBox1.GetWrappedLines();
ORIGINAL ANSWER
WinForm TextBox is actually a wrapper of Windows GDI edit control, which handles text wrapping natively. That being said, even if the TextBox keeps an array of wrapped lines, it is not exposed by public API, not even brought to managed environment (which, if it did, can however be retrieved with reflection). So your best bet is still MeasureString.
To check if particular line is wrapped or not, here is the GDI Function you need to use:
1. [DllImport("user32.dll")]
static extern int DrawText(IntPtr hdc, string lpStr, int nCount, ref Dimension lpRect, int wFormat);
Here are what you need to get things done:
public enum DrawTextFlags
{
CalculateArea = 0x00000400,
WordBreak = 0x00000010,
TextBoxControl = 0x00002000,
Top = 0x00000000,
Left = 0x00000000,
HorizontalCenter = 0x00000001,
Right = 0x00000002,
VerticalCenter = 0x00000004,
Bottom = 0x00000008,
SingleLine = 0x00000020,
ExpandTabs = 0x00000040,
TabStop = 0x00000080,
NoClipping = 0x00000100,
ExternalLeading = 0x00000200,
NoPrefix = 0x00000800,
Internal = 0x00001000,
PathEllipsis = 0x00004000,
EndEllipsis = 0x00008000,
WordEllipsis = 0x00040000,
ModifyString = 0x00010000,
RightToLeft = 0x00020000,
NoFullWidthCharacterBreak = 0x00080000,
HidePrefix = 0x00100000,
PrefixOnly = 0x00200000,
NoPadding = 0x10000000,
}
[StructLayout(LayoutKind.Sequential)]
public struct Dimension
{
public int Left, Top, Right, Bottom;
public Dimension(int left, int top, int right, int bottom)
{
this.Left = left;
this.Right = right;
this.Top = top;
this.Bottom = bottom;
}
public Dimension(Rectangle r)
{
this.Left = r.Left;
this.Top = r.Top;
this.Bottom = r.Bottom;
this.Right = r.Right;
}
public static implicit operator Rectangle(Dimension rc)
{
return Rectangle.FromLTRB(rc.Left, rc.Top, rc.Right, rc.Bottom);
}
public static implicit operator Dimension(Rectangle rc)
{
return new Dimension(rc);
}
public static Dimension Default
{
get { return new Dimension(0, 0, 1, 1); }
}
}
So to know whether a particular line is wrapped or not, you would call the function like this:
Dimension rc = new Dimension(0,0,2,2);
var flag = DrawTextFlags.CalculateArea | DrawTextFlags.TextBoxControl | DrawTextFlags.WordBreak;
DrawText(hdc, line, line.length, ref rc, (int)flag);
Now if height of rc you get after executing this function is greater then your font height or tmHeight if you use TextMetric (that is what minimum required for a line to fit vertically) you can safely assume your line is wrapped.
Apart from this,
You can use the following function as an alternative approach:
static extern bool GetTextExtentExPoint(IntPtr hDc, string str, int nLength,
int nMaxExtent, int[] lpnFit, int[] alpDx, ref Size size);
I'm trying to get a window handle from point using p/invoke, where window is a form, and not any child control. I have a simple interface where X and Y are entered by user, and then Find button is used to call win32 and get necessary information. My problem is that window is not necessarily a form, it can also be a control. See below screenshot - at (100,100) happened to be Notepad's text area with "StackOverflow" written in it. As a result, Found window shows "StackOverflow".
Is there any way I can restrict window type to be a Form? Expected result is "Untitled - Notepad" for below test case. Alternatively, is there a way to ask another application's control to provide its form's handle? In short, I need to get form's title from (x,y) point. Button click handler code:
private void btn_Find_Click(object sender, EventArgs e)
{
int xPoint = Convert.ToInt32(txt_WindowX.Text);
int yPoint = Convert.ToInt32(txt_WindowY.Text);
IntPtr hWnd = Win32.GetWindowHandleFromPoint(xPoint, yPoint);
txt_FormTitle.Text = Win32.GetWindowTitle(hWnd);
}
Major portion of Win32 class comes from this answer:
Tergiver's answer to "C# - unable to read another application's caption"
Full Win32 class code is provided below:
public class Win32
{
/// <summary>
///
/// </summary>
/// <param name="hwnd"></param>
/// <remarks>https://stackoverflow.com/questions/4604023/unable-to-read-another-applications-caption</remarks>
public static string GetWindowTitle(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
throw new ArgumentNullException("hwnd");
int length = Win32.SendMessageGetTextLength(hwnd, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (length > 0 && length < int.MaxValue)
{
length++; // room for EOS terminator
StringBuilder sb = new StringBuilder(length);
Win32.SendMessageGetText(hwnd, WM_GETTEXT, (IntPtr)sb.Capacity, sb);
return sb.ToString();
}
return String.Empty;
}
public static IntPtr GetWindowHandleFromPoint(int x, int y)
{
var point = new Point(x, y);
return Win32.WindowFromPoint(point);
}
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(Point p);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, [Out] StringBuilder lParam);
}
You need to locate the top level window. Start from the window that GetWindowHandleFromPoint yielded. Then call GetParent repeatedly until you find a window with no parent. That window with no parent is the top level window that you are looking for.
It seems that all notes under StikyNot.exe are single exes instead of multiple. Also that means the coordinates of its location are always 0, 0, 0, 0. Is there a way to move it around? I tried using Win32's MoveWindow function without success.
Here's an example of how to iterate through all the Sticky Note windows and move each of them. (Error checking has been removed for brevity. Also, be sure to read the note at the end for some comments on this implementation.)
First, we have to define the RECT struct.
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public RECT(int l, int t, int r, int b)
{
Left = l;
Top = t;
Right = r;
Bottom = b;
}
public int Left;
public int Top;
public int Right;
public int Bottom;
}
Then some key p/Invokes. We'll need FindWindowExW to locate the window with the correct window class for a sticky note. We also need GetWindowRect, so we can figure out the size of the window, so we only move it, rather than a move and resize. Finally, we need SetWindowPos which is pretty self-explanatory.
[DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndAfter,
string lpszClass, string lpszWindow);
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
int Y, int cx, int cy, uint uFlags);
Finally our algorithm.
IntPtr hWnd = FindWindowExW(IntPtr.Zero, IntPtr.Zero, "Sticky_Notes_Note_Window", null);
if (hWnd == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
if (error > 0) throw new Win32Exception(error);
else return;
}
IntPtr first = hWnd;
int currentX = 0;
while (hWnd != IntPtr.Zero)
{
RECT r;
bool result = GetWindowRect(hWnd, out r);
if (!result)
{
int error = Marshal.GetLastWin32Error();
if (error > 0) throw new Win32Exception(error);
else return;
}
result = SetWindowPos(hWnd,
IntPtr.Zero,
currentX,
0,
r.Right - r.Left,
r.Bottom - r.Top,
0);
if (!result)
{
int error = Marshal.GetLastWin32Error();
if (error > 0) throw new Win32Exception(error);
else return;
}
currentX += r.Right - r.Left;
hWnd = FindWindowExW(IntPtr.Zero, hWnd, "Sticky_Notes_Note_Window", null);
if (hWnd == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
if (error > 0) throw new Win32Exception(error);
else return;
}
if (hWnd == first) hWnd = IntPtr.Zero;
}
How does it work? First, using a tool like Spy++, I found the window class. From the window's property sheet, we can see that the window's class name is Sticky_Notes_Note_Window.
With the information from Spy++, the first window handle is obtained using FindWindowExW. This value is cached so that it can be determined when we've finished iterating all the windows. Inside the loop, we move the window, then use FindWindowEx to again locate the next window with the same class, if none are found, hWnd will be IntPtr.Zero aka NULL. We also have to check whether we are back to the start of our iteration. (If the notes are wider than the screen, they will spill off to the right. Wrapping the notes to another row is left as an exercise)
The issue with this implementation is that, if the first sticky note is closed, before we have iterated through all of them, then the program will never terminate. It would be better to keep track of all the windows that have been seen, and if any is seen again, then all have been enumerated.
An alternative method would be to use EnumWindows and inside the callback call GetClassName to see if it's a Sticky_Notes_Note_Window, and then act appropriately. The method above required less work, so it's the method I chose.
References:
RECT Structure
FindWindowEx(W)
GetWindowRect
SetWindowPos
Edit: Added error checking based on #DavidHeffernan's comment. Also added clarification about how I found the Window class name.
In my application's form, I have two RichTextBox objects. They will both always have the same number of lines of text. I would like to "synchronize" the vertical scrolling between these two, so that when the user changes the vertical scroll position on one, the other scrolls the same amount. How might I go about doing this?
Thanks Jay for your answer; after some more searching I also found the method described here. I'll outline it below for anyone else interested.
First, declare the following enums:
public enum ScrollBarType : uint {
SbHorz = 0,
SbVert = 1,
SbCtl = 2,
SbBoth = 3
}
public enum Message : uint {
WM_VSCROLL = 0x0115
}
public enum ScrollBarCommands : uint {
SB_THUMBPOSITION = 4
}
Next, add external references to GetScrollPos and SendMessage.
[DllImport( "User32.dll" )]
public extern static int GetScrollPos( IntPtr hWnd, int nBar );
[DllImport( "User32.dll" )]
public extern static int SendMessage( IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam );
Finally, add an event handler for the VScroll event of the appropriate RichTextBox:
private void myRichTextBox1_VScroll( object sender, EventArgs e )
{
int nPos = GetScrollPos( richTextBox1.Handle, (int)ScrollBarType.SbVert );
nPos <<= 16;
uint wParam = (uint)ScrollBarCommands.SB_THUMBPOSITION | (uint)nPos;
SendMessage( richTextBox2.Handle, (int)Message.WM_VSCROLL, new IntPtr( wParam ), new IntPtr( 0 ) );
}
In this case, richTextBox2's vertical scroll position will be synchronized with richTextBox1.
I did this for a small project a while ago, and here's the simplist solution I found.
Create a new control by subclassing RichTextBox:
public class SynchronizedScrollRichTextBox : System.Windows.Forms.RichTextBox
{
public event vScrollEventHandler vScroll;
public delegate void vScrollEventHandler(System.Windows.Forms.Message message);
public const int WM_VSCROLL = 0x115;
protected override void WndProc(ref System.Windows.Forms.Message msg) {
if (msg.Msg == WM_VSCROLL) {
if (vScroll != null) {
vScroll(msg);
}
}
base.WndProc(ref msg);
}
public void PubWndProc(ref System.Windows.Forms.Message msg) {
base.WndProc(ref msg);
}
}
Add the new control to your form and for each control explicitly notify the other instances of the control that its vScroll position has changed. Somthing like this:
private void scrollSyncTxtBox1_vScroll(Message msg) {
msg.HWnd = scrollSyncTxtBox2.Handle;
scrollSyncTxtBox2.PubWndProc(ref msg);
}
I think this code has problems if all the 'linked' controls don't have the same number of displayable lines.
[Visual Studio C# 2010 Express, v10.0.30319 on a Windows 7 64bit installation]
I've used Donut's solution posted above, but found a problem when scrolling to the end of RichTextBoxes that contain many lines.
If the result of GetScrollPos() is >0x7FFF then when nPos is shifted, the top bit is set. The creation of the IntPtr with the resulting wParam variable will then fail with an OverflowException. You can easily test this with the following (the second line will fail):
IntPtr ip = new IntPtr(0x7FFF0000);
IntPtr ip2 = new IntPtr(0x80000000);
A version of SendMessage() that uses UIntPtr would appear to be a solution, but I couldn't get that to work. So, I've use the following:
[DllImport("User32.dll")]
public extern static int SendMessage(IntPtr hWnd, uint msg, UInt32 wParam, UInt32 lParam);
This should be good up to 0xffff, but would fail after that. I've not yet experienced a >0xffff result from GetScrollPos(), and assume that User32.dll is unlikely to have a 64bit version of SendCommand(), but any solutions to that problem would be greatly appreciated.
const int WM_USER = 0x400;
const int EM_GETSCROLLPOS = WM_USER + 221;
const int EM_SETSCROLLPOS = WM_USER + 222;
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref Point lParam);
private void RichTextBox1_VScroll(object sender, EventArgs e)
{
Point pt;
SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, ref pt);
SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, ref pt);
}
private void RichTextBox2_VScroll(object sender, EventArgs e)
{
Point pt;
SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, ref pt);
SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, ref pt);
}
A variation of Jay's subclass approach can be found in Joseph Kingry's answer here: Synchronizing Multiline Textbox Positions in C#.
Joseph's approach also subclasses but doesn't require a _VScroll event handler. I used that approach to do a 3-way bind between 3 boxes and added WM_HSCROLL.
#Sudhakar MuthuKrishnan's answer needs some fixes, but works. Thanks!
First GetScrollPos which rised event and then set scroll position for others.
private void RichTextBox1_VScroll(object sender, EventArgs e)
{
Point pt = new Point();
SendMessage(RichTextBox1.Handle, EM_GETSCROLLPOS, 0, ref pt);
SendMessage(RichTextBox2.Handle, EM_SETSCROLLPOS, 0, ref pt);
}
private void RichTextBox2_VScroll(object sender, EventArgs e)
{
Point pt = new Point();
SendMessage(RichTextBox2.Handle, EM_GETSCROLLPOS, 0, ref pt);
SendMessage(RichTextBox1.Handle, EM_SETSCROLLPOS, 0, ref pt);
}