C# System.Windows.Automation get element text - c#

I am trying to get text/labels from application controls with Automation in C#.
So far I am able to obtain AutomationElement tree of application (for example Notepad) with this function:
private void WalkControlElements(AutomationElement rootElement, TreeNode treeNode)
{
AutomationElement elementNode = TreeWalker.ContentViewWalker.GetFirstChild(rootElement);;
while (elementNode != null)
{
TreeNode childTreeNode = treeNode.Nodes.Add(elementNode.Current.ControlType.LocalizedControlType);
// here I want to get text from 'elementNode'
WalkControlElements(elementNode, childTreeNode);
elementNode = TreeWalker.ControlViewWalker.GetNextSibling(elementNode);
}
}
I tried to follow this article http://msdn.microsoft.com/en-us/library/ms788751(v=vs.110).aspx but it only can get text attributes as font name, font weight and so on.
Could anybody point me to the right procedure how to get element text with Automation?

That sample is showing you how to get text attributes, i.e. information about the display of the text in the UI, not the actual displayed text. Getting all the actual displayed text for a general application is more difficult that it might first appear.
It is made difficult by the fact that there are several ways get text and there is inconsistent support by applications and controls. There are two patterns that are of some use, ValuePattern and TextPattern. By convention the Name property contains text displayed to the user however adherence to this is inconsistent. Below is a helper method that I've used in UI automation for testing. It basically goes through those patterns checking the control for support and falls back to the Name.
public static class AutomationExtensions
{
public static string GetText(this AutomationElement element)
{
object patternObj;
if (element.TryGetCurrentPattern(ValuePattern.Pattern, out patternObj))
{
var valuePattern = (ValuePattern)patternObj;
return valuePattern.Current.Value;
}
else if (element.TryGetCurrentPattern(TextPattern.Pattern, out patternObj))
{
var textPattern = (TextPattern)patternObj;
return textPattern.DocumentRange.GetText(-1).TrimEnd('\r'); // often there is an extra '\r' hanging off the end.
}
else
{
return element.Current.Name;
}
}
}
This takes care of getting the text out of simple controls like labels, textboxes (both vanilla textbox and richtextbox), and buttons. Controls like listboxes and comboboxes (esp. in WPF) can be tricker because their items can be virtualized so they may not exist in the automation tree until the user interacts with them. You may want to filter and call this method only on certain UI Automation control types like Edit, Text, and Document which you know contain text.

Mike Zboray answer works fine. In case you have access to pattern-Matching, here is the same (condensed) code :
public static class AutomationExtensions
{
public static string GetText(this AutomationElement element)
=> element.TryGetCurrentPattern(ValuePattern.Pattern, out object patternValue) ? ((ValuePattern)patternValue).Current.Value
: element.TryGetCurrentPattern(TextPattern.Pattern, out object patternText) ? ((TextPattern)patternText).DocumentRange.GetText(-1).TrimEnd('\r') // often there is an extra '\r' hanging off the end.
: element.Current.Name;
}

Related

How to access the Items of a User Control

I have a C# Form that prints multiple instances of a User Control. Let's say that the form prints 5 instances of the User Control (Please see the link attached). How can I store/save the data inputted in all User Controls? Thanks
Here is the screenshot of the C# Form:
You'll have to store the User Controls when you instantiate them in a List or something.
You could have a class like this:
class SomeUC : UserControl
{
public SomeUC()
{
}
// A public method.
public string GetData()
{
return textBox1.Text;
}
}
Where textBox1 is the Name of a TextBox in your SomeUC
And then inside your main or something.
// Instantiate a List that will hold your UserControls, this has to be outside all methods
List<SomeUC> list = new List<SomeUC>();
// And now when you want to build your UCs
// Instantiate your UserControl
SomeUC uc1 = new SomeUC();
// Store your UserControl in a List or something (Can't help you with that)
list.Add(uc1);
Add as much as you want.
A List is not the only way you can do that, but since you don't know how many UserControls you're going to build beforehand, it makes since to use a List.
And then you can access them from the list by their index.
SomeUC uc1 = list[0];
string data = uc1.GetData();
This is an example of accessing one control (the TextBox) in your SomeUC. For other classes (such as the ComboBox) the interaction is different. Meaning you won't have a Text property in the ComboBox. You'll have to figure out things like that on youself. A little research is what it takes. You can always come back if you couldn't find a solution for something.
You can create a property like this for each item in user control.
public string DG
{
get
{
return txtDG.Text;
}
set
{
txtDG.Text = value;
}
}
Then you can access the control value by using following line in your form.
supposed you have created a usercontrol MyControl and you have placed some object of this control in FlowLayoutPenal (pnlFLP).
To get value from control
string DG = ((MyControl)pnlFLP.Controls[0]).DG;
To set value in control
((MyControl)pnlFLP.Controls[0]).DG = "1";
Try this code for accessing user control in the page
Dim txtName As TextBox = TryCast(UserControlName.FindControl("txtName"), TextBox)

Keeping track of 40+ control values

I am in need of some guidance for the following design.
I have a tab control that contains various group boxes. Within the group box, there are specific controls that relates to that group box. For example:
Now whenever a change is made to any control in the group box, the value for the control needs to be tracked because at the end of the application run cycle, the control data will need to be saved to a file that contains that value. An example file is:
HOT_STANDBY_FEATURE_ENABLE [val from control here]
HEART_BEAT_DIGITAL_OUTPUT [val from control here]
....
A design that I have in mind has another that has just properties that the group box form sets whenever a ValueChanged event occurs on a control.
Example code:
class ConfigurationValues
{
public int HotStandbyValue { get; set; }
public int HeartBeatDigitalOutputValue { get; set; }
//...add all other controls here
}
The downside that I see to this is that there are 40 controls on that tab page, so I'd have to manually type each property. When the file needs to be generated at the end of the application run cycle, I have a method that gets the value of the control need.
Example:
private void GenerateFile()
{
string[] file =
"HOT_STANDBY_FEATURE_ENABLE " + ConfigurationTabSettings.HotStandbyValue;
}
Another design consideration I need to make is that whenever a user clicks "Open Configuration File", the values from the file from disk need to be loaded into the properties so the form can take that data on startup and populate the controls within the group boxes with their respective values.
Any suggestions on this design would be greatly appreciated. I know this is not the most efficent way to do this and am not the most experienced programmer, so any Google keywords I can search for would be great also.
You could xml serialise and xml deserialise your ConfigurationValues class rather than writing manual "generate file" and "read file" methods
http://support.microsoft.com/kb/815813
You'll need to bind the controls Text or Value properties to the properties in your ConfigurationValues class e.g.
ConfigurationValues cv = Repository.ReadConfigValues();
numPulseFilterDelay.DataBindings.Add("Value", cv, "PulseFilterDelay");
// Add the rest of your control bindings here
on the btnSave_Click() of your Form, end the current edit on the form and save the config values
void btnSave_Click(object sender, EventArgs e)
{
BindingContext[cv].EndCurrentEdit(); // Commits all values to the underlying data source
Repository.SaveConfigValues(cv);
}
In your repository class you'll need methods to Load() and Save() the data. You can put XmlSerialization code in here, or write your own format (depending on your requirements)
public class Repository
{
public static ConfigurationValues LoadConfigValues()
{
var cv = new ConfigurationValues();
string[] lines = File.ReadAllLines("values.cfg");
foreach (string cfg in lines)
{
string[] nameValue = cfg.Split(new char[] { ' ' } ); // To get label/value
if (nameValue[0] == "HOT_STANDBY_FEATURE_ENABLE")
cv.HotStandbyFeatureEnable = nameValue[1];
else if (nameValue[0] == "SOME_OTHER_PROPERTY")
cv.SomeOtherProperty = nameValue[2];
// Continue for all properties
}
return cv;
}
public static void SaveConfigValues(ConfigurationValues cv)
{
var builder = new StringBuilder();
builder.AppendFormat("HOST_STANDBY_FEATURE_ENABLE {0}\r\n", cv.HostStandbyFeatureEnable);
// Add the rest of your properties
File.WriteAllText("values.cfg", builder.ToString());
}
}

Why does MonoTouch.Dialog use public fields for some Element options, and public properties for others

I am trying to get a StringElement's 'Value' to update in the UI when I set it after already setting up the DVC.
e.g:
public partial class TestDialog : DialogViewController
{
public TestDialog() : base (UITableViewStyle.Grouped, null)
{
var stringElement = new StringElement("Hola");
stringElement.Value = "0 Taps";
int tapCount = 0;
stringElement.Tapped += () => stringElement.Value = ++tapCount + " Taps";
Root = new RootElement("TestDialog")
{
new Section("First Section")
{
stringElement,
},
};
}
}
However the StringElement.Value is just a public field, and is only written to the UICell during initialization when Element.GetCell is called.
Why isn't it a property, with logic in the setter to update the UICell (like the majority of Elements, e.g. EntryElement.Value):
public string Value
{
get { return val; }
set
{
val = value;
if (entry != null)
entry.Text = value;
}
}
EDIT :
I made my own version of StringElement, derived from Element (basically just copied the source code from here verbatim)
I then changed it to take a class scoped reference to the cell created in GetCell, rather than function scoped. Then changed the Value field to a property:
public string Value
{
get { return val; }
set
{
val = value;
if (cell != null)
{
// (The below is copied direct from GetCell)
// The check is needed because the cell might have been recycled.
if (cell.DetailTextLabel != null)
cell.DetailTextLabel.Text = Value == null ? "" : Value;
}
}
}
It works in initial testing. However I am not sure on whether taking a reference to the cell is allowed, none of the other elements seem to do it (they only take references to control's placed within the cells). Is it possible that multiple 'live'* cell's are created based on the one MonoTouch.Dialog.Element instance?
*I say live to indicate cells currently part of the active UI. I did notice when navigating back to the dialog from a child dialog the GetCell method is invoked again and a new cell created based on the Element, but this is still a 1-1 between the element and the live cell.
For the main question:
Why does MonoTouch.Dialog use public fields for some Element options, and public properties for others?
I've been through the code, and I don't think there's a consistent reason for use of either.
The Dialog project was not part of the MonoTouch project initially - I don't think Miguel knew how useful it was going to turn out when he started wrote and grew it - I think he was more focussed on writing other apps like TweetStation at the time.
I know of several people (including me!) who have branched the code and adapted it for their purposes. I would guess at some future point Xamarin might write a 2.0 version with stricter coding standards.
Taking references to live cells
For limited use you can do this... but in general don't.
The idea of the table view is that cells get reused when the user scrolls up and down - especially in order to save memory and ui resources. Because of this is a long list, multiple elements might get references to the same cell.
If you do want to cache a cell reference then you probably should override GetCell() so that it never tries to reuse existing cells (never calls DequeueReusableCell)
Alternatively, you could try to change some code in the base Element class in order to find out if the Element has a current attached cell - this is what CurrentAttachedCell does in my branch of Dialog https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross.Dialog/Dialog/Elements/Element.cs (but that branch has other added functions and dependencies so you probably won't want to use it for this current work!)

How to write an add-in to change text color in Visual Studio editor?

After searching for a long time for a simple way of changing the text color of a #region directive in Visual Studio, I've concluded there is no easy way of doing so.
I know how to change the #region statement color, and how to change the collapsed region color, but I want to change the color of the text with the region description. So:
#region Some text <--- all this text should be in a different color
public void Test()
{
}
#endregion <--- this too
It seems a lot of people are looking for something like this - see How to change the color of expanded regions' titles in VS2008?.
So I've been looking at writing a simple Visual Studio add-in to change the color.
However, it's more complicated than I thought it would be, with classes like Snapshot, Tagger, Classifier, WpfTextViewCreationListener, AdornmentLayer etc.
Simply put, I don't know where to start! I followed a couple of tutorials at the MSDN site, but they seem too complicated for what I'm trying to do.
Can someone point me to the most simple way of doing this? Ie. which classes/methods/events within the VS SDK I should use. I don't mind if the color is not customisable via the UI etc either. I'm using VS2010.
Edit: Just had the mztools website recommended to me; I'll take a look there too. Also noticed that StackOverflow's syntax highlighting of regions is pretty much exactly what I want!
I eventually came up with a solution, at least for VS2010.
Whilst I have used this for coloring '#region' and '#endregion' tags, a similar solution ought to be applicable for any text content in a Visual Studio window.
It seems that this sort of problem can be resolved by creating a IViewTaggerProvider which will 'tag' parts of the source code with a 'classification'.
Visual Studio will provide a style for text tagged with that classification which can then be changed by the user to the desired style via Tools > Options... > Environment > Fonts and Colors.
The Tagger provider looks like:
[Export(typeof(IViewTaggerProvider))]
[ContentType("any")]
[TagType(typeof(ClassificationTag))]
public sealed class RegionTaggerProvider : IViewTaggerProvider
{
[Import]
public IClassificationTypeRegistryService Registry;
[Import]
internal ITextSearchService TextSearchService { get; set; }
public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
{
if (buffer != textView.TextBuffer)
return null;
var classType = Registry.GetClassificationType("region-foreground");
return new RegionTagger(textView, TextSearchService, classType) as ITagger<T>;
}
}
This creates an ITagger object, which, given a Visual Studio text view, will tag parts of the text with the given classification type. Note that this will work for all text views (i.e. source code editor, 'Find Results' windows etc.). It may be possible to change this by editing the ContentType attribute (to just C#?).
The classification type (in this case "region-foreground") is defined as:
public static class TypeExports
{
[Export(typeof(ClassificationTypeDefinition))]
[Name("region-foreground")]
public static ClassificationTypeDefinition OrdinaryClassificationType;
}
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "region-foreground")]
[Name("region-foreground")]
[UserVisible(true)]
[Order(After = Priority.High)]
public sealed class RegionForeground : ClassificationFormatDefinition
{
public RegionForeground()
{
DisplayName = "Region Foreground";
ForegroundColor = Colors.Gray;
}
}
The Order attribute determines when the classification will be applied compared to other classifications which may also apply to a span of text.
The DisplayName will be used in the Tools > Options... dialog.
Once the classification is defined, an ITagger class can search a view's text and provide classifications for applicable sections of the text it finds.
Simply put, its job is to listen for the ViewLayoutChanged event of the text view, which is fired when the provided text view's content changes (e.g. because the the user has typed something).
It must then search the text for the area of text it is interested in (called a 'span'). Here, it returns spans of lines containing either #region or #endregion. I've kept this simple, but the TextSearchService used to find matches can also search using regular expressions.
Finally, a method is provided for Visual Studio to retrieve the tags of the text it has found, called GetTags(). For a given span collection, this will return text spans with classification tags, i.e. areas of those spans which should be classified in a certain way.
Its code is:
public sealed class RegionTagger : ITagger<ClassificationTag>
{
private readonly ITextView m_View;
private readonly ITextSearchService m_SearchService;
private readonly IClassificationType m_Type;
private NormalizedSnapshotSpanCollection m_CurrentSpans;
public event EventHandler<SnapshotSpanEventArgs> TagsChanged = delegate { };
public RegionTagger(ITextView view, ITextSearchService searchService, IClassificationType type)
{
m_View = view;
m_SearchService = searchService;
m_Type = type;
m_CurrentSpans = GetWordSpans(m_View.TextSnapshot);
m_View.GotAggregateFocus += SetupSelectionChangedListener;
}
private void SetupSelectionChangedListener(object sender, EventArgs e)
{
if (m_View != null)
{
m_View.LayoutChanged += ViewLayoutChanged;
m_View.GotAggregateFocus -= SetupSelectionChangedListener;
}
}
private void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
if (e.OldSnapshot != e.NewSnapshot)
{
m_CurrentSpans = GetWordSpans(e.NewSnapshot);
TagsChanged(this, new SnapshotSpanEventArgs(new SnapshotSpan(e.NewSnapshot, 0, e.NewSnapshot.Length)));
}
}
private NormalizedSnapshotSpanCollection GetWordSpans(ITextSnapshot snapshot)
{
var wordSpans = new List<SnapshotSpan>();
wordSpans.AddRange(FindAll(#"#region", snapshot).Select(regionLine => regionLine.Start.GetContainingLine().Extent));
wordSpans.AddRange(FindAll(#"#endregion", snapshot).Select(regionLine => regionLine.Start.GetContainingLine().Extent));
return new NormalizedSnapshotSpanCollection(wordSpans);
}
private IEnumerable<SnapshotSpan> FindAll(String searchPattern, ITextSnapshot textSnapshot)
{
if (textSnapshot == null)
return null;
return m_SearchService.FindAll(
new FindData(searchPattern, textSnapshot) {
FindOptions = FindOptions.WholeWord | FindOptions.MatchCase
});
}
public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans)
{
if (spans == null || spans.Count == 0 || m_CurrentSpans.Count == 0)
yield break;
ITextSnapshot snapshot = m_CurrentSpans[0].Snapshot;
spans = new NormalizedSnapshotSpanCollection(spans.Select(s => s.TranslateTo(snapshot, SpanTrackingMode.EdgeExclusive)));
foreach (var span in NormalizedSnapshotSpanCollection.Intersection(m_CurrentSpans, spans))
{
yield return new TagSpan<ClassificationTag>(span, new ClassificationTag(m_Type));
}
}
}
For brevity I have omitted namespaces and the using statements, which are typically of the form Microsoft.VisualStudio.Text.*. For these to be available, the Visual Studio 2010 SDK must first be downloaded.
I've been using this solution for the past few months without issue.
One limitation I noticed is colours are not 'blended', so a color with less than 100% opacity will not 'fade out' the existing colours in a span - which might be useful to preserve syntax highlighting.
I also have little idea of its efficiency, as it looks like it will repeatedly search a document on each keypress. I have not done the research to see if Visual Studio optimises this somehow. I do notice a slowdown of Visual Studio on large files (> ~1000 lines), but I also use Resharper, so I cannot attribute this to this plugin alone.
As this was coded mostly using guesswork, I welcome any comments or code changes which could clarify or simplify things or improve upon the code's performance.
I guess you could start with Visual Studio Add-in project, it will used EnvDTE, which was considered as Visual Studio Object Model, and please find MSDN document here:
http://msdn.microsoft.com/en-us/vstudio/bb968855
You could control your visual studio behavior, like debugger, code editor and so on by EnvDTE.

Allow multi-line String properties in the Properties window

I've got a Windows Form User Control with a string property for setting the text of a textbox. This string can be multi-line.
I've noticed that on some controls with a text property, and instead of being forced to type in the single line property textbox, you get a little pop up where you can type multiple lines. (As a matter of fact, a Windows Forms Textbox control allows this on the Text property.)
How do I enable this functionality in the properties window for the property I have designed?
The following is not real code in my app, but an example of how such a property might be defined
public string Instructions
{
get
{
return TextBox1.Text;
}
set
{
TextBox1.Text = value;
}
}
You can use the EditorAttribute with a MultilineStringEditor:
[EditorAttribute(typeof(MultilineStringEditor),
typeof(System.Drawing.Design.UITypeEditor))]
public string Instructions
{
get
{
return TextBox1.Text;
}
set
{
TextBox1.Text = value;
}
}
To avoid adding a reference to System.Design and thus requiring the Full framework, you can also write the attribute like this:
[EditorAttribute(
"System.ComponentModel.Design.MultilineStringEditor, System.Design",
"System.Drawing.Design.UITypeEditor")]
Although this is less of a problem now that they've stopped splitting the framework into a Client Profile and a Full one.

Categories

Resources