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!
Pingback: Inconsistent Behaviour & Odds and Sods – Trouble In Paradise | Potato IT