Event handlers firing backwards?

Posted by jmgawlik on 21-Apr-2009 16:02

Ok, I created a toolstrip with some buttons to handle my AddRecord() and SaveChanges() method calls. I'm using the UltraWinGrid's BeforeRowUpdate event to call the Assign() method on my binding source. In a previous app that I built (using regular old buttons), I can update the row then click on save and the 'Assign' happens then the 'Save' happens. Well, this is what I want (obviously) but with the one I wrote today 'Save' happens then 'Assign' happens. This doesn't work very well. I have been going through the settings for my grid and I can't see any differences. I'm stumped. Anyone else ever see this? Am I just missing something obvious? Any suggestions on what to look at? I'm really scratching my head here, help would be appreciated immensely.

All Replies

Posted by Admin on 21-Apr-2009 23:29

Can you post a bit sample code?

It sounds like you are running into a general issue with Toolbar Buttons (no matter if Infragistics or Microsoft). They do not receive the focus (before getting clicked). Your other button receives focus before it get's clicked, so the Grid's focus is lost and it writes its values to the binding source.

If that's the actual issue, you need to call a method of the Grid and write the values back to the binding source before the Assign. I don't have the method name at hand right now, but I can look it up for you, if that's the issue.

Posted by jmgawlik on 22-Apr-2009 10:13

Hi again Mike. I keep having questions and I never seem to be the one with answers.

I just happened to determine that is has to be caused by a focus issue. Although, I can't really understand how the toolstrip buttons can be so different, I figure that is the only logical assumption that I can make. I just happened to be experimenting with creating my own 'BindingNavigator' from our previous thread using Sports2000 when I tripped over this one. Anyway, if you really still want to see the code, I can zip up the project. Otherwise, if you have the name of the method that you mentioned I would appreciate it.

In the future, I would really like to be the one helping someone for a change. Thanks again, Mike.

-John

Posted by Admin on 22-Apr-2009 13:48

Hey John! Don't hesitate...

Check this out: http://devcenter.infragistics.com/Support/KnowledgeBaseArticle.Aspx?ArticleID=4220

In ABL Code, that the following code for the ToolClick handler:

METHOD PRIVATE VOID ultraToolbarsManager1_ToolClick( INPUT sender AS System.Object, INPUT e AS Infragistics.Win.UltraWinToolbars.ToolClickEventArgs ):

          

    CASE e:Tool:Key:

        WHEN "Save" THEN DO:                                                      

            ultraGrid1:PerformAction(Infragistics.Win.UltraWinGrid.UltraGridAction:ExitEditMode).

            ultraGrid1:UpdateData().                                                                                                             

        END.

    END CASE.

                 

    CATCH err AS Progress.Lang.Error :

             

        MESSAGE "Error" err:GetMessage(1) VIEW-AS ALERT-BOX ERROR .                   

                  

    END CATCH.               

END METHOD.

and the following code for the BeforeRowUpdate (which is triggered from the UpdateData() call):
METHOD PRIVATE VOID ultraGrid1_BeforeRowUpdate( INPUT sender AS System.Object, INPUT e AS Infragistics.Win.UltraWinGrid.CancelableRowEventArgs ):
     
    DO TRANSACTION:
        GET CURRENT qQuery EXCLUSIVE .    
        bindingSource1:Assign().
        GET CURRENT qQuery NO-LOCK  .
    END.
          
    RETURN.
END METHOD.

Let me know, if that works for you!

Posted by jmgawlik on 23-Apr-2009 09:34

Hi Mike,

Just want to let you know that that was what I needed for my toolstrip button. I had found the UpdateData but I hadn't stumbled on to the ...PerformAction(...ExitEditMode). Thanks again.

I do have another question for you or anyone else. I have noticed that when I create a new record in my grid that some odd rendering is happening. The record is there but afterward I see the selected record is actually the record above it.

For example:

1st Record:                1     Lift Tours     276 North Drive

Selected Record:        1     Lift Tours     276 North Drive 

As I move down I see:

1st Record:                1     Lift Tours            276 North Drive

2nd Record:               2     Urpon Frisbee     Rattipolku 3      

Selected Record:       2     Urpon Frisbee     Rattipolku 3      

Have you ever seen that?

Posted by Admin on 23-Apr-2009 10:43

Did you add the new record to the Queries result list?

Posted by jmgawlik on 23-Apr-2009 10:57

This is my AddRecord method. I could be doing it all wrong but it seems to be working...well, sort of.

define variable hnQuery as handle no-undo.

define variable riRowid   as rowid   no-undo.

define variable hnBuffer  as handle no-undo.

hnQuery = objBindingSource:Handle:top-nav-query.

hnBuffer = objBindingSource:Handle:get-top-bufer().

hnBuffer:buffer-create().

riRowid = hnBuffer:rowid.

hnQuery:create-result-list-entry().

hnQuery:reposition-to-rowid(riRowid).

That's basically all I'm doing. I set my objBindingSource when I load the data but it's a copy of the original. I'm wondering if that might be my problem. It may not like that fact that I'm using a copy...I thought that it would be by reference but maybe not.

Posted by Peter Judge on 23-Apr-2009 10:57

Did you add the new record to the Queries result list?

An alternative to this approach might be to reopen the query, and then to reposition to the relevant row. You'd want to do this when, for instance, the changed/new record is out of sort order.

In general, I like the approach of working on the data (rather than the UI) and telling the UI (controls) that there's a change in the data and that they should update themselves. This means that I can swap out UI without having to re-implement this sort of stuff every time, and also allows me to abstract my UI Logic from the actual UI, and also any "client-side" data logic too.

So my 'Add' button would call a routine - let's call it "AddRecord" - that added a record to the temp-table and then (re)opened the query. The AddRecord() routine would then notify the UI that a new record had been added, and that it should do something. If a ProBindingSource is used, for instance, "do something" might be "do nothing" since the PBS is Smart Like That, what with AutoSync and all that cool hotness (tm). In other cases, there'd need to be some code which dealt with refreshing the UI, but in all cases, the actual creation/modification of the data would be the same code.

-- peter

Posted by Admin on 23-Apr-2009 11:02

pjudge schrieb:

An alternative to this approach might be to reopen the query, and then to reposition to the relevant row. You'd want to do this when, for instance, the changed/new record is out of sort order.

That might be o.k. for a small set of records, right? The Query would be reopened as a PRESELECT query and that may take some time for a large result set.

But I agree with you totally, that that's not a task of the UI to do. In any reasonable program, that should not be directly in the CreateRow event handler of the bindingSource. It's rather a metod of the service adapter on the client (or how ever you'd like to call that component).

Posted by Peter Judge on 23-Apr-2009 11:12

An alternative to this approach might be to reopen the query, and then to reposition to the relevant row. You'd want to do this when, for instance, the changed/new record is out of sort order.

That might be o.k. for a small set of records, right? The Query would be reopened as a PRESELECT query and that may take some time for a large result set.

Of course. But the question of whether a newly-added or -updated record should be sorted correctly remains (and was, as you may remember, a prickly issue in ADM2 too). Discussion of this leads us into the deep woods of batching strategies  and how much data to keep on the client given that a UI can only display a finite amount at any given time.

It's rather a metod of the service adapter on the client (or how ever you'd like to call that component).

Absolutely. I like the name Model, myself.

-- peter

Posted by jmgawlik on 23-Apr-2009 11:17

I think I see what you and Peter are saying. Just for clarification though, the actual AddRecord is in a service adapter class. I was trying to make it a little more generic so that maybe I could use it as an overridable method in a super class. I was also trying to test out creating my own Navigation Bar. Probably should have taken that a step at a time.

Question to Peter: if you were to reopen your query, wouldn't you want to reposition to that new record? I have done this before and I always have to reset my query back to the original once I'm done or I just get that one record. In the past I have had to capture the prepare-string as a property of my class, ensure that I have a unique record when I create a new one, reposition to that new record, then once everything is committed back to the db, reset the query back to the original prepare-string.

I hope that made sense. Perhaps I'm making this too hard.

Posted by jmgawlik on 23-Apr-2009 12:19

To answer one of my own little questions: it doesn't seem to matter, UI wise, if the binding source is a copy or not. It may matter in other instances but I get the same odd rendering either way.

method public void AddRecord(input hBuffer as handle):

  def var hQuery as handle no-undo.

  def var rRowid  as rowid   no-undo.

  hQuery = hBuffer:query.

  hBuffer:buffer-create() .

  rRowid = hBuffer:rowid.

  hQuery:create-result-list-entry().

  hQuery:reposition-to-rowid(rRowid).

end method.

versus:

method public void SetBindingSource(input objBndSrc as Progress.Data.BindingSource):

  objBindingSource = objBndSrc.

end method.

then...

method public void AddRecord():

  def var hQuery as handle no-undo.

  def var rRowid  as rowid   no-undo.

  def var hBuffer  as handle no-undo.

  hQuery = objBindingSource:Handle:top-nav-query.

  hBuffer  = objBindingSource:Handle:get-top-buffer().

  hBuffer:buffer-create() .

  rRowid = hBuffer:rowid.

  hQuery:create-result-list-entry().

  hQuery:reposition-to-rowid(rRowid).

end method.

Both seem to work just fine for a single temp-table dataset but both result in the strange grid rendering.

Posted by Peter Judge on 23-Apr-2009 12:28

Question to Peter: if you were to reopen your query, wouldn't you want to reposition to that new record? I have done this before and I always have to reset my query back to the original once I'm done or I just get that one record.

Yep. You can store a set of idenfitiers for the record, which are either ROWIDs or unique identifiers for the record (the buffer's :Keys values; which are translated into ROWIDs).

In the past I have had to capture the prepare-string as a property of my class, ensure that I have a unique record when I create a new one, reposition to that new record, then once everything is committed back to the db, reset the query back to the original prepare-string.

What do you mean by capturing (and resetting) the prepare-string?

-- peter

Posted by jmgawlik on 23-Apr-2009 12:40

Yep. You can store a set of idenfitiers for the record, which are either ROWIDs or unique identifiers for the record (the buffer's :Keys values; which are translated into ROWIDs).


I hadn't thought of that way. I will have to look int that one.

What do you mean by capturing (and resetting) the prepare-string?

As a quick fix I defined a private character variable like chDefaultQueryString and once I fill the dataset I did the following:

chDefaultQueryString = dataset dsCustomers:top-nav-query:prepare-string.

Then once I had called SaveChanges() method I called another method to reopen the query with the original query string stored in chDefaultQuery. To be quite honest with you it's actually a pretty feeble way to do it but lack of time and knowledge kills me every time.

Posted by Peter Judge on 23-Apr-2009 14:22

Yep. You can store a set of idenfitiers for the record, which are either ROWIDs or unique identifiers for the record (the buffer's :Keys values; which are translated into ROWIDs).


I hadn't thought of that way. I will have to look int that one.

What do you mean by capturing (and resetting) the prepare-string?

As a quick fix I defined a private character variable like chDefaultQueryString and once I fill the dataset I did the following:

chDefaultQueryString = dataset dsCustomers:top-nav-query:prepare-string.

Then once I had called SaveChanges() method I called another method to reopen the query with the original query string stored in chDefaultQuery. To be quite honest with you it's actually a pretty feeble way to do it but lack of time and knowledge kills me every time.

You can reopen a query without specifying the query string.  What's really nice about the ABL is that for queries for ProDataSet child relations, the parent keys are resolved for you, so you don't need to figure it out.

        if valid-handle(phQuery) then
        do:
            if phQuery:is-open then
                phQuery:query-close().
            phQuery:query-open().
        end.

I hadn't thought of that way. I will have to look int that one.

The following assumes you have a current row in a query:

    method protected char extent  GetCurrentRowKey(phQuery as handle):
        define variable cKeyWhere as character extent no-undo.
        define variable iLoop as integer no-undo.
        define variable iFields as integer no-undo.
        define variable cKeys as character no-undo.
        define variable hBuffer as handle no-undo.
       
        if valid-handle(phQuery) and phQuery:is-open then
        do:
            extent(cKeyWhere) = phQuery:num-buffers.
                       
            do iLoop = 1 to phQuery:num-buffers:
                hBuffer = phQuery:get-buffer-handle(iLoop).
                if not hBuffer:available then
                do:
                    cKeyWhere[iLoop] = ?.
                    leave.
                end.
               
                cKeys = hBuffer:keys.
                if cKeys eq 'Rowid' then
                    cKeyWhere[iLoop] = string(hBuffer:rowid).
                else
                do iFields = 1 to num-entries(cKeys):
                    cKeyWhere[iLoop] = cKeyWhere[iLoop]
                                     + (if iFields eq 1 then 'where ' else ' and ')
                                     + hBuffer:name + '.' + entry(iFields, cKeys)
                                     + ' = ' + quoter(hBuffer:buffer-field(entry(iFields, cKeys)):buffer-value)
                                     .
                end.    /* key fields */
            end.
        end.
        else
            assign extent(cKeyWhere) = 1
                   cKeyWhere = ?.
       
        return cKeyWhere.
    end method.

hth

-- peter

Posted by Peter Judge on 23-Apr-2009 14:23


Both seem to work just fine for a single temp-table dataset but both result in the strange grid rendering.

It might be worth reporting this to Tech Support.

-- peter

Posted by jmgawlik on 23-Apr-2009 16:28

That GetCurrentRowKey() method is pretty slick. I am assuming that you would have to use the current-query for sending the correct query when you have child tables, is that what you're doing? I am just trying to figure out how to best use this. I have some ideas but...well, I'm not sure.

I am pretty far behind in knowledge here. Sadly, I'm still the only developer here that has even used the GUI for .Net or the OOP (hopefully, this will be changing soon) so I don't have anyone else to ask. Hope I'm not being a pest.

I would be happy to peek at any examples you might have too. Ok...now I'm a pest.

Posted by Peter Judge on 24-Apr-2009 08:37

hat GetCurrentRowKey() method is pretty slick. I am assuming that you would have to use the current-query for sending the correct query when you have child tables, is that what you're doing? I am just trying to figure out how to best use this. I have some ideas but...well, I'm not surefor

Yeah, you need to use current-query for cases where you have ProDataSet providing data to a ProBindingSource which provides data to an UltraGrid (or a grid that supports child bands; not all do). Although thre are about a bajillion* queries associated with a PDS which you can potentially use, and also remember that you may not always be working with a grid (or even a binding source, depending on how over-engineered future-proof generic well-designed :-) you want your code to be).

* give or take one or two

I am pretty far behind in knowledge here. Sadly, I'm still the only developer here that has even used the GUI for .Net or the OOP (hopefully, this will be changing soon) so I don't have anyone else to ask. Hope I'm not being a pest.

That's what these forums (and community) are here for.

I would be happy to peek at any examples you might have too.  Ok...now I'm a pest.

The code below is an example of using the array created for repositioning. The reposition-to-rowid method and statement can take an array. Note that while the find-unique should find the record, you need to do a reposition to notify the binding source that the cursor position has changed.

method protected void RepositionQuery (phQuery as handle, pcRecord as char extent):
        define variable rCurrentRow as rowid extent no-undo.
        define variable iExtent as integer no-undo.
        define variable hBuffer as handle no-undo.

        extent(rCurrentRow) = extent(pcRecord).
        rCurrentRow = ?.
       
        do iExtent = 1 to extent(pcRecord):
            if pcRecord[iExtent] eq '' or
               pcRecord[iExtent] eq ? then
                next.
           
            if pcRecord[iExtent] begins 'where' then
            do:
                hBuffer = phQuery:get-buffer-handle(iExtent).
                hBuffer:find-unique(pcRecord[iExtent]) no-error.
                if hBuffer:available then
                    rCurrentRow[iExtent] = hBuffer:rowid.
            end.
            else
                rCurrentRow[iExtent] = to-rowid(pcrecord[iExtent]).
        end.    /* loop through where */
       
        RepositionQuery(phQuery, rCurrentRow).
    end method.

-- peter

Posted by jmgawlik on 24-Apr-2009 10:48

Wow, I like that RepositionQuery.

That's what these forums (and community) are here for.

I suppose that is very true. I can't tell you how much I have learned from this particular forum. Lately, it's been mostly you and Mike.

Just from looking at this method though, wouldn't you alter the recursive call at the bottom due to the signature?

From: RepositionQuery(phQuery, rCurrentRow).

To:     RepositionQuery(phQuery, string(rCurrentRow)).

Finally, are you using these method when Add, Edit, and Save? I could see them both as being useful in service adapter super class since they're 'well-designed' enough to do so. I guess I have to sort of re-think how I've been doing this up to now. Always a better way.

Posted by Peter Judge on 24-Apr-2009 11:08

Just from looking at this method though, wouldn't you alter the recursive call at the bottom due to the signature?

From: RepositionQuery(phQuery, rCurrentRow).

To:     RepositionQuery(phQuery, string(rCurrentRow)).

For completeness, here's the actual RepositionQuery method. Incidentally, I'm finding one of the really cool things about OO programming in general is overloading. While it can make your code extremely verbose in the class that implements the methods, it makes the callers far more readable (since you can get rid of a bunch of optional parameters, and pass in parameters without having to do any conversions).

method protected void RepositionQuery (phQuery as handle, prRecord as rowid extent):
        if prRecord[1] ne ? then
        do:
            if extent(prRecord) eq 1 then
                phQuery:reposition-to-rowid(prRecord[1]).
            else
                phQuery:reposition-to-rowid(prRecord).
           
            /* deal with non-scrolling queries */
            if not phQuery:get-buffer-handle(1):available then
                phQuery:get-next().
        end.
end method.

Finally, are you using these method when Add, Edit, and Save? I could see them both as being useful in service adapter super class since they're 'well-designed' enough to do so. I guess I have to sort of re-think how I've been doing this up to now. Always a better way.

I think we started off by talking about reopening queries and repositioning in that context, so this would be used whenever a query's reopened. That could be on Add, Save (locally, to ProDataSet), Commit (to database) , etc. Also, as Mike pointed out, you may not always want to reopen queries (for performance or other reasons).

That's what these forums (and community) are here for.

I suppose that is very true. I can't tell you how much I have learned from this particular forum. Lately, it's been mostly you and Mike.

The OOABL forum is also really useful. Attendance and participation vary here; sometimes it's really busy*. There are also other some resources like the PEG and OE Hive that are great and definitely worth checking out (if you haven't already).

This discussion has probably moved beyond the scope of GUI for .NET, possibly into OOABL territory, but almost certainly into application design/architecture .

-- peter

* Although it is evening in Europe by now and I'm sure that the participants who live there have better things to do on a Friday night

Posted by Admin on 24-Apr-2009 11:29

-- peter

* Although it is evening in Europe by now and I'm sure that the participants who live there have better things to do on a Friday night

Late afternoon actually. But although the sun is shining on my balcony, I still consider that it's during business hours :-(

Posted by Admin on 24-Apr-2009 11:32

jmgawlik schrieb:

I am pretty far behind in knowledge here. Sadly, I'm still the only developer here that has even used the GUI for .Net or the OOP (hopefully, this will be changing soon) so I don't have anyone else to ask. Hope I'm not being a pest.

I'd say by using ProDatasets, OO and GUI for .NET you are far ahead most OpenEdge (or should I say Progress) programmers...

Posted by jmgawlik on 24-Apr-2009 12:14

I will definitely look into all of this and try to figure that best approach to take in each instance. We are getting a great opportunity redesign a huge piece of our system here and we're taking hard look at the GUI for .Net as well as OOP and distributed environment (none of which we really do right now).

I geuss I got off on a bit of a tangent with regard to the queries but I still appreciate all of your time. I will definitely look at the OOP forum too.

Thanks again to both of you. Have a great weekend!

Posted by jmgawlik on 24-Apr-2009 12:16

Well, Mike, it could be worse, it's only 10 AM here so I still have most of my day still.

This thread is closed