Monotouch Dialog - Reload data list after modifying row detail not updating list when returning - xamarin.ios

When an item is clicked, a details view controller is opened that makes some webservice calls to save back the data if the user clicks save (this works fine). The issue I have is after saving the data when the user clicks back to go back to the list, the list item (ie if the name was updated) is not updated. So, perhaps the user changes the name and so the list prior should be updated, I'd like to update the value without having to make another webservice call to repopulate.
I am calling this.ReloadData() when the view appears, but this never seems to update the list names. I've checked workouts which is a list of objects, and it has been modified by the detail controller, however the reload data doesn't seem to do anything.
How do I refresh the data in the case that another controller has modified the list of objects.
public override void ViewWillAppear (bool animated)
{
InvokeOnMainThread (delegate {
this.ReloadData();
}); // call reload on main thread
base.ViewWillAppear (animated);
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
workouts = manager.GetWorkouts(Helpers.User.Auth);
var sections = new Section();
var root = new RootElement("Workouts"){sections};
foreach (var wo in workouts) {
var tempwo = wo;
var wodetail = new RootElement(wo.Name, delegate {
var W = new WorkoutViewModel(tempwo);
var p = new WorkoutDetailController(W, tempwo);
return p;
});
sections.Add (wodetail);
}
Root = root;
}

It seems that when you assign the value of an element from a custom object like so:
AccountDetailsSection = new Section ("Details"){
new StringElement ("Code:", _Account.Code),
new StringElement ("Name:", _Account.Name) };
You get the desired result but the element loses the pointer back to the property of the object. So if you change the value of the property later for example:
_Account.Code = "xxx";
Reloading the data doesn't do anything. You should manually update the value like so:
(AccountDetailsSection.Elements [0] as StringElement).Value = "xxx";
this.Root.Reload ( AccountDetailsSection, UITableViewRowAnimation.Fade);
Hope this Helps.

Related

Is there a way to make custom lookup on dialog field on Microsoft Dynamics 365?

I have problems with my dialog field. I have button that opens dialog tab with field. It was supposed to show on that field lookup exact records(i guess i need select there by one field value). Right now i have this code:
DialogField Journal = dialog.addField(extendedTypeStr(JournalId));
This dialog line adds a field with all values on that EDT. I have 3 journal types - NEW, UPDATE, DELETE. Right now on that field lookup it shows me all 3 journal types. I want to make custom lookup that shows exact type , example - if i click that button on journal that has type "NEW", then it should show only "NEW" type of journal types on lookup. I heard there is something like dialog.addLookup or something. Can someone help me?
You already added your dialog field (in the dialog() method). Now add the dialogRunPost() method that is executed after the form GUI is initialized. At that point you can fetch the underlying FormStringControl behind the dialog field. Subscribing to the FormStringControl.OnLookup event allows you to override the lookup.
I did not have some journal data available, so I created a similar example with customers. My example dialog (MyDialog) takes a source customer (customerCaller) and shows a dialog with a custom lookup that only shows customers with the same customer group.
My example is also a standalone, runnable class and is not called from a form. Comments have been added to indicate where this affects the code.
Full example
public class MyDialog extends Runbase
{
// fields
protected Args args;
protected CustTable customerCaller;
protected DialogField dfCustomerId;
// construct
public static MyDialog newArgs(Args _args)
{
MyDialog ret = new MyDialog();
ret.args = _args;
return ret;
}
// initialize
public boolean init()
{
boolean ret = super();
// validate and fetch caller
if (args.record() && args.record().TableId == tableNum(CustTable))
//if (args.caller() && args.caller().dataset() == tableNum(CustTable)) --> when called from form
{
customerCaller = args.record();
//customerCaller = args.caller().record();
}
else
{
throw error(Error::missingRecord('My Dialog'));
}
return ret;
}
// build dialog
public Object dialog()
{
Dialog ret = super();
// optional reference to visualize the input
ret.addText('Caller customer group = ' + customerCaller.CustGroup);
// add field
dfCustomerId = ret.addField(extendedTypeStr(CustAccount)); // default lookup = all CustTable.AccountNum values
return ret;
}
public void dialogPostRun(DialogRunbase dialog)
{
super(dialog);
// subscribe to lookup event
FormStringControl fscCustomerId = dfCustomerId.control();
fscCustomerId .OnLookup += eventhandler(this.customerId_OnLookup);
}
// custom lookup for customer id
protected void customerId_OnLookup(FormControl _sender, FormControlEventArgs _e)
{
// cancel default
FormControlCancelableSuperEventArgs eventArgs = _e;
eventArgs.CancelSuperCall();
// define lookup query (list all customers with same customer group as input customer)
Query query = new Query();
QueryBuildDataSource qbds = SysQuery::findOrCreateDataSource(query, tableNum(CustTable));
SysQuery::findOrCreateRange(qbds, fieldNum(CustTable, CustGroup)).value(SysQuery::value(customerCaller.CustGroup));
// do lookup
SysTableLookup lookup = SysTableLookup::newParameters(tableNum(CustTable), _sender);
lookup.parmQuery(query);
lookup.addLookupfield(fieldNum(CustTable, AccountNum), true);
lookup.addLookupfield(fieldNum(CustTable, CustGroup));
lookup.performFormLookup();
}
// run dialog
public static void main(Args _args)
{
// I am running this dialog directly (not from a form), generating some random input
CustTable customer;
select firstonly customer where customer.CustGroup != '';
_args.record(customer);
// end of random input
MyDialog md = MyDialog::newArgs(_args);
md.init();
if (md.prompt())
{
md.run();
}
}
}
Result

Interacting with Hidden Fields and Actions with Javascript

I am trying to figure out how to get values back and forth between an external javascript library and the Acumatica back end.
Currently I have 2 fields that are hidden (set to hidden on the PXUIField Attribute) and I am picking up their values successfully as follows:
function doSomething() {
var url = px_alls['txtUrl'].value;
var clientId = px_alls['txtClientID'].value;
}
However I am not having the same luck setting a hidden fields values and then posting the data to the back end in this way:
client.on('someEvent', (data) => {
px_alls['txtId'].value = data.id;
Save();
})
How can I accomplish this?
Thanks
-Kyle
Reading form fields from px_alls works fine but I was not able to automate saving.
I use action callback to send the values from JavaScript to an action defined in the Graph.
var ds = px_alls['ds'];
ds.executeCallback('TestAction', 'parameter value');
Then In the graph you can use the parameter value to update the document:
public PXAction<PrimaryDAC> TestAction;
[PXButton]
[PXUIField(DisplayName = "Test Action")]
public virtual IEnumerable testAction(PXAdapter adapter)
{
string parameterValue = adapter.CommandArguments;
// Update document here with parameter value
return adapter.Get();
}

Have processing icons appear during custom button action

I have a process which is activated by a button on a screen - but I'd like to know how to make it work like a process button, where the spinning wheel happens and the green checkbox appears at the end. I've got the following code, which I had wrapped in a PXLongOperation.StartOperation(...) as follows (the PXLongOperation is commented out here, because it didn't seem to be doing anything):
public PXAction<APInvoice> CreatePOBillings;
// [PXButton(CommitChanges = true)]
[PXProcessButton]
[PXUIField(DisplayName = "Create PO Billings", MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update)]
protected void createPOBillings()
{
int i = 0;
try
{
//This wraps the whole process into a 'PXLongOperation' function that will create the spinning 'busy' wheel at the top toolbar...
//PXLongOperation.StartOperation(this, delegate()
//{
var apinvoice = (APInvoice)Base.Document.Current;
if (apinvoice == null) return;
string RefNbr = apinvoice.RefNbr;
//Run the stored procedure which will get the records to create the Project Transactions. This will populate the table 'xCreatePOBilings':
var pars = new PXSPParameter[] { new PXSPInParameter("#p_RefNbr", RefNbr) }; //, new PXSPOutParameter("p2", outp2) };
var results = PXDatabase.Execute("xspMarketingPOBilling", pars);
//Get the dataset from the xCreatePOBillings table which was populated from the stored procedure above:
PXResultset<xCreatePOBillings> res = PXSelect<xCreatePOBillings,
Where<xCreatePOBillings.ponbr, Equal<Required<xCreatePOBillings.ponbr>>>
,OrderBy<Asc<xCreatePOBillings.ponbr
,Asc<xCreatePOBillings.destProject
,Asc<xCreatePOBillings.startDate>>>>>.Select(Base, RefNbr);
//Create the graph for the Project Transactions screen:
RegisterEntry graph = PXGraph.CreateInstance<RegisterEntry>();
//Create a new cache object for the header of Project Transactions:
PMRegister pmreg = new PMRegister();
pmreg.Module = "PM";
graph.Document.Insert(pmreg);
//Define the cache for the Project Transactions screen's grid records:
PMTran pmtrn;
foreach (PXResult<xCreatePOBillings> rec in res)
{
....
graph.Actions.PressSave();
//});
What is the best way to achieve this, if it's possible?
When the static PXLongOperation.StartOperation method is invoked inside a BLC extension class, as first parameter it can only accept Base property instead of this keyword:
PXLongOperation.StartOperation(Base, delegate()
{
...
}

Is this a memory leak in Xamarin Forms?

I have run into a problem where it appears Page objects are not being Garbage Collected once they have been navigated away from. I have put together a very basic example of this that demonstrates the issue when using a NavigationPage and the PushAsync method. The page displays the number of 'Alive' pages using a list of weak references:
public class AppNavigationPage
{
private static List<WeakReference> pageRefs = new List<WeakReference>();
public static Page GetMainPage()
{
return new NavigationPage(CreateWeakReferencedPage());
}
private static Page CreateWeakReferencedPage()
{
GC.Collect();
var result = CreatePage();
pageRefs.Add(new WeakReference(result));
// Add a second unreferenced page to prove that the problem only exists
// when pages are actually navigated to/from
pageRefs.Add(new WeakReference(CreatePage()));
GC.Collect();
return result;
}
private static Page CreatePage()
{
var page = new ContentPage();
var contents = new StackLayout();
contents.Children.Add(
new Button
{
Text = "Next Page",
Command = new Command(() => page.Navigation.PushAsync(CreateWeakReferencedPage()))
});
contents.Children.Add(
new Label
{
Text = string.Format(
"References alive at time of creation: {0}",
pageRefs.Count(p => p.IsAlive)),
HorizontalOptions = LayoutOptions.CenterAndExpand
});
page.Content = contents;
return page;
}
}
As you click the Next Page button, a new page is created with a fixed value label showing the number of page references alive at the point this page was created. Each time you click the button you obviously see this number increase by 1. My understanding is that when you click 'back' on the Navigation Page, the view should be popped off the stack and thrown away (allowing it to be GC'd). However, when I run this test code it indicates that after we have gone back, this view is being retained in memory. This can be demonstrated by clicking Next Page a few times until the reference count is at 3. If you then click Back and then Next Page, I believe the reference count should still be 3 (indicating the old page was GC'd before the new one was created) however the new reference count is now 4.
This seems like quite a serious bug in the X-Forms navigation implementation for iOS (I haven't tested this for other platforms), my guess being it is somehow related to the Strong Reference Cycle problem described here: http://developer.xamarin.com/guides/cross-platform/application_fundamentals/memory_perf_best_practices/
Has anyone else encountered this and/or come up with a solution/workaround for it? Would anyone else agree this is a bug?
As an addition, I did a second example that doesn't involve a NavigationPage (so has to use PushModalAsync instead) and found I had the same problem, so this issue doesn't look to be unique to NavigationPage navigation. For reference the code for that (very similar) test is here:
public class AppModal
{
private static List<WeakReference> pageRefs = new List<WeakReference>();
public static Page GetMainPage()
{
return CreateWeakReferencedPage();
}
private static Page CreateWeakReferencedPage()
{
GC.Collect();
var result = CreatePage();
pageRefs.Add(new WeakReference(result));
// Add a second unreferenced page to prove that the problem only exists
// when pages are actually navigated to/from
pageRefs.Add(new WeakReference(CreatePage()));
GC.Collect();
return result;
}
private static Page CreatePage()
{
var page = new ContentPage();
var contents = new StackLayout();
contents.Children.Add(
new Button
{
Text = "Next Page",
Command = new Command(() => page.Navigation.PushModalAsync(CreateWeakReferencedPage()))
});
contents.Children.Add(
new Button
{
Text = "Close",
Command = new Command(() => page.Navigation.PopModalAsync())
});
contents.Children.Add(
new Label
{
Text = string.Format(
"References alive at time of creation: {0}",
pageRefs.Count(p => p.IsAlive)),
HorizontalOptions = LayoutOptions.CenterAndExpand
});
page.Content = contents;
return page;
}
}
I think what you are seeing is a side effect of async navigation, not memory leak. Instead of WeakReferences you might opt for a finalizer instead and create instances of MyPage (instead of ContentPage).
public class MyPage: ContentPage
{
private static int count;
public MyPage()
{
count++;
Debug.WriteLine("Created total " + count);
}
~MyPage()
{
count--;
Debug.WriteLine("Finalizer, remaining " + count);
}
}
Next trick is to add a delayed GC.Collect() call, like:
private static Page CreateWeakReferencedPage()
{
GC.Collect();
var result = CreatePage();
var ignore = DelayedGCAsync();
return result;
}
private static async Task DelayedGCAsync()
{
await Task.Delay(2000);
GC.Collect();
}
You will note that instances get garbage collected within this delayed collection (output window).
As per Xamarin GarbageCollector: I doubt that it has serious flaws. A minor bug here and there but not that huge. That said, dealing with garbage collections in Android is particularly tricky because there are two of those - Dalvik's and Xamarin's. But that is another story.

Monotouch.Dialog RootElement that opens a UIViewController and Passing in Data

Is there a proper way to use Monotouch.Dialog (iOs) and call a UIViewController when a user clicks on a RootElement? I'm building a page of data based on an array and when clicked I'd like to open this custom view and pass in the array element.. Something like this (doesn't work). Any help is appreciated.
RootElement CreateMenuCategory(JToken menucat) {
RootElement MenuCategory = new RootElement(menucat["menucategoryname"].Value<String>());
RootElement root_element;
Section section = new Section();
foreach(JToken menuitem in menucat["menuitems"]) {
root_element = new RootElement(menuitem["menuitemname"].Value<String>(), (RootElement e) => {
return _menuitemView.LoadMenuItem(menuitem); // menuitem on view is always the same
});
section.Add (root_element);
}
MenuCategory.Add (section);
return MenuCategory;
}
That code doesn't work as the delegate always passes in the same element every time.
This is merely a side effect of how the "menuitem" variable is captured by the lambda function.
Change your foreach loop to look like this instead:
foreach (JToken iteratorMenuitem in menucat ["menuitems"]){
var menuitem = iteratorMenuitem;
//.. the rest

Resources