XMLLayoutPanel - A Custom Dotnet Control for 3dsMax
XML LayoutPanel - Another Custom control for 3dsMax
Please note - this tutorial was written pre max 2010 which includes the method dotnet.setlifetimecontrol. This resolves the global dotnet handler issues mentioned at the start.
Recently,I was using a FlowLayoutPanel to provide a UI to load XML presets on a
Character Pose system. The FlowlayoutPanel is a really useful UI
component available in the DotNet toolbox. It is basically a container
for you to place other controls into, and as a result, is perfect for
dynamic interfaces. The problem in Max is that DotNet event handlers
need to be specified in GLOBAL scope in order for them to register.
This creates issues when using them in as part of a custom attribute or
modifier plugin, the very thing (regrettably) that I wanted to do.
The way around this to build your own class that inherits
the FlowLayoutPanel and adds the handlers outside of Max. Here is the control in the interface -
The control
to the right is a node storage system for all of the facial bones, and
the left hand interface is the custom FlowLayoutPanel. Clicking a
button gives access to the XML file where the stored transforms for
various facial expressions are stored.
The Panel has a single method, populate <path string> ,which
searches the folder and adds a button to the layout pane for each file
it finds. It will also look for a related image. This is how I wanted
to show the presets. If there is a png file with the same name as the
file in the folder, it will put that on the button. If that isn’t
present, it checks if there is a file called “Default.png”. This was so
I could put a rough icon for the storage type if I wanted. If not, the
default XML icon is used. There is a compact (pictured) and larger icon
and it depends on the button size if the larger one gets used. Take a
look at the class diagram below for more information on other
properties you can set -
Setting up the control properties in 3dsMax
Here are some examples of setting properties using this class within 3dsMax - Note the namespace of the control -
dotNetControl XMLPanel “LoneRobot.UI.Character.XMLLayoutPanel” pos:[0,186] width:161 height:336
Also note the use of the + sign when using a custom enumeration in 3dsMax -
XMLPanel.thumbsize = dotNetObject “system.drawing.size” 140 30
XMLPanel.selectedborderwidth = 2
XMLPanel.border = true
XMLPanel.textalign=(dotnetclass “System.Drawing.ContentAlignment”).middleright
XMLPanel.imagealign=(dotnetclass “System.Drawing.ContentAlignment”).middleleft
XMLPanel.sortby=(dotnetclass “LoneRobot.UI.Character.XMLLayoutPanel+SortByOptions”).filename
XMLPanel.sortby=(dotnetclass “LoneRobot.UI.Character.XMLLayoutPanel+SortByOptions”).length
XMLPanel.sortby=(dotnetclass”LoneRobot.UI.Character.XMLLayoutPanel+SortByOptions”).lastwritetime
XMLPanel.populate DirectoryPath
Top of the Class
The more observant may have noticed that in the event section of the class diagram was an event called Picked. If you did, then award yourself a biscuit. Picked isn’t a standard dotnet event for the FlowLayoutPanel. It is in XMLLayoutPanel though, so what is this and how does it work?
You may already be familiar with
the concept of Object Orientated Programming, or OOP. It’s kind of
fundamental to working with DotNet, and getting a handle on it will
really improve how you approach a task. If you’re not 100% straight on
what all this OOP malarkey is, I’ll try to give a brief explanation.
The DotNet framework is a big orchard of classes. Almost everything
(with some exceptions naturally!) is a class in dotnet. One way to look
at it is that if a class was a banana, the dotnetclass would be a blueprint for what a banana is. A dotnetobject is the actual banana. And there are multiple bananas.
Buttons are classes, Forms are classes, BackgroundWorkers are classes. Each time you use a dotnetobject, you are using an instance of that class.
Now this is all very
straightforward so far, and is the sort of thing that really impresses
girls when you talk to them about it.
Let’s go on by looking at what happens when you press a button in dotnet. In order to handle the event, you have an eventarg
attached to it. This has relevant information pertaining to the button
being pressed, like the x and y position of the mouse etc. However, on
an XMLLayoutPanel, that sort of information isn’t really useful. What
you need is something that will convey the data you need after the
event is fired.
You’ll remember at the start that I talked about inheriting classes like the FlowLayoutPanel to make the XMLLayoutPanel. Where it gets more interesting is when you realize that the eventarg is also a class. This means you can inherit them also, and change the functionality to something you want to use.
For the XMLLayoutPanel, all that you really need is the path and filename to the XML file you have picked. Since the system.eventarg
tied to a button press doesn’t have that kind of information in it, you
can write a custom eventarg to provide it. Once you have defined the
event name in Visual Studio, you will need to write the event handler
class. Here is the setup of this in VB - don’t worry if it makes no
sense, i’ll try to explain the process of what is going on.
Public Class XMLPickedEventArgs
Inherits EventArgs
Private _fileName As String
Public Property FileName() As String
Get
Return _fileName
End Get
Set(ByVal value As String)
_fileName = value
End Set
End Property
Public Class XMLPickedEventArgs
Public Sub New(ByRef control As XMLLayoutPanel, ByVal fileName As String)
control.CurrentItem = fileName
Me.FileName = fileName
End Sub
End Class
XMLPickedEventArgs is created with two constructors, a reference to me ,the base control itself (not the sender in this case, which would be the button)
and the Tag property of the button, which is the string path of the XML
file. Now that this object has been instantiated, it means the XML
filename has been stored in the eventargs filename property, and it can
now be passed to raise the event.
If you look at this class in
Visual studio, you will see that the control has a new custom
registered event in the properties window that isn’t part of the
FlowlayoutPanel -
Even better, Intellisense tells us
the arguments in our custom class. As the class has just one property,
that is all we see. However, this is the only one we need! So from
this, we know that if we access the filename property of the eventarg,
we will get the path to the XML file we just clicked. This load string
can then be handled in max and passed to whatever XML function you wish.
You can do the same when using the
class in max by calling showproperties on the eventarg in max - Notice
the structure is the same, with sender being the custom control itself,
and args being the eventarg. Depending on what you pass from the
assembly you could do other things, for example , In the sender
variable you could have passed a reference to the button from the
event, meaning that you could use the event to change the state of it
(i.e. backcolor, border etc), or you could keep the sender as the
XMLPanel and add a property to the eventarg to refer to the button.
It’s really up to you.
on XMLPanel picked sender args do
(
showproperties args
)
listener output -
.FileName : <System.String>
.Empty : <System.EventArgs>, read-only, static
In short, with Visual Studio you
can build a control as broad or as specialized as you like. In this
case, I really wanted something for this particular task, and was able
to build it. The main advantage is it bypasses the event handler issues
of the 3dsMax command pane. Of course this approach could be done for a
variety of filetypes
I hope this has given you a
background as to why you would write your own event handler, and how to
use it in Max. Feel free to download the control and use it in your
projects. I’m using this control more and more in the utilities I
write; I think that it gives a good visual feel to the UI and allows
for dynamic layouts to be made where the layout logic is completely
handled by the control, not by you.
Which is nice.