15 Replies Latest reply on Nov 22, 2011 11:26 AM by William Spence

    Custom dragIndicator on Spark List Control

    William Spence Level 1

      I have a Spark List Control that is displaying a tilelayout of dynamically imported Images.  Basically, a bunch of thumbnail Images that are displayed in a tile layout.  I have drag drop enabled so that I can reorder these thumbnails and everything is working great except for one thing, the dragIndicator.  In my itemrenderer, I have the Image included in the "dragging" state, but the image is not showing in the dragIndicator.  I am basically just getting a semi-transparent square as my dragIndicator, but I want to be able to actually drag my thumbnail.  Any ideas how to do this?

       

      Here is my List:

       

      <s:List id="ImageList1" x="77" y="95" width="858" height="412" dataProvider="{imageAC}"

           itemRenderer="renderers.ImageACSmallItemRenderer" contentBackgroundColor="0x000000"

           borderVisible="false" dragEnabled="true" dropEnabled="true" dragMoveEnabled="true"

           allowMultipleSelection="true" skinClass="skins.General.ListSkin" focusAlpha="0" mouseMove="getImageProxy(event)">

           <s:layout>

                <s:TileLayout columnWidth="76" rowHeight="76" horizontalAlign="center" verticalAlign="middle"

                       horizontalGap="8" verticalGap="8"/>

           </s:layout>

      </s:List>

       

      Here is my Itemrenderer:

       

      <fx:Script>

           <![CDATA[

                import mx.utils.ObjectProxy;

       

                [Bindable]

                public var dataProxy:ObjectProxy;

       

                private function init():void {

                     dataProxy = new ObjectProxy(data);

                }

           ]]>

      </fx:Script>

       

           <s:states>

                <s:State name="normal" />

                <s:State name="hovered" />

                <s:State name="selected" />

                <s:State name="dragging" />

           </s:states>

       

       

           <mx:Image source="{dataProxy.pathSmall}" horizontalCenter="0" verticalCenter="0" includeIn="normal, hovered, selected, dragging"/>

      </s:ItemRenderer>

      Thanks for any insight!

        • 1. Re: Custom dragIndicator on Spark List Control
          William Spence Level 1

          Any thoughts at all?  I would think that this would be a common problem in Flex - wanting to display a list of thumbnail images loaded by a user and wanting to drag those images in a list with a dragIndicator that is actually the thumbnail?  But I have looked everywhere and found no solutions thusfar.  Any brilliant programmers want to throw me a bone?

          • 2. Re: Custom dragIndicator on Spark List Control
            Flex harUI Adobe Employee

            Hard to say what's wrong.  I think it usually "just works".  What is the

            getImageProxy on mouseMove?  How does the init() method get called?

            • 3. Re: Custom dragIndicator on Spark List Control
              William Spence Level 1

              Sorry, the init() function gets called by the creationComplete event of the ItemRenderer.  It is just made to create a simple ObjectProxy for the data from my dataprovider.   The getImageProxy function called by mouseMove was just a function that I was trying things in during all of my Google searching but nothing worked and I forgot to remove it before I posted.  Still searching and still nothing, but I will update you soon with a little information that might steer you in the right direction in helping me.

              • 4. Re: Custom dragIndicator on Spark List Control
                Flex harUI Adobe Employee

                Use of creationComplete is questionable.  You might have wrapped an old data

                object.  I think I would use dataChange event to propagate the current data

                item to where it needs to be.

                • 5. Re: Custom dragIndicator on Spark List Control
                  William Spence Level 1

                  First, I just want to thank you for suggesting using the dataChange event rather than creation complete.  I started having problems and that was the solution.

                   

                  So regarding the custom dragIndicator that I was asking you about, here is what I found out.  The drag proxy image in the dragManger (which I assume is used underneath the hood of list components for drag operations) needs to implement the IFlexDisplayObject Interface.  You can see from the itemRenderer in my earlier post that I use an mx Image, which I have to because these images are loaded dynamically by the user, and Image does not fall under the IFlexDisplayObject Interface.  The object that does that I could likely use is the BitmapAsset.  I think I can figure out how to convert my Image to a BitmapAsset, but I have no idea how I would feed that to the list as my dragIndicator.  Does all this sound right and do you know how I can feed a BitmapAsset dynamically to a dragIndicator?

                  • 6. Re: Custom dragIndicator on Spark List Control
                    William Spence Level 1

                    OK, I've done a little testing and if I embed an image and use a BitmapImage component instead of an image component in the itemRender, dragging the image works fine.   So it is simply an issue of Image being the wrong tool for the job when you want to drag images in an itemRenderer.  The problem is, these images are loaded dynamically by the user so BitmapImage can't be used for this. So basically, I need to figure out a way to load an image dynamically and push that data into a BitmapImage in the itemRenderer.  I am currently reading about using a SWFloader to load the image and then pushing this information into a BitmapData component.  Do you think this might work?

                    • 7. Re: Custom dragIndicator on Spark List Control
                      William Spence Level 1

                      Flex harUI, or anyone who has a thought, please share a little insight.  I have an Air project running on the desktop.  I have a Flex List in a tile layout that is populated with thumbnail images.  Users are able to select the images on their harddrive that they want to use in the project to populate the list, and then they are able to drag them into the order that they want to use them.  Everything is working fine except for the dragIndicator.  When you drag a Spark list, it uses a dragIndicator image to show that you are moving something.  I want my dragIndicator to be a semi-transparent version of the thumbnail image that is being dragged.  Using a Spark List, you should be able to place an image in the ItemRenderer that is included in the dragging state of the itemRenderer, and voila, it becomes your dragIndicator.  The catch  is that you have to use an embedded image in a BitmapImage component.  Unfortunately, I cannot embed the images because they are loaded dynamically by the user.

                       

                      I have tried everything that I can think of at the ItemRender level.  I have tried loading the images using both Image and SWFloader, and pushing the bitmap data into a BitmapImage component, but no luck.  I have been stuck on this little problem for over 3 days.

                       

                      I would think this is a very common scenario, especially with new mobile apps.  I would think that creating thumbnail images dynamically and dragging them in a list would be a very common thing, and that someone would have figured this out for the newer Spark controls, but I have not found anything except for the dragManager and I don't know how to use that in conjunction with the List drag features.  Maybe i need to leave the itemRender and attack this from the Level of the List control?

                       

                      Any thoughts, ideas, suggestions???

                       

                      Thanks.

                      • 8. Re: Custom dragIndicator on Spark List Control
                        Evtim Georgiev (Adobe) Level 2

                        Hi William,

                         

                        I think the reason that the image doesn't show up in the drag indicator is because it's being dynamically loaded.  The item-renderer for the drag proxy is actually re-created and the image is being re-loaded in there.  The problem is that with AIR there's a limitation in the drag manager that actually captures a bitmap of your drag proxy... you don't see the same problem in a web app right?

                         

                        So here is the order of things happening when you start dragging:

                         

                        1. Drag starts

                        2. Flex creates the DragProxy

                        3. The DragProxy creates new ItemRenderer instance and sets the same data as the original renderer

                        4. The ItemRenderer sets the source of Image, this triggers async load and the image is not visible yet

                        5. The AIR DragManager takes a bitmap snapshot of the DragProxy which contains the ItemRenderer that has not yet completed loading of the image

                        6. The DragManager uses the captured snapshot to display the drag indicator (so the image is missing)

                         

                        A possible work-around would be to ensure the image gets loaded / displayed right away.

                         

                        In Flex 4.5 the way to do it would be to use the ContentCache.  In Flex 4.5 the BitmapImage / Spark Image both support remote content loading and also have  a "contentLoader" property - you can create an instance of ContentCache class and set that as the value of the "contentLoader" property of the BitmapImage / Image components (use single shared ContentCache instance, perhaps in a static variable in the ItemRenderer then all images in all renderes share as their contentLoader).  This will cause the new itemRenderer for the drag indicator to immediately complete the load request if the original item renderer in the list has already completed the request and the image should be reflected in the drag proxy bitmap snapshot.

                         

                        Hope this helps.

                         

                        -Evtim

                        • 9. Re: Custom dragIndicator on Spark List Control
                          Flex harUI Adobe Employee

                          In 4.5, Spark Image has a cache which was supposed to make this problem go

                          away.  If you can't use 4.5, there is a component called SuperImage from

                          quietlyscheming.com that has a cache.

                          • 10. Re: Custom dragIndicator on Spark List Control
                            William Spence Level 1

                            Evtim, I can't thank you enough for your input, you seem to have a great understanding of the process.  Your explanation really made it clear what was going on behind the scenes and why nothing was working for me.  Unfortunately, I am not out of the woods yet.  I have to apologize because I am very new to Flex and extremely new to 4.5 SDK, so I probably am just missing some basic concept or syntax.  Here is what my itemRenderer currently looks like:

                             

                             

                            <?xml version="1.0" encoding="utf-8"?>
                            <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" 
                                                xmlns:s="library://ns.adobe.com/flex/spark" 
                                                xmlns:mx="library://ns.adobe.com/flex/mx" 
                                                autoDrawBackground="false" width="76" height="76" focusEnabled="false"
                                                creationComplete="listenForDataChange()">
                                 
                                 <fx:Script>
                                      <![CDATA[
                                           import mx.utils.ObjectProxy;               
                                           import spark.core.ContentCache;
                                           
                                           [Bindable]
                                           public var dataProxy:ObjectProxy;
                                           [Bindable]
                                           public var contentCache:ContentCache = new ContentCache();
                                           
                                           private function listenForDataChange():void{
                                                dataProxy = new ObjectProxy(data);
                                                addEventListener("dataChange", handleDataChanged);                    
                                                contentCache.load(dataProxy.pathSmall);
                                           }
                                           
                                           private function handleDataChanged(event:Event):void {
                                                dataProxy = new ObjectProxy(data);
                                           }
                                      ]]>
                                 </fx:Script>
                                 
                                 <s:states>
                                      <s:State name="normal" />
                                      <s:State name="hovered" />
                                      <s:State name="selected" />
                                      <s:State name="dragging" />
                                 </s:states>
                                 
                                 
                                 <s:Image source="{dataProxy.pathSmall}" width="68" height="68" contentLoader="{contentCache}"
                                            horizontalCenter="0" verticalCenter="0" cacheAsBitmap="true" includeIn="normal, hovered, selected, dragging"/>
                            </s:ItemRenderer>
                            

                             

                             

                            At creationComplete for the itemRenderer, I add a listener for dataChange and load the contentCache.  I have a pretty complicated dataProvider for the List so in the itemRenderer, I had to create a dataProxy and it works good.

                             

                            dataProxy.pathSmall is a string path to each image.  i.e. dataProxy.pathSmall = "C:\Images\small2.jpg"

                            I use the same path to load the ContentCache as I do for the Image source, and the Image is displaying great.  But when I drag the Image, I just get a faded box, no image.

                             

                            Am I missing something that is easy for you to see regarding the ContentCache and the contentLoader?

                             

                            Thanks, Bill

                            • 11. Re: Custom dragIndicator on Spark List Control
                              Evtim Georgiev (Adobe) Level 2

                              Hi Bill,


                              It looks like you are creating a new ContentCache object for each ItemRenderer, to create only one shared instance, add "static" like this:

                               

                              static public var contentCache:ContentCache = new ContentCache();

                              This will ensure that the contentCache variable is shared between all of the ItemRenderers.

                              Additionally, I think there's no need to explicitly call the contentCache like this:

                              contentCache.load(dataProxy.pathSmall);

                              Just setting the contentCache as the contentLoader property on the Image, the way you did should be enough.

                               

                              I just did a quick experiment and here's an example with an ItemRenderer that exhibits the problem:

                               

                               

                              <?xml version="1.0" encoding="utf-8"?>
                              <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
                                              xmlns:s="library://ns.adobe.com/flex/spark"
                                              xmlns:assets="assets.*">
                                     
                                  <s:BitmapImage id="icon" source="{data.graphic}" width="48" height="48"/>
                                     
                              </s:ItemRenderer>

                               

                              And here's the work-around with contentCache (works well for me in AIR):

                               

                              <?xml version="1.0" encoding="utf-8"?>
                              <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
                                              xmlns:s="library://ns.adobe.com/flex/spark"
                                              xmlns:assets="assets.*">
                                 
                                  <fx:Script>
                                      <![CDATA[
                                          import spark.core.ContentCache;
                                          static private const contentCache:ContentCache = new ContentCache();
                                      ]]>
                                  </fx:Script>

                                  <s:BitmapImage id="icon" source="{data.graphic}" width="48" height="48" contentLoader="{contentCache}"/>
                              </s:ItemRenderer>

                               

                              Cheers,

                              Evtim

                              • 12. Re: Custom dragIndicator on Spark List Control
                                William Spence Level 1

                                Evtim, there is a good reason that Adobe hired you to work for them, because you are REALLY good at this stuff!  I can't thank you enough for your help with this, I have been pulling my hair out trying to figure this out and it was so simple.  YOU ARE THE MAN!!!

                                 

                                There is an interesting little twist I came across that you may have some insight on.  I tried your suggestion in my application, and it did not work so I was initially a little discouraged.  But you sent your own itemRenderer along in your post, and so I created a quick little sample application that just contained a dataProvider, a List, and your itemRenderer, and it WORKED LIKE A CHARM!!!  I was now very excited, but why didn't it work in my application?  There was not a lot of difference between the basic structure of our itemRenderers, so I started changing these little differences, and one change turned out to be the cause of my problem - the objectProxy.  When you use the objectProxy, for whatever reason, it no longer works.

                                 

                                I may be doing something wrong with my dataProvider, but here is the reason that I started to use the objectProxy.  I didn't want to keep track of separate arrays for all of the photo operations that I will be doing in the application, so I decided to keep all of the important information that I need about the photos in one big ArrayCollection.  Here is how I fill it:

                                 

                                 

                                imageAC.addItem({pathSmall: file.nativePath, pathLarge: file2.nativePath,

                                     originalPath: filesEvent.files[imageIndex].nativePath, originalName: filesEvent.files[imageIndex].name,

                                     nameNum: NAME_COUNT, rotation: null, text: null, fpType: null, fpX: null, fpY: null});

                                 

                                 

                                So I use imageAC as the dataProvider in my List.  The path to each image is in imageAC.pathSmall, so in my itemRenderer, I try to access the path with

                                data.pathSmall.  When I run the program in debug mode, it works fine, but I keep getting the message:

                                 

                                "warning: unable to bind to property 'pathSmall' on class 'Object' (class is not an IEventDispatcher)"

                                 

                                After a google search, it was found that using an objectProxy would get rid of this warning.   I tried it and it did.  But it also is the reason that with the contentCache, dragged images will not work correctly.

                                 

                                Can you tell if I am not loading my arrayCollection correctly to use as a dataProvider, or is this a common warning that comes up that I should just not worry about?

                                 

                                Again, many thanks for your help!

                                 

                                Bill

                                 


                                • 13. Re: Custom dragIndicator on Spark List Control
                                  Evtim Georgiev (Adobe) Level 2

                                  Hey Bill,

                                  I figured out what's wrong with your itemRenderer - you are attaching the dataChange listener too late, just add it as an attribute to the ItemRenderer like this:

                                   

                                  <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
                                                  xmlns:s="library://ns.adobe.com/flex/spark"
                                                  xmlns:assets="assets.*"
                                                  dataChange="handleDataChanged(event)">

                                   

                                  I think you can save yourself the proxy if you're using it just to avoid binding warning, by avoiding the binding altogether (and it will be faster!) like this:

                                   

                                  <?xml version="1.0" encoding="utf-8"?>
                                  <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
                                                  xmlns:s="library://ns.adobe.com/flex/spark"
                                                  xmlns:assets="assets.*"
                                                  dataChange="handleDataChanged(event)">

                                   

                                      <fx:Script>
                                          <![CDATA[
                                              import spark.core.ContentCache;
                                              static private const contentCache:ContentCache = new ContentCache();

                                   

                                              private function handleDataChanged(event:Event):void {
                                                  icon.source = data.smallPath;
                                              }
                                          ]]>
                                      </fx:Script>

                                   

                                      <s:BitmapImage id="icon" width="48" height="48" contentLoader="{contentCache}"/>
                                      <s:Label text="foo  asdf asd fas df asf sa da fd sad" right="0"/>
                                  </s:ItemRenderer>

                                   

                                   

                                  Cheers,

                                  Evtim

                                  • 14. Re: Custom dragIndicator on Spark List Control
                                    William Spence Level 1

                                    You are brilliant! Thanks for your help Evtim and enjoy your weekend!

                                    • 15. Re: Custom dragIndicator on Spark List Control
                                      William Spence Level 1

                                      Hi Evtim,

                                       

                                      Earlier this summer, you helped me to create an itemRenderer that would allow me to drag photos in a list with a grid layout so that I could re-order them, and the dragIndicator would remain the photo instead of a empty box.  We accomplished this by using the contentCache feature with the Spark BitmapImage.  Now when I drag my photos, they remain photos even while being dragged.  Your very elegant solution ended up looking like this:

                                       

                                       

                                      <?xml version="1.0" encoding="utf-8"?>
                                      <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" 
                                                                              xmlns:s="library://ns.adobe.com/flex/spark" 
                                                                              xmlns:mx="library://ns.adobe.com/flex/mx" 
                                                                              width="76" height="76" focusEnabled="false">
                                        
                                                <fx:Script>
                                                          <![CDATA[
                                                                    import spark.core.ContentCache; 
                                      
                                                                    static private const contentCache:ContentCache = new ContentCache();
                                      
                                                          ]]>
                                                </fx:Script>
                                        
                                                <s:states>
                                                          <s:State name="normal" />
                                                          <s:State name="hovered" />
                                                          <s:State name="selected" />
                                                          <s:State name="dragging" />
                                                </s:states>
                                        
                                      
                                                <s:BitmapImage source="{data.pathSmall}" width="70" height="70" contentLoader="{contentCache}"
                                                                                  horizontalCenter="0" verticalCenter="0" alpha.dragging="2" includeIn="normal, hovered, selected, dragging"/>
                                      </s:ItemRenderer>
                                      
                                      

                                       

                                      Now I have another little interesting challenge with this itemRenderer.  I don't want to bore you with details, but the situation is that I will no longer be loading thumbnails that are 70x70 pixels in size, but I will be loading photos with various dimensions that have to be displayed as a 70x70 pixel thumbnail.  So I am going to create a Group that is 70x70 pixels, turn on the clipAndEnableScrolling for the Group, and then place the photos within the Group so that they essentially get cropped.  The images that I import will have to be resized to either 70 pixels wide or 70 pixels high depending on what is the larger dimension of the of the image, and then placed inside the Group so that I get a perfect center-crop of the images.


                                      So to summarize, I need to load the photos using a loader.  Then I can figure out with the height and width of the photos is.  Then I can scale the photo so that the smaller dimension is 70 pixels, and then I can place it in the Group.  But to do all of this, I can no longer do it in MXML but have to do it in ActionScript... and thus my dilema.  I can't figure out how to use the contentCache properly in ActionScript so that the images stay images when dragged.  Here is what I have so far:

                                       


                                      <?xml version="1.0" encoding="utf-8"?>
                                      <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" 
                                                                              xmlns:s="library://ns.adobe.com/flex/spark" 
                                                                              xmlns:mx="library://ns.adobe.com/flex/mx" 
                                                                              width="76" height="76" focusEnabled="false" dataChange="init(event)">
                                        
                                                <fx:Script>
                                                          <![CDATA[
                                                                    import mx.core.FlexGlobals;
                                                                    import mx.events.FlexEvent;
                                        
                                                                    import spark.core.ContentCache;  
                                                                    private var contentCache:ContentCache = new ContentCache();
                                      
                                                                    private var imageLoader:Loader = new Loader();
                                                                    private var bitmapImg:BitmapImage = new BitmapImage();
                                                                    private var widthHolder:int;
                                                                    private var heightHolder:int;
                                        
                                                                    protected function init(event:FlexEvent):void {
                                                                              var request:URLRequest = new URLRequest(FlexGlobals.topLevelApplication.imageAC[this.itemIndex].pathSmall);
                                                                              imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, createVisuals);
                                                                              imageLoader.load(request);
                                                                    }
                                        
                                                                    private function createVisuals(event:Event):void{
                                                                              bitmapImg.source = Bitmap(imageLoader.content);
                                                                              bitmapImg.horizontalCenter = 0;
                                                                              bitmapImg.verticalCenter = 0;
                                                                              bitmapImg.smooth = true;
                                                                              bitmapImg.smoothingQuality = "high";
                                                                              widthHolder = imageLoader.width;
                                                                              heightHolder = imageLoader.height;
                                        
                                                                              if (widthHolder > heightHolder) {
                                                                                        bitmapImg.width = int(Math.round(70*widthHolder/heightHolder));
                                                                                        bitmapImg.height = 70;
                                                                              }
                                                                              else {
                                                                                        bitmapImg.width = 70;
                                                                                        bitmapImg.height = int(Math.round(70*heightHolder/widthHolder));
                                                                              }
                                        
                                                                              imageGroup.addElement(bitmapImg);
                                                                    }
                                        
                                                          ]]>
                                                </fx:Script>
                                        
                                                <s:states>
                                                          <s:State name="normal" />
                                                          <s:State name="hovered" />
                                                          <s:State name="selected" />
                                                          <s:State name="dragging"/>
                                                </s:states>
                                        
                                      
                                                <s:Group id="imageGroup" width="70" height="70" horizontalCenter="0" verticalCenter="0" clipAndEnableScrolling="true"/> 
                                                          
                                      </s:ItemRenderer>
                                      
                                      

                                       

                                      So when there is a dataChange in my List, the init() function gets called to load the image.  When the load is complete, the createVisuals() method resizes the image and places it in the Group. Everything is working as expected.  The images get resized, centered and cropped within the group, and smoothed out so that they look nice.  But now when I drag them, I get an empty box while dragging.  I am unsure of how to tie in the contentCache with my bitmapImage object so that it will stay an image while being dragged using actionScript.  I don't know if it is as simple as setting the contentLoader property for the bitmapImage object, or if it is more complicated and I need to set the image source for the "dragging" state.  Could you possibly give me some insight as to how to make this happen in ActionScript?

                                       

                                      Thanks for any insight!

                                      Bill