You are currently viewing archive for June 2010
Posted By James Haywood

I recently came across a nasty issue that comes up when hosting a WPF control inside of a docked Max rollout.  The problem is that anything that causes the entire Max UI to redraw (like docking/undocking a rollout or changing the minimized state of the Graphite ribbon while it's docked) makes the WPF control completely disappear.  The elements of the control pop back into existence as you mouse over them, but of course it's a pain to have to keep waving your arrow over a section of the screen to get your controls back.

Fortunately, there's a fairly easy work around to keep it from disappearing.

So, execute the following script and you'll see a row of WPF buttons in a stackpanel docked to the left side of the screen like so...

WPF refresh issue 1

dotNet.loadAssembly @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\WindowsFormsIntegration.dll"

rollout wpfRefresh_RLT "" width:70 height:220
  dotNetControl eh "Integration.ElementHost" width:70 height:500 align:#left offset:[-14,-5]
   on wpfRefresh_RLT open do
     uc = dotNetObject "System.Windows.Controls.UserControl"
     eh.child = uc
     sp = dotNetObject "System.Windows.Controls.StackPanel"
     for i = 1 to 10 do
       btn = dotNetObject "System.Windows.Controls.Button"
       btn.height = 20
       btn.content = "button " + i as string
       sp.children.add btn
     uc.content = sp

createDialog wpfRefresh_RLT style:#(#style_toolwindow,#style_sysmenu,#style_resizing)
cui.RegisterDialogBar wpfRefresh_RLT style:#(#cui_floatable,#cui_dock_left,#cui_handles)
cui.DockDialogBar wpfRefresh_RLT #cui_dock_left

And now if you do something to make the UI redraw, like I mentioned above, you get this...

WPF refresh issue 2

Wave the mouse arrow over the controls and you'll see them reappear one at a time.

The way to keep this from happening is to catch the Paint event on the ElementHost and manually force it to redraw by calling it's Refresh() method.  That's easy enough, but it turns out that calling Refresh() causes the Paint event to trigger again.  So it gets into a nice little infinite loop and really messes with Max's overall performance.

So we need to add one more step, which is to use a boolean variable to make sure that it only refreshes once and then stops.

Add the following to the rollout to make these changes:

local doRefresh = true

on eh Paint args do

    if doRefresh then
       doRefresh = false
       doRefresh = true

Now the WPF control will redraw itself whenever it needs to.  The full script can be downloaded here.

Posted By James Haywood

Torus on the highway
Torus on the highway

I was recently directed to a great HDR toolset called Smart IBL.  It's basically a system for creating HDR images with a bunch of imbedded information that makes it really easy to set up a realistic rendering environment inside of any (well most) 3D apps.

The main application is called sIBL-GUI.  It's a browser for your sIBL set, and the interface for connecting to the 3D app.  It's really easy to set up with Max so that all you have to do is download or create some sIBL files, add them to the sIBL-GUI database, select one, and then click a button to have a light (or lights), a ground plane, and environment settings all created automatically.

The other tool, sIBL-Edit, is also useful, but the automated script for creating the Max environment isn't as up to date as the sIBL-GUI version.  So use GUI for 3D app interop, and Edit for modifying the embedded information.

I've been having a lot of fun with both of these lately, and have even contributed to the Max template in the GUI app.  So if you're doing any HDR rendering, give it a shot.


Smart IBL
IBL Tools
HDR Labs Home

Posted By James Haywood

Progress Bar, Part 3


This will be a short post.  I just wanted to finish up this series about hosting a WPF control inside of Max by showing how, as one final method, you can also use a WPF window instead of either the MaxForm window or the rollout.  The script code is basically the same as the MaxForm version, minus the ElementHost.  You don't need it because the WPF control is being loaded into a WPF window.  No host needed.

dotNet.loadAssembly @"<folder name>\ProgressBar\ProgressBar\bin\Debug\ProgressBar.dll"

bar = dotNetObject "ProgressBar.UserControl1"
frm = dotnetobject "System.Windows.Window"

frm.title = "Progress"
frm.width = bar.width + 16
frm.height = bar.height + 34
frm.windowStartupLocation = frm.windowStartupLocation.centerScreen
frm.windowStyle = frm.windowStyle.toolWindow

frm.content = bar

There are a couple of extra drawbacks though. One, you need to manage all of the foreground and background colors yourself if you want it to look like it's part of Max.  Same goes for assigning the parent window as Max itself, so that it will follow along with Max as you maximize/minimize or navigate away to different programs.  Otherwise it just kinds of floats off by itself and is easy to lose.

Finally, and probably most importantly, you need to set the "accelerators" state depending on whether the window has focus or not.  Accelerators are just another name for hotkeys.  And if don't manually manage this, the user won't be able to type into any text boxes in your tool.  So in the "GotFocus" event you'll want to turn hotkeys off with "enableAccelerators = false".  And then turn them back on in the "LostFocus" event.

So overall, unless you're planning on doing something that you can't do with a regular window, like changing the shape or something crazy like that, I don't see any benefit to using a WPF window over either of the other two methods.

Posted By James Haywood

Progress Bar, Part 2

To follow up on part 1, a nice side benefit to using the ElementHost for loading your WPF control into Max is that you can put it inside of a rollout.  This let's you do things like dock it to the Max viewport, add it inside of a scripted modifier or plugin, or any number of other Max-specific things you can do with a rollout.

And here is a sample of how to do that with the progress bar I posted in Part 1:


-- load the assemblies
dotNet.loadAssembly @"<folder name>\Projects\ProgressBar\ProgressBar\bin\Debug\ProgressBar.dll"
dotNet.loadAssembly @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\WindowsFormsIntegration.dll"

-- create the rollout
bar = dotNetObject "ProgressBar.UserControl1"
size = [bar.width,bar.height]
frm = rollout progressBar_RLT "Progress" width:size.x height:size.y
    dotNetControl eh "Integration.ElementHost" width:size.x height:size.y align:#left offset:[-14,-5]
    on progressBar_RLT open do
        eh.child = bar

createDialog progressBar_RLT
cui.RegisterDialogBar progressBar_RLT


Once you have it loaded, the commands used in the previous post to make it progress through the steps work just the same, since the WPF assembly itself hasn't changed.

You might notice that the background is different now.  That's because when using the MaxForm window, the background of the window is set automatically to whatever color Max itself is set to, and all the controls contained in that window inherit that color for their background.  But this doesn't happen witha a rollout.

So if you want to maintain a consistent look to your UI, you'll need to set the background of the WPF control manually.  The best way to do that would be to set up the "Loaded" event for the UserControl on the code side of your project to query the background color from Max and then apply it.  Lone Robot has some great information on how do to just that here.

Posted By James Haywood

Progress Bar


Some time ago I wanted to start getting my head around WPF, and especially how I could integrate tools made with WPF into Max.  So I decided to start with something simple, which was to take an existing script that displays a progress bar using standard Max controls and convert it to WPF.

I'm not going into the details of WPF programming because there's a ton of useful information on the interwebs already.  And if you want a good reference book, I can recommend Adam Nathan's "WPF Unleashed" (although you might want to wait for the new version).  But the WPF portion of this project is available for download at the bottom of this post.

A few things to note:

* The process I'm describing here only works with Max 2010 and up.

* If using VS 2010 to build your own projects, you'll need to build them using .Net 3.5 since Max doesn’t support 4.0 yet.  Do this by going to the project properties and changing the Target Framework to ".NET Framework 3.5".

* If you don’t want to load the VS project, the .dll you'll need is included with the project files in the “bin/debug” folder.

So the basic idea is that you create an ElementHost WinForms in either a rollout or MaxForm window, and then put the WPF control inside of that.

Here's the MaxScript portion needed to create the progress bar in Max...


-- load the progress bar library
dotNet.loadAssembly @"<folder location>\ProgressBar\ProgressBar\bin\Debug\ProgressBar.dll"

-- load the WPF Integration library needed to create the ElementHost control
dotNet.loadAssembly @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\WindowsFormsIntegration.dll"

-- define controls
bar = dotNetObject "ProgressBar.UserControl1"
frm = dotNetObject "MaxCustomControls.MaxForm"
eh = dotNetObject "System.Windows.Forms.Integration.ElementHost"

-- make controls fill the form window
eh.dock = eh.dock.fill

-- create form
frm.text = "Progress"
frm.width = bar.width + 16
frm.height = bar.height + 34
frm.startPosition = frm.startPosition.centerScreen
frm.formBorderStyle = frm.formBorderStyle.fixedToolWindow

-- add ElementHost, which holds the WPF control
frm.controls.Add eh
-- add the WPF control
eh.child = bar
-- open the window

-- set number of steps to use
bar.steps = 50

-- loop through the steps
for i = 1 to bar.steps do
    -- force the UI to update

    (dotNetClass "System.Windows.Forms.Application").DoEvents()
    -- increment one step and change label
    bar.ProgressLabel = "Thing " + i as string
    sleep .05



And here is the Visual Studio 2010 project.

< Edited to remove the UserControl, which I orginally had to hold the ElementHost, but isn't actually needed. >




User Profile
James Haywood
Seattle, WA


You have 545295 hits.