Iterating Through Panels (Sheets) To Find Specifically Named Control

I have a requirement on the NonConformance module (form) to make the “Request Move” checkbox unavailable for all users.

One of the immediate ways I thought of doing this (and I’m certainly open to more reasonable suggestions), is to write a form load event where I would iterate through each panel (tab sheet), one at a time, to look for the “chkRequestMove” control and its associated label (thank goodness these controls are named exactly the same on each panel). Once the checkbox is found, adjust the “Visible” property to “false”.

What I don’t know how to do is properly reference these panels (tab sheets) in custom code; let alone iterate through them with a “for each” statement.

If my goal is to check each panel on the “NonConfForm” module for the “chkRequestMove” control, how would I properly do this in custom code?

Thanks for your time.

I like the idea of programmatically changing the controls properties. But … what’s wrong with just manually selecting them and setting their visibility property?

Absolutely nothing wrong with that at all, Calvin. Thanks for responding.

I was thinking that it might be less painful should the need arise to restore those checkbox controls, for the next person to simply comment out the code in one place, versus resetting the properties on each control.

I could definitely go the route you have suggested.

At my former employer, we hid the Request Move checkbox everywhere too… It’d be even better if that was a setting in Company or Site Config…

Can you just refer to the controls by their EpiGuid, in the Form_Load event?

That would put the “functionality” in one place, making it easy to disable.

Not a bad idea.

Since I don’t exactly know how to iterate through each panel to isolate them by their common name, I suppose isolating them by their GUID would certainly work.

I’ll give that a try and report my results back to the thread.

This might help get you started. This is on form load.

this.UD08Form.WindowState = FormWindowState.Maximized;

			foreach ( Control control in this.UD08Form.Controls )
			{
				if ( control.Name == "windowDockingArea1" )
				{
					control.Size = new System.Drawing.Size( 0, 0 );
					foreach ( Control control2 in control.Controls )
					{
						if ( control2.Name == "dockableWindow3" )
						{
							control2.Size = new System.Drawing.Size( 0, 0 );
							foreach ( Control control3 in control2.Controls )
							{
								if ( control3.Name == "epiTreeViewPanel1" )
								{
									control3.Size = new System.Drawing.Size( 0, 0 );
									foreach ( Control control4 in control3.Controls )
									{
										if ( control4.Name == "treeView" ) {   control4.Size = new System.Drawing.Size( 0, 0 );   }
									} //   end foreach ( Control control4 in control3.Controls )
								} //   end if ( control3.Name == "epiTreeViewPanel1" )
							} //   end foreach ( Control control3 in control2.Controls )
						} //   end if ( control2.Name == "dockableWindow3" )
					} //   end foreach ( Control control2 in control.Controls )
					control.Size = new System.Drawing.Size( 10, 10 );
				} //   end if ( control.Name == "windowDockingArea1" )
			} //   end foreach ( Control control in this.UD08Form.Controls )
1 Like

I have done something a little like this, though normally when something needs to happen depending on events within the form.

My approach has been to set the form itself as a variable using its GUID. Then create a function which has a “for each” through the controls within the control collection of the control passed in as a parameter. If any of the controls match what you want, take the action you want. If not, but they have children controls of their own, call the same function again passing that control in. That way you usually don’t need to know what type of control/sheet you’re dealing with unless it’s the ones you want.

@knash :

More than likely, I’ll implement Calvin’s suggestion, but I’d like to understand (in the effort to become a bit more educated) how form controls iteration in the Epicor environment actually works.

Let’s say I can successfully declare/identify my form object like so:

Erp.UI.App.NonConfEntry.NonConfForm frmNonConf;
frmNonConf = (Erp.UI.App.NonConfEntry.NonConfForm)csm.GetNativeControlReference("2dcd1674-5e34-4d98-b493-c75747027376");

MessageBox.Show(frmNonConf.Name);

The message box successfully shows the name of the form, so I know I have that correct.

If I apply that to your example, how would I reference the form object correctly?

Neither of the following attempts seem to work:

foreach (Control control in this.frmNonConf.Controls )

- OR -

foreach (Control control in frmNonConf.Controls )

What might be the correct way to do this?


EDIT: By the way, my error message is:

'Script' does not contain a definition for 'frmNonConf' and no extension method 'frmNonConf' accepting a first argument of type 'Script' could be found (are you missing a using directive or an assembly reference?)

Do I need to add an assembly of some sort?

1 Like

This works for me:

trackerPanel = (EpiBasePanel)csm.GetNativeControlReference(“9fc6a346-e062-4c54-a790-6e37882f4463”);
SetUpButtons(trackerPanel);

private void SetUpButtons(Control parentcontrol)
{
foreach (Control c in parentcontrol.Controls)
{
if (c is EpiButton && !c.Name.StartsWith(“btnTab”))
{
EpiButton b = (EpiButton)c;
b.UseAppStyling = false;
b.UseOsThemes = DefaultableBoolean.False;
b.Appearance.BackColor = System.Drawing.Color.DimGray;
b.Appearance.BorderColor = System.Drawing.Color.DimGray;
b.Appearance.ForeColor = System.Drawing.Color.White;
}
else if (c.HasChildren)
{
SetUpButtons( c);
}
}
}

@dhewi / @knash :

Odd. It seems to recognize:

foreach (Control ctrl in frmNonConf.Controls)

… but not:

foreach (Control control in frmNonConf.Controls )


Unless I’m clinically drunk, the syntax appears to be the same.

(Working on the rest of the code now…)

The second param in the ForEach is the temp variable name, no?

is “control” a reserved word, while “ctrl” isn’t?

@ckrusen :

Correct. That would be the variable which contains the control “object” in the list of possible controls on the given form.

I’d say you’re on to something there. It very well may be a reserved word/phrase, which would explain the compiling errors when that specific variable name was used.

Well… I do have some success iterating through all of the controls, one level at a time, to ultimately find the ones associated with “Request Move” on each panel/sheet/tab. From there, it was just a matter of making those controls “not visible”. In order to do this, I had to drill down through a total of eight (8) levels of control “layers” in order to isolate exactly what I needed.

I’m going to work up a little “How To” that covers exactly what I did, then I’ll post that information here for anyone who might be interested. That documentation will also help me remember what the heck I did.

I’m sure there’s a simpler way, but based on all the information suggested here (thanks to all who participated in the discussion to help me out), this appeared to be the most logical way to approach my issue.

More to follow…


@ckrusen – After further being told by my supervisor that this sort of customization might need to be applied to other form modules, I’m going to stick with the custom code method instead of manually editing the control properties across each control object (which is still a viable solution).

1 Like

Count me in as VERY interested.

Does your code need to know the complete structure a head of time? Or can it dynamically find objects that have sub-objects, that will need drilling through?

If anyone is familiar with jQuery in web programming, you know how versatile the selector system is. You could have found all the objects in question with one line of code. And actually set the visibility property in that same line.

$("#chkRequestMove").hide();

I ended up using a variant of @knash’s solution, whereby, you’d need to reference the specific names of each control through each level of iteration, one at a time. Admittedly, this seems to be a pain in the seat cushion, but these types of control names are specific to each form module, so you’d need to uncover their identities in order to work with them eventually.

It does find the objects it needs “dynamically”, but only one level at a time. You’ll see why when I post the documentation. A quick hint is that, when I tried to iterate through controls on the “form level”, I assumed that a simple “foreach (Control)” statement would find all control objects embedded in the form. Oh… but no… we can’t have that simplicity, can we? That statement merely listed the “docking area” controls - nothing else. I then had to iterate through the controls on the “docking area” to uncover “dockable windows” only… and so on and so on.

As long as you reference the control names you need to drill down, it works rather well. I just wish that I knew how to indicate, “find controls of type [checkbox]” only - and - if that type of control does not exist at the form level, drill down until you find them. I believe that would be the dynamic nature you’re looking for (I’d prefer that approach, as well).

Nonetheless, as long as you isolate the names of each control you need to iterate through at each level, it appears to be rather fool-proof (see @knash’s example which shows how he needed to be very specific with the control names that he was iterating through).

I’m working on the documentation now. Hopefully, I won’t be typically long-winded while typing everything up.

More to follow…

2 Likes

If it helps, the code snippet I posted does both things you’re wishing for: it dives as deep as you need, all by itself, and finds controls of a given type (EpiButton in this snippet). The key is that it’s recursive.

As far as finding which ones you want, one approach that has worked for me is comparing the EpiBinding property - if it’s linked to the field you want then take the action you want. That way you don’t need to know what the names of the controls are either.

1 Like

Might sound like a crazy idea, but what about taking it the next level and having a function that looks up user codes, you can then apply that to all your custom forms loads. Then all you need to know is the information to update the user code with what ever object name and property/value and set the inactive flag in the usercode if you want.

Not sure how well that would work but it’s an idea.

Greetings All.

This is how I approached the issue (and I apologize ahead of time for the length of this post, but I tend to like details because I walk away with a better understanding - so I prefer to explain things with that in mind).

Initially, I merely wanted to see what controls were available on the “NonConformance” form and I had assumed that a simple call to the “Controls” collection of the form object would show me ALL of the controls. That did not seem to be the case (at least in the manner I started coding the solution):

The results did not line up well with my expectations, as I had assumed I would see something related to the “Request Move” checkbox… and certainly more than 12 total hits.

My only recognition of “Sonoma” is that its a well-known county in California… or a race track also located in California… or that it’s a clothing line that typically distributes to JCPenney, so I tried to stick with results that seemed to be logical:

  • windowDockingArea2
  • windowDockingArea1

Ok. So, I decided to look for possible controls contained within those specific controls:

Gee… 1 control listed for “windowDockingArea2”. Again, not what I had expected.

I decided to go a bit deeper, taking a lead from Ken Nash’s example above:

… and deeper:

… and deeper:

Ok. Well, here, I started to get a clue that the nine (9) hits might correspond with the nine (9) tab panels contained within the “NonConformance” form module. But, I needed to validate that, so I went a bit further:

Great. Now I’m getting the actual names for the panel controls. Yet, I still need to work within the panel object to iterate through the controls on that level:

Terrific. Now I’m down to the group controls level. At this point, I made the assumption (based on the object tree on the form customization view) that the checkbox control would reside within the just-discovered group controls, but I noticed that Epicor did not maintain standard nomenclature for the groups. So, I merely looked at each group name and created a conditional statement against those names. Before adding that code, I knew that I did not have to continue looking for specific control names once my existing code arrived at the “dockableWindow” control level, because this level represents the tab panels (all I need to do is hunt through those panel controls). So, I went back to modify the code a bit, as explained here:

I replaced that code with the conditional statement that identifies only the “Material Reporting” group and included the logic necessary to isolate and modify the properties of the “Request Move” checkbox:

09


In any event, this works.

Efficient code? Nope - never said it was. But based on my level of skill and knowledge, this is how I approached it.

I will also concede that there are support issues with this approach, especially if Epicor decides to change their naming conventions for controls contained within the “NonConformance” form module. That’s a large drawback in this solution that you’d need to be aware of – because that would force you to edit the code with the updated nomenclature.

I was hoping to implement something like what Daryl Hewison had illustrated, but I didn’t see how iteration below the form level (where my code found only 12 controls in the collection) would have gone deeper.

If you decide to use this sort of approach, just be aware that there is vulnerability should you upgrade to a different version of Epicor. But - if you’re documenting your customizations well, then you’ll be aware of the need to review this type of customization against any possible nomenclature changes initiated by Epicor in future versions.

[By the way, we’re limping along here on v10.1.400.14]

4 Likes

After posting the previous novel concerning the solution I worked on, I went back to take a better look at Daryl Hewison’s suggestion and it appears that I missed a pretty obvious line in his code that enables further iteration through child controls contained within the parent:

else if (c.HasChildren)

That’s pretty important - because it completely negates all of the nonsense I posted above, but it also helps me understand how Daryl was automating the process of iterating through to the next level of controls without knowing anything about the controls themselves.

Using Daryl’s example, I came up with this code block, which eliminates the need to know the names of any of your form module controls, especially level by level –

Again, on form load, I declare/define the form by GUID:

Erp.UI.App.NonConfEntry.NonConfForm frmNonConf;
frmNonConf = (Erp.UI.App.NonConfEntry.NonConfForm)csm.GetNativeControlReference("<PLACE YOUR GUID HERE>");

I then define the control name parameter that I want to search on (in my case, the checkbox control and its label control end with the phrase “RequestMove”):

string strCtrlName = "RequestMove";

I then make the call to the method that performs the work by passing in the form object (as the parent control) and the control search string:

modifyCheckbox(frmNonConf, strCtrlName);

The method looks like this:

private void modifyCheckbox(Control parentControl, string strControlName)
{
	foreach (Control ctrl in parentControl.Controls)
	{
		if (ctrl.Name.EndsWith(strControlName))
		{
			ctrl.Visible = false;
		}
		else if (ctrl.HasChildren)
		{
			modifyCheckbox(ctrl, strControlName);
		}
	}
}

Tested and working, simply by editing the “Visible” property value in code and nothing else:


Thanks, Daryl… now I understand it!

1 Like