views:

965

answers:

3

Hey folks,

my mission is to select an item in a DataGrid instance with nothing but the coordinates on screen.

We are implementing right-click functionality in our Flash application, with the goal of being able to right-click a DG row, which would select that row plus show a popup window containing some context commands.

I have managed to get the right click event into my Flex app with the help of this site.

Further progress so far has been to obtain the DataGrid instance via

var objects : Array = this.getObjectsUnderPoint(new Point(this.mouseX, this.mouseY));

and then to investige each of the array's items, for one of those 'parent.parentList' refers to the DataGrid instance.

Now I am stuck -- I couldn't find any point-to-item converter function or anything. Any comments about my approach so far very welcome, too!

Thanks!

PS: Using the standard Flash ContextMenu is, unfortunately, not an option.

+2  A: 
/**
* Let mx and my be the mouse coordinates 
* (relative to the stage, not relative to the clicked object)
* */

var len:Number = dg.dataProvider.length;
var i:Number;
var p1:Point;
var p2:Point;
var renderer:DisplayObject;
for(i = 0; i < len; i++)
{
  renderer = DisplayObject(dg.indexToItemRenderer(i));
  if(!renderer)//item is not displayed (scroll to view it)
    continue;
  p1 = new Point(renderer.x, renderer.y);
  p2 = new Point(renderer.width, renderer.height);
  p1 = renderer.parent.localToGlobal(p1);
  p2 = renderer.localToGlobal(p2);
  if(mx >= p1.x && mx <= p2.x && my >= p1.y && my <= p2.y)
  {
    trace("You clicked on " + dg.dataProvider.getItemAt(i));
    break;
  }
}


You can attach the ContextMenu to the DataGrid's itemRenderer instead - that way you can get the right-clicked item from the event's currentTarget property. As simple as it can get.

Amarghosh
No, unfortunately its not. Using the standard Flex ContextMenu is not an option, so back to square one: "my mission is to select an item in a DataGrid instance with nothing but the coordinates on screen."
Tom
if you are able to get datagrid using the items-under-point function, you should be able to get the item renderer too.
Amarghosh
try this code - not tested, but should work.
Amarghosh
thanks heaps, will try next week!
Tom
Great, works like a charm! I have added an answer with the complete code, see below. Thanks mate.
Tom
A: 

You could use the itemRollOver event (and the related itemRollOut) to keep track of the most recent item that the mouse was over. Just save the item in a variable. When you display the context menu, you can use the saved item directly rather than trying to find it based on the (x,y) coordinates.

joshtynjala
+1  A: 

Here is the complete AS3 code for the Flash side of things. Note that you also need Javascript in your embedding HTML to make it work.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" 
 minWidth="1024" minHeight="768"
 creationComplete="onAppCreationComplete()"
 click="onRightClick()"
>
 <mx:DataGrid
  id="dgTest"
  dataProvider="{['aaa','bbbbbbbbbbbbbbb']}"
  >
  <mx:columns>
   <mx:DataGridColumn />
  </mx:columns>
 </mx:DataGrid>

 <mx:Script>
 <![CDATA[
  import mx.binding.utils.BindingUtils;
  import mx.controls.Alert;
  import mx.controls.Menu;
  import mx.effects.Fade;
  import mx.events.MenuEvent;

  [Bindable]
  public var customContextMenuItem : Object;

  public var customContextMenu : Menu;


  protected function onAppCreationComplete () : void
  {
   ExternalInterface.addCallback("rightClick", onRightClick);
   this.customContextMenu = this.createCustomContextMenu();
  }


  protected function onRightClick () : void
  {
   // find datagrid at mouse click coords
   var dg : DataGrid = this.getDataGridFromObjectsUnderPoint(this.mouseX, this.mouseY);
   if (dg) {
    // if any, find clicked item
    this.customContextMenuItem = this.findClickedItem(this.mouseX, this.mouseY, dg);
    if (this.customContextMenuItem) {
     // right clicking an item with the menu already showing does not show a new menu
     // unless the previous one is hidden first
     this.customContextMenu.hide();
     this.customContextMenu.show(this.mouseX+3, this.mouseY+2);
    }
   }
  }


  protected function getDataGridFromObjectsUnderPoint (x:Number, y:Number) : DataGrid
  {
   var objectsHere : Array = this.getObjectsUnderPoint(new Point(this.mouseX, this.mouseY));
   for each (var dispObj:DisplayObject in objectsHere) {
    while (dispObj) {
     if (dispObj is DataGrid)
      return dispObj as DataGrid;
     dispObj = dispObj.parent;
    }
   }
   return null;
  }


  /**
   * Returns a dataProvider item that displays at the given coords for the given dataGrid.
   * Code provided by Stackoverflow user http://stackoverflow.com/users/165297/amarghosh,
   * thanks a lot!
   */
  protected function findClickedItem (x:Number, y:Number, dg:DataGrid) : Object
  {
   var p1 : Point;
   var p2 : Point;
   var renderer : DisplayObject;

   for(var i:int=0; i<dg.dataProvider.length; i++) {
    renderer = DisplayObject(dg.indexToItemRenderer(i));
    if (!renderer) //item is not displayed (scroll to view it)
     continue;
    p1 = new Point(renderer.x, renderer.y);
    p2 = new Point(renderer.width, renderer.height);
    p1 = renderer.parent.localToGlobal(p1);
    p2 = renderer.localToGlobal(p2);
    if(x >= p1.x && x <= p2.x && y >= p1.y && y <= p2.y)
     return dg.dataProvider.getItemAt(i);
   }   
   return null;
  }


  protected function createCustomContextMenu () : Menu
  {
   // create a dynamic-object as our first menu item entry, and use data binding
   // to dynamically populate the 'title' value whenever our right-clicked item 
   // has changed
   var menuItem : Object = new Object();
   menuItem.title = "default";
   BindingUtils.bindSetter(function (item:Object) : void {
    trace(item);
    menuItem.title = "Edit '" + item + "'";
   }, this, ["customContextMenuItem"]);
   var dataProvider : Array = [ menuItem, {title:"Exit"} ];

   // create a nicely styled menu that looks very different to the standard Flash menu
   var menu : Menu = Menu.createMenu(this, dataProvider, false);
   menu.setStyle("fontWeight", "bold");
   menu.setStyle("backgroundColor", 0x000000);  // standard back/foreground
   menu.setStyle("color", 0xf0f0f0);
   menu.setStyle("rollOverColor", 0x444444);   // mouse hover back/foreground
   menu.setStyle("textRollOverColor", 0xffffff);
   menu.setStyle("selectionColor", 0x444444);  // mouse click back/foreground
   menu.setStyle("textSelectedColor", 0xe18c31);
   menu.setStyle("openDuration", 0);
   menu.labelField = "title";

   // we want to react to clicks in the menu
   menu.addEventListener(MenuEvent.ITEM_CLICK, function (event:MenuEvent) : void {
    Alert.show("Menu item clicked - clicked item title '" + event.item.title + "'");
   });

   // done
   return menu;
  }
 ]]>
 </mx:Script>
</mx:Application>
Tom