Evaluate Binding Path Dynamically in Silverlight in XAML - silverlight

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;
}
}

Related

How to handle items added to Attached Property of ObservableCollection type

I have a UWP project that uses MapControl, which is a sealed class - cant derive a new class from it.
Trying to make a bindable Attached Property, which would have access to MapControl.Children.
The problem is that it only works when I set ViewModel's collection, but not when I add a new element to that collection:
// Works fine
this.MapChildrenExtCollection = new ObservableCollection<MapChildElement>();
// Nothing happens
this.MapChildrenExtCollection.Add(new MapChildElement());
Heres my code for the Attached Property:
namespace UWPMap.Extensions
{
public class MapControlExt : DependencyObject
{
public static readonly DependencyProperty ChildrenExtProperty = DependencyProperty.Register(
"ChildrenExt",
typeof(ObservableCollection<MapChildElement>),
typeof(MapControlExt),
new PropertyMetadata(new ObservableCollection<MapChildElement>(), ChildrenExtPropertyChanged));
public ObservableCollection<MapChildElement> ChildrenExt
{
get { return (ObservableCollection<MapChildElement>)GetValue(ChildrenExtProperty); }
set { SetValue(ChildrenExtProperty, value); }
}
public static void SetChildrenExt(UIElement element, ObservableCollection<MapChildElement> value)
{
element.SetValue(ChildrenExtProperty, value);
}
public static ObservableCollection<MapChildElement> GetChildrenExt(UIElement element)
{
return (ObservableCollection<MapChildElement>)element.GetValue(ChildrenExtProperty);
}
private static void ChildrenExtPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control = (MapControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
if (newCollection != null)
{
oldCollection.CollectionChanged += Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
ManageChildrenExt();
}
static void ChildrenExtCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ManageChildrenExt();
}
static private void ManageChildrenExt()
{
// Access MapControl.Children here
}
}
}
XAML:
<maps:MapControl x:Name="MyMap"
ext:MapControlExt.ChildrenExt="{x:Bind Path=MapChildrenExtCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</maps:MapControl>
The problem is that you are not adding the event handler to the new collection and using oldCollection variable by mistake.
The following snippet:
if (newCollection != null)
{
oldCollection.CollectionChanged += //here you have oldCollection by mistake
Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
Should be:
if (newCollection != null)
{
newCollection.CollectionChanged +=
Extensions.MapControlExt.ChildrenExtCollectionChanged;
}

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();
}

Re-use Animations in VisualStates

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;
}
}

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.

MVVM Invoke Method in View (from ControlTemplate via ViewModel)

I would like to know how the following problem can be solved WITHOUT using Event Aggregation. This is for WPF 3.5 SP1, so the CallMethodBehavior is not available.
Simple Scenario: A click on a button inside a ControlTemplate needs to be triggered to the VM. I used CaliburnMicro's ActionMessage which worked fine. Inside the ViewModel I want to trigger a method inside the View, which only starts a custom transition (no real logic). I tried many things, but I did not work out.
I created a Property in my view, which could call the method but I am not able to use Triggers to set a new value for the property, because I can't tell the setter to target a property outside the controltemplate.
So in essence I want to update a Property in the viewmodel and trigger a set-property in the view class. Or if you have any idea how to get around this at all: I am open to new ideas! :D
Regards
Gope
i think the most simple way is to expose an event from your vm and subscribe to it in your view?
i used this for dialogs to send DialogResult from vm
I found a solution I can live with: I ported the CallMethodAction to 3.5 and wrote my own PropertyChangedTrigger. It's pretty simple to call a method inside the view via a PropertyChange in the viewmodel - Kids: don't try this at home. It's only for special scenarios! :D
Find my code below:
usage:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<i:Interaction.Triggers >
<Framework:PropertyChangedTrigger Binding="{Binding StartTransition}" Value="True">
<Framework:CallMethodAction MethodName="ApplyTransition" />
</Framework:PropertyChangedTrigger>
</i:Interaction.Triggers>
PropertyChangedTrigger:
public class PropertyChangedTrigger : TriggerBase<DependencyObject>
{
public static readonly DependencyProperty BindingProperty = DependencyProperty.Register("Binding", typeof(object), typeof(PropertyChangedTrigger), new PropertyMetadata(new PropertyChangedCallback(OnBindingChanged)));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(PropertyChangedTrigger), new PropertyMetadata(null));
public object Binding
{
get
{
return base.GetValue(BindingProperty);
}
set
{
base.SetValue(BindingProperty, value);
}
}
public object Value
{
get
{
return base.GetValue(ValueProperty);
}
set
{
base.SetValue(ValueProperty, value);
}
}
protected virtual void EvaluateBindingChange(object args)
{
var propertyChangedArgs = (DependencyPropertyChangedEventArgs)args;
string newValue = propertyChangedArgs.NewValue.ToString();
bool equal = string.Equals(newValue, Value.ToString(),StringComparison.InvariantCultureIgnoreCase);
if(equal)
{
InvokeActions(args);
}
}
private static void OnBindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((PropertyChangedTrigger)sender).EvaluateBindingChange(args);
}
}
CallMethodAction:
public class CallMethodAction : TargetedTriggerAction<FrameworkElement>
{
private List<MethodDescriptor> methodDescriptors = new List<MethodDescriptor>();
public static readonly DependencyProperty MethodNameProperty = DependencyProperty.Register("MethodName", typeof(string), typeof(CallMethodAction), new PropertyMetadata(new PropertyChangedCallback(OnMethodNameChanged)));
public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register("TargetObject", typeof(object), typeof(CallMethodAction), new PropertyMetadata(new PropertyChangedCallback(OnTargetObjectChanged)));
protected override void OnAttached()
{
base.OnAttached();
this.UpdateMethodInfo();
}
protected override void OnDetaching()
{
this.methodDescriptors.Clear();
base.OnDetaching();
}
private static void OnMethodNameChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((CallMethodAction)sender).UpdateMethodInfo();
}
private static void OnTargetObjectChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((CallMethodAction)sender).UpdateMethodInfo();
}
private static bool AreMethodParamsValid(ParameterInfo[] methodParams)
{
if (methodParams.Length == 2)
{
if (methodParams[0].ParameterType != typeof(object))
{
return false;
}
if (!typeof(EventArgs).IsAssignableFrom(methodParams[1].ParameterType))
{
return false;
}
}
else if (methodParams.Length != 0)
{
return false;
}
return true;
}
protected override void Invoke(object parameter)
{
if (base.AssociatedObject != null)
{
MethodDescriptor descriptor = this.FindBestMethod(parameter);
if (descriptor != null)
{
ParameterInfo[] parameters = descriptor.Parameters;
if (parameters.Length == 0)
{
descriptor.MethodInfo.Invoke(this.Target, null);
}
else if ((((parameters.Length == 2) && (base.AssociatedObject != null)) && ((parameter != null) && parameters[0].ParameterType.IsAssignableFrom(base.AssociatedObject.GetType()))) && parameters[1].ParameterType.IsAssignableFrom(parameter.GetType()))
{
descriptor.MethodInfo.Invoke(this.Target, new object[] { base.AssociatedObject, parameter });
}
}
else if (this.TargetObject != null)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "No valid method found.", new object[] { this.MethodName, this.TargetObject.GetType().Name }));
}
}
}
private MethodDescriptor FindBestMethod(object parameter)
{
if (parameter != null)
{
parameter.GetType();
}
return this.methodDescriptors.FirstOrDefault(methodDescriptor => (!methodDescriptor.HasParameters || ((parameter != null) && methodDescriptor.SecondParameterType.IsAssignableFrom(parameter.GetType()))));
}
private void UpdateMethodInfo()
{
this.methodDescriptors.Clear();
if ((this.Target != null) && !string.IsNullOrEmpty(this.MethodName))
{
foreach (MethodInfo info in this.Target.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
if (this.IsMethodValid(info))
{
ParameterInfo[] parameters = info.GetParameters();
if (AreMethodParamsValid(parameters))
{
this.methodDescriptors.Add(new MethodDescriptor(info, parameters));
}
}
}
this.methodDescriptors = this.methodDescriptors.OrderByDescending<MethodDescriptor, int>(delegate(MethodDescriptor methodDescriptor)
{
int num = 0;
if (methodDescriptor.HasParameters)
{
for (Type type = methodDescriptor.SecondParameterType; type != typeof(EventArgs); type = type.BaseType)
{
num++;
}
}
return (methodDescriptor.ParameterCount + num);
}).ToList<MethodDescriptor>();
}
}
private bool IsMethodValid(MethodInfo method)
{
if (!string.Equals(method.Name, this.MethodName, StringComparison.Ordinal))
{
return false;
}
if (method.ReturnType != typeof(void))
{
return false;
}
return true;
}
public void InvokeInternal()
{
if (AssociatedObject != null)
{
foreach (
MethodInfo info in AssociatedObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
if (IsMethodValid(info))
{
info.Invoke(AssociatedObject, new object[0]);
}
}
}
}
public string MethodName
{
get
{
return (string)base.GetValue(MethodNameProperty);
}
set
{
base.SetValue(MethodNameProperty, value);
}
}
private object Target
{
get
{
return (TargetObject ?? base.AssociatedObject);
}
}
public object TargetObject
{
get
{
return base.GetValue(TargetObjectProperty);
}
set
{
base.SetValue(TargetObjectProperty, value);
}
}
private class MethodDescriptor
{
public MethodDescriptor(MethodInfo methodInfo, ParameterInfo[] methodParams)
{
MethodInfo = methodInfo;
Parameters = methodParams;
}
public bool HasParameters
{
get
{
return (Parameters.Length > 0);
}
}
public MethodInfo MethodInfo { get; private set; }
public int ParameterCount
{
get
{
return Parameters.Length;
}
}
public ParameterInfo[] Parameters { get; private set; }
public Type SecondParameterType
{
get
{
if (Parameters.Length >= 2)
{
return Parameters[1].ParameterType;
}
return null;
}
}
}
}
Hope this helps anybode. All questions are welcome! Remeber: all this can be found in the Expression Blend SDK 4. This code is only for people who are forced to work with older versions like 3.5
Regards
Gope
Gope

Resources