I am working on a project using MvvmCross and Xamarin. I am trying to add a custom transition when presenting and dismissing my modal view. Currently I am presenting it this way:
[MvxModalPresentation(WrapInNavigationController = true, ModalPresentationStyle = UIModalPresentationStyle.Custom)]
public partial class MyView : MvxViewController
{
and dismissing it this way:
NavigationController.DismissModalViewController(true);
So I have my animation ready, but I am assuming that I need to set the transition delegate to the one I've created. How do I do this?
I am fairly new to MvvmCross, so any tips and tricks are very much appreciated. Thanks!
I Am not sure, what you actually want to achieve here.
If you are looking for syntax help. This should be something like this.
this.NavigationController.TransitioningDelegate = new MyOwnDelegate();
internal class MyOwnDelegate : IUIViewControllerTransitioningDelegate
{
public IntPtr Handle => throw new NotImplementedException();
public void Dispose()
{
//throw new NotImplementedException();
}
}
But normally people use, this one. I am also giving some syntax if that helps
this.NavigationController.Delegate = new NavigationControllerDelegate();
public class NavigationControllerDelegate : UINavigationControllerDelegate
{
public NavigationControllerDelegate(IntPtr handle) : base(handle)
{
}
public NavigationControllerDelegate()
{
}
public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForOperation(UINavigationController navigationController, UINavigationControllerOperation operation, UIViewController fromViewController, UIViewController toViewController)
{
var fromVcConformA = fromViewController as ICustomTransition;
var fromVCConFromB = fromViewController as IWaterFallViewControllerProtocol;
var fromVCCConformc = fromViewController as IHorizontalPageViewControllerProtocol;
var toVcConformA = toViewController as ICustomTransition;
var toVCConfromB = toViewController as IWaterFallViewControllerProtocol;
var toVCCConformc = toViewController as IHorizontalPageViewControllerProtocol;
if ((fromVcConformA != null) && (toVcConformA != null) && ((fromVCConFromB != null && toVCCConformc != null) || (fromVCCConformc != null && toVCConfromB != null)))
{
var transition = new CustomTransition();
transition.presenting = operation == UINavigationControllerOperation.Pop;
return transition;
}
else
{
return null;
}
}
}
I use FlowDocument with BlockUIContainer and InlineUIContainer elements containing (or as base classes) some custom blocks - SVG, math formulas etc.
Because of that using Selection.Load(stream, DataFormats.XamlPackage) wont work as the serialization will drop the contents of *UIContainers except if the Child property is an image as available in Microsoft reference source:
private static void WriteStartXamlElement(...)
{
...
if ((inlineUIContainer == null || !(inlineUIContainer.Child is Image)) &&
(blockUIContainer == null || !(blockUIContainer.Child is Image)))
{
...
elementTypeStandardized = TextSchema.GetStandardElementType(elementType, /*reduceElement:*/true);
}
...
}
The only option in this case is to use is to use XamlWriter.Save and XamlReader.Load which are working flawlessly, serialize and deserialize all required properties and objects of a FlowDocument yet the Copy+Paste must be implemented manually as default implementation of Copy+Paste uses Selection.Load/Save.
Copy/Paste is critical as it is also used to handle dragging of elements in or between RichTextBox controls - the only way objects can be manipulated without custom dragging code.
This is why I am looking to implement copy/paste using a FlowDocument serialization but unfortunately there are some issues with it:
In current solution a whole FlowDocument object needs to be serialized/deserialized. Performance-wise it should not be a problem but I need to store information what selection range needs to be pasted from it (CustomRichTextBoxTag class).
Apparently objects cannot be removed from one document and added to another (a dead-end I discovered recently): 'InlineCollection' element cannot be inserted in a tree because it is already a child of a tree.
[TextElementCollection.cs]
public void InsertAfter(TextElementType previousSibling, TextElementType newItem)
{
...
if (previousSibling.Parent != this.Parent)
throw new InvalidOperationException(System.Windows.SR.Get("TextElementCollection_PreviousSiblingDoesNotBelongToThisCollection", new object[1]
{
(object) previousSibling.GetType().Name
}));
...
}
I think about setting FrameworkContentElement._parent using reflection in all elements which need to be moved to another document but that's a last resort hackish and dirty solution:
In theory I can copy only required objects: (optional) partial run with text at the beginning of selection, all paragraphs and inlines in between and and (possibly) partial run at the end, encapsulate these in a custom class and serialize/deserialize using XamlReader/XamlWriter.
Another solution I didn't think about.
Here is the custom RichTextBox control implementation with partially working custom Copy/Paste code:
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
namespace FlowMathTest
{
public class CustomRichTextBoxTag: DependencyObject
{
public static readonly DependencyProperty SelectionStartProperty = DependencyProperty.Register(
"SelectionStart",
typeof(int),
typeof(CustomRichTextBoxTag));
public int SelectionStart
{
get { return (int)GetValue(SelectionStartProperty); }
set { SetValue(SelectionStartProperty, value); }
}
public static readonly DependencyProperty SelectionEndProperty = DependencyProperty.Register(
"SelectionEnd",
typeof(int),
typeof(CustomRichTextBoxTag));
public int SelectionEnd
{
get { return (int)GetValue(SelectionEndProperty); }
set { SetValue(SelectionEndProperty, value); }
}
}
public class CustomRichTextBox: RichTextBox
{
public CustomRichTextBox()
{
DataObject.AddCopyingHandler(this, OnCopy);
DataObject.AddPastingHandler(this, OnPaste);
}
protected override void OnSelectionChanged(RoutedEventArgs e)
{
base.OnSelectionChanged(e);
var tag = Document.Tag as CustomRichTextBoxTag;
if(tag == null)
{
tag = new CustomRichTextBoxTag();
Document.Tag = tag;
}
tag.SelectionStart = Document.ContentStart.GetOffsetToPosition(Selection.Start);
tag.SelectionEnd = Document.ContentStart.GetOffsetToPosition(Selection.End);
}
private void OnCopy(object sender, DataObjectCopyingEventArgs e)
{
if(e.DataObject != null)
{
e.Handled = true;
var ms = new MemoryStream();
XamlWriter.Save(Document, ms);
e.DataObject.SetData(DataFormats.Xaml, ms);
}
}
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
var xamlData = e.DataObject.GetData(DataFormats.Xaml) as MemoryStream;
if(xamlData != null)
{
xamlData.Position = 0;
var fd = XamlReader.Load(xamlData) as FlowDocument;
if(fd != null)
{
var tag = fd.Tag as CustomRichTextBoxTag;
if(tag != null)
{
InsertAt(Document, Selection.Start, Selection.End, fd, fd.ContentStart.GetPositionAtOffset(tag.SelectionStart), fd.ContentStart.GetPositionAtOffset(tag.SelectionEnd));
e.Handled = true;
}
}
}
}
public static void InsertAt(FlowDocument destDocument, TextPointer destStart, TextPointer destEnd, FlowDocument sourceDocument, TextPointer sourceStart, TextPointer sourceEnd)
{
var destRange = new TextRange(destStart, destEnd);
destRange.Text = string.Empty;
// insert partial text of the first run in the selection
if(sourceStart.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
var sourceRange = new TextRange(sourceStart, sourceStart.GetNextContextPosition(LogicalDirection.Forward));
destStart.InsertTextInRun(sourceRange.Text);
sourceStart = sourceStart.GetNextContextPosition(LogicalDirection.Forward);
destStart = destStart.GetNextContextPosition(LogicalDirection.Forward);
}
var field = typeof(FrameworkContentElement).GetField("_parent", BindingFlags.NonPublic | BindingFlags.Instance);
while(sourceStart != null && sourceStart.CompareTo(sourceEnd) <= 0 && sourceStart.Paragraph != null)
{
var sourceInline = sourceStart.Parent as Inline;
if(sourceInline != null)
{
sourceStart.Paragraph.Inlines.Remove(sourceInline);
if(destStart.Parent is Inline)
{
field.SetValue(sourceInline, null);
destStart.Paragraph.Inlines.InsertAfter(destStart.Parent as Inline, sourceInline);
}
else
{
var p = new Paragraph();
destDocument.Blocks.InsertAfter(destStart.Paragraph, p);
p.Inlines.Add(sourceInline);
}
sourceStart = sourceStart.GetNextContextPosition(LogicalDirection.Forward);
}
else
{
var sourceBlock = sourceStart.Parent as Block;
field.SetValue(sourceBlock, null);
destDocument.Blocks.InsertAfter(destStart.Paragraph, sourceBlock);
sourceStart = sourceStart.GetNextContextPosition(LogicalDirection.Forward);
}
}
}
}
}
And the question - is there an existing solution for custom Copy+Paste code for FlowDocument using XamlReader and XamlWriter?
How to fix the code above so it won't complain about different FlowDocument object or work around this limitation?
EDIT: As an experiment I implemented 2) so that objects can be moved from one FlowDocument to another. The code above is updated - all references to the "field" variable.
It seems the bounty period is about to expire and I made a breakthrough how to implement the above problem so I will share it here.
First of all TextRange.Save has a "preserveTextElements" argument which can be used to serialize InlineUIContainer and BlockUIContainer elements. Also, both these controls are not sealed so can be used as base classes for a custom TextElement implementation.
With the above in mind:
I created an InlineMedia element inherited from InlineUIContainer which serializes it's Child "manually" into a "ChildSource" dependency property using XamlReader and XamlWriter and hides the original "Child" from default serializer
I changed the above implementation of CustomRichTextBox to copy selection using range.Save(ms, DataFormats.Xaml, true).
As you can notice, no special Paste handling is necessary as Xaml is nicely deserialized after swapping original Xaml in the clipboard and this means dragging works as copy from all CustomRichtextBox controls and Paste works even into normal RichTextBox.
The only limitation is that for all InlineMedia controls the ChildSource property need to be updated by serializing it's Child before serializing a whole document and I found no way to do it automatically (hook into TextRange.Save before element is saved).
I can live with that but a nicer solution without this problem will still get a bounty!
InlineMedia element code:
public class InlineMedia: InlineUIContainer
{
public InlineMedia()
{
}
public InlineMedia(UIElement childUIElement) : base(childUIElement)
{
UpdateChildSource();
}
public InlineMedia(UIElement childUIElement, TextPointer insertPosition)
: base(childUIElement, insertPosition)
{
UpdateChildSource();
}
public static readonly DependencyProperty ChildSourceProperty = DependencyProperty.Register
(
"ChildSource",
typeof(string),
typeof(InlineMedia),
new FrameworkPropertyMetadata(null, OnChildSourceChanged));
public string ChildSource
{
get
{
return (string)GetValue(ChildSourceProperty);
}
set
{
SetValue(ChildSourceProperty, value);
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new UIElement Child
{
get
{
return base.Child;
}
set
{
base.Child = value;
UpdateChildSource();
}
}
public void UpdateChildSource()
{
IsInternalChildSourceChange = true;
try
{
ChildSource = Save();
}
finally
{
IsInternalChildSourceChange = false;
}
}
public string Save()
{
if(Child == null)
{
return null;
}
using(var stream = new MemoryStream())
{
XamlWriter.Save(Child, stream);
stream.Position = 0;
using(var reader = new StreamReader(stream, Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
}
public void Load(string sourceData)
{
if(string.IsNullOrEmpty(sourceData))
{
base.Child = null;
}
else
{
using(var stream = new MemoryStream(Encoding.UTF8.GetBytes(sourceData)))
{
var child = XamlReader.Load(stream);
base.Child = (UIElement)child;
}
}
}
private static void OnChildSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var img = (InlineMedia) sender;
if(img != null && !img.IsInternalChildSourceChange)
{
img.Load((string)e.NewValue);
}
}
protected bool IsInternalChildSourceChange { get; private set; }
}
CustomRichTextBox control code:
public class CustomRichTextBox: RichTextBox
{
public CustomRichTextBox()
{
DataObject.AddCopyingHandler(this, OnCopy);
}
private void OnCopy(object sender, DataObjectCopyingEventArgs e)
{
if(e.DataObject != null)
{
UpdateDocument();
var range = new TextRange(Selection.Start, Selection.End);
using(var ms = new MemoryStream())
{
range.Save(ms, DataFormats.Xaml, true);
ms.Position = 0;
using(var reader = new StreamReader(ms, Encoding.UTF8))
{
var xaml = reader.ReadToEnd();
e.DataObject.SetData(DataFormats.Xaml, xaml);
}
}
e.Handled = true;
}
}
public void UpdateDocument()
{
ObjectHelper.ExecuteRecursive<InlineMedia>(Document, i => i.UpdateChildSource(), FlowDocumentVisitors);
}
private static readonly Func<object, object>[] FlowDocumentVisitors =
{
x => (x is FlowDocument) ? ((FlowDocument) x).Blocks : null,
x => (x is Section) ? ((Section) x).Blocks : null,
x => (x is BlockUIContainer) ? ((BlockUIContainer) x).Child : null,
x => (x is InlineUIContainer) ? ((InlineUIContainer) x).Child : null,
x => (x is Span) ? ((Span) x).Inlines : null,
x => (x is Paragraph) ? ((Paragraph) x).Inlines : null,
x => (x is Table) ? ((Table) x).RowGroups : null,
x => (x is Table) ? ((Table) x).Columns : null,
x => (x is Table) ? ((Table) x).RowGroups.SelectMany(rg => rg.Rows) : null,
x => (x is Table) ? ((Table) x).RowGroups.SelectMany(rg => rg.Rows).SelectMany(r => r.Cells) : null,
x => (x is TableCell) ? ((TableCell) x).Blocks : null,
x => (x is TableCell) ? ((TableCell) x).BorderBrush : null,
x => (x is List) ? ((List) x).ListItems : null,
x => (x is ListItem) ? ((ListItem) x).Blocks : null
};
}
and finally ObjectHelper class - a visitor helper:
public static class ObjectHelper
{
public static void ExecuteRecursive(object item, Action<object> execute, params Func<object, object>[] childSelectors)
{
ExecuteRecursive<object, object>(item, null, (c, i) => execute(i), childSelectors);
}
public static void ExecuteRecursive<TObject>(object item, Action<TObject> execute, params Func<object, object>[] childSelectors)
{
ExecuteRecursive<object, TObject>(item, null, (c, i) => execute(i), childSelectors);
}
public static void ExecuteRecursive<TContext, TObject>(object item, TContext context, Action<TContext, TObject> execute, params Func<object, object>[] childSelectors)
{
ExecuteRecursive(item, context, (c, i) =>
{
if(i is TObject)
{
execute(c, (TObject)i);
}
}, childSelectors);
}
public static void ExecuteRecursive<TContext>(object item, TContext context, Action<TContext, object> execute, params Func<object, object>[] childSelectors)
{
execute(context, item);
if(item is IEnumerable)
{
foreach(var subItem in item as IEnumerable)
{
ExecuteRecursive(subItem, context, execute, childSelectors);
}
}
if(childSelectors != null)
{
foreach(var subItem in childSelectors.Select(x => x(item)).Where(x => x != null))
{
ExecuteRecursive(subItem, context, execute, childSelectors);
}
}
}
}
1.In current solution a whole FlowDocument object needs to be serialized/deserialized. Performance-wise it should not be a problem
but I need to store information what selection range needs to be
pasted from it (CustomRichTextBoxTag class).
This smells like an opportunity to use an attached property based on the intended behavior you identified. I understand attached properties as a way of adding additional behavior to an element. When you register an attached property, you can add an event handler for when that property value changes.
To take advantage of this, I would wire this attached property to a DataTrigger to update the selection range value for your copy/paste operation.
2.Apparently objects cannot be removed from one document and added to another (a dead-end I discovered recently): 'InlineCollection' element
cannot be inserted in a tree because it is already a child of a tree.
You can get around this by constructing your elements programmatically and also removing your elements programmatically. At the end of the day, you're mainly dealing with either an ItemsControl or a ContentControl. In this case your working with an ItemsControl (i.e. document). As a result just add and remove child elements from your ItemsControl (document) programmatically.
Currently I'm implementing a Screen indicating wheater a module is not existing or still in development.
The Back Button has the following code:
regionNavigationService.Journal.GoBack();
This is working as expected. But the user is not coming from the Home Screen. So I need to access the View Name from the last Entry in Navigation Journal.
Example: User is coming from Settings Screen => The text should display "Back to Settings Screen"
Assuming the view name you are looking for is when you do new Uri("Main", UriKind.Relative) that you would want the word Main as the view name.
The forward and backward stacks in the RegionNavigationJournal are private. You could use reflection to get access to it.
var journal = regionNavigationService.Journal as RegionNavigationJournal;
if (journal != null)
{
var stack =
(Stack<IRegionNavigationJournalEntry>)
typeof (RegionNavigationJournal).GetField("backStack",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(journal);
var name = stack.Peek().Uri.OriginalString;
}
Or a better way is to implement your own IRegionNavigationJournal that is a wrapper around it. This is using Unity to constructor inject the default RegionNavigationJournal if using MEF you might need to put the ImportingConstructorAttribute on it.
public class RegionNavigationJournalWrapper : IRegionNavigationJournal
{
private readonly IRegionNavigationJournal _regionNavigationJournal;
private readonly Stack<Uri> _backStack = new Stack<Uri>();
// Constructor inject prism default RegionNavigationJournal to wrap
public RegionNavigationJournalWrapper(RegionNavigationJournal regionNavigationJournal)
{
_regionNavigationJournal = regionNavigationJournal;
}
public string PreviousViewName
{
get
{
if (_backStack.Count > 0)
{
return _backStack.Peek().OriginalString;
}
return String.Empty;
}
}
public bool CanGoBack
{
get { return _regionNavigationJournal.CanGoBack; }
}
public bool CanGoForward
{
get { return _regionNavigationJournal.CanGoForward; }
}
public void Clear()
{
_backStack.Clear();
_regionNavigationJournal.Clear();
}
public IRegionNavigationJournalEntry CurrentEntry
{
get { return _regionNavigationJournal.CurrentEntry; }
}
public void GoBack()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go back
_regionNavigationJournal.GoBack();
// if currententry isn't equal to previous entry then we moved back
if (CurrentEntry != currentEntry)
{
_backStack.Pop();
}
}
public void GoForward()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go forward
_regionNavigationJournal.GoForward();
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry != currentEntry)
{
_backStack.Push(currentEntry.Uri);
}
}
public INavigateAsync NavigationTarget
{
get { return _regionNavigationJournal.NavigationTarget; }
set { _regionNavigationJournal.NavigationTarget = value; }
}
public void RecordNavigation(IRegionNavigationJournalEntry entry)
{
var currentEntry = CurrentEntry;
_regionNavigationJournal.RecordNavigation(entry);
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry == entry)
{
_backStack.Push(currentEntry.Uri);
}
}
}
If using unity in your Prism Bootstrapper you will need to replace the default registration of the IRegionNavigationJournal
protected override void ConfigureContainer()
{
this.RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournalWrapper), false);
base.ConfigureContainer();
}
If using MEF you will need to put the ExportAttribute on top of the RegionNavigationJournalWrapper
[Export(typeof(IRegionNavigationJournal))]
You can see http://msdn.microsoft.com/en-us/library/gg430866%28v=pandp.40%29.aspx for more information on replacing their default implementation with your own. Once you have the wrapper you will still need to cast it as RegionNavigationJournalWrapper to get access to the PreviousViewName so still not perfect or create an interface that RegionNavigationJournalWrapper also implements to cast to that to get you access to the PreviousViewName
I'm working on a debugging application and I'm building a form ( based on a very small extension of the System.Windows.Form ) that is intended to take in a Constructor and create a new Parameter control for each Parameter in the Constructor.
My issue at the moment is that for some reason, my ParameterControls are being added to the form, but only the first one added is visible at the end of the operation.
The code in question and the supporting method; are as follows:
class ConstructorDialog : Namespace.Forms.Form
{
protected void InitializeInterface()
{
if (this.TargetType == null)
{
throw new InvalidOperationException("Cannot GenerateFields for ConstructorDialog. ConstructorDialog TargetType is null.");
}
else if (this.TargetConstructor == null)
{
}
else
{
foreach( ParameterInfo Parameter in this.TargetConstructor.GetParameters())
{
try
{
ParameterControl NewParameterControl = new ParameterControl(Parameter);
NewParameterControl.Location = new Point(0, 30 + (30 * Parameter.Position));
this.AddControl(NewParameterControl);
continue;
}
catch (Exception e)
{
}
}
return;
}
}
}
class Namespace.Forms.Form : System.Windows.Forms.Form
{
public Control AddControl(Control Control)
{
if (Control == null)
throw new InvalidOperationException("Form cannot AddControl. Control is null.");
else
{
this.Controls.Add(Control);
return Control;
}
}
}
class Namespace.Debugging.ParameterControl : Namespace.Forms.UserControl
{
protected void InitializeInterface()
{
if (this.TargetParameter == null)
{
throw new InvalidOperationException("Cannot InitializeInterface for ConstructorParameterControl. ConstructorParameterControl TargetParameter is null.");
}
else
{
this.Controls.Clear();
this.AddLabel(this.TargetParameter.Name + "_Label", this.TargetParameter.Name, new Point(25,0));
return;
}
}
}
class Namespace.Forms.UserControl : System.Windows.Forms.UserControl
{
public Label AddLabel(String LabelName, String LabelText, Point Location)
{
if (String.IsNullOrEmpty(LabelName))
throw new ArgumentNullException();
else if (String.IsNullOrEmpty(LabelText))
throw new ArgumentNullException();
else
{
Label NewLabel = new Label();
NewLabel.Name = LabelName;
NewLabel.Text = LabelText;
NewLabel.Location = Location;
return this.AddLabel(NewLabel);
}
}
public Label AddLabel(Label Label)
{
if (Label == null)
throw new ArgumentNullException();
else
{
this.Controls.Add(Label);
return Label;
}
}
}
My Form extension is still in its infancy, so it's quite likely that I overlooked something ( especially since my forms knowledge is only apprentice-worthy ), but this operation seems simple enough and in my assessment ought to work.
Some debugging information:
The controls are being added to the base 'Controls' collection.
The positions of the controls are being set to what they ought to, so it is not a matter of them overlapping.
No exceptions are encountered during execution.
As suggested by #sysexpand, I manually set the height and width of the ParameterControl objects, as well as setting the 'Visible' property to true and seem to have resolved the issue.
My assessment of this is that by setting these variables before the ParameterControl is a member of its parent, these variables are being overwritten when the control is added to its parent.
I've been doing a refactor of an existing application, and I am attempting to use an Attribute on a property to trigger the NotifyPropertyChanged event using Unity interception. I got to the point where the event was firing, but the controls were not updating.
I wasn't sure if it was invoking the event correctly, so on my ViewModelBase I created a DispatchPropertyChanged method that invokes the property changed event. This method works to launch property changed when called directly from a view model, but when called from a ViewModel retrieved through reflection inside the intercept handler, it doesn't work.
I've inserted a link to the https://www.dropbox.com/s/9qg2n0gd2n62elc/WPFUnityTest.zip. If you open this solution and run the application, then click the "Reset" button, you'll see that the "Normal" text box updates, but the "Unity" text box does not update.
If you place a breakpoint at line 65 of the MainWindowViewModel and line 53 of the NotifyPropertyChangedHandler you'll see that the handler is working, the dispatch method is being called, and the event is being invoked. However, only the "Normal" one updates.
Any help on why the "Unity" text box isn't updating would be wonderful, thanks!
Amanda
EDIT:
Sorry for not including this originally, I really had no idea where the problem was. This was the original code for the interception behavior that was correct below:
public class NotifyPropertyChangedHandler : IInterceptionBehavior
{
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
var npcAttribute = getNotifyPropertyChangedAttributeForSetter(input) as NotifyPropertyChangedAttribute;
if (npcAttribute != null)
{
if (npcAttribute.TimingOption == PropertyChangedTiming.Always||
shouldRaiseEvent(input))
{
raiseEvent(input);
}
}
return getNext()(input, getNext);
}
public IEnumerable<Type> GetRequiredInterfaces()
{
return new[] { typeof(INotifyPropertyChanged) };
}
public bool WillExecute { get { return true; } }
private object getNotifyPropertyChangedAttributeForSetter(IMethodInvocation input)
{
if (!input.MethodBase.Name.StartsWith("set_"))
{
return null;
}
return input.MethodBase.ReflectedType.GetProperty(input.MethodBase.Name.Substring(4))
.GetCustomAttributes(true).SingleOrDefault(attr => attr.GetType() == typeof (NotifyPropertyChangedAttribute));
}
private void raiseEvent(IMethodInvocation input)
{
raiseEvent(input, new PropertyChangedEventArgs(input.MethodBase.Name.Substring(4)));
}
private void raiseEvent(IMethodInvocation input, PropertyChangedEventArgs eventArgs)
{
var viewModel = input.Target as ViewModelBase;
if (viewModel != null)
{
viewModel.DispatchPropertyChangedEvent(eventArgs.PropertyName);
}
}
private static bool shouldRaiseEvent(IMethodInvocation input)
{
var methodBase = input.MethodBase;
if (!methodBase.IsSpecialName || !methodBase.Name.StartsWith("set_"))
{
return false;
}
var propertyName = methodBase.Name.Substring(4);
var property = methodBase.ReflectedType.GetProperty(propertyName);
var getMethod = property.GetGetMethod();
if (getMethod == null)
{
return false;
}
var oldValue = getMethod.Invoke(input.Target, null);
var value = input.Arguments[0];
return (value == null) ? oldValue !=null :
!value.Equals(oldValue);
}
}
There is a problem in your NotifyPropertyChangedHandler.Invoke method code:
Your Code
I have added some comments to describe what the problem is with this code.
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
var npcAttribute = getNotifyPropertyChangedAttributeForSetter(input) as NotifyPropertyChangedAttribute;
if (npcAttribute != null)
{
if (npcAttribute.TimingOption == PropertyChangedTiming.Always||
shouldRaiseEvent(input))
{
// You are raising property changed event here,
// however the value of the property is not changed until getNext()(input, getNext) called
// So, WPF will re-read the same "old" value and you don't see anything updated on the screen
raiseEvent(input);
}
}
// Property value is updated here!!!
return getNext()(input, getNext);
}
Change that code to something like:
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
var temp = false;
var npcAttribute = getNotifyPropertyChangedAttributeForSetter(input) as NotifyPropertyChangedAttribute;
if (npcAttribute != null)
{
if (npcAttribute.TimingOption == PropertyChangedTiming.Always||
shouldRaiseEvent(input))
{
temp = true;
}
}
var returnValue = getNext()(input, getNext); // Changing the value here
if (temp) raiseEvent(input); // Raising property changed event, if necessary
return returnValue;
}
I have tested this code and it works. Hope this helps!