I am having an interesting issue with a COM component written to function as a toolbar in IE. Basically if you open up several tabs at once in IE the individual instances of the COM objects get all twisted around. Bear with me here.
Say I open up five browser tabs all at once by right clicking several different links and opening them in new tabs. Now a function of my toolbar involves selecting text in the web page and then clicking a button to copy that text into the Toolbar. So let's do that in tab 3. We select text and click the button and nothing is there. However, if we select text in tab 2, then go back to tab 3 and click the button we get the text selected in tab 2. So...the toolbar in tab 3 getting stuff from tab 2. Not good.
I have traced this problem back to static references inside our COM object, the toolbar.
[ComVisible(true), Guid("2CC75392-1182-470D-BECC-EFA33E629AB8")]
[CLSCompliant(false)]
public sealed class Toolbar : ADXIEToolbar
{
public static Toolbar Instance;
public Toolbar()
{
Instance = this;
InitializeComponent();
}
...other code...
}
Note only one toolbar instance exists per each IE tab.
This reference doesn't get assigned properly, almost like it isn't thread safe (it isn't) but instead not domain safe or something. It will sometimes reference another instance down the line. Same with other static fields and even thread-safe singletons. I don't get it.
Also note that if I pass a reference to this toolbar (inside InitializeComponent) to a control I have the same issue.
this.publicationDateCb.Toolbar = this;
This reference will sometimes point to a different tab.
If I use a purely subscription based model with absolutely zero static references with the toolbar as the referee then things seem to work fine. This basically means I would have to re-design the program to where no classes interacted with each other directly - they fire events that the toolbar subscribes to, calling methods in other classes. Ouch.
So should I go with that model (which may be ideal but I am pretty far along here) or is there a simple fix I am missing here?
Other notes:
All IE tabs are running in seperate processes.
The BHO/Toolbar is running in the same process as the IE tab.
I am using Add-In-Express for Internet Explorer to handle the IE integration.
The project is written for .NET 3.5; the loader uses .NET 2.0
If you want to share your selected text within all your toolbars you can look at: http://www.add-in-express.com/creating-addins-blog/2009/06/19/internet-explorer-plugin-settings-synchronize/
Problem solved but static references are gone. I did a few things:
First off, I changed the target .NET version to 4.0. Apparently BHOs written in 4.0 work better - I can't find a link to substantiate this claim but I have read it somewhere.
More importantly I did away with static references within the assembly altogether. I got rid of the singletons and instead created a property for each former singleton class in my Toolbar class, which will always be unique. I then passed a reference to the Toolbar whenever a class needed to reference a former singleton.
So...constructors look like this now:
internal class RegistryData
{
public RegistryData(Toolbar toolbar)
{
ToolbarRef = toolbar;
}
...
}
And let's say RegistryData needs to call Messaging.
private void RegistryUpdated(int keyId)
{
ToolbarRef.Messaging.SendMessage(keyId);
}
Huge pain, right? Hours of work. But problem solved. I would not be shocked if this issue were related exclusively to Add-In-Express.
Related
I have a telerik control (Telerik.WinControls.UI.RadToggleSwitch) which is used to toggle between state 1 and state 2 with "Click and drag (left or right)" to make the toggle effect. I want to do a hand-coded UI test to select a state and proceed further. I need a class to call that control(i assumed it is WinClient).
I need the proper code to perform toggle action. Thanks in advance.
You may handcode your Coded UI Tests but handcoding your UIControls is really troublesome to do as you can never be sure that the Control you added is actually found and everything you did is correct.
A better alternative is to use the UIMap to manually add your controls via the Coded UI Test Builder. It also saves a lot of time.
When the Coded UI Test Builder is open you can hover with your mouse over a control and type "Control+I" to get info on that specific control. If you now click on the << on the infobox that opens you see your UIMap with the control you did "Control+I" over on the left added. But the control is not added yet permanently. From here you can add it permanently by clicking on the square with the green plus sign.
Add Button Icon
Alternatively the easy way is to hover over a control and push "Control+Shift+I".
See also: https://learn.microsoft.com/en-us/visualstudio/test/use-ui-automation-to-test-your-code
After adding the specific control to the UIMap you may use it by referecing to the UIMap. When coding in the *.cs file of the UIMaps you can reference to it by the "this" statement.
For example:
this.UIWindow.UITitleBar.UICloseButton;
When you want to use it outside of the files of the UIMap you have to create an object of the class of the UIMap and then can use it like above by repacing "this" with the object reference.
For example:
MyUIMapClass uIMapObject = new MyUIMapClass();
uIMapObject.UIWindow.UITitleBar.UICloseButton;
If the file you are coding in is not in the same namespace you have to add a using statement for the namespace of the UIMap (the namespace is defined at the begining of each file in the UIMap).
So for your control I think what you need is Mouse.StartDragging() and Mouse.StopDragging().
public static void StartDragging(UITestControl control);
public static void StopDragging(UITestControl control, int moveByX, int moveByY);
So a dragging towards left would be:
Mouse.StartDragging(UIYourControl)
Mouse.StopDragging(UIYourControl, -20, 0);
And toward right:
Mouse.StartDragging(UIYourControl)
Mouse.StopDragging(UIYourControl, 20, 0);
You should test a bit with the amount you need to drag in each direction for it to register as dragging but I think -20 and 20 should be fine.
I hope I helped a bit. :)
I've Googled and Binged and Yahoo'd and even Dogpiled. I found mostly Python, PHP, and Ruby that don't apply. Only one item shows up (from 2012) but it has no answer: unit test fails in r# but passes in mstest
I have two tests that work exactly as recorded. For one, when the test gets to the Submit button, the app says the page isn't ready to be submitted and the Submit button remains disabled. For the other, the Submit button is enabled, but the page does nothing. When I run the exact same actions manually (using keyboard and mouse) with identical data (or with different data), the Submit buttons on both pages work and both pages process.
I've run the tests to the point where the Submit buttons are clicked, but nothing happens when I manually click them, either. I've tabbed from one field to the next thinking it might be an event not firing, but no.
Here's what I've found: If the automation opens the page, the Submit buttons will not work. If even one item on the page is entered or selected by the automation, the Submit buttons will not work. BUT: If I run the automation to the point BEFORE the page opens, then if I click to open the page and manually keyboard and mouse the entries, and then manually mouse-click the Submit button, it will work.
The tests were recorded in MTM and imported into Visual Studio 2012. I've tried re-importing them, and I've manually recorded the steps using Visual Studio.
We're now thinking there's some unexpected interaction between the testing software (MSTest) and the JavaScript in the pages. We've coded (Coded UI) 11 other pages in the application. All 11 application pages use the same basic architecture and the same controls. We testers are thinking the JavaScript might be broken (missing closing brace, missing pair of parentheses, missing a semi-colon, a line-break in the wrong place, something).
There is absolutely nothing unique about these tests. They are among the simplest tests I've ever coded. One of them only has four input fields. I've coded nothing manually; The UIMap.cs file is empty. It's all in the UIMap.Designer.cs based on the UIMap.UITest file.
Here is the significant portion of my Coded UI Test Class (variable setup and try-catch logic omitted):
try
{
User.OpenBrowser(TestContext);
this.UIMap.ClickFeesButtonInSecondaryMenu();
this.UIMap.ClickAddPaymentButton();
this.UIMap.OpenYearDropdownAndSelect2016();
this.UIMap.Type1234InCheckNumber();
this.UIMap.Type275InTotalAmount();
this.UIMap.SelectBusinessFees();
this.UIMap.SelectInstructorFees();
this.UIMap.ClickSubmitButton();
User.CloseBrowser(TestContext);
}
catch . . . .
Here is part of my UIMap.Designer.cs:
public void ClickFeesButtonInSecondaryMenu()
{
#region Variable Declarations
HtmlHyperlink uIFeesHyperlink = this.UIInternetExplorerWindow.UIDocument.UIFeesHyperlink;
#endregion
// Click 'Fees' link
Mouse.Click(uIFeesHyperlink, new Point(22, 21));
}
public void ClickAddPaymentButton()
{
#region Variable Declarations
HtmlHyperlink uIAddPaymentHyperlink = this.UIInternetExplorerWindow.UIDocument1.UIAddPaperPaymentHyperlink;
#endregion
// Click 'Add Payment' link
Mouse.Click(uIAddPaymentHyperlink, new Point(84, 15));
}
public void OpenYearDropdownAndSelect2016()
{
#region Variable Declarations
HtmlHyperlink uIItem2017Hyperlink = this.UIFeeInternetExplorerWindow.UIFeeDocument.UIItem2017YearYearPane.UIItem2017Hyperlink;
HtmlDiv uIItem2016Pane = this.UIFeeInternetExplorerWindow.UIFeeDocument.UIItem2016Pane;
#endregion
// Click '2017' link
Mouse.Click(uIItem2017Hyperlink, new Point(149, 20));
// Click '2016' pane
Mouse.Click(uIItem2016Pane, new Point(124, 3));
}
public void Type1234InCheckNumber()
{
#region Variable Declarations
HtmlEdit uICheckNumberEdit = this.UIFeeInternetExplorerWindow.UIFeeDocument1.UICheckNumberEdit;
#endregion
// Type '1234' in 'Check Number' text box
uICheckNumberEdit.Text = this.Type1234InCheckNumberParams.UICheckNumberEditText;
}
. . . and so on until you get to . . .
public void ClickSubmitButton()
{
#region Variable Declarations
HtmlInputButton uISubmitButton = this.UIFeeInternetExplorerWindow.UIFeeDocument1.UISubmitButton;
#endregion
// Click 'Submit' button
Mouse.Click(uISubmitButton, new Point(27, 17));
}
I've only been doing C# MSTEST Coded UI just over a year, but I have three other automators here with tons more experience than I have, and this problem is new to us all. We've looked at everything we can think of, but no joy. Has anyone seen this? What causes this behavior? Most importantly, is there a fix?
Because you mentioned that you think the JS may be broken, that leads me to believe that perhaps some sort of AJAX is going on in the background after clicking the button. If that is the case, please see this question to see if it applies to your case.
This seems like a fairly basic thing to do, but for some reason it just fails silently:
/// <summary>
/// Sets the button to show it's busy image
/// </summary>
public void SetBusy()
{
if (Control is Button)
{ ((Button)Control).Image = BusyImage; }
else if (Control is ToolStripButton)
{ ((ToolStripButton)Control).Image = BusyImage; }
}
BusyImage is set using BusyImage = Properties.Resources.Busy;
If I debug this, I can see that the image appears to be setting correctly (if I hover over the Image member when at a breakpoint I can see it change), but it doesn't actually change the image when you look at the button.
I have noticed that this works when all the above code is hosted in the same Project file as the UI, but when it's shipped out to a different project (but within the same Solution), it fails silently.
Any ideas where I'm going wrong?
Thanks
EDIT 1:
Even trying to set the Image to a file from the Resources of the same project as the ToolStripButton doesn't work (still fails silently).
Interestingly, it works absolutely fine when using a normal Button, regardless of which project the images are in.
Why the difference in behaviour between Button and ToolStripButton?
EDIT 2:
It appears that moving the code that sets the image into the same project as the ToolStripButton works. However, I would like to keep it in a separate project if at all possible...
try this instead, tested it and working fine at my end:
public void ChangeImg(Component ctrl)
{
if (ctrl is Button)
{ ((Button)ctrl).Image = Properties.Resources.keylock; }
else if (ctrl is ToolStripButton)
{ ((ToolStripButton)ctrl).Image = Properties.Resources.keylock; }
}
Finally figured this out. To cut a long story short, I had some ToolStripButtons hidden somewhere in my form, only visible in the combobox in the designer's properties window (even when you select it from there, you can't see it on the form anywhere). I was passing the name of one of these to the ImageButton instead of the correct one (which had a default name like toolStripButton3)...
I'd love to know how it happened, I suspect user error on my part...but then again I find it strange that VS will allow a ToolStripButton to exist when it doesn't appear on any ToolStrip on the form.
Either way, my code seems to work quite happily now. The reason it appeared to work when run from the same project was that I was using a different button to test the theory.
Lots of process of elimination got it down to just two buttons that weren't playing ball; on a hunch I decided to compare the properties of the working and non-working buttons, whereupon I discovered the issue...
I need to create a non-visual component, FooComponent, that will do some management for all controls of type Bar that resides in its form.
I have the following constraints:
The FooComponent can only be added to forms.
Only one FooComponent per form is allowed.
FooComponent should register to the form closing event, and when it fires and to some function on all Bar's and sent the e.Cancel value based on the returned values.
#1 and #2 above should be enforced on run-time as well as design time.
#3 event registration should be made automatically and not by the FooComponent's users.
I searched Google and MSDN for some help and read about Component and ComponentDesigner classes, but I didn't find anything for the rescue.
What should I do?
(1) To control that the component can only be added to a form, use a FooComponent constructor that is passed a form, and don't define the default constructor. It's called like:
FooComponent component = new FooComponent(this);
where the component is created from within the form itself. By not-defining the default constructor, this:
FooComponent component = new FooComponent();
will not compile.
(2) Expose a FooComponent property on the form itself, and in the constructor of the FooComponent, set the passed form's FooComponent to this.
(3) Same thing, in the constructor for the FooComponent, register with the closing event for the form you passed
Put it all together and you get:
public class MyForm : Form {
public FooComponent OwnedComponent { get; set; }
}
public class FooComponent {
public FooComponent (MyForm OwnerForm) {
OwnerForm.OwnedComponent = this;
OwnerForm.FormClosing += MyCallback;
}
private void MyCallback(object sender, FormClosingEventArgs e) {
...
}
}
EDIT
Unfortunately, if you need the default constructor, and if it has to be a true drop-on-the-form Component, there's no way to enforce that a component is only created on a Form, or that the Form only has one instance of the component (not from within the component, anyway).
The problem is twofold:
(1) Dropping a component doesn't add the component to the form, it adds it to the form's components collection. So even if you could get a handle to the parent/owner, it will never be a form.
(2) As Neil pointed out, dropping a component onto a form calls the default constructor, which passes no parameters, and, of course, none of the component's properties (such as site or container) are populated.
Possibly helpful: A component can be designed to be notified when it is created in a couple of ways:
(1) By implementing a constructor that takes an IContainer parameter. When the component is dropped on a form, the generated code will call this constructor, instead. However, it will only do this at runtime, not design time. But the container will be a handle to the form's components collection.
public FooComponent(IContainer container) {...}
(2) By implementing ISupportInitialize. When the component is dropped on a form, the generated code will additionally call BeginInit() and EndInit(). In EndInit(), you can access properties such as the Site and Container. Again, you'll only get this at runtime, not designtime, and throwing an exception here won't stop the component from being created.
Old, but excellent articles on Components and Controls from MSDN Magazine by Michael Weinhardt and Chris Sells.
April 2003 Building Windows Forms Controls and Components with Rich Design-Time Features
May 2003 Building Windows Forms Controls and Components with Rich Design-Time Features, Part 2
These are now .chm help files. You will need to unblock in the file's property page to enable reading the contents after downloading.
I don't think it's possible to define exactly what a contained class can be contained within. I've certainly never seen an instance where I've gotten an error (or even a warning) for setting up a property of one type in another, even in WinForms.
Something you might be able to do is to define a Form-derived ancestor for your forms that contains a reference to your (internally-visible) FooComponent, initializes one on instantiation, and attaches the handlers. For best results it should be parameterless and the only constructor overload, so it forms the base for any constructor your consumers come up with. Then, just make it a house rule that forms derive from your ancestor class and not directly from Form (you might be able to use a code inspection tool like FxCop or similar to enforce this when code is committed to source control). Your users now get a FooComponent in every Form they create, cannot create their own (it's internal and should be in another project with your Form ancestor) and don't have to do anything other than derive from the new class to make their forms behave the way you want.
You are asking for a lot. In general, making components aware of the form they are dropped on is quite difficult. This answer can help you get the event handler implemented. You'll need to implement ISupportInitialize to get the EndInit() call to setup the event handler.
Preventing multiples is quite hard too, I can only think of a custom designer that can step in early enough to prevent the 2nd one from being added.
My question is simple: how bad is the following snippet of code? How would you do it?
CancelEventHandler _windowClosing;
private CancelEventHandler WindowClosing
{
set
{
clearEventHandlerList();
this.Closing += value;
_windowClosing = value;
/*
* if calling the method with null parameters,
* it will set up itself as the primary control on the Window
*/
_windowClosing(null,null);
}
get
{
return _windowClosing;
}
}
private readonly CancelEventHandler[] CONTROLS = null;
private int current = 0;
public InitializerForm()
{
InitializeComponent();
/*
* these are the handlers for the different controls,
* in the order of appereance to the user
*/
STATES = new CancelEventHandler[] { handler1, handler2, etc. };
WindowClosing = CONTROLS[0];
}
private void clearEventHandlerList()
{
foreach (CancelEventHandler c in CONTROLS)
{
this.Closing -= c;
}
}
private void handler1(object obj, CancelEventArgs e)
{
if (obj == null)
{
//hide every other control, but this one, also set up if necessary
}
else
{
//do something
WindowClosing = CONTROLS[++current]; // set the next control to show
e.Cancel = true;
}
}
The point would be that the code wouldn't close a form, but instead show another component on it, and the set the way to handle that (this is mobile platform, so clicking OK button on the top generates a closing event). This is because showing several forms (4 or 5) one after another to the user makes the app blink, and also very annoying, while replacing just components is much smoother. This model works, but seems very nasty, and I would like a cleaner way to handle this.
Update:
I updated the code sample so that variable names are somewhat speaky. Still, I'm convinced this is awful, (a) but not sure how much, and more importantly, (b) how to do it better.
Update 2:
So, it seems that the code is still a bit mysterious.
Now here's what the problem is:
I show the user a form, which instructs him what to do in several languages. He proceeds by clicking OK on the window. Next, I ask for his language, and then a few questions (where his/her GPS is, etc.) like this. After he could answer the questions (this shouldn't take more than a few seconds each), I show him a splash screen (I load stuff in a separate thread meanwhile), which has a picture. Showing these forms one after another makes the whole application start slow, and filled with UI lags.
Here's what I do to work around the lags: I put the content of the windows into panels, and put those panels one on another, and hide every one of them but the one that should be visible to the user. (current variable) Each of the windows does different things, so I need to change handler of the window closing event in addition. In this code the part which enables the panel is in the same function (handler1, handler2, etc.) with the part which handles the window closing event. If the arguments are null, it does the former, if it isn't (that means it was triggered by the user) it does the latter.
I need an extensible solution to this so that I can insert and remove dialogs anytime I want (the order and the pointers to the functions are stored in the CONTROLS field, and this seems to be very convenient, if you actually understand it. Although it is never easy to change the entire content of a form, there ought to be a simpler way to do this, as well a nicer one, that is what I'm looking for.
I hope this time I could explain how the model works.
I think it might be theoretically possible to make that code more delightfully diverting, perilously puckish, jovially jeopardous, cheerily chancy and unwarily whimsical but it would require some serious thought.
somehow your code makes me want to cry, i´m sorry. i read it twice and all i know about it is that it "doesStuff" with "STATES".
if you really want some help on this one you will have to work on it yourself first...
Use, XML! It's human-readable!
More seriously-
It seems like you're trying to create some sort of configuration wizard, so I'd start by researching that. Regarding your particular solution, I generally advocate very strongly against the "layered panel" approach. I do so because I maintain apps written by people who found this approach, or the related "hidden tabs on a tab control" approach, to be a good idea. It's not, and maintainers will curse your name for years to come.
That being said, what alternatives are there? Well, one alternative is what you've already dismissed because of its "flicker". I'd say that, in general, the flicker isn't that big of a deal for a quick and dirty application. It might be a good idea to make sure that your new window is called up before closing the old one, for example. (I'm assuming this is possible, I haven't developed on a mobile device.)
Another possibility might be a less-evil version of your layered panels. Instead of throwing a half-dozen panels into one form, create a separate user control for each wizard page and then add/remove the user controls to a containing form. This can avoid your flicker and will prove to be much easier to maintain because each page is in a different control. This might also ease any subsequent "Back" button functionality and make your data structures more naturally defined because those user controls will be associated with a specific logical bit of data. It's still not ideal, but it's probably good enough for a one-off solution.
A third technique, if you foresee extensive wizard modification as the product matures, might be to generalize the creation of your user controls by defining them in a more logical/declarative manner (e.g. via XML). If you dynamically generate sensible controls based on XML, then modifying the panels might be as easy as diving into your XML and doing something like:
<Questions>
<Question type="Text"> <!-- generate a textbox for the answer field -->
Favorite Color:
</Question>
<Question type="Number" range="0-255"> <!-- Maybe this is a spinner -->
The answer to life, the universe, and everything:
</Question>
</Questions>
That's just off the top of my head, and completely overkill for any one-off application, but it's a possibility.
Now, let me caveat this by saying this might work, but it may not be the answer to your real problem - that of a slow and unresponsive UI when you have a lot of forms. The real answer may be to just go ahead and do all separate forms, but have each form load its child forms in a background thread while the user is staring at the first form.
But assuming you're still set on this, I'd start off by making a separate class just to handle the Panel stacking/hierarchy. Call it PanelManager. You would instantiate the PanelManager and associate it with the main form, then add Panels to it (perhaps keyed to a String) and set the order. In the main form, have the closing handler call PanelManager.CloseCurrentPanel() and if there are no more Panels to show then it's time to close the main form.
Time for pseudo-code! Here's a quick idea for the class, i'll leave it to you to implement it:
public class PanelManager {
// constructor
public PanelManager (Form ownerForm);
// short-cut properties
public Panel this[int idx]
{ get; set; }
public int Index
{ get; set; }
// main functionality
public int AddPanel (Panel p);
public void SetPanelOrder (Panel p, int idx);
public void RemovePanel (Panel p);
public void RemovePanelAt (int idx);
// shows the first Panel
public void Show ();
// shows Panel[idx]
public void Show (int idx);
// adds the panel to the top of the stack and displays it
// returns the index of the panel
public int AddPanelAndShow (Panel p);
// hides the current panel, displays the one underneath it
// returns false if there are no more panels
public bool HideCurrentPanel ();
}
in the constructor for the main form, instantiate it by new PanelManager (this), then in the closing event handler, call panelManager.HideCurrentPanel () and then figure out whether or not you need to close it after that.