View Full Version : Mouse input window
Kenzor 12-03-2007, 02:20 PM Spinner Controls..
http://forums.cgsociety.org/attachment.php?attachmentid=120992&stc=1
I've created this face rigging tool (above) which allows the user to pose a bunch of bones, the dial up that pose using a spinner. The poses can be blended together and what you end with is something like a morph controller interface.
What I'd like to do is replace the spinners (which only accept input on 1 axis) to a mouse input window (which accepts inputs on 2 axis) (see below). Ie click and drag the dot around the white box. This would be a much nicer interface for animators, and would allow me to cram more poses onto one control.
This kind of windowed control does not exist in maxscript. I believe it's possible to do this kind of thing with activeX but I've no experience in creating them. Can anyone point me in the right direction i.e. could I create such a control using say flash and embed it a max dialog?
(I know i could create this kind of control using shapes and wire-parameters but I really want to keep all the animation controls in a floating dialog box..)
If people are interested theres a version of the script on my site.
Mouse input control
http://forums.cgsociety.org/attachment.php?attachmentid=120993&stc=1
|
|
MoonDoggie
12-03-2007, 03:47 PM
This is a great idea. I'm curious to see how it could be done.
focomoso
12-03-2007, 08:32 PM
I have no experience with dot net controls, so the answer may lie there, but you can do this in maxScript using a custom bitmap. Look at the how to... develop a bitmap painting tool. I've done something similar in the past, but abandoned it in favor of real max objects that float in front of the face. Yes, the spinners suck (we even tried scripted manipulators which were worse). The advantage to using boxes was that you could use the basic max functionality like selecting multiple objects at once and dragging or using the transform type-in or using the align tool.
Kenzor
12-05-2007, 03:17 PM
Thanks focomoso. I went throught the tutorial and I've come up with this test control. Interestingly (although I've yet to try it out) using this method it would be possible to load up the control with different functions if 'control', 'alt' , or 'shift' are pressed whilst moving the mouse...
Just left click and drag in the window
try(destroyDialog TestRoll)catch()
Rollout TestRoll "test"
(
local size = 200
Label lblX
Label lblY
Label lblK
Label lblCross "+" pos: [(size/2)-3,(size/2)-7]
Local followMouse = false
fn moveCross pos =
(
p = [pos.x-3,pos.y-7]
lblCross.pos = p
)
fn reportXY pos =
(
lblX.text = pos.x as string
lblY.text = pos.Y as string
lblk.text = if keyboard.controlPressed then "ctrl" else ""
moveCross pos
)
on TestRoll lbuttondown pos do
(
followMouse = true
setSysCur #select
reportXY pos
)
on TestRoll lbuttonup pos do followMouse = false
on testRoll mousemove pos do
(
if followMouse do reportXY pos
)
on TestRoll Rbuttondown pos do
(
followMouse = false
)
)
createDialog TestRoll 200 200
focomoso
12-05-2007, 07:05 PM
my max is tied up in a big render right now, but I'll give this a shot tonight.
Kenzor
01-16-2008, 11:47 AM
I've written my own mouse control in c#.
It has many advantages over using the Maxscript only method I posted previously
You can grab the dll from my site
http://www.designimage.co.uk/Max/files/MouseControl/AnimControl.dll (http://www.designimage.co.uk/Max/files/MouseControl/AnimControl.dll)
I'll keep it upto date as i make changes
Put the AnimControl.dll in your scripts directory
The sample code shows various methods to use the control.
Eventually I'll write a maxscript rollout to manage multiple floating Mouse windows
fn MyPos a b =
(
format "pos: %, %\n" b.x b.y
)
assemblyFileName = ((getdir #scripts) as string + "/AnimControl.dll")
-- Load the class library created
dotnet.LoadAssembly assemblyFileName
hForm = dotNetObject "AnimControl.MouseWindow"
dotNet.addEventHandler hForm "PosEvent" MyPos
hForm.show()
hform.ctrl.SetX 0 0 --sets the x position of the cross in the window
hform.ctrl.SetY 0 0 --sets the y position of the cross in the window
ypuech
01-16-2008, 01:28 PM
It's a cool control. I tried to use it in a rollout as a dotNetControl but I didn't succeed.
Does this control is a regular .NET custom control or just a .NET form ?
A custom control is the best solution because we can use it in a MAXScript rollout and it will be integrated in Max in a more consistent manner.
Kenzor
01-16-2008, 01:41 PM
At the moment it's a dot Net form, because thats what i wanted for my design.
I think it would be pretty easy to make it into a custom control. I was thinking of doing it when I get time after my current project.
I could give you the C# code .. but it's my first time with C# so its a bit messy... and probally neads a bit of a rewrite first.
You can also use the imgTag for this. It has several event handlers that can be called and you can supply an image that you can track under the mouse if you are a bit clever with it.
I put together an example for you
try(destroyDialog testImgTag)catch()
rollout testImgTag "Test Img Tag" width:220
(
local currentControl=undefined
imgTag mouseTracker "MouseTracker" \
height:200 width:200 bitMap:undefined
timer mouseTime interval:200
fn createBitMap control:undefined width:0 height:0 pos:[0,0]=
(
if control!=undefined then
(
crossCol=(color 255 0 0)
bm=bitmap width height color:(color 255 255 255)
for i = 0 to width do (setPixels bm [i,pos.y] #(crossCol))
for i = 0 to height do (setPixels bm [pos.x,i] #(crossCol))
control.bitmap=bm
)
)
on mouseTracker lbuttondown pos flag do (currentControl=mouseTracker)
on mouseTracker lbuttonup pos flag do (currentControl=undefined;gc light:true)
on mouseTracker mouseout do (currentControl=undefined;gc light:true)
on mouseTime tick do
(
if currentControl!=undefined then
(
newPos=(mouse.pos-(GetDialogPos testImgTag)-currentControl.pos+[0,58])
createBitMap control:currentControl width:currentControl.width height:currentControl.height pos:newPos
)
)
on testImgTag open do
(
createBitMap control:mouseTracker width:mouseTracker.width height:mouseTracker.height
)
)
createDialog testImgTag
I should mention that the draw back here is flashing in the imgTag as the map is redrawn over and over. But it works without anything fancy. Would be nice to have a dotNet version, let me know when that tool is ready.
Kenzor
01-16-2008, 05:03 PM
Many thanks Pen. I appreciate your time. I've not use the imgTag before. Clearly it a pretty powerful tool.
I've noticed that changing viewport can cause the red lines to disappear. It looks like 'Mouse.pos' reports values in viewport coordinates whilst 'GetDialogPos' reports in Screen coordinates. But I'm sure theres a simple way around the problem.
I be sure to post up new versions of the mouse window as I develop it. I'll try and see if theres an easy way to develop the DotNet Control version and Floating Form version at the same time.
Oops, I should be using screenPos not pos. I'll correct that and post again. I thought there was something odd going on.
Two controls working now in it.
try(destroyDialog testImgTag)catch()
rollout testImgTag "Test Img Tag"
(
local currentControl=undefined
imgTag mouseTracker "MouseTracker" \
height:100 width:100 bitMap:undefined
imgTag mouseTracker02 "MouseTracker" \
height:100 width:100 bitMap:undefined
timer mouseTime interval:50
fn createBitMap control:undefined width:0 height:0 pos:[0,0]=
(
if control!=undefined then
(
crossCol=(color 255 0 0)
bm=bitmap width height color:(color 255 255 255)
for i = 0 to width do (setPixels bm [i,pos.y] #(crossCol))
for i = 0 to height do (setPixels bm [pos.x,i] #(crossCol))
control.bitmap=bm
)
)
on mouseTracker lbuttondown pos flag do (currentControl=mouseTracker)
on mouseTracker lbuttonup pos flag do (currentControl=undefined;gc light:true)
on mouseTracker mouseout do (currentControl=undefined;gc light:true)
on mouseTracker02 lbuttondown pos flag do (currentControl=mouseTracker02)
on mouseTracker02 lbuttonup pos flag do (currentControl=undefined;gc light:true)
on mouseTracker02 mouseout do (currentControl=undefined;gc light:true)
on mouseTime tick do
(
if currentControl!=undefined then
(
newPos=(mouse.screenPos-(GetDialogPos testImgTag)-currentControl.pos-[3,22])
createBitMap control:currentControl width:currentControl.width height:currentControl.height pos:newPos
)
)
on testImgTag open do
(
createBitMap control:mouseTracker width:mouseTracker.width height:mouseTracker.height
createBitMap control:mouseTracker02 width:mouseTracker02.width height:mouseTracker02.height
)
)
createDialog testImgTag
focomoso
01-16-2008, 09:20 PM
Good stuff Paul. Funny, I was just about to post in the python thread about how to do something like this using python.
ypuech
01-17-2008, 11:43 AM
I didn't know this imgTag control. It seems powerful.
About the mouse control, I think it would not be so difficult to create a .NET custom control version.
I would like to see how that is done with the new Blur plugin by Eric, I wonder if it would be faster or slower? I would think that there might be more control with the functions in Python, I have not used them but I have poked around them a bit for building maps. I still haven't found time to get deep in to python as I'm always being pulled off onto more pressing work.
I didn't know this imgTag control. It seems powerful.
About the mouse control, I think it would not be so difficult to create a .NET custom control version.
I believe some one is already working on that. This is a good alternative if you don't want to add any plugins or add ons. I often do work for small quick clients, the last thing that I want to introduce is plugins into thier pipeline or lack of one where they don't know how to get a tool working again after a machine has been rebuilt. So alternatives like this for me is something that I will use even if there is a better way. If the client can handle it then I will drop every thing I can on them.
Some more fun.
try(destroyDialog testImgTag)catch()
rollout testImgTag "Test Img Tag"
(
local curBoxControl=undefined
local curSliderControlX=undefined
local curSliderControlY=undefined
local boxControlItVal=[0,0]
imgTag boxControlIt "MouseTracker" \
height:100 width:100 bitMap:undefined
imgTag sliderControlItY "MouseTracker" \
height:100 width:15 bitMap:undefined offset:[105,-105]
imgTag sliderControlItX "MouseTracker" \
height:15 width:100 bitMap:undefined
timer mouseTime interval:50
fn boxControl control:undefined width:0 height:0 pos:[0,0]=
(
if control!=undefined then
(
crossCol=(color 255 0 0)
bm=bitmap width height color:(color 255 255 255)
for i = 0 to width do (setPixels bm [i,pos.y] #(crossCol))
for i = 0 to height do (setPixels bm [pos.x,i] #(crossCol))
control.bitmap=bm
boxControlItVal=pos
)
)
fn sliderControl control:undefined width:0 height:0 pos:[0,0] axis:#x=
(
if control!=undefined then
(
col=(color 50 50 255)
bm=bitmap width height color:(color 220 220 220)
case axis of
(
#x:
(
for h = 0 to height do
(
for i = 0 to pos.x do
(
setPixels bm [i,h] #(col)
)
for i = 0 to width by 10 do
(
curCol=(getPixels bm [i,h] 1)[1]
if curCol!=undefined then
(
curCol.v+=100
setPixels bm [i,h] #(curCol)
)
)
)
)
#y:
(
for w = 0 to width do
(
for i = height to pos.y by-1 do
(
setPixels bm [w,i] #(col)
)
for i = 0 to height by 10 do
(
curCol=(getPixels bm [w,i] 1)[1]
if curCol!=undefined then
(
curCol.v+=100
setPixels bm [w,i] #(curCol)
)
)
)
)
)
control.bitmap=bm
)
)
fn conMenus=
(
rcMenu menu
(
menuItem reset "Reset"
menuItem resetX "Reset X"
menuItem resetY "Reset Y"
on reset picked do
(
boxControlItVal=[boxControlIt.width/2,boxControlIt.height/2]
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:boxControlItVal
sliderControl control:sliderControlItX width:sliderControlItX.width height:sliderControlItX.height pos:boxControlItVal axis:#x
sliderControl control:sliderControlItY width:sliderControlItY.width height:sliderControlItY.height pos:boxControlItVal axis:#y
)
on resetX picked do
(
boxControlItVal=[boxControlIt.width/2,boxControlItVal.y]
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:boxControlItVal
sliderControl control:sliderControlItX width:sliderControlItX.width height:sliderControlItX.height pos:boxControlItVal axis:#x
)
on resetY picked do
(
boxControlItVal=[boxControlItVal.x,boxControlIt.height/2]
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:boxControlItVal
sliderControl control:sliderControlItY width:sliderControlItY.width height:sliderControlItY.height pos:boxControlItVal axis:#y
)
)
)
on boxControlIt lbuttondown pos flag do (curBoxControl=boxControlIt)
on boxControlIt lbuttonup pos flag do (curBoxControl=undefined;gc light:true)
on boxControlIt mouseout do (curBoxControl=undefined;gc light:true)
on boxControlIt rbuttonDown do
(
PopupMenu (conMenus())
)
on sliderControlItX lbuttondown pos flag do (curSliderControlX=sliderControlItX)
on sliderControlItX lbuttonup pos flag do (curSliderControlX=undefined;gc light:true)
on sliderControlItX mouseout do (curSliderControlX=undefined;gc light:true)
on sliderControlItY lbuttondown pos flag do (curSliderControlY=sliderControlItY)
on sliderControlItY lbuttonup pos flag do (curSliderControlY=undefined;gc light:true)
on sliderControlItY mouseout do (curSliderControlY=undefined;gc light:true)
on mouseTime tick do
(
dPos=mouse.screenPos-(GetDialogPos testImgTag)
if curBoxControl!=undefined then
(
newPos=dPos-curBoxControl.pos-[3,22]
boxControl control:curBoxControl width:curBoxControl.width height:curBoxControl.height pos:newPos
sliderControl control:sliderControlItX width:sliderControlItX.width height:sliderControlItX.height pos:newPos axis:#x
sliderControl control:sliderControlItY width:sliderControlItY.width height:sliderControlItY.height pos:newPos axis:#y
)
if curSliderControlX!=undefined then
(
newPos=dPos-curSliderControlX.pos-[3,22]
sliderControl control:curSliderControlX width:curSliderControlX.width height:curSliderControlX.height pos:newPos axis:#x
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:[newPos.x,boxControlItVal.y]
)
if curSliderControlY!=undefined then
(
newPos=dPos-curSliderControlY.pos-[3,22]
sliderControl control:curSliderControlY width:curSliderControlY.width height:curSliderControlY.height pos:newPos axis:#y
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:[boxControlItVal.x,newPos.y]
)
)
on testImgTag open do
(
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:[boxControlIt.width/2,boxControlIt.height/2]
sliderControl control:sliderControlItX width:sliderControlItX.width height:sliderControlItX.height pos:[sliderControlItX.width/2,sliderControlItX.height/2] axis:#x
sliderControl control:sliderControlItY width:sliderControlItY.width height:sliderControlItY.height pos:[sliderControlItY.width/2,sliderControlItY.height/2] axis:#y
)
)
createDialog testImgTag
I should mention that there is a RC menu on the right click over the box control as well in that last version.
Also there are problems with the position if you are using the newer windows look with the rounded title bar. Set the values [3,22] to [3,30] and it will work better.
No animator could ever complain about this verision, I call it the supper trippy, psycho-delic UI.
try(destroyDialog testImgTag)catch()
rollout testImgTag "Test Img Tag"
(
local curBoxControl=undefined
local curSliderControlX=undefined
local curSliderControlY=undefined
local boxControlItVal=[0,0]
imgTag boxControlIt "MouseTracker" \
height:100 width:100 bitMap:undefined
imgTag sliderControlItY "MouseTracker" \
height:100 width:15 bitMap:undefined offset:[105,-105]
imgTag sliderControlItX "MouseTracker" \
height:15 width:100 bitMap:undefined
timer mouseTime interval:50
fn boxControl control:undefined width:0 height:0 pos:[0,0]=
(
if control!=undefined then
(
crossCol=(color 255 0 0)
bm=bitmap width height color:(random (color 255 255 255) (color 1 1 1))
for i = 0 to width do (setPixels bm [i,pos.y] #(crossCol))
for i = 0 to height do (setPixels bm [pos.x,i] #(crossCol))
control.bitmap=bm
boxControlItVal=pos
)
)
fn sliderControl control:undefined width:0 height:0 pos:[0,0] axis:#x=
(
if control!=undefined then
(
bm=bitmap width height color:(color 220 220 220)
case axis of
(
#x:
(
for h = 0 to height do
(
col=(color 0 0 255)
for i = 0 to pos.x do
(
col.h+=1
setPixels bm [i,h] #(col)
)
for i = 0 to width by 10 do
(
curCol=(getPixels bm [i,h] 1)[1]
if curCol!=undefined then
(
curCol.v+=100
setPixels bm [i,h] #(curCol)
)
)
)
)
#y:
(
for w = 0 to width do
(
col=(color 0 0 255)
for i = height to pos.y by-1 do
(
col.h+=1
setPixels bm [w,i] #(col)
)
for i = 0 to height by 10 do
(
curCol=(getPixels bm [w,i] 1)[1]
if curCol!=undefined then
(
curCol.v+=100
setPixels bm [w,i] #(curCol)
)
)
)
)
)
control.bitmap=bm
)
)
fn conMenus=
(
rcMenu menu
(
menuItem reset "Reset"
menuItem resetX "Reset X"
menuItem resetY "Reset Y"
on reset picked do
(
boxControlItVal=[boxControlIt.width/2,boxControlIt.height/2]
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:boxControlItVal
sliderControl control:sliderControlItX width:sliderControlItX.width height:sliderControlItX.height pos:boxControlItVal axis:#x
sliderControl control:sliderControlItY width:sliderControlItY.width height:sliderControlItY.height pos:boxControlItVal axis:#y
)
on resetX picked do
(
boxControlItVal=[boxControlIt.width/2,boxControlItVal.y]
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:boxControlItVal
sliderControl control:sliderControlItX width:sliderControlItX.width height:sliderControlItX.height pos:boxControlItVal axis:#x
)
on resetY picked do
(
boxControlItVal=[boxControlItVal.x,boxControlIt.height/2]
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:boxControlItVal
sliderControl control:sliderControlItY width:sliderControlItY.width height:sliderControlItY.height pos:boxControlItVal axis:#y
)
)
)
on boxControlIt lbuttondown pos flag do (curBoxControl=boxControlIt)
on boxControlIt lbuttonup pos flag do (curBoxControl=undefined;gc light:true)
on boxControlIt mouseout do (curBoxControl=undefined;gc light:true)
on boxControlIt rbuttonDown do
(
PopupMenu (conMenus())
)
on sliderControlItX lbuttondown pos flag do (curSliderControlX=sliderControlItX)
on sliderControlItX lbuttonup pos flag do (curSliderControlX=undefined;gc light:true)
on sliderControlItX mouseout do (curSliderControlX=undefined;gc light:true)
on sliderControlItY lbuttondown pos flag do (curSliderControlY=sliderControlItY)
on sliderControlItY lbuttonup pos flag do (curSliderControlY=undefined;gc light:true)
on sliderControlItY mouseout do (curSliderControlY=undefined;gc light:true)
on mouseTime tick do
(
dPos=mouse.screenPos-(GetDialogPos testImgTag)-[3,22]
if curBoxControl!=undefined then
(
newPos=dPos-curBoxControl.pos
boxControl control:curBoxControl width:curBoxControl.width height:curBoxControl.height pos:newPos
sliderControl control:sliderControlItX width:sliderControlItX.width height:sliderControlItX.height pos:newPos axis:#x
sliderControl control:sliderControlItY width:sliderControlItY.width height:sliderControlItY.height pos:newPos axis:#y
)
if curSliderControlX!=undefined then
(
newPos=dPos-curSliderControlX.pos
sliderControl control:curSliderControlX width:curSliderControlX.width height:curSliderControlX.height pos:newPos axis:#x
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:[newPos.x,boxControlItVal.y]
)
if curSliderControlY!=undefined then
(
newPos=dPos-curSliderControlY.pos
sliderControl control:curSliderControlY width:curSliderControlY.width height:curSliderControlY.height pos:newPos axis:#y
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:[boxControlItVal.x,newPos.y]
)
)
on testImgTag open do
(
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:[boxControlIt.width/2,boxControlIt.height/2]
sliderControl control:sliderControlItX width:sliderControlItX.width height:sliderControlItX.height pos:[sliderControlItX.width/2,sliderControlItX.height/2] axis:#x
sliderControl control:sliderControlItY width:sliderControlItY.width height:sliderControlItY.height pos:[sliderControlItY.width/2,sliderControlItY.height/2] axis:#y
)
)
createDialog testImgTag
BobbyB
01-17-2008, 04:11 PM
Haha how could the animators complain wial they are having Seizures.
(http://au.wrs.yahoo.com/_ylt=A0oGkuy2i49HqU8BDFkL5gt.;_ylu=X3oDMTExY281YmEwBHNlYwNzcgRwb3MDMQRjb2xvA3NrMQR2dGlkAwRsA1dTMQ--/SIG=124jpuv8b/EXP=1200676150/**http%3A//www.nlm.nih.gov/medlineplus/seizures.html)
Teirz
01-18-2008, 02:56 AM
Omg this is really a psycho UI. Gotta add this into my April fools sabotage script :P
TzMtN
02-26-2008, 04:28 PM
That last one is pretty cool Paul,
you can add a function that checks wich user is working with it
and if this user is on your black list you can turn the psycho function on.
now here is something I was working on before I found this thread..
try (destroyDialog joyStick) catch ()
rollout joyStick "JoyStick"
(
-- Local Variable Declerations
------------------------------------------
local upColor = black
local downColor = yellow
local dim = 200
local joyDim = 10
local activateJS = false
local joyBitmap = bitmap joyDim joyDim color:upColor
local joyBitmapMask = bitmap joyDim joyDim color:black
local mouseLBState = false
local mouseOffset = [0,0]
local spinnerSize = 60
local joyStickPos = [dim / 2,dim / 2]
local signState = false
-- User Interface
------------------------------------------
-- Build the static Cross
groupBox gb1 "" pos:[0, - 6] width:(dim/2) height:(dim/2 + 6)
groupBox gb2 "" pos:[(dim/2), - 6] width:(dim/2) height:(dim/2 + 6)
groupBox gb3 "" pos:[0,(dim/2) - 6] width:(dim/2) height:(dim/2 + 6)
groupBox gb4 "" pos:[(dim/2),(dim/2) - 6] width:(dim/2) height:(dim/2 + 6)
-- Adds the Values to the Cross
label lbA "1" pos:[dim / 2 - 9,2]
label lbB "-1" pos:[dim / 2 - 12,dim - 15]
label lbC "-1" pos:[2,dim / 2 - 16]
label lbD "1" pos:[dim - 10,dim / 2 - 16]
label lbSignature "" pos:[5,5]
-- Build the dynamic Cross
groupBox gbVer "" pos:[0,(dim - joyDim)/ 2] width:(dim * 3) height:(dim * 3)
groupBox gbHor "" pos:[dim / 2 ,-10] width:(dim * 3) height:(dim * 3)
-- X and Y spinners
spinner spX "X:" pos:[10,dim + 4] width:spinnerSize scale:0.001 range:[-1,1,0]
spinner spY "Y:" pos:[dim - spinnerSize - 5,dim + 4] width:spinnerSize scale:0.001 range:[-1,1,0]
-- The JoyStick
checkButton cbJoy "" width:joyDim height:joyDim pos:[(dim - joyDim)/2,(dim - joyDim)/2]
images:#(joyBitmap,joyBitmapMask,1,1,2,1,1) highlightColor:downColor
-- Functions
------------------------------------------
fn posToVal posX posY =
(
[2 * posX / dim - 1 , 1 - 2 * posY / dim]
)
fn ValToPos valX valY =
(
[dim * (valX + 1) / 2 , (1 - valY) * dim / 2]
)
fn controlTarget pos =
(
if selection.count == 1 do (
$.pos.x = pos.x * 100
$.pos.y = pos.y * 100
)
)
fn updateJS mPos =
(
if mPos.y > dim do mPos.y = dim
if activateJS then (
joyStickPos = mPos
gbVer.pos.y = mPos.y - joyDim/2
gbHor.pos.x = mPos.x
local newPos = posToVal mPos.x mPos.y
spX.value = newPos.x
spY.value = newPos.y
spX.pos *= 1
spY.pos *= 1
cbJoy.pos = joyStickPos - [joyDim/2,joyDim/2]
controlTarget newPos
) else (
if mouseLBState then SetDialogPos joyStick (mouse.screenPos - mouseOffset)
)
)
fn updateSpinner axis valX valY =
(
if axis < 3 do joyStickPos.x = (ValToPos valX valY).x
if axis > 1 do joyStickPos.y = (ValToPos valX valY).y
gbVer.pos.y = joyStickPos.y - joyDim/2
gbHor.pos.x = joyStickPos.x
spX.pos.y = spY.pos.y = dim + 4
spY.pos.x = dim - 18
cbJoy.pos = joyStickPos - [joyDim/2,joyDim/2]
controlTarget (posToVal joyStickPos.x joyStickPos.y)
)
fn reSizeDialog pos =
(
dim = (pos.x + pos.y - 24) / 2
joyStick.width = dim
joyStick.height = dim + 24
gb1.width = gb2.width = gb3.width = gb4.width = dim/2
gb1.height = gb2.height = gb3.height = gb4.height = dim/2 + 6
gb2.pos = [(dim/2), - 6]
gb3.pos = [0,(dim/2) - 6]
gb4.pos = [(dim/2),(dim/2) - 6]
lbA.pos = [dim / 2 - 9,2]
lbB.pos = [dim / 2 - 12,dim - 15]
lbC.pos = [2,dim / 2 - 16]
lbD.pos = [dim - 10,dim / 2 - 16]
gbVer.width = gbVer.height = gbHor.width = gbHor.height = (dim * 3)
updateSpinner 2 spX.value spY.value
)
fn signature state =
(
if keyboard.altPressed and keyboard.controlPressed do signState = not signState
lbSignature.caption = if signState then "Matan Halberstadt" else ""
lbSignature.pos = [5,5]
)
fn openDialog =
(
createDialog joyStick width:dim height:(dim + 24) style:#(#style_sunkenedge, #style_resizing)
SetDialogPos joyStick (mouse.screenPos - [dim/2,dim/2])
)
fn init =
(
)
fn done =
(
gc light:true
)
-- Event Handlers
------------------------------------------
on cbJoy changed state do (
activateJS = state
signature state
)
on joyStick mouseMove pos do updateJS pos
on joyStick rbuttonup pos do destroyDialog joyStick
on joyStick lbuttondown pos do (
mouseLBState = true
mouseOffset = pos
)
on joyStick lbuttonup pos do mouseLBState = false
on spX changed val do updateSpinner 1 val 0
on spY changed val do updateSpinner 3 0 val
on joyStick resized pos do reSizeDialog pos
on joyStick open do init()
on joyStick close do done()
) -- end of rollout
joyStick.openDialog()
magicm
02-29-2008, 12:41 AM
I couldn't resist and wrote a DotNet version real quick. It seems to mess up garbage collection once in a while but I'm too lazy to figure out why right now :)
Martijn
Kenzor
04-21-2008, 04:37 PM
I've been meaning to get back to this for a while now
I've finally re-written the c# control to sit inside a max rollout
you can grab the .dll from my site
http://www.designimage.co.uk/Max/files/MouseControl/windowsControl.dll
put the dll in your scripts directory
The sample code shows various methods to use the control.
Set the control size as you would normally using the width and height properties
Use the SetRange function to set the range of values you want the control to output.
double click the control to zero (function controlled by the maxscript)
right click to cancel. (function built into the dot net control)
note that when you do right click to cancel the control sends out another PosEvent with the original postion.
assemblyFileName = ((getdir #scripts) as string + "/WindowsControl.dll")
dotnet.LoadAssembly assemblyFileName
rollout TestRoll "test"
(
dotNetControl dnMouseWindow "WindowsControl.MouseWindow" width:200 height:200 align:#left
local oldpos = [0,0]
on dnMouseWindow PosEvent a b do format "pos: %, %\n" b.x b.y
on dnMousewindow DoubleClick do dnMousewindow.SetPos 0 0
on dnMousewindow mouseDown do
(
oldPos.x = (dnMousewindow.getPos()).x
oldpos.y = (dnMousewindow.getPos()).y
)
on TestRoll open do
(
dnMousewindow.SetRange -100 -100 100 100 -- bottom left x y topright x y : default 0,0 1,1
--top right will always have higher values than bottom left
--If values are the wrong way round the SetRange function swaps them rather than throwing and error.
dnMousewindow.SetPos 0 0 -- set the cross position after setting the range values.
-- if SetPos is out of range the value is clamped.
)
)
if TestWindow != undefined then CloseRolloutFloater TestWindow
TestWindow = NewRolloutFloater "" 400 300
AddRollout TestRoll TestWindow
for those that are interested here's the c# code
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
namespace WindowsControl
{
/// <summary>
/// Summary description for UserControl1.
/// </summary>
public delegate void PosEventHandler(object sender,PosEventArgs e);
public class MouseWindow : System.Windows.Forms.UserControl
{
/// <summary>
/// Required designer variable.
/// </summary>
private RangeLimit xRange = new RangeLimit();
private RangeLimit yRange = new RangeLimit();
private System.ComponentModel.Container components = null;
private System.Windows.Forms.Label LblCross;
private Point crossOffset = new Point(0,0);
private Point oldCrossPostion = new Point(0,0);
private FloatPoint pos = new FloatPoint(0,0);
private bool mouseCancel = false;
public int xWinOffset = -13;
public int yWinOffset = -16;
private void SetOldCrossPostion()
{
this.oldCrossPostion = this.LblCross.Location;
}
public void SetRange(float xMin,float yMin,float xMax,float yMax)
{
xRange.set(xMin, xMax);
yRange.set(yMin, yMax);
}
public void SetPos(float x, float y)
{
pos.x = xRange.clamp(x);
pos.y = yRange.clamp(y);
Point P = new Point(0,0);
P.X = xRange.ScaleToWindow(pos.x,this.Size.Width)+ xWinOffset;
P.Y = this.Size.Height - yRange.ScaleToWindow(pos.y,this.Size.Height)+ yWinOffset;
this.LblCross.Location = P;
this.SetOldCrossPostion();
}
public FloatPoint GetPos()
{
setPosFromWindow();
return pos;
}
private void CalcCrossOffset()
{
crossOffset.X = this.PointToClient(Control.MousePosition).X - this.LblCross.Location.X;
crossOffset.Y = this.PointToClient(Control.MousePosition).Y - this.LblCross.Location.Y;
}
private void setPosFromWindow()
{
pos.x = xRange.ScaleFromWindow(this.LblCross.Location.X - xWinOffset,this.Size.Width);
pos.y = yRange.ScaleFromWindow(this.Size.Height - (this.LblCross.Location.Y - yWinOffset),this.Size.Height);
}
private void MoveCross()
{
Point P = this.PointToClient(Control.MousePosition);
P.X -= crossOffset.X;
P.Y -= crossOffset.Y;
P.X = RangeLimit.clamp(P.X,xWinOffset,this.Size.Width + xWinOffset);
P.Y = RangeLimit.clamp(P.Y,yWinOffset,this.Size.Height + yWinOffset);
this.LblCross.Location = P;
this.setPosFromWindow();
}
public event PosEventHandler PosEvent;
public PosEventArgs GetPosEventArgs()
{
//return new PosEventArgs(pos[index],index);
return new PosEventArgs(this.pos,1);
}
protected virtual void OnPosEvent(PosEventArgs e)
{
if (PosEvent != null)
PosEvent(this, e);
}
private void setPos(object sender, System.Windows.Forms.MouseEventArgs e)
{
if ((e.Button == MouseButtons.Left) && !mouseCancel)
{
MoveCross();
OnPosEvent(GetPosEventArgs());
}
}
private void SetOffset(object sender, System.Windows.Forms.MouseEventArgs e)
{
if ((e.Button == MouseButtons.Right) && (e.Button == MouseButtons.Right))
{
this.LblCross.Location = this.oldCrossPostion;
this.setPosFromWindow();
mouseCancel = true;
OnPosEvent(GetPosEventArgs());
}
if (e.Button == MouseButtons.Left)
{
mouseCancel = false;
this.SetOldCrossPostion();
CalcCrossOffset();
}
}
public MouseWindow()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
// TODO: Add any initialization after the InitComponent call
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if( components != null )
components.Dispose();
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.LblCross = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// LblCross
//
this.LblCross.Font = new System.Drawing.Font("Microsoft Sans Serif", 20.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.LblCross.Location = new System.Drawing.Point(96, 96);
this.LblCross.Name = "LblCross";
this.LblCross.Size = new System.Drawing.Size(30, 30);
this.LblCross.TabIndex = 0;
this.LblCross.Text = "+";
this.LblCross.MouseMove += new System.Windows.Forms.MouseEventHandler(this.setPos);
this.LblCross.MouseDown += new System.Windows.Forms.MouseEventHandler(this.SetOffset);
//
// MouseWindow
//
this.Controls.Add(this.LblCross);
this.Name = "MouseWindow";
this.Size = new System.Drawing.Size(200, 200);
this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.setPos);
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.SetOffset);
this.ResumeLayout(false);
}
#endregion
}
public class PosEventArgs : EventArgs
{
public float x = 0;
public float y = 0;
public int index = 0;
public PosEventArgs(FloatPoint p,int i)
{
x = p.x;
y = p.y;
index = i;
}
}
public class FloatPoint
{
public float x = 0;
public float y = 0;
public FloatPoint(){}
public FloatPoint(float x , float y)
{
set (x , y);
}
public FloatPoint (System.Drawing.Point p)
{
set (p);
}
public void set (float x , float y)
{
this.x = x;
this.y = y;
}
public void set (System.Drawing.Point p)
{
set (p.X,p.Y ); //implicit type conversion
}
}
public class RangeLimit
{
public float outputMin = 0;
public float outputMax = 1;
public int ScaleToWindow(float i,int scale)
{
float f = (i - outputMin)/(outputMax - outputMin);
return (int)(f * scale);
}
public float ScaleFromWindow(int i,int scale)
{
float s = (float)i/(float) scale;
return (float)((outputMax - outputMin) * s ) + outputMin;
}
public void set(float min,float max)
{
if (max > min )
{
this.outputMin = min;
this.outputMax = max;
}
else
{
this.outputMin = max;
this.outputMax = min;
}
}
public RangeLimit(){}
public RangeLimit(float max)
{
this.set(0,max);
}
public RangeLimit(float min , float max)
{
this.set(min,max);
}
public static int clamp(int i,int min,int max)
{
if (i < min){i = min;}
else
{
if(i>(max)){i = max;}
}
return i;
}
public static float clamp(float i,float min,float max)
{
if (i < min){i = min;}
else
{
if(i>(max)){i = max;}
}
return i;
}
public float clamp(float i)
{
if (i < this.outputMin){i = this.outputMin;}
else
{
if(i>(this.outputMax)){i = this.outputMax;}
}
return i;
}
}
}
I haven't tried yet but why are you running it out of the script directory instead of plugins?
Nice work, do we have drawing options for the target and back ground?
Kenzor
04-21-2008, 05:39 PM
Nice work, do we have drawing options for the target and back ground?
the background inherits from System.Windows.Forms.Usercontrol which has a whole bunch of properties to play with.. ie.
.BackgroundImage : <System.Drawing.Image>
.BackColor : <System.Drawing.Color>
the target is just a text label ... System.Windows.Forms.Label.
currently its a private object so there's no access from Maxscript
I could make the target public.. but it would then be possible to break the control..
or I could make a bunch of get and set functions for the properties you need...
what would be useful?
Kenzor
04-21-2008, 05:54 PM
What I'd really like to do is link the mouseWindow x y position properties directly to two controllers .
In the same way you use the controller property of a spinner.
.. though i doubt it possible in c# ....
-----------------------
The .dll can go anywhere. I guess I felt that the plugins directory should be reserved for actual max plugins rather than dotnet controls.
I didn't get it to work from the plugins folder either. I didn't load the dll as it is not a max dll.
Could we have an image maybe for the target or at least a selection of different choices. With the one that I use now I have a circle with target lines.
LoneRobot
04-22-2008, 02:12 PM
What I'd really like to do is link the mouseWindow x y position properties directly to two controllers .
In the same way you use the controller property of a spinner.
.. though i doubt it possible in c# ....
-----------------------
The .dll can go anywhere. I guess I felt that the plugins directory should be reserved for actual max plugins rather than dotnet controls.
nice one ken, I'd been thinking about this for a while too, and Had come up with this stumbling block also. I think this could be done with a timechange callback. You can link this to update the properties of a dotnet object. I have managed to do this with a dotnet custom slider object i have written.
what id like to do is make a composite slider object for a morpher that adds min/max buttons in dotnet. but i cant get the trackbar scroll event in the control to be exposed to the base usercontrol scroll event. Any thoughts?
Kenzor
04-22-2008, 02:14 PM
I've made some changes to allow for customization
Theres a zip file on my site with all the new files including the C# source "UserControl1.cs"
Zip File (http://www.designimage.co.uk/Max/Files/MouseControl/MouseControl.zip)
I've added some new functions to edit the target labels properties.
.SetTargetImage <filePath> -- returns true if successful
.SetTargetSize x y
.SetTargetBackgroundColor a r g b
.SetTargetBackgroundColor r g b
There's also a new property .TargetCenter a "system.drawing.point". It's automatically set to half the label size, when you set a new target image or size.
Note that because the default target label displays a '+' sign the default TargetCenter is actually slightly below center (15,14) because of the font.
I've also made the target label a public object so you access it properties directly if you wish.
The new test code shows some of these methods
rollout TestRoll "test"
(
local scriptDir = ((getdir #scripts) as string)
local test = dotnet.LoadAssembly (scriptDir + "/CsharpClasses/WindowsControl.dll")
dotNetControl dnMouseWindow "WindowsControl.MouseWindow" width:200 height:200 align:#center
local oldpos = [0,0]
on dnMouseWindow PosEvent a b do format "pos: %, %\n" b.x b.y
on dnMousewindow DoubleClick do dnMousewindow.SetPos 0 0
on dnMousewindow mouseDown do
(
oldPos.x = (dnMousewindow.getPos()).x
oldpos.y = (dnMousewindow.getPos()).y
)
on TestRoll open do
(
dnMousewindow.SetRange -100 -100 100 100 -- bottom left x y topright x y : default 0,0 1,1
--top right will always have higher values than bottom left
--If values are the wrong way round the SetRange function swaps them rather than throwing and error.
dnMousewindow.SetTargetImage (scriptDir + "/target.jpg")
dnMousewindow.SetTargetSize 16 16
dnMousewindow.SetPos 0 0 -- set the cross position after setting the range values. and target size
-- if SetPos is out of range the value is clamped.
--to access the target label properties use dnMousewindow.Target
)
)
if TestWindow != undefined then CloseRolloutFloater TestWindow
TestWindow = NewRolloutFloater "" 400 300
AddRollout TestRoll TestWindow
Kenzor
04-22-2008, 02:34 PM
[QUOTE=
what id like to do is make a composite slider object for a morpher that adds min/max buttons in dotnet. but i cant get the trackbar scroll event in the control to be exposed to the base usercontrol scroll event. Any thoughts?[/QUOTE]
I had some problems exposing properties to Max when I tried defining classes within classes.
In the end I kept everyting very flat and simple.
I did manage to create my own event ( called every time the mouse is moved) from this article (http://www.csharphelp.com/archives4/archive678.html)
whats your code like could I have a Look?
LoneRobot
04-22-2008, 02:54 PM
Hi Ken, thanks for the article - im trying to get my head around this.
I wasnt sure if the vb.net 'Addhandler' method actually creates the delegates for you or not.
But I have been having the same problem exposing the properties of nested classes. Someone said you can expose the nested item as a property within the class, but that sounds like the wrong way to go since you could then change it programatically, as it would expose the whole element. you essentially only want to show the bit you want people to see, just like you did with the label control on your code. I tried something inheriting the trackbar class too, as i though it would automatically inherit the scroll handler, but wasnt able to make the trackbar smaller in the UI so that i could add the buttons this way. Im sure there is a correct way of doing it and Im just doing it wrong!
But I think composite dotnet controls (ie a user control build from other dotnet Ui controls) would be useful for character animation - a composite morph slider would give you a plug in control that you could set up to each target, without having to build a UI for reset button and you could have programmable presets too for common values if you wanted.
I cant get access to my code as i'm at work, but i'll try to post something later. Like you said Ken, i'm not sure my programming skills are that pretty! thanks mate.
Kenzor
04-22-2008, 04:20 PM
It took me a while to get my head round events and deligates.
I'll try and explain how it works in my code.
Basically there are two events working here. The MouseMove event which notifies .setPos which in turn notifies Max using a 'posEvent'. I did this because I wanted to be sure that the target position had been updated before Max registered the event.
This code is outside the usercontrol object.
The delegate which subscribers must implement
public delegate void PosEventHandler(object sender,PosEventArgs e);
A simple class that inherits from eventArgs to hold the mouse postion.
An instance of this class will be sent to subscribers every time the event is triggered
public class PosEventArgs : EventArgs
{
public float x = 0;
public float y = 0;
public int index = 0;
public PosEventArgs(FloatPoint p,int i)
{
x = p.x;
y = p.y;
index = i;
}
}
The Rest of the code is inside the usercontrol class
The event controls access to the delegate
public event PosEventHandler PosEvent;
This function Returns a PosEventArgs instance.
which contains the mouse position 'this.pos', and an index
Note... the index isn't used at the moment which is why its set to '1' ...
... it's there so I can expand the code later..
public PosEventArgs GetPosEventArgs()
{
return new PosEventArgs(this.pos,1);
}
If anyone has subscribed notify them
protected virtual void OnPosEvent(PosEventArgs e)
{
if (PosEvent != null) PosEvent(this, e);
}
This is the function called by the mouse move event...
After moving the Target object by calling 'MoveCross()' it then calls 'OnPosEvent' which notifies the subscribers
private void setPos(object sender, System.Windows.Forms.MouseEventArgs e)
{
if ((e.Button == MouseButtons.Left) && !mouseCancel)
{
MoveCross();
OnPosEvent(GetPosEventArgs());
}
}
This is the code generated by visual studio which adds my function 'setPos' to the list of functions called when the mouse is moved.
this.Target.MouseMove += new System.Windows.Forms.MouseEventHandler(this.setPos);
Unfortunitly I'm in production in Max 8 right now so I think I'm going to be stuck with the bitmap method. Here is my problem. I'm want to use the control in a CA def on a modifier. To set the position I need to get the screenPos of the mouse- the position of the dialog - the position of the imgTag. For a dialog getDialogPos will do that but for a rollout it will not. Even the rollout.pos property doesn't work as it is not in a rollout floater. Any one have any idea of how I can nail down the position of the mouse over an image tag that is in the modifier panel?
LoneRobot
04-23-2008, 08:51 AM
Ken -
Many thanks for the heads up. I had actually got a tutorial in a book about this pertaining to a treeview, it hadn't occured to me to use it with other controls and i was barking up a different alley. I will post some code tonight.
paul -
This isnt much help except to say I tried to do the same thing as you a while back and came up with the same conclusion. I made a max8 control similar to yours (although not as trippy!) for eyeball position on a character. I couldnt figure out why it wasnt working, except for the fact that inside the modifer panel it seems to override the mousedown events to provide the ability to scroll the panel up and down. it doesnt do this on buttons, but seems to disable the handlers for bitmaps. How have you got round the lack of a mousemove handler in imgtag? In the end, I used a checkbox linked to an ini setting that remembered whether I wanted the dialog to open the rollout floater on the selection of the node. This was tied to a selectionset callback that destroyed it when the character node with the ca's was deselected. this was the best i could come up with.
Pete, Im' not having any issues with the event handlers for the imgTag, my problem is know where the mouse is in relation to the imgTag. How did you work that out.
Here is a version of what I'm thinking of using, not so trippy. This one is in a createDialog and I can get the position of the mouse - position of dialog - position of imgTag. When you do this in the modifier panel I can't get the position of the rollout on screen, only the position of the mouse and the imgTag, this isn't enough to solve where the mouse is over the imgTag.
rollout testImgTag "Test Img Tag" width:220
(
local curBoxControl=undefined
local curSliderControlX=undefined
local curSliderControlY=undefined
local boxControlItVal=[0,0]
local crossCol=(color 255 0 0)
local circleCol=(color 0 0 100)
local bgCol=(color 100 100 100)
local gridCol=(color 120 120 120)
local gridSpace=25
imgTag boxControlIt "MouseTracker" \
height:100 width:100 bitMap:undefined
timer mouseTime interval:1
fn boxControl control:undefined width:0 height:0 pos:[0,0]=
(
if control!=undefined then
(
local bm=bitmap width height color:bgCol
for h = 0 to height by gridSpace do
(
for i = 0 to width do (setPixels bm [i,h] #(gridCol))
)
for w = 0 to width by gridSpace do
(
for i = 0 to height do (setPixels bm [w,i] #(gridCol))
)
for i = 0 to width do (setPixels bm [i,pos.y] #(crossCol))
for i = 0 to height do (setPixels bm [pos.x,i] #(crossCol))
r=10
j=pos.x
k=pos.y
for i = 1 to 360 by 5 do
(
x=r*cos i + j
y=r*sin i + k
setPixels bm [x,y] #(circleCol)
)
control.bitmap=bm
boxControlItVal=pos
)
)
local rcMenuCon=undefined
fn conMenus con:undefined=
(
rcMenu menu
(
menuItem reset "Reset"
menuItem resetX "Reset X"
menuItem resetY "Reset Y"
on reset picked do
(
boxControlItVal=[rcMenuCon.width/2,rcMenuCon.height/2]
boxControl control:rcMenuCon width:rcMenuCon.width height:rcMenuCon.height pos:boxControlItVal
)
on resetX picked do
(
boxControlItVal=[boxControlIt.width/2,boxControlItVal.y]
boxControl control:rcMenuCon width:rcMenuCon.width height:rcMenuCon.height pos:boxControlItVal
)
on resetY picked do
(
boxControlItVal=[boxControlItVal.x,rcMenuCon.height/2]
boxControl control:rcMenuCon width:rcMenuCon.width height:rcMenuCon.height pos:boxControlItVal
)
)
rcMenuCon=con
menu
)
local testMouseOut=false
on boxControlIt lbuttondown pos flag do
(
testMouseOut=true
curBoxControl=boxControlIt
circleCol=(color 200 0 0)
)
on boxControlIt lbuttonup pos flag do
(
print "mouseUp"
testMouseOut=false
circleCol=(color 0 0 100)
dPos=mouse.screenPos-(GetDialogPos testImgTag)-[3,22]
newPos=dPos-boxControlIt.pos
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:newPos
curBoxControl=undefined
gc light:true
)
on boxControlIt mouseout do
(
if testMouseOut then
(
circleCol=(color 0 0 100)
dPos=mouse.screenPos-(GetDialogPos testImgTag)-[3,22]
newPos=dPos-boxControlIt.pos
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:newPos
)
curBoxControl=undefined
gc light:true
)
on boxControlIt rbuttonDown do
(
PopupMenu (conMenus con:boxControlIt)
)
on mouseTime tick do
(
dPos=mouse.screenPos-(GetDialogPos testImgTag)-[3,22]
if curBoxControl!=undefined then
(
newPos=dPos-curBoxControl.pos
boxControl control:curBoxControl width:curBoxControl.width height:curBoxControl.height pos:newPos
)
)
on testImgTag open do
(
boxControl control:boxControlIt width:boxControlIt.width height:boxControlIt.height pos:[boxControlIt.width/2,boxControlIt.height/2]
)
)
createDialog testImgTag
thatoneguy
04-23-2008, 04:34 PM
Paul, one thing I figured out while working on my "Box Drawing" script was that IMGTags flicker, but bitmaps don't.
Perhaps what you should do is take in all of the mouse controls through a transparent IMGTAG which doesn't change and then modify a BITMAP below that.
LoneRobot
04-24-2008, 09:10 AM
Hi Paul,
I used a much simpler setup as you, but i used the mousemove property on the rollout to return the position and set the dialog background to the bitmap. Although it simplifies things, it is not better, as I couldn't get the mouse.state global to work over the rollout (i think you said in a previous post it only works in a viewport) So my version was to trigger it on a keyboard.controlpressed event. This didnt matter, as as soon as you put this rollout in an attribute and assign it to the modify/command panel, it no longer registers the mouse move event. Since i couldnt figure a better way of doing this until your example i was looking into writing a dotnet one. (which Ken has done brilliantly) but in the end, whatever i did is rendered non functioning when placed in an attribute inside the panel. which was why i decided to create the rollout from the character attribute opening and destroy it when the object was deselected. mooo :shrug:
i used your code as a base as i liked your setpixel method.
rollout testImgTag "Test Img Tag" width:100 height:100
(
fn boxControl control:undefined width:0 height:0 pos:[0,0]=
(
if control!=undefined then
(
local curBoxControl=undefined
local curSliderControlX=undefined
local curSliderControlY=undefined
local boxControlItVal=[0,0]
local crossCol=(color 255 0 0)
local circleCol=(color 0 0 100)
local bgCol=(color 100 100 100)
local gridCol=(color 120 120 120)
local gridSpace=25
local bm=bitmap width height color:bgCol
for h = 0 to height by gridSpace do
(
for i = 0 to width do (setPixels bm [i,h] #(gridCol))
)
for w = 0 to width by gridSpace do
(
for i = 0 to height do (setPixels bm [w,i] #(gridCol))
)
for i = 0 to width do (setPixels bm [i,pos.y] #(crossCol))
for i = 0 to height do (setPixels bm [pos.x,i] #(crossCol))
r=10
j=pos.x
k=pos.y
for i = 1 to 360 by 5 do
(
x=r*cos i + j
y=r*sin i + k
setPixels bm [x,y] #(circleCol)
)
SetDialogBitmap testImgTag bm
testImgTagVal=pos
)
)
on testImgTag mousemove pos do
(
if keyboard.controlpressed do
(
format "Mouse Position : %\n" pos
curBoxControl=testImgTag
circleCol=(color 200 0 0)
boxControl control:curBoxControl width:curBoxControl.width height:curBoxControl.height pos:Pos
)
)
on testImgTag open do
(
boxControl control:testImgTag width:testImgTag.width height:testImgTag.height pos:[testImgTag.width/2,testImgTag.height/2]
)
)
createdialog testimgTag
anyone know how to get a mousestate into this???
LoneRobot
04-25-2008, 10:22 AM
am going away but wanted to update with the progress on a morph slider control - will post it when i get back and can finish it off. it has a node string property so that each slider can affect various objects. this is not a great implementation between dotnet and max, but can be coded into any rollout and is good for specific scenarios. It also has a setkey event too that can be assigned to any max function, as well as all the button states to nudge+1,+5+10 and reset all within the control. I am looking on how to implement a callback to update them.
should mention that it wont be limited to just morph targets, by default the range will be 0-100 but i will add a property to change it to whatever you are doing. Im hoping it will be a fast way of adding functionality via a control without having to code each button.
I was excited to see max 2008 new node event system - it looks very promising for many things, not just dotnet control integration. anyone else used this?
will post more next week!
Wow I haven't seen Swami's name hanging around a max forum in a very long time. Wonder what ever happened to him. Last I knew he was getting in to photography.
I'll poke at using bitmaps to day as well but I think that I'm going to end up with the same problems.
LoneRobot
05-01-2008, 09:58 PM
I was thinking of starting a "where is Swami?" thread. delic!!!
Sorry this has been taking so long but I've only been able to work on this during my train ride in and back! Just posting an update and a screenshot of the progress of the joystick user control. The colors are a bit gaudy but you get the idea! Still got a couple of bugs i want to iron out before I post the dll as I realised when it was added as an atrribute in the command panel it's slightly too wide. Also i want to add a property to display a title of what it controls somewhere too. I have set this up with an appropriate MXS function to handle multiple morph target blending, for facial expressions and eye movements. I will also post the slider control i have been working on.
At the moment, there is a panel refresh issue with the controls. After adding them I have to click the create panel and back in order for them to be redrawn (and to be shown). anyone know what might be causing this?
Kenzor
05-02-2008, 10:30 AM
Pete, your screen shot looks very cool, I'm looking forward to seeing the finished version.
I've been working on a floating control panel. The Idea being that you can have multiple panels open at the same time for different characters in the scene.
Theres still a lot to do but you get the idea.
http://www.designimage.co.uk/Max/Files/MouseControl/Control%20Bar.jpg
There's also some improvements to the orignal mouse window, with the new dll.
Heres all the source and the new dll (http://www.designimage.co.uk/Max/Files/MouseControl/MouseControl.zip)
LoneRobot
05-02-2008, 11:08 AM
Hi Ken, That looks great!
At the moment i'm using a function that divides the joystick area into 4 quadrants depending on the x and y location. This can then workout which quadrant the cursor is in and blend the targets accordingly. In the future i'm sure this could be handled by the control, but im doing it in MXS for the time being. I've added a property to allow the user to interactively and specify a constrain to axis option - this is enabled as a property or by the two small checkbuttons on the bottom of the control.
I too think the floater is a good approach, but IMHO do like to keep character UIs in the modify panel on CA's. However, I do like your semi-transparent overlay, and a callback could be introduced to initialize the floater to change according to different characters. I assume this is what you are doing! Sometimes we have 6 or seven characters in a scene so would need this to happen.
I am also trying to keep the control compact enough too, sacrificing a little precision in the process but good enough for most circumstances.
I will post code as soon as i have something stable, the control is fine but I just want to resolve the max implementation and give some instructions before i do.
Pete, how are you creating the controls again? A custom dotNet control or using existing controls?
LoneRobot
05-02-2008, 03:20 PM
Hi Paul,
My last screenshot is a composite dotNet control. It is formed with a label inside a panel control with some extra function buttons like reset, setkey, nudge value and constrain axis.
I couldn't get something like this working a: in the command panel or b:with standard controls. :(
however, the function for dividing the control into quadrants for morph handling is the same.
It is a dotNet label? Willing to share a bit of test code just to see the direction that you are going?
Hi Paul,
My last screenshot is a composite dotNet control. It is formed with a label inside a panel control with some extra function buttons like reset, setkey, nudge value and constain axis.
I couldn't get something like this working a: in the command panel or b:with standard controls. :(
however, the function for dividing the control into quadrants for morph handling is the same.
LoneRobot
05-02-2008, 04:11 PM
Absolutely! its actually using this control that allows you to lock it into the bounds of the parent container -
http://www.codeproject.com/KB/cpp/RuntimeMovableControls.aspx
I am hoping to post the code in the next few days when I iron out a couple of issues. Sorry for the delay!
Ya I look forward to seeing that. Thanks
Thought that I would bring this tread back from the dead.
http://www.paulneale.com/temp/joyStickControls.jpg
Here is my current implimentation using "system.windows.forms.pictureBox". Not sure that I'm going with those built in buttons, looks cool but a popUp menu is easier to use I think. This one works in the command panel and is part of a scripted modifier.
LoneRobot
07-14-2008, 09:30 PM
Hi Paul!
Looks cool! what size are you using for those? 25x25?
I hadnt forgotten about this thread, my joystick control was almost ready to post but ive taken on some extra work that is preventing me from finishing it off. I also wanted to post a custom slider control also. so watch this space!
im liking the idea of a scripted mod and had thought about that direction too. you could have a pick button perhaps and choose the object you wanted the joystick to move and this could be stored in the #maxobject as a weak ref to the node. this would be better than before as i was trying to store the node name in the dot net control - not, on reflection the best place for it.
Well I'm having to rethink this. Although I have corrected some of the issues I was seeing when using pictureBox it is slow once I added all the other joy stick controls. I'm comming up on 17 controls and I just isn't working fast enough. I have a couple of other change I'm going to make first but I don't think that it will help. The next option will be to have two images, one is the background and the second the target and just move it. I thought it would be cleaner to do it all in one but it isn't happing. I also tried using the backGroundImage param and then set a target to the image param but I can't find a way to move it. I'm using pictureBox if you have any ideas.
Oh and the size is 80x80 and the large one is 160 square.
LoneRobot
07-15-2008, 08:46 AM
Hi Paul,
Are you overriding the onpaint handler to draw the background grid or just setting the background image property? in my current implementation i've got a two label controls embedded in a user control, the background has a custom onpaint handler which draws according to properties on the joystick class. the actual widget that you move is a slightly rehashed moveable label control. as this inherits the label class propeties i have embedded an imagelist with the cursor appearance types. it is then simple to alter the shape of the widget. Since i am using predefined windows brushes for the grid background it is fast. I was worried about the onpaint refresh slowing it down, but it seems acceptable to me with the small test i have been doing. however, im hoping to iron out my last issues with getting feedback from the maxobject to the control and post the results.
I dont know why the picturebox is giving you slowdown issues, i reckon setting the image or backgroundimage property of a picturebox is slower than the onpaint handler of something like a label. The best i can understand it is that using the onpaint method uses a GDI+ graphics class for drawing and is much faster.
This is certainly evident in another dotnet project I have also been working on - I've been writing a new RAM player for sequence playback, and have been using GDI+ in order to speed things up. It is currently working about 3-4 times quicker than the one in max to cache frames.
I'm not using on paint, just setting the backGroundImage and Image properties directly. I will look into the on paint and see what I get. It is slow as I'm drawing pixels but I didn't think that it should be that slow.
I'll let you know what I find today.
I'm just reading up on the paint event, If I get this right it looks like you can determine where it paints, does this mean I can use it to position a the bitmap that I'm placing in the Image property?
LoneRobot
07-15-2008, 11:42 AM
yes - the way the onpaint will work is you pass it the background image and it is converted to a GDI graphics via Graphics.FromImage
then, you draw whatever you want on it (there are many brushes, shapes etc to do this) and then dispose of your resources when done.
this was one example of this but there's absolutely loads around -
http://www.bobpowell.net/modifyImage.htm
if you want to overlay a bitmap of the cursor you can do this too with a transparent color passed as the background - there is an example here -
http://www.syncfusion.com/faq/windowsforms/faq_c3c.aspx#q657q
one additional option is that you could pass the cursor postion to the onpaint event and that would allow you to draw your overlay at the correct postion. i have used this on my ram player to paint the progress of the sequence load on the bottom of the frame, replacing the need for a progressbar. you can setup a case expression to handle different paint states according to properties of the class you are using.
again, there are quite a few ways of doing this, and this may not be the best for what you are doing but it will certainly be quicker.
In looking through some of that it might just work. Thanks. I will probably get to this next week now, have to deliver the first tests today work work on targets more so I'm going to have to try and speed up the UI once the client complains about it:S
Here is where I'm at with the UI, Lots more to go as far as tools go. I have done it all in a scritped modifier as I know that it will not change really for other characters and I find scripted plugins easier to deal with then CA defs.
You can use shift or alt to constrain moving to one axis or another. Shift+Ctrl to press the imbedded buttons and right click to get a pop up menu with the same options.
http://paulneale.com/temp/joyStickControls2.jpg
LoneRobot
07-15-2008, 04:33 PM
are you binding the movement to the tracks you are controlling - ie does it update on scrub, or just using it to set the values?
It updates on scrubbing, I'm using time call backs when each of the rollouts is open to pass the data from the params to each control. I thought of using on get val events in the param block but that can get slow as well I have found unless you do a pile of management on them. Each time the file is opened for instance they will be called so load times go up unless you determine that it shouldn't change.
OK I have it working using On PicBox Paint, it paints directly into the backGroundImage property, I wonder if it is possible to paint into the image property and then just move it using the transform methods? At no point have I told it to paint into the backGroundImage so how can it be changed?
Let me rephrase that, it isn't painting into either the backGroundImage or image properties. Very interesting.
LoneRobot
07-15-2008, 07:14 PM
I've read about strange results before using the onpaint of a picturebox. image and background image are properties in the control, and the onpaint handler will overwrite these until the control is invalidated and redrawn. if you are then changing these properties you may lose anything you have done in the onpaint. a better explanation of this is here -
http://www.bobpowell.net/picturebox.htm
I took his advice and used the onpaint of a panel control on my joystick control. so the code in visual studio for the onpaint event is like this -
Private Sub PanelBase_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PanelBase.Paint
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias
If BackgroundStyle = BackgroundStyles.Grid Then
Dim Rect1 As System.Drawing.Drawing2D.GraphicsPath = New System.Drawing.Drawing2D.GraphicsPath(New System.Drawing.PointF() {New System.Drawing.PointF(108.5!, 0.000001057378!), New System.Drawing.PointF(117.0604!, -0.000006580535!), New System.Drawing.PointF(124.0!, 6.939575!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 108.5!), New System.Drawing.PointF(124.0!, 117.0604!), New System.Drawing.PointF(117.0604!, 124.0!), New System.Drawing.PointF(108.5!, 124.0!), New System.Drawing.PointF(15.5!, 124.0!), New System.Drawing.PointF(6.939589!, 124.0!), New System.Drawing.PointF(0.0000005662081!, 117.0604!), New System.Drawing.PointF(0.0000003830284!, 108.5!), New System.Drawing.PointF(0.0000003830282!, 15.5!), New System.Drawing.PointF(-0.000001943159!, 6.939591!), New System.Drawing.PointF(6.939581!, 0.000002968986!), New System.Drawing.PointF(15.49999!, -0.0000002913209!)}, New Byte() {0, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 131})
Dim Rect1Brush As HatchBrush = New System.Drawing.Drawing2D.HatchBrush(System.Drawing.Drawing2D.HatchStyle.DottedGrid, Me.CrossHairColor, Me.BackStyleColor)
e.Graphics.FillPath(Rect1Brush, Rect1)
Rect1.Dispose()
Rect1Brush.Dispose()
ElseIf BackgroundStyle = BackgroundStyles.Flat Then
Dim Rect1 As System.Drawing.Drawing2D.GraphicsPath = New System.Drawing.Drawing2D.GraphicsPath(New System.Drawing.PointF() {New System.Drawing.PointF(108.5!, 0.000001057378!), New System.Drawing.PointF(117.0604!, -0.000006580535!), New System.Drawing.PointF(124.0!, 6.939575!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 108.5!), New System.Drawing.PointF(124.0!, 117.0604!), New System.Drawing.PointF(117.0604!, 124.0!), New System.Drawing.PointF(108.5!, 124.0!), New System.Drawing.PointF(15.5!, 124.0!), New System.Drawing.PointF(6.939589!, 124.0!), New System.Drawing.PointF(0.0000005662081!, 117.0604!), New System.Drawing.PointF(0.0000003830284!, 108.5!), New System.Drawing.PointF(0.0000003830282!, 15.5!), New System.Drawing.PointF(-0.000001943159!, 6.939591!), New System.Drawing.PointF(6.939581!, 0.000002968986!), New System.Drawing.PointF(15.49999!, -0.0000002913209!)}, New Byte() {0, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 131})
Dim Rect1Brush As New SolidBrush(Me.BackStyleColor)
e.Graphics.FillPath(Rect1Brush, Rect1)
Rect1.Dispose()
Rect1Brush.Dispose()
ElseIf BackgroundStyle = BackgroundStyles.Check Then
Dim Rect1 As System.Drawing.Drawing2D.GraphicsPath = New System.Drawing.Drawing2D.GraphicsPath(New System.Drawing.PointF() {New System.Drawing.PointF(108.5!, 0.000001057378!), New System.Drawing.PointF(117.0604!, -0.000006580535!), New System.Drawing.PointF(124.0!, 6.939575!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 15.5!), New System.Drawing.PointF(124.0!, 108.5!), New System.Drawing.PointF(124.0!, 117.0604!), New System.Drawing.PointF(117.0604!, 124.0!), New System.Drawing.PointF(108.5!, 124.0!), New System.Drawing.PointF(15.5!, 124.0!), New System.Drawing.PointF(6.939589!, 124.0!), New System.Drawing.PointF(0.0000005662081!, 117.0604!), New System.Drawing.PointF(0.0000003830284!, 108.5!), New System.Drawing.PointF(0.0000003830282!, 15.5!), New System.Drawing.PointF(-0.000001943159!, 6.939591!), New System.Drawing.PointF(6.939581!, 0.000002968986!), New System.Drawing.PointF(15.49999!, -0.0000002913209!)}, New Byte() {0, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 131})
Dim Rect1Brush As HatchBrush = New System.Drawing.Drawing2D.HatchBrush(System.Drawing.Drawing2D.HatchStyle.LargeCheckerBoard, Me.CrossHairColor, Me.BackStyleColor)
e.Graphics.FillPath(Rect1Brush, Rect1)
Rect1.Dispose()
Rect1Brush.Dispose()
End If
If CrossHairStyle <> CrossHairStyles.None Then
Dim pn As New Pen(Me.CrossHairColor, Me.CrossHairWidth)
pn.DashStyle = Me.CrossHairStyle
Dim pt1 As New Point(Me.MovableLabel.Location.X + 12, 0)
Dim pt2 As New Point(Me.MovableLabel.Location.X + 12, 126)
Dim pt3 As New Point(0, Me.MovableLabel.Location.Y + 12)
Dim pt4 As New Point(126, Me.MovableLabel.Location.Y + 12)
e.Graphics.DrawLine(pn, pt1, pt2)
e.Graphics.DrawLine(pn, pt3, pt4)
pn.Dispose()
End If
If DisplayText Then
Dim TextFont As System.Drawing.Font = New System.Drawing.Font("Verdana", 8, CType(0, System.Drawing.FontStyle), System.Drawing.GraphicsUnit.Point, 1)
Dim TextBrush As System.Drawing.SolidBrush = New System.Drawing.SolidBrush(Me.DisplayTextColor)
e.Graphics.DrawString(Me.Text, TextFont, TextBrush, 5, 5)
TextFont.Dispose()
TextBrush.Dispose()
End If
e.Graphics.Dispose()
End Sub
please go easy all you code gurus, im a novice vb programmer!
I don't know a thing about VB, what is it you are doing here, making your own dotNet control? If so I can't do that in Max script and I don't know VB, C# or anything else I can use.
I have it working really well right now except the onPaint is getting spammed. Not sure how to stop this. Any test I add in there kills it completely.
Also I'm not sure how I should be using dispose(), anything I try throws errors. What shoudl I be disposing? and when?
LoneRobot
07-15-2008, 07:50 PM
ah sorry paul, i got you. I assumed that as your previous incarnation was max8 this was a dotnet version in visual studio. It could be that the refresh/invalidate of the control is being triggered somewhere else (perhaps in the command panel, or dotnetcontrol object or something, your guess as good as mine) so is clearing whatever you do. I am indeed building a custom dotnet control. i decided that having the class control everything would make an implementation easier, although i realize its an extra dll. My attempts at this solely in max would have been impossible doing it your way, I found i was fumbling around too much and using vs as a testing environment worked better. I hope to release a bunch of controls really soon.
Sorry i cant help more but maybe you could try using the panel control paint to do your GDI+ drawing and calling the controlname.invalidate() function (which triggers a redraw of the control) on the mouse move event.
LoneRobot
07-15-2008, 07:58 PM
Also I'm not sure how I should be using dispose(), anything I try throws errors. What shoudl I be disposing? and when?
you use dispose() to clear any GDI+ resources from memory - if the paint event fires repeatedly and you havent released them, you could tie up memory. so as i understand it, you release any brushes/shapes you use in the paint event after you have finished with them, as well as the GDI+ graphics object. you can get the idea from the code i posted.
a paint handler has the event handler argument - System.Windows.Forms.PaintEventArgs that passes the graphics object in it so in theory you should be able to access it by calling e.Graphics.Dispose()
the dotnetobject event handlers in max should allow you to access these.
I have something with pictureBox again. I was unnessesarily loading the backGroundImage in the paint handler and that was forcing it to spam. Doesn't look like I'm getting any major memory leaks any more and it draws really nice. Still not sure where to dispose()
OK after all that I have this, still have a leak if you can find it. I'm loading two images one that is 81x81 and the target that is 20x20.
try(destroyDialog testR)catch()
rollout testR "Test"
(
local col=dotNetClass "system.drawing.color"
local bgm=dotNetObject "system.drawing.bitMap" ((getDir #UI)+"\\\Icons\penProductions\\joyStick80Sq.bmp")
local targetBm=dotNetObject "system.drawing.bitMap" ((getDir #UI)+"\\\Icons\penProductions\\target.bmp")
local targRad=targetBm.width/2
local rec=dotNetObject "System.Drawing.Rectangle" 10 10 20 20
dotNetControl picBox "system.windows.forms.pictureBox" width:81 height:81
fn drawTarget control paintArg bg target=
(
if rec.x<-targRad then rec.x= -targRad
if rec.x>bg.width-targRad then rec.x=bg.width-targRad
if rec.y<-targRad then rec.y=-targRad
if rec.y>bg.height-targRad then rec.y=bg.height-targRad
graphics=paintArg.graphics
paintArg.dispose()
Graphics.DrawImage target rec
)
on picBox mouseMove arg do
(
if arg.button==arg.button.left then
(
rec.x=arg.x-targRad
rec.y=arg.y-targRad
picBox.Invalidate()
)
)
on picBox mouseUp arg do
(
)
on picBox paint senderArg paintArg do
(
drawTarget senderArg paintArg bgm targetBm
)
on testR open do
(
targetBm.makeTransparent()
picBox.backGroundImage=copy bgm
rec.x=bgm.width/2-targRad
rec.y=bgm.height/2-targRad
)
on testR close do
(
bgm.dispose()
targetBm.dispose()
)
)
createDialog testR
clearListener()
LoneRobot
07-15-2008, 09:35 PM
hi paul,
i usually dispose at the end of my drawing function - but the way you have done it doesnt look like it should have any memory problems. The only thing i have done differently in VS is dispose the graphics surface, not the painteventargs. However, this is still a legitimate call and since the painteventarg contains reference to the grpahics surface i dont know. I get the same exception as you if i call graphics.dispose() so im not sure if it is actually disposing correctly anyway.
Can you describe more about when you see a leak, is it when you move the control handle around?
ive just tested drawing directly onto the picturebox surface with GDI+. I am thinking that with your graphics you are using you could recreate this easilly with system.brushes without having to load the bitmaps. just another option! i have left the parentheses off the dispose call - with them it gives an exception, but without i dont think it's doing anything. i will look into this when i can.
rollout dnpb "" width:140 height:140
(
dotnetcontrol pb "System.Windows.Forms.Picturebox" pos:[5,5] width:130 height:130
on pb paint e do
(
g = e.graphics
GDIsmoothing = dotnetclass "System.Drawing.Drawing2D.SmoothingMode"
g.SmoothingMode = GDIsmoothing.AntiAlias
g.FillRectangle abrush 3 3 124 124
g.FillEllipse anotherbrush 50 50 30 30
-- this line is problematic!!
-- g.dispose
)
on dnpb open do
(
acolor = dotnetclass "system.drawing.color"
abrush = dotnetobject "System.Drawing.SolidBrush" acolor.yellow
anotherbrush = dotnetobject "System.Drawing.SolidBrush" acolor.Fuchsia
)
on dnpb close do
(
-- as are these !!
-- abrush.dispose
-- anotherbrush.dispose
)
)
createdialog dnpb
LoneRobot
07-16-2008, 12:56 AM
okay my second take on this GDI+ only method. There is something not right with using the picturebox as a drawing surface. Because if i substitute a panel for the picturebox, the dispose function calls without an exception. What i am seeing that is unwanted is a flicker when moved quickly. I have also set up a test painting onto a label control. this throws an exception if i try to dispose of the graphics in the paint handler. If i remove it, it functions normally without flicker.
I do not know if doing this creates a memory problem. if i perform the same dispose function in a label's paint handler in Visual studio, I also get the same exception, so this must be a behaviour of the label control.
In VB i would solve flicker by setting the control double buffered property to true. This usually solves any redraw flicker. However i cant seem to set this in an MXS form and it appears that some controls like panel, even though they should inherit this class will still not allow you to set doublebuffered to true.
rollout dnpb "GDI+ paint" width:280 height:140
(
local acolor = dotnetclass "system.drawing.color"
local abrush = dotnetobject "System.Drawing.SolidBrush" acolor.yellow
local anotherbrush = dotnetobject "System.Drawing.SolidBrush" acolor.Fuchsia
local pos1x = 65
local pos1y = 65
local pos2x = 65
local pos2y = 65
dotnetcontrol pb "System.Windows.Forms.label" pos:[5,5] width:130 height:130
dotnetcontrol pb1 "System.Windows.Forms.panel" pos:[145,5] width:130 height:130
on pb paint e do
(
g = e.graphics
GDIsmoothing = dotnetclass "System.Drawing.Drawing2D.SmoothingMode"
g.SmoothingMode = GDIsmoothing.highspeed
g.FillRectangle abrush 3 3 124 124
g.FillEllipse anotherbrush (pos1x-15) (pos1y-15) 30 30
)
on pb1 paint e do
(
g = e.graphics
GDIsmoothing = dotnetclass "System.Drawing.Drawing2D.SmoothingMode"
g.SmoothingMode = GDIsmoothing.highspeed
g.FillRectangle abrush 3 3 124 124
g.FillEllipse anotherbrush (pos2x-15) (pos2y-15) 30 30
g.dispose()
)
on pb mouseMove sender arg do
(
if arg.button==arg.button.left then
(
pos1x = arg.x
pos1y = arg.y
pb.invalidate()
)
)
on pb1 mouseMove sender arg do
(
if arg.button==arg.button.left then
(
pos2x = arg.x
pos2y = arg.y
pb1.invalidate()
)
)
--
on dnpb close do
(
abrush.dispose()
anotherbrush.dispose()
)
)
createdialog dnpb
again, the more i experiment in max makes me think that for me, building a custom user control in VS is the best way to go....
hi paul,
Can you describe more about when you see a leak, is it when you move the control handle around?
I'm not sure that I am seeing memory issues, just tried shutting everything down on my system and leaving it when I went for dinner. I didn't have any more memory used then when I left. Just had the mouse hovering over the
This works now, I just don't think that you had things in scope.
rollout dnpb "" width:140 height:140
(
local acolor = dotnetclass "system.drawing.color"
local abrush = dotnetobject "System.Drawing.SolidBrush" acolor.yellow
local anotherbrush = dotnetobject "System.Drawing.SolidBrush" acolor.Fuchsia
dotnetcontrol pb "System.Windows.Forms.Picturebox" pos:[5,5] width:130 height:130
on pb paint e do
(
g = e.graphics
GDIsmoothing = dotnetclass "System.Drawing.Drawing2D.SmoothingMode"
g.SmoothingMode = GDIsmoothing.AntiAlias
g.FillRectangle abrush 3 3 124 124
g.FillEllipse anotherbrush 50 50 30 30
-- this line is problematic!!
g.dispose
)
on dnpb close do
(
-- as are these !!
abrush.dispose
anotherbrush.dispose
)
)
createdialog dnpb
okay my second take on this GDI+ only method. There is something
Interesting that the forms.panel is showing the same problem that the imgTag is in Max. I have a much brighter out look on doing it with pictureBox at this point. Although I don't have all the controls working agian I'm nto seeing any of the issues I was using setPixel. I thank you for pointing me down this path. I'm not getting flicker at all and the amount of code that I need to manage this has gone way down. I will report back with speed once I get it all working and see what kind of redraw that I'm getting compared to before and if it is in an acceptable range.
LoneRobot
07-16-2008, 01:31 AM
This works now, I just don't think that you had things in scope.
ya i saw that and fixed it in my second post. the dispose() issue is weird though, im seeing differences between objects that i wasnt expecting. Good to know for the future. I will crack on with my dotnet user control and see what happens! cheers Paul, this is an interesting thread discussion! keep 'em coming!
LoneRobot
07-16-2008, 01:36 AM
actually while i think about it, i found a great tool that is a WYSIWYG GDI+ editor. It helped me loads by showing the drawing options open to GDI+. it can generate the code for VB or C# but even if you can't use this you can see the implementation enough to use it in Max dotnet. best of all, its FREE!!!
http://mrgsoft.com/products/GDIPlusArchitect/default.htm
Here is the biggest issue that I have so far with doing this, undo isn't working. When you scrub a control and you have undo on it is recording all the changes as your scrub so you need to undo many times to undo a single change.
Is there a way to stuff a specific event into the undo stack? So I could record the value of the parameters on mouse up and put that last value in the undo stack?
Of course as I posted I thought of a solutions and it works.
In the on mouseDown even I just do
animate off (undo on (theDrivenparam=theDrivenParam))
Can any one think of anything cleaner?
Looks nicer now as well
http://paulneale.com/temp/joyStickControls3.jpg
OK another odd problem. I have three rollouts, if all rollouts are in the same subRollout, even in the modifier panel all is well...I think. If I extend my modifier panel to two wide or create a dialog that has two subs in it and load one rollout into one and two into another when one of the two that are together rolls up it tries to refresh all my pictureBox's. It is odd as well that they get some crazy value passed to all of them. If I widen the modifier panel when they are in there it goes crazy as well. So is there a way to know that you don't have to update?
Light
07-17-2008, 01:17 AM
Hey Paul,
You can use the MouseUp event of the PictureBox. You might also need to use TheHold instead of the undo context, like:
TheHold.Begin()
DoSomething()
TheHold.Accept "Something"
Light
I'm having some serious problems with this and speed updating the image. I don't think that it is the pictureBox directly. What I have is a modifier with parameters that drive targets. When you move the joyStick it sets the value of the param and calls an invalidate and the paint event calls a function, passing the parameters into the function and sets the value of an System.Drawing.Rectangle to update the image in the joyStick. As the scene gets more complex the less the invalidate actualy gets called if there parameters are being passed. Wow, does this make any sense at all? In simple tests it works great, drop it in a scene that has DX shaders turned on and it barely updates at all.
Any suggestions or any one that wants to see code?
Is there a way to NOT use the paint event?
LoneRobot
07-21-2008, 10:26 PM
Hi Paul,
Your method is not much different to the way i have set up my joystick control - i will try to get it finished this week and i will test it in a scene to see if i get the same slowdowns as you. Though I'm not drawing to a picturebox and mine is a custom dotnet user control so it will be interesting to see how it fares compared to your MXS version.
I dont know if this is something you've already tried, but here is something i lifted from a C# forum about redraw methods - there are three and they might make a difference when calling multiple times -
Invalidate marks the control as in need of repainting, but
doesn't immediately repaint (the repaint is triggered when everything else has
been taken care of and the app becomes idle).
Update causes the control to immediately repaint if any portions have been
invalidated.
Refresh causes the control to invalidate, and then update (i.e. immediately
repaint itself).
Most of the time Invalidate is sufficient, and advisable as you can do a bunch
of invalidations (either explicit or implicit) and then let the control repaint
itself when the app is idle. It is advisable to use Update or Refresh when you
want the control to immediately repaint because the app will not be idle for a
user-noticable period of time.
if you want to send me an example, PM me and i'd be happy to take a look.
At this point I'm thinking that it isn't the dotNetControl but the time it take to get values from the parameters, or some combination. It will be interesting to see what happens with yours. I only see it once I have it all connected up to a character, on it's own or in a simple scene it works great. This is with only one control working as well. It isn't like I have 10 of then trying to redraw all at the same time.
Just tried
senderArg.Invalidate()
senderArg.update()
and it works, I had tried update() on it's own and it was even slow but looks like you need to call the invalidate and then the update and it forces it. Thanks, reading that bit you found may have done it for me.
LoneRobot
07-22-2008, 09:27 AM
I had the same issue with mine Paul, as invalidate only flags an update it queues it until the next redraw. This is usually pretty quick but on a fast updating user control you need it to do it immediately.
One issue with repeated repaint calls apart from speed is the flicker issue which i have come across also with this method. The flash is caused by the control erasing the image and displaying a blank background before it redraws the control. It is possible in dotnet to override the paint method to do nothing and perform the redraw elsewhere.
the other option is to use the SetStyle() method on a dotnet form to set the AllPaintingInWmPaint property to true. This will allow the form to disregard paint calls.
One option to try might be to try using this SetStyle method is possible, and move the redraw to the mouse move event rather than the paint event. At the moment the paint it is passing the redraw from the event to the other. The mouse event invalidates so it redraws, then sends the coordinates to the drawtarget function which invalidates and redraws again. perhaps you could avoid this double redraw by removing the paint call, doing all the work in the mouse move, sending that to the drawtarget sub and invalidate finally at the end when all the drawing is done.
The other property of use would most certainly be doublebuffering. with this, the image is built in memory instead on the control and then drawn on after onto the form. It takes the same amount of time to do this but dramatically reduces flicker.
Well all sounds good. At the moment I'm not getting any flicker. How ever I would like to get rid of the paint event as I think it is the cause of other issues that I ran in to and have been trying to correct. The paint calls are being called when the dialog goes behind another dialog and then gets focus again. This causes some very strange results.
So, how do I go about painting without the paint event? I have been looking but not seen anything that jumps out at me at the moment. Also removing the paint event would simplify the code just that much more and give me a bit more control as to when painting happens.
Thanks for all the help. I'll post a final working control when I know that it isn't doing anything nasty.
Looks like I will need "system.drawing.graphics" but not sure how to apply it with out the paint event.
magicm
07-22-2008, 12:36 PM
Looks like I will need "system.drawing.graphics" but not sure how to apply it with out the paint event.
You can create a graphics object yourself by invoking the .CreateGraphics method of a control:
local g = MyControl.CreateGraphics()
g.FillRectangle brush rect
g.Dispose()
Cheers,
Martijn
Doh, wasn't looking there. Good stuff. I have it working as it is so I will poke around with this and see if I can simplify it later. So far it is responding quickly on my lap top without flashing and I have an old lap top. Unfortunitly I'm sitting in the emergency room with my wife trying to get work done. We might get free medical care in Canada but you sure do have to wait for it, 6 hours+ so far today.
BobbyB
08-24-2008, 01:01 AM
So how did everything progress with this ? It would be a cool thing to know how to do.
Much of it is posted here, but here is a basic over view of what I needed to do.
I loaded a image in to the backgroundImage property. For the target I created another BMP with a backGround color that was the same color as the backGroundColor property of the dotnet pictureBox controls, this is what is used as the transperent color.
To draw the target I'm using the GDI drawing methods that have been duscussed here to place the target BMP where the mouse is.
I have several event handlers for each of the joy stuck controls. The paint event that calls the drawing of the target function, mouseDown, mouseUp and mouseOut I think. I would have to have a look. With the way that I did it I now have the ability to multi select controls so that they can be reset and keyed through a RC menu as well as create groups of controls for saving poses just like PENAttribute Holder 2 but on streoids. For the pose capture I have a treeView and you can just scrub on the items in the tree and again I use GDI drawing, this time just using a filled rectangle as the slider. To show the selections of controls I use a system.drawing.pen and out line the control in yellow.
All in all it works well, it is a little slow to draw all the controls at once when scrubbing animation but it is acceptable. To get the controls to update durring scrubbing I'm using a timeChange callBack that gets the values from the parameters and applies them back to the poictureBox controls. I store an array of structs that hold the information about which parameters are to work with which pictureBox controls.
I can't show the final results as the client will not allow it at this point. It is working very well and they love the tool and find it very easy to use. One cool feature that I added was being able to copy and paste values from one to the other with the ability to mirror the value on the X. I have other feature like being able to contrain the control to either X or Y depending on shift, ctrl alt being pressed and several other options. All the poses are store in an XML file on the server so that when some one saves a pose every one gets it.
TzMtN
08-24-2008, 04:00 PM
That sounds really cool Paul, and looks great too from the images you've posted..:applause:
Floating_Inkpot
12-01-2008, 09:28 AM
Hello,
This post is very very interesting , thanks for all the stuff and ideas discussed!
it was a very great help for me :)
I am using the dotnet4Controls previously posted here and it works very well for me. Although i have a little problem. When i move the orange pointer, it leaves a trail. It seems that the dotnet label doesn't update very well.
http://belokanweb.free.fr/DotNetTrails.jpg
Here is the source code previously posted :
(
/*
DotNet Joystick Test.
Usage:
Hold SHIFT while dragging to allow X movement only
Hold CONTROL while dragging to allow Y movement only
Right click while dragging to cancel and revert to previous position
Double click to reset stick
To add a joystick:
1. Add another "System.Windows.Forms.Label" dotNetControl below
2. Add 4 event handlers (see 'Joystick_01 MouseDown' etc below)
3. Add the control to the 'Joysticks' and 'JoystickTitles' arrays (in the rollout's open handler)
*/
rollout JoystickRollout "DotNet Joystick Test" width:184 height:184
(
local Joysticks
local JoystickTitles
dotNetControl Joystick_01 "System.Windows.Forms.Label" pos:[8,8] width:80 height:80
dotNetControl Joystick_02 "System.Windows.Forms.Label" pos:[96,8] width:80 height:80
dotNetControl Joystick_03 "System.Windows.Forms.Label" pos:[8,96] width:80 height:80
dotNetControl Joystick_04 "System.Windows.Forms.Label" pos:[96,96] width:80 height:80
/*
Moves stick to the specified position.
*/
fn Joystick_Position s pos =
(
local x = if keyboard.controlPressed then s.Tag.X else (if pos.x < 0 then 0 else if pos.x > s.Width - 2 then s.Width - 2 else pos.x)
local y = if keyboard.shiftPressed then s.Tag.Y else (if pos.y < 0 then 0 else if pos.y > s.Height - 2 then s.Height - 2 else pos.y)
s.Controls.Item[0].Location.X = x - 4
s.Controls.Item[0].Location.Y = y - 4
/* Uncomment below two lines to view joystick outputs in listener. */
local i = findItem Joysticks s
format "Joystick % : [%, %]\n" i (x as float / (s.Width - 2)) (y as float / (s.Height - 2))
)
/*
Event delegates for joysticks.
*/
fn Joystick_MouseDown s e =
(
if (dotNet.CompareEnums e.Button e.Button.Left) then
(
s.Tag = s.Controls.Item[0].Location
Joystick_Position s [e.X, e.Y]
)
if (dotNet.CompareEnums e.Button e.Button.Right) and s.Tag != undefined then
(
s.Controls.Item[0].Location = s.Tag
s.Tag = undefined
)
)
fn Joystick_MouseMove s e =
(
if (dotNet.CompareEnums e.Button e.Button.Left) and s.Tag != undefined then
Joystick_Position s [e.X, e.Y]
)
fn Joystick_MouseUp s e =
(
if (dotNet.CompareEnums e.Button e.Button.Left) and s.Tag != undefined then
s.Tag = undefined
)
fn Joystick_DoubleClick s e =
(
Joystick_Position s [(s.Width - 2) / 2, (s.Height - 2) / 2]
)
/*
Event handlers for joysticks.
*/
on Joystick_01 MouseDown s e do Joystick_MouseDown s e
on Joystick_01 MouseMove s e do Joystick_MouseMove s e
on Joystick_01 MouseUp s e do Joystick_MouseUp s e
on Joystick_01 DoubleClick s e do Joystick_DoubleClick s e
on Joystick_02 MouseDown s e do Joystick_MouseDown s e
on Joystick_02 MouseMove s e do Joystick_MouseMove s e
on Joystick_02 MouseUp s e do Joystick_MouseUp s e
on Joystick_02 DoubleClick s e do Joystick_DoubleClick s e
on Joystick_03 MouseDown s e do Joystick_MouseDown s e
on Joystick_03 MouseMove s e do Joystick_MouseMove s e
on Joystick_03 MouseUp s e do Joystick_MouseUp s e
on Joystick_03 DoubleClick s e do Joystick_DoubleClick s e
on Joystick_04 MouseDown s e do Joystick_MouseDown s e
on Joystick_04 MouseMove s e do Joystick_MouseMove s e
on Joystick_04 MouseUp s e do Joystick_MouseUp s e
on Joystick_04 DoubleClick s e do Joystick_DoubleClick s e
/*
Initialize joysticks when rollout is opened.
*/
on JoystickRollout open do
(
-- Array containing joystick controls and their captions.
Joysticks = #(Joystick_01, Joystick_02, Joystick_03, Joystick_04)
JoystickTitles = #("Joystick", "Another one", "Yet another joystick", "Last one")
-- Initialize joysticks.
for i = 1 to Joysticks.count do
(
-- Setup joystick background.
Joysticks[i].ForeColor = (dotNetClass "System.Drawing.Color").FromARGB 164 164 164
Joysticks[i].BackColor = (dotNetClass "System.Drawing.Color").FromARGB 255 255 255
Joysticks[i].BorderStyle = (dotNetClass "System.Windows.Forms.BorderStyle").FixedSingle
Joysticks[i].Padding = dotNetObject "System.Windows.Forms.Padding" 2 2 2 2
Joysticks[i].TextAlign = (dotNetClass "System.Drawing.ContentAlignment").TopCenter
Joysticks[i].Text = JoystickTitles[i]
-- Setup "stick".
Stick = dotNetObject "System.Windows.Forms.Label"
Stick.Enabled = false
Stick.Size = dotNetObject "System.Drawing.Size" 8 8
Stick.BackColor = (dotNetClass "System.Drawing.Color").FromARGB 255 128 0
Stick.BorderStyle = (dotNetClass "System.Windows.Forms.BorderStyle").FixedSingle
Stick.Location = dotNetObject "System.Drawing.Point" (((Joysticks[i].Width - 2) / 2) - 4) (((Joysticks[i].Height - 2) / 2) - 4)
-- Add "stick" to joystick.
Joysticks[i].Controls.Add Stick
)
)
)
createDialog JoystickRollout
)
This happens only when i output value to the listener by uncommenting this two lines.
local i = findItem Joysticks s
format "Joystick % : [%, %]\n" i (x as float / (s.Width - 2)) (y as float / (s.Height - 2))
It happens also when i link the X and Y values to an object in my scene.
Strangely, i don't see this sort of trails on my computer at home.
I have roughly the same PC than at work. Vista pro, 3dsmax 2008.
Does someone have an idea ? Is it happening on your computer ?
Can it be graphic card drivers or windows configuration ?
Floating_Inkpot
12-01-2008, 09:43 AM
I finally found the solution bymyself...
maybe, writing the question helped me to find the right method to search the solution... ^^
So what i did, is after the updating of information in the listener, i wrote : s.refresh()
and the awful trails were gone :)
local i = findItem Joysticks s
format "Joystick % : [%, %]\n" i (x as float / (s.Width - 2)) (y as float / (s.Height - 2))
s.refresh()
Glad you found the problem. You need to constantly refresh the label to make sure trials are not left but I didn't find that it slowed it down a whole lot. I will be working on version 2 of the system that I put together soon, should be interesting how it ends up working out.
LoneRobot
12-01-2008, 11:26 AM
So what i did, is after the updating of information in the listener, i wrote : s.refresh()
and the awful trails were gone :)
Hello,
That's looking good.
There is a brief mention of this in post 86 which lists the differences between refresh and invalidate on a control. There is also an overloaded method on invalidate that allows you to just refresh a region or rectangle to be redrawn, in case you don't want to invalidate the whole control. Refresh() just does the whole control and any child controls.
in case you haven't seen, there is a beta download of a dotnet joystick control here (http://www.lonerobot.com/joystick.html)
anggahenggara
12-01-2008, 04:56 PM
Hi guys...you RockS!!
I had try your script that post in here, whoawwww it's great!!
I have i rigged character with shape/helper obj controller like usual
then i want change all those controller with scripted Custom UI that you post in here.
My quation is, how to do that? how to link your UI with my character?and how to make it behave like object in Max (i mean UI controller can make keyframe when AutoKey is On then controller moving as I playback animation)
Thank you...for your sharing..
anggahenggara
12-01-2008, 04:57 PM
can anyone answer my question? help me:cry:
CGTalk Moderation
12-01-2008, 04:57 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.
vBulletin v3.0.5, Copyright ©2000-2012, Jelsoft Enterprises Ltd.