Programmatically Scrolling Through Lists in M3 Part III

Well, this is the third instalment in this little tale – proof that the time spent blogging about some of this stuff can yield good feedback that helps me solve my problems. Tonight is a night of credit where credit is deserved 🙂

If you want a recap:
https://potatoit.wordpress.com/2010/11/18/programatically-scrolling-through-lists-and-m3/

And
https://potatoit.wordpress.com/2010/12/02/lso-scrolling-through-lists-hasmorerows/

Of particular interest is the comments section of the first link where Karin provided some very useful information that solved several of my issues and prompted me to do some significant changes to my code.

So, if you didn’t trawl through the links above, then here is the issue “user wants to easily scroll through a long list to a specific item on PMS050/B1”

In previous incarnations, the code I was testing was a hack that would simulate the depression of the PageDown key on the currently active Window on the currently active Application. Of-course, if the focus changes then we have all sorts of problems.

So, below is a snapshot of the normal PMS050/B panel, not a lot in the way of options.

I go out and add a TextBox and two Buttons so it looks like this:

The user keys in their ITNO in to the TextBox and then they hit Go to Item, this will scroll through the list until we get an ITNO match. We will then scroll that row in to view and highlight it.

If the user gets impatient, then we have a Stop button to break out to the search – the stop button is very simple, it sets a class Boolean variable, gbRunning to false. Code later will check this variable and only continue if gbRunning is to true.

        public function OnStopClick(sender: Object, e: RoutedEventArgs)
        {
            // later in the code we will check this variable to see
            // if the user wants us to stop prematurely
            gbRunning = false;
        }

Instead of the “Go to Item” button containing all of the looping code, it will just check if the ITNO is in the currently loaded records, if not it will do a single PageDown. After that, the RequestCompleted event takes over and effectively does the looping for us.

        public function OnClick(sender: Object, e: RoutedEventArgs)
        {
            // set our running state to True
            gbRunning = true;
            
            // set the Found to false
            gbFound = false;
            
            // set the position that we are starting from back to 0
            giLVItemCount = 0;
            
            // do a search for the item before polling the server
            // for more data.
            // In this instance we are comparing the value of the
            // TextBox we added against column 0 in the ListView
            searchForSpecificITNO(gtbTextBox.Text, 0);
            
            // if gbFound is still false then we want to request
            // more data from the server
            if(false == gbFound)
            {
                pageDown();
            }
        }


The RequestCompleted event occurs after records have been retrieved from the server and added to the ListView, this provides us with the perfect event to do our checking. We will then call another PageDown() if we can’t find out ITNO.


        public function OnRequestCompleted(sender: Object, e: RequestEventArgs)
        {
            // verify that these are the events we are looking for!
            if((e.CommandType == MNEProtocol.CommandTypePage) && (e.CommandValue == MNEProtocol.CommandValuePageDown))
            {
                // if we are still running, fantastic!
                if(true == gbRunning)
                {
                    // have we already matched the item we are looking for?
                    if(false == gbFound)
                    {
                        // now do the search of the data
                        searchForSpecificITNO(gtbTextBox.Text, 0);
                    }
                    // did we find the Item?
                    if(gbFound == true)
                    {
                        gbRunning = false;
                    }
                    else
                    {
                        // issue a pageDown() to retrieve some more records from the server
                        pageDown();
                    }
                }
            }
            else
            {
                gdebug.WriteLine("OnRequestCompleted() Other command, disconnecting event");
                gcontroller.remove_RequestCompleted(OnRequestCompleted);
            }
        }

 

Our PageDown call is now from mforms::IInstanceController::PageDown()

        // issue the actual PageDown command, but only if we have more
        // rows.  If we don't then we will display a MessageBox informing
        // the user we couldn't find the item they requested.
        private function pageDown()
        {
            try
            {
                var listControl = gcontroller.RenderEngine.ListControl;

                
                // there is no point in getting more rows if we have all of the rows
                if(true == listControl.HasMoreRows)
                {
                    // 'press' the Page Down key
                    gcontroller.PageDown(listControl);
                }
                else
                {
                    gbRunning = false;
                    MessageBox.Show("Sorry, but no item of '" + gtbTextBox.Text + "' was found");
                }
                
            }
            catch(ex)
            {
                //MessageBox.Show(ex.Message);
                gdebug.WriteLine(ex.Message + " " + ex.StackTrace);
            }
        }

Anyways, here is the complete code below, gratuitous use of comments. Still needs some polish, but is functional. For my user I will probably add code in there so they can search on the MO Number exclusively, Item Number exclusively or a combination of the two.

// Name: PMS050_B_SendKey
// Version: 3.03 2010-12-03
// Description: adds a TextBox and Button and will scroll
//    through the listview until it finds a matching product
//
// Notes: 
// We use mforms.IInstanceController.PageDown,
//  this will effectively force the client to retrieve more rows from the server
//    We use an undocumented feature in the ListControl called
//     HasMoreRows to determine when we should stop scrolling
//
// Revision History:
//    1.00    2010-12-02    * Completed
//    1.01    2010-12-03    * removed some of the debugging
//                            * we reset giLVItemCount to zero whenever the 'Go To Item' button is clicked
// 3.03    2011-01-02    * we now subscribe to the Requested and RequestCompleted events,
//                              which allows us to determine when the records have been returned from the server
//                              before attempting to search again.  Credit to Karin Portillo
//                            * We now use the controller.PageDown() to issue the pagedown - a little safer
//                              then the previous hack - ok, ALOT safer 😉  Credit to Karin Portillo
//                            * added functionality to break the search

import System;
import System.Windows;
import System.Windows.Controls;
import MForms;

package MForms.JScript
{
    class PMS050_B_SendKey
    {
        var gdebug, gcontroller;
        
        // this is the button that initiates the search
        var gbtnGotoItem : Button;
        // the TextBox that the user keys the Item Number in to
        var gtbTextBox : TextBox;
        
        // are we currently looking for a match?
        var gbRunning = false;
        
        // have we found a match?
        var gbFound = false;
        
        // this effectively becomes our start position to begin the search from
        // so after we have done a pageDown() we don't search from the beginning,
        // but where we last left off
        var giLVItemCount = 0;

        // the stop button
        var gbtnStop : Button;
        
        public function Init(element: Object, args: Object, controller : Object, debug : Object)
        {
            var content : Object = controller.RenderEngine.Content;
            
            // save these aganist the class so we
            // can use them in other functions
            gcontroller = controller;
            gdebug = debug;


            // create the TextBox that contains the
            // ITNO
            gtbTextBox = new TextBox();
            gtbTextBox.Width = 50;
            
            // create the Button that will
            // initiate the search
            gbtnGotoItem = new Button();
            gbtnGotoItem.Content = "Go to Item";
            Grid.SetColumnSpan(gbtnGotoItem, 10);
            Grid.SetColumn(gbtnGotoItem, 11);
            Grid.SetRow(gbtnGotoItem, 0);

            gbtnStop = new Button();
            gbtnStop.Content = "Stop";

            Grid.SetColumnSpan(gbtnStop, 10);
            Grid.SetColumn(gbtnStop, 19);
            Grid.SetRow(gbtnStop, 0);

            Grid.SetColumnSpan(gtbTextBox,12);
            Grid.SetColumn(gtbTextBox, 0);
            Grid.SetRow(gtbTextBox, 0);
            
            // Add the controls
            controller.RenderEngine.Content.Children.Add(gtbTextBox);                                
            controller.RenderEngine.Content.Children.Add(gbtnGotoItem);
            controller.RenderEngine.Content.Children.Add(gbtnStop);

            // add the events for the controls
            gbtnGotoItem.add_Click(OnClick);
            gbtnGotoItem.add_Unloaded(OnUnloaded);
            gbtnStop.add_Click(OnStopClick);
            gbtnStop.add_Unloaded(OnStopUnloaded);
            
            // we subscribe to these events so we can determine when
            // a PageDown() has been issued and when the server has
            // returned the data.
            // It is within the RequestCompleted() that we will effectively
            // loop through looking for our data.
            controller.add_Requested(OnRequested);
            controller.add_RequestCompleted(OnRequestCompleted);


        }

        // issue the actual PageDown command, but only if we have more
        // rows.  If we don't then we will display a MessageBox informing
        // the user we couldn't find the item they requested.
        private function pageDown()
        {
            try
            {
                var listControl = gcontroller.RenderEngine.ListControl;
                // ensure that the ListControl has focus
                //listControl.Focus();
                
                // there is no point in getting more rows if we have all of the rows
                if(true == listControl.HasMoreRows)
                {
                    // 'press' the Page Down key
                    gcontroller.PageDown(listControl);
                }
                else
                {
                    gbRunning = false;
                    MessageBox.Show("Sorry, but no item of '" + gtbTextBox.Text + "' was found");
                }
                
            }
            catch(ex)
            {
                //MessageBox.Show(ex.Message);
                gdebug.WriteLine(ex.Message + " " + ex.StackTrace);
            }
        }

        // searchForSpecificITNO
        //  scroll down through the alvListControl until we match our ITNO
        // astrITNO = the Item Number (we actually do a IndexOf when we compare)
        // aiColumnNumber = the column that we should look for the astrITNO in
        private function searchForSpecificITNO(astrITNO, aiColumnNumber)
        {

            try
            {
                if(!String.IsNullOrEmpty(astrITNO))
                {
                    var lvListControl = gcontroller.RenderEngine.ListControl;
                    var lvListView = lvListControl.ListView;

                    // have we found the ITNO?
                    gbFound = false;
                    
                    // loop through the current items in the list, we want to make sure that we
                    // start at the last value so we don't search the same items multiple times
                    for(var iCount : int = giLVItemCount; iCount < lvListView.Items.Count; iCount++)
                    {
                        // retrieve the current record
                        var itmCurrentItem = lvListView.Items[iCount];
                        if(null != itmCurrentItem)
                        {
                            // ensure that there the column we are searching for isn't
                            // null on the current record
                            if(!String.IsNullOrEmpty(itmCurrentItem[aiColumnNumber]))
                            {
                                // convert the current item to a string
                                var strCurrentString = itmCurrentItem[aiColumnNumber].ToString();
                                
                                // we only care about a partial match
                                if(-1 != strCurrentString.IndexOf(astrITNO))
                                {
                                    // because we are nice, we'll scroll the first matching
                                    // item in to view
                                    lvListView.ScrollIntoView(itmCurrentItem);
                                    lvListView.SelectedItem = itmCurrentItem;
                                    gbFound = true;
                                    break;
                                }
                            }
                        }
                    }
                    if(false == gbFound)
                    {
                        // we didn't find the item, we want to make note
                        // of the previous item count (I was using this
                        // originally so I knew when to stop, however the loop
                        // would complete before M3 had returned the new records :-O
                        giLVItemCount = lvListView.Items.Count;
                    }
                }
            }
            catch(ex)
            {
                gdebug.WriteLine(ex.Message);
            }
            return(gbFound);
        }
        
        public function OnStopClick(sender: Object, e: RoutedEventArgs)
        {
            // later in the code we will check this variable to see
            // if the user wants us to stop prematurely
            gbRunning = false;
        }

        public function OnClick(sender: Object, e: RoutedEventArgs)
        {
            // set our running state to True
            gbRunning = true;
            
            // set the Found to false
            gbFound = false;
            
            // set the position that we are starting from back to 0
            giLVItemCount = 0;
            
            // do a search for the item before polling the server
            // for more data.
            // In this instance we are comparing the value of the
            // TextBox we added against column 0 in the ListView
            searchForSpecificITNO(gtbTextBox.Text, 0);
            
            // if gbFound is still false then we want to request
            // more data from the server
            if(false == gbFound)
            {
                pageDown();
            }
        }

        // some unloaded events - us cleaning up
        public function OnStopUnloaded(sender: Object, e: RoutedEventArgs)
        {
            gbRunning = false;
            gbtnStop.remove_Click(OnStopClick);
            gbtnStop.remove_Unloaded(OnStopUnloaded);
        }

        public function OnUnloaded(sender: Object, e: RoutedEventArgs)
        {
            gbtnGotoItem.remove_Click(OnClick);
            gbtnGotoItem.remove_Unloaded(OnUnloaded);
        }

        public function OnRequested(sender: Object, e: RequestEventArgs)
        {
            if(e.CommandType == MNEProtocol.CommandTypePage)
            {
                //gdebug.WriteLine("OnRequested() Page command");
                // This is before the request for more lines is done
            }
            else
            {
                gdebug.WriteLine("OnRequested() Other command, disconnecting event");
                gcontroller.remove_Requested(OnRequested);
            }
        }

        public function OnRequestCompleted(sender: Object, e: RequestEventArgs)
        {
            // verify that these are the events we are looking for!
            if((e.CommandType == MNEProtocol.CommandTypePage) && (e.CommandValue == MNEProtocol.CommandValuePageDown))
            {
                // if we are still running, fantastic!
                if(true == gbRunning)
                {
                    // have we already matched the item we are looking for?
                    if(false == gbFound)
                    {
                        // now do the search of the data
                        searchForSpecificITNO(gtbTextBox.Text, 0);
                    }
                    // did we find the Item?
                    if(gbFound == true)
                    {
                        gbRunning = false;
                    }
                    else
                    {
                        // issue a pageDown() to retrieve some more records from the server
                        pageDown();
                    }
                }
            }
            else
            {
                gdebug.WriteLine("OnRequestCompleted() Other command, disconnecting event");
                gcontroller.remove_RequestCompleted(OnRequestCompleted);
            }
        }
        
    }
}

Enjoy!

This entry was posted in Development, How Far is Too Far?, M3 / MoveX. Bookmark the permalink.

1 Response to Programmatically Scrolling Through Lists in M3 Part III

  1. Pingback: Inconsistent Behaviour & Odds and Sods – Trouble In Paradise | Potato IT

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s