Fanning Software Consulting

Adding Menu Items Dynamically

QUESTION: Can you show me how to dynamically add items to a pull-down menu or droplist widget while my program is running?

ANSWER: Adding a new menu item is done in two different ways, depending upon the menu type. For a pull-down menu, the item can just be added to menu by creating a new menu button. For a droplist menu, the droplist widget must be destroyed and recreated. This is because droplist widgets do not have "values" in the sense that buttons have values. That is to say, the value of a droplist widget cannot be obtained or set with the Widget_Control procedure.

This is hard to explain, and easy to show. Here is an example program named Dynamic_Menus that demonstrates how it works. Compile and run the program like this:

   IDL> .Compile
   IDL> Dynamic_Menus

Here is how the program appears when you run it.

The Dynamic_Menus program as it appears to the user.

Select items from the list at the top. Each selected item will then disappear from the list widget and re-appear in both the droplist menu and in the pull-down menu that are below the list widget in the program. Activating either the droplist menu or the pull-down menu button will cause the current animal selection to be printed in the output window.

Deleting Items from a Pull-Down Menu

Ben Tupper had the opposite problem. He wanted to delete items from a pull-down menu. He used the information in this article to work the other way, and wrote a small sample program, named Dynamic_Pulldown, that illustrates how this can be done.

Program Code

Here is the entire code for the Dynamic_Menus program:

   PRO Dynamic_Menus_List_Events, event
   ; This event handler responds to animal selections from
   ; the list widget. The selected item is added to the
   ; droplist and pull-down menu widgets.
      Widget_Control,, Get_UValue=info, /No_Copy
      ; How many animals currently on the list?
      Widget_Control,, Get_UValue=animals
      numAnimals = N_Elements(animals)
      ; Get the selected animal. If it is "None" ignore event.
      selectedAnimal = animals[event.index]
      IF selectedAnimal EQ 'None' THEN BEGIN
         Widget_Control,, Set_UValue=info, /No_Copy
      ; Special case if only one animal is left.
      IF numAnimals EQ 1 AND event.index EQ 0 THEN BEGIN
         newAnimals = ['None']
         Widget_Control, info.listID, Set_Value=newAnimals, $
         ; All other cases handled here.
         CASE event.index OF
            0: newAnimals = animals[1:numAnimals-1]
            numAnimals-1: newAnimals = animals[0:numAnimals-2]
            ELSE: newAnimals = [animals[0:event.index-1], animals[event.index+1:*]]
      ; Update the animals in the list widget.
      Widget_Control, info.listID, Set_Value=newAnimals, $
      ; Add the selected animal to the droplist widget by first destroying
      ; the old droplist widget and re-creating a new one.
      Widget_Control, info.dropID, /Destroy, Get_UValue=currentAnimals
      info.dropID = Widget_Droplist(info.dropbase, Title='Current Animal Menu', $
            Value=[currentAnimals, animals[event.index]], $
            UValue=[currentAnimals, animals[event.index]], $
      Widget_Control, info.dropID, Set_Droplist_Select=N_Elements(currentAnimals)
      ; Add the selected animal to the pull-down menu widget.
      ; The procedure here is to add a new button to the existing ones.
      Widget_Control, info.menuID, Get_UValue=currentAnimals
      ; Build a new menu button.
      currentAnimals = [currentAnimals, animals[event.index]]
      lastIndex = N_Elements(currentAnimals) - 1
      *info.buttons = [*info.buttons, Widget_Button(info.menuID, $
      ; Update the animals in the menu list.
      Widget_Control, info.menuID, Set_UValue=currentAnimals
      Widget_Control,, Set_UValue=info, /No_Copy
   PRO Dynamic_Menus_Droplist_Events, event
   ; Handles events from the droplist widget.
   ; Which animal was selected? Print it.
      Widget_Control,, Get_UValue=animals
      Print, 'Current DropList Selection: ', animals[event.index]
   PRO Dynamic_Menus_DropMenu_Events, event
   ; Handles events from the pull-down menu widget.
      Widget_Control,, Get_UValue=info, /No_Copy
      ; Which animal was selected? Print it.
      Widget_Control,, Get_Value=thisAnimal
      Print, 'Pull-Down Menu Selection: ', thisAnimal
      ; Update the current selection in the droplist widget.
      Widget_Control, info.dropID, Get_UValue=animals
      thisAnimalIndex = Where(animals EQ thisAnimal)
      Widget_Control, info.dropID, Set_Droplist_Select=thisAnimalIndex[0]
      Widget_Control,, Set_UValue=info, /No_Copy
   PRO Dynamic_Menus_Cleanup, tlb
      Widget_Control, tlb, Get_UValue=info
      Ptr_Free, info.buttons
   PRO Dynamic_Menus
   ; This program shows how to dynamically add to and
   ; delete items from list widgets, pull-down menu
   ; button, and droplist widgets.
      ; Create the widgets for the program.
      tlb = Widget_Base(Column=1, /Base_Align_Center, $
         MBar=menuID, Title='Dynamic Menus')
      ; Create the list widget.
      animals = ['Cat', 'Dog', 'Cow', 'Eagle', 'Fox']
      listbase = Widget_Base(tlb, Column=1)
      label = Widget_Label(listbase, Value='Add These Animals to Menus')
      listID = Widget_List(listbase, YSize = 5, Value=animals, $
         UValue=animals, Event_Pro='Dynamic_Menus_List_Events')
      ; Create the droplist widget.
      dropbase = Widget_Base(tlb, Column=1)
      dropValues = 'Coyote'
      dropID = Widget_Droplist(dropbase, Title='Current Animal Menu', $
         Value=dropValues, UValue=dropValues, $
      ; Create the pull-down menu widget.
      menubase = Widget_Base(tlb, /Base_Align_Center)
      menuID = Widget_Button(menubase, Value='Select Animal Menu', $
         /Menu, UValue=['Coyote'], $
      buttons = IntArr(1)
      buttons[0] = Widget_Button(menuID, Value='Coyote')
      ; Center the top-level base.
      Device, Get_Screen_Size=screenSize
      xCenter = screenSize(0) / 2
      yCenter = screenSize(1) / 2
      geom = Widget_Info(tlb, /Geometry)
      xHalfSize = geom.Scr_XSize / 2
      yHalfSize = geom.Scr_YSize / 2
      Widget_Control, tlb, XOffset = xCenter-xHalfSize, $
         YOffset = yCenter-yHalfSize
      ; Realize the widgets.
      Widget_Control, tlb, /Realize
      ; Create the info structure to hold program information.
      info={ dropID:dropID, $             ; The identifier of the droplist widget.
             dropbase:dropbase, $         ; The droplist base widget.
             menuID:menuID, $             ; The identifier of the pull-down menu widget.
             menubase:menubase, $         ; The pull-down menu base widget.
             listID:listID, $             ; The identifier of the list widget.
             listbase:listbase, $         ; The list base widget.
             buttons:Ptr_New(buttons) $   ; The IDs of the buttons in the pull-down menu.
      Widget_Control, tlb, Set_UValue=info, /No_Copy
      ; Run the program as a non-blocking widget.
      XManager, 'dynamic_menus', tlb, /No_Block, Cleanup='Dynamic_Menus_Cleanup'

Web Coyote's Guide to IDL Programming