Question
Can anyone please explain (preferrably with a code example) how the AutomationProperties.Name property is used programmatically and declaratively with XAML?
Explanation
I understand that the Coded UI Builder in Visual Studio 2010, for instance, takes a Window's name as SearchProperty.
Since my Window's name changes, I would like to have a constant SearchProperty that my Coded UI Tests can rely on.
In the code example below, I don't want the window title to be hard-coded as "Properties of Pipe 1" since that changes.
Code example
[GeneratedCode("Coded UITest Builder", "10.0.30319.1")]
public class UIListViewPropertiesTable1 : WpfTable
{
public UIListViewPropertiesTable1(UITestControl searchLimitContainer) :
base(searchLimitContainer)
{
#region Search Criteria
this.SearchProperties[WpfTable.PropertyNames.AutomationId] = "listViewProperties";
this.WindowTitles.Add("Properties of Pipe 1");
#endregion
}
#region Properties
public WpfText NameOfComponent
{
get
{
if ((this.mNameOfComponent == null))
{
this.mNameOfComponent = new WpfText(this);
#region Search Criteria
this.mNameOfComponent.SearchProperties[WpfText.PropertyNames.Name] = "Pipe 1";
this.mNameOfComponent.WindowTitles.Add("Properties of Pipe 1");
#endregion
}
return this.mNameOfComponent;
}
}
#endregion
#region Fields
private WpfText mNameOfComponent;
#endregion
}
Links
Here is an example: How To: Get automation working properly on data bound WPF list or combo box. I wasn't able to adapt it for a Window.
You can change the attached property AutomationProperties.Name either in XAML using:
AutomationProperties.Name = "new name"
or in code using:
Button.SetValue(AutomationProperties.NameProperty, "new value");
or
AutomationProperties.SetName(Button, "new value");
You can pass the Window Title as parameter to its parent and set this parameter while initializing.
I do this way and works fine.
There is a way to work around that but its a bit ugly.
We will be using the fact that the the proprty that contains the reference to the window is cached and not looked up every time.
uimap class is a partial class and you can have code in the uimap.cs file that stil counts as a part of the uimap class.
Add a method there that accepts as a parameter the window title, and performs the search, and that puts the found window into the UIListViewPropertiesTable1 property of the generated code.
Related
I have asked this question before and it got flagged for not being detailed enough so I thought I would rephrase it and better detail my problem.
Summarise the problem:
First of all, I was following this tutorial 'Using SQLite in C# - Building Simple, Powerful, Portable Databases for Your Application' (https://www.youtube.com/watch?v=ayp3tHEkRc0). The creator of this tutorial had this form:
In the form you could add a first name and a listname and it shows up in the List People. This was his code-behind:
I followed the tutorial and I decided I would replicate this but instead of 'Person' it will display the user's statistics:
I want to be able to display the SQLite table content inside a listbox. But the problem is that in WPF there is no such thing as 'DataSource' or 'DisplayMember'. This is my code so far:
public partial class databasetestform : Window
{
List<UserStatistics> userstatistics = new List<UserStatistics>();
public databasetestform()
{
InitializeComponent();
}
private void LoadStatistics_List()
{
userstatistics = SQLiteDataAccess.LoadStatistics();
WriteUpStatisticsList();
}
public void WriteUpStatisticsList()
{
// Unsure what to do here
}
private void Load_button_Click(object sender, RoutedEventArgs e)
{
LoadStatistics_List();
UserStatistics s = new UserStatistics();
int result1;
int result2;
if(int.TryParse(AVGtxtbox.Text, out result1))
{
s.GamesPlayed = result1;
SQLiteDataAccess.SaveStatistics(s);
}
if (int.TryParse(AVGtxtbox.Text, out result2))
{
s.GamesPlayed_ScoreAverage = result2;
SQLiteDataAccess.SaveStatistics(s);
}
AVGtxtbox.Text = "";
PLYDtxtbox.Text = "";
}
}
Describe what you've tried
I've tried for many hours to find a work-around, I tried to use 'ItemsSource' instead of DataSource but I still had no work-around for 'DisplayMember'.
Hope I can get a solution to this.
Thanks,
Seems like you're on the right track.
ItemsSource is the correct property to use for your data source. It accepts any IEnumerable so your List<T> should work just fine.
Instead of DisplayMember, I think you're looking for DisplayMemberPath. You can set this to the name of a property declared on your UserStatistics object and the ListBoxItems will display the value of that property.
The C# would be as simple as:
MyListBox.ItemsSource = userstatistics;
DisplayMemberPath would usually be set on the ListBox tag in the XAML:
<ListBox Name="MyListBox" DisplayMemberPath="SomePropertyName"/>
The above gives you the simple answer, which does work, but there are a couple other concepts you should be aware of and maybe look into later on:
In WPF, one generally tries to avoid giving a UI element an explicit name and referencing it in code. The goal behind this is to try and separate the functional components of the program from the visual interface.
To acheive this, the visual components (declared in XAML) are usually connected to the functional code (written in C# or VB.NET) using data binding.
This isn't a completely strict rule, however, and I've broken it on a few occasions when it made scense, or when referencing a UI element by name was just far, far easier.
While DisplayMemberPath works for displaying the value of a single property, sometimes you need a more complex display of items. This can be accomplished using data templates and the ItemTemplate property. It requires knowledge and use of data binding.
I'm attempting to use CodedUI in a code-first approach (page object pattern) for a WPF UI. I'm able to navigate to a specific list item within a groupbox within a tab on the main window. Each list item contains a checkbox along with some other content; I'd like to automate clicking the checkbox, but I'm getting an exception with the message 'Search may have failed at " TabList as it may have virtualized children...'
The only thing is that I'm setting the containing WpfListItem as the parent for the WpfCheckBox per the following code:
public class ConfigItem
{
private readonly WpfListItem _instance;
public WecoConfigItem([NotNull] WpfListItem instance)
{
if (instance == null) throw new ArgumentNullException("instance");
_instance = instance;
}
public ConfigItem SelectConfiguration()
{
var checkBox = new WpfCheckBox(_instance);
_instance.DrawHighlight();
checkBox.SearchProperties.Add(WpfCheckBox.PropertyNames.AutomationId, "cbIsSelected");
Mouse.Click(checkBox);
return this;
}
}
The failure occurs in the SelectConfiguration method. During test execution, the corresponding ListItem is highlighted, but then in the html output the recorded image highlights the application. So, some questions:
Why is the search starting from the application window when I'm providing the WpfListItem as the parent in the constructor?
Am I doing something that is causing the discrepancy between the DrawHighlight() output and the HTML output?
How do I constrain the search to begin with the WpfListItem parent object, for a code-first page object pattern approach?
EDIT: The search is actually beginning from the top-level application, not the tab - I was looking at a stale HTML log. Problem statement is still essentially the same.
The moment you call DrawHighlight() you are initiating a search. The next statement then gives additional search criteria and then you access the control again (Mouse.click()), but then you are reading from cache. I assume that you either need to disable the cache by setting the SearchOptions to AlwaysSearch or add the criteria before you call DrawHighlight().
I have this class:
public class MyProps
{
public MyProps()
{
}
protected string myVar;
public string MyProperty
{
get { return myVar; }
set { myVar = value; }
}
protected int myOtherVar;
public int MyOtherProperty
{
get { return myOtherVar; }
set { myOtherVar = value; }
}
}
That I want to add to my Form, so when I inherit from it I will be able to fill the properties in the MyPropsX property.
I have this code in my form:
protected MyProps propsX = new MyProps();
[TypeConverter(typeof(ExpandableObjectConverter))]
public MyProps MyPropsX
{
get
{
return propsX;
}
set
{
propsX = value;
}
}
Now, the properties MyProperty and MyOtherProperty are nicely shown in the Properties Window, and I can set their values directly there.
But when I close my form and I open it again, all my changes are lost, the properties being reset to show zero and an empty string.
What am I missing?
Should I inherit my MyProps class from certain special class or interfase?
Or some special attribute?
This is a little bit much for a comment and maybe your solution, so i'm answering to your comment with an answer instead with another comment:
With does not happen when I put properties directly on a form you mean, you are using the designer to set some property of the form. These will be written into the MyForm.designer.cs file. When you go into the code of your class you'll find within the constructor a method InitializeComponent(). Set the cursor on it an press F12. Here you can see what the designer has written into all the properties. You should respect the comment above the mentioned method and not start to modify the code with the code editor unless you really have understand how and when the designer will read and write code here (which is another chapter i can explain if needed). Otherwise it will happen that trying to opening your form with the designer after the code change will lead to an error message or code loss.
If you like to set some default value also, you should go back into the constructor and add the needed initialization code below the InitializeComponent() function and everything should work as expected.
Update
As you wrote in your comment you already know how the Designer interacts with the *.designer.cs file. So i really can't understand your concrete problem but maybe one of these articles can give you a more insight about how Microsoft wrote their components:
Make Your Components Really RAD with Visual Studio .NET Property Browser
Components in Visual Studio
This is very normal, since each time you are closing the form and opening it again you are having a new instance from the form MyPropsX, so the best way would be to save your properties in any kind of a database (sql, access, textfiles,...)
Does anyone know of a good component (C# WinForms) which would allow creating an options (settings) form, given a custom class with a bunch of properties? I am not looking for something shiny, but something merely better than a property grid. I can easily take care of the visual part, but I simply don't want to lose time doing reflection to add and bind controls if it already exists.
I am pretty sure I've seen a Visual Studio options-like form somewhere before, which was created dynamically (with some attributes attached to the properties of the class, to allow grouping and additional info).
[Edit] For example, I might have an options class:
public class Options : SerializableOptions<Options>
{
[Category("General")]
[Name("User name")]
[Description("Some text")]
public string Username { get; set; }
[Category("General")]
[Name("Log in automatically")]
public bool LogInAutomatically { get; set; }
[Category("Advanced")]
// ConnectionType is enum
public ConnectionType ConnectionType { get; set; }
// ...
}
After passing it to this form, it would create two panels ("General" and "Advanced"), with a CheckBox and a TextBox on the first panel, and one ComboBox (with all available enums) on the second panel.
If there isn't such a control, what do you guys use? Manually add, populate, format and bind controls for each option?
I'm not aware of any controls that allow you to do this, but it isn't difficult to do yourself. The easiest way is to create the dialog shell, a user control which acts as the base class for the options "panels", one (or more) attribute to control the name and grouping information, and an interface (which the user control implements).
Each of your custom options panels derives from the user control and overrides some sort of Initialize() and Save() method (provided by the user control). It also provides your attribute (or attributes) that determine the name/grouping information.
In the dialog shell, reflectively inspect all public types from your assembly (or all loaded assemblies) looking for types that implement your interface. As you find a type, get the attributes to determine where to place it in your grouping (easiest thing here is to use a tree view), call Activator.CreateInstance to create an instance of the user control and store it in the Tag property. When the user clicks on an entry in the grouping (a tree node), get the Tag and set the panel which contains the user control to the object in the Tag property. Finally, when the user clicks "OK" on the dialog, loop through the tree nodes, get the Tag property and call the Save method.
Update:
Another option would be to use a property grid control. It doesn't have a "pretty" UI look to it, but it is very functional, already supports grouping by a category attribute, and allows a great deal of flexibility. You could go with a single property grid that shows all of the options, or go with a "hybrid" approach with a tree view that groups by major functions (plugin, capability, etc.), probably based on the type. When the user clicks that node, give the property grid the object instance. The only drawback to this approach is that when changes are made to the property grid values they are "live" in that the underlying property is immediately changed, which means there is no concept of "Cancel" short of saving a copy of each value that could change and performing some type of "reset" yourself.
I don't know if such a control exists, but writing the required reflection code is really not that hard. E.g. something like this:
// the class for which to create an UI
public class MyClass
{
public string Text { get; set; }
public int ID { get; set; }
}
...
// basic reflection code to build the UI for an object
var obj = new MyClass() { Text="some text", ID=3};
foreach (var pi in obj.GetType().GetProperties())
{
var name = pi.Name;
var type = pi.PropertyType;
var value = pi.GetValue(obj, null);
//now setup the UI control for this property and display the value
}
I accidentally found something similar to this, I remebered that I had this problem a while ago and thought I should share it.
Here is a simple example: http://blog.denouter.net/2008/08/simple-reflection-form.html. It uses reflection to create several controls based on object's properties.
I have subclassed Form to include some extra functionality, which boils down to a List<Image> which displays in a set of predefined spots on the form. I have the following:
public class ButtonForm : Form
{
public class TitleButton
{
public TitleButton() { /* does stuff here */ }
// there's other stuff too, just thought I should point out there's
// a default constructor.
}
private List<TitleButton> _buttons = new List<TitleButton>();
public List<TitleButton> TitleButtons
{
get { return _buttons; }
set { _buttons = value; }
}
// Other stuff here
}
Then my actual form that I want to use is a subclass of ButtonForm instead of Form. This all works great, Designer even picks up the new property and shows it up on the property list. I thought this would be great! It showed the collection, I could add the buttons into there and away I would go. So I opened the collection editor, added in all the objects, and lo and behold, there sitting in the designer was a picture perfect view of what I wanted.
This is where it starts to get ugly. For some reason or another, Designer refuses to actually generate code to create the objects and attach them to the collection, so while it looks great in Design mode, as soon as I compile and run it, it all disappears again and I'm back to square one. I'm at a total loss as to why this would happen; if the Designer can generate it well enough to get a picture perfect view of my form with the extra behaviour, why can't/won't it generate the code into the actual code file?
First of all you need to inherit your TitleButton class from Component so that the designer knows it is a component that can be created via designer generated code. Then you need to instruct the designer code generator to work on the contents of the collection and not the collection instance itself. So try the following...
public class TitleButton : Component
{
// ...
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<TitleButton> TitleButtons
{
// ...
}