skinning multiple UI components - apache-flex

Let's say you have a large number (N) of spark buttons in your app. Let's also say that your buttons all have very similar skins (size, various effects, etc) - the only difference being the specific png that they use as their BitmapImage.
Do you end up with N skin files, all differing by 1 line? Or is there a smarter way to do this while not adding a lot of code when you create the buttons in MXML (in fact, ideally, none).

Creating a custom Button with a icon SkinPart typed as a BitmapImage will allow you to use the same Skin for all buttons :
<YourCustomButton icon="#Embed('yourIconFile.png') />
CustomButton.as
public class CustomButton extends Button
{
[SkinPart(required="false")]
public var iconContainer:BitmapImage;
private var _icon:Object;
public function CustomButton()
{
super();
}
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
if (instance == iconContainer && _icon)
iconContainer.source = _icon;
}
public function get icon():Object
{
return _icon;
}
public function set icon(value:Object):void
{
if (iconContainer)
iconContainer.source = value;
_icon = value;
}
}

Related

Structuring a MonoTouch.Dialog application

From the examples at Xamarin.com you can build basic M.T. Dialog apps, but how do you build a real life application?
Do you:
1) Create a single DialogViewController and tree every view/RootElement from there or,
2) Create a DialogViewController for every view and use the UINavigationController and push it on as needed?
Depending on your answer, the better response is how? I've built the example task app, so I understand adding elements to a table, click it to go to the 'next' view for editing, but how to click for non-editing? How to click a button, go next view if answer is number 1?
Revised:
There is probably no one right answer, but what I've come up with seems to work for us. Number 2 from above is what was chosen, below is an example of the code as it currently exists. What we did was create a navigation controller in AppDelegate and give access to it throughout the whole application like this:
public partial class AppDelegate : UIApplicationDelegate
{
public UIWindow window { get; private set; }
//< There's a Window property/field which we chose not to bother with
public static AppDelegate Current { get; private set; }
public UINavigationController NavController { get; private set; }
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
Current = this;
window = new UIWindow (UIScreen.MainScreen.Bounds);
NavController = new UINavigationController();
// See About Controller below
DialogViewController about = new AboutController();
NavController.PushViewController(about, true);
window.RootViewController = NavController;
window.MakeKeyAndVisible ();
return true;
}
}
Then every Dialog has a structure like this:
public class AboutController : DialogViewController
{
public delegate void D(AboutController dvc);
public event D ViewLoaded = delegate { };
static About about;
public AboutController()
: base(about = new About())
{
Autorotate = true;
about.SetDialogViewController(this);
}
public override void LoadView()
{
base.LoadView();
ViewLoaded(this);
}
}
public class About : RootElement
{
static AboutModel about = AboutVM.About;
public About()
: base(about.Title)
{
string[] message = about.Text.Split(...);
Add(new Section(){
new AboutMessage(message[0]),
new About_Image(about),
new AboutMessage(message[1]),
});
}
internal void SetDialogViewController(AboutController dvc)
{
var next = new UIBarButtonItem(UIBarButtonSystemItem.Play);
dvc.NavigationItem.RightBarButtonItem = next;
dvc.ViewLoaded += new AboutController.D(dvc_ViewLoaded);
next.Clicked += new System.EventHandler(next_Clicked);
}
void next_Clicked(object sender, System.EventArgs e)
{
// Load next controller
AppDelegate.Current.NavController.PushViewController(new IssuesController(), true);
}
void dvc_ViewLoaded(AboutController dvc)
{
// Swipe location: https://gist.github.com/2884348
dvc.View.Swipe(UISwipeGestureRecognizerDirection.Left).Event +=
delegate { next_Clicked(null, null); };
}
}
Create a sub-class of elements as needed:
public class About_Image : Element, IElementSizing
{
static NSString skey = new NSString("About_Image");
AboutModel about;
UIImage image;
public About_Image(AboutModel about)
: base(string.Empty)
{
this.about = about;
FileInfo imageFile = App.LibraryFile(about.Image ?? "filler.png");
if (imageFile.Exists)
{
float size = 240;
image = UIImage.FromFile(imageFile.FullName);
var resizer = new ImageResizer(image);
resizer.Resize(size, size);
image = resizer.ModifiedImage;
}
}
public override UITableViewCell GetCell(UITableView tv)
{
var cell = tv.DequeueReusableCell(skey);
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, skey)
{
SelectionStyle = UITableViewCellSelectionStyle.None,
Accessory = UITableViewCellAccessory.None,
};
}
if (null != image)
{
cell.ImageView.ContentMode = UIViewContentMode.Center;
cell.ImageView.Image = image;
}
return cell;
}
public float GetHeight(UITableView tableView, NSIndexPath indexPath)
{
float height = 100;
if (null != image)
height = image.Size.Height;
return height;
}
public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath indexPath)
{
//base.Selected(dvc, tableView, path);
tableView.DeselectRow(indexPath, true);
}
}
#miquel
The current idea of a workflow is an app that starts with a jpg of the Default.png that fades into the first view, with a flow control button(s) that would move to the main app. This view, which I had working previous to M.T.D. (MonoTouch.Dialog), which is a table of text rows with an image. When each row is clicked, it moves to another view that has the row/text in more detail.
The app also supports in-app-purchasing, so if the client wishes to purchase more of the product, then switch to another view to transact the purchase(s). This part was the main reason for switching to M.T.D., as I thought M.T.D. would be perfect for it.
Lastly there would be a settings view to re-enable purchases, etc.
PS How does one know when the app is un-minimized? We would like to show the fade in image again.
I have been asking myself the same questions. I've used the Funq Dependency Injection framework and I create a new DialogViewController for each view. It's effectively the same approach I've used previously developing ASP.NET MVC applications and means I can keep the controller logic nicely separated. I subclass DialogViewController for each view which allows me to pass in to the controller any application data required for that particular controller. I'm not sure if this is the recommended approach but so far it's working for me.
I too have looked at the TweetStation application and I find it a useful reference but the associated documentation specifically says that it isn't trying to be an example of how to structure a MonoTouch application.
I use option 2 that you stated as well, it works pretty nicely as you're able to edit the toolbar options on a per-root-view basis and such.
Option 2 is more feasible, as it also gives you more control on each DialogViewController. It can also helps if you want to conditionally load the view.

Flex combobox needs to be selected twice to open drop down list

When a combobox is elected in the flex app, there is a quick flicker, then the combobox needs to be selected again in order to get the dropdown to open. After that, the dropdown works as expected, but only while selecting the control subsequent times while on the form. Reloading the form requires the double selection again. Any insights to how to clear this up would be very much appreciated.
The way I had to get around this issue was my creating a custom component that extends the ComboBox control that will set the ComboBox's List dataProvider at the same time as the ComboBox's dataProvider.
ComboBoxFix.as
package
{
import mx.controls.ComboBox;
public class ComboBoxFix extends ComboBox
{
public function ComboBoxFix()
{
super();
}
override public function set dataProvider(value:Object):void
{
super.dataProvider=value;
if(dropdown != null)
{
super.dropdown.dataProvider=value;
}
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number ):void
{
super.updateDisplayList (unscaledWidth, unscaledHeight);
if (dropdown != null)
{
dropdown.width = unscaledWidth;
}
}
}
}

Flex switch item-renderer

I was wondering if anyone had any luck with the following senario in flex.
I'd like to be able to have a custom item renderer which delegates to another renderer inside.
The reason for this would be in a datagrid for instance displaying a checkbox if the dataprovider for the row had a boolean value. Using the default item renderer when the value was a non boolean.
Basically I was hoping to use a proxy object (though not necessarily the proxy class) so that I could a renderer which delegated all of its responsibilties to a sub renderer.
Hard to explain.
Edit 1
I think the following gives a clearer idea of what I had in mind. This is only knocked up quickly for the purpose of showing the idea.
SwitchingRenderer.as
package com.example
{
import mx.controls.CheckBox;
import mx.controls.dataGridClasses.DataGridItemRenderer;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.core.IDataRenderer;
import mx.core.UIComponent;
public class SwitchingRenderer extends UIComponent implements IDataRenderer, IDropInListItemRenderer
{
private var checkboxRenderer:CheckBox;
private var defaultRenderer:DataGridItemRenderer;
private var currentRenderer:IDataRenderer;
public function SwitchingRenderer()
{
this.checkboxRenderer = new CheckBox();
this.defaultRenderer = new DataGridItemRenderer();
this.currentRenderer = defaultRenderer();
super();
}
public function get data():Object
{
//If the data for this cell is a boolean
// currentRender = checkBoxRenderer
// otherwise
// currentRenderer = defaultRenderer
}
public function set data(value:Object):void
{
currentRenderer.data = value;
}
public function get listData():BaseListData
{
return currentRenderer.listData;
}
public function set listData(value:BaseListData):void
{
currentRenderer.listData = value;
}
}
}
If you're using Flex 4 spark components look into the itemRendererFunction,
Here is a good sample from the interwebs.
Unfortunately, Flex 3 components, such as the DataGrid do not support that.
You're a bit vague on what you'd be displaying if the data sent into the itemRenderer was not a Boolean value. But, you can easily modify the visual appearance of a component based on the data change event, including swapping visible properties of a component's children, changing states or change the selectedIndex of a ViewStack. All these things can be done within an itemRenderer w/o issues.
Edit:
Based on the user's additional posting, I'd add that what he is after can be done like this:
public function get data():Object
{
if(this.data is Boolean){
checkBoxRenderer.visible = true;
defaultRenderer.visible = false;
} else {
checkBoxRenderer.visible = false;
defaultRenderer.visible = true;
}
}

How do I set the dimensions of a custom component defined in an ActionScript class?

I'm trying to set the height of a vertical bar (activityBar) but it does not appear to do anything. i have tried something similar with the whole component, but setting the dimensions does nothing (even in the mxml used to instantiate the class). Indeed, I've added transparent graphics just to give the component some dimensions
I'm not sure what I'm doing wrong. It's something bad though; my approach seems dire.
FYI: I'm trying to create a mic activity bar that will respond to the mic by simply setting the height of the activityBar child (which seems to me to be more efficient than redrawing the graphics each time).
Thanks for your help!
package components {
import mx.core.UIComponent;
public class MicActivityBar extends UIComponent {
public var activityBar:UIComponent;
// Constructor
public function MicActivityBar() {
super();
this.opaqueBackground = 0xcc4444;
graphics.beginFill(0xcccccc, 0);
graphics.drawRect(0,-15,5,30);
graphics.endFill();// background for bar
activityBar = new UIComponent();
activityBar.graphics.beginFill(0xcccccc, 0.8);
activityBar.graphics.drawRect(0,-15,5,20);
activityBar.graphics.endFill();
activityBar.height=10;
addChild(activityBar);
}
}
}
package components {
import mx.core.UIComponent;
public class MicActivityBar extends UIComponent {
public var activityBar:UIComponent;
private var _w:Number;
// Constructor
public function MicActivityBar() {
super();
this.opaqueBackground = 0xcc4444;
graphics.beginFill(0xcccccc, 0);
graphics.drawRect(0,-15,5,30);
graphics.endFill();// background for bar
activityBar = new UIComponent();
activityBar.graphics.beginFill(0xcccccc, 0.8);
activityBar.graphics.drawRect(0,-15,5,20);
activityBar.graphics.endFill();
activityBar.height=10;
addChild(activityBar);
}
public function get w():Number {
return _w;
}
public function set w(value:Number):void {
_w = value;
}
override protected function updateDisplayList(unscaledWidth:Number,unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
this.height=w;
this.weight=w;
}
}
}
See my updated code above.

possible to display an arrayCollection of Sprites in a List component?

I have an arrayCollection of objects that extend Sprite, and have bitmaps within them.
I want to display these in a list (or some other component that would allow a user to scroll through them, and see their associated data.)
When I do: myList.dataProvider = myArrayCollection
the list just shows a bunch of lines of [Object, Item] instead of the visual sprites.
Here is a simplified version of my Object:
public class myUIC extends UIComponent
{
public var mySprite:Sprite = new Sprite;
[Embed(source="assets/BGimage.png")]
public var BGimage:Class;
public var myBitmap:Bitmap;
public var wordText:TextField = new TextField;
public function myUIC(myWord:String)
{
this.wordText.text = myWord;
this.myBitmap = new BGimage;
this.mySprite.addChild(this.myBitmap);
this.mySprite.addChild(this.wordText);
this.addChild(this.mySprite);
}
}
Tried many different ways to get it to show up in a List, but can't do it.
See this tutorial: Flex Examples - displaying icons in a flex list control
Sounds like you may want to try writing a simple item renderer (perhaps based off UIComponent) that adds the associated sprite the display list of the render using addChild().
try rawChildren.addChild for adding the Sprite
Here, try using an itemRenderer something like this. It ought to work with any generic DisplayObject. It's grabbing the width and height from the assigned data property, so you might need to set variableRowHeight to true in your actual list for it to work as expected.
package
{
import flash.display.DisplayObject;
import mx.controls.listClasses.IListItemRenderer;
import mx.core.UIComponent;
import mx.events.FlexEvent;
/*
Extending UIComponent means we can add Sprites (or any DisplayObject)
with addChild() directly, instead of going through the rawChildren property.
Plus, in this case, we don't need the extra overhead of Canvas's layout code.
IListItemRenderer lets us use it as a List's itemRenderer. UIComponent already
implements all of IListItemRenderer except for the data property
*/
public class SpriteRenderer extends UIComponent implements IListItemRenderer
{
// Implementing the data property for IListItemRenderer is really easy,
// you can find example code in the LiveDocs for IDataRenderer
private var _data:Object;
[Bindable("dataChange")]
public function get data():Object
{
return _data;
}
public function set data(value:Object):void
{
if (value !== _data) {
// We need to make sure to remove any previous data object from the child list
// since itemRenderers are recycled
if (_data is DisplayObject && contains(_data as DisplayObject)) {
removeChild(_data as DisplayObject);
}
_data = value;
// Now we just make sure that the new data object is something we can add
// and add it
if (_data is DisplayObject) {
this.width = (_data as DisplayObject).width;
this.height = (_data as DisplayObject).height;
addChild(_data as DisplayObject);
}
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
}
public function SpriteRenderer()
{
super();
}
}
}

Resources