Notifications
Clear all

[Closed] Dotnet ListBox flickering

Hi all,

This is my first real attempt at using dotnet in maxscript and I’ve managed with some help to hack my way through some of it but now I’m stumped. I have a listbox which I need to update with the timeslider so I’ve registered a .refresh function to the registerTmeCallback. So far so good but now I have a problem with it flickering. I changed my original code to draw to a temporary bitmap and then copy the result to the control. However I’ve found that I actually need to intercept the WM_ERASEBKGND windows message to keep it from redrawing the background everytime it refreshes. Here is a simple script which shows the problem pretty well.


  utility test2 "test DotNetListBox" width:162 height:200
  (
  	
  	dotNetControl mylistBox "System.Windows.Forms.ListBox" width:140 height:100 
  	
  	local mycolor, mybrush
  	
  	fn fillInListBox lb =
  	(
  		for i =  1 to 10 do
  		(
  			li = dotNetObject "System.Windows.Forms.Label"
  			li.text = "text " + (i as string)
  			lb.Items.add li
  		)
  	)	
  		
  	fn initListBox lb =
  	(
  		lb.multiColumn = false
  		lb.drawmode = lb.drawmode.OwnerDrawFixed
  		myColor = (dotNetClass "System.Drawing.Color").fromArgb 255 0 0
  		lb.backColor = mycolor
  		myColor = (dotNetClass "System.Drawing.Color").fromArgb 0 0 0			
  		myBrush = dotNetObject "System.Drawing.SolidBrush" myColor
  		fillinListBox lb
  	)
  
  	
  	on mylistbox DrawItem  e do
  	(
  		-- Create a temporary bitmap to draw the listbox into
  		tmpBitmap = dotNetobject "system.drawing.bitmap" e.bounds.width e.bounds.height 
  		tmpGraphics = dotNetClass "system.drawing.graphics"
  		g = tmpGraphics.fromImage tmpBitmap
  		
  		tmpPointf = dotnetobject "system.drawing.pointf" e.bounds.height 0
  		tmpFont = dotnetobject "system.drawing.font" "Arial" 8
  	
  		if dotnet.CompareEnums (dotnet.combineEnums e.State (dotnetclass "system.windows.forms.DrawItemState").Selected)  (dotnetclass "system.windows.forms.DrawItemState").Selected then
  		 (
  			 myBrush.color = (dotnetclass "system.drawing.color").fromARGB 255 255 255
  			 g.fillrectangle myBrush 0 0 e.bounds.width e.bounds.height
  			 myBrush.color = (dotnetclass "system.drawing.color").fromARGB 0 0 0 
  		 )
  		 else
  		 ( 
  			 myBrush.color = (dotnetclass "system.drawing.color").fromARGB 0 0 180
  			 
  			g.fillrectangle myBrush 0 0 e.bounds.width e.bounds.height
  
  			 myBrush.color = (dotnetclass "system.drawing.color").fromARGB 255 255 255
  		 )			
  
  		g.DrawString (myListBox.items.item(e.index)).text tmpFont  myBrush  tmpPointf 
  		
  		--write the temporary bitmap into the ListBox
  		e.graphics.drawimage tmpBitmap e.bounds.x e.bounds.y 
  	)
  
  	fn updateLB =
  	(
  		myListBox.refresh()		
  	)	
  
  	on test2 open do
  	(
  		initListBox myListBox
  		registerTimeCallback updateLB
  	)
  	on test2 close do
  	(
  		unregisterTimeCallback updateLB
  	)		
  )
  
  

I’ve found some reference in the forums to using the wndproc() method to catch the message and then just return it. I’m not really sure how to do this in maxscript though.

Another thought was to use the

.<System.Windows.Forms.PreProcessControlState>PreProcessControlMessage <System.Windows.Forms.Message&>msg

method to tell it to ignore the message but this one also has me stumped.

I also noticed that the Control Class has a SetStyle method that if you set to AllPaintingInWmPaint sets the control to ignore the WM_ERASEBKGND message but doesn’t seem to be availiable for the Listbox.

Any help would be greatly appreciated.

Thanks,
Rod

14 Replies

This looks pretty cool…I may need to steal it…but…

Could you use mylistBox.beginUpdate and mylistBox.EndUpdate??

This may buffer the updates until they have finished…I use them on listboxes when I need to update their contents…

I don’t know if this will help, because you have your own drawing routine

Hey Rusty,

Wish I could take more credit for it but got a lot of help with the dotnet stuff. Whats really cool about it you can draw whatever you want in the list rectangles. In my actual script I draw circles before the text that will change colors depending on certain conditions. Pretty sweet except for the flickering.

Anyway I tried the beginupdate and endupdate but no luck. Looks like they are basically used when populating a large number of listbox items.

Rod

Oh well, worth a try…

I did a VERY quick search and I don’t know if it will help at all, but you might want to consider setting the “DoubleBuffered” property to true ( I have no idea what it is set to normally ), but this has been some of the recommendations I’ve read PLUS, scanning through the docs, the DoubleBuffered property says:

Gets or sets a value indicating whether this control should redraw its surface using a secondary buffer to reduce or prevent flicker.(Inherited from Control.)
I only hope this helps!!

Shane

This is exactly the problem Rod is having. But you can’t set this DoubleBuffered property in MAXScript; it’s a protected member.
So Rod has to develop it’s own custom ListBox in C# or VB.NET and use it in MAXScript. I thinks it’s the best solution.

3 Replies
(@rustyknight)
Joined: 11 months ago

Posts: 0

Hi Ypuech!

I can’t believe I missed that , must be getting late…

I’m curious, do you know if it is possible to globally enable double buffering for the dotnet system? I know there are ways to do this in Java, but my dotnet is that extensive.

Shane

(@ypuech)
Joined: 11 months ago

Posts: 0

As far as I know I’ve never heard about that with .NET.

I think it’s better to develop a custom control because the code of the control drawing will be faster, it will be easy to reuse it in another script and it will be no flickering!
I’m pretty sure Rod is having the flickering problem because of the DoubleBuffered state; I had such behaviour in a custom control where this property wasn’t enabled.

(@rustyknight)
Joined: 11 months ago

Posts: 0

Just a thought

I think it’s better to develop a custom control because the code of the control drawing will be faster, it will be easy to reuse it in another script and it will be no flickering!
I’m pretty sure Rod is having the flickering problem because of the DoubleBuffered state; I had such behaviour in a custom control where this property wasn’t enabled.
I’m prefer to try all the “out of the box” solutions I can first, and I agree, I think the double buffer is the issue. It’s just a shame (and annoying) that it can’t be changed without extending the class…seems somewhat stupid to me…but that’s just me

Shane

Hey guys,

We were just coming to the realization that we might have to build a custom control. Unfortunately neither of us have ventured into that area yet. I did find some source code for a colorlistbox from http://www.dnzone.com/showDetail.asp?TypeId=2&NewsId=1328 which I can probably modify for our purposes though.

I did have some problems figuring out how to access a custom control in maxscript but thanks to your “helloworld.hello” example on your webpage, Yannick, I was able to figure that out so thanks for that. Be nice to get that into the Maxscript help. Right now this all still seems like blackmagic to me.

Thanks again,
Rod

1 Reply
(@ypuech)
Joined: 11 months ago

Posts: 0

Happy to see my article can help people using .NET with MAXScript!

In fact, if you want to override such methods (DrawItem() for example), it’s better to do that in C# or VB.NET. Custom controls in .NET are so easy and cool to develop.
Doing that in MAXScript is a hack for me ;).

1 Reply
(@rustyknight)
Joined: 11 months ago

Posts: 0

No argument here, but it’s hard enough keep the studios scripts in line without adding more to it

Yannick,

So how do you go about debugging these controls. I’m messing with that Colorlistbox that I downloaded from the link above and can do certain things with it but if I try to select an item either with the mouse or by script I get a crash. Not sure why since it seems to work fine on a form. I’m using c# express which apparently doesn’t let me attach to a process or add JIT debugging code. Is this correct.

Maybe I’ll try and run it on a dotnet form in max and see what happens.

Rod

Ok I had one of those wake up in the middle of the night moments of clarity. After hacking away at that colorlistbox code yesterday I just realized all I have to do is make a new custom listbox control that just captures the WM_ERASEBKGND message and replace the maxscript listbox with it and done. I’m still doing my drawing in Maxscript though. Maybe down the road I’ll do it in the control but will have to buy a C# book before that.

I do have one more question for Yannick. How do you usually deploy your .dll’s. Do you put them in a windows directory, max directory, keep them with the scripts. Just curious.

Anyway here is the test program again using the new control and also the C# code in case it might help someone else down the road. I have the text changing colors as the time slider moves.

Thanks to all,
Rod


 assemblyfilename = "C:\\ListBox.dll"
 dotnet.loadAssembly assemblyfilename
 
 utility test2 "test DotNetListBox" width:162 height:200
 (
 	
 	dotNetControl mylistBox "MyNameSpace.CustListBox" width:140 height:100 
 	
 	local mycolor, mybrush
 	
 	fn fillInListBox lb =
 	(
 		for i =  1 to 10 do
 		(
 			li = dotNetObject "System.Windows.Forms.Label"
 			li.text = "text " + (i as string)
 			lb.Items.add li
 		)
 	)	
 		
 	fn initListBox lb =
 	(
 		lb.multiColumn = false
 		lb.drawmode = lb.drawmode.OwnerDrawFixed
 		myColor = (dotNetClass "System.Drawing.Color").fromArgb 255 0 0
 		lb.backColor = mycolor
 		myColor = (dotNetClass "System.Drawing.Color").fromArgb 0 0 0			
 		myBrush = dotNetObject "System.Drawing.SolidBrush" myColor
 		fillinListBox lb
 	)
 
 	
 	on mylistbox DrawItem  e do
 	(
 		-- Create a temporary bitmap to draw the listbox into
 		tmpBitmap = dotNetobject "system.drawing.bitmap" e.bounds.width e.bounds.height --tmpPixelFmt
 		tmpGraphics = dotNetClass "system.drawing.graphics"
 		g = tmpGraphics.fromImage tmpBitmap
 		
 		ttt = (currentTime as integer)/TicksPerFrame
 		if ttt > 255 then ttt = 255
 		
 		tmpPointf = dotnetobject "system.drawing.pointf" e.bounds.height 0
 		tmpFont = dotnetobject "system.drawing.font" "Arial" 8
 	
 		if dotnet.CompareEnums (dotnet.combineEnums e.State (dotnetclass "system.windows.forms.DrawItemState").Selected)  (dotnetclass "system.windows.forms.DrawItemState").Selected then
 		 (
 			 myBrush.color = (dotnetclass "system.drawing.color").fromARGB 255 255 255
 			 g.fillrectangle myBrush 0 0 e.bounds.width e.bounds.height
 			 myBrush.color = (dotnetclass "system.drawing.color").fromARGB ttt 0 0 
 		 )
 		 else
 		 ( 
 			 myBrush.color = (dotnetclass "system.drawing.color").fromARGB 0 0 180
 			 
 			g.fillrectangle myBrush 0 0 e.bounds.width e.bounds.height
 
 			 myBrush.color = (dotnetclass "system.drawing.color").fromARGB 255 255 255
 		 )			
 
 		g.DrawString (myListBox.items.item(e.index)).text tmpFont  myBrush  tmpPointf 
 		
 		--write the temporary bitmap into the ListBox
 		e.graphics.drawimage tmpBitmap e.bounds.x e.bounds.y 
 	)
 
 	fn updateLB =
 	(
 		myListBox.refresh()		
 	)	
 
 	on test2 open do
 	(
 		initListBox myListBox
 		registerTimeCallback updateLB
 	)
 	on test2 close do
 	(
 		unregisterTimeCallback updateLB
 	)		
 )
 

 using System;
 using System.Windows.Forms;
 using System.Drawing;
 using System.Runtime.InteropServices;
 using System.Collections;
 using System.ComponentModel;
 using System.Data;
 
 namespace MyNameSpace
 {
 	public class CustListBox : ListBox
 	{
 		[DllImport("user32.dll", CharSet = CharSet.Auto)]
 		private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
 
 		private const int WM_ERASEBKGND = 0x0014;		
 		
 		private bool _BlockEraseBackGnd = true;
 
 		protected override void WndProc(ref Message m)
 		{
 			switch (m.Msg)
 			{
 				case (int)WM_ERASEBKGND:
 
 					if (_BlockEraseBackGnd)
 					{
 						return;
 					}
 
 					break;
 		   }
 
 			base.WndProc(ref m);
 
 		}
 	}
 }
 

Yes, the right place for the drawing stuff is in C# as what you’re doing is a hack ; you don’t need to override WndProc(): just set DoubleBuffered property to true (in designer custom control properties).

Script directory is the best solution for me because if you have Max 9 and Max 2008 installed you’ll only have one dll to copy for the two versions.