Handheld Move Inventory Custom UI

I’m trying to create a UI customization that, when a user scans a lot number into the UD lot field, it finds the bin with the lot and puts the part, qty, first bin, and first whse code into the fields.
I was trying to key into the scanLotTXT_ADW field’s validated event and have it trigger from there, but I’m not sure how to get a new record/dataset to put the fields into.


This is what I was going for (trying to create a new row in the view dataset and insert fields, but it throws errors left and right). Has anyone done something like this that could offer me some guidance?
The below code, I was trying to get it to work with hard-coded data before I went further and used the dynamic data from whatever lot they scanned.

	private void scanLotTXT_ADW_Validated(object sender, System.EventArgs args)
		// ** Place Event Handling Code Here **
		EpiDataView edvMove = oTrans.Factory("view");
		var newRow = edvMove.dataView.AddNew();
		newRow["PartNum"] = "S-160";
	    newRow["RowMod"] = "A";
		newRow["TransferQty"] = 2;
		newRow["FromWarehouseCode"] = "MAIN";
		newRow["FromBinNum"] = "TRK";
		newRow["TransferQtyUOM"] = "EA";
		newRow["FromLotNum"] = this.scanLotTXT_ADW.Value;
		newRow["Company"] = "ISI"; 

	private void CallInvTransferAdapterGetTransferRecordMethod()
			// Declare and Initialize EpiDataView Variables
			// Declare and create an instance of the Adapter.
			InvTransferAdapter adapterInvTransfer = new InvTransferAdapter(this.oTrans);

			// Declare and Initialize Variables
			// TODO: You may need to replace the default initialization with valid values as required for the BL method call.
			string iPartNum = "S-160";
			string uomCode = "";

			// Call Adapter method
			Erp.BO.InvTransferDataSet dsInvTransfer = adapterInvTransfer.GetTransferRecord(iPartNum, uomCode);

			// Cleanup Adapter Reference

		} catch (System.Exception ex)

So what I’ve done before is once they scan the lot, use a BAQ to find it, once the results are found you need to then mimic the trace or trigger the standard form events.
For example in this form they call the


where tbPartNum is the PartNum textbox, so do that, fill in the PartNum textbox.text property then call oTrans.OnValidateKeyField(tbPartNum)

This will (should) trigger all the standard lookup stuff, then just populate the Whse , Bin and Lot in the dataview like normal.

1 Like

How do you go about finding that form call if I were to do other things?

Say what now?

How did you come up with this: oTrans.OnValidateKeyField(tbPartNum)?
If I needed to find that info myself, how would I go about it?

You buy the SDK :stuck_out_tongue:
Or use Reflection dnSpy is your friend

1 Like

1 Like


Hi Aaron

I was previously doing this with some BPMs, but when upgrading to 10.2 changed it out for a screen customisation because Epicor changed the signatures on some of the methods that the BPMs were attached to.

This should do what you ask :slight_smile:

// **************************************************
// Custom code for HHInvTransferForm
// Created: 04/11/2016 22:52:40
// **************************************************
using System;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Forms;
using Erp.Adapters;
using Erp.UI;
using Ice.Lib;
using Ice.Adapters;
using Ice.Lib.Customization;
using Ice.Lib.ExtendedProps;
using Ice.Lib.Framework;
using Ice.Lib.Searches;
using Ice.UI.FormFunctions;

using Erp.Proxy.BO;

public class Script
	// ** Wizard Insert Location - Do Not Remove 'Begin/End Wizard Added Module Level Variables' Comments! **
	// Begin Wizard Added Module Level Variables **

	// End Wizard Added Module Level Variables **

	// Add Custom Module Level Variables Here **
	private EpiBaseAdapter oTrans_adapter;

	public void InitializeCustomCode()
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Variable Initialization' lines **
		// Begin Wizard Added Variable Initialization

		// End Wizard Added Variable Initialization

		// Begin Wizard Added Custom Method Calls

		this.txtMyLot.Leave += new System.EventHandler(this.txtMyLot_Leave);
		this.HHInvTransferForm.AfterToolClick += new Ice.Lib.Framework.AfterToolClickEventHandler(this.HHInvTransferForm_AfterToolClick);
		this.oTrans_adapter = ((EpiBaseAdapter)(this.csm.TransAdaptersHT["oTrans_adapter"]));
		// End Wizard Added Custom Method Calls

	public void DestroyCustomCode()
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Object Disposal' lines **
		// Begin Wizard Added Object Disposal

		this.txtMyLot.Leave -= new System.EventHandler(this.txtMyLot_Leave);
		// End Wizard Added Object Disposal

		// Begin Custom Code Disposal
	    this.HHInvTransferForm.AfterToolClick -= new Ice.Lib.Framework.AfterToolClickEventHandler(this.HHInvTransferForm_AfterToolClick);
		// End Custom Code Disposal

	private void txtMyLot_Leave(object sender, System.EventArgs args)
		EpiTextBox txtMyLot = (EpiTextBox)csm.GetNativeControlReference("827a4923-705b-4a91-b03a-3fc7add83cf8");
		EpiTextBox txtMyFromLot = (EpiTextBox)csm.GetNativeControlReference("e2f83141-e64e-4d72-ae69-6f17d8b31e96");
		EpiTextBox txtMyToLot = (EpiTextBox)csm.GetNativeControlReference("d4902d27-86a8-4474-ad1a-5e13605316e5");
		EpiTextBox txtPart = (EpiTextBox)csm.GetNativeControlReference("8f54f627-4a40-4e54-8a2a-fe39fccf325f");
		//EpiTextBox txtFromWhse = (EpiTextBox)csm.GetNativeControlReference("d4e998d4-fbdf-4868-ba34-70ff1dd9dbf4");
		//EpiTextBox txtFromBin = (EpiTextBox)csm.GetNativeControlReference("e4286565-e4c0-4e9e-ae3d-3ceb8e0bcc2d");
		EpiNumericEditor numMyQty = (EpiNumericEditor)csm.GetNativeControlReference("e6615dea-11a8-4a39-9e0c-8c8894160e96");
		if(txtMyLot.Text.Length >= 8)
			txtPart.Text = txtMyLot.Text.Substring(0,8) ;
			txtMyFromLot.Text = txtMyLot.Text;
			txtMyToLot.Text = txtMyLot.Text;

			string strPartNum = txtPart.Text;
			string strLotNum = txtMyLot.Text;
				var session = (Ice.Core.Session)oTrans.Session; 
				var bo = WCFServiceSupport.CreateImpl<PartBinSearchImpl>(session, PartBinSearchImpl.UriPath);
				var ds = bo.GetPartBinByLot(strPartNum, strLotNum);
				var strExpr = "QtyOnHand > 0";
				var strSort = "";
				var foundRows = ds.Tables[0].Select(strExpr, strSort);  
				EpiDataView edvView = (EpiDataView)(oTrans.EpiDataViews["view"]);
				edvView.dataView[edvView.Row]["FromWarehouseCode"] = foundRows[0]["WhseCode"].ToString();		
				edvView.dataView[edvView.Row]["FromBinNum"] = foundRows[0]["BinNum"].ToString();
			} catch (Exception ex) {
				MessageBox.Show("Cannot locate PartBin record: " + ex.Message) ;
		//oTrans.ViewRow["FromWarehouseCode"] = "DEFBIN";

	private void HHInvTransferForm_Load(object sender, EventArgs args)
		// Add Event Handler Code

		EpiDataView evContext = ((EpiDataView)(oTrans.EpiDataViews["CallContextClientData"])); 
	    lblCustomisationID.Text  = "CustomisationID - " + evContext.CurrentDataRow["CustomizationId"].ToString();

		EpiTextBox txtMyLot = (EpiTextBox)csm.GetNativeControlReference("827a4923-705b-4a91-b03a-3fc7add83cf8");
		txtMyLot.CharacterCasing = CharacterCasing.Upper;


	private void HHInvTransferForm_AfterToolClick(object sender, Ice.Lib.Framework.AfterToolClickEventArgs args)
		switch (args.Tool.Key)
	    		case "ClearTool":
	       		EpiTextBox txtMyLot = (EpiTextBox)csm.GetNativeControlReference("827a4923-705b-4a91-b03a-3fc7add83cf8");
	    	   	txtMyLot.Text = String.Empty;


How do I get it to focus the new lot number field and stick?
I tried setting scanLotTXT_ADW.Focus() and scanLotTXT_ADW.Select() on both the initialize and load events, but what seems to happen is it does focus the field, then immediately focuses the Part field right after. I can’t get that Part focus to stop.

Yeah I’m willing to be they hard coded that… @jgiese.wci how did we fix this?

Yup they hard coded it everywhere!


You could try not making that PartNum field a tab stop but I doubt it would work.
You can also use a timer but that’s gross

Try setting the TabIndex to 1 on your Lot and the parNum tab index to something higher.

I tried that. Same thing happens.

It’s hacky but you can try to use the GotFocus event on the part field and then pass focus to the lot field. You would need a flagged bypass on the click event, tab event, or other events for the part field when you actually want to get into that field. Again super hacky but maybe better than a timer also ew…

Hah–that’s exactly the direction I was working on:

	private void part_GotFocus(object sender, System.EventArgs args)
		if( initFlag )
			initFlag = false;

No bueno.
I actually had to change it to the Part.Enter event instead of GotFocus to get it to fire each time, but doing either scanLotTXT_ADW.Select() or .Focus() doesn’t seem to actually select the control…

I’ve used a timer for this in the past I hate it … but it works…

1 Like

Hmm, so the timer more or less works, but I’m still having issues with the select/focus events. They don’t seem to be doing anything.

I mean .Focus() will work (should) but it may get taken away by something else that epicor has hard coded.
Have the timer wait 5 seconds after the form load just for a test. And see if the focus stays after that point.

.Focus() doesn’t seem to do anything at all.
If I set .Focus(), after 5 (and even 10) seconds, the Part field still has the cursor.
If I set it to .Select(), after 5 seconds, the cursor disappears from all fields as shown in the above gif.

Ah right!, sorry I forgot
Timer’s run in a different thread so you have to “Invoke” them into the right thread. Try this in your Timed Event

using System.Threading;
private void OnTimedEvent(Object source, ElapsedEventArgs e) {
 yourTextBox.Invoke((ThreadStart) delegate() {