Flex 4 Desktop App TooltipManager - apache-flex

I have a flex app that used to be an internet based app. In it I have a function that creates tooltip error messages when I tell it to create an error message. I pulled this app out verbatim to a desktop app and restructured things a bit to get it to run, but I did not mess with the core fundamentals of the mxml file that utilizes this aside from changing the root tag from a type of 's:Group' to 's:Window'
Everything runs correctly, but the tooltips are not displaying. I can't seem to figure out why, so I thought I'd run this by you guys. Here's the tooltip code (inline mxml code in the fx:script cdata tag):
import mx.controls.ToolTip;
import mx.managers.ToolTipManager;
public var errorTip:ToolTip;
private function createErrorMsg(errorMsg:String, object:Object):void {
if (errorTip){ToolTipManager.destroyToolTip(errorTip);};
errorTip = ToolTipManager.createToolTip(errorMsg,object.getBounds(root).x + object.width,object.getBounds(root).y) as ToolTip;
errorTip.setStyle("styleName", "errorTip");
errorTip.visible = true;
errorTip.enabled = true;
}
Basically, I pass the function a string and an object (text input, checkbox, button, etc...etc...) and it positions it and displays the error message. This fully works in my web version, but not in my desktop version.
Here's the code that instantiates the window:
var window:LoginWindow = new LoginWindow();
Window.systemChrome = NativeWindowSystemChrome.NONE;
Window.transparent = true;
Window.open(true);
Window.maximize()
Any ideas?
On a side note, I check to see if the errorTip exists at the beginning of the function and then destroy it so that the higher scoped variable 'errorTip' always equals the reference to the currently displayed error. This allows me to just destroy that error tip on form validation and then error check again, but it only allows one tooltip to be displayed at a time. Is there a better way to query the tooltip manager for all of it's currently displayed tooltips and destroy them all?

To resolve this you must change the line that calls 'createTooltip' to the following:
errorTip = ToolTipManager.createToolTip(errorMsg,object.getBounds(root).x + object.width,object.getBounds(root).y, null, object as IUIComponent) as ToolTip;
The idea behind this is that you can now just call 'createErrorMsg("myError",myComponent)' and it'll display an error message there, which you can then add another function:
private function clearError():void
{
if(errorTip) errorTip.visible = false;
}
to actually remove the error message. The down side of this is that it only displays one message at a time but I'll tweak it a bit to change that up. So here's my better resolution:
Error messages will (traditionally) only be displayed in a batch set. (Basically you will only have a handful of tooltips displayed at one point in time and they will all be related to the form you are on). This won't always be true, but that's not my concern here. I created a utility class that performs error message handling:
package Utils
{
import mx.controls.ToolTip;
import mx.core.IUIComponent;
import mx.managers.ToolTipManager;
public class ErrorUtils
{
private static var errorCache:Array = new Array();
public function ErrorUtils()
{
}
public static function createErrorMsg(errorMsg:String, object:Object):void {
var errorTip:ToolTip = ToolTipManager.createToolTip(errorMsg,object.getBounds(object.root).x + object.width,object.getBounds(object.root).y, null, object as IUIComponent) as ToolTip
errorTip.setStyle("styleName", "errorTip");
errorTip.visible = true;
errorTip.enabled = true;
var position:int = errorCache.length;
errorCache.push(errorTip);
}
public static function clearAllErrors():void
{
for each(var error:ToolTip in errorCache)
{
ToolTipManager.destroyToolTip(error);
}
}
}
}
To use the class you simply call ErrorUtils.createErrorMsg("my message",invalidComponent), and to clear the errors you simply call ErrorUtils.clearAllErrors()
That way if you have a form and you have a validation function for it, you simply call that function every time it comes back as invalid. The only thing that this is missing is any kind of way to clear a specific error, but to handle that you could place all of the creation calls inside of a master validation function that validates all the fields in your form, then call that each time you gain or loose focus on a field and call 'ErrorUtils.clearAllErrors()' at the beginning of the function.
BTW:
this is the source of the original tutorial I received to do this, I just abstracted it a bit
http://aralbalkan.com/1125
so credits go to the above site for the original code

Related

Using CEFSharp ILifeSpanHandler interface to Handle Popups

I have an issue with handling popups. I have implemented ILifeSpanHandler and OnBeforeBrowse (amoungst others) from the IRequestHandler.
How do I know in the ILifeSpanHandler what URL is being called? I am unable to get it in either the OnAfterCreated or OnBeforePopup. Currently I see it first in OnBeforeBrowse.
I have no code as my question is a "How to". In OnBeforePopup I have checked targetUrl however it seems to be there for decoration as I have read that it is not implemented anyway. I have also looked at the browner/chromiumWebBrowser objects, browser and newBroswer seem to be nothing. One would expect in OnAfterCreated chromiumWebBrowser would return an object but it is nothing in my case.
I am testing with the following
Public Sub OnAfterCreated(chromiumWebBrowser As IWebBrowser, browser As IBrowser) Implements ILifeSpanHandler.OnAfterCreated
Try
Debug.Print(vbNewLine)
Debug.Print("OnAfterCreated")
Debug.Print(String.Concat("OnAfterCreated - MainFrame.Url "), browser.MainFrame.Url)
Debug.Print("OnAfterCreated")
Debug.Print(vbNewLine)
Catch ex As Exception
End Try
End Sub
And I have the following
Public Function OnBeforePopup(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, targetUrl As String, targetFrameName As String, targetDisposition As WindowOpenDisposition, userGesture As Boolean, popupFeatures As IPopupFeatures, windowInfo As IWindowInfo, browserSettings As IBrowserSettings, ByRef noJavascriptAccess As Boolean, ByRef newBrowser As IWebBrowser) As Boolean Implements ILifeSpanHandler.OnBeforePopup
Try
Debug.Print(vbNewLine)
Debug.Print("OnBeforePopup")
Debug.Print(String.Concat("OnBeforePopup - targetUrl "), targetUrl)
Debug.Print(String.Concat("OnBeforePopup - browser.MainFrame.Url "), browser.MainFrame.Url)
Debug.Print(String.Concat("OnBeforePopup - chromiumWebBrowser.Address "), chromiumWebBrowser.Address)
Debug.Print("OnBeforePopup")
Debug.Print(vbNewLine)
Catch ex As Exception
End Try
Return False
End Function
I have seen different approaches in handling popups using ILifeSpanHandler interface. One approach that I've seen also here in Stack Overflow and was accepted as the correct answer to that particular question is to return true in the OnBeforePopup implementation of ILifeSpanHandler then pass the targetURL argument to a handler that creates the popup.
This approach is very unideal because you are destroying the link between the page that actually opened the popup and the popup itself. If you access via JavaScript the opener property of the window object inside the popup you would notice that it is null. And the page that opened the popup would never know that the popup was actually opened because returning true cancels the creation.
The other approach is to let Cef create the popup and the programmer just decides whether to show the browser as a popup window or a child to control (like in tabbed browsing). This is error-free and almost ideal. But the problem with this approach is that you are not able to listen to events such as FrameLoadStart, FrameLoadEnd, AddressChanged, TitleChanged, etc.
One approach that is tagged experimental by the Cef developers is to return a new IWebBrowser instance via newWebBrowser out parameter. This has so many many side effects. The page that opened the popup would, of course, recognize the popup as his although it was not the original browser (IBrowser) that it created. The page may just ignore it like btcclicks.com and in that case, there'd be no problem. But there are websites like drops.xyz that is so particular with his stuff and will discard everything that is not originally his. So this is a problem.
So what is the correct approach?
The ChromeWebBrowser control
Now I'm going to share with you an undocumented approach in handling popups. Speaking of ChromeWebBrowser control, it is very much of help that we know how it creates the webbrowser which, in reality, it doesn't. The control just hosts the webbrowser window handle. It has a private field called managedCefBrowserAdapter (ManagedCefBrowserAdapter class) that handles the actual creation of the web browser. ChromiumWEbBrowser implements the IWebBrowserInternal that has a method OnAfterBrowserCreated with a single parameter of type IBrowser. The control then invokes browser.GetHost().GetWindowHandle() to get the actual window handle (HWND) of the webbrowser it is being hosted. It is quite good.
The problem of the ChromeWebBrowser is that it won't have a constructor that accepts an IBrowser as an argument. It only has constructor that accepts HtmlString, string and IRequestContext arguments. These control waits for the
invocation of OnHandleCreated (a base class override) where it calls the managedCefBrowserAdapter.CreateBrowser after which it waits till its implementation of IWebBrowserInternal's OnAfterBrowserCreated is invoked.
Again, what is the approach that works?
Now, this approach that actually works is a product of long series of trial and error. One caution though is that I don't know why and how it works but I know it works.
First, I did not use ChromeWebBrowser. But I copied its code omitting the part where it creates .net control. In this case, I am targeting the browser's window handle (HWND) to be host by any object that exposes a HWND. Obviously I created a class (NativeCefWebBrowser) that uses the modified code. The ChromeWebBrowser orignal constructors were still there untouched becuase they are used to the create the parent webrowser. But I added one constructor that accept the following arguments: ICefBrowserParent parent (an interface I've created and IBrowser browser that receives the browser argument in the ILifeSpanHandler's OnBeforePopup. I also added a public method AttachBrowser that has a single parameter IBrowser that recieves the IBrowser argument in the ILifeSpanHandler's OnAfterCreated. It the browser that will be kept by CefNativeWebBrowser class.
Why didn't I keep the browser instance received form ILifeSpanHandler.OnBeforePopup but used the instance received from ILifeSpanHandler.OnAfterCreated when they are the same browser instance? This is one of those parts that I don't know why. One thing I noticed is that when I called browser.GetHost().GetWindowHandle() during ILiffeSpanHandler.OnBeforePopup, the first window handle I received was the different compared to when I invoked the method during ILifeSpanHandler.OnAfterCreatd. Because of that, I store the browser instance from the latter that I passed to the NativeCefWebBrowser.AttachBrowser for its safekeeping.
In the NativeCefWebBrowser(ICefBrowserParent parent, IBrowser browser) contructor, I set the private following fields to true: browsercreated, browserinitialized (chromewebbrwoser orginal fields) and isAttachingBrowser (added private field). You don't call the ManagedCefBrowserAdapter's CreateBrowser in this contructor in instead call its OnAfterBrowserCreated passing the browser instance. You don't much in this constructor as you will wait the ILifeSpanHandler implementor to pass you the browser instance it will receive during its OnAfterCreated method. Take note that when calling the ManagedCefBrowserAdapter's OnAfterBrowserCreated method, ManagedCefBrowserAdapter will still invoke IWebBrowserInternal implementation of OnAfterBrowserCreated that when happens you have to exit immediately when isAttachingBrowser is true as the following code will no sense no more.
After calling the NativeCefWebBrowser(ICefBrowserParent, IBroser) construct, you can normally set event listeners as you will normally do.
And that's it.
The following are parts of the code that I wrote
The ICefBrowserParent interface
public interface ICefBrowserParent
{
IntPtr Handle { get; }
Size ClientSize { get; }
bool Disposing { get; }
bool IsDisposed { get; }
bool InvokeRequired { get; }
IAsyncResult BeginInvoke(Delegate d);
object Invoke(Delegate d);
event EventHandler Resize;
}
As you would notice, the methods, properties and events in this interface are already implemented by the System.Windowns.Forms.Control class. So if you implementing this from class inhering Control class, you would not need to implement this anymore. This interface is only for non-Control class.
class NativeCefWebBrowser
{
public NativeCefWebBrowser(ICefBrowserParent, IBroser)
{
requestContext = browser.GetHost().RequestContext;
this.parent = parent; // added field
HasParent = true; // IWebBrowserInternal. I don't know what's this for
mustSetBounds = true; // added field
browserCreated = true;
isAttachingBrowser = true; // added field
InitializeFieldsAndCefIfRequired();
managedCefBrowserAdapter.OnAfterBrowserCreated(browser);
}
}
ILifeSpanHandler.OnBeforePopup(..., out IWebBrowser newWebBrowser)
{
CefNativeWebBrowser b = new CefNativeWebBrowser
(
parent, // defined else where
browser
);
// Attach event handlers
b.TitleChanged...;
newWebBrowser = b;
}
ILifeSpanHandler.OnAfterCreated(...)
{
((CefNativeWebBrowser)webBrowser).AttachBrowser(browser);
}

How to Clear Static array in Flex action Script

I am using Flex Action Script.I am facing issue with static array. I have one static array used by two tabs.First time when I login the data is coming fine but if I am going to another tab and coming back to first tab then data from 2nd tab is appended into it and displayed in first.How to get rid of this problem?
//I'm assuming you have a var like this:
public static var sharedArray:Array;
//Then, assuming you're using a Spark TabBar, make sure you have a "change" listener defined for it, and make sure it includes the following code:
public function onTabChange(event:IndexChangeEvent):void {
sharedArray = [];
}

How can I create a button on a form in a J2ME application?

I attempt to make a simple "hello world" application, where on clicking the button , it prints a string "hello world". How can I add a button on a form?
I need to create a button, on which when I click it can produce a string. How can I add a button without using canvas in j2me?
There is an API for this, but you better think twice whether you really need it.
API is desctibed in the Appearance modes section for lcdui Item objects
The StringItem and ImageItem classes have an appearance mode attribute that can be set in their constructors. This attribute can have one of the values PLAIN, HYPERLINK, or BUTTON. An appearance mode of PLAIN is typically used for non-interactive display of textual or graphical material. The appearance mode values do not have any side effects on the interactivity of the item. In order to be interactive, the item must have one or more Commands (preferably with a default command assigned), and it must have a CommandListener that receives notification of Command invocations...
A StringItem or ImageItem in BUTTON mode can be used to create a button-based user interface...
Note that this section also explains cases when using button appearance might be problematic:
...This can easily lead to applications that are inconvenient to use. For example, in a traversal-based system, users must navigate to a button before they can invoke any commands on it. If buttons are spread across a long Form, users may be required to perform a considerable amount of navigation in order to discover all the available commands. Furthermore, invoking a command from a button at the other end of the Form can be quite cumbersome. Traversal-based systems often provide a means of invoking commands from anywhere (such as from a menu), without the need to traverse to a particular item. Instead of adding a command to a button and placing that button into a Form, it would often be more appropriate and convenient for users if that command were added directly to the Form. Buttons should be used only in cases where direct user interaction with the item's string or image contents is essential to the user's understanding of the commands that can be invoked from that item.
From the class diagram I found in an old J2ME book, and which is online at http://www.stardeveloper.com/articles/display.html?article=2002121101&page=2 it seems that J2ME don't do buttons. Well no need for them on an old mobile phone.
Just create a "hello" command and add it to a menu or form. The system will then put it on whatever button is available on your device. For touch screen devices that probably turns it into something clickable.
Here's the code
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class HelloWorld extends MIDlet implements CommandListener {
private static final String HELLO_WORLD = "Hello, World!!";
private Form form= new Form ("");
private Command exit= new Command("Exit", Command.EXIT, 0x01);
private Command ok= new Command("OK", Command.OK, 0x01);
private Command hello= new Command("HELLO", Command.SCREEN, 0x01);
private TextBox textBox= new TextBox("Hello World", HELLO_WORLD, HELLO_WORLD.length(), TextField.UNEDITABLE);
public HelloWorld() {
this.form.addCommand(exit);
this.form.addCommand(hello);
this.form.setCommandListener(this);
this.textBox.addCommand(ok);
this.textBox.addCommand(exit);
this.textBox.setCommandListener(this);
}
protected void destroyApp(boolean unconditional)
throws MIDletStateChangeException { }
protected void pauseApp() { }
protected void startApp() throws MIDletStateChangeException {
Display.getDisplay(this).setCurrent(this.form);
}
public void commandAction(Command c, Displayable d) {
if (c == this.exit) {
this.notifyDestroyed();
}
if(c == this.ok) {
Display.getDisplay(this).setCurrent(this.form);
}
if(c == this.hello) {
Display.getDisplay(this).setCurrent(this.textBox);
}
}
}

Data binding across multiple objects in Flex 3

I am new to Flex (got assigned to maintain an old project at work) and am having some trouble getting data binding to work correctly. I have a popup form class, AddOffer.mxml which uses a model AddOfferModel.as. On my popup form, I have the following component:
<mx:FormItem label="{getResource('addOffer.form.OFFER_DATE')}:"
labelWidth="90">
<views:OfferWindowDatesFragment
id="offerWindowField"
start="{model.offerStartDate}"
stop="{model.offerStopDate}" />
</mx:FormItem>
My AddForm.mxml file also has some embedded actionscript where I define my 'model' variable:
[Bindable]
public var model:AddOfferModel;
The model variables I am trying to bind to are standard getters/setters and look like this inside AddOfferModel.as:
[Bindable]
public function set offerStartDate(val:EditableInstant):void
{
_offerStartDate = val;
}
public function get offerStartDate():EditableInstant
{
return _offerStartDate;
}
private var _offerStartDate:EditableInstant;
[Bindable]
public function set offerStopDate(val:EditableInstant):void
{
_offerStopDate = val;
}
public function get offerStopDate():EditableInstant
{
return _offerStopDate;
}
private var _offerStopDate:EditableInstant;
Inside the OfferWindowDatesFragment component class, the start and stop variables look like this:
[Bindable]
public function set start(val:EditableInstant):void
{
_start = val;
}
public function get start():EditableInstant
{
return _start;
}
private var _start:EditableInstant;
[Bindable]
public function set stop(val:EditableInstant):void
{
_stop = val;
}
public function get stop():EditableInstant
{
return _stop;
}
private var _stop:EditableInstant;
Basically, I just want to bind the start and stop variables in my OfferWindowDatesFragment class to the offerStartDate and offerStopDate variables in the AddOfferModel.as file. Whenever I access the start/stop variables in functions inside the OfferWindowDatesFragment class, they are null.
I have an event listener function that gets triggered in OfferWindowDatesFragment anytime a user selects a new date, it looks like this:
private function changeOfferDate():void
{
start.currentValue = offerDateEditor.start;
stop.currentValue = offerDateEditor.stop;
}
Every time I reach this function, it throws up an error because 'start' and 'stop' are both null ... but should have been initialized and bound already. If I look at the variables in the debugger, I can confirm that values on the right side of the assignment expression are valid, and not what is causing the error.
I am not real familiar with how initialization works in Flex, and I assumed as long as I instantiated the component as seen in the first code snippet at the top of my post, it would initialize all the class variables, and setup the bindings. Am I missing something? Perhaps I am not properly initializing the model or class data for AddForm.mxml or AddFormModel.as, thereby binding null references to the start/stop fields in my OfferWindowDatesFragment class?
Any help would be greatly appreciated. Thanks!
EDIT:
I looked into this further and tried using Mate to inject the 'model' variable inside AddOffer.mxml with a valid AddOfferModel object:
<Injectors target="{AddOffer}" debug="{debug}">
<ObjectBuilder generator="{AddOfferModel}" constructorArguments="{scope.dispatcher}" cache="local"/>
<PropertyInjector targetKey="model" source="{lastReturn}" />
</Injectors>
I load the AddOffer.mxml dialog as the result of a button click event on another form. The function that pops it up looks like this:
public function addOffer():void
{
var addOfferDialog:AddOffer = new AddOffer();
addOfferDialog.addEventListener("addOffer", addOfferFromDialog);
modalUtil.popup(addOfferDialog);
}
It doesn't seem to be assigning anything to the 'model' variable in AddOffer.mxml. Does loading a view/dialog this way not trigger an injection from Mate by chance? (I realize this last part might belong in the Mate forums, but I'm hoping somebody here might have some insight on all of this).
In AddOffer.mxml, you have this code:
[Bindable]
public var model:AddOfferModel;
Is there something outside AddOffer.mxml that is setting this to a valid AddOfferModel? There should be. The nature of how the Flex component life cycle means that you can expect that things may be null at times as a View builds. So you should build your components to be able to "right themselves" after receiving bad data, if the data eventually comes good.
Data binding is one way to do this, but it may not paper over everything depending on what else is going on.
Have you verified that the model value you're getting is not null at the point where the user selects the date and that its offerStartDate and offerEndDate properties have been populated with valid EditableInstants? If both of those are correct, I'd start looking for pieces of the Views that expect to have stuff at a given instant and then don't recover if it is provided later.

Passing along information on a FileReference Complete event

I need to pass along a string with my FileReference, or provide that string as an argument when an event fires. To be clear, it really annoys me that AS3 doesn't allow you to pass parameters on events.
Right now, I've extended the FileReference class to include an additional variable. I'm trying to get this to compile, but it won't compile; I think I don't know how to import this class correctly. If you can tell me how to import this class correctly so that I no longer get Error: Type was not found or was not a compile-time constant at compile time that would be great.
This is the extended FileReference class:
import flash.net.FileReference;
public class SxmFR extends FileReference {
public var housenum:String = "";
public function SxmFR(str:String) {
housenum = str;
super();
}
}
I've tried that in a .mxml and a .as in the same folder. Neither is automatically imported.
I've also tried to extend the Event class, but I couldn't figure out how to make the event dispatch, since I need to respond to the Event.COMPLETE event. If you can tell me how to make it dispatch on this, it also might work.
Please help me get this figured out, and much love and thanks to all involved. : )
If you add your event listener as a closure you have access to the variables in the current function:
function myFunction(): void {
var aParam: String = "This is a parameter";
dispatcher.addEventListener("eventName", function (e: Event): void {
// you can access aParam here
trace(aParam);
});
}
The aParam inside the function will have the same value as it had when addEventListener was called.

Resources