My previous posting I talked about using web services, specifically I used the example to retrieve the Free Cap Units for an item and yes, I chose that for a good reason.
We use the Free Cap Units to represent the number of cartons we can fit in to a 20′ container. We have a modification which takes the quantity a user entered in to OIS101 and divides it against the Free Cap Units – this gives our staff an idea of how much of a 20′ container product is consuming. We have a percentage per line and a total percentage
My ultimate aim was to use web services to extract the free cap units per item (using a SQL Web Service), create my own calculations, add a total TextBox and add an extra column to the ListView which will contain the percentage. Sounds pretty straight forward? Well five days of fairly solid testing and I still haven’t come to a solution that I liked – which may well be forest for the trees.
I have a solution, but it is a very distinct hack (firmly under the “How Far is Too Far” category) and I’m less than happy with it – I suspect that this is something that would need to be developed with the SDK.
The basic premise is that we need to add a column, and then remove each item in the list, add the new piece of data and then add the item back in to the listview.
My investigation in to this is what todays post is about…
If you’ve played around with WPF and ListViews you’d probably be thinking that Lawson used bindings – but not so – I’m guessing that Lawson have done this so they can better handle situations where an order has a large number of lines (if you remember from a previous post I mentioned that Smart Office grabs rows in groups of around 30 records).
Eitherway, we can prove that they aren’t using bindings through Reflection.
// Retrieve the ListControl var lvListControl = controller.RenderEngine.ListControl; // Retrieve the ListView from the ListControl var lvListView = lvListControl.ListView; // Retrieve the GridView, the GridView contains the columns var gvGrid : GridView = lvListControl.GridView; if(null != gvGrid) { if(gvGrid.Columns.Count >= 1) { // Here we retrieve the binding for the first column in the Grid var bndBinding : Binding = gvGrid.Columns[0].DisplayMemberBinding; if(null != bndBinding) { // there is a binding, so show the path MessageBox.Show(bndBinding.Path); } else MessageBox.Show("No Binding"); // there was no binding } } else MessageBox.Show("No Grid");
I dropped this in to the Init() function for OIS101 which yielded the message “No Binding”
So, the next thing that we need to do is look at the more traditional approach of how ListViews work. So we need to take a look at the type of object the items in the list are. To do so I used the following code.
if(lvListView.Items.Count >= 1) { MessageBox.Show("lvListView.Items Type = " + lvListView.Items.GetType() + Environment.NewLine + "lvListView.Items[0] Type = " + lvListView.Items[0].GetType()); var lviItem = lvListView.ItemContainerGenerator.ContainerFromItem(lvListView.Items[0]); if(null != lviItem) { MessageBox.Show("Content = " + lviItem.Content); } else MessageBox.Show("No ListView Item"); } else MessageBox.Show("No items in listview");
This shows us that we are using Mango.UI.Services.Lists.ListRow objects, an class that is derived from Mango.UI.Services.Lists.IListRow. If we then take a look at the ListRow object then we can see some nifty things but frustratingly after some experimentation nothing that obviously allows us to change anything.
Interesting tidbit, I spent a while trying to understand how the framework knew how to extract the data
Apparently there is a “DefaultMemberAttribute”, how nifty 🙂
But as per usual, getting side-tracked. Adding a column to a ListView is really easy and it looks like Lawson have provided us with lots of clues in the functions available in the ListControl object. We have AddListColumn()
And some properties that talk about Columns
Looks pretty straight forward?
private function addColumn(alcListControl : ListControl, astrColumnName : String) { // create a MForms.ListColumn var lcListColumn : MForms.ListColumn = new MForms.ListColumn(); if(null != lcListColumn) { // Add the ListColumn to the ListControl alcListControl.AddListColumn(lcListColumn); // here we add our column name to the Names, Columns and Headers alcListControl.ColumnNames.Add("FreeCap"); alcListControl.Columns.Add("FreeCap"); alcListControl.Headers.Add("FreeCap"); // and here we set the text that will appear in the header alcListControl.GridView.Columns[alcListControl.GridView.Columns.Count-1].Header = astrColumnName; } }
Well, yup, it is very easy to add a column, but getting something meaningful displayed in that column is a challenge.
When we use this method and add data we appear to have a link to column 0, a change to column 0 effects the new column that we have added and vice versa.
Other things of note, we have to remove the ListRow and then re-add it to update.
This is what the end result looks like
(Just to confuse the issue, the Free Units is a column added by our MOD – but it was the last column added so it has been overwritten by the word POTATO).
Anyways, getting late, so here is the code. At the moment the addDataToColumnBasic() will overwrite the last column with our data. My plan of attack will probably be to choose a column which doesn’t have much in the way of useful data and overwrite it until I can figure out a clean way to handle this – or maybe put in an enhancement request 🙂
I have left some of the experimentation code in place – addDataToColumn2() is the original function that I used to explore what was happening – largely so you can get an idea that this has been a case of try, try, try again. addDataToColumn() has been left there – my attempt to mimic what Smart Office does based on what functions and properties are exposed though I am clearly missing something. Others may find the code useful and be kind enough to provide a solution 🙂
import System; import System.Windows; import System.Windows.Controls; import System.Windows.Data; import MForms; import Mango.UI.Core; import Mango.UI.Services.Lists; package MForms.JScript { class DisplayBindings { var gctrlControl; public function Init(element: Object, args: Object, controller : Object, debug : Object) { var content : Object = controller.RenderEngine.Content; gctrlControl = controller; // retrieve the ListControl var lvListControl = controller.RenderEngine.ListControl; // extract the ListView from the ListControl var lvListView = lvListControl.ListView; // extract the GridView from the ListControl // the GridView actually contains the columns var gvGrid : GridView = lvListControl.GridView; if(null != gvGrid) { if(gvGrid.Columns.Count >= 1) { // here we are looking to see if any of the columns are bound // to specific object properties var bndBinding : Binding = gvGrid.Columns[0].DisplayMemberBinding; if(null != bndBinding) { // this should tell us the path if we have a binding MessageBox.Show(bndBinding.Path); } else MessageBox.Show("No Binding"); // if not binding then we will get a message "No Binding" } } else MessageBox.Show("No Grid"); // on the assumption that we didn't have a binding, we need to discover what type // of object is in the rows of the ListView if(lvListView.Items.Count >= 1) { MessageBox.Show("lvListView.Items Type = " + lvListView.Items.GetType() + Environment.NewLine + "lvListView.Items[0] Type = " + lvListView.Items[0].GetType()); var lviItem = lvListView.ItemContainerGenerator.ContainerFromItem(lvListView.Items[0]); if(null != lviItem) { MessageBox.Show("Content = " + lviItem.Content); } else MessageBox.Show("No ListView Item"); } else MessageBox.Show("No items in listview"); // to experiemtn, uncomment the next two lines and comment out the 3rd //addColumn(lvListControl, "Free Cap"); // addDataToColumn(lvListControl); addDataToColumnBasic(lvListControl); // finally resize the colums to fit the content lvListControl.ResizeColumns(); } // here we add the data to an existing column (the best solution I have so far) private function addDataToColumnBasic(alcListControl : ListControl) { try { // loop through each item in the listview for(var i :int = 0; i < alcListControl.ListView.Items.Count; i++) { // we are always going to work with item 0, it will eventually // be removed from the list and then added back to the end var lrCurrentListRow : ListRow = alcListControl.ListView.Items[0]; if(null != lrCurrentListRow) { // remove the ListRow alcListControl.ListView.Items.Remove(lrCurrentListRow); // Change the last column contents lrCurrentListRow.Items[lrCurrentListRow.Items.length-1] = "POTATO " + i; // now we add the ListRow back to the list (effectively updating) alcListControl.ListView.Items.Add(lrCurrentListRow); } } } catch(e) { MessageBox.Show("Exception: " + e.message); } } // this one will assume that we are adding a new column (so we need to // uncomment addColumn() in the Init() function) and we create // a new ListRow and use as many of the functions that we can see // in the Object Viewer to be as true as possible to how Smart Office // works private function addDataToColumn(alcListControl : ListControl) { try { // loop through the items in the list for(var i :int = 0; i < alcListControl.ListView.Items.Count; i++) { // always retrieve item 0, we remove items and add them to the end of the // list so each loop will bring us a new item to play with var lrCurrentListRow : ListRow = alcListControl.ListView.Items[0]; var newListRow : ListRow; if(null != lrCurrentListRow) { // remove the item from the ListView alcListControl.ListView.Items.Remove(lrCurrentListRow); // create a new ListRow mimicing the one that we just removed // the important thing is that we are adding 1 to the column count newListRow = new ListRow(lrCurrentListRow.Name, lrCurrentListRow.Items.length + 1, lrCurrentListRow.IsSelected, lrCurrentListRow.IsProtected, false); if(null != newListRow) { // now we need to loop through the items and copy them // from the original ListRow to the new ListRow for(var j : int = 0; j < lrCurrentListRow.Items.length; j++) { newListRow.Add(lrCurrentListRow.Items[j]); } // add our new column newListRow.Add("POTATO"); // add our newly created ListRow - it won't have the // formatting of the original one, but this is just // a test alcListControl.ListView.Items.Add(newListRow); } } } } catch(e) { MessageBox.Show("Exception: " + e.message); } } // add a new column to the ListControl private function addColumn(alcListControl : ListControl, astrColumnName : String) { var lcListColumn : MForms.ListColumn = new MForms.ListColumn(); if(null != lcListColumn) { // do the actual add of the column alcListControl.AddListColumn(lcListColumn); // not sure if they were needed , but filled // them in just incase alcListControl.ColumnNames.Add("FreeCap"); alcListControl.Columns.Add("FreeCap"); alcListControl.Headers.Add("FreeCap"); // set the display test of the column alcListControl.GridView.Columns[alcListControl.GridView.Columns.Count-1].Header = astrColumnName; } } // this function isn't commented, it is a whole heap of code // that I wrote to explore and test various options, it is // included here purely to illustrate some of the random // ideas thrown at this to try and solve the problem, again // this stuff assumes that we have called the addColumn() function private function addDataToColumn2(alcListControl : ListControl) { var jCount : int = 0; var kCount : int = 0; try { for(var i :int = 0; i < alcListControl.ListView.Items.Count; i++) { var itmItem = alcListControl.ListView.Items[0]; if(null != itmItem) { alcListControl.ListView.Items.Remove(itmItem); jCount = itmItem.Items.Count; if(kCount == 0) { kCount = itmItem.ColumnCount; } itmItem.Index = 15; //itmItem.Items[alcListControl.ListView.Items.Count-1] = "POT" + i; itmItem.GridViewColumns = alcListControl.GridView.Columns; MessageBox.Show("Condition Count = " + itmItem.Conditions.Count + " Type = " + itmItem.Conditions.GetType()); var cnd : Mango.UI.Services.Lists.IListRow.CellCondition[] = new Mango.UI.Services.Lists.IListRow.CellCondition[16]; itmItem.Conditions = cnd; //MessageBox.Show(alcListControl.GridView.Columns.Count); //var tmpListCell : ListCell = new ListCell(); //MessageBox.Show(tmpListCell.GetType()); //var arNewArray : System.Object[] = new System.Object[16]; //for(var j : int = 0; j < itmItem.Items.Count; j++) //{ // arNewArray[j] = itmItem.Items[j]; //itmItem.Items[14] = "POT" + i; //itmItem.Add("POT" + i); //MessageBox.Show(itmItem.Items.GetType()); //} //MessageBox.Show("arNewArray: " + arNewArray.GetType() + ", itmItem: " + itmItem.Items.GetType()); //arNewArray[15] = "POT" + i; //itmItem.Items = arNewArray; //MessageBox.Show(itmItem.Items + Environment.NewLine + itmItem.Items.GetType() + Environment.NewLine + itmItem.Items[0].GetType() + " = " + itmItem.Items[0] + Environment.NewLine + itmItem.Items[0][0].GetType() + " = " + itmItem.Items[0][0] + " *** " + + itmItem.Items[2][2]); var strTemp : String; for(var j : int = 0; j < itmItem.Items.length; j++) { strTemp = strTemp + j + ": " + itmItem.Items + Environment.NewLine; strTemp = strTemp + j + ": " + itmItem.Items[j] + " = " + itmItem.Items[j].GetType() + Environment.NewLine; var itmSubItem = itmItem.Items[j]; if(itmSubItem.GetType().IsArray == true) { for(var k : int = 0; k < itmSubItem.length; k++) { strTemp = strTemp + " - " + k + ": " + itmSubItem[k] + " = " + itmSubItem[k].GetType() + Environment.NewLine; } } else MessageBox.Show("Not an array"); } MessageBox.Show(strTemp); var strRawData = itmItem.Items + "," + "POT" + i; //MessageBox.Show(strRawData); // itmItem.Items = strRawData.Split(","); // MessageBox.Show(itmItem.Items); // itmItem.Items[0][15] = "POT" + i; itmItem.Items = strRawData.Split(","); //itmItem.Items[0] = strRawData.Split(","); itmItem.Add("POT" + i); //itmItem.Items[15][15] = "POT" + i; alcListControl.ListView.Items.Add(itmItem); //gctrlControl.UpdateListRow(alcListControl.ListView.Items.Count-1); } } MessageBox.Show("addDataToColumn count = " + jCount + ", " + kCount); // alcListControl.ListView. } catch(e) { MessageBox.Show("Exception addDataToColumn: " + e.message + " : " + jCount); } } } }
Happy coding!
He you are really a crazy guy 🙂
Haha, someone has to be 😉
Hey Scott,
From my recollection of the jscript documentation a couple of years ago there was some documentation on adding a column to a list view. Is this the same approach or a new one?
Cheers, Al.
Hi Al,
funny you should say that. I thought that there was an example aswell. When I looked through the developer guide trying to find it I realised that the script in question adds an image beside the list.
If I’ve overlooked something obvious, then I would be very interested to know…this isn’t a fantastic method.
Cheers,
Scott
Hi !
you have really nice ideas! I have made several scripts, and your examples are really helpfull. Yesterday I started to dig how to add column to the listview. Also spent a day on that, and could not understand how the class works. Then found your article exactly on this… Looks promising. However, there are still some things, I could not find how to correct – when script put additional column to ListView, and your press “Refresh” in a program, it blaims – failed to render panel. It loks, that program tries to save the new column as well. Did you face this ?
Cheers,
aivaras
Glad you have been finding the site useful.
When there is a refresh you effectively have to rebuild everything again.
There is a really good detailed explanation from Thibaud here:
http://thibaudatwork.wordpress.com/2011/09/22/how-to-add-a-column-to-a-list/
Be sure to read through the comments. 🙂