Re-use Animations in VisualStates - silverlight

What I want to do (in pseudo);
<!-- As a resource -->
<Storyboard x:Key="RepetitiveAnimation">
<DoubleAnimation .../>
</Storyboard>
<!-- In the templates -->
<VisualStateManager>
<VisualState x:Name="blah" Storyboard="{StaticResource RepetitiveAnimation}"/>
</VisualStateManager>
or even like;
<VisualStateManager>
<VisualState x:Name="blah">
<BeginStoryboard Storyboard="{StaticResource RepetitiveAnimation}"/>
</VisualState>
</VisualStateManager>
What doesnt work; See above.
You can do this in WPF, but in SL there's no joy. Is there an equivalent I'm missing?
Cheers!

I tried reusing Storyboards in my VisualStates as well, but to no avail.
But there is a thing you can do that may mitigate the problem:
Define your own AttachedProperties (attachable to instances of VisualState) for frequently used Storyboard tasks, for example:
setting Visibility on named Elements
applying Opacity values to named Elements
even setting Focus via a Storyboard.
The xaml would look like this:
<VisualStateGroup x:Name="TravelPlanningSteps">
<VisualState x:Name="DateSelection"
utils:VisualStateUtils.VisibleElements="DateSelector"
utils:VisualStateUtils.FocusedElement="DateSelector"/>
<VisualState x:Name="HotelSelection"
utils:VisualStateUtils.VisibleElements="HotelSelector, BreakfastSelector"
utils:VisualStateUtils.FocusedElement="HotelSelector"/>
</VisualStateGroup>
And the code (sorry, it's a bit lengthy):
public static class VisualStateUtils
{
#region FocusedElement
public static string GetFocusedElement( VisualState obj )
{
return (string) obj.GetValue( FocusedElementProperty );
}
public static void SetFocusedElement( VisualState obj, string value )
{
obj.SetValue( FocusedElementProperty, value );
}
public static readonly DependencyProperty FocusedElementProperty =
DependencyProperty.RegisterAttached( "FocusedElement", typeof( string ), typeof( VisualStateUtils ), new PropertyMetadata( HandleFocusedElementChanged ) );
private static void HandleFocusedElementChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var state = d as VisualState;
if (state == null) return;
string elementName = e.NewValue as string;
if (elementName == null) return;
var storyBoard = state.Storyboard;
if (storyBoard == null)
{
storyBoard = new Storyboard();
state.Storyboard = storyBoard;
}
ClearAutoDefinedFocusClaim( storyBoard );
var ani = new ObjectAnimationUsingKeyFrames();
Storyboard.SetTargetName( ani, elementName );
Storyboard.SetTargetProperty( ani, new PropertyPath( VisualStateUtils.FocusClaimProperty ) );
VisualStateUtils.SetIsAutoDefinedFocusClaim( ani, true );
ani.KeyFrames.Add( new DiscreteObjectKeyFrame { Value = true, KeyTime = KeyTime.FromTimeSpan( TimeSpan.FromMilliseconds( 0 ) ) } );
storyBoard.Children.Add( ani );
}
public static bool GetFocusClaim( Control focusTarget )
{
return (bool) focusTarget.GetValue( FocusClaimProperty );
}
public static void SetFocusClaim( Control focusTarget, bool value )
{
focusTarget.SetValue( FocusClaimProperty, value );
}
public static readonly DependencyProperty FocusClaimProperty =
DependencyProperty.RegisterAttached( "FocusClaim", typeof( bool ), typeof( VisualStateUtils ), new PropertyMetadata( false, HandleFocusClaimChanged ) );
private static void HandleFocusClaimChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var focusTarget = d as Control;
if(focusTarget==null) return;
var shouldReceiveFocusNow = (bool) e.NewValue;
if (shouldReceiveFocusNow)
{
if (!focusTarget.Focus()) CheckFocusability( focusTarget );
}
}
private static void CheckFocusability(Control focusTarget)
{
//so the focus() call was not successful, what's the problem? lets see...
//the control may still be collapsed
//(and another animation will turn it visible anytime soon, remember: we are part of ongoing VisualState switching)
if (!focusTarget.IsLoaded())
focusTarget.Loaded += HandleFocusTargetLoaded;
//it may be disabled (and another animation will enable it)
else if (!focusTarget.IsEnabled)
focusTarget.IsEnabledChanged += HandleFocusTargetEnabled;
}
private static void HandleFocusTargetLoaded( object sender, RoutedEventArgs routedEventArgs )
{
var focusTarget = (Control) sender;
focusTarget.Loaded -= HandleFocusTargetLoaded;
focusTarget.Focus();
}
private static void HandleFocusTargetEnabled(object sender, DependencyPropertyChangedEventArgs e)
{
var focusTarget = (Control) sender;
focusTarget.IsEnabledChanged -= HandleFocusTargetEnabled;
focusTarget.Focus();
}
public static bool GetIsAutoDefinedFocusClaim( DependencyObject obj )
{
return (bool) obj.GetValue( IsAutoDefinedFocusClaimProperty );
}
public static void SetIsAutoDefinedFocusClaim( DependencyObject obj, bool value )
{
obj.SetValue( IsAutoDefinedFocusClaimProperty, value );
}
public static readonly DependencyProperty IsAutoDefinedFocusClaimProperty =
DependencyProperty.RegisterAttached( "IsAutoDefinedFocusClaim", typeof( bool ), typeof( VisualStateUtils ), new PropertyMetadata( false ) );
private static void ClearAutoDefinedFocusClaim( Storyboard storyBoard )
{
var toDelete = storyBoard.Children.Where( VisualStateUtils.GetIsAutoDefinedFocusClaim ).ToList();
toDelete.ForEach( animation => storyBoard.Children.Remove( animation ) );
}
#endregion
#region CollapsedElements
public static readonly DependencyProperty IsAutoCreatedCollapsedElementProperty =
DependencyProperty.RegisterAttached( "IsAutoCreatedCollapsedElement", typeof( bool ), typeof( VisualStateUtils ), new PropertyMetadata( default( bool ) ) );
private static void SetIsAutoCreatedCollapsedElement( DependencyObject element, bool value )
{
element.SetValue( IsAutoCreatedCollapsedElementProperty, value );
}
private static bool GetIsAutoCreatedCollapsedElement( DependencyObject element )
{
return (bool) element.GetValue( IsAutoCreatedCollapsedElementProperty );
}
public static readonly DependencyProperty CollapsedElementsProperty =
DependencyProperty.RegisterAttached( "CollapsedElements", typeof( string ), typeof( VisualStateUtils ), new PropertyMetadata( default( string ), HandleCollapsedElementsChanged ) );
private static void HandleCollapsedElementsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var state = d as VisualState;
if (state == null) return;
string elementNames = e.NewValue as string;
if (elementNames == null) return;
CreateAutoDefinedAnimationsForVisibility( Visibility.Collapsed, state, elementNames );
}
public static void SetCollapsedElements( VisualState state, string value )
{
state.SetValue( CollapsedElementsProperty, value );
}
public static string GetCollapsedElements( VisualState state )
{
return (string) state.GetValue( CollapsedElementsProperty );
}
#endregion
#region VisibleElements
public static readonly DependencyProperty VisibleElementsProperty =
DependencyProperty.RegisterAttached( "VisibleElements", typeof( string ), typeof( VisualStateUtils ), new PropertyMetadata( default( string ), HandleVisibleElementsChanged ) );
private static void HandleVisibleElementsChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var state = d as VisualState;
if (state == null) return;
string elementNames = e.NewValue as string;
if (elementNames == null) return;
CreateAutoDefinedAnimationsForVisibility( Visibility.Visible, state, elementNames );
}
public static void SetVisibleElements( VisualState state, string value )
{
state.SetValue( VisibleElementsProperty, value );
}
public static string GetVisibleElements( VisualState state )
{
return (string) state.GetValue( VisibleElementsProperty );
}
public static readonly DependencyProperty IsAutoCreatedVisibleElementProperty =
DependencyProperty.RegisterAttached( "IsAutoCreatedVisibleElement", typeof( bool ), typeof( VisualStateUtils ), new PropertyMetadata( default( bool ) ) );
private static void SetIsAutoCreatedVisibleElement( DependencyObject element, bool value )
{
element.SetValue( IsAutoCreatedVisibleElementProperty, value );
}
private static bool GetIsAutoCreatedVisibleElement( DependencyObject element )
{
return (bool) element.GetValue( IsAutoCreatedVisibleElementProperty );
}
#endregion
private static void CreateAutoDefinedAnimationsForVisibility( Visibility visibility, VisualState state, string elementNames )
{
var storyBoard = state.Storyboard;
if (storyBoard == null)
{
storyBoard = new Storyboard();
state.Storyboard = storyBoard;
}
ClearAutoDefinedElementAnimations( visibility, storyBoard );
string[] namesOfManipulatedElements = (elementNames ?? string.Empty).Split( ',' );
namesOfManipulatedElements = namesOfManipulatedElements.Select( name => name.Trim() ).ToArray();
foreach (var elementName in namesOfManipulatedElements)
{
var ani = new ObjectAnimationUsingKeyFrames();
Storyboard.SetTargetName( ani, elementName );
Storyboard.SetTargetProperty( ani, new PropertyPath( "Visibility" ) );
MarkAutoDefinedElementAnimation( visibility, ani );
ani.KeyFrames.Add( new DiscreteObjectKeyFrame { Value = visibility, KeyTime = KeyTime.FromTimeSpan( TimeSpan.FromMilliseconds( 0 ) ) } );
storyBoard.Children.Add( ani );
}
}
private static void ClearAutoDefinedElementAnimations( Visibility visibility, Storyboard storyBoard )
{
if (visibility == Visibility.Visible)
{
var toDelete = storyBoard.Children.Where( VisualStateUtils.GetIsAutoCreatedVisibleElement ).ToList();
toDelete.ForEach( ani => storyBoard.Children.Remove( ani ) );
}
else
{
var toDelete = storyBoard.Children.Where( VisualStateUtils.GetIsAutoCreatedCollapsedElement ).ToList();
toDelete.ForEach( ani => storyBoard.Children.Remove( ani ) );
}
}
private static void MarkAutoDefinedElementAnimation( Visibility visibility, ObjectAnimationUsingKeyFrames animation )
{
if (visibility == Visibility.Visible)
VisualStateUtils.SetIsAutoCreatedVisibleElement( animation, true );
else
VisualStateUtils.SetIsAutoCreatedCollapsedElement( animation, true );
}
}
And not to forget the handy Extension to check whether a control is loaded or not:
public static class ControlExtensions
{
public static bool IsLoaded(this FrameworkElement element)
{
return element.GetVisualChildren().Any();
//or just check the parent ...not sure what's better
//return System.Windows.Media.VisualTreeHelper.GetParent(element) != null;
}
}

Related

Inserting an item in an ItemsControl

Here are the requirements.
In my ItemsControl (you can use a ListView if it helps you to consider the scenario). I want to inject, not a record, but a unique DataTemplate into an arbitrary indexed location in my list.
For example, I might want to insert it into the first position, index 0, or the third position, index 2, or perhaps even have the logic to insert it into the last position, index count-1.
I will need to sub-class ListView to accomplish this, I realize. That being said, I could easily create the SpecialItemTemplate, SpecialItemIndex DP properties to have the values.
Added requirements:
Don't require a special type of collection
Don't require manipulating the existing data
Allow the IsHitTestVisible to be variable, too
Any ideas how to accomplish this feat (in WinRT)?
Here is a solution that is basically a Behavior with a Template property which can be attached to any ItemsControl. I tested it with virtualizing and non-virtualizing panels, works for both cases. If you think the code is convoluted... well I can't disagree, wrote it a while back and I can't remember what reasons I had to write it the way it ended up.
Usage:
<ListBox ItemsSource="{Binding Persons}">
<Interactivity:Interaction.Behaviors>
<AlternativeItemTemplate Index="42">
<DataTemplate>
...foo...
</DataTemplate>
</AlternativeItemTemplate>
</Interactivity:Interaction.Behaviors>
<ListBox.ItemTemplate>
<DataTemplate>
...bar...
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and the classes:
[ContentProperty( "ItemTemplate" )]
public class AlternativeItemTemplate : ItemContainerDecorator
{
public DataTemplate ItemTemplate
{
get { return (DataTemplate) GetValue( ItemTemplateProperty ); }
set { SetValue( ItemTemplateProperty, value ); }
}
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register( "ItemTemplate", typeof( DataTemplate ), typeof( AlternativeItemTemplate ), new PropertyMetadata( null ) );
public int Index
{
get { return (int) GetValue( IndexProperty ); }
set { SetValue( IndexProperty, value ); }
}
public static readonly DependencyProperty IndexProperty =
DependencyProperty.Register( "Index", typeof( int ), typeof( AlternativeItemTemplate ), new PropertyMetadata( -1 ) );
protected override void OnContainersChanged()
{
if (!AssociatedObject.Items.Any() || Index < 0 || Index >= AssociatedObject.Items.Count)
{
ItemContentPresenter = null;
ItemContentControl = null;
m_overwrittenTemplate = null;
return;
}
TryUpdateItem( ItemContainerGenerator.ContainerFromItem( AssociatedObject.Items[Index] ) );
}
private ContentPresenter ItemContentPresenter { get; set; }
private ContentControl ItemContentControl { get; set; }
private DataTemplate m_overwrittenTemplate;
private void TryUpdateItem( DependencyObject itemContainer )
{
if (itemContainer == null)
{
ResetOverwrittenTemplate();
}
var containerAsPresenter = itemContainer as ContentPresenter;
if (containerAsPresenter != null) UpdateItemContentPresenter( containerAsPresenter );
else
{
var containerAsControl = itemContainer as ContentControl;
if (containerAsControl != null) UpdateItemContentControl( containerAsControl );
}
}
private void ResetOverwrittenTemplate()
{
if (ItemContentPresenter != null)
ItemContentPresenter.ContentTemplate = m_overwrittenTemplate;
if (ItemContentControl != null)
ItemContentControl.ContentTemplate = m_overwrittenTemplate;
ItemContentPresenter = null;
ItemContentControl = null;
m_overwrittenTemplate = null;
}
private void UpdateItemContentPresenter( ContentPresenter container )
{
if (ItemContentPresenter != null)
ItemContentPresenter.ContentTemplate = m_overwrittenTemplate;
ItemContentPresenter = container;
m_overwrittenTemplate = ItemContentPresenter.ContentTemplate;
ItemContentPresenter.ContentTemplate = ItemTemplate;
}
private void UpdateItemContentControl( ContentControl container )
{
if (ItemContentControl != null)
ItemContentControl.ContentTemplate = m_overwrittenTemplate;
ItemContentControl = container;
m_overwrittenTemplate = ItemContentControl.ContentTemplate;
ItemContentControl.ContentTemplate = ItemTemplate;
}
}
public abstract class ItemContainerDecorator : Behavior<ItemsControl>
{
private Dictionary<object, DependencyObject> LastKnownContainers = new Dictionary<object, DependencyObject>();
protected ItemContainerGenerator ItemContainerGenerator { get { return (AssociatedObject != null) ? AssociatedObject.ItemContainerGenerator : null; } }
protected override void OnAttached()
{
base.OnAttached();
ItemContainerGenerator.ItemsChanged += HandleItemsChangedInitially;
if (!TryAddObservers())
{
AssociatedObject.Loaded += AddObserversOnLoaded;
}
AssociatedObject.Loaded += OnItemsControlLoaded;
AssociatedObject.LayoutUpdated += OnItemsControlLayoutUpdated;
CheckContainersChanged();
}
private void OnItemsControlLayoutUpdated(object sender, EventArgs eventArgs)
{
CheckContainersChanged();
}
private void OnItemsControlLoaded(object sender, RoutedEventArgs e)
{
CheckContainersChanged();
}
private void AddObserversOnLoaded( object sender, RoutedEventArgs e )
{
AssociatedObject.Loaded -= AddObserversOnLoaded;
TryAddObservers();
}
private bool TryAddObservers()
{
const bool success = true;
Panel itemsHost =
AssociatedObject.GetVisualDescendants().OfType<Panel>().FirstOrDefault( panel => panel.IsItemsHost );
if (itemsHost != null)
{
var virtualizingItemsHost = itemsHost as VirtualizingPanel;
if (virtualizingItemsHost != null)
{
virtualizingItemsHost.LayoutUpdated += OnVirtualizingItemsHostLayoutUpdated;
m_virtualizingItemsHost = virtualizingItemsHost;
}
return success;
}
return !success;
}
private VirtualizingPanel m_virtualizingItemsHost;
private bool LayoutUpdatedOccurredFirst;
private void OnVirtualizingItemsHostLayoutUpdated( object sender, EventArgs eventArgs )
{
LayoutUpdatedOccurredFirst = true;
CheckContainersChanged();
}
protected override void OnDetaching()
{
ItemContainerGenerator.ItemsChanged -= HandleItemsChangedInitially;
ItemContainerGenerator.ItemsChanged -= HandleItemsChanged;
AssociatedObject.Loaded -= OnItemsControlLoaded;
AssociatedObject.LayoutUpdated -= OnItemsControlLayoutUpdated;
AssociatedObject.Loaded -= AddObserversOnLoaded;
if (m_virtualizingItemsHost != null) m_virtualizingItemsHost.LayoutUpdated -= OnVirtualizingItemsHostLayoutUpdated;
m_virtualizingItemsHost = null;
base.OnDetaching();
}
private void HandleItemsChangedInitially( object sender, ItemsChangedEventArgs e )
{
ItemContainerGenerator.ItemsChanged -= HandleItemsChangedInitially;
if (!LayoutUpdatedOccurredFirst)
{
//sometimes calling UpdateLayout throws an ArgumentException
//don't know why so we just swallow it
//it's not particularly important
try
{
AssociatedObject.UpdateLayout();
}
catch (ArgumentException) { }
}
ItemContainerGenerator.ItemsChanged += HandleItemsChanged;
CheckContainersChanged();
}
private void HandleItemsChanged( object sender, ItemsChangedEventArgs e )
{
CheckContainersChanged();
}
private void CheckContainersChanged()
{
var newestContainers = new Dictionary<object, DependencyObject>();
foreach (var item in AssociatedObject.Items)
{
newestContainers[item] = ItemContainerGenerator.ContainerFromItem( item );
}
if (!LastKnownContainers.SequenceEqual( newestContainers ))
{
LastKnownContainers = newestContainers;
OnContainersChanged();
}
}
protected abstract void OnContainersChanged();
}

Evaluate Binding Path Dynamically in Silverlight in XAML

How can I evaluate a binding path dynamically, that is, the actual path comes from a property? For example, suppose I have a property of DisplayMemberPath, and I wanted to have something like:
Content="{Binding Path=(DisplayMemberPath)}"
I know that this doesn't work, it's just to explain what I wanted.
I know how to build a custom DataTemplate by code and set it dynamically, but that's not what I want.
You can use a custom behavior:
<SomeControl>
<Interaction.Behaviors>
<DynamicBindingBehavior TargetProperty="Content"
BindingPath="{Binding DisplayMemberPath}"/>
</Interaction.Behaviors>
...
</SomeControl>
and the code:
public class DynamicBindingBehavior : Behavior<DependencyObject>
{
private string m_targetProperty;
public string TargetProperty
{
get { return m_targetProperty; }
set
{
m_targetProperty = value;
TryFindTargetProperty();
}
}
private DependencyProperty m_targetDependencyProperty;
private void TryFindTargetProperty()
{
if (m_targetProperty == null || AssociatedObject == null)
{
m_targetDependencyProperty = null;
}
else
{
var targetDependencyPropertyInfo = AssociatedObject.GetType()
.GetProperty( TargetProperty + "Property", typeof( DependencyProperty ) );
m_targetDependencyProperty =
(DependencyProperty) targetDependencyPropertyInfo.GetValue(
AssociatedObject, null );
}
}
public string BindingPath
{
get { return (string) GetValue( BindingPathProperty ); }
set { SetValue( BindingPathProperty, value ); }
}
public static readonly DependencyProperty BindingPathProperty =
DependencyProperty.Register( "BindingPath", typeof( string ),
typeof( DynamicBindingBehavior ),
new PropertyMetadata( BindingPathChanged ) );
private static void BindingPathChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((DynamicBindingBehavior) d).BindingPathChanged();
}
private void BindingPathChanged()
{
if (m_targetDependencyProperty == null) return;
if (BindingPath == null)
{
AssociatedObject.ClearValue(m_targetDependencyProperty);
}
else
{
BindingOperations.SetBinding( AssociatedObject,
m_targetDependencyProperty, new Binding( BindingPath ) );
}
}
protected override void OnAttached()
{
base.OnAttached();
TryFindTargetProperty();
}
protected override void OnDetaching()
{
if (m_targetDependencyProperty != null)
AssociatedObject.ClearValue( m_targetDependencyProperty );
base.OnDetaching();
m_targetDependencyProperty = null;
}
}

How to bind an object's boolean property to a CheckBox's IsChecked property?

I have an ObservableCollection of objects that have a boolean property.
In the GUI, I have a CheckBox from which I want to bind its IsChecked property to each object's boolean property.
Is it possible using XAML only? How?
I want to do it only with binding cause biding is faster than loop
Try this:
<ListBox ItemsSource={Binding path}>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding yourBoolPropertyName, Mode = TwoWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This will create list of checkboxes that will bind to your collection. Of course, you must properly set paths for binding.
Create a bool property on your ViewModel which will loop through all Objects of your ObservableCollection to see property is true for every object -
public bool AllTrue
{
get
{
return Objects.All(o => o.Selected);
}
}
Here Objects is instance of your ObservableCollection and Selected is a bool property in an object.
XAML
<CheckBox IsChecked="{Binding AllTrue}"/>
I have created a behavior to allow a property in a control to be bound to a property of a collection of items, in a way that:
If you change the property in the control, all of the items are updated.
If you change the property in a item, if all the items have the same property the control will reflect it. If not, the property of the control will be given a fallback value (like null).
public class CollectionPropertyBehavior : Behavior<DependencyObject>
{
private IEnumerable<ValueProxy> proxies;
private bool syncking;
public string SourcePropertyPath
{
get { return (string)GetValue(SourcePropertyPathProperty); }
set { SetValue(SourcePropertyPathProperty, value); }
}
public static readonly DependencyProperty SourcePropertyPathProperty =
DependencyProperty.Register("SourcePropertyPath", typeof(string), typeof(CollectionPropertyBehavior), new PropertyMetadata(null));
public string CollectionPropertyPath
{
get { return (string)GetValue(CollectionPropertyPathProperty); }
set { SetValue(CollectionPropertyPathProperty, value); }
}
public static readonly DependencyProperty CollectionPropertyPathProperty =
DependencyProperty.Register("CollectionPropertyPath", typeof(string), typeof(CollectionPropertyBehavior), new PropertyMetadata(null));
private IEnumerable<object> Items { get { return this.ItemsSource == null ? null : this.ItemsSource.OfType<object>(); } }
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CollectionPropertyBehavior), new PropertyMetadata(null, ItemsSourceChanged));
private object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(CollectionPropertyBehavior), new PropertyMetadata(null, ValueChanged));
public object DefaultValue
{
get { return (object)GetValue(DefaultValueProperty); }
set { SetValue(DefaultValueProperty, value); }
}
public static readonly DependencyProperty DefaultValueProperty =
DependencyProperty.Register("DefaultValue", typeof(object), typeof(CollectionPropertyBehavior), new PropertyMetadata(null));
private static void ValueChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var element = sender as CollectionPropertyBehavior;
if (element == null || element.ItemsSource == null) return;
element.UpdateCollection();
}
private static void ItemsSourceChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var element = sender as CollectionPropertyBehavior;
if (element == null || element.ItemsSource == null) return;
element.ItemsSourceChanged();
}
private void ItemsSourceChanged()
{
this.proxies = null;
if (this.Items == null || !this.Items.Any() || this.CollectionPropertyPath == null) return;
// Cria os proxies
this.proxies = this.Items.Select(o =>
{
var proxy = new ValueProxy();
proxy.Bind(o, this.CollectionPropertyPath);
proxy.ValueChanged += (s, e) => this.UpdateSource();
return proxy;
}).ToArray();
this.UpdateSource();
}
private void UpdateSource()
{
if (this.syncking) return;
// Atualiza o valor
using (new SynckingScope(this))
{
object value = this.proxies.First().Value;
foreach (var proxy in this.proxies.Skip(1))
{
value = object.Equals(proxy.Value, value) ? value : this.DefaultValue;
}
this.Value = value;
}
}
private void UpdateCollection()
{
// Se o valor estiver mudando em função da atualização de algum
// elemento da coleção, não faz nada
if (this.syncking) return;
using (new SynckingScope(this))
{
// Atualiza todos os elementos da coleção,
// atrávés dos proxies
if (this.proxies != null)
foreach (var proxy in this.proxies)
proxy.Value = this.Value;
}
}
protected override void OnAttached()
{
base.OnAttached();
// Bind da propriedade do objeto fonte para o behavior
var binding = new Binding(this.SourcePropertyPath);
binding.Source = this.AssociatedObject;
binding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(this, ValueProperty, binding);
}
protected override void OnDetaching()
{
base.OnDetaching();
// Limpa o binding de value para a propriedade do objeto associado
this.ClearValue(ValueProperty);
}
internal class SynckingScope : IDisposable
{
private readonly CollectionPropertyBehavior parent;
public SynckingScope(CollectionPropertyBehavior parent)
{
this.parent = parent;
this.parent.syncking = true;
}
public void Dispose()
{
this.parent.syncking = false;
}
}
internal class ValueProxy : DependencyObject
{
public event EventHandler ValueChanged;
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(ValueProxy), new PropertyMetadata(null, OnValueChanged));
private static void OnValueChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var element = sender as ValueProxy;
if (element == null || element.ValueChanged == null) return;
element.ValueChanged(element, EventArgs.Empty);
}
public void Bind(object source, string path)
{
// Realiza o binding de value com o objeto desejado
var binding = new Binding(path);
binding.Source = source;
binding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(this, ValueProperty, binding);
}
}
}
You can use it like this:
<CheckBox>
<i:Interaction.Behaviors>
<local:CollectionPropertyBehavior CollectionPropertyPath="MyBooleanProperty" SourcePropertyPath="IsChecked" ItemsSource="{Binding CollectionInViewModel}"/>
</i:Interaction.Behaviors>
</CheckBox>
It doesn't support collection changes yet (just collection swap), but I believe it can be easily modified to to that. If you want to use it out of the box, you can just add a handler to the CollectionChanged event of your ObservableCollection so it will trigger the ItemsSource update:
observableCollection.CollectionChanged += (s,e) => this.OnPropertyChanged("ItemsSource);
I've posted another example here.

Binding to DependencyProperty of a UserControl

I Create a TimeInput Control Like to Enter Time.
<TextBox Text="{Binding Path=Hours}" />
<TextBox IsReadOnly="True"
Focusable="False"
Text=":" />
<TextBox Text="{Binding Path=Minutes}" />
and
public int Hours {
get { return (int)this.GetValue(HoursProperty); }
set {
this.SetValue(HoursProperty, value);
this.OnPropertyChanged("Hours");
}
}
public static readonly DependencyProperty HoursProperty =
DependencyProperty.Register("Hours", typeof(int), typeof(UserControl1), new UIPropertyMetadata(0, new PropertyChangedCallback(OnHoursChanged)));
private static void OnHoursChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
if (obj != null) {
int newValue = (int)e.NewValue;
}
}
public int Minutes {
get { return (int)this.GetValue(MinutesProperty); }
set {
this.SetValue(MinutesProperty, value);
this.OnPropertyChanged("Minutes");
}
}
// Using a DependencyProperty as the backing store for Minutes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MinutesProperty =
DependencyProperty.Register("Minutes", typeof(int), typeof(UserControl1), new UIPropertyMetadata(0, new PropertyChangedCallback(OnMinutesChanged)));
private static void OnMinutesChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
if (obj != null) {
int newValue = (int)e.NewValue;
}
}
public Nullable<TimeSpan> Value {
get { return (Nullable<TimeSpan>)this.GetValue(ValueProperty); }
set {
this.SetValue(ValueProperty, value);
this.OnPropertyChanged("Value");
}
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(Nullable<TimeSpan>), typeof(UserControl1), new UIPropertyMetadata(null, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
if (obj != null) {
(obj as UserControl1).UpdateTime(e.NewValue as TimeSpan?);
}
}
public void UpdateTime(TimeSpan? newTimeSpan) {
if (newTimeSpan.HasValue) {
this.Hours = newTimeSpan.Value.Hours;
this.Minutes = newTimeSpan.Value.Minutes;
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
While I use this on another UserControl and Bind to a Property It doesn't work and show values.
I use it like this:
<uc:UserControl1 Value="{Binding StartTime}"/>
and
public TimeSpan StartTime
{
get { return new Types.Time(Item.StartTime).ToTimeSpan(); }
set { Item.StartTime = new Types.Time(value).ToShort(); NotifyPropertyChanged("StartTime"); }
}
That Item is an entity read from database and bind and StartTime is short form of hhmm.
i have updated your code, with dependency properties you don't need fire the property changed event explicit.
public partial class UserControl1 : UserControl
{
public UserControl1() {
this.InitializeComponent();
}
public int Hours {
get { return (int)this.GetValue(HoursProperty); }
set { this.SetValue(HoursProperty, value); }
}
public static readonly DependencyProperty HoursProperty =
DependencyProperty.Register("Hours", typeof(int), typeof(UserControl1), new UIPropertyMetadata(0, new PropertyChangedCallback(OnHoursChanged)));
private static void OnHoursChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
var uc = obj as UserControl1;
if (uc != null && e.NewValue != e.OldValue) {
int newValue = (int)e.NewValue;
uc.TimeValue = new TimeSpan(newValue, uc.Minutes, 0);
}
}
public int Minutes {
get { return (int)this.GetValue(MinutesProperty); }
set { this.SetValue(MinutesProperty, value); }
}
// Using a DependencyProperty as the backing store for Minutes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MinutesProperty =
DependencyProperty.Register("Minutes", typeof(int), typeof(UserControl1), new UIPropertyMetadata(0, new PropertyChangedCallback(OnMinutesChanged)));
private static void OnMinutesChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
var uc = obj as UserControl1;
if (uc != null && e.NewValue != e.OldValue) {
int newValue = (int)e.NewValue;
uc.TimeValue = new TimeSpan(uc.Hours, newValue, 0);
}
}
public Nullable<TimeSpan> TimeValue {
get { return (Nullable<TimeSpan>)this.GetValue(ValueProperty); }
set { this.SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("TimeValue", typeof(Nullable<TimeSpan>), typeof(UserControl1), new UIPropertyMetadata(null, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
var uc = obj as UserControl1;
if (uc != null && e.NewValue != e.OldValue) {
uc.UpdateTime(e.NewValue as TimeSpan?);
}
}
public void UpdateTime(TimeSpan? newTimeSpan) {
if (newTimeSpan.HasValue) {
this.Hours = newTimeSpan.Value.Hours;
this.Minutes = newTimeSpan.Value.Minutes;
}
}
}
second, i think you use the StartTime property incorrect, use it as dependency property too, or implement INotifyPropertyChanged.
{
// .....
StartTime = new Types.Time(this.Item.StartTime).ToTimeSpan();
// .....
}
public static readonly DependencyProperty StartTimeProperty =
DependencyProperty.Register("StartTime", typeof(TimeSpan?), typeof(Window1), new PropertyMetadata(default(TimeSpan?), new PropertyChangedCallback(OnStartTimePropertyChanged)));
private static void OnStartTimePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
if(e.NewValue != e.OldValue) {
(dependencyObject as Window1).Item.StartTime = new Types.Time(e.NewValue).ToShort();
}
}
public TimeSpan? StartTime {
get { return (TimeSpan?)GetValue(StartTimeProperty); }
set { SetValue(StartTimeProperty, value); }
}
hope this helps
You should not have any other code then calling GetValue and SetValue inside getter and setter of dependency property. But this may not resolve you problem. If you want to call some code when value change then do that inside callback method instead of setter.

How can I bind to a helper property in Silverlight

For the sake of argument, here's a simple person class
public class Person : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register( "FirstName",
typeof ( string ),
typeof ( Person ),
null );
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register( "LastName",
typeof( string ),
typeof( Person ),
null );
public string FirstName
{
get
{
return ( string ) GetValue( FirstNameProperty );
}
set
{
SetValue( FirstNameProperty, value );
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs( "FirstName" ));
}
}
public string LastName
{
get
{
return ( string ) GetValue( LastNameProperty );
}
set
{
SetValue( LastNameProperty, value );
if ( PropertyChanged != null )
PropertyChanged( this, new PropertyChangedEventArgs( "LastName" ) );
}
}
}
I want to go about creating a readonly property like this
public string FullName
{
get { return FirstName + " " + LastName; }
}
How does binding work in this scenario? I've tried adding a DependancyProperty and raised the PropertyChanged event for the fullname. Basically I just want to have a property that I can bind to that returns the fullname of a user whenever the first or last name changes. Here's the final class I'm using with the modifications.
public class Person : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register( "FirstName",
typeof ( string ),
typeof ( Person ),
null );
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register( "LastName",
typeof( string ),
typeof( Person ),
null );
public static readonly DependencyProperty FullNameProperty =
DependencyProperty.Register( "FullName",
typeof( string ),
typeof( Person ),
null );
public string FirstName
{
get
{
return ( string ) GetValue( FirstNameProperty );
}
set
{
SetValue( FirstNameProperty, value );
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( "FirstName" ) );
PropertyChanged( this, new PropertyChangedEventArgs( "FullName" ) );
}
}
}
public string LastName
{
get
{
return ( string ) GetValue( LastNameProperty );
}
set
{
SetValue( LastNameProperty, value );
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( "LastName" ) );
PropertyChanged( this, new PropertyChangedEventArgs( "FullName" ) );
}
}
}
public string FullName
{
get { return GetValue( FirstNameProperty ) + " " + GetValue( LastNameProperty ); }
}
}
I'm not sure what you are trying to achieve here, but why is your Person class inheriting from DependencyObject and why are FirstName and LastName DependencyProperties? If all you want to do is bind the Person properties to user controls on your view, having the Person class implementing INotifyPropertyChanged is enough to make the data binding work. You will typically bind it to properties of a user control that are dependency properties (eg the Text property of a TextBlock).
Try this for you Person class:
using System.ComponentModel;
public class Person : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (value != _firstName)
{
_firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
if (value != _lastName)
{
_lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName
{
get { return FirstName + " " + LastName; }
}
}
And use it like this in your view:
<Grid x:Name="LayoutRoot" Background="White" >
<TextBlock Text="{Binding FullName}"/>
</Grid>
Then, in your codebehind, you could instantiate it like so:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
DataContext = new Person { FirstName = "John", LastName = "Doe" };
}
}
HTH,
Phil
First of all your implementation of the existing FirstName and LastName properties is flawed. The DependencyObject already has ways to inform bindings of changes to the values and values can be changed by other mechanism than calling the Setter methods.
My first question would be why are FirstName and LastName dependency properties at all? That seems like a strange choice for this type of class. Phil's answer has already provided what I would really expect the correct answer to be.
However in case your code was actually a simplification and that there is in fact a genuine need to create dependency properties here is how it should be done:-
public class Person : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register( "FirstName",
typeof ( string ),
typeof ( Person ),
OnNamePropertyChanged);
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register( "LastName",
typeof( string ),
typeof( Person ),
OnNamePropertyChanged);
private static void OnNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Person)d).OnNamePropertyChanged();
}
private void OnNamePropertyChanged()
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("FullName")));
}
public string FirstName
{
get { return GetValue(FirstNameProperty) as string; }
set { SetValue(FirstNameProperty, value); }
}
public string LastName
{
get { return GetValue(LastNameProperty) as string; }
set { SetValue(LastNameProperty, value); }
}
public string FullName
{
get { return FirstName + " " + LastName; }
}
}

Resources