DotNet form, Create many controls at once.

Become a member of the CGSociety

Connect, Share, and Learn with our Large Growing CG Art Community. It's Free!

 
Thread Tools Search this Thread Display Modes
  1 Week Ago
DotNet form, Create many controls at once.

Hi guys,
I'm building an object lister script. The main job is automatically creates many controls when needed. it will have moreless 80 controls and divided to 6 groups so user can hide unneeded group they want.

here the simplified code:

try(testForm.close())catch()
::Test_LastCreatedCtrl=undefined
testForm= dotNetObject "MaxCustomControls.MaxForm"
( testForm.text= "test"
 testForm.autosize= true
 testForm.MaximumSize= dotNetObject "System.Drawing.Size" (sysInfo.desktopSize[1]-100) 500
 testForm.AutoScroll= true
 dotNet.SetLifeTimeControl testForm #dotNet
 
 testButton= dotNetObject #button
 testButton.text= "createControls"
 testForm.Controls.add testButton
 dotNet.SetLifeTimeControl testButton #dotNet
 
 testButton2= dotNetObject #button
 testButton2.text= "clearControls"
 testButton2.left= 200
 testForm.Controls.add testButton2
 dotNet.SetLifeTimeControl testButton2 #dotNet
 
 fn setCtrlTop_fn ctrl=
 ( local theTop=
  ( if ::Test_LastCreatedCtrl == undefined then
   ( local ctrlsCount= testForm.controls.count-1
    local mostBottom= testForm.Controls.item[ctrlsCount].bottom
   ) else
   ( ::Test_LastCreatedCtrl.bottom
   )
  )
  ctrl.top= TheTop+3
 )
 fn createControls_fn=
 ( for i=1 to 2 do
  ( 
   local thePanel= dotnetobject #FlowLayoutPanel
   thePanel.FlowDirection= thePanel.FlowDirection.LeftToRight
   thePanel.AutoSize= true
   thePanel.MaximumSize= dotNetObject "System.Drawing.Size" 3000 30
   thePanel.margin= dotNetObject #padding 3 3 3 3
   theBackColor= 
   ( local val= random 30 150
    (dotNetClass "system.drawing.color").fromArgb val val val
   )
   thePanel.BackColor= theBackColor
   setCtrlTop_fn thePanel
   testForm.Controls.add thePanel
   ( for x=1 to 40 do
    ( local theControls= #(
      ( btn= dotNetObject #button
       btn.text= "test"
       btn
      ),
      ( upDown= dotNetObject #numericUpDown
       upDown.value= random 1 100
       upDown
      ),
      ( cb= dotNetObject #checkBox
       cb.text= "testing"
       cb
      )
     )
     local ctrl= theControls[random 1 theControls.count]
     thePanel.Controls.add ctrl
     dotNet.SetLifeTimeControl ctrl #dotNet
    )
   )
   ::Test_LastCreatedCtrl= thePanel
   dotNet.SetLifeTimeControl thePanel #dotNet
  )
 )
 dotNet.AddEventHandler testButton #click createControls_fn
 
 fn clearControls_fn=
 ( for i=testForm.Controls.Count-1 to 2 by -1 do
  ( testForm.Controls.RemoveAt i
  )
  ::Test_LastCreatedCtrl=undefined
  (dotnetclass "system.gc").collect()
  gc()
 )
 dotNet.AddEventHandler testButton2 #click clearControls_fn
 
 fn testForm_Closed_fn=
 ( ::Test_LastCreatedCtrl=undefined
  (dotnetclass "system.gc").collect()
  gc()
 )
 dotNet.AddEventHandler testForm #closed testForm_Closed_fn
 
 testForm.ResumeLayout()
)
testForm.showModeless()


The problem are
1. when user run it so many times at one 3dsMax session, it will be heavier to operate. There is kind of memory leak even though i used dotnet gc and max gc at form closed event.
2. Control creation process will be very heavy if i include createControl functions in nodeCreated callback. Delay it using dotnet timer help it a little bit but not significant.

Additional question:
in point 2, it seems not possible to create controls using background thread. if i am not wrong, how do you guys trick it?
 
  1 Week Ago
Why do you need to recreate whole ui from scratch on node created event? Have you tried addSubRollout / removeSubRollout?
btw. All ui & node related manipulations should be done in main thread.
 
  6 Days Ago
Originally Posted by Serejah: Why do you need to recreate whole ui from scratch on node created event? Have you tried addSubRollout / removeSubRollout?
btw. All ui & node related manipulations should be done in main thread.
sorry for long delay,
Is it possible to insert SubRollout in dotNet form?
 
  3 Days Ago
Originally Posted by noel20: The problem are
1. when user run it so many times at one 3dsMax session, it will be heavier to operate. There is kind of memory leak even though i used dotnet gc and max gc at form closed event.
2. Control creation process will be very heavy if i include createControl functions in nodeCreated callback. Delay it using dotnet timer help it a little bit but not significant.

Additional question:
in point 2, it seems not possible to create controls using background thread. if i am not wrong, how do you guys trick it?

- You used ResumLayout, but forget to use SuspendLayout.
- Sometimes HeapSize is not enough for creating so many dotnet objects. So try to increase it.
- In some cases disabling automatic properties like AutoScroll, AutoSize, etc will increase the speed.
- You over created controls in your loop, there is no need to create array of controls to choose from. instead you can use case switch.
- WPF is much better than WinForm, So I always prefer WPF to create UI.
This is cleaner version of your code:


global Form
(
   if heapSize < 100000000 do heapSize = 100000000 --100Mb
   
   -- Form
   if Form != undefined do Form.close()
   Form = dotNetObject "MaxCustomControls.MaxForm"
   Form.Width = Form.Height = 500
   dotNet.SetLifeTimeControl Form #dotNet
   
   -- Main Panel
   MainPanel = dotnetobject "FlowLayoutPanel"
   MainPanel.AutoScroll = true
   MainPanel.top = 30
   MainPanel.backcolor = (dotNetClass "system.drawing.color").fromArgb 150 160 130
   MainPanel.Width = MainPanel.Height = 470
   Form.controls.add MainPanel
   fn ResizeMainPanel Sender Arg =
   (
      local MainPanel = Form.Controls.item[0]
      MainPanel.width = Sender.width - 30
      MainPanel.Height = Sender.Height - 30
   )
   dotNet.AddEventHandler Form #Resize ResizeMainPanel
   dotNet.SetLifeTimeControl MainPanel #dotNet
   
   -- Create Button
   fn CreateControls Sender Arg =
   (
      local MainPanel = Form.Controls.item[0]
      Form.SuspendLayout()
      for i = 1 to 2 do
      (
         Panel = dotnetobject "FlowLayoutPanel"
         Panel.backcolor = (dotNetClass "system.drawing.color").fromArgb (random 1 255) (random 1 255) (random 1 255)
         Panel.FlowDirection = (dotNetclass "FlowDirection").LeftToRight
         MainPanel.controls.add Panel
         dotNet.SetLifeTimeControl Panel #dotNet
         for x = 1 to 40 do
         (
            Rand = random 1 3
            Control = case Rand of
            (
               1:
               (
                  Button = dotNetObject "Button"
                  Button.text = "Test"
                  Button
               )
               2:
               (
                  NumericUpDown = dotNetObject "NumericUpDown"
                  NumericUpDown.value = random 1 100
                  NumericUpDown
               )
               3:
               (
                  CheckBox = dotNetObject "CheckBox"
                  CheckBox.text = "Test"
                  CheckBox
               )
            )
            
            Panel.controls.add Control
            dotNet.SetLifeTimeControl Control #dotNet
         )
         Panel.AutoScroll = true
      )
      Form.ResumeLayout()
   )
   CreateButton = dotNetObject "button"
   CreateButton.autosize = true
  CreateButton.text = "Create Controls"
  Form.Controls.add CreateButton
   dotNet.AddEventHandler CreateButton #Click CreateControls
   dotNet.SetLifeTimeControl CreateButton #dotNet
   
   -- Delete Button
   fn DeleteControls Sender Arg =
   (
      local MainPanel = Form.Controls.item[0]
      Form.SuspendLayout()
      MainPanel.Controls.clear()
      Form.ResumeLayout()
   )
   DeleteButton = dotNetObject "button"
   DeleteButton.left = 100
   DeleteButton.autosize = true
  DeleteButton.text = "Delete Controls"
  Form.Controls.add DeleteButton
   dotNet.AddEventHandler DeleteButton #Click DeleteControls
   dotNet.SetLifeTimeControl DeleteButton #dotNet
   
   -- Show Form
   Form.ShowModeless()
)
__________________
http://3dcutout.com/
 
  3 Days Ago
Originally Posted by MZ: - You used ResumLayout, but forget to use SuspendLayout.
- Sometimes HeapSize is not enough for creating so many dotnet objects. So try to increase it.
- In some cases disabling automatic properties like AutoScroll, AutoSize, etc will increase the speed.
- You over created controls in your loop, there is no need to create array of controls to choose from. instead you can use case switch.
- WPF is much better than WinForm, So I always prefer WPF to create UI.
This is cleaner version of your code:
hi MZ,
sorry, i simplified the codes too rough.
1. actually i replaced Suspend & Resume Layout in main form with WM_SETREDRAW. I see it makes the form performs way better when creating many controls.
2. sorry i forgot the HeapSize line. But memory still growing and never goes back normally after run the script many times in single session (mxs gc and dotnet gc helps but not much).
3. Disabling automatic properties, i just tried it and yes it helps a bit. thanks
4. Yes sorry it is unnecessary, I didnt use array of controls in my actual script. i just use it for easier example, and i didnt really create controls randomly.
5. Some friends always recommends it but it always feels too complicated to learn. Surely this is the time to manage some free times to learn WPF.

Btw, would you like to convert your winform codes above to wpf and share it here? (no need to create random controls, and please add some event handler)

thanks
 
  3 Days Ago
Originally Posted by noel20: hi MZ,
sorry, i simplified the codes too rough.
1. actually i replaced Suspend & Resume Layout in main form with WM_SETREDRAW. I see it makes the form performs way better when creating many controls.
2. sorry i forgot the HeapSize line. But memory still growing and never goes back normally after run the script many times in single session (mxs gc and dotnet gc helps but not much).
3. Disabling automatic properties, i just tried it and yes it helps a bit. thanks
4. Yes sorry it is unnecessary, I didnt use array of controls in my actual script. i just use it for easier example, and i didnt really create controls randomly.
5. Some friends always recommends it but it always feels too complicated to learn. Surely this is the time to manage some free times to learn WPF.

Btw, would you like to convert your winform codes above to wpf and share it here? (no need to create random controls, and please add some event handler)

thanks

I didn't heard aboutWM_SETREDRAW, Thank You.
About WPF, there is couple ways to use WPF inside Max:
1 - Create every functionality in C#as much as possible then import it as dll. (Highly Recomended)
2 - Executing Xaml code inside Max.
3 - Executing C# code inside Max.
4 - Creating all objects inside max (Old School method)
WPF is highly customizeable ,You can create almost everything with WPF, But I just want to inform you some general controls is not exist inside WPF at all. for example Spinner. So you should create it yourself.
And this is the WPF version of your code. Half of it is Xaml and another half is creating dynamic controls and event handlers by MAxScript.


global Window
(
   if heapSize < 100000000 do heapSize = 100000000 --100Mb
   
   Local Class_AppSDK = dotnetclass "ManagedServices.AppSDK"
   Local Class_MediaColor = dotnetclass "System.Windows.Media.Color"
   Local Class_XamlReader = dotnetclass "System.Windows.Markup.XamlReader"
   
   fn RandomRGB = Class_MediaColor.FromArgb 255 (random 1 255) (random 1 255) (random 1 255)
   
   -- Window
   if Window != undefined do Window.close()
   XamlString =
   "
   <Window
      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"
      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"
      Title=\"MainWindow\" Height=\"350\" Width=\"525\">
      <Grid>
         <Grid.RowDefinitions>
            <RowDefinition Height=\"Auto\"/>
            <RowDefinition />
         </Grid.RowDefinitions>
         <StackPanel Orientation=\"Horizontal\">
            <Button Name=\"CreateButton\" Content=\"Create Button\" Margin=\"2\" Padding=\"2\"/>
            <Button Name=\"DeleteButton\" Content=\"Delete Button\" Margin=\"2\" Padding=\"2\"/>
         </StackPanel>
         <ListView Grid.Row=\"1\" Name=\"MainPanel\" Background=\"DarkCyan\"/>
      </Grid>
   </Window>
   "
   Window = Class_XamlReader.Parse XamlString
   
   -- Create Button
   CreateButton = Window.findname "CreateButton"
   fn CreateControls Sender Arg =
   (
      MainPanel = Window.findname "MainPanel"
      for i = 1 to 2 do
      (
         -- Panel
         Panel = dotnetobject "System.windows.Controls.WrapPanel"
         Panel.Background = dotnetobject "System.Windows.Media.SolidColorBrush" (RandomRGB())
         Panel.Margin = dotnetobject "System.Windows.Thickness" 0 30 0 30 -- Left Top Right Bottom
         for x = 1 to 40 do
         (
            Rand = random 1 3
            Control = case Rand of
            (
               1:
               (
                  Button = dotNetObject "System.windows.Controls.button"
                  Button.Foreground = dotnetobject "System.Windows.Media.SolidColorBrush" (RandomRGB())
                  Button.BorderBrush = dotnetobject "System.Windows.Media.SolidColorBrush" (RandomRGB())
                  Button.Content = "Test"
                  Button
               )
               2:
               (
                  Spinner = dotNetObject "System.windows.Controls.Slider"
                  Spinner.value = random 1 100
                  Spinner.Width = 75
                  Spinner
               )
               3:
               (
                  CheckBox = dotNetObject "System.windows.Controls.CheckBox"
                  CheckBox.Content = "Test"
                  CheckBox
               )
            )
            Panel.Children.add Control
         )
         MainPanel.Items.add Panel
      )
   )
   dotNet.AddEventHandler CreateButton #Click CreateControls
   dotNet.SetLifeTimeControl CreateButton #dotNet
   
   -- Delete Button
   DeleteButton = Window.findname "DeleteButton"
   fn DeleteControls Sender Arg =
   (
      MainPanel = Window.findname "MainPanel"
      MainPanel.Items.Clear()
   )
   dotNet.AddEventHandler DeleteButton #Click DeleteControls
   dotNet.SetLifeTimeControl DeleteButton #dotNet
   
   -- Show Window
   (dotnetobject "System.Windows.Interop.WindowInteropHelper" Window).owner = dotnetobject "IntPtr" (windows.getMAXHWND())
   Class_AppSDK.ConfigureWindowForMax Window
   Window.show()
)
__________________
http://3dcutout.com/
 
  3 Days Ago
thats awesome
thanks!
 
  2 Days Ago
Originally Posted by noel20: thats awesome
thanks!

And about memory, it's not good idea to use dotnet GC, Max GC works very well. In our example replace this line of code to releasing the Window from memory:



   -- Window
   if Window != undefined do
   (
      Window.close()
      Window = undefined
      gc()
   )
__________________
http://3dcutout.com/
 
  1 Day Ago
hi Mz,
i included these to dialog closed event: set all structure and variable to undefined (especially for big arrays), free all strings and bitmap var, full max and dotnet garbage collection.
my script has no bitmap operation at all except tiny one with using (dotnetclass "system.drawing.icon").extractAssociatedIcon and (dotnetclass "system.drawing.image").fromstream
The memory still grows for hundreds kb at each script session until 3ds max restarted even i didnt do any scene operation at all and reset scene doesnt help too.
I dont know if it is normal or not, but my expectation was if i close the script then the memory should goes back to where the script wasnt running before.

about the winapi setRedraw, here the codes for example:

fn setRedrawMaxFloatingDialog_fn txt bool=
( local popUpDialogs= UIAccessor.GetPopupDialogs()
 
 local theHWND
 ( for i in PopUpDialogs where 
  ( UIAccessor.GetWindowText i == txt 
  ) while theHWND==undefined do 
  ( theHWND= i
  )
 )
 
 if theHWND != undefined then
 ( local WM_PRINT= 0x317
  local WM_SETREDRAW= 0x000B -- winmessage code numbers list: https://www.autoitscript.com/autoit3/docs/appendix/WinMsgCodes.htm or https://www.pinvoke.net/default.aspx/Constants/WM.html
  local redraw= if bool==true then 1 else 0
  windows.sendmessage theHWND WM_SETREDRAW Redraw 0
  windows.sendmessage theHWND WM_PRINT 1 0 
 ) else
 ( format ("HWND ("+(HWND as string)+") is not found\n")
  --for i in PopUpDialogs do print (UIAccessor.GetWindowText i)
 )
)
setListenerSel #(-1,-1) --openListener
setRedrawMaxFloatingDialog_fn "MAXScript Listener" false --change false to true to activate it
 
  2 Hours Ago
Originally Posted by noel20: hi Mz,
i included these to dialog closed event: set all structure and variable to undefined (especially for big arrays), free all strings and bitmap var, full max and dotnet garbage collection.
my script has no bitmap operation at all except tiny one with using (dotnetclass "system.drawing.icon").extractAssociatedIcon and (dotnetclass "system.drawing.image").fromstream
The memory still grows for hundreds kb at each script session until 3ds max restarted even i didnt do any scene operation at all and reset scene doesnt help too.
I dont know if it is normal or not, but my expectation was if i close the script then the memory should goes back to where the script wasnt running before.

I think is not good idea to do GC on windows event, because when window can do operations, it means it's still alive so max don't removed it. So try to make "Window" variable undefined outside of the window itself (like my example).
__________________
http://3dcutout.com/
 
reply share thread



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
CGSociety
Society of Digital Artists
www.cgsociety.org

Powered by vBulletin
Copyright 2000 - 2006,
Jelsoft Enterprises Ltd.
Minimize Ads
Forum Jump
Miscellaneous

All times are GMT. The time now is 11:41 AM.


Powered by vBulletin
Copyright ©2000 - 2018, Jelsoft Enterprises Ltd.