How do I add design-time properties to this XBL control? - orbeon

Using Orbeon, I have the following XBL for a simple control:
<xbl:xbl xmlns:xh="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"
xmlns:xxf="http://orbeon.org/oxf/xml/xforms"
xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
xmlns:saxon="http://saxon.sf.net/"
xmlns:xbl="http://www.w3.org/ns/xbl"
xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"
xmlns:nrs="http://nrs.foo/2014/xml/xbl">
<xbl:script src="/xbl/nrs/foo/foo.js"/>
<xbl:binding id="nrs-foo" element="nrs|foo" xxbl:mode="lhha binding value">
<metadata xmlns="http://orbeon.org/oxf/xml/form-builder">
<display-name lang="en">Foo</display-name>
<templates>
<view>
<nrs:foo>
<xf:label ref=""/>
<xf:hint ref=""/>
<xf:help ref=""/>
<xf:alert ref=""/>
</nrs:foo>
</view>
</templates>
</metadata>
<xbl:template>
<xf:group xxbl:scope="outer">
<xf:group xbl:attr="model context ref bind">
<!-- Constructor -->
<xf:group xxbl:scope="inner">
<xxf:script id="xf-en" ev:event="xforms-enabled">YAHOO.xbl.nrs.foo.instance(this).init();</xxf:script>
</xf:group>
</xf:group>
<xf:input class="nrs-foo-json" ref="xxf:binding('nrs-foo')" xxbl:scope="inner" />
</xf:group>
</xbl:template>
</xbl:binding>
.. and accompanying JS module:
(function() {
var $ = ORBEON.jQuery;
var Document = ORBEON.xforms.Document;
YAHOO.namespace("xbl.nrs");
YAHOO.xbl.nrs.Foo= function() {};
ORBEON.xforms.XBL.declareClass(YAHOO.xbl.nrs.Foo, "xbl-nrs-foo");
YAHOO.xbl.nrs.Foo.prototype = {
fooInput : null,
init: function() {
var self = this;
self.fooInput = $(this.container).find( '.nrs-foo-json' );
// get design time property
},
};
})();
How would I modify the XBL to get a design-time property (say a string called 'URL', that I can configure on the properties panel in the form designer), that is stored in the form definition and I can access in the JS code (at place marked by comment)?

Check how it is done in autocomplete.xbl, and more specifically at the the content of <control-details> inside <metadata>. In that case the value set in Form Builder through the Control Settings dialog ends up in the resource="..." attribute on the control, and accessed in the XBL through the resource-avt variable.
(Note that there are some complications there as we want the value to be interpreted as an AVT, and the variable to be accessible both in the inner and outer scope, but hopefully this example helps you get the idea.)

Related

Theme Resources to define gradient stop color UWP

I am creating UWP application.I have few LinearGradientBrushes, where the color is set directly in the LinearGradientBrush reference as GradientStops. However, I want to have a predefined set of colors defined in the resource distionary that I can use a a reference for each GradientStop, so that changing the color scheme for the application is a matter of changing the values of the SolidColorBrushes:
<!--Resource Dictionary -->
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="stop1" Color="#FF5A5A5A"/>
<SolidColorBrush x:Key="stop2" Color="#FF222222"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="stop1" Color="Black"/>
<SolidColorBrush x:Key="stop2" Color="White"/>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<SolidColorBrush x:Key="stop1" Color="Black"/>
<SolidColorBrush x:Key="stop2" Color="White"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<!-- control Template-->
<LinearGradientBrush x:Key="gradient">
<GradientStop Color="{Binding Source={Themeresource stop1},Path=Color}" Offset="0"/>
<GradientStop Color="{Binding Source={Themeresource stop2},Path=Color}" Offset="1"/>
</LinearGradientBrush>
Its Giving error that nam/key stop1 is not Found
The problem is stop1 is static resource but not Themeresource . So, we need edit binding source as StaticResource.
<LinearGradientBrush x:Key="gradient">
<GradientStop Color="{Binding Source={StaticResource stop1},Path=Color}" Offset="0"/>
<GradientStop Color="{Binding Source={StaticResource stop2},Path=Color}" Offset="1"/>
</LinearGradientBrush>
Update
For the testing, if we place above in ResourceDictionary, it will work.
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="stop1" Color="#FF5A5A5A"/>
<SolidColorBrush x:Key="stop2" Color="#FF222222"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="stop1" Color="Black"/>
<SolidColorBrush x:Key="stop2" Color="White"/>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<SolidColorBrush x:Key="stop1" Color="Black"/>
<SolidColorBrush x:Key="stop2" Color="White"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<!-- control Template-->
<LinearGradientBrush x:Key="gradient">
<GradientStop Color="{Binding Source={ThemeResource stop1},Path=Color}" Offset="0"/>
<GradientStop Color="{Binding Source={ThemeResource stop2},Path=Color}" Offset="1"/>
</LinearGradientBrush>
</ResourceDictionary>
</Page.Resources>
The above problem can be solved by using binding
public LinearGradientBrush GradientBrush
{
get { return _GradientBrush; }
set
{
_GradientBrush = value;
RaisePropertyChanged("GradientBrush");
}
}
GradientBrush = GetGradientBrush();
public static LinearGradientBrush GetGradientBrush()
{
var grColor1 = ((SolidColorBrush)Application.Current.Resources["stop1"]).Color;
var grColor2 = ((SolidColorBrush)Application.Current.Resources["stop2"]).Color;
LinearGradientBrush lgBrush = new LinearGradientBrush();
lgBrush.GradientStops.Add(new GradientStop() { Color = grColor1, Offset = 0.1 });
lgBrush.GradientStops.Add(new GradientStop() { Color = grColor2, Offset = 0.9 });
lgBrush.StartPoint = new Point(0, 1);
lgBrush.EndPoint = new Point(1, 0);
return lgBrush;
}
<Grid Background="{Binding GradientBrush}" >

Add settings to the Basic Settings dialog for a custom control

Hopefully not too much of an Orbeon-noob question. I have built a custom control for Orbeon (a simple slider for now) and was wanting to add the ability to change the min, max and step parameters for the range input in the Basic Settings dialog. I have had a look at the Dynamic Driven Dropdown and have added the control-details section in the control metadata, but I am stumped on how to get them to show up and how to use the value on the actual input element. Any help / example code would be hugely appreciated.
<xbl:xbl xmlns:xh="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"
xmlns:xxf="http://orbeon.org/oxf/xml/xforms"
xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
xmlns:saxon="http://saxon.sf.net/"
xmlns:oxf="http://www.orbeon.com/oxf/processors"
xmlns:xbl="http://www.w3.org/ns/xbl"
xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xbl:binding id="fr-slider" element="fr|slider"
xxf:external-events="fr-value-changed"
xxbl:mode="lhha binding value">
<metadata xmlns="http://orbeon.org/oxf/xml/form-builder">
<display-name lang="en">Slider</display-name>
<icon lang="en">
<small-icon>/forms/orbeon/builder/images/timeline_marker.png</small-icon>
<large-icon>/forms/orbeon/builder/images/timeline_marker.png</large-icon>
</icon>
<datatype>xf:number</datatype>
<template>
<fr:slider>
<xf:label ref=""/>
<xf:hint ref=""/>
<xf:help ref=""/>
<xf:alert ref=""/>
<xf:min ref=""/>
<xf:max ref=""/>
<xf:step ref=""/>
</fr:slider>
</template>
<control-details>
<xf:input ref="xf:min/#ref" type="number">
<xf:label>Minimum Value</xf:label>
<xf:hint />
</xf:input>
<xf:input ref="xf:max/#ref" type="number">
<xf:label>Maximum Value</xf:label>
<xf:hint />
</xf:input>
<xf:input ref="xf:step/#ref" type="number">
<xf:label>Step Size</xf:label>
<xf:hint>Smallest change in value the slider will allow</xf:hint>
</xf:input>
</control-details>
</metadata>
<xbl:resource>
<xbl:style>
input.fr-slider { width: 100% };
</xbl:style>
</xbl:resource>
<xbl:template xxbl:transform="oxf:unsafe-xslt">
<xsl:transform version="2.0">
<xsl:import href="oxf:/oxf/xslt/utils/xbl.xsl"/>
<xsl:template match="/*">
<xh:input type="range" min="0" max="10" step="1" class="fr-slider"/>
</xsl:template>
</xsl:transform>
</xbl:template>
</xbl:binding>
</xbl:xbl>
To get the inputs in the <control-detail> to show in the Basic Settings dialog, you need to add a lang parameter to the <xf:label> and <xf:hint> elements (thanks to #avernet for solving this).
Below is a full example of a custom control cust:betterinput that uses an xhtml input element for its input. It uses custom settings (and also the data type [set in Control Settings -> Validation and Alerts -> Data Type]) to configure parameters on the html element (note that the custom parameters have been changed to control parameters). Javascript is used to sync the value between the xhtml input and the xforms input (so that the value is captured by Orbeon). The example is based on number control included in Orbeon
xbl/cust/betterinput/betterinput.xbl
<xbl:xbl xmlns:xh="http://www.w3.org/1999/xhtml"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xxi="http://orbeon.org/oxf/xml/xinclude"
xmlns:xxf="http://orbeon.org/oxf/xml/xforms"
xmlns:fr="http://orbeon.org/oxf/xml/form-runner"
xmlns:saxon="http://saxon.sf.net/"
xmlns:exf="http://www.exforms.org/exf/1-0"
xmlns:oxf="http://www.orbeon.com/oxf/processors"
xmlns:xbl="http://www.w3.org/ns/xbl"
xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cust="http://www.cust.com/">
<xbl:script src="/xbl/cust/betterinput/betterinput.js"/>
<xbl:binding
id="cust-betterinput"
element="cust|betterinput"
xxbl:mode="lhha binding value focus"
xxbl:label-for="html-input">
<metadata xmlns="http://orbeon.org/oxf/xml/form-builder">
<display-name lang="en">Better Input</display-name>
<icon lang="en">
<small-icon>/forms/orbeon/builder/images/input.png</small-icon>
<large-icon>/forms/orbeon/builder/images/input.png</large-icon>
</icon>
<description lang="en"/>
<templates>
<view>
<cust:betterinput type="" prefix="" suffix="">
<xf:label ref=""/>
<xf:hint ref=""/>
<xf:help ref=""/>
<xf:alert ref=""/>
</cust:betterinput>
</view>
</templates>
<control-details>
<xf:input ref="#type">
<xf:label lang="en">Input Type</xf:label>
<xf:hint lang="en">HTML5 Input Type</xf:hint>
</xf:input>
<xf:input ref="#prefix">
<xf:label lang="en">Input Prefix</xf:label>
<xf:hint/>
</xf:input>
<xf:input ref="#suffix">
<xf:label lang="en">Input Suffix</xf:label>
<xf:hint/>
</xf:input>
</control-details>
</metadata>
<xbl:resources>
<xbl:style src="/xbl/cust/betterinput/betterinput.css"/>
</xbl:resources>
<xbl:template xxbl:transform="oxf:unsafe-xslt">
<xsl:transform version="2.0">
<xsl:import href="oxf:/oxf/xslt/utils/xbl.xsl"/>
<xsl:template match="/*">
<xsl:variable
name="js-object"
as="xs:string"
select="'YAHOO.xbl.cust.BetterInput.instance(this)'"/>
<xf:group>
<xf:action type="javascript" ev:event="xforms-disabled" ev:target="#observer">
<xsl:value-of select="$js-object"/>.destroy();
</xf:action>
<xf:var name="binding" value="xxf:binding('cust-betterinput')"/>
<xf:var name="view"
value="exf:readonly($binding) and property('xxf:readonly-appearance') = 'static'"/>
<xf:action
ev:target="#observer"
ev:event="xforms-enabled xforms-value-changed">
<xxf:script>
<xsl:value-of select="$js-object"/>.updateWithServerValue();
</xxf:script>
</xf:action>
<xf:action
ev:target="#observer"
ev:event="DOMFocusOut">
<xxf:script>
<xsl:value-of select="$js-object"/>.updateWithServerValue();
</xxf:script>
</xf:action>
<xf:var name="htmlinputs" value="' email url color number datetime-local '" />
<xsl:copy-of select="xxbl:parameter(., 'type')"/>
<xsl:copy-of select="xxbl:parameter(., 'prefix')"/>
<xsl:copy-of select="xxbl:parameter(., 'suffix')"/>
<xf:group ref="$binding[not($view)]">
<xf:input ref="." class="betterinput-xform-input xforms-hidden">
<xf:action type="javascript" id="xf-ro" ev:event="xforms-readonly"><xsl:value-of select="$js-object"/>.readonly();</xf:action>
<xf:action type="javascript" id="xf-rw" ev:event="xforms-readwrite"><xsl:value-of select="$js-object"/>.readwrite();</xf:action>
</xf:input>
<xh:span class="{{(if ($prefix) then 'input-prepend' else (), if ($suffix) then 'input-append' else ())}}">
<xf:group class="add-on" ref=".[$prefix]"><xf:output value="$prefix"/></xf:group>
<xh:input id="html-input" class="betterinput-html-input" type="{{
if (not(type = '') and contains($htmlinputs, concat(' ', $type, ' '))) then
$type
else
if (contains(' decimal integer double ', concat(' ', xxf:type($binding), ' '))) then
'number'
else
if (contains($htmlinputs, concat(' ', xxf:type($binding), ' '))) then
xxf:type($binding)
else
'text'
}}"/>
<xf:group class="add-on" ref=".[$suffix]"><xf:output value="$suffix"/></xf:group>
</xh:span>
</xf:group>
<!-- Static readonly mode -->
<xf:group ref="$binding[$view]" class="{{(if ($prefix) then 'input-prepend' else (), if ($suffix) then 'input-append' else ())}}">
<xf:group class="add-on" ref=".[$prefix]"><xf:output value="$prefix"/></xf:group>
<xf:input ref="$binding[$view]" class="betterinput-html-input" />
<xf:group class="add-on" ref=".[$suffix]"><xf:output value="$suffix"/></xf:group>
</xf:group>
</xf:group>
</xsl:template>
</xsl:transform>
</xbl:template>
</xbl:binding>
</xbl:xbl>
xbl/cust/betterinput/betterinput.js
(function() {
var $ = ORBEON.jQuery;
var AS = ORBEON.xforms.server.AjaxServer;
var Document = ORBEON.xforms.Document;
YAHOO.namespace("xbl.cust");
YAHOO.xbl.cust.BetterInput = function() {};
ORBEON.xforms.XBL.declareClass(YAHOO.xbl.cust.BetterInput, "xbl-cust-betterinput");
YAHOO.xbl.cust.BetterInput.prototype = {
xformsInputElement: null,
visibleInputElement: null,
prefixElement: null,
prefix: null,
init: function() {
// Get information from the DOM
console.log('betterinput init');
this.xformsInputElement = YAHOO.util.Dom.getElementsByClassName("betterinput-xform-input", null, this.container)[0];
this.visibleInputElement = YAHOO.util.Dom.getElementsByClassName("betterinput-html-input", null, this.container)[0];
// Properties
// Find prefix based on class/control name, as this JS can be used with fr:number and fr:currency and properties use the control name
var controlClassPrefix = null;
var containerClasses = this.container.className.split(" ");
for (var classIndex = 0; classIndex < containerClasses.length; classIndex++) {
var currentClass = containerClasses[classIndex];
if (currentClass.indexOf("xbl-cust-") == 0) {
controlClassPrefix = currentClass;
break;
}
}
this.prefixElement = YAHOO.util.Dom.getElementsByClassName(controlClassPrefix + "-prefix", null, this.container)[0];
this.prefix = Document.getValue(this.prefixElement.id);
// Register listeners
// Restore input type, send the value to the server, and updates value after server response
$(this.visibleInputElement).on('change blur', _.bind(function(e) {
this.sendValueToServer();
var formId = $(this.container).parents('form').attr('id');
// Always update visible value with XForms value
// - relying just value change event from server is not enough
// - value change not dispatched if server value hasn't changed
// - if visible changed, but XForms hasn't, we still need to show XForms value
// - see: https://github.com/orbeon/orbeon-forms/issues/1026
AS.nextAjaxResponse(formId).then(_.bind(this.updateWithServerValue, this));
}, this));
$(this.visibleInputElement).on('keypress', _.bind(function(e) {
if (e.which == 13)
this.sendValueToServer();
}, this));
},
setFocus: function() {
this.visibleInputElement.focus();
},
sendValueToServer: function() {
var newValue = this.visibleInputElement.value;
Document.setValue(this.xformsInputElement.id, newValue);
},
updateWithServerValue: function() {
var value = Document.getValue(this.xformsInputElement.id);
this.visibleInputElement.value = value;
// Also update disabled because this might be called upon an iteration being moved, in which case all the control properties must be updated
this.visibleInputElement.disabled = YAHOO.util.Dom.hasClass(this.xformsInputElement, "xforms-readonly");
},
readonly: function() {
this.visibleInputElement.disabled = true;
},
readwrite: function() {
this.visibleInputElement.disabled = false;
},
};
})();

FireFox Extension XulSchool - Adding Events and Commands - on load function

I'm trying to edit the helloworld from this tutorial:
https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Getting_Started_with_Firefox_Extensions
So, I need to execute a function when page loads, this part of the tutorial explains how to do that:
https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Adding_Events_and_Commands
I edited the browserOverlay.js file, I writed the init function and that listener at the end of the code:
/**
* XULSchoolChrome namespace.
*/
if ("undefined" == typeof(XULSchoolChrome)) {
var XULSchoolChrome = {};
};
/**
* Controls the browser overlay for the Hello World extension.
*/
XULSchoolChrome.BrowserOverlay = {
init : function(event) {
window.alert('aaaaeeee');
},
/**
* Says 'Hello' to the user.
*/
sayHello : function(aEvent) {
let stringBundle = document.getElementById("xulschoolhello-string-bundle");
let message = stringBundle.getString("xulschoolhello.greeting.label");
window.alert(message);
}
};
window.addEventListener("load", function() {
//window.removeEventListener("load", onFirefoxLoadEvent, false); // remove listener, no longer needed
XULSchoolChrome.BrowserOverlay.init();
}, false);
and I edited the broserOverlay.xul, I writed the onload propertie at overlay tag:
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin/" ?>
<?xml-stylesheet type="text/css"
href="chrome://xulschoolhello/skin/browserOverlay.css" ?>
<!DOCTYPE overlay SYSTEM
"chrome://xulschoolhello/locale/browserOverlay.dtd">
<overlay id="xulschoolhello-browser-overlay"
onload="XulSchoolChrome.BrowserOverlay.init();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript"
src="chrome://xulschoolhello/content/browserOverlay.js" />
<stringbundleset id="stringbundleset">
<stringbundle id="xulschoolhello-string-bundle"
src="chrome://xulschoolhello/locale/browserOverlay.properties" />
</stringbundleset>
<menubar id="main-menubar">
<menu id="xulschoolhello-hello-menu" label="&xulschoolhello.hello.label;"
accesskey="&xulschoolhello.helloMenu.accesskey;" insertafter="helpMenu">
<menupopup>
<menuitem id="xulschoolhello-hello-menu-item"
label="&xulschoolhello.hello.label;"
accesskey="&xulschoolhello.helloItem.accesskey;"
oncommand="XULSchoolChrome.BrowserOverlay.sayHello(event);" />
</menupopup>
</menu>
</menubar>
<vbox id="appmenuSecondaryPane">
<menu id="xulschoolhello-hello-menu-2" label="&xulschoolhello.hello.label;"
accesskey="&xulschoolhello.helloMenu.accesskey;"
insertafter="appmenu_addons">
<menupopup>
<menuitem id="xulschoolhello-hello-menu-item-2"
label="&xulschoolhello.hello.label;"
accesskey="&xulschoolhello.helloItem.accesskey;"
oncommand="XULSchoolChrome.BrowserOverlay.sayHello(event);" />
</menupopup>
</menu>
</vbox>
</overlay>
It does not alert on page loads.... why?

How to construct a settings page similar to the stock one in Windows Phone 7?

When I launch the Windows Phone Settings app, what is presented is a pivot control with a bunch of items on each page. For example, the first item is:
THEME
blue
What is the standard way of creating these items? I want them to have the same font style and look. Is there any control to represent the item above?
Thanks!
It is a ListBox control and a DataTemplate for eacht item. The template defines two TextBox controls, one for the 'title' and one for a 'description/value'. You can set the style for each TextBox.
Edit: here's an example code
<ListBox x:Name="YourListBox" Margin="0" ItemsSource="{Binding YourItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding ItemTitle}" TextWrapping="Wrap" Style="{StaticResource PhoneTextTitle2Style}"/>
<TextBlock Text="{Binding ItemValue}" TextWrapping="Wrap" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
After playing with this for 2 hours pixel by pixel and using a paint program to compare screenshots with the real thing side by side (there's gotta be a better way!), I found the following solution.
Here is the settings page replicated exactly. I've created a custom user control so that adding an item to xaml is as easy as this:
<MyApp:SettingsItem Label="theme" Sub="blue"/>
Here's the code:
SettingsItem.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace MyApp
{
// This custom control class is to show a standard item in a settings page.
public partial class SettingsItem : UserControl
{
private const string TAG = "SettingsItem";
public SettingsItem()
{
// Required to initialize variables
InitializeComponent();
}
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register(
"Label",
typeof(string),
typeof(SettingsItem),
new PropertyMetadata(new PropertyChangedCallback
(OnLabelChanged)));
public string Label
{
get
{
return (string)GetValue(LabelProperty);
}
set
{
SetValue(LabelProperty, value);
}
}
private static void OnLabelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
SettingsItem item = (SettingsItem)d;
string newValue = (string)e.NewValue;
item.m_label.Text = newValue.ToLower();
}
public static readonly DependencyProperty SubProperty =
DependencyProperty.Register(
"Sub",
typeof(string),
typeof(SettingsItem),
new PropertyMetadata(new PropertyChangedCallback
(OnSubChanged)));
public string Sub
{
get
{
return (string)GetValue(SubProperty);
}
set
{
SetValue(SubProperty, value);
}
}
private static void OnSubChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
SettingsItem item = (SettingsItem)d;
string newValue = (string)e.NewValue;
item.m_sub.Text = newValue;
}
}
}
SettingsItem.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="MyApp.SettingsItem"
d:DesignWidth="428" d:DesignHeight="0">
<Grid x:Name="LayoutRoot" Background="Transparent">
<StackPanel
Orientation="Vertical"
>
<TextBlock
x:Name="m_label"
Text="label"
Style="{StaticResource PhoneTextExtraLargeStyle}"
Margin="0, 18, 0, 0"
/>
<TextBlock
x:Name="m_sub"
Text="Sub"
Style="{StaticResource PhoneTextSubtleStyle}"
TextWrapping="Wrap"
Margin="0, -6, 0, 0"
/>
</StackPanel>
</Grid>
</UserControl>
and here's the page xaml:
<phone:PhoneApplicationPage
x:Class="MyApp.SettingsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
xmlns:MyApp="clr-namespace:MyApp;assembly=MyApp"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"
mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Pivot Control-->
<controls:Pivot Title="MY APP" SelectedIndex="0">
<controls:PivotItem
Name="PIVOT_GENERAL"
Margin="0"
Header="settings">
<Grid Margin="26,9,0,0">
<StackPanel>
<MyApp:SettingsItem
Label="theme"
Sub="blue"
/>
<MyApp:SettingsItem
Label="date+time"
Sub="UTC-08 Pacific Time (US + Canada)"
/>
<MyApp:SettingsItem
Label="region+language"
Sub="United States"
/>
</StackPanel>
</Grid>
</controls:PivotItem>
</controls:Pivot>
</Grid>
</phone:PhoneApplicationPage>
My suggestion:
<Button>
<Button.Template>
<ControlTemplate>
<StackPanel>
<TextBlock Text="Label" Style="{StaticResource PhoneTextExtraLargeStyle}" Margin="0 18 0 0"/>
<TextBlock Text="Sub" Style="{StaticResource PhoneTextSubtleStyle}" Margin="0 -6 0 0"/>
</StackPanel>
</ControlTemplate>
</Button.Template>

Custom Silverlight button with <Path> content and visual states

I would like to create a button control that has a Path element as its content--an IconButton if you will.
This button should fulfill two conditions:
1. The Path element's stroke and fill colors should be available for manipulation by the VisualStateManager.
2. The Path element's data string (which defines it's shape) can be set in code or in XAML, so that I can create several such buttons without creating a new custom control for each.
The only way I can see to do it would involve no XAML whatsoever. That is, setting all the visual states and animations in the code behind, plus generating the Geometry objects point by point (and not being able to use the convenient Data string property on the Path element).
Is there a simpler way to do this?
EDIT
So one of the limitations I'm running into is that Silverlight does not support the mini-language for path expressions in PathGeometry, only in Path. I've got some detailed geometry going on in some of these icons, and I really don't want to take the convenient strings I generated with an Illustrator plug-in (pretty sure it was this one) and make them into separate line segments and curves.
Nice one Chris. If you want to store the paths as resources, you can modify things a bit like so:
First change the dependency property to a Path type:
/// <summary>
/// Button that contains an icon, where the icon is drawn from a path.
/// </summary>
public class IconButton : Button
{
/// <summary>
/// The path data that draws the icon.
/// </summary>
public static readonly DependencyProperty IconPathProperty =
DependencyProperty.Register("IconPath", typeof(Path), typeof(IconButton), new PropertyMetadata(default(Path)));
/// <summary>
/// Depencendy property backer.
/// </summary>
public Path IconPath
{
get { return (Path)GetValue(IconPathProperty); }
set { SetValue(IconPathProperty, value); }
}
}
Next, here's the IconButton control template.
<!-- Icon Button Control Template -->
<ControlTemplate x:Key="IconButtonControlTemplate" TargetType="{x:Type usercontrols:IconButton}">
<Grid x:Name="Grid">
<Border SnapsToDevicePixels="True" x:Name="Border" CornerRadius="1" BorderThickness="1" BorderBrush="{StaticResource ButtonBorderBrush}">
<Path x:Name="Icon" Height="16" Width="16" Stretch="Fill"
Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IconPath.Data}"
UseLayoutRounding="False" Grid.Column="1" VerticalAlignment="Center" Margin="0">
<Path.Fill>
<SolidColorBrush x:Name="IconColor" Color="{Binding Color, Source={StaticResource ButtonIconBrush}}" />
</Path.Fill>
</Path>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorderBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{StaticResource ButtonBackgroundMouseOverBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorderMouseOverBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" Value="{StaticResource ButtonBackgroundPressedBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Fill" Value="{StaticResource DisabledIconBrush}" TargetName="Icon"/>
<Setter Property="Background" Value="{StaticResource ButtonBackgroundDisabledBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorderDisabledBrush}" TargetName="Border"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
... and here's the IconButton style that uses this template, and adds a few defaults:
<!-- Icon Button Style -->
<Style TargetType="{x:Type usercontrols:IconButton}">
<Setter Property="FocusVisualStyle" Value="{DynamicResource SimpleButtonFocusVisual}"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Height" Value="26"/>
<Setter Property="Width" Value="26"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Content" Value="Button"/>
<Setter Property="Template" Value="{StaticResource IconButtonControlTemplate}"/>
</Style>
Now you can go ahead and create icons and save them in your resource file:
<!-- Undo Icon -->
<Path x:Key="UndoIcon" Stretch="Fill" Data="F1 M 87.7743,80.7396L 87.5215,75.9539L 89.8692,74.2202C 89.9775,75.0329 90.0859,76.586 90.0859,76.5318C 92.9302,73.7417 97.5369,72.9755 100.208,76.2158C 102.019,78.413 102.258,81.2543 99.7657,83.9361C 97.2735,86.6179 92.6142,90.1124 92.6142,90.1124L 90.3748,87.6744L 97.3096,81.769C 97.3096,81.769 99.1516,79.9992 97.8514,78.3558C 96.2374,76.316 94.384,77.2542 92.1447,78.8795C 92.1176,78.9608 93.3998,79.1143 94.3118,79.2768L 92.4336,81.4439L 87.7743,80.7396 Z "/>
<!-- Filter Icon -->
<Path x:Key="FilterIcon" Stretch="Fill" Data="F1 M 6,16L 10,16L 10.0208,10L 16,3L 16,2.86102e-006L 0,9.53674e-007L 0,3L 6,10L 6,16 Z "/>
And finally, create buttons:
<usercontrols:IconButton IconPath="{StaticResource UndoIcon}"></usercontrols:IconButton>
The Path Data property is simply a Geometry. When you say "several such buttons" one assumes that there are a finite number of such buttons which vary with Icon.
Store each "Icon" as a PathGeomerty in a ResourceDictionaryand have that ResourceDictionary referenced as a merged dictionary in the app.xaml.
Now you can simply assign the geomerty into the Path shap Data property.
For the second part of the problem above, here's a kludge which works pretty well for storing string Path data as resources and accessing them from code.
For the first part of the problem, I just created all the animations in code, apply them on mouse events, got rid of the XAML page altogether.
This ship may have long since sailed, but I needed something very similar and was able to roll my own by inheriting from Button:
public class PathButton : Button
{
public static readonly DependencyProperty PathDataProperty =
DependencyProperty.Register("PathData", typeof(Geometry), typeof(PathButton), new PropertyMetadata(default(Geometry)));
public Geometry PathData
{
get { return (Geometry) GetValue(PathDataProperty); }
set { SetValue(PathDataProperty, value); }
}
}
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.1"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0" To="{StaticResource BlueColor}" Storyboard.TargetProperty="Color" Storyboard.TargetName="BackgroundRectangleColor" />
<ColorAnimation Duration="0" To="{StaticResource WhiteColor}" Storyboard.TargetProperty="Color" Storyboard.TargetName="IconColor" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed"/>
<VisualState x:Name="Disabled"/>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Unfocused"/>
<VisualState x:Name="Focused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.Projection>
<PlaneProjection/>
</Grid.Projection>
<Rectangle x:Name="BackgroundRectangle" >
<Rectangle.Fill>
<SolidColorBrush x:Name="BackgroundRectangleColor" Color="{StaticResource GrayColor}" />
</Rectangle.Fill>
</Rectangle>
<Path x:Name="Icon" Stretch="Fill"
Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PathData}"
UseLayoutRounding="False" Grid.Column="1" Width="24" Height="24" VerticalAlignment="Center" Margin="0">
<Path.Fill>
<SolidColorBrush x:Name="IconColor" Color="{StaticResource LightGrayColor}" />
</Path.Fill>
</Path>
<TextBlock x:Name="Text" HorizontalAlignment="Center" TextWrapping="Wrap" Text="{TemplateBinding Content}" FontSize="9.333" FontFamily="Segoe UI Light" Foreground="{StaticResource LightGray}" Height="11" Margin="0,0,0,3" VerticalAlignment="Bottom"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Example usage:
<Controls:PathButton Content="Options" PathData="F1M50.6954,32.1146C50.7057,31.1041,50.6068,30.1107,50.4583,29.1459L54.5052,25.9193C53.4636,22.0377,51.4583,18.5612,48.7409,15.7604L43.9063,17.5885C42.3776,16.3229,40.6407,15.3099,38.7552,14.5814L37.9857,9.47656C36.1237,8.98181 34.1719,8.68225 32.1511,8.66663 30.1302,8.65625 28.1771,8.92578 26.2995,9.39844L25.4714,14.4948C23.5794,15.2057,21.8229,16.1901,20.2813,17.4401L15.4675,15.5521C12.7201,18.3138,10.6706,21.7631,9.57556,25.6328L13.582,28.9088C13.4193,29.875 13.3164,30.8658 13.3047,31.8776 13.2969,32.8984 13.3945,33.8854 13.5469,34.8588L9.49353,38.0807C10.5417,41.9584,12.5469,45.4401,15.2604,48.2383L20.0938,46.4127C21.6224,47.6744,23.3659,48.6875,25.2513,49.4193L26.0091,54.5234C27.8802,55.0209 29.8333,55.3177 31.8503,55.3334 33.8698,55.3385 35.8243,55.0729 37.6979,54.6041L38.5352,49.5C40.4219,48.7916,42.1784,47.806,43.7253,46.5664L48.53,48.4531C51.2813,45.6836,53.3268,42.233,54.4245,38.3685L50.418,35.0963C50.5833,34.1224,50.6862,33.1354,50.6954,32.1146 M31.9362,41.6615C26.6068,41.6302 22.3073,37.2734 22.3411,31.9375 22.3776,26.6002 26.7266,22.3008 32.0651,22.3359 37.4011,22.3698 41.7005,26.7252 41.6653,32.0625 41.629,37.4023 37.2786,41.6979 31.9362,41.6615"
Style="{StaticResource PathButtonStyle1}" Grid.RowSpan="2" Grid.Column="1" />

Resources