Implementing a simple list



  • On 27/05/2013 at 15:38, xxxxxxxx wrote:

    I need to implement a simple list of items. I have to be able to fill it with items through code and also be able to update it (re-populate it with new items, if needed).
    It should have a vertical scroll bar when the number of items is too long to be able to be displayed in the window.
    And the window layout is defined by a resource.
    If I had created my layout with code, I may have used a SCROLLGROUP.
    But, since it is defined by a resource, what is the best way to define a list box and how can I access it through code?
    Thank you very much in advance for any reply.

    Rui Batista



  • On 27/05/2013 at 16:08, xxxxxxxx wrote:

    Are you talking about dialgos or descriptions ? For dialogs there are tons of options. The
    most obvious way would be GeDialog.AddChildren. It does not matter if the dialog is
    loaded from a file or not. Here an example for a file based dialog with a dynamically filled
    combobox.

    def fillComboBox(self, select = srCon.ID_PRESETIDOFFSET) :
            self.FreeChildren(srCon.IDC_PRESET_COMBOBOX)
            # start layout change
            self.LayoutFlushGroup(srCon.IDC_PRESET_COMBOBOX)
            lastIndex = 0
            for index, value in self.Presets:
                self.AddChild(srCon.IDC_PRESET_COMBOBOX , index, value.GetString(srCon.IDC_PRESETNAME))
                if (index == srCon.ID_PRESETIDOFFSET) :
                    self.AddChild(srCon.IDC_PRESET_COMBOBOX , srCon.IDC_SEPERATOR, "")
                lastIndex = index
            self.LayoutChanged(srCon.IDC_PRESET_COMBOBOX)
            # end layout change
            if(select == -1) :
                self.SetLong(srCon.IDC_PRESET_COMBOBOX, lastIndex)
            else:
                self.SetLong(srCon.IDC_PRESET_COMBOBOX, select)
    

    For descriptions the only solution we could use in python would be InExclude list. Create
    some dummy nulls and place it in the InEx. Messy but it works.

    Happy rendering,
    Ferdinand



  • On 27/05/2013 at 20:48, xxxxxxxx wrote:

    If this is what you want:http://gambasdoc.org/help/comp/gb.qt/listview?view
    Then what you're looking for is probably the CUSTOMGUI_LISTVIEW gizmo.

    This is not documented in the Python docs. Which might mean it's not officially supported.
    I've been able to create the gizmo so it shows up in the dialog. But I do not know how to fill and delete the entries in it.
    There's a class in C++ that handles that stuff using functions like SetLayout(), SetItem, etc. which I don't see listed in the Python docs.

    -ScottA



  • On 28/05/2013 at 06:04, xxxxxxxx wrote:

    Littledevil, that would only work for combo boxes or popup buttons :-(

    Yes, that is what I need, Scott. But, how do I define it in the resource file?

    Thank you both for the reply.



  • On 28/05/2013 at 06:32, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    Littledevil, that would only work for combo boxes or popup buttons :-(

    Yes, that is what I need, Scott. But, how do I define it in the resource file?

    Thank you both for the reply.

    As you did refer to a 'list' and not a 'listview' I thought a combobox would also do it. The link
    Scott posted seems to be a Qt example, so has nothing to do with c4d. As he already pointed 
    out the c4d gadget SimpleListView is not wrapped for python, so you can define it in the res
    file but you won't be able to use it / read its content.

    Alternatives to a ComboBox would be the QuickTabGui which is wrapped for python. It does
    resemble in some qualities to a ListView and would allow dynamic content. The most flexible 
    route would be a UserArea of course. It should  not be too complicated to implement a ListView, 
    I also think Nikklas has done this once and provided his results as Open Source.

    Happy rendering,
    Ferdinand



  • On 28/05/2013 at 07:33, xxxxxxxx wrote:

    Thank you for the reply, littledevil.
    I have found no references to any QuickTabGui in the python SDK :-(
    I never thought it would be so complicated to create a list (listview) in python, using resources.
    So, what should I search for, to find that implementation by Nikklas?

    Rui Batista



  • On 28/05/2013 at 07:49, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    Yes, that is what I need, Scott. But, how do I define it in the resource file?

    It probably won't do you any good to create it if there's nothing in the python SDK to use it.
    But I'll post how to create it anyway so you at least know how to create it.

      
    ###### This is the code for the ListView gizmo in the .res file ######  
      
      GROUP LIST_GROUP    
      {     
      LISTVIEW MY_LISTBOX  {ALIGN_TOP; ALIGN_LEFT; SIZE 150, 50;}   
      }  
      
      
      
      
      
      
    ###### This is the code that goes in the plugin's .pyp file ######  
      
    CUSTOMGUI_LISTVIEW = 1018398    #The text Id for the ListView gizmo is not set up yet. So do it manually   
    MY_LISTBOX = 10001  
      
      
    #The CreateLayout method   
      def CreateLayout(self) :      
      
          res = self.LoadDialogResource(ResBasedMenu)                        #Loads GUI stuff from the .res file  
            
          self.listbox = self.FindCustomGui(MY_LISTBOX, CUSTOMGUI_LISTVIEW)  #Creates the ListView gizmo          
            
          return res
    

    -Scott



  • On 28/05/2013 at 08:05, xxxxxxxx wrote:

    Thank you.
    So, we can create it but we cant add anything to it, right?



  • On 28/05/2013 at 08:20, xxxxxxxx wrote:

    And, shouldn't it be AddCustomGui instead of FindCustomGui?



  • On 28/05/2013 at 08:27, xxxxxxxx wrote:

    So, it all indicates that I need to create a SCROLLGROUP with a USERAREA inside it an implement a LISTVIEW manually.
    Am I correct into assuming this?

    Rui Batista



  • On 28/05/2013 at 08:39, xxxxxxxx wrote:

    1. The QuickTabGui is documented under c4d.gui.BaseCustomGui.QuickTabGui

    2. You have to call FindCustomGui to get hold of the CustomGui class providing methods
        to interact with the gadget. It will return the helper class for the CustomGui at that id.
        For a ListView it will be None, as there is no such class in python.

    3. I am not sure if you did understand the difference between the definition of a CustomGui
        in a resource file and its wrapping class. Defining a ListView is no problem, it will show
        up in your gui, but you won't be able to read or write data to it, as there is no wrapping
        CustomGui class in python providing methods like SetItem() or GetItem() to change the
        the items in your ListView.

    4. Not sure where to search. I just know that he once wrote it. Just start a google search
        for Nikklas Rosenstein ListView.



  • On 28/05/2013 at 08:54, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    And, shouldn't it be AddCustomGui instead of FindCustomGui?

    AddCustomGui returns an error when used with CustomGui. Don't ask me why.
    If you want to see a working example of this. Use CUSTOMGUI_LINKBOX instead of CUSTOMGUI_LISTVIEW.
    The custom .res based LinkBox gizmo seems to be supported....Or it works by accident.
    And using FindCustomGui is the only way it works for me.

    Originally posted by xxxxxxxx

    So, it all indicates that I need to create a SCROLLGROUP with a USERAREA inside it an implement a LISTVIEW manually.
    Am I correct into assuming this?

    That's one possible option. But it sounds like a fairly ugly proposal to me.
    Another option is switching to C++ so you stop running into things that are not supported in Python. 😉

    -ScottA



  • On 28/05/2013 at 09:17, xxxxxxxx wrote:

    I don't intend to switch to C++ soon as it is much more complex than python and, also, requires different compilation for Mac an Windows.
    I may have to create my own implementation of a LIST_VIEW :-(



  • On 28/05/2013 at 09:26, xxxxxxxx wrote:

    ^Yeah. I know. I was just kidding you about that.

    -ScottA



  • On 28/05/2013 at 11:40, xxxxxxxx wrote:

    Hi Rui,

    this is a very very old implementation of a simple ListView of mine. I've used it in Description Editor
    plugin. Works relatively fine, very basic. Please don't judge my coding style, this snippet is about 2
    years old. :)

    Maybe you can use it.

    class ListView(GeUserArea) :
        MOUSEWHEEL_MODE         = 1027215       # Registered at Plugincafe.com
        SELECTEDROW_UP          = 1027216       # Registered at Plugincafe.com
        SELECTEDROW_DOWN        = 1027217       # Registered at Plugincafe.com     
        SELECTEDROW_CHANGED     = 1027218       # Registered at Plugincafe.com
        SELECTEDROW_PREVIOUS    = 1027219       # Registered at Plugincafe.com
      
        colors             = {
            "background":       Vector(.2),
            "lighterRow":       Vector(.31),
            "darkerRow":        Vector(.29),
            "markedRow":        Vector(.38),
            "text":             Vector(.64705),
            "markedText":       Vector(1., .66, .024)
        }
      
        def __init__(
                self,
                parentDialog    = None,                 # The parent-dialog, can be None if the 'Command'-Method should not be invoked on an Input-Event
                initialData     = (()),                 # Data to display, must be 2 dimensional
                rowHeight       = 16,                   # Height of a row in the ListView
                columnWidths    = (100,),               # Widths of the columns in the ListView
                minSize         = (300, 100),           # Minimum size of the area. If second is None, it will be calculated.
            ) :
      
            self.parentDialog       = parentDialog
      
            self.rowHeight          = rowHeight
            self.columnWidths       = tuple(columnWidths)
            self.data               = list(initialData)
            self.selectedRow        = -1
      
            self.minSize            = tuple(minSize)
      
        # Overriden
        def DrawMsg(self, x1, y1, x2, y2, msg) :
            """ Called when redrawing the UserArea """
            self.SetClippingRegion(x1, y1, x2, y2)
      
            self.DrawSetPen(self.colors["background"])  # Set Background Color
            self.DrawRectangle(x1, y1, x2, y2)          # Fill Background
      
            """
            Draw lighter rows
            """
            self.DrawSetPen(self.colors["lighterRow"])
            for i in xrange(0,len(self.data), 2) :           # Iterate the  specified rows
                Rectangle       = {
                    'x1':               0,
                    'x2':               x2,
                    'y1':               self.rowHeight * i,
                    'y2':               self.rowHeight * (i + 1)
                }
                # If the selected Row is about to be drawn
                if i == self.selectedRow and self.colors["markedRow"]:
                    self.DrawSetPen(self.colors["markedRow"])
                    self.DrawRectangle(**Rectangle)
                    self.DrawSetPen(self.colors["lighterRow"])
                else:
                    self.DrawRectangle(**Rectangle)
      
            """
            Draw darker rows
            """
            self.DrawSetPen(self.colors["darkerRow"]) 
            for i in xrange(1,len(self.data), 2) :           # Iterate the  specified rows
                Rectangle       = {
                    'x1':               0,
                    'x2':               x2,
                    'y1':               self.rowHeight * i,
                    'y2':               self.rowHeight * (i + 1)
                }
                # If the selected Row is about to be drawn
                if i == self.selectedRow and self.colors["markedRow"]:
                    self.DrawSetPen(self.colors["markedRow"])
                    self.DrawRectangle(**Rectangle)
                    self.DrawSetPen(self.colors["darkerRow"])
                else:
                    self.DrawRectangle(**Rectangle)
      
            """
            Draw data.
            """
            self.DrawSetTextCol(self.colors["text"], c4d.COLOR_TRANS)
            for i in xrange(len(self.data)) :                # Iterate the  specified rows
                previousItemPosX    = x1 + 5
                for j in xrange(len(self.data[i])) :         # Iterate the datas columns
                    if j >= len(self.columnWidths) :
                        width       = self.columnWidths[-1]
                    else:
                        width       = self.columnWidths[j]
      
                    textToDraw          = str(self.data[i][j])
      
                    # If the selected Row is about to be drawn
                    if i == self.selectedRow:
                        self.DrawSetTextCol(self.colors["markedText"], c4d.COLOR_TRANS)
                        self.DrawText(  txt     = textToDraw,
                                        x1      = x1 + previousItemPosX ,
                                        y1      = y1 + i *  self.rowHeight + self.rowHeight - 14,
                        )
                        self.DrawSetTextCol(self.colors["text"], c4d.COLOR_TRANS)
                    else:
                        self.DrawText(  txt     = textToDraw,
                                        x1      = x1 + previousItemPosX ,
                                        y1      = y1 + i *  self.rowHeight + self.rowHeight - 14,
                        )
                    previousItemPosX   += width
      
        def InputEvent(self, msg) :
            inputChannel            = msg[c4d.BFM_INPUT_CHANNEL]
      
            # Left or Right Mousebutton
            if inputChannel in (c4d.BFM_INPUT_MOUSELEFT, c4d.BFM_INPUT_MOUSERIGHT, c4d.BFM_INPUT_MOUSEMIDDLE) :
                positionInDialog    = self.Global2Local()
                mousePosX           = msg[c4d.BFM_INPUT_X] + positionInDialog["x"]
                mousePosY           = msg[c4d.BFM_INPUT_Y] + positionInDialog["y"]
      
                clickedRow          = int(mousePosY/self.rowHeight)
      
                msg[self.SELECTEDROW_PREVIOUS]  = self.selectedRow
      
                if clickedRow > len(self.data) - 1:
                    self.selectedRow    = -1
                else:
                    self.selectedRow    = clickedRow
      
                self.Redraw()
      
            # MouseWheel switching of elements
            elif inputChannel == c4d.BFM_INPUT_MOUSEWHEEL:
                inputValue      = msg[c4d.BFM_INPUT_VALUE]
                selectedRow     = self.selectedRow
      
                # Go up (in visual), means to go back in the list 
                if inputValue > 0:
                    # Do not if data-begin is reached
                    if selectedRow > 0:
                        if self.parentDialog:
                            bc         = c4d.BaseContainer()
      
                            bc[self.MOUSEWHEEL_MODE]        = self.SELECTEDROW_UP
                            bc[self.SELECTEDROW_CHANGED]    = -1    # Selected row + SELECTEDROW_CHANGED = newSelectedRow
      
                            self.parentDialog.Command(self.GetId(), bc) 
      
                        self.selectedRow   -= 1
      
                # Go down (in visual), means to go further in the list
                elif inputValue < 0:
                    # Do not if data-end is reached
                    if selectedRow <= len(self.data) -2:
                        if self.parentDialog:
                            bc         = c4d.BaseContainer()
      
                            bc[self.MOUSEWHEEL_MODE]        = self.SELECTEDROW_DOWN
                            bc[self.SELECTEDROW_CHANGED]    = 1     # Selected row + SELECTEDROW_CHANGED = newSelectedRow
      
                            self.parentDialog.Command(self.GetId(), bc)
      
                        self.selectedRow   += 1
      
                self.Redraw()
      
            # if parentDialog is defined, send Command-Message
            if self.parentDialog:
                self.parentDialog.Command(self.GetId(), msg)
      
      
            return True
      
        def GetMinSize(self) :
            """ Returns the minimum size of the area. """
            return tuple(self.minSize)
      
        # Getters
        def GetMinHeight(self) :
            return self.minSize[1]
      
        def GetSelectedRow(self) :
            return self.selectedRow
      
        def GetRowHeight(self) :
            return self.rowHeight
      
        def GetColumnWidths(self) :
            return self.columnWidths
      
        def GetParentDialog(self) :
            return self.parentDialog
      
        # Setters
        def SetColors(self, **kwargs) :
            for k, v in kwargs.iteritems() :
                if k in self.colors.keys() :
                    self.colors[k]  = v
      
        def SetData(self, data) :
            self.data   = data
      
        def SetRowHeight(self, height) :
            self.rowHeight      = height
            self.minSize[1]     = self.rowHeight * len(self.data)
      
        def SetColumnWidths(self, columnWidths) :
            self.columnWidths   = columnWidths
      
        def SetSelectedRow(self, index) :
            if index >= len(self.data) :
                self.selectedRow    = -1
                return False
            else:
                self.selectedRow    = index
                return True
      
        def SetParentDialog(self, parent) :
            self.parentDialog       = parent
      
        # Adders
        def AppendData(self, data) :
            self.data.append(data)
      
        def InsertData_AtIndex(self, data, index) :
            if index > len( self.data ) :
                return False
            else:
                self.data.insert(index, data)
      
        # Removers
        def RemoveData(self, subData) :
            if subData in self.data:
                del self.data[ self.data.index(subData) ]
                return True
            else:
                return False
      
        def RemoveData_ByIndex(self, index) :
            if index > len( self.data ) :
                return False
            else:
                del self.data[index]
      
        # Update
        pass
    

    -Nik



  • On 29/05/2013 at 09:06, xxxxxxxx wrote:

    Thank you very muck, Nikklas.
    I will check it out, to see if I can use it.
    This creates color bars, right?
    I want to create a list of text items. Should not be too complicated to change this to my needs :-)

    Rui Batista



  • On 29/05/2013 at 09:25, xxxxxxxx wrote:

    Yep, it looks like this:

    The data it uses to display must be a two dimensional list with each row having the same number of columns! A selected item can be moved up and down by scrolling the mouse-wheel. But scrolling the view is not implemented in this user area.



  • On 29/05/2013 at 15:38, xxxxxxxx wrote:

    It is working fine, Nikklas :-)
    However, if I add more than a certain number of items, they just disappear down the bottom.
    Is there any way to implement a vertical scroll bar?



  • On 30/05/2013 at 01:57, xxxxxxxx wrote:

    Sure. I think the easiest way would be to implement GetMinSize() and call LayoutChange() on the
    dialog when the elements in the list have changed and put the user area into a scroll-group.

    -Nik



  • On 30/05/2013 at 02:14, xxxxxxxx wrote:

    You code already has a GetMinSize() and the user area it already inside a SCROLLGROUP, like this:

    SCROLLGROUP DC_SCRIPT_CONTAINER
         {
         SCALE_V;
         SCALE_H;
         SCROLL_V;
         SCROLL_BORDER;

    USERAREA DC_SCRIPT_LIST { SCALE_V; SCALE_H; }
         }

    In my main code, I already filled the data with many, many items, just to test if a vertical scroll bar would appear. But it doesn't. Even if I change the size of my plugin window :-(


Log in to reply