I have the same issue as the below link but with a different graph (APPrintChecks)
how-do-i-override-pxfilteredprocessingjoin-in-a-graph-extension-without-altering
I am overriding the main view to pull in the remittance name from APContact to show in the grid.
[PXFilterable]
public PXFilteredProcessingJoin<APPayment, PrintChecksFilter,
InnerJoin<Vendor, On<Vendor.bAccountID, Equal<APPayment.vendorID>>,
InnerJoin<APContact, On<APContact.contactID, Equal<APPayment.remitContactID>>>>,
Where<boolTrue, Equal<boolTrue>>,
OrderBy<Asc<Vendor.acctName, Asc<APPayment.refNbr>>>> APPaymentList;
However, I do not know how to override the delegate so I won't have the same problem as the other poster (no filter being applied).
protected virtual IEnumerable appaymentlist()
{
if (cleared)
{
foreach (APPayment doc in APPaymentList.Cache.Updated)
{
doc.Passed = false;
}
}
foreach (PXResult<APPayment, Vendor, PaymentMethod, CABatchDetail> doc in PXSelectJoin<APPayment,
InnerJoinSingleTable<Vendor, On<Vendor.bAccountID, Equal<APPayment.vendorID>>,
InnerJoin<PaymentMethod, On<PaymentMethod.paymentMethodID, Equal<APPayment.paymentMethodID>>,
LeftJoin<CABatchDetail, On<CABatchDetail.origModule, Equal<BatchModule.moduleAP>,
And<CABatchDetail.origDocType, Equal<APPayment.docType>,
And<CABatchDetail.origRefNbr, Equal<APPayment.refNbr>>>>>>>,
Where2<Where<APPayment.status, Equal<APDocStatus.pendingPrint>,
And<CABatchDetail.batchNbr, IsNull,
And<APPayment.cashAccountID, Equal<Current<PrintChecksFilter.payAccountID>>,
And<APPayment.paymentMethodID, Equal<Current<PrintChecksFilter.payTypeID>>,
And<Match<Vendor, Current<AccessInfo.userName>>>>>>>,
And<APPayment.docType, In3<APDocType.check, APDocType.prepayment, APDocType.quickCheck>>>>.Select(this))
{
yield return new PXResult<APPayment, Vendor>(doc, doc);
if (_copies.ContainsKey((APPayment)doc))
{
_copies.Remove((APPayment)doc);
}
_copies.Add((APPayment)doc, PXCache<APPayment>.CreateCopy(doc));
}
}
There are other private variables that are referenced in this. Any help appreciated.
Also, if there's a simpler way to pull in a related value on a grid like this (virtual field in DAC?) I'm not stuck on doing it with a graph extension.
So this appears to work but it seems messy and duplicates a lot of code and private variables. Appreciate any feedback if there's a better way to do this:
public class APPrintChecks_Extension : PXGraphExtension<APPrintChecks> {
[PXFilterable]
public PXFilteredProcessingJoin<APPayment, PrintChecksFilter,
InnerJoin<Vendor, On<Vendor.bAccountID, Equal<APPayment.vendorID>>,
InnerJoin<APContact, On<APContact.contactID, Equal<APPayment.remitContactID>>>>,
Where<boolTrue, Equal<boolTrue>>,
OrderBy<Asc<Vendor.acctName, Asc<APPayment.refNbr>>>> APPaymentList;
public IEnumerable appaymentlist()
{
if (cleared)
{
foreach (APPayment doc in APPaymentList.Cache.Updated)
{
doc.Passed = false;
}
}
foreach (PXResult<APPayment, Vendor, APContact, PaymentMethod, CABatchDetail> doc in PXSelectJoin<APPayment,
InnerJoinSingleTable<Vendor, On<Vendor.bAccountID, Equal<APPayment.vendorID>>,
InnerJoin<APContact, On<APContact.contactID, Equal<APPayment.remitContactID>>,
InnerJoin<PaymentMethod, On<PaymentMethod.paymentMethodID, Equal<APPayment.paymentMethodID>>,
LeftJoin<CABatchDetail, On<CABatchDetail.origModule, Equal<BatchModule.moduleAP>,
And<CABatchDetail.origDocType, Equal<APPayment.docType>,
And<CABatchDetail.origRefNbr, Equal<APPayment.refNbr>>>>>>>>,
Where2<Where<APPayment.status, Equal<APDocStatus.pendingPrint>,
And<CABatchDetail.batchNbr, IsNull,
And<APPayment.cashAccountID, Equal<Current<PrintChecksFilter.payAccountID>>,
And<APPayment.paymentMethodID, Equal<Current<PrintChecksFilter.payTypeID>>,
And<Match<Vendor, Current<AccessInfo.userName>>>>>>>,
And<APPayment.docType, In3<APDocType.check, APDocType.prepayment, APDocType.quickCheck>>>>.Select(Base))
{
yield return new PXResult<APPayment, Vendor, APContact>(doc, doc, doc);
if (_copies.ContainsKey((APPayment)doc))
{
_copies.Remove((APPayment)doc);
}
_copies.Add((APPayment)doc, PXCache<APPayment>.CreateCopy(doc));
}
}
private bool cleared;
public void Clear()
{
Base.Filter.Current.CurySelTotal = 0m;
Base.Filter.Current.SelTotal = 0m;
Base.Filter.Current.SelCount = 0;
cleared = true;
Base.Clear();
}
private readonly Dictionary<object, object> _copies = new Dictionary<object, object>();
}
Per Rick's suggestion I implemented the FieldSelecting method. Much, much simpler/cleaner code. It does in fact cause a round trip to the database for each row when using this in a grid column, however, for check printing this should be acceptable. Thanks Rick! Code below.
protected void APPayment_UsrRemitTo_FieldSelecting(PXCache cache, PXFieldSelectingEventArgs e)
{
var row = (APPayment)e.Row;
// fill usrRemitTo from APContact
if (row != null)
{
var extension = PXCache<APRegister>.GetExtension<APRegisterExt>(row);
using (PXConnectionScope cs = new PXConnectionScope())
{
APContact rec = PXSelectReadonly<APContact, Where<APContact.contactID, Equal<Required<APPayment.remitContactID>>>>.Select(Base, row.RemitContactID);
if (rec != null)
{
string remitToName = (!string.IsNullOrEmpty(rec.FullName)) ? rec.FullName : "";
e.ReturnValue = remitToName;
}
else
{
e.ReturnValue = "";
}
}
}
}
Using DBScalar, you can further simplify your event with just one line :
public class APPaymentExt : PXCacheExtension<APPayment>
{
#region UsrRemitTo
[PXString(100)]
[PXUIField(DisplayName="Remit To")]
[PXDBScalar(typeof(Search<APContact.fullName,Where<APContact.contactID, Equal<APPayment.remitContactID>>>))]
public virtual string UsrRemitTo { get; set; }
public abstract class usrRemitTo : PX.Data.BQL.BqlString.Field<usrRemitTo> { }
#endregion
}
My app is a list of element names. Clicking one goes to another activity with more details, and an editText and button to change its name. When I go back to the list, I want it to have the new name if updated. My onCreate method has this code which populates it from the database perfectly (ignore the hardcoded size, its just simpler for debugging)
final String DATABASE_NAME = "element_db";
final AppDatabase appDatabase;
appDatabase = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, DATABASE_NAME).build();
ListView list = (ListView)findViewById(R.id.listView);
final String[] element = new String[3];
new Thread(new Runnable() {
#Override
public void run() {
List<Element> elements;
elements = appDatabase.elementDao().fetchElements(); // a dao query "select*from"
int size = elements.size();
for (int i = 0; i < size; i++){
element[i] = elements.get(i).getName();
}
}
}) .start();
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this, R.layout.activity_list_view, R.id.textView, element);
list.setAdapter(arrayAdapter);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent i = new Intent(MainActivity.this, itemdetail.class);
i.putExtra("pos", position);
startActivity(i);
}
});
To achieve an updated list when returning to this screen, I copied the above code into the onRestart method, but now the database does not return anything. If I just set the array that goes into the adapter to {"a", "a", "a"} the list populates fine. Any ideas why the database works onCreate but not onRestart?
I think you can use ViewModel and livedata to observe any database change
I got some questions regarding the use of threads, specially when you have to wait for a thread to be finished so you can perform other operations.
In my app, I use threads for operations such as http connections or when I read from or write to a RecordStore.
For example in the following class that I use to initialize my thread, I retrieve some customers from a webservice using the method called HttpQueryCustomers.
public class thrLoadCustomers implements Runnable {
private RMSCustomer mRMSCustomer;
private String mUrl;
public thrLoadCustomers(RMSCustomer rmsCust, String url) {
mRMSCustomer = rmsCust;
mUrl = url;
}
public void run() {
String jsonResultados = "";
try {
jsonResultados = HttpQueryCustomers();
} catch (IOException ex) {
//How to show a message from here??
} catch (SecurityException se) {
//How to show a message here??
} catch (NullPointerException npe) {
//How to show a message from here??
}
if (!jsonResultados.equals("")) {
try {
mRMSCustomer.save(jsonResultados);
} catch (RecordStoreException ex) {
//How to show a message from here???
}
}
}
public String HttpQueryCustomers() throws IOException,SecurityException,NullPointerException {
StringBuffer stringBuffer = new StringBuffer();
HttpConnection hc = null;
InputStream is = null;
System.out.println(mUrl);
try {
hc = (HttpConnection) Connector.open(mUrl);
if (hc.getResponseCode() == HttpConnection.HTTP_OK) {
is = hc.openInputStream();
int ch;
while ((ch = is.read()) != -1) {
stringBuffer.append((char) ch);
}
}
} finally {
is.close();
hc.close();
}
String jsonData = stringBuffer.toString();
return jsonData.toString();
}
}
Notice in the above class that I pass a parameter called rmsCust of the type RMSCustomer
RMSCustomer is a class that I use to handle all the operations related to RMS:
public class RMSCustomer {
private String mRecordStoreName;
private Customer[] mCustomerList;
public RMSCustomer(String recordStoreName) {
mRecordStoreName = recordStoreName;
}
public Customer[] getCustomers() {
return mCustomerList;
}
public Customer get(int index) {
return mCustomerList[index];
}
public void save(String data) throws RecordStoreException,JSONException,NullPointerException {
RecordStore rs = null;
int idNuevoRegistro;
String stringJSON;
try {
rs = RecordStore.openRecordStore(mRecordStoreName, true);
JSONArray js = new JSONArray(data);
//Set the size of the array
mCustomerList = new Customer[js.length()];
for (int i = 0; i < js.length(); i++) {
JSONObject jsObj = js.getJSONObject(i);
stringJSON = jsObj.toString();
idNuevoRegistro = addRecord(stringJSON, rs);
//Add a new Customer to the array
mCustomerList[i] = initializeCustomer(stringJSON, idNuevoRegistro);
}
} finally {
if (rs != null) {
rs.closeRecordStore();
}
}
}
public int addRecord(String stringJSON, RecordStore rs) throws JSONException,RecordStoreException {
byte[] raw = stringJSON.getBytes();
int idNuevoRegistro = rs.addRecord(raw, 0, raw.length);
return idNuevoRegistro;
}
public Customer initializeCustomer(String stringJSON, int idRecord) throws JSONException {
Customer c = new Customer();
JSONObject jsonObj = new JSONObject(stringJSON);
// Set Customer properties
//...
return c;
}
}
This class is used to show a list of customer and ,as you can see, it extends the List class and receives an array of Customers as a parameter.
public class ListCustomers extends List {
private final Customer[] mData;
public static ListCustomers create(Customer[] data) {
int i = 0;
for (; i < data.length; i++) {
if (data[i] == null) {
break;
}
}
String[] names = new String[i];
for (int j = 0; j < i; j++) {
names[j] = data[j].name;
}
return new ListCustomers(names, data);
}
protected ListCustomers(String names[], Customer[] data) {
super("List of Customer", IMPLICIT, names, null);
mData = data;
}
public Customer getSelectedObject() {
return mData[this.getSelectedIndex()];
}
}
Finally this is how I call the thread from the MIDlet (using all the 3 previous classes) when I want to show a List of Customers:
private void showCustomerList(String url) {
showWaitForm();
if (scrCustomerList == null) {
rmsCustomers = new RMSCustomer("rmsCustomers");
thrLoadCustomers load = new thrLoadCustomers(rmsCustomers, url);
Thread t = new Thread(load);
t.start();
try {
t.join();
} catch (InterruptedException ex) {
}
scrCustomerList = ListCustomers.create(rmsCustomers.getCustomers());
scrCustomerList.addCommand(cmdSelect);
scrCustomerList.addCommand(cmdBack);
scrCustomerList.setCommandListener(this);
}
mDisplay.setCurrent(scrCustomerList);
}
Now here's the problems I have :
The showWaitForm() doesn't work (it sets a form with a Gauge as the
Current form)
I don't know how to show all the exceptions that might be thrown from
within the thrLoadCustomers class.
I don't know whether using t.join() is the best choice
The last question is about something the book I'm reading says :
Threads, in particular, can be a scarce commodity. The MSA
specification requires that an application must be allowed to create
ten threads. Just because you can doesn’t mean you should. In general,
try to use the fewest resources possible so that your application will
run as smoothly as possible
This is the first time a use threads, and in my app I might have up to 10 threads (classes). However, I will only execute once thread at the time, will I be going against what the previous quotation says??
I hope I'm not asking too many questions. Thank you very much for your help.
P.D Much of the code I posted here wouldn't have been possible with the help of Gregor Ophey
Question #1 is about a different problem not related to threading, and for which very little code is shown. I'd suggest you to post a new dedicated question with proper explanation of the issue.
Questions #2 and #3: You could define a wrapper class like this:
public class WSResult {
private boolean success; //true if the WS call went ok, false otherwise
private String errorMessage; //Error message to display if the WS call failed.
private Object result; //Result, only if the WS call succeeded.
private boolean completed = false;
//TODO getter and setters methods here
}
In your screen, you can create an instance of result and wait for it:
WSResult result = new WSResult();
//Start thread here
new Thread(new LoadCustomersTask(result)).start();
//This is old school thread sync.
synchronized(result){
while(!result.isCompleted()){
result.wait();
}
}
//Here the thread has returned, and we can diaplay the error message if any
if(result.isSuccess()){
} else {
//Display result.getErrorMessage()
}
Then your runnable would be like this:
class LoadCustomersTask implements Runnable {
private final WSResult result;
public LoadCustomersTask(WSResult res){
result = res;
}
public void run(){
//Do the WS call
//If it went well
result.setSuccess(true);
result.setResult(jsonResultados);
//Else
result.setSuccess(false);
result.setErrorMessage("Your error message");
//In any case, mark as completed
result.setcompleted(true);
//And notify awaiting threads
synchronized(result){
result.notifyAll();
}
}
}
You can also do it with thread.join, but wait/notify is better because you not making the screen depend on the particular thread where the runnable runs. You can wait/notify on the result instance, as shown, or on the runnable if it is intended for a single use.
Question #4: Yes threads must not be abused, specially in JavaME where programs usually run in single core CPUs with a frecuency in the order of MHz. Try not to have more than 1-3 threads running at the same time. If you really need to, consider using a single thread for running all background tasks (a blocking queue).
EDIT 2: If you're looking for an answer to a similar problem, check Stuart's answer and my comments on it.
EDIT: I am actually getting a Mono.Debugger.Soft.VMDisconnectedException. I also recently installed Windows 8.1 and Resharper (though Resharper is suspended now).
When I access a very simple list property of my view model in my MVVMCross Xamarin iOS application, the program fails. It doesn't quit most of the time: it acts like it's running. The simulator has a black screen and there is no exception. If I breakpoint on if (messagesViewModel != null) source.ItemsSource = messagesViewModel.Messages; and then type messagesViewModel.Messages into the Immediate Window, everything stops, so I can tell it is failing at this line. If instead I "step over", it never moves to the next line.
I was having similar behavior when I was toggling this code in the MvxTableViewSource:
public override int RowsInSection(UITableView tableview, int section)
{
return 1;
}
My view model looks like this:
public class MessagesViewModel : MvxViewModel
{
private List<BaseMessage> _messages = null;
public List<BaseMessage> Messages
{
get
{
return _messages; //yes, I know I'm returning null
//I wasn't at first.
}
}
public MessagesViewModel()
{
}
}
This is my ViewDIdLoad on the MvxTableViewController:
public override void ViewDidLoad()
{
base.ViewDidLoad();
var source = new MessagesTableViewSource(TableView);
//was binding here, removed it for debug purposes
//failure on second line here
var messagesViewModel = ViewModel as MessagesViewModel;
if (messagesViewModel != null) source.ItemsSource = messagesViewModel.Messages;
TableView.Source = source;
TableView.ReloadData();
}
Some initialization code:
public class App : MvxApplication
{
public App()
{
var appStart = new MvxAppStart<MessagesViewModel>();
Mvx.RegisterSingleton<IMvxAppStart>(appStart);
}
}
public partial class AppDelegate : MvxApplicationDelegate
{
//empty functions removed.
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
var presenter = new MvxTouchViewPresenter(this, Window);
var setup = new Setup(this, presenter);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
Window.MakeKeyAndVisible();
return true;
}
}
I suspect whatever the error is, it isn't in any of the code you have posted.
I just created a simple ViewModel:
public class FirstViewModel
: MvxViewModel
{
private List<string> _items = new List<string>() { "One", "Two", "Three"};
public List<string> Items
{
get { return _items; }
set { _items = value; RaisePropertyChanged(() => Items); }
}
}
And a simple View:
[Register("FirstView")]
public class FirstView : MvxTableViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
// ios7 layout
if (RespondsToSelector(new Selector("edgesForExtendedLayout")))
EdgesForExtendedLayout = UIRectEdge.None;
var firstViewModel = ViewModel as FirstViewModel;
var source = new MessagesTableViewSource(TableView);
source.ItemsSource = firstViewModel.Items;
TableView.Source = source;
}
public class MessagesTableViewSource : MvxTableViewSource
{
public MessagesTableViewSource(UITableView tableView) : base(tableView)
{
tableView.RegisterClassForCellReuse(typeof(MessagesCell), new NSString("MessagesCell"));
}
protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
return tableView.DequeueReusableCell("MessagesCell");
}
}
public class MessagesCell : MvxTableViewCell
{
public MessagesCell(IntPtr handle)
: base(handle)
{
var txt = new UILabel(new RectangleF(0, 0, 320, 44));
Add(txt);
this.DelayBind(() =>
{
this.CreateBinding(txt).Apply();
});
}
}
}
And this code runs fine...
I wouldn't completely trust the integration of Xamarin.iOS with the Immediate window - it is better now than it used to be, but I've seen several problems with it before.
Some things to possibly check:
does the above code work for you?
if it does, then what's in your BaseMessage and MessagesTableViewSource classes - perhaps they are causing the problem?
can you use Mvx.Trace("The list is {0}", messagesViewModel.Messages ?? "-null") to view the list? Can you use trace within the ViewModel property get - is it being called? Can you use trace within the ViewModel constructor?
are all your assemblies building against the same versions of things? Are all your assemblies definitely rebuilt? (Check "Build|Configuration Manager")- what version of Xamarin.iOS are you running in VS and in the Mac?
I'm using this code to create the dialog:
SettingsDialog settingDialog = new SettingsDialog ();
RootElement dialog = settingDialog.CreateDialog ();
DialogViewController dv = new DialogViewController (dialog, true);
dv.ViewDissapearing += delegate(object sender1, EventArgs e1) {
// update the values
Config.VendorId = settingDialog.VendorId;
} ;
NavigationController.PushViewController (dv, true);
Ths is the code for the SettingsDialogClass
public class SettingsDialog
{
private EntryElement idElement;
public RootElement CreateDialog ()
{
idElement = new EntryElement ("Vendor Id", string.Empty, Config.VendorId.ToString (CultureInfo.InvariantCulture)){ KeyboardType = UIKeyboardType.NumberPad };
element = new RootElement ("Configuration"){
new Section (){
idElement
} ,
...
}
public string VendorId {
get {
return idElement.Value;
}
}
The problem is that the above property ALWAYS returns the old value, it never gets updated.
Any idea why?
Thank,
Ignacio
I found the answer to my question, this thread had the key to it
How can I pass data into MonoTouch.Dialog's delegates?
this line:
i was able to get around it by calling field.FetchValue() before trying to retrieve it
So I modified the property as
public string VendorId {
get {
idElement.FetchValue();
return idElement.Value;
}
}