Notifications
Clear all

[Closed] How to setup LB_GETTEXT??

Hi,

i want to use the windows.sendmessage lb_gettext to get a string of a 3dsmax listbox item. But i just get the string length not the string. I think it has something to do with the lParam value. Cheers…

15 Replies

Yes, you’re right it has to do with the lParam value.
windows.SendMessage does not work with LB_GETTEXT because it needs to handle a string buffer to get the text.

With this function you cannot pass a string parameter. windows.SendMessage works only with “simple” messages having integer parameters:

windows.SendMessage <int HWND> <int message> <int messageParameter1> <int messageParameter2>

If you’re under 3ds Max 2011, take a look at MaxCustomControls.Win32API.SendMessage():

[static]<System.Int32>MaxCustomControls.Win32API.SendMessage <System.IntPtr>hWnd <System.UInt32>Msg <System.UIntPtr>wParam <System.UIntPtr>lParam

I never used this function but I think it would be possible to call this function with LB_GETTEXT and get a .NET string in the lParam.

2 Replies
(@denist)
Joined: 10 months ago

Posts: 0

we can use MaxCustomControls.Win32API.SendMessage in MAX 2010, but I don’t know how can it help in this case.

(@ypuech)
Joined: 10 months ago

Posts: 0

I think it can help because we pass <System.UIntPtr>lParam. A UIntPtr is a handle, so if you build a .NET string and pass it to the function I suspect we can get the returned text value in lParam. But I don’t have time to test that.

Georgios, can you post your script so we have a good base to test the different solutions ?

Thanx for the quick answer…

i don’t know how to do it without c# and .NET help. here is my solution:


global ListBoxAssembly
fn CreateListBoxAssembly forceRecompile:on =
(
	if forceRecompile or not iskindof ::ListBoxAssembly dotnetobject or (::ListBoxAssembly.GetType()).name != "Assembly" do
	(
		
source = ""
source += "using System;
"
source += "using System.Text;
"
source += "using System.Runtime.InteropServices;
"
source += "public class ListBoxOps
"
source += "{
"
source += "	[DllImport(\"user32.dll\")]
"
source += "	public static extern int SendMessage(IntPtr window, int message, int wParam, IntPtr lParam);
"
source += "	[DllImport(\"user32.dll\")]
"
source += "	public static extern int SendMessage(IntPtr hWnd, int wMsg, int wParam, StringBuilder lParam);
"
source += "		private const int LB_GETTEXT		= 0x0189;
"
source += "		private const int LB_GETTEXTLEN		= 0x018A;
"
source += "	public int GetTextLength(IntPtr hWnd, int item)
"
source += "	{
"
source += "		return (SendMessage(hWnd, LB_GETTEXTLEN, item, IntPtr.Zero));
"
source += "	}
"
source += "	public string GetText(IntPtr hWnd, int item)
"
source += "	{
"
source += "		int len = GetTextLength(hWnd, item);
"
source += "		StringBuilder text = new StringBuilder(len+1);
"
source += "		SendMessage(hWnd, LB_GETTEXT, item, text);
"
source += "		return text.ToString();
"
source += "	}
"
source += "}
"

		csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
		compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"

		compilerParams.ReferencedAssemblies.Add("System.dll");

		compilerParams.GenerateInMemory = true
		compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
		
		if (compilerResults.Errors.Count > 0 ) then
		(
			errs = stringstream ""
			for i = 0 to (compilerResults.Errors.Count-1) do
			(
				err = compilerResults.Errors.Item[i]
				format "Error:% Line:% Column:% %
" err.ErrorNumber err.Line err.Column err.ErrorText to:errs 
			)
			MessageBox (errs as string) title: "Errors encountered while compiling C# code"
			format "%
" errs
			undefined
		)
		else
		(
			ListBoxAssembly = compilerResults.CompiledAssembly
			ListBoxAssembly.CreateInstance "ListBoxOps"
		)
	)
)
global ListBoxOps = CreateListBoxAssembly forceRecompile:on

to use:


ListBoxOps.GetText (dotnetobject "System.IntPtr" <listbox handle or hwnd>) <item's index>

You don’t need to use the MaxCustomControl.Win32API (UIntPtr conversions blech!) , and you can avoid calling a C# snippet if you marshal the string buffer yourself:

fn GetLabelString listBoxHWnd itemNum = 
(
	LB_GETTEXT = 0x0189
	LB_GETTEXTLEN = 0x018A
	
	reqsize = windows.SendMessage listBoxHWnd LB_GETTEXTLEN itemNum 0

	-- Create an unmanaged buffer large enough to hold the string
	Marshal = dotnetclass "System.Runtime.InteropServices.Marshal"		
	unmanagedBuff = Marshal.AllocHGlobal (reqsize+1) asdotnetobject:true
	
	-- Wrap the logic that fiddles with the unmanaged pointer in a try/catch so that we are sure to release the handle on failure
	try (
		
		insize = windows.SendMessage listBoxHWnd LB_GETTEXT itemNum (unmanagedBuff.ToInt64())
		if insize != reqsize then throw "We didn't read back as many characters as we should have!"
			
		-- Convert the unmanaged buffer all the way back into a maxscript string
		result = (Marshal.PtrToStringAnsi unmanagedBuff asdotnetobject:true).ToString()
		
	)
	catch
	( 
		print ("Error sending windows message: "+(getCurrentException()))
		result = undefined
	)

	Marshal.FreeHGlobal unmanagedBuff

	result

)

fn TestIt =
(
	rollout ListBoxTester "Sample List Box Tester" 

	(
		listbox items "Objects:" items:(for o = 1 to 5 collect ("Item #"+ (o as string)))
	)

	createDialog ListBoxTester
	
	-- Search the controls on the form for a ListBox
	listboxHWND = for ctrl in (windows.getChildrenHWND (windows.getChildHWND 0 ListBoxTester.Title)[1]) 
		where ctrl[4] == "ListBox" 
		do exit with ctrl[1]
	
	for i = 0 to 4 do
	(
		format "item % is %
" i (GetLabelString listboxHWND i)
	)
)

.biddle

absolutely right! finally i came to exactly same solution. only difference is how to get allocated memory size. in my variant it was:


Marshal.SystemDefaultCharSize*(len+1)

but honestly i don’t understand why it’s recommended to increase the buffer’s size by 1…

So.

Does windows.SendMessage call SendMessageA or SendMessageW under the hood?

From the source it looks like it depends on what it (maxscrpt.dll) was compiled for.

It would appear that on my system it uses the ANSI flavour (SendMessageA) as doubling the size of the buffer then calling Marshal.PtrToStringAuto or Marshal.PtrToStringUni on the result returns an empty string.

This is despite the fact that my system has a Marshal.SystemDefaultCharSize of 2.

1 Reply
(@denist)
Joined: 10 months ago

Posts: 0

i’m probably paranoid by using unmanager .net code in max but i always trying to check the size of a type before using it in 32/64 bits version.

ps. i’ve thought that max always calls SendMessageA. i’ve never played with any other culture than en-US

Thanks for the tip Mike.

The size is len+1 to include the null character (’\0’) in the allocation, the documentation of LB_GETTEXTLEN states:

The return value is the length of the string, in TCHARs, excluding the terminating null character.

2 Replies
(@denist)
Joined: 10 months ago

Posts: 0

i know that. the question is why do we need to read the null terminator in our case?

(@ypuech)
Joined: 10 months ago

Posts: 0

Old Win32 API weirdness ?

You guys are awesome!! I give it a try and i report back …

I used biddle’s method and it worked great. Again thanx guys for the fast help.

Page 1 / 2