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
Related
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;
}
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();
}
I am using Phone7.Fx R1
The following works. The system does not react when a user presses the button. This means, than there is no reaction if Stop Game is pressed without a game has been started and vice versa.
However the button looks active. I am aware that I can bind the IsEnabled to a different property, but I would like it to bind to NewGameCanExecute and StopGameCanExecute. Is this possible?
Some XAML code:
<Preview:BindableApplicationBarIconButton Command="{Binding NewGame}" IconUri="/images/icons/appbar.add.rest.png" Text="New game" />
<Preview:BindableApplicationBarIconButton Command="{Binding StopGame}" IconUri="/images/icons/appbar.stop.rest.png" Text="Stop game" />
And the relay commands:
public RelayCommand NewGame { get; private set; }
public RelayCommand StopGame { get; private set; }
//Constructor
NewGame = new RelayCommand(NewGameExecute, NewGameCanExecute);
StopGame = new RelayCommand(StopGameExecute, StopGameCanExecute);
void NewGameExecute()
{
_gameStarted = true;
_gameControlModel.StartNewGame();
StopGame.RaiseCanExecuteChanged();
}
bool NewGameCanExecute()
{
return !_gameStarted;
}
void StopGameExecute()
{
_gameControlModel.StopGame();
_gameStarted = false;
NewGame.RaiseCanExecuteChanged();
}
bool StopGameCanExecute()
{
return _gameStarted;
}
Couple of questions and answers:
Q: Have you tried to set a breakpoint in the CanExecute functions to see if it gets called?
A: Yes. It does get called, but the icon is not grayed out, eventhough false is returned.
The Execute method is not executed, if the CanExecute method returns false. But I want the icon to be grayed out like a normal button.
SOLUTION
I spend some time and came up with a solution, which can be found here:
http://pastebin.com/MM75xACj
This is obviously a bug in whatever BindableApplicationBarIconButton implementation you're using.
Ask the author of it for help, or debug your 3rd party software yourself.
SOLUTION
I spend some time and came up with a solution and edited the applicationbariconbutton class.
namespace Phone7.Fx.Controls
{
public class BindableApplicationBarIconButton : FrameworkElement, IApplicationBarIconButton
{
public int Index { get; set; }
public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(BindableApplicationBarIconButton), new PropertyMetadata(null, OnCommandPropertyChanged));
private static void OnCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
((BindableApplicationBarIconButton)d).Command = (ICommand)e.NewValue;
}
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set {
Command.CanExecuteChanged -= ValueOnCanExecuteChanged;
SetValue(CommandProperty, value);
Command.CanExecuteChanged += ValueOnCanExecuteChanged;
}
}
private void ValueOnCanExecuteChanged(object sender, EventArgs eventArgs)
{
ICommand commandSender = sender as ICommand;
if(commandSender != null)
{IsEnabled = commandSender.CanExecute(null);}
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(BindableApplicationBarIconButton), new PropertyMetadata(null, OnCommandParameterPropertyChanged));
private static void OnCommandParameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var invokeCommand = d as BindableApplicationBarIconButton;
if (invokeCommand != null)
{
invokeCommand.SetValue(CommandParameterProperty, e.NewValue);
}
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set
{
SetValue(CommandParameterProperty, value);
}
}
public static readonly DependencyProperty CommandParameterValueProperty =
DependencyProperty.RegisterAttached("CommandParameterValue", typeof(object), typeof(BindableApplicationBarIconButton), null);
public object CommandParameterValue
{
get
{
var returnValue = GetValue(CommandParameterValueProperty);
return returnValue;
}
set { SetValue(CommandParameterValueProperty, value); }
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(BindableApplicationBarIconButton), new PropertyMetadata(true, OnEnabledChanged));
private static void OnEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
((BindableApplicationBarIconButton)d).Button.IsEnabled = (bool)e.NewValue;
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.RegisterAttached("Text", typeof(string), typeof(BindableApplicationBarIconButton), new PropertyMetadata(OnTextChanged));
public new static readonly DependencyProperty VisibilityProperty =
DependencyProperty.RegisterAttached("Visibility", typeof(Visibility), typeof(BindableApplicationBarIconButton), new PropertyMetadata(OnVisibilityChanged));
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
var button = ((BindableApplicationBarIconButton)d);
BindableApplicationBar bar = button.Parent as BindableApplicationBar;
bar.Invalidate();
}
}
public new Visibility Visibility
{
get { return (Visibility)GetValue(VisibilityProperty); }
set { SetValue(VisibilityProperty, value); }
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
((BindableApplicationBarIconButton)d).Button.Text = e.NewValue.ToString();
}
}
public ApplicationBarIconButton Button { get; set; }
public BindableApplicationBarIconButton()
{
Button = new ApplicationBarIconButton();
Button.Text = "Text";
Button.Click += ApplicationBarIconButtonClick;
}
void ApplicationBarIconButtonClick(object sender, EventArgs e)
{
if (Command != null && CommandParameter != null)
Command.Execute(CommandParameter);
else if (Command != null)
Command.Execute(CommandParameterValue);
if (Click != null)
Click(this, e);
}
public bool IsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public event EventHandler Click;
public Uri IconUri
{
get { return Button.IconUri; }
set { Button.IconUri = value; }
}
}
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'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>();
}