I have a requirement to be able to auto-tab from one control to the "next control" in a SL3 app. For example, a TextBox is limited to 3 characters - on typing the 3rd character the focus should automatically move to the next control on the form (my actual usage is slightly different but that example suffices).
However, as SL automatically determines the tab sequence there doesn't seem to be a way of doing this apart from reverse engineering/duplicating Silverlight's logic to figure out which control in the visual tree should be the next control to gain focus.
Has anybody implemented this already?
I was looking for a fairly generalised solution - but I've been able to make do with something fairly specific - basically it uses the VisualTreeHelper to find children with the same parent as the control that I want to tab next to, and sets focus to that.
It's a more palatable solution than having to go through all my controls (and this is for a fairly large LOB application) and configure the "next" control for each of them.
Here's my code, in case it helps somebody else. (VisualTreeeHelperUtil is a class of my own that adds some utility functions to VisualTreeHelper)
public static void TabNext(DependencyObject parentElement, Control fromControl)
{
var children = VisualTreeHelperUtil.FindChildren<Control>(parentElement).
Where(c => c.IsEnabled && c.IsTabStop && c.Visibility == Visibility.Visible).
ToList();
if (children.Contains(fromControl))
{
var thisIndex = children.IndexOf(fromControl);
var targetIndex = thisIndex + 1;
if (children.Count > targetIndex)
{
var targetChild = children[targetIndex];
fromControl.Dispatcher.BeginInvoke(() =>
{
targetChild.Focus();
var txt = targetChild as TextBox;
if (txt != null)
{
txt.SelectAll();
}
});
}
}
}
If you're looking for a generalized solution, and are OK basing it on the visual tree ordering (as opposed to arranged layout), I imagine it would not be so bad. Haven't heard of it being done, though.
Most of the phone number or credit card entry forms I've seen with this behavior honestly just hard-code the next field in the proper change handler when the right # of characters have been entered.
Since it sounds like your auto-focusing solution (for 3 chars) is going to already require some sort of event hookup, monitoring the TextChanged event, couldn't you just go ahead and either 1) hard-code a Focus() to the next form element that you know, 2) use the Tag property to store the name of the control that you'd like to focus next, then do a FindName + Focus on that, or 3) do some sort of VisualTreeHelper (or logical tree search through peers)?
Related
I am testing a WPF application and am not privy to it's exact workings but I am finding many instances where I need to find if a control is shown. All the traditional answers on this on Stack Overflow and MS forums etc say to use one of the following ...
IsVisible,
Exists,
TryGetClickablePoint,
State (e.g. OffScreen
The problem is that for this system, many controls return true for all of those even when the control cannot be seen! They also return a point with co-ordinates (-1, -1, -1, -1) whether the control is visible or not.
The only thing I have had any success with is using a try catch finally. I try to click on the control and if that fails, I go in to the catch block. That takes 60 seconds to time out though and I am getting intermittent issues with tests that run 9 times out of 10. Maybe the constant use of try catch is causing performance issues.
Is there an approach that actually works when all the standard approaches fail? I have noticed lots of other people asking these question are also testing WPF. Is there something WPF developers are doing to hide controls that makes CodedUI think they are still present and visible etc. Are they just behind something?
Many thanks in advance.
The solution was two-fold. Firstly I had to find the element and this was not working properly with my recorded steps. The element was buried too deeply in the system under test which is WPF (XAML). Secondly I had to prove I had found the element and for this I can't use TryGetClickablePoint, Exists, Top or Width. None of them seemed to work properly at all for my element. I had to use State.
public void Assert_MyElementShown()
{
#region Variable Declarations
WpfCustom uISurfaceCustom = this.UISysUnderTestClientShWindow.UIItemCustom1.UISurfaceCustom;
WpfCustom uIYAxisLabelsCustom = new WpfCustom();
#endregion
//Find the Element using it's Container and SearchProperties
uIYAxisLabelsCustom.Container = uISurfaceCustom;
uIYAxisLabelsCustom.SearchProperties[WpfControl.PropertyNames.ClassName] = "Uia.AxisLabelControl";
uIYAxisLabelsCustom.SearchProperties[WpfControl.PropertyNames.AutomationId] = "YAxisLabels";
//Use the State to find if it's on screen or not
var state = uIYAxisLabelsCustom.State;
if (state == Microsoft.VisualStudio.TestTools.UITest.Extension.ControlStates.Default)
{
//Element is visible, do stuff here!
}
else if (state == Microsoft.VisualStudio.TestTools.UITest.Extension.ControlStates.Offscreen)
{
//The control may exist, it may have location on screen and may even
//appear to be clickable according to coded ui framework but is is NOT
//shown on the screen.
}
}
You can try this approach for your application..if control properties are showing true for viable than we can go for height and width.Means if control is not visible in UI and but still all properties are showing true than check control height and width must be in -ve number.Than we can keep a assertion like
If control.height<0
Not visible in UI
I am trying to design a simple Coded UI tests that access the two following functions. Currently they are written with the CUITe framework but I see the same issue when using Coded UI code.
I have a test case which first calls GLJEEnterDescription, then calls GLJEEnterNotes. When the test runs, GLJEEnterDescription is manipulated and BOTH strings passed into the functions are entered into it. Nothing is ever entered into GLJEEnterNotes. I have checked and rechecked and the properties for the controls are correct.
The only difference between the two controls is GLJEEnterDescription is a standard single line text box and GLJEEnterNotes is a custom (derived from standard) multiline text box. Any thoughts on why I would not be able to access and use the GLJEEnterNotes text box but not the GLJEEnterDescription text box? Below is my functions called from the Coded UI tests:
public void GLJEEnterDescription(string JEDescription)
{
akwindow.Find<WinEdit>(By.ControlName("txtJEDescription")).Text = JEDescription;
}
public void GLJEEnterNotes(string JENotes)
{
akwindow.Find<WinEdit>(By.ControlName("txtMultiJENotes")).Text = JENotes;
}
You most likely need to specify more search criteria for those controls.
Ideally, I would recommend trying to use control ID's or even AutomationID's for your controls. These are the HIGHEST priority for the search algorithm
Like in This Post
var textBox = new WinEdit(yourAppWindow);
textBox.SearchProperties.Add(WinEdit.PropertyNames.Name, "txtMultiJENotes");
textBox.SearchProperties.Add(WinEdit.PropertyNames.LineCount, "1");
else you might have to use FindMatchingControls and cycle each control for the right things you want.
I have a fairly standard requirement — I need to be able to open a dialog where user can change values in data-bound fields, and then choose to click OK or Cancel, where clicking Cancel reverts the changes.
I've looked at IEditableCollectionView, IEditableObject and BindingGroups, but they all seem to be meant for editing a single item at a time. My program provides a collection of objects in a list, user selects an item from the list and edits it using SelectedItem-bound TextBoxes. Meaning that any number of items may be edited, including adding and removing them from the list, and all of those changes need to be reverted if he presses cancel.
At first I was simply making object backups through deep-copy (serialization) and restoring them on cancel, but now the objects must contain references to other, shared objects, making this approach problematic.
What's the best way to approach such a scenario without manually copying objects and/or values back and forth?
In this case the DataTable class would work Perfectly. It can save changes, go back (step by step) or revert all changes and many other features.
DataTable class has a nested feature that goes well with XML.
In case you're willing to save in a database then take a look at EntityFramework
After more thought on the matter, I have concluded that the best way, at least for small-scale implementation, is to write a "by value deep copy" method that copies values of objects fields and properties without replacing the objects themselves (so that any references to the edited objects remain intact even when data is restored).
For this purpose I have written the following extension method:
public static void CopyDataTo(this Object source, Object target) {
// Recurse into lists
if (source is IList) {
var a = 0;
foreach (var item in (IList)source) {
if (a >= ((IList)target).Count) {
var type = item.GetType();
var assembly = Assembly.GetAssembly(type);
var newItem = assembly.CreateInstance(type.FullName);
((IList)target).Add(newItem);
}
item.CopyDataTo(((IList)target)[a]);
a++;
}
while (a < ((IList)target).Count) {
((IList)target).RemoveAt(a);
}
}
// Copy over fields
foreach (var field in source.GetType().GetFields())
field.SetValue(target, field.GetValue(source));
// Copy properties
foreach (var property in source.GetType().GetProperties().Where(
property => property.CanWrite && !property.GetMethod.GetParameters().Any()))
{
property.SetValue(target, property.GetValue(source));
}
}
It's no silver bullet: it only works on objects of the same type, list items have to have a parametrless constructor and there is no way to control recursion depth. In addition, I haven't yet had a chance to test in any long-term or more complex scenarios, but so far it does what it should (copies values between objects) and can be used for a simple backup/restore scenario:
var backup = new TypeOfVariableToEdit();
data.CopyDataTo(backup);
var clickedOK = RunDataEditor(data);
if (!clickedOK)
backup.CopyDataTo(data);
The best approach is not to:
if you need these items, get a fresh copy of them from the database or whatever data storage, allow the user to make changes, and if they press cancel, just discard the changes. If they press save, save the data to the storage and then refresh your existing screens or whatever.
This drives me crazy:
i'm using a datagridview (bound to a dataset) and textboxes also bound to the dataset (which means that everything typed into the textboxes is reflected in the datagridview). The datagridview is actually used only to show up the values (user is NOT able to edit anything in the cells).
So: when the user pushes the add new record button (automatically created by visual studio using binding navigator etc. ) i would like to programmatically select this new line (remember: user has not the ability to select with the mouse) and insert some values.
I tried to use the
DefaultValuesNeeded
event, but this is firing only if the user selects the row with the asterisk indicator (what the user is not allowed to do).
How can i simulate that behavior? Should i take another approach?
Thanks in advance!!
Was searching for alternatives to how I currently do this and just wanted to let you know about a couple ways I know of. Both of these will set defaults when using the * (new row) or using a BindingNavigator.
private void CityBindingSource_AddingNew(object sender,
AddingNewEventArgs e)
{
// Simply set the default value to the next viable ID,
// will automatically set the value when needed.
locationDataSet.CITY.CITY_IDColumn.DefaultValue =
CityTableAdapter.GetNewID(); // ++maxID;
}
Or
private void CityDataGridView_DefaultValuesNeeded(object sender,
DataGridViewRowEventArgs e)
{
// Gets called when datagridview's */NewRow is entered.
e.Row.Cells[0].Value = CityTableAdapter.GetNewID(); // ++maxID;
}
private void bindingNavigatorAddNewItem_Click(object sender,
EventArgs e)
{
// BindingNavigator button's AddNewItem automatic tie-
// in to bindingSource's AddNew() is removed.
// This sets the current cell to the NewRow's cell to trigger
// DefaultValuesNeeded event.
CityDataGridView.CurrentCell =
CityDataGridView.Rows[CityDataGridView.NewRowIndex].Cells[1];
}
I was really hoping to use this:
private void CityBindingSource_AddingNew(object sender,
AddingNewEventArgs e)
{
// Cast your strongly-typed
// bindingsource List to a DataView and add a new DataRowView
DataRowView rowView =
((DataView)CityBindingSource.List).AddNew();
var newRow = (LocationDTSDataSet.CITYRow)rowView.Row;
newRow.CITY_ID = CityTableAdapter.GetNewID();
newRow.CMMT_TXT = "Whatever defaults I want";
e.NewObject = rowView;
}
But when adding using the BindingNavigator or any code based bindingSource.AddNew() it results in just a blank row. Hopefully someone finds a better way, but this is working for me so far.
Revised:
How about the Datatable's TableNewRow event, then? If that gets fired when the BindingNavigator creates a new row, then that's an opportunity to populate the object.
I'm in the process of developing a Silverlight custom control that "hosts" a Flash instance. The way that you do this, of course, is to position the HTML element in question over your Silverlight instance, as described, say, here. The problem I'm running into is that when I use the GeneralTransform.Transform() method to get the absolute coordinates of my control, so that I can position the HTML element correctly, the Point object that's returned always has .X=0 and .Y=0.
public void InitControl(string id)
{
GeneralTransform gt = this.TransformToVisual(Application.Current.RootVisual);
Point localPos = gt.Transform(new Point(_htmlControlLeft, _htmlControlTop));
// Create the containing DIV tag.
HtmlDocument doc = HtmlPage.Document;
divHost = doc.CreateElement("div");
divHost.SetAttribute("id", System.Guid.NewGuid().ToString());
divHost.SetStyleAttribute("position", "absolute");
divHost.SetStyleAttribute("left", localPos.X.ToString() + "px"); // always 0
divHost.SetStyleAttribute("top", localPos.Y.ToString() + "px"); // always 0
Debug.WriteLine("x,y=" + localPos.X.ToString() + "," + localPos.Y.ToString());
divHost.SetStyleAttribute("width", Width.ToString() + "px");
divHost.SetStyleAttribute("height", Height.ToString() + "px");
divHost.SetStyleAttribute("z-index", _htmlZIndex.ToString());
}
I haven't been able to find great documentation on the GeneralTransform.Transform() method, but it seems like I'm using it correctly. Any thoughts on what I'm doing wrong?
Edit 4/28/09: I still haven't found an answer, but I AM using the Transform() method properly. The problem only shows up if I call the InitControl() method during the Page.Loaded event. If I wait a few seconds, then call it (say) from a Button_Click event, the same code works fine. According to the SL docs, everything should be laid out appropriately by the time the Page.Loaded event fires, but clearly that's not the case.
I should also note that every once in a while, the code above works perfectly fine, even when it's called from the Page.Loaded event. Huh.
My workaround so far, for what it's worth, is hide the control for a couple seconds after the form loads, then show it. It's an ugly hack, but unless anyone has any better ideas...?
This is due to RootVisual having a 0,0 x/y axis. Try using the next element within the tree, typically it's layoutRoot by default (assuming you haven't changed its name etc). That or utilise FindName() to ensure you have a direct reference to the said layer you are wanting to position the iFrame over.
Scott Barnes / Rich Platforms Product Manager / Microsoft.