MarkupGrid Item Metadata with fixedLayout="true"

Is this the correct way to declare X,Y,W,H values for items in a MarkupGrid? Does it automatically know to look for these values when fixedLayout is set to “true” as described in the Fields table here?

<component name="GuideItemData" extends="ContentNode">
 
<interface> 
  <field id="labelText" type="string" />
  <field id="X" type="integer" />
  <field id="Y" type="integer" />  
  <field id="W" type="integer" />  
  <field id="H" type="integer" />
</interface>

</component>

You don’t need to explicitly add the interface fields. They should already be present on ContentNode. And, yes, the grid knows to automatically look for those fields when fixedLayout is set to true.

Thank you for the reply. Removing those interface fields fixed a crash I was experiencing when the grid tried to render.

One more issue: the children itemComponents are not rendering in the grid properly. The first element displays as expected with a black rectangle background with the text on top, but the rest do not appear. I can navigate to the other cells (or at least where they should be) but the focus indicator shrinks and regrows to the proper size in the direction of the movement.

Am I setting sizes somewhere I shouldn’t be? I only set the row and column sizes in the MarkupGrid and also the height/width of the children elements of the GuideItem as seen below.

My MarkupGrid:


		<MarkupGrid
			id="GuideGrid"
			itemComponentName="GuideItem"
			fixedLayout="true"
			translation="[300,310]" 
			numRows="2"
			numColumns="4"	
			rowHeights="80"
			columnWidths="390"
			itemSpacing="[3,3]"	>
		</MarkupGrid>

and my GuideItem.xml:


<?xml version="1.0" encoding="utf-8" ?>

<component name="GuideItem" extends="Group">
 
<interface> 
  <field id="itemContent" type="node" onChange="itemContentChanged"/> 
</interface>
 
<script type="text/brightscript" >
<![CDATA[ 
  function itemContentChanged() 
    itemData = m.top.itemContent
    m.itemText.text = itemData.labelText
  end function
    
  function init() 
    m.itemText = m.top.findNode("itemText") 
  end function
]]>
</script>

<children> 
	<Rectangle
		id="boundingRect"
		color="#000000"   
		height="80"
		width="390" >
        <Label id="itemText" font="font:MediumSystemFont" height="80" width="390" vertAlign="center" horizAlign="center"/>
     </Rectangle>
</children>

</component>

Solved my own issue by setting itemSize on the MarkupGrid and also using “m.boundingRect.width *= itemData.W” in the “onContentChanged” function of GuideItem.xml

You’re probably sick of me by now, but is there a way to get the rows of MarkupGrid to scroll horizontally when the right of left edge of the grid is reached? I tried using horizFocusAnimationStyle = “floatingFocus” but it does not work as expected. The focus indicator simply continues offscreen and the elements do not scroll left or right to move onscreen and into focus.

Have you tried setting the numColumns value to just the visible column count?

I have, since that method works fine for numRows, but setting numColumns to anything less than the highest X value in my data results in only the first element (0,0) displaying properly and debugger errors about Fixed Layout cells already being occupied (quoted below).

For instance, I create data items to fill two rows and five columns. Element 9 has W=2 so it spans two columns. Four full columns will fit in the space I’ve allotted for the MarkupGrid.

Setting numColumns to 5 results in all elements rendering properly, but the last column is 90% offscreen. This is what I want for a guide, but the last column does not shift onto the screen when focused.

Setting numColumns to 4 results in only the first element rendering, but the focus indicator can move to the spaces where the other elements should be.
I also receive this error from the debugger:

** Error - ArrayGrid Layout: Fixed Layout cell was already occupied
** cell 4, current item: 4, new item: 5
** Row (Y): 1 Column (X): 0 Width (W):1 Height (H): 1
** Error - ArrayGrid Layout: Fixed Layout cell was already occupied
** cell 8, current item: 0, new item: 8
** Row (Y): 1 Column (X): 3 Width (W):2 Height (H): 1

It should be drawing the 5th element at (4,0), but instead tries to put it at (0,1) since there is no 5th column.

I see that the definition of numRows says “Note that the actual number of rows may be more or less than the number of visible rows depending on the number of items in the grid content.” and that this is not present in the numColumns definition, so I guess I’ll just hope for that functionality to be added in the future and figure out an alternative approach for now (any suggestions appreciated).

What about checking the x/y coordinates of the focused item and moving the grid programmatically to ensure they’re in view?

Ok, so I get the focused child, check if it is in the offscreen column, and then try to reduce all the child X values by 4 so they all shift to the left (and supposedly all the items which were previously on screen will have X values less than 0 and they won’t be drawn? Or is this an error to have X values less than 0?)

The code below does not work. I’m getting a “Type Mismatch” error on the decrement “m.guideGrid.content.getChild(count).X -= 4”


function onFocusChanged() as void
    print "Focus on item: " + stri(m.guideGrid.itemFocused)
    index = m.guideGrid.itemFocused
       
    'Check if focused on offscreen column'   
    if (m.guideGrid.content.getChild(index).X = 4) then
           
        count=0
        
        'Iterate through all child nodes and decrement'
        while (m.guideGrid.content.getChild(count) <> invalid)
            m.guideGrid.content.getChild(count).X -= 4
            count++
        end while
    endif
    
end function

I’m curious about the 4. Are you really only wanting to shift the items by 4 pixels?

As for the Type Mismatch, that would suggest that X on the item is invalid/not set.

With that said, I was more thinking that you’d shift the whole grid over, not the individual items (i.e., change m.guideGrid.translation).

The X value is the column index, not number of pixels. I know it’s a valid value because it properly evaluates the “if (m.guideGrid.content.getChild(count).X = 4)” statement. I can also loop through the data set and print out the X values using this code:


    if (m.guideGrid.content.getChild(index).X = 4) then
        count=0
        while (m.guideGrid.content.getChild(count) <> invalid)
            print stri(m.guideGrid.content.getChild(count).X)
            count++
        end while
    endif
    

All of the printed values are what I expect and integers. The error only occurs when I try to modify the value (I also tried incrementing by 1 to make sure it wasn’t a “less than 0 value” error).

I don’t think translation would work because I want the grid to remain in the same spot on the screen. I tried using m.guideGrid.animateToItem but I think it’s only for rows because it did nothing.

One other issue: Have you ever seen an itemFocused Observer randomly start getting called twice? I only set this once in init() and I’m losing my mind trying to figure out what is going on.

    m.guideGrid.ObserveField("itemFocused", "onFocusChanged")

“agale” wrote:
The X value is the column index, not number of pixels.
Of course it is.. d’oh! :oops:

“agale” wrote:
One other issue: Have you ever seen an itemFocused Observer randomly start getting called twice? I only set this once in init() and I’m losing my mind trying to figure out what is going on.

    m.guideGrid.ObserveField("itemFocused", "onFocusChanged")

Could it be an orphaned or second instance of the observer? Have you tried adding a “stop” to the init function to see if it’s being called more than once?

I added a stop at the end of init() and it is only being called once. I also added one in onFocusChanged() and it is definitely being called twice. Looking at the variables I am unable to see any reference to an Observer. Executing “print m.guideGrid” only prints out the fields and their values.

Would an orphaned observer persist through multiple re-exports and device reboots? I’ve completely removed the channel and re-run but saw the same behavior.

“agale” wrote:
Would an orphaned observer persist through multiple re-exports and device reboots? I’ve completely removed the channel and re-run but saw the same behavior.
No, definitely not. The only time I’ve seen this happen is when I either inadvertently registered the observer twice (e.g., in a content onChange event), or when I had multiple instances of the observer, so it was being called twice, but on different objects. I’d search your code for an observeField that you might have forgotten about.

I am 100% positive that I am only setting the Observer once. That’s the only Observer I set in the whole program. The only other event listener is in my GuideItem interface for the content onChange event, but all of those events trigger before the itemFocused Observer is even set.

The only other clue I have is that when the grid first gains focus, onFocusChanged() is only called once. After that, focusing on a new element generates two calls to onFocusChanged().

I’m including all of my code below because I’m all out of ideas. If you get a chance to copy it over and run it I would be very interested in your results.

And thank you so much for your help so far.

main.brs


sub Main()
    showChannelSGScreen()
end sub

sub showChannelSGScreen()
    screen = CreateObject("roSGScreen")
    m.port = CreateObject("roMessagePort")
    screen.setMessagePort(m.port)
    scene = screen.CreateScene("GridGuideScene")
    screen.show()

    while(true)
        msg = wait(0, m.port)
    msgType = type(msg)
        if msgType = "roSGScreenEvent"
            if msg.isScreenClosed() then return
        end if
    end while
end sub

GridGuideScene.brs


sub init()
    m.guideGrid = m.top.findNode("GuideGrid")
    m.guideGrid.horizFocusAnimationStyle = "floatingFocus"
    m.guideGrid.vertFocusAnimationStyle = "floatingFocus"
    m.guideGrid.content = getGuideGridData()
    m.guideGrid.SetFocus(true)
    m.guideGrid.ObserveField("itemFocused", "onFocusChanged")
End sub

function getGuideGridData() as object
    data = CreateObject("roSGNode", "ContentNode")

    count=0
    for i = 0 to 1  'Rows'
        for j = 0 to 4  'Cols'
            count++
            
            if (count<>10) then 
                dataItem = data.CreateChild("GuideItemData")
                dataItem.labelText = "Guide item" + stri(count)
                
                dataItem.X = j
                dataItem.Y = i
                dataItem.W = 1
                dataItem.H = 1
                
                if (count=9) then
                    dataItem.W = 2
                endif
                
            endif
                        
        end for
    end for
    return data
end function

function onFocusChanged() as void
    print "Focus on item: " + stri(m.guideGrid.itemFocused)
    'stop'
end function

function onKeyEvent(key as String, press as Boolean) as Boolean
    print "onKeyEvent: " + key
    result = false
    
    return result 
end function

GridGuideScene.xml


<?xml version="1.0" encoding="UTF-8"?>

<component name="GridGuideScene" extends="Scene" xsi:noNamespaceSchemaLocation="https://devtools.web.roku.com/schema/RokuSceneGraph.xsd">

<script type="text/brightscript" uri="pkg:/components/GridGuideScene.brs" />

<children>
    	
    	<MarkupGrid
			id="GuideGrid"
			itemSize="[390,80]"
			itemComponentName="GuideItem"
			fixedLayout="true"
			translation="[300,310]" 
			numRows="6"
			numColumns="5"
			rowHeights="80"
			columnWidths="390"
			itemSpacing="[3,3]"	>
		</MarkupGrid>
		
	</children>

</component>

GuideItem.xml


<?xml version="1.0" encoding="utf-8" ?>

<component name="GuideItem" extends="Group">
 
<interface> 
  <field id="itemContent" type="node" onChange="itemContentChanged"/> 
</interface>
 
<script type="text/brightscript" >
<![CDATA[ 
  function itemContentChanged() 
    itemData = m.top.itemContent
    m.itemText.text = itemData.labelText
	
	m.boundRect = m.top.findNode("boundingRect")
	m.boundRect.width *= itemData.W
  end function
    
  function init() 
    m.itemText = m.top.findNode("itemText") 
  end function
]]>
</script>

<children> 
	<Rectangle
		id="boundingRect"
		color="#000000"   
		height="80"
		width="390" >
        <Label id="itemText" font="font:MediumSystemFont" height="80" width="390" vertAlign="center" horizAlign="center"/>
     </Rectangle>
</children>

</component>

GuideItemData.xml


<?xml version="1.0" encoding="utf-8" ?>

<component name="GuideItemData" extends="ContentNode">
 
<interface> 
  <field id="labelText" type="string" />
</interface>

</component>

Well.. some good news and some bad news. The good news is that I was able to reproduce it with your code. The bad news is that I can’t see anything in your code that would cause it, so it’s almost certainly a firmware bug of some sort. To add fuel to that fire, if you set “numRows” to 1, the issue goes away, but that’s obviously not a solution.

As a workaround, you could track the last focused item index, and only perform an action in “onFocusChanged” if that value changes.

“TheEndless” wrote:
You don’t need to explicitly add the interface fields. They should already be present on ContentNode. And, yes, the grid knows to automatically look for those fields when fixedLayout is set to true.
“agale” wrote:
Thank you for the reply. Removing those interface fields fixed a crash I was experiencing when the grid tried to render.
Does SG just crash (no error) if existing fields are re-defined? That would be… peculiar.

“EnTerr” wrote:
Does SG just crash (no error) if existing fields are re-defined? That would be… peculiar.
No, but it can cause some confusing error messages in the debug console. In the case of a fixedLayout MarkupGrid, if you explicitly define those fields in the interface of your extended ContentNode, it will result in errors like this:

** Error - ArrayGrid Layout: Fixed Layout cell was already occupied
**         cell 0, current item: 0, new item: 1
**         Row (Y): 0 Column (X): 0 Width (W):1 Height (H): 1

which is the same error you get if you don’t specify a value for X and/or Y, so it’s as if explicitly adding the interface fields overwrites the implicit fields and makes them unreadable by the underlying component.

EnTerr wrote:
Does SG just crash (no error) if existing fields are re-defined? That would be… peculiar.
Actually, yes it does. Changing GuideItemData.xml to the code below causes a crash and device reboot. No errors anywhere.


<?xml version="1.0" encoding="utf-8" ?>
 
<component name="GuideItemData" extends="ContentNode">
 
<interface> 
  <field id="labelText" type="string" />
  <field id="X" type="integer" />
  <field id="Y" type="integer" />
  <field id="W" type="integer" />
  <field id="H" type="integer" />
</interface>

</component>