I have created Blend behavior, but when I add children to it in xaml, they does not appear in the collection. What might be the reason of that ?
When app is running, Actions collection does not contain any action, though it certainly should.
<Helpers:EnterKeyUpEventBehavior>
<Helpers:CloseFlyoutAction />
</Helpers:EnterKeyUpEventBehavior>
[ContentProperty(Name = "Actions")]
class EnterKeyUpEventBehavior : DependencyObject, IBehavior
{
public static readonly DependencyProperty ActionsProperty = DependencyProperty.Register(
"Actions", typeof (ActionCollection), typeof (EnterKeyUpEventBehavior), new PropertyMetadata(default(ActionCollection)));
public ActionCollection Actions
{
get
{
var actions = (ActionCollection) GetValue(ActionsProperty);
if (actions == null)
{
actions = new ActionCollection();
base.SetValue(ActionsProperty, actions);
}
return actions;
}
set { SetValue(ActionsProperty, value); }
}
private TextBox _associatedTextBox;
public DependencyObject AssociatedObject
{
get { return _associatedTextBox; }
}
public void Attach(DependencyObject associatedObject)
{
_associatedTextBox = associatedObject as TextBox;
if(_associatedTextBox == null)
throw new ArgumentException("This Behavior only works with TextBox control!");
_associatedTextBox.KeyUp += _associatedTextBox_KeyUp;
Actions = new ActionCollection();
}
void _associatedTextBox_KeyUp(object sender, Windows.UI.Xaml.Input.KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Enter)
{
Interaction.ExecuteActions(_associatedTextBox, Actions, null);
}
}
public void Detach()
{
_associatedTextBox.KeyUp -= _associatedTextBox_KeyUp;
}
}
public ActionCollection Actions
{
get { return (ActionCollection) GetValue(ActionsProperty); }
set { SetValue(ActionsProperty, value); }
}
public EnterKeyUpEventBehavior()
{
Actions = new ActionCollection();
}
The xaml parsing and instatiation mechanism does not use your getter and setter, it uses GetValue(ActionsProperty) and SetValue(ActionsProperty) directly, circumventing your "lazy init".
In my experience when I want to use a collection type property as ContentProperty I have to either use a plain List or a DependencyObjectCollection (this is needed when you want the DataContext inheritance mechanism to function properly).
either:
public List<Action> Actions
{
get { return (List<Action>) GetValue(ActionsProperty); }
set { SetValue(ActionsProperty, value); }
}
public EnterKeyUpEventBehavior()
{
Actions = new List<Action>();
}
or:
public DependencyObjectCollection Actions
{
get { return (DependencyObjectCollection) GetValue(ActionsProperty); }
set { SetValue(ActionsProperty, value); }
}
public EnterKeyUpEventBehavior()
{
Actions = new DependencyObjectCollection();
}
Related
I've just discovered that WPF Markup extension instances are reused in control templates. So each copy of the control template gets the same set of markup extensions.
This doesn't work if you want the extension to maintain some state per control it is attached to. Any idea how to solve this.
Don't store state in the Markup extension. Store it another way. For example.
public abstract class DynamicMarkupExtension : MarkupExtension
{
public class State
{
public object TargetObject { get; set; }
public object TargetProperty { get; set; }
public void UpdateValue(object value)
{
if (TargetObject != null)
{
if (TargetProperty is DependencyProperty)
{
DependencyObject obj = TargetObject as DependencyObject;
DependencyProperty prop = TargetProperty as DependencyProperty;
Action updateAction = () => obj.SetValue(prop, value);
// Check whether the target object can be accessed from the
// current thread, and use Dispatcher.Invoke if it can't
if (obj.CheckAccess())
updateAction();
else
obj.Dispatcher.Invoke(updateAction);
}
else // TargetProperty is PropertyInfo
{
PropertyInfo prop = TargetProperty as PropertyInfo;
prop.SetValue(TargetObject, value, null);
}
}
}
}
public sealed override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
State state = new State();
if (target != null)
{
state.TargetObject = target.TargetObject;
state.TargetProperty = target.TargetProperty;
return ProvideValueInternal(serviceProvider, state);
}
else
{
return null;
}
}
protected abstract object ProvideValueInternal(IServiceProvider serviceProvider, State state);
}
is a base class for handling the type of problem where you need to update the property the markup
extension is attached to at run time. For example a markup extension for binding to ISubject as
<TextBox Text="{Markup:Subscription Path=Excenter, ErrorsPath=Errors}"/>
using the SubscriptionExtension as below. I had had trouble with the code when I used it
within templates but I fixed it so the MarkupExtension did not store state in itself
using ReactiveUI.Ext;
using ReactiveUI.Subjects;
using ReactiveUI.Utils;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace ReactiveUI.Markup
{
[MarkupExtensionReturnType(typeof(BindingExpression))]
public class SubscriptionExtension : DynamicMarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
[ConstructorArgument("errorsPath")]
public PropertyPath ErrorsPath { get; set; }
public SubscriptionExtension() { }
Maybe<Exception> currentErrorState = Maybe.None<Exception>();
public SubscriptionExtension(PropertyPath path, PropertyPath errorsPath)
{
Path = path;
ErrorsPath = errorsPath;
}
class Proxy : ReactiveObject, IDataErrorInfo, IDisposable
{
string _Value;
public string Value
{
get { return _Value; }
set { this.RaiseAndSetIfChanged(value); }
}
public string Error
{
get { return currentError.Select(e => e.Message).Else(""); }
}
public string this[string columnName]
{
get { return currentError.Select(e => e.Message).Else(""); }
}
public IObservable<Maybe<Exception>> Errors { get; set; }
public Maybe<Exception> currentError = Maybe.None<Exception>();
private CompositeDisposable Subscriptions = new CompositeDisposable();
public Proxy(IObservable<Maybe<Exception>> errors)
{
Errors = errors;
var subscription = errors.Subscribe(e => currentError = e);
Subscriptions.Add(subscription);
}
public void Dispose()
{
Subscriptions.Dispose();
}
}
protected override object ProvideValueInternal(IServiceProvider serviceProvider, DynamicMarkupExtension.State state )
{
var pvt = serviceProvider as IProvideValueTarget;
if (pvt == null)
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if (frameworkElement == null)
{
return this;
}
DependencyPropertyChangedEventHandler myd = delegate(object sender, DependencyPropertyChangedEventArgs e){
state.UpdateValue(MakeBinding(serviceProvider, frameworkElement));
};
frameworkElement.DataContextChanged += myd;
return MakeBinding(serviceProvider, frameworkElement);
}
private object MakeBinding(IServiceProvider serviceProvider, FrameworkElement frameworkElement)
{
var dataContext = frameworkElement.DataContext;
if (dataContext is String)
{
return dataContext;
}
ISubject<string> subject = Lens.Empty<string>().Subject;
IObservable<Maybe<Exception>> errors = Observable.Empty<Maybe<Exception>>();
Binding binding;
Proxy proxy = new Proxy(errors);
bool madeit = false;
if (dataContext != null)
{
subject = GetProperty<ISubject<string>>(dataContext, Path);
if (subject != null)
{
errors = GetProperty<IObservable<Maybe<Exception>>>
(dataContext
, ErrorsPath) ?? Observable.Empty<Maybe<Exception>>();
proxy = new Proxy(errors);
}
madeit = true;
}
if(!madeit)
{
subject = new BehaviorSubject<string>("Binding Error");
}
// Bind the subject to the property via a helper ( in private library )
var subscription = subject.TwoWayBindTo(proxy, x => x.Value);
// Make sure we don't leak subscriptions
frameworkElement.Unloaded += (e, v) => subscription.Dispose();
binding = new Binding()
{
Source = proxy,
Path = new System.Windows.PropertyPath("Value"),
ValidatesOnDataErrors = true
};
return binding.ProvideValue(serviceProvider);
}
private static T GetProperty<T>(object context, PropertyPath propPath)
where T : class
{
if (propPath==null)
{
return null;
}
try
{
object propValue = propPath.Path
.Split('.')
.Aggregate(context, (value, name)
=> value.GetType()
.GetProperty(name)
.GetValue(value, null));
return propValue as T;
}
catch (NullReferenceException e)
{
throw new MemberAccessException(propPath.Path + " is not available on " + context.GetType(),e);
}
}
}
}
I have a Silverlight application that I am currently working that implements Caliburn.Micro for its MVVM framework. Things are working fine but I am noticing some funniness in some of the binding. What I have is a ShellViewModel and a ShellView that handle the navigation for the application. The ShellViewModel has a list of the loaded ViewModels for the application. The ShellViewModel inherits from Conductor so that it can handle all the activation and deactivation.
I also have a type of ViewModel base class called BaseConductorViewModel that also inherits from Conductor. This is for ViewModel’s that are basically Master-Detail views. For these BaseConductorViewModels I have a BindableCollection called Items. The idea being to bind this collection to a ListBox or other ItemsControl.
When I create an child of this ViewModel and an associated View I have noticed that the ListBox(in this case) only refreshes the binding when I change the ActiveItem at the ShellViewModel level. So when the application initially loads and this view is the default active view you won’t see anything in the list (I am calling Ria service to get the data for this list). But, if I click on another ViewModel on the ShellViewModel/ShellView and then click back it will show the items in the list. This also follows for adding items to the list or removing them. It won’t refresh unless I switch active views. This seems very odd to me and I can’t seem to figure out a way to get it bind as I would except. One more thing to note, when I am adding/removing items; I call the Refresh method, currently I am not using the NotifyOfPropertyChange method though I did try that previously to the same result.
Does anyone have any ideas of what might be going on here? Or any ideas on how I could go about trying to debug this?
Thank you in advance!
Here is the ShellViewModel
public abstract class ShellViewModel<V,M>:Conductor<IViewModel<V, M>>.Collection.OneActive, IViewModelCatalogShell<V,M>
where V:IView
where M:IModel
{
#region Properties/Members
public ViewModelSelectedItemList<V, M> Catalog { get; set; }
#endregion
#region Constructors
public ShellViewModel()
{
Catalog = new ViewModelSelectedItemList<V, M>();
}
#endregion
}
And here is the BaseConductorViewModel
public abstract class BaseConductorViewModel<T,V,M>:Conductor<T>, IViewModel<V, M>
where V:IView
where M:IModel
{
#region Properties/Members
protected Guid _id=Guid.Empty;
public Guid Id
{
get{return _id;}
set
{
_id =value;
NotifyOfPropertyChange("Id");
}
}
protected string _name=string.Empty;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange("Name");
}
}
public string TypeName
{
get
{
return this.GetType().FullName;
}
}
protected string _description = string.Empty;
public string Description
{
get { return _description; }
protected set
{
_description = value;
NotifyOfPropertyChange(() => Description);
}
}
protected V _view;
public V View
{
get { return _view; }
set
{
_view = value;
NotifyOfPropertyChange("View");
}
}
protected M _model;
public M Model
{
get { return _model; }
set
{
_model = value;
NotifyOfPropertyChange("Model");
}
}
protected SelectedItemList<T> _items;
public SelectedItemList<T> Items
{
get { return _items; }
set
{
_items = value;
NotifyOfPropertyChange(() => Items);
}
}
protected Guid _lastModifiedBy = Guid.Empty;
public Guid LastModifiedBy
{
get { return _lastModifiedBy; }
set
{
_lastModifiedBy = value;
NotifyOfPropertyChange("LastModifiedBy");
}
}
protected DateTime _lastModifiedOn = DateTime.Today;
public DateTime LastModifiedOn
{
get { return _lastModifiedOn; }
set
{
_lastModifiedOn = value;
NotifyOfPropertyChange("LastModifiedOn");
}
}
protected string _imageSource = string.Empty;
public string ImageSource
{
get { return _imageSource; }
protected set
{
_imageSource = value;
NotifyOfPropertyChange("ImageSource");
}
}
#endregion
#region Constructors
public BaseConductorViewModel()
{
_items = new SelectedItemList<T>();
Items.SelectItemChanged += new SelectedItemChangedEvent(Items_SelectItemChanged);
Items.SelectedIndexChanged += new SelectedIndexChangedEvent(Items_SelectedIndexChanged);
LoadData();
}
public BaseConductorViewModel(V view, M model)
:this()
{
_items = new SelectedItemList<T>();
View = view;
Model = model;
Items.SelectItemChanged += new SelectedItemChangedEvent(Items_SelectItemChanged);
Items.SelectedIndexChanged += new SelectedIndexChangedEvent(Items_SelectedIndexChanged);
LoadData();
}
#endregion
#region Methods
public abstract void LoadData();
#endregion
#region Event Handlers
private void Items_SelectItemChanged()
{
ChangeActiveItem(Items.SelectedItem, true);
OnActiveItemChanged();
}
private void Items_SelectedIndexChanged(int index)
{
ChangeActiveItem(Items.SelectedItem, true);
OnActiveItemChanged();
}
#endregion
}
The ViewModelSelectedItemList is just a typed version of this class
public class SelectedItemList<T>:IObservableCollection<T>
{
#region Properties/Members
protected BindableCollection<T> _items = new BindableCollection<T>();
protected bool _isReadOnly = false;
protected bool _isNotifying = true;
public bool IsNotifying
{
get
{
return _isNotifying;
}
set
{
_isNotifying = value;
}
}
public int Count
{
get { return _items.Count; }
}
protected int _selectedIndex = -1;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
NotifyOfPropertyChange("SelectedIndex");
FireSelectedIndexChangedEvent(_selectedIndex);
}
}
public T SelectedItem
{
get
{ return _items[_selectedIndex]; }
set
{
_selectedIndex = _items.IndexOf(value);
NotifyOfPropertyChange("SelectedItem");
FireSelectedItemChangedEvent();
}
}
#endregion
#region Events
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
public event SelectedIndexChangedEvent SelectedIndexChanged;
public event SelectedItemChangedEvent SelectItemChanged;
#endregion
#region Constructors
#endregion
#region Methods
public void AddRange(System.Collections.Generic.IEnumerable<T> items)
{
if (!_isReadOnly)
{
foreach (T item in items)
{
_items.Add(item);
}
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public void RemoveRange(System.Collections.Generic.IEnumerable<T> items)
{
if (!_isReadOnly)
{
foreach (T item in items)
{
_items.Remove(item);
}
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public int IndexOf(T item)
{
return _items.IndexOf(item);
}
public void Insert(int index, T item)
{
if (!_isReadOnly)
{
_items.Insert(index, item);
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public void RemoveAt(int index)
{
if (!_isReadOnly)
{
_items.RemoveAt(index);
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public T this[int index]
{
get
{
return _items[index];
}
set
{
_items[index] = value;
}
}
public void Add(T item)
{
if (!_isReadOnly)
{
_items.Add(item);
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
_items.Refresh();
}
if (_items.Count == 1)
{
SelectedIndex = 0;
}
}
}
public bool Remove(T item)
{
if (!_isReadOnly)
{
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
return _items.Remove(item);
}
else
{
return false;
}
}
public void Clear()
{
_items.Clear();
}
public bool Contains(T item)
{
return _items.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
if (!_isReadOnly)
{
_items.CopyTo(array, arrayIndex);
if (_isNotifying)
{
NotifyOfPropertyChange("Count");
}
}
}
public bool IsReadOnly
{
get { return _isReadOnly; }
}
public void Lock()
{
_isReadOnly = true;
}
public void Unlock()
{
_isReadOnly = false;
}
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
{
return _items.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _items.GetEnumerator();
}
public void NotifyOfPropertyChange(string propertyName)
{
FirePropertyChangedEvent(propertyName);
}
public void Refresh()
{
_items.Refresh();
}
#region Helper Methods
protected void FirePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
protected void FireCollectionChangedEvent(NotifyCollectionChangedAction action)
{
if (CollectionChanged != null)
{
CollectionChanged(this, new System.Collections.Specialized.NotifyCollectionChangedEventArgs(action));
}
}
protected void FireSelectedIndexChangedEvent(int index)
{
if (SelectedIndexChanged != null)
{
SelectedIndexChanged(index);
}
}
protected void FireSelectedItemChangedEvent()
{
if (SelectItemChanged != null)
{
SelectItemChanged();
}
}
#endregion
#endregion
}
Not sure if you're problem has something to do with this, from the docs:
Since all OOTB implementations of IConductor inherit from Screen it
means that they too have a lifecycle and that lifecycle cascades to
whatever items they are conducting. So, if a conductor is deactivated,
it’s ActiveItem will be deactivated as well. If you try to close a
conductor, it’s going to only be able to close if all of the items it
conducts can close. This turns out to be a very powerful feature.
There’s one aspect about this that I’ve noticed frequently trips up
developers. If you activate an item in a conductor that is itself not
active, that item won’t actually be activated until the conductor gets
activated. This makes sense when you think about it, but can
occasionally cause hair pulling.
edit:
I think I see what you're trying to do, a couple questions though:
Your ShellViewModel is
Conductor<IViewModel<V,M>>.Collection.OneActive, when is Catalog
activated? I'd think you'd want to add Catalog to Items, then
Activate it.
With the BaseConductorViewModel, it inherits from Conductor which
inherits from Screen, which gets a reference to it's view when it's
bound. I'm not sure what the View property you add is for.
CM can handle setting the selected item for you. So for a master
detail situation where you have an ItemsControl, CM will set the
SelectedItem and from that you can populate the detail.
I'm trying to databind a Button's IsMouseOver read-only dependency property to a boolean read/write property in my view model.
Basically I need the Button's IsMouseOver property value to be read to a view model's property.
<Button IsMouseOver="{Binding Path=IsMouseOverProperty, Mode=OneWayToSource}" />
I'm getting a compile error: 'IsMouseOver' property is read-only and cannot be set from markup. What am I doing wrong?
No mistake. This is a limitation of WPF - a read-only property cannot be bound OneWayToSource unless the source is also a DependencyProperty.
An alternative is an attached behavior.
As many people have mentioned, this is a bug in WPF and the best way is to do it is attached property like Tim/Kent suggested. Here is the attached property I use in my project. I intentionally do it this way for readability, unit testability, and sticking to MVVM without codebehind on the view to handle the events manually everywhere.
public interface IMouseOverListener
{
void SetIsMouseOver(bool value);
}
public static class ControlExtensions
{
public static readonly DependencyProperty MouseOverListenerProperty =
DependencyProperty.RegisterAttached("MouseOverListener", typeof (IMouseOverListener), typeof (ControlExtensions), new PropertyMetadata(OnMouseOverListenerChanged));
private static void OnMouseOverListenerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = ((UIElement)d);
if(e.OldValue != null)
{
element.MouseEnter -= ElementOnMouseEnter;
element.MouseLeave -= ElementOnMouseLeave;
}
if(e.NewValue != null)
{
element.MouseEnter += ElementOnMouseEnter;
element.MouseLeave += ElementOnMouseLeave;
}
}
public static void SetMouseOverListener(UIElement element, IMouseOverListener value)
{
element.SetValue(MouseOverListenerProperty, value);
}
public static IMouseOverListener GetMouseOverListener(UIElement element)
{
return (IMouseOverListener) element.GetValue(MouseOverListenerProperty);
}
private static void ElementOnMouseLeave(object sender, MouseEventArgs mouseEventArgs)
{
var element = ((UIElement)sender);
var listener = GetMouseOverListener(element);
if(listener != null)
listener.SetIsMouseOver(false);
}
private static void ElementOnMouseEnter(object sender, MouseEventArgs mouseEventArgs)
{
var element = ((UIElement)sender);
var listener = GetMouseOverListener(element);
if (listener != null)
listener.SetIsMouseOver(true);
}
}
Here's a rough draft of what i resorted to while seeking a general solution to this problem. It employs a css-style formatting to specify Dependency-Properties to be bound to model properties (models gotten from the DataContext); this also means it will work only on FrameworkElements.
I haven't thoroughly tested it, but the happy path works just fine for the few test cases i ran.
public class BindingInfo
{
internal string sourceString = null;
public DependencyProperty source { get; internal set; }
public string targetProperty { get; private set; }
public bool isResolved => source != null;
public BindingInfo(string source, string target)
{
this.sourceString = source;
this.targetProperty = target;
validate();
}
private void validate()
{
//verify that targetProperty is a valid c# property access path
if (!targetProperty.Split('.')
.All(p => Identifier.IsMatch(p)))
throw new Exception("Invalid target property - " + targetProperty);
//verify that sourceString is a [Class].[DependencyProperty] formatted string.
if (!sourceString.Split('.')
.All(p => Identifier.IsMatch(p)))
throw new Exception("Invalid source property - " + sourceString);
}
private static readonly Regex Identifier = new Regex(#"[_a-z][_\w]*$", RegexOptions.IgnoreCase);
}
[TypeConverter(typeof(BindingInfoConverter))]
public class BindingInfoGroup
{
private List<BindingInfo> _infoList = new List<BindingInfo>();
public IEnumerable<BindingInfo> InfoList
{
get { return _infoList.ToArray(); }
set
{
_infoList.Clear();
if (value != null) _infoList.AddRange(value);
}
}
}
public class BindingInfoConverter: TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string)) return true;
return base.CanConvertFrom(context, sourceType);
}
// Override CanConvertTo to return true for Complex-to-String conversions.
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string)) return true;
return base.CanConvertTo(context, destinationType);
}
// Override ConvertFrom to convert from a string to an instance of Complex.
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
string text = value as string;
return new BindingInfoGroup
{
InfoList = text.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
.Select(binfo =>
{
var parts = binfo.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2) throw new Exception("invalid binding info - " + binfo);
return new BindingInfo(parts[0].Trim(), parts[1].Trim());
})
};
}
// Override ConvertTo to convert from an instance of Complex to string.
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture,
object value, Type destinationType)
{
var bgroup = value as BindingInfoGroup;
return bgroup.InfoList
.Select(bi => $"{bi.sourceString}:{bi.targetProperty};")
.Aggregate((n, p) => n += $"{p} ")
.Trim();
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => false;
}
public class Bindings
{
#region Fields
private static ConcurrentDictionary<DependencyProperty, PropertyChangeHandler> _Properties =
new ConcurrentDictionary<DependencyProperty, PropertyChangeHandler>();
#endregion
#region OnewayBindings
public static readonly DependencyProperty OnewayBindingsProperty =
DependencyProperty.RegisterAttached("OnewayBindings", typeof(BindingInfoGroup), typeof(Bindings), new FrameworkPropertyMetadata
{
DefaultValue = null,
PropertyChangedCallback = (x, y) =>
{
var fwe = x as FrameworkElement;
if (fwe == null) return;
//resolve the bindings
resolve(fwe);
//add change delegates
(GetOnewayBindings(fwe)?.InfoList ?? new BindingInfo[0])
.Where(bi => bi.isResolved)
.ToList()
.ForEach(bi =>
{
var descriptor = DependencyPropertyDescriptor.FromProperty(bi.source, fwe.GetType());
PropertyChangeHandler listener = null;
if (_Properties.TryGetValue(bi.source, out listener))
{
descriptor.RemoveValueChanged(fwe, listener.callback); //cus there's no way to check if it had one before...
descriptor.AddValueChanged(fwe, listener.callback);
}
});
}
});
private static void resolve(FrameworkElement element)
{
var bgroup = GetOnewayBindings(element);
bgroup.InfoList
.ToList()
.ForEach(bg =>
{
//source
var sourceParts = bg.sourceString.Split('.');
if (sourceParts.Length == 1)
{
bg.source = element.GetType()
.baseTypes() //<- flattens base types, including current type
.SelectMany(t => t.GetRuntimeFields()
.Where(p => p.IsStatic)
.Where(p => p.FieldType == typeof(DependencyProperty)))
.Select(fi => fi.GetValue(null) as DependencyProperty)
.FirstOrDefault(dp => dp.Name == sourceParts[0])
.ThrowIfNull($"Dependency Property '{sourceParts[0]}' was not found");
}
else
{
//resolve the dependency property [ClassName].[PropertyName]Property - e.g FrameworkElement.DataContextProperty
bg.source = Type.GetType(sourceParts[0])
.GetField(sourceParts[1])
.GetValue(null)
.ThrowIfNull($"Dependency Property '{bg.sourceString}' was not found") as DependencyProperty;
}
_Properties.GetOrAdd(bg.source, ddp => new PropertyChangeHandler { property = ddp }); //incase it wasnt added before.
});
}
public static BindingInfoGroup GetOnewayBindings(FrameworkElement source)
=> source.GetValue(OnewayBindingsProperty) as BindingInfoGroup;
public static void SetOnewayBindings(FrameworkElement source, string value)
=> source.SetValue(OnewayBindingsProperty, value);
#endregion
}
public class PropertyChangeHandler
{
internal DependencyProperty property { get; set; }
public void callback(object obj, EventArgs args)
{
var fwe = obj as FrameworkElement;
var target = fwe.DataContext;
if (fwe == null) return;
if (target == null) return;
var bg = Bindings.GetOnewayBindings(fwe);
if (bg == null) return;
else bg.InfoList
.Where(bi => bi.isResolved)
.Where(bi => bi.source == property)
.ToList()
.ForEach(bi =>
{
//transfer data to the object
var data = fwe.GetValue(property);
KeyValuePair<object, PropertyInfo>? pinfo = resolveProperty(target, bi.targetProperty);
if (pinfo == null) return;
else pinfo.Value.Value.SetValue(pinfo.Value.Key, data);
});
}
private KeyValuePair<object, PropertyInfo>? resolveProperty(object target, string path)
{
try
{
var parts = path.Split('.');
if (parts.Length == 1) return new KeyValuePair<object, PropertyInfo>(target, target.GetType().GetProperty(parts[0]));
else //(parts.Length>1)
return resolveProperty(target.GetType().GetProperty(parts[0]).GetValue(target),
string.Join(".", parts.Skip(1)));
}
catch (Exception e) //too lazy to care :D
{
return null;
}
}
}
And to use the XAML...
<Grid ab:Bindings.OnewayBindings="IsMouseOver:mouseOver;">...</Grid>
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
I've created some simple custom ModuleManager in my silverlight application based on PRISM. I also registered this type in bootstrapper, but PRISM still use the default manager. The constructor of my CustomModuleManager is called, but the property ModuleTypeLoaders is never accessed. I can't figure it out, how can I make it work properly?
Here is bootstrapper.cs
protected override void ConfigureContainer()
{
Container.RegisterType<IShellProvider, Shell>();
Container.RegisterType<IModuleManager, CustomModuleManager>();
base.ConfigureContainer();
}
CustomModuleManager.cs
public class CustomModuleManager : ModuleManager
{
IEnumerable<IModuleTypeLoader> _typeLoaders;
public CustomModuleManager(IModuleInitializer moduleInitializer,
IModuleCatalog moduleCatalog,
ILoggerFacade loggerFacade)
: base(moduleInitializer, moduleCatalog, loggerFacade)
{
MessageBox.Show("ctor");
}
public override IEnumerable<IModuleTypeLoader> ModuleTypeLoaders
{
get
{
MessageBox.Show("getter");
if (_typeLoaders == null)
{
_typeLoaders = new List<IModuleTypeLoader>
{
new CustomXapModuleTypeLoader()
};
}
return _typeLoaders;
}
set
{
MessageBox.Show("setter");
_typeLoaders = value;
}
}
}
CustomXapModuleTypeLoader.cs
public class CustomXapModuleTypeLoader : XapModuleTypeLoader
{
protected override IFileDownloader CreateDownloader()
{
return new CustomFileDownloader();
}
}
CustomFileDownloader.cs
public class CustomFileDownloader : IFileDownloader
{
public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;
readonly FileDownloader _dler = new FileDownloader();
public CustomFileDownloader()
{
_dler.DownloadCompleted += DlerDownloadCompleted;
}
void DlerDownloadCompleted(object sender, DownloadCompletedEventArgs e)
{
_dler.DownloadCompleted -= DlerDownloadCompleted;
if (DownloadCompleted != null)
{
if (e.Cancelled || e.Error != null)
{
DownloadCompleted(this, e);
}
else
{
DownloadCompleted(this,
new DownloadCompletedEventArgs(e.Result,
e.Error,
e.Cancelled,
e.UserState));
}
}
}
public void DownloadAsync(Uri uri, object userToken)
{
_dler.DownloadAsync(uri, userToken);
}
}
Reorder your call to base.ConfigureContainer so that yours wins (last one wins):
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType<IShellProvider, Shell>();
Container.RegisterType<IModuleManager, CustomModuleManager>();
}