CodedUI search for grandchild control? - c#

I have a table which has multiple rows. I want to verify that a specific string StringName is in the table. I used CodedUI to find the control, and the UI map is like this:
public class UIItemTable : WpfTable
{
#region Fields
private UIItemRow mUIItemRow;
#endregion
}
public class UIItemRow : WpfRow
{
#region Fields
private UIBlahBlahBlahCell mUIBlahBlahBlahCell;
#endregion
}
public class UIBlahBlahBlahCell : WpfCell
{
#region Fields
private WpfText mUIBlahBlahBlahText;
#endregion
}
I want to find a WpfText that matches my StringName. So I added a function to UIItemTable:
public class UIItemTable : WpfTable
{
public WpfText Find(string StringName)
{
WpfText StringNameWpfText = new WpfText(this);
StringNameWpfText.SearchProperties[WpfText.PropertyNames.Name] = StringName;
return StringNameWpfText;
}
#region Fields
private UIItemRow mUIItemRow;
#endregion
}
But CodedUI cannot find the WpfText control. The error I received is:
Microsoft.VisualStudio.TestTools.UITest.Extension.UITestControlNotFoundException.
The playback failed to find the control with the given search
properties. Additional Details: TechnologyName: 'UIA' ControlType:
'Text' Name: ...
I think this error may be due to the fact that the WpfCell I want to search is actually a grandchild of the table. But I thought CodedUI handles tree traversals right? How do I search for a grandchild?

You should move your code out of the partial UIMap.designer.cs class as it will be overwritten the next time you record a control/assert/etc. Move you code to the uimap.cs partial class instead.
The text control may or may not be the next level down, if it is not on the child level of UIItemTable, your code will fail. You should try to use the test tool builder cross hair to identify the level it exists at.
You can use the child enumerator and search through all child/grandchild/etc elements until you find your text item, an example below would be something like:
public UIControl FindText(UIControl ui)
{
UIControl returnControl = null;
IEnumerator<UITestControl> UIItemTableChildEnumerator =
ui.GetChildren().GetEnumerator();
while(uiChildEnumerator.MoveNext())
{
if(uiChildEnumerator.Current.DisplayText.equals(StringName))
{
returnControl = uiChildEnumerator.Current
break;
}
else
{
returnControl = this.FindText(uiChildEnumerator.Current)
if(returnControl != null)
{
break;
}
}
}
return returnControl;
}
4 . Another option would be to use the FindMatchingControls() function and parse through similar to the above statement. You might still need the proper direct parents though
WpfText StringNameWpfText = new WpfText(this);
IEnumerator<UITestControl> textItems = StringNameWpfText .FindMatchingControls().GetEnumerator();
while (textItems.MoveNext())
{
//search
}
Hope this helps

I usually find cells first. I believe any content in a table would be in a cell. So we can search for the cells first and then dive in it.
One way to find cell whose value matches with the given string.
WpfCell cell = myWpfTable.FindFirstCellWithValue(string StringName);
if(cell != null)
return cell.Value // returns WpfControl;
else
return null;
Another way to get the value of cell which contains a particular string
WpfCell myCell = myWpfTable.Cells
.FirstOrDefault(c => c.Value.Contains("StringName"));
var myTextControl = myCell != null ? myCell.Value : null;
If the text is nested deep in the cell, then you can do this. Just like what you were doing with the table
// Find cell which contains the particular string, let say it "myCell"
WpfText mytext = new WpfText(myCell);
mytext.SearchProperties.Add(WpfText.PropertyNames.Name, "StringName", PropertyExpressionOperator.Contains);
if(mytext.Exist)
return myText;
else
retrun null;

Related

Why is my AutoComplete suggestion dropdown blank

I have a Xamarin form where I am trying to add a SyncFusion AutoComplete control. The data is a simple class with only three string fields (CUSTNMBR, CUSTNAME, ZIP). I want it to match on any of the fields and display the coresponding CUSTNMBR. Here it my line in Xaml:
<xForms:SfAutoComplete x:Name="customerAutoComplete" WidthRequest="120" BackgroundColor="White" />
In the form's code-behind constructor I call LoadCustomerData():
private async void LoadCustomerData()
{
customerAutoComplete.DataSource = await GetCustomerCodes();
customerAutoComplete.DisplayMemberPath = "CUSTNMBR";
customerAutoComplete.SelectedValuePath = "CUSTNMBR";
customerAutoComplete.SuggestionMode = SuggestionMode.Custom;
customerAutoComplete.Filter = FilterCustomers;
customerAutoComplete.AutoCompleteMode = AutoCompleteMode.Suggest;
customerAutoComplete.Watermark = "Zip Code, Customer ID, or Customer Name";
customerAutoComplete.MinimumPrefixCharacters = 3;
}
Here is my filter method.
private bool FilterCustomers(string search, object customer)
{
var text = customerAutoComplete.Text;
if (customer != null)
{
var myCustomer = (OrganizationSearchDto)customer;
if (myCustomer.CustName.Contains(text) || myCustomer.CustNmbr.Contains(text) ||
myCustomer.Zip.Contains(text))
{
return true;
}
}
return false;
}
The above code worked partially when I had customerAutoComplete.SuggestionMode = SuggestionMode.Contains but it did not match on the other two fields. Now it still runs, however nothing is shown in the dropdown list (its blank). Why is my dropdown blank? Any hints, suggestion or a hard shove in the right direction will be appreciated.
For anyone encountering this, tests to try:
Put a breakpoint on return true - is that breakpoint hit for the customer(s) you expect to be shown as suggestions?
Swap return true and return false, so it is true for all the OTHER customers - the opposite of what you want. See if it is still blank. If it is, then it isn't the filter - code elsewhere is interfering with display. Would need to show more code, or make a github containing a minimum repo that shows the problem.
[from OP] The issue was that property names on DisplayMemberPath are case sensitive, as are the filter checks.
The fix for the filter was to ignore case everywhere. E.g.
if (myCustomer.CustName.ToLower().Contains(text.ToLower()) || ...)
We have analyzed the reported query. We have achieved the requirement by using the following code snippet,
public bool ContainingSpaceFilter(string search, object item)
{
if (item != null)
{
var myCustomer = item as Employee;
if (**myCustomer.Name.ToUpper().Contains(search.ToUpper()**) || myCustomer.ID.Contains(search) ||
myCustomer.ZipCode.Contains(search))
{
return true;
}
}
return false;
}

Can't use Expand and Collapse methods or patterns on TreeItems using FlaUI in C#?

I'm trying to define a function that expands or collapses a given TreeItem by a target state using FlaUI for testing a program in C#.
I'm able to find the element, but I can't access any information or methods for expanding and collapsing TreeItem elements. I get the following error when trying to set the currentPattern variable. I was also not able to just run the Expand and Collapse methods on the TreeItem.
Error:
FlaUI.Core.Exceptions.PatternNotSupportedException: 'The requested pattern 'ExpandCollapse [#10005]' is not supported'
The function I have written is:
public TreeItem ToggleTreeNode(string inNodeName, ExpandCollapseState inTargetState, AutomationElement inParentNode = null)
{
TreeItem nodeElement = null; //TreeItem nodeElement = null;
if (inParentNode == null)
{
nodeElement = mSTGOCMainForm.FindFirstDescendant(cf => cf.ByName(inNodeName)).AsTreeItem();
}
else
{
nodeElement = inParentNode.FindFirstDescendant(cf => cf.ByName(inNodeName)).AsTreeItem();
}
// Collapse or Expand
var currentPattern = nodeElement.Patterns.ExpandCollapse.Pattern;
var currentState = currentPattern.ExpandCollapseState.Value;
if (inTargetState != currentState)
{
//Then do the operation
if (inTargetState == ExpandCollapseState.Collapsed)
{
nodeElement.Collapse();
}
else if (inTargetState == ExpandCollapseState.Expanded)
{
nodeElement.Expand();
}
}
return nodeElement;
}
I'm using FlaUI.Core and FlaUI.UIA2, version 3.2.0.
It seems that your TreeItems just don't support the UIA Collapse/Expand pattern. Either there is a nested or parent element that supports the pattern (check with any inspect tool) or you need to use keyboard or mouse to collapse/expand.
I had the same error when I found a button on my TreeItem. The button had an automation ID, but the TreeItem did not. To use Expand(), I had to find the button and define my variable as the Parent. Note the .Parent.AsTreeItem() at the end.
TreeItem DiagnosticLogsTreeItem => Window.FindFirstDescendant(By.ByAutomationId("PageLink_Diagnostic Logs")).Parent.AsTreeItem();

In relation to acumatica, is there a way to grab a variable from one table and use it as part of an id for another table?

The 'Customer' form has a variable called AcctReferenceNbr (Variable that I'm trying to grab shown in yellow) which takes a two-letter abbreviation of the customer name. I am currently editing the Projects form, and I want to use this abbreviation as part of the External Ref. Nbr.
The attached image End Result I'm trying to achieve shows what the end result should look like. The number from the QuoteID is appended to the abbreviation.
I am able to successfully grab the QuoteID as it is part of the Projects table, but I am currently unable to grab the AcctReference Nbr from the Customer table.
I have a RowSelected event on the QuoteID field, which is shown below:
namespace PX.Objects.PM
{
public class ProjectEntry_Extension : PXGraphExtension<ProjectEntry>
{
#region Event Handlers
protected void PMProject_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
PMProject row = (PMProject)e.Row;
if (row.ContractCD != null) {
PMProject item = PXSelectorAttribute.Select<PMProject.contractCD>(cache, row) as PMProject;
// The "UP" string is where the abbreviation is supposed to be,
// but I just added two letters to test if the appending works, which it does.
row.ExtRefNbr = "UP" + item.ContractCD;
}
}
#endregion
}
}
What I've tried so far:
Accessing the Customer table namespace to grab the value and pass it to the Projects form, which didn't work because it didn't accept the Customer type in the Projects form.
Adding a PXDefault attribute to the External Ref. Nbr which would try and grab the variable using SQL.
I'm a bit stuck on what else I can try. Any help would be appreciated :)
UPDATED
Below is how I went about trying to grab the AcctReferenceNbr value from the Customer table.
The reason why I tried using the PXSelectorAttribute method was that I added the AcctReferenceNbr as a column to the Quote ID selector (selector is shown in the link above called 'End Result I'm trying to achieve').
So I figured I could try and grab that value in the Customer namespace, as that is where the variable resides, and pass that up to the Project namespace above.
Then, I would call the public method below in the Project namespace to get the required abbreviation:
// instead of this
row.ExtRefNbr = "UP" + item.ContractCD;
// it would be this
row.ExtRefNbr = PX.Objects.AR.CustomerMaint_Extension.getAcctReferenceNbr(cache, e) + item.ContractCD;
namespace PX.Objects.AR
{
public class CustomerMaint_Extension : PXGraphExtension<CustomerMaint>
{
#region Event Handlers
public static string getAcctReferenceNbr(PXCache cache, PXRowSelectedEventArgs e)
{
BAccount row = (BAccount)e.Row;
BAccount item = PXSelectorAttribute.Select<BAccount.acctReferenceNbr>(cache, row) as BAccount;
return item.acctReferenceNbr;
}
}
#endregion
}
}
Is there a proper way to target the actual table?
try this. I haven't tested this but give it a go.
protected void PMProject_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
PMProject row = (PMProject)e.Row;
if (row.ContractCD != null && row.CustomerID != null)
{
BAccount ba = (BAccount )PXSelectorAttribute.Select<PMProject.customerID>(cache, row) ;
row.ExtRefNbr = ba.AcctReferenceNbr+ row.ContractCD;
}
}
you certainly don't need to extend the CustomerMaint graph.

Epplus - checking if named style already exists before adding it

I have a class Comparer, which defines the following:
// partial Comparer code
public class Comparer
{
private readonly Color colorWarning = Color.Red;
private readonly string SPREADSHEET_RED_WARNING_STYLE = "red warning style";
private OfficeOpenXml.Style.XmlAccess.ExcelNamedStyle redWarningStyle;
}
This class has a method prepareSpreadsheet:
private void prepareSpreadsheet()
{
// spreadsheet styles
redWarningStyle = spreadsheet.Workbook.Styles.CreateNamedStyle(SPREADSHEET_RED_WARNING_STYLE);
redWarningStyle.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
redWarningStyle.Style.Fill.BackgroundColor.SetColor(colorWarning);
redWarningStyle.Style.Font.Color.SetColor(Color.Black)
}
If the spreadsheet file already contains such a named style, an exception is thrown. Can Epplus programatically check if a certain named style already exists in the spreadsheet, and remove it if it does?
I have managed to get it working, but not sure if it is the best solution. It does not remove them, only adds if it finds a style with an existing name (cannot guarantee it has the same style):
// retrieve a list of styles from the spreadsheet
List<OfficeOpenXml.Style.XmlAccess.ExcelNamedStyleXml> spreadsheetNamedStyles = spreadsheet.Workbook.Styles.NamedStyles.ToList();
// check if it already exists before attempting to add it
if (spreadsheetNamedStyles.FirstOrDefault(namedStyle => namedStyle.Name.Equals(SPREADSHEET_RED_WARNING_STYLE)) == null)
{
redWarningStyle = spreadsheet.Workbook.Styles.CreateNamedStyle(SPREADSHEET_RED_WARNING_STYLE);
redWarningStyle.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
redWarningStyle.Style.Fill.BackgroundColor.SetColor(colorWarning);
redWarningStyle.Style.Font.Color.SetColor(Color.Black);
}

Select a property in the property grid

I am using a PropertyGrid to display the content of an object to the user.
This PropertyGrid is synchronized with an Excel sheet, assuming a cell value match a property value.
As the user selects a property in the PropertyGrid, the application highlights the corresponding cell in the Excel sheet which is opened beside. I can do this using the SelectedGridItemChanged event.
Now, I want to have a property selected in my PropertyGrid when the user selects a cell in the Excel sheet.
void myWorkbook_SheetSelectionChangeEvent(NetOffice.COMObject Sh, Excel.Range Target)
{
if (eventMask > 0)
return;
try
{
eventMask++;
this.Invoke(new Action(() =>
{
propertyGrid1.SelectedGridItem = ... // ?
}
}
finally
{
eventMask--;
}
}
I noticed that the SelectedGridItem can be written to.
Unfortunately I do not find a way to access the GridItems collection of my PropertyGrid so I can look for the right GridItem and select it.
How can I do this?
You can get all the GridItems from the root. I am using the below code to retrieve all the griditems within a property grid
private GridItem Root
{
get
{
GridItem aRoot = myPropertyGrid.SelectedGridItem;
do
{
aRoot = aRoot.Parent ?? aRoot;
} while (aRoot.Parent != null);
return aRoot;
}
}
and pass the root to the below method
private IList<GridItem> GetAllChildGridItems(GridItem theParent)
{
List<GridItem> aGridItems = new List<GridItem>();
foreach (GridItem aItem in theParent.GridItems)
{
aGridItems.Add(aItem);
if (aItem.GridItems.Count > 0)
{
aGridItems.AddRange(GetAllChildGridItems(aItem));
}
}
return aGridItems;
}
I came up against this quite a bit a project so I wrote an extension method for it:
if (!SetupManagerSettings.BootStrapperLocation.IsFile()) // Just another extension method to check if its a file
{
settingsToolStripMenuItem.Checked = true; // Event handler OnChecked ensures the settings panel is unhidden
settingsPropertyGrid.ActivateControl();
settingsPropertyGrid.SelectPropertyGridItemByName("BootStrapperLocation"); // Here is the extension method
return false;
}
Here is the extension method with a private, supporting method for traversing the hierarchy of objects (if this applies to your object model):
public static bool SelectPropertyGridItemByName(this PropertyGrid propertyGrid, string propertyName)
{
MethodInfo getPropEntriesMethod = propertyGrid.GetType().GetMethod("GetPropEntries", BindingFlags.NonPublic | BindingFlags.Instance);
Debug.Assert(getPropEntriesMethod != null, #"GetPropEntries by reflection is still valid in .NET 4.6.1 ");
GridItemCollection gridItemCollection = (GridItemCollection)getPropEntriesMethod.Invoke(propertyGrid, null);
GridItem gridItem = TraverseGridItems(gridItemCollection, propertyName);
if (gridItem == null)
{
return false;
}
propertyGrid.SelectedGridItem = gridItem;
return true;
}
private static GridItem TraverseGridItems(IEnumerable parentGridItemCollection, string propertyName)
{
foreach (GridItem gridItem in parentGridItemCollection)
{
if (gridItem.Label != null && gridItem.Label.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
{
return gridItem;
}
if (gridItem.GridItems == null)
{
continue;
}
GridItem childGridItem = TraverseGridItems(gridItem.GridItems, propertyName);
if (childGridItem != null)
{
return childGridItem;
}
}
return null;
}
I looked at the above options and did not like them that much, I altered it a bit found that this works well for me
bool TryFindGridItem(PropertyGrid grid, string propertyName, out GridItem discover)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentException("You need to provide a property name", nameof(propertyName));
}
discover = null;
var root = pgTrainResult.SelectedGridItem;
while (root.Parent != null)
root = root.Parent;
foreach (GridItem item in root.GridItems)
{
//let's not find the category labels
if (item.GridItemType!=GridItemType.Category)
{
if (match(item, propertyName))
{
discover= item;
return true;
}
}
//loop over sub items in case the property is a group
foreach (GridItem child in item.GridItems)
{
if (match(child, propertyName))
{
discover= child;
return true;
}
}
//match based on the property name or the DisplayName if set by the user
static bool match(GridItem item, string name)
=> item.PropertyDescriptor.Name.Equals(name, StringComparison.Ordinal) || item.Label.Equals(name, StringComparison.Ordinal);
}
return false;
}
it uses a local method named match, if you're version of C# does not allow it then just put it external to the method and perhaps give it a better name.
Match looks at the DisplayName as well as the property name and returns true if either is a match, this might not be what you would like so then update that.
In my usecase I need to select the property based on a value the user can select so call the above method like this:
if (TryFindGridItem(pgMain, propertyName, out GridItem gridItem))
{
gridItem.Select();
}
It could theoretically never not find it unless the user selects a string that is not proper this way the if can be updated using an else .. I rather keep it safe then have a null-pointer exception if I can't find the name specified.
Also I am not going into sub classes and lists of classes as my use case doesn't need that, if yours does than perhaps make recursive calls in the GridItem.
Small note, replace GridItem with var and the code brakes as at compile time the IDE has no clue what a GridItemCollection returns…

Categories