13 Replies Latest reply on Feb 16, 2009 3:48 PM by dalejoel

    Force AdvancedDataGrid header redraw

    dalejoel Level 1
      I have an AdvancedDataGrid where I need to be able to programmatically cause a column header to redraw. For the life of me I can't figure this out. Basically my scenario is I want to respond to an external event that will cause a header to highlight. Clicking on the header is not an option in this case.

      The only thing I've found that works is casting the adg's dataprovider to the correct type of view and calling "refresh" which forces the headers to redraw. The problem is that takes too long and is refreshing things that do not need it. I want to single out just the headers.

      Any ideas?

      Thanks,
      Joel


        • 1. Re: Force AdvancedDataGrid header redraw
          Level 7

          "dalejoel" <webforumsuser@macromedia.com> wrote in message
          news:gmq0sc$bre$1@forums.macromedia.com...
          >I have an AdvancedDataGrid where I need to be able to programmatically
          >cause a
          > column header to redraw. For the life of me I can't figure this out.
          > Basically
          > my scenario is I want to respond to an external event that will cause a
          > header
          > to highlight. Clicking on the header is not an option in this case.
          >
          > The only thing I've found that works is casting the adg's dataprovider to
          > the
          > correct type of view and calling "refresh" which forces the headers to
          > redraw.
          > The problem is that takes too long and is refreshing things that do not
          > need
          > it. I want to single out just the headers.

          Check out their approach to headerRenderers
          http://www.returnundefined.com/2006/11/creating-truly-reusable-renderers-with-classfactory


          • 2. Re: Force AdvancedDataGrid header redraw
            dalejoel Level 1
            I've read about this before. The way I interpret that article to my situation is that I should store all the created item renderers in the ClassFactory instance and when needed, walk the renderers and invalidateDisplayList( ) on each.

            So I would need to provide a "refresh" method on my instance of the ClassFactory. Because my problem is that they are not redrawing. This article implies they are constantly being redrawn and recreated but I'm not seeing that. My state, as it were, is dependent on the data being passed to the renderer. As it renders, it needs to know if it draws as highlighted or not. And if the state changes, I need to redraw. The state is contained in an outside object, but what causes the redraw is the bigger problem.

            -Joel
            • 3. Re: Force AdvancedDataGrid header redraw
              Level 7

              "dalejoel" <webforumsuser@macromedia.com> wrote in message
              news:gmqgeh$kg$1@forums.macromedia.com...
              > I've read about this before. The way I interpret that article to my
              > situation
              > is that I should store all the created item renderers in the ClassFactory
              > instance and when needed, walk the renderers and invalidateDisplayList( )
              > on
              > each.
              >
              > So I would need to provide a "refresh" method on my instance of the
              > ClassFactory. Because my problem is that they are not redrawing. This
              > article
              > implies they are constantly being redrawn and recreated but I'm not seeing
              > that. My state, as it were, is dependent on the data being passed to the
              > renderer. As it renders, it needs to know if it draws as highlighted or
              > not.
              > And if the state changes, I need to redraw. The state is contained in an
              > outside object, but what causes the redraw is the bigger problem.

              No, you need to look at how the headerRenderer is binding to a property in
              the main document. You only use the ClassFactory to tell the header what to
              bind to. Then all you have to do is change that value and away you go.


              • 4. Re: Force AdvancedDataGrid header redraw
                dalejoel Level 1
                okay - I got it working but slightly differently. I do bind at runtime but the example assumes I need no information from the data set in the renderer. In my case I reuse the header renderer for sixty different columns and thus I need to associate each renderer with the unique column it is rendering for. So I actually bind to the internal state of the data passed into the set data function on the header renderer as part of set data. I also store the watcher returned from the binding and store it locally in the header renderer becauase since renderers are reused, every time set data is called, I call unwatch on the local watcher to break any previous state and then re-bind and assign a new watcher.

                So basically this:

                public class MyHeaderRenderer extend AdvancedDataGridHeaderRenderer
                {

                protected var watcher:ChangeWatcher = null;
                public function MyHeaderRenderer ()
                {
                super();
                }
                public function onUpdate(value:Boolean):void
                {
                invalidateDisplayList();
                }
                public override function set data(value:Object):void
                {
                super.data = value;
                //unbind the property we bound on
                if(watcher != null)
                {
                watcher.unwatch();
                }

                if(value is ISelectable)
                {
                //Bind on element in the actual value
                watcher = BindingUtils.bindSetter(onUpdate, value, "_isSelected");
                }
                }
                }
                • 5. Re: Force AdvancedDataGrid header redraw
                  Level 7

                  "dalejoel" <webforumsuser@macromedia.com> wrote in message
                  news:gmsf0n$em6$1@forums.macromedia.com...
                  > okay - I got it working but slightly differently. I do bind at runtime but
                  > the
                  > example assumes I need no information from the data set in the renderer.
                  > In my
                  > case I reuse the header renderer for sixty different columns and thus I
                  > need to
                  > associate each renderer with the unique column it is rendering for.

                  It seems like you still haven't understood the example. You'd create a
                  ClassFactory for each column, and then you'd set the property that tells it
                  what to bind to on each ClassFactory. What you're showing here is that all
                  of your columns are going to bind on exactly the same property of the data
                  object, _isSelected. I don't think you can count on the data object of the
                  header being any particular row, so it seems like you're setting yourself up
                  to have a big problem here.

                  > So I
                  > actually bind to the internal state of the data passed into the set data
                  > function on the header renderer as part of set data. I also store the
                  > watcher
                  > returned from the binding and store it locally in the header renderer
                  > becauase
                  > since renderers are reused, every time set data is called, I call unwatch
                  > on
                  > the local watcher to break any previous state and then re-bind and assign
                  > a new
                  > watcher.

                  That doesn't really make much sense... The headers shouldn't be getting
                  much new data as such, though they may get a set data call happening when
                  something in the collection changes.

                  > So basically this:
                  >
                  > public class MyHeaderRenderer extend AdvancedDataGridHeaderRenderer
                  > {
                  >
                  > protected var watcher:ChangeWatcher = null;
                  > public function MyHeaderRenderer ()
                  > {
                  > super();
                  > }
                  > public function onUpdate(value:Boolean):void
                  > {
                  > invalidateDisplayList();
                  > }
                  > public override function set data(value:Object):void
                  > {
                  > super.data = value;
                  > //unbind the property we bound on
                  > if(watcher != null)
                  > {
                  > watcher.unwatch();
                  > }
                  >
                  > if(value is ISelectable)
                  > {
                  > //Bind on element in the actual value
                  > watcher = BindingUtils.bindSetter(onUpdate, value, "_isSelected");
                  > }
                  > }
                  > }

                  If you bind to a property in the parent document, then that should be
                  consistent for that column unless you're doing something that you haven't
                  even hinted at here, such as constantly changing what the columns are
                  looking at. You're adding a ton of extra overhead, and I suspect you're not
                  releasing the reference by just setting the watcher to null. Expect memory
                  leaks.

                  I would really, really, suggest that you read this article as many times as
                  you need to to truly understand what it is doing, because the approach
                  you're taking is fraught with problems.

                  Also, check out this presentation for why you will probably get memory leaks
                  with this approach:
                  http://tinyurl.com/databinding


                  • 6. Re: Force AdvancedDataGrid header redraw
                    dalejoel Level 1
                    Hi Amy -
                    Thanks for the help. I believe I understood the example but I guess it really boils down to this:

                    Am I to understand that by using the same factory instance per column, the instances of the renderer will potentially get reused among the columns (the scenario I am protecting against with my watch/unwatch thing)?

                    But by using a new factory instance per column there will be no sharing of the generated renderers among columns and thus I should have no worry that the data passed into set data( ) will be a different object and will not need to protect against it? Because if there's a potential that my external binding could get mapped to a second object that had at one time rendered a different column, that is a problem.

                    A second short question - do you know why the example marks the external Factory member as bindable? This example is also in the Flex 3 Cookbook from OReilly and they simply say "for convenience" but I am not sure it is necessary at all.

                    Thanks,
                    Joel
                    • 7. Re: Force AdvancedDataGrid header redraw
                      Level 7

                      "dalejoel" <webforumsuser@macromedia.com> wrote in message
                      news:gn29gs$qm6$1@forums.macromedia.com...
                      > Hi Amy -
                      > Thanks for the help. I believe I understood the example but I guess it
                      > really
                      > boils down to this:
                      >
                      > Am I to understand that by using the same factory instance per column, the
                      > instances of the renderer will potentially get reused among the columns
                      > (the
                      > scenario I am protecting against with my watch/unwatch thing)?

                      I don't think you understand what a Factory is. There is no such thing as a
                      Factory Instance. Think of a Factory as like a cookie cutter. Each cookie
                      cutter will make a given kind of cookie, but a cutter is not a cookie. When
                      you set up a ClassFactory, you say I want Cookies (the class) and they need
                      to be shaped like Christmas trees (the properties). When you want
                      star-shaped cookes, you use a different cookie cutter.

                      So, the ClassFactory is the cutter, and the renderers that Flex makes from
                      it are the Cookies (instances).

                      > But by using a new factory instance per column there will be no sharing of
                      > the
                      > generated renderers among columns and thus I should have no worry that the
                      > data
                      > passed into set data( ) will be a different object and will not need to
                      > protect
                      > against it? Because if there's a potential that my external binding could
                      > get
                      > mapped to a second object that had at one time rendered a different
                      > column,
                      > that is a problem.

                      The data passed into set data() is exactly the same for every renderer of a
                      row, regardless of whether the renderers of the different columns in the row
                      were created from the same ClassFactory or not. The data property is just
                      the item of the collection that the row is looking at. You can't tell
                      anything about the column by looking at the data property by itself.

                      > A second short question - do you know why the example marks the external
                      > Factory member as bindable? This example is also in the Flex 3 Cookbook
                      > from
                      > OReilly and they simply say "for convenience" but I am not sure it is
                      > necessary
                      > at all.

                      I'm not sure what you mean by the external factory member. If you mean the
                      var checkBoxHeaderRenderer, well, one thing you could do there is choose to
                      change the ClassFactory that variable contained, and when the value changed
                      the datagrid would automatically show the new renderer.

                      If you mean the allSelected property on the main application, this is
                      Bindable because the isSelected property in the renderer is bound to the
                      isSelected property in the main document so it doesn't "forget" its state
                      when the header refreshes. But what I'm telling you is that you can use
                      this same concept of binding to an external property to cause something
                      inside the renderer to change in response to something in the outer
                      application.


                      • 8. Re: Force AdvancedDataGrid header redraw
                        dalejoel Level 1
                        Hi Amy -

                        In my scenario, I am discussing the HeaderRenderer so there is no random row data getting passed in. The data passed in is the column object. A column has a header renderer associated with it. When the ClassFactory creates an instance of the header renderer, the owning column is passed in as the data. Seems almost circular but that's what I see happening.

                        Now in that sense the scenario is a little different than general item renderers. However I do agree that data passed to renderers in general is just some row data.

                        <There is no such thing as a Factory Instance. Think of a Factory as like a cookie cutter. Each cookie
                        <cutter will make a given kind of cookie, but a cutter is not a cookie. When
                        <you set up a ClassFactory, you say I want Cookies (the class) and they need
                        <to be shaped like Christmas trees (the properties). When you want
                        <star-shaped cookes, you use a different cookie cutter.

                        <So, the ClassFactory is the cutter, and the renderers that Flex makes from
                        <it are the Cookies (instances).

                        I must disagree with you on your point that there is no instance of a factory. In the example code in question they have these lines:
                        [Bindable] private var checkBoxHeaderRenderer:ClassFactory;
                        ...
                        checkBoxHeaderRenderer = new ClassFactory(GenericCheckBoxHeaderRenderer);
                        checkBoxHeaderRenderer.properties = {externalObject: this, externalPropertyName: "allSelected"};

                        The checkBoxHeaderRenderer is of ClassFactory type and is initialized with an actual instance of a ClassFactory. That factory only makes GenericCheckBoxHeaderRenderer objects. Then, state is set on the factory with the properties array. The factory happens to pass the state to the renderer instance, but the state comes from the factory. And since the ClassFactory instance uses this internal state to initialize any renderer it creates, passing that binding along to the renderer at creation time is in essence tying the renderer to the state of the ClassFactory at a given moment in time. So then if I want to use that instance of the ClassFactory but change the properties array, any instance of a renderer already instantiated will still use the original properties passed in, not the new ones. And any new renderer will use the new properties.

                        In the case of the example - if that exact ClassFactory and property array were used for two columns, the renders would both be bound to the exact same allSelected. So if that allSelected changed, both columns would show as checked/unchecked the same way.

                        Now in my personal scenario, each column acts the same way in terms of functionality but I need each header renderer to be bound to a different property so that each column header is distinguished from one another. In order to do this via the ClassFactory technique, I would have to create a new ClassFactory for each column's header renderer and set the properties array to be the exact unique object I want to bind to. If I do not do this, then I would have multiple renderers bound to the same external object and that would lead to multiple column headers showing as being selected when one single external property changes.

                        So I have two choices as I see it.

                        1) Create a new ClassFactory instance per column and set the "properties" on the factory to the external data in question per column.

                        or

                        2) Create one ClassFactory that is used all over the place for the column headers and make sure when data is set, that I remove old bindings and make a new binding to the correct external data so that at any given moment, the viewable header renderers are bound correctly even in the face of being reused.

                        Thanks for the link to the binding video by the way - I have only had time to watch a little but I can tell it will be good.

                        -Joel

                        • 9. Re: Force AdvancedDataGrid header redraw
                          dalejoel Level 1
                          I wanted to add one big point of confusion for me. I do not see where the fact that the GenericCheckBoxHeaderRenderer is an IFactory is even used. ClassFactory takes an Class type in its constructor and never calls newItem on it.
                          • 10. Re: Force AdvancedDataGrid header redraw
                            Level 7

                            "dalejoel" <webforumsuser@macromedia.com> wrote in message
                            news:gn4p43$3qg$1@forums.macromedia.com...
                            >I wanted to add one big point of confusion for me. I do not see where the
                            >fact
                            > that the GenericCheckBoxHeaderRenderer is an IFactory is even used.
                            > ClassFactory takes an Class type in its constructor and never calls
                            > newItem on
                            > it.
                            http://blog.flashgen.com/flex/itemrendering-pt1/


                            • 11. Re: Force AdvancedDataGrid header redraw
                              dalejoel Level 1
                              I believe that the link you showed me and Adobe's own documentation are incorrect. And I say that because I looked at the ClassFactory source file and it's extremely short and never uses IFactory.

                              Look at this:
                              http://livedocs.adobe.com/flex/3/html/help.html?content=cellrenderer_4.html

                              Now - according to that page the following code example is used:
                              < // Cast the value of the itemRenderer property
                              < // to ClassFactory.
                              < public function initCellEditor():void {
                              < myList.itemRenderer=new ClassFactory(RendererState);

                              The comment claims that RendererState is being cast to ClassFactory. But ClassFactory has a constructor that takes in a Class instance. So no casting is going on here, it's just the construction of a ClassFactory.

                              Then - by looking at the ClassFactory source code, "newInstance" is never called on the so called "generator" which is just the RendererState passed in. This is because the "generator" does not have to be an IFactory.

                              Even though Adobe claims in the docs that in mxml, the itemRenderer is cast to be ClassFactory, OReilly claims that a line like "<mx:DataGridColumn itemRenderer="mx.controls.CheckBox"> actually gets turned into something like
                              var temp: mx.core.ClassFactory = new mx.core.ClassFactory();
                              temp.generator = mx.controls.CheckBox;
                              return temp.

                              And when I looked at CheckBox in AdobeDocs or even ItemRenderer for that matter, they do not implement IFactory. So I think we are being told one thing but something else is actually happening.

                              -Joel

                              • 12. Re: Force AdvancedDataGrid header redraw
                                Level 7

                                "dalejoel" <webforumsuser@macromedia.com> wrote in message
                                news:gn58ge$mof$1@forums.macromedia.com...
                                >I believe that the link you showed me and Adobe's own documentation are
                                > incorrect. And I say that because I looked at the ClassFactory source file
                                > and
                                > it's extremely short and never uses IFactory.
                                >
                                > Look at this:
                                > http://livedocs.adobe.com/flex/3/html/help.html?content=cellrenderer_4.html
                                >
                                > Now - according to that page the following code example is used:
                                > < // Cast the value of the itemRenderer property
                                > < // to ClassFactory.
                                > < public function initCellEditor():void {
                                > < myList.itemRenderer=new ClassFactory(RendererState);
                                >
                                > The comment claims that RendererState is being cast to ClassFactory. But
                                > ClassFactory has a constructor that takes in a Class instance. So no
                                > casting is
                                > going on here, it's just the construction of a ClassFactory.
                                >
                                > Then - by looking at the ClassFactory source code, "newInstance" is never
                                > called on the so called "generator" which is just the RendererState passed
                                > in.
                                > This is because the "generator" does not have to be an IFactory.
                                >
                                > Even though Adobe claims in the docs that in mxml, the itemRenderer is
                                > cast to
                                > be ClassFactory, OReilly claims that a line like "<mx:DataGridColumn
                                > itemRenderer="mx.controls.CheckBox"> actually gets turned into something
                                > like
                                > var temp: mx.core.ClassFactory = new mx.core.ClassFactory();
                                > temp.generator = mx.controls.CheckBox;
                                > return temp.
                                >
                                > And when I looked at CheckBox in AdobeDocs or even ItemRenderer for that
                                > matter, they do not implement IFactory. So I think we are being told one
                                > thing
                                > but something else is actually happening.

                                You couldn't do this:

                                myRenderer = new ClassFactory(CheckBox);
                                myRenderer.properties = {something:'foo', somethingElse:'bar'};

                                That is what you get by implementing IFactory.

                                Good luck with it...


                                • 13. Re: Force AdvancedDataGrid header redraw
                                  dalejoel Level 1
                                  I got it working.
                                  The bit in the article about GenericCheckBoxHeaderRenderer implementing IFactory is unneeded and just confuses things. Especially since having a non-static member that creates itself is weird because you would need to have an instance of yourself to create yourself. It's redundant. Other than that it works. The trick is in understanding that this technique replaces using a constructor for dependency injection. The downside is the introduction of possible runtime type checking errors since there is no type safety at compile time.