Behaviour to un/hide Framework Element - c#

I have created a custom TextBlock that changes the Visibility after some seconds specified by a DependencyProperty ShowTime:
<customUserControl:AutoHideTextBlock Text="AutoHideTextBlock" Visibility="{Binding IsVisibleEnabled, Converter={StaticResource BoolToVisConverter}}" ShowTime="3"/>
This is a nice solution and it works, the problem is that I have several other elements that needs the same behaviour and I cannot really make it a CustomUserControl for all of them, I have created the following class to help me with that:
public class AutoHideExtension
{
public static readonly DependencyProperty VisibilityListenerProperty =
DependencyProperty.RegisterAttached(
"VisibilityListener",
typeof(bool),
typeof(AutoHideExtension),
new PropertyMetadata(false, VisibilityChanged));
public static double GetVisibilityListener(DependencyObject obj)
=> (double)obj.GetValue(VisibilityListenerProperty);
public static void SetVisibilityListener(DependencyObject obj, double value)
=> obj.SetValue(VisibilityListenerProperty, value);
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = (FrameworkElement)d;
if (element.Visibility == Visibility.Collapsed || !IsLoaded)
{
return;
}
DispatcherTimer timer = new DispatcherTimer(DispatcherPriority.Background)
{
Interval =
new TimeSpan(
0,
0,
ShowTime)
};
timer.Tick += (senderEvent, args) =>
{
element.Visibility = Visibility.Collapsed;
timer.Stop();
};
timer.Start();
}
}
The idea is that I can attach this new property to any element and change the visibility after the specified time, something like follows:
<TextBlock Text="TextToHide"
helpers:AutoHideExtension.VisibilityListener="{Binding ChangesSavedEnabled}"/>
The problem is that I don't know how to specify the ShowTime as property in the extension class, and that this is simply not working since is not changing the Visibility.
Any Ideas or suggestions on how I can continue forward with this?
Thanks in advance!

Your dependency property VisibilityListener should get and set a bool value:
public static bool GetVisibilityListener(DependencyObject obj)
=> (bool)obj.GetValue(VisibilityListenerProperty);
public static void SetVisibilityListener(DependencyObject obj, bool value)
=> obj.SetValue(VisibilityListenerProperty, value);
You could then define another attached property for the ShowTime, or you could define a Blend behaviour that contains two properties:
<TextBlock xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Text="TextToHide">
<i:Interaction.Behaviors>
<local:AutoHideExtensionBehavior VisibilityListener="{Binding ChangesSavedEnabled}" ShowTime="3" />
</i:Interaction.Behaviors>
</TextBlock>

Related

Get width and height of Grid one way from XAML (target) to source (property) [duplicate]

I want to write a ViewModel that always knows the current state of some read-only dependency properties from the View.
Specifically, my GUI contains a FlowDocumentPageViewer, which displays one page at a time from a FlowDocument. FlowDocumentPageViewer exposes two read-only dependency properties called CanGoToPreviousPage and CanGoToNextPage. I want my ViewModel to always know the values of these two View properties.
I figured I could do this with a OneWayToSource databinding:
<FlowDocumentPageViewer
CanGoToNextPage="{Binding NextPageAvailable, Mode=OneWayToSource}" ...>
If this was allowed, it would be perfect: whenever the FlowDocumentPageViewer's CanGoToNextPage property changed, the new value would get pushed down into the ViewModel's NextPageAvailable property, which is exactly what I want.
Unfortunately, this doesn't compile: I get an error saying 'CanGoToPreviousPage' property is read-only and cannot be set from markup. Apparently read-only properties don't support any kind of databinding, not even databinding that's read-only with respect to that property.
I could make my ViewModel's properties be DependencyProperties, and make a OneWay binding going the other way, but I'm not crazy about the separation-of-concerns violation (ViewModel would need a reference to the View, which MVVM databinding is supposed to avoid).
FlowDocumentPageViewer doesn't expose a CanGoToNextPageChanged event, and I don't know of any good way to get change notifications from a DependencyProperty, short of creating another DependencyProperty to bind it to, which seems like overkill here.
How can I keep my ViewModel informed of changes to the view's read-only properties?
Yes, I've done this in the past with the ActualWidth and ActualHeight properties, both of which are read-only. I created an attached behavior that has ObservedWidth and ObservedHeight attached properties. It also has an Observe property that is used to do the initial hook-up. Usage looks like this:
<UserControl ...
SizeObserver.Observe="True"
SizeObserver.ObservedWidth="{Binding Width, Mode=OneWayToSource}"
SizeObserver.ObservedHeight="{Binding Height, Mode=OneWayToSource}"
So the view model has Width and Height properties that are always in sync with the ObservedWidth and ObservedHeight attached properties. The Observe property simply attaches to the SizeChanged event of the FrameworkElement. In the handle, it updates its ObservedWidth and ObservedHeight properties. Ergo, the Width and Height of the view model is always in sync with the ActualWidth and ActualHeight of the UserControl.
Perhaps not the perfect solution (I agree - read-only DPs should support OneWayToSource bindings), but it works and it upholds the MVVM pattern. Obviously, the ObservedWidth and ObservedHeight DPs are not read-only.
UPDATE: here's code that implements the functionality described above:
public static class SizeObserver
{
public static readonly DependencyProperty ObserveProperty = DependencyProperty.RegisterAttached(
"Observe",
typeof(bool),
typeof(SizeObserver),
new FrameworkPropertyMetadata(OnObserveChanged));
public static readonly DependencyProperty ObservedWidthProperty = DependencyProperty.RegisterAttached(
"ObservedWidth",
typeof(double),
typeof(SizeObserver));
public static readonly DependencyProperty ObservedHeightProperty = DependencyProperty.RegisterAttached(
"ObservedHeight",
typeof(double),
typeof(SizeObserver));
public static bool GetObserve(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (bool)frameworkElement.GetValue(ObserveProperty);
}
public static void SetObserve(FrameworkElement frameworkElement, bool observe)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObserveProperty, observe);
}
public static double GetObservedWidth(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (double)frameworkElement.GetValue(ObservedWidthProperty);
}
public static void SetObservedWidth(FrameworkElement frameworkElement, double observedWidth)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObservedWidthProperty, observedWidth);
}
public static double GetObservedHeight(FrameworkElement frameworkElement)
{
frameworkElement.AssertNotNull("frameworkElement");
return (double)frameworkElement.GetValue(ObservedHeightProperty);
}
public static void SetObservedHeight(FrameworkElement frameworkElement, double observedHeight)
{
frameworkElement.AssertNotNull("frameworkElement");
frameworkElement.SetValue(ObservedHeightProperty, observedHeight);
}
private static void OnObserveChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = (FrameworkElement)dependencyObject;
if ((bool)e.NewValue)
{
frameworkElement.SizeChanged += OnFrameworkElementSizeChanged;
UpdateObservedSizesForFrameworkElement(frameworkElement);
}
else
{
frameworkElement.SizeChanged -= OnFrameworkElementSizeChanged;
}
}
private static void OnFrameworkElementSizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateObservedSizesForFrameworkElement((FrameworkElement)sender);
}
private static void UpdateObservedSizesForFrameworkElement(FrameworkElement frameworkElement)
{
// WPF 4.0 onwards
frameworkElement.SetCurrentValue(ObservedWidthProperty, frameworkElement.ActualWidth);
frameworkElement.SetCurrentValue(ObservedHeightProperty, frameworkElement.ActualHeight);
// WPF 3.5 and prior
////SetObservedWidth(frameworkElement, frameworkElement.ActualWidth);
////SetObservedHeight(frameworkElement, frameworkElement.ActualHeight);
}
}
I use a universal solution which works not only with ActualWidth and ActualHeight, but also with any data you can bind to at least in reading mode.
The markup looks like this, provided ViewportWidth and ViewportHeight are properties of the view model
<Canvas>
<u:DataPiping.DataPipes>
<u:DataPipeCollection>
<u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualWidth}"
Target="{Binding Path=ViewportWidth, Mode=OneWayToSource}"/>
<u:DataPipe Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualHeight}"
Target="{Binding Path=ViewportHeight, Mode=OneWayToSource}"/>
</u:DataPipeCollection>
</u:DataPiping.DataPipes>
<Canvas>
Here is the source code for the custom elements
public class DataPiping
{
#region DataPipes (Attached DependencyProperty)
public static readonly DependencyProperty DataPipesProperty =
DependencyProperty.RegisterAttached("DataPipes",
typeof(DataPipeCollection),
typeof(DataPiping),
new UIPropertyMetadata(null));
public static void SetDataPipes(DependencyObject o, DataPipeCollection value)
{
o.SetValue(DataPipesProperty, value);
}
public static DataPipeCollection GetDataPipes(DependencyObject o)
{
return (DataPipeCollection)o.GetValue(DataPipesProperty);
}
#endregion
}
public class DataPipeCollection : FreezableCollection<DataPipe>
{
}
public class DataPipe : Freezable
{
#region Source (DependencyProperty)
public object Source
{
get { return (object)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(object), typeof(DataPipe),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnSourceChanged)));
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DataPipe)d).OnSourceChanged(e);
}
protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e)
{
Target = e.NewValue;
}
#endregion
#region Target (DependencyProperty)
public object Target
{
get { return (object)GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(object), typeof(DataPipe),
new FrameworkPropertyMetadata(null));
#endregion
protected override Freezable CreateInstanceCore()
{
return new DataPipe();
}
}
If anyone else is interested, I coded up an approximation of Kent's solution here:
class SizeObserver
{
#region " Observe "
public static bool GetObserve(FrameworkElement elem)
{
return (bool)elem.GetValue(ObserveProperty);
}
public static void SetObserve(
FrameworkElement elem, bool value)
{
elem.SetValue(ObserveProperty, value);
}
public static readonly DependencyProperty ObserveProperty =
DependencyProperty.RegisterAttached("Observe", typeof(bool), typeof(SizeObserver),
new UIPropertyMetadata(false, OnObserveChanged));
static void OnObserveChanged(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem = depObj as FrameworkElement;
if (elem == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
elem.SizeChanged += OnSizeChanged;
else
elem.SizeChanged -= OnSizeChanged;
}
static void OnSizeChanged(object sender, RoutedEventArgs e)
{
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
FrameworkElement elem = e.OriginalSource as FrameworkElement;
if (elem != null)
{
SetObservedWidth(elem, elem.ActualWidth);
SetObservedHeight(elem, elem.ActualHeight);
}
}
#endregion
#region " ObservedWidth "
public static double GetObservedWidth(DependencyObject obj)
{
return (double)obj.GetValue(ObservedWidthProperty);
}
public static void SetObservedWidth(DependencyObject obj, double value)
{
obj.SetValue(ObservedWidthProperty, value);
}
// Using a DependencyProperty as the backing store for ObservedWidth. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ObservedWidthProperty =
DependencyProperty.RegisterAttached("ObservedWidth", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));
#endregion
#region " ObservedHeight "
public static double GetObservedHeight(DependencyObject obj)
{
return (double)obj.GetValue(ObservedHeightProperty);
}
public static void SetObservedHeight(DependencyObject obj, double value)
{
obj.SetValue(ObservedHeightProperty, value);
}
// Using a DependencyProperty as the backing store for ObservedHeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ObservedHeightProperty =
DependencyProperty.RegisterAttached("ObservedHeight", typeof(double), typeof(SizeObserver), new UIPropertyMetadata(0.0));
#endregion
}
Feel free to use it in your apps. It works well. (Thanks Kent!)
Here is another solution to this "bug" which I blogged about here:
OneWayToSource Binding for ReadOnly Dependency Property
It works by using two Dependency Properties, Listener and Mirror. Listener is bound OneWay to the TargetProperty and in the PropertyChangedCallback it updates the Mirror property which is bound OneWayToSource to whatever was specified in the Binding. I call it PushBinding and it can be set on any read-only Dependency Property like this
<TextBlock Name="myTextBlock"
Background="LightBlue">
<pb:PushBindingManager.PushBindings>
<pb:PushBinding TargetProperty="ActualHeight" Path="Height"/>
<pb:PushBinding TargetProperty="ActualWidth" Path="Width"/>
</pb:PushBindingManager.PushBindings>
</TextBlock>
Download Demo Project Here.
It contains source code and short sample usage.
One last note, since .NET 4.0 we are even further away from built-in-support for this, since a OneWayToSource Binding reads the value back from the Source after it has updated it
I like Dmitry Tashkinov's solution!
However it crashed my VS in design mode. That's why I added a line to OnSourceChanged method:
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!((bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue))
((DataPipe)d).OnSourceChanged(e);
}
I think it can be done a bit simpler:
xaml:
behavior:ReadOnlyPropertyToModelBindingBehavior.ReadOnlyDependencyProperty="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
behavior:ReadOnlyPropertyToModelBindingBehavior.ModelProperty="{Binding MyViewModelProperty}"
cs:
public class ReadOnlyPropertyToModelBindingBehavior
{
public static readonly DependencyProperty ReadOnlyDependencyPropertyProperty = DependencyProperty.RegisterAttached(
"ReadOnlyDependencyProperty",
typeof(object),
typeof(ReadOnlyPropertyToModelBindingBehavior),
new PropertyMetadata(OnReadOnlyDependencyPropertyPropertyChanged));
public static void SetReadOnlyDependencyProperty(DependencyObject element, object value)
{
element.SetValue(ReadOnlyDependencyPropertyProperty, value);
}
public static object GetReadOnlyDependencyProperty(DependencyObject element)
{
return element.GetValue(ReadOnlyDependencyPropertyProperty);
}
private static void OnReadOnlyDependencyPropertyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
SetModelProperty(obj, e.NewValue);
}
public static readonly DependencyProperty ModelPropertyProperty = DependencyProperty.RegisterAttached(
"ModelProperty",
typeof(object),
typeof(ReadOnlyPropertyToModelBindingBehavior),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static void SetModelProperty(DependencyObject element, object value)
{
element.SetValue(ModelPropertyProperty, value);
}
public static object GetModelProperty(DependencyObject element)
{
return element.GetValue(ModelPropertyProperty);
}
}

How to avoid using extra attached property?

I have attached behavior with 2 properties. Here is what I am trying to do, for this question details are optional.
First one is used to enable/disable behavior:
public static bool GetEnableHasErrors(DependencyObject obj) => (bool)obj.GetValue(EnableHasErrorsProperty);
public static void SetEnableHasErrors(DependencyObject obj, bool value) => obj.SetValue(EnableHasErrorsProperty, value);
public static readonly DependencyProperty EnableHasErrorsProperty =
DependencyProperty.RegisterAttached("EnableHasErrors", typeof(bool), typeof(Behaviors), new PropertyMetadata((d, e) =>
{
var element = d as FrameworkElement;
if (element == null)
throw new ArgumentException("Only used with FrameworkElement");
var handler = new RoutedEventHandler((s, a) => { ... }); // logic to set value of HasErrorsProperty attached property on element
if ((bool)e.NewValue)
element.SomeRoutedEvent += handler;
else
element.SomeRoutedEvent -= handler;
}));
Second one is used to pass the result out:
public static bool GetHasErrors(DependencyObject obj) => (bool)obj.GetValue(HasErrorsProperty);
public static void SetHasErrors(DependencyObject obj, bool value) => obj.SetValue(HasErrorsProperty, value);
public static readonly DependencyProperty HasErrorsProperty =
DependencyProperty.RegisterAttached("HasErrors", typeof(bool), typeof(Behaviors));
And this result can go into view model via normal binding or used in the view, whatever:
<Grid local:Behaviors.EnableHasErrors="True"
local:Behaviors.HasErrors="{Binding HasErrors, Mode=OneWayToSource}" >
It feels wrong what I need 2 dependency properties for this. Is it possible to use just one? Couldn't I somehow infer inside behavior what I have logic enabled by having binding set? Isn't that enough?
I tried to use single property of BindingBase type, failed, found my own question and this duplicate with not safisfying answer, so BindingBase feels wrong to me.
Ideas?
I don't know of any way to avoid this for your specific case.
For more complex behaviors like this, it can be useful to use an attached behavior. Attached behaviors have methods which are called when the behavior is attached or detached, which you can use to subscribe to / unsubscribe from events. These are however significantly more verbose to use.
For example:
public class ErrorsBehavior : Behavior<FrameworkElement>
{
public bool HasErrors
{
get => (bool )this.GetValue(HasErrorsProperty);
set => this.SetValue(HasErrorsProperty, value);
}
public static readonly DependencyProperty HasErrorsProperty =
DependencyProperty.Register(nameof(HasErrors), typeof(bool), typeof(ErrorsBehavior ), new PropertyMetadata(false));
protected override void OnAttached()
{
AssociatedObject.SomeRoutedEvent += ...
}
protected override void OnDetaching()
{
AssociatedObject.SomeRoutedEvent -= ...
}
}
Usage would then be something like:
<Grid>
<i:Interaction.Behaviors>
<my:ErrorsBehavior HasErrors="{Binding HasErrors, Mode=OneWayToSource}"/>
</i:Interaction.Behaviors>
</Grid>
These used to be available only if you specifically installed the "Blend for Visual Studio SDK for .NET" component (Visual Studio 2017), but are now available in the Microsoft.Behaviors.Xaml.Wpf NuGet package.
If you've got a two-way binding which takes a more complex type than bool (such as object), there is a trick you can do:
public static class MyExtensions
{
private static readonly object initialBindingTarget = new object();
public static object GetSomething(DependencyObject obj) => obj.GetValue(SomethingProperty);
public static void SetSomething(DependencyObject obj, object value) => obj.SetValue(SomethingProperty, value);
public static readonly DependencyProperty SomethingProperty =
DependencyProperty.RegisterAttached(
"Something",
typeof(object),
typeof(MyExtensions),
new FrameworkPropertyMetadata(
initialBindingTarget,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (d, e) =>
{
// Trick to see if this is the first time we're set
if (e.OldValue == initialBindingTarget)
{
// Do your first-time initialisation here
}
}));
}
This uses a sentinel initial value of initialBindingTarget, and checks to see when the binding changes the value away from this.

Using attached properties in WPF

I am trying to create a Read Only attach property in WPF that will calculate the total Visual Child count of the control. The benefit of this is not important to me, it's being able to use attach properties properly!
Firstly I've declared my property like so:
internal static readonly DependencyPropertyKey TotalChildCountPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("TotalChildCount", typeof(int), typeof(MyAttachClass), new FrameworkPropertyMetadata(0));
public static readonly DependencyProperty TotalChildCountProperty = TotalChildCountPropertyKey.DependencyProperty;
public static int GetTotalChildCount(DependencyObject obj)
{
return (int)obj.GetValue(TotalChildCountProperty);
}
public static void SetTotalChildCount(DependencyObject obj, int value)
{
obj.SetValue(TotalChildCountPropertyKey, value);
}
I also have a recursive method declared else where like so:
public static class Recursive
{
public static IEnumerable<DependencyObject> GetAllChildren(DependencyObject obj)
{
List<DependencyObject> col = new List<DependencyObject>();
GetAllChildrenImp(obj, col);
return col;
}
private static void GetAllChildrenImp(DependencyObject current, List<DependencyObject> col)
{
if (current != null)
{
col.Add(current);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++ )
{
GetAllChildrenImp(VisualTreeHelper.GetChild(current, i), col);
}
}
}
}
Now I wish to use this method to assign a value to the GetTotalChildCount property, but I cannot figure out the best way of doing. I could add an event handler to the dependency property changes, but this will never fire because I will only be reading from the value in xaml.
Here's how I am using it in xaml:
<TextBox DataContext="{RelativeSource Self}" Text="{Binding local:MyAttachClass.TotalChildCount}"></TextBox>
So to summarize. I wish to set a DependencyObjects TotalChildCount attach property and then be able to bind to it in xaml. As it stands this is not working, the GetTotalChildCount is not even getting hit.
Oh this is my first question, hopefully I was clear enough
You can try it this way
Attached property
public class MyAttachClass
{
public static readonly DependencyProperty TotalChildCountProperty = DependencyProperty.RegisterAttached("TotalChildCount", typeof(int), typeof(MyAttachClass),
new PropertyMetadata(-1, OnTotalChildCountChanged));
public static int GetTotalChildCount(DependencyObject obj)
{
return (int)obj.GetValue(TotalChildCountProperty);
}
public static void SetTotalChildCount(DependencyObject obj, int value)
{
obj.SetValue(TotalChildCountProperty, value);
}
public static void OnTotalChildCountChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TextBox txt = sender as TextBox;
if (txt != null)
{
var children = Recursive.GetAllChildren(txt);
txt.Text = children.Count().ToString();
}
}
}
And the xaml as
<TextBox local:MyAttachClass.TotalChildCount="0" ></TextBox>
Hope it helps!

DependencyProperty's value is null when method in attached property's class is called

we have been working a complete day on this problem and have it all summed up to a small example. We are currently converting a project from Silverlight to WPF, in Silverlight both versions work, in WPF only one does.
We have a simple control with a string-type dependencyproperty like this:
public class MyControl : Control
{
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(MyControl), new PropertyMetadata(null, TextChanged));
private static void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
Then we have a class with an attached property as follows:
public class MyAttachedProperty
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.RegisterAttached("Description", typeof(String), typeof(MyAttachedProperty), new PropertyMetadata(null, DescriptionPropertyChanged));
public static String GetDescription(DependencyObject obj, String value)
{
return (String)obj.GetValue(DescriptionProperty);
}
public static void SetDescription(DependencyObject obj, String value)
{
obj.SetValue(DescriptionProperty, value);
}
private static void DescriptionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var MySuperbControl = d as MyControl;
Debug.WriteLine("The control's text is: " + MySuperbControl.Text);
}
public static void DoNothing()
{
}
}
We implement our control like this in MainWindow.xaml:
<ContentControl x:Name="MyContentControl">
<ContentControl.ContentTemplate>
<DataTemplate>
<local:MyControl x:Name="MyCntrl" Text="DefaultText" att:MyAttachedProperty.Description="Test"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
and in the code-behind have this constructor:
public MainWindow()
{
MyAttachedProperty.DoNothing();
InitializeComponent();
}
If you start the project this way, the Debug-text will not contain any text. If you call DoNothing() after InitializeComponent(), it will show the text. Can anyone please explain, why? Note, in Silverlight both ways work. Also, if you do not use the control in a datatemplate both ways work.
It's interesting side effect. It makes sense when you think that DependencyProperty registration adds it to some global collection. If you call static constructor on MyAttachedProperty first it is added to the collection first and set first for an object.
If you force static constructor to run first on MyControl by adding same empty static method DoNothing then you can do
public MainWindow()
{
MyControl.DoNothing();
MyAttachedProperty.DoNothing();
InitializeComponent();
}
and the text will be shown
or in case
public MainWindow()
{
MyAttachedProperty.DoNothing();
MyControl.DoNothing();
InitializeComponent();
}
the empty text will be shown.

Binding Failure in WPF using MVVM

I have created a custom TextEditor control that inherits from AvalonEdit. I have done this to facilitate the use of MVVM and Caliburn Micro using this editor control. The [cut down for display purposes] MvvTextEditor class is
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public MvvmTextEditor()
{
TextArea.SelectionChanged += TextArea_SelectionChanged;
}
void TextArea_SelectionChanged(object sender, EventArgs e)
{
this.SelectionStart = SelectionStart;
this.SelectionLength = SelectionLength;
}
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.SelectionLength = (int)args.NewValue;
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
set { SetValue(SelectionLengthProperty, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
Now, in the view that holds this control, I have the following XAML:
<Controls:MvvmTextEditor
Caliburn:Message.Attach="[Event TextChanged] = [Action DocumentChanged()]"
TextLocation="{Binding TextLocation, Mode=TwoWay}"
SyntaxHighlighting="{Binding HighlightingDefinition}"
SelectionLength="{Binding SelectionLength,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
Document="{Binding Document, Mode=TwoWay}"/>
My issue is SelectionLength (and SelectionStart but let us just consider the length for now as the problem is the same). If I selected something with the mouse, the binding from the View to my View Model works great. Now, I have written a find and replace utility and I want to set the SelectionLength (which has get and set available in the TextEditor control) from the code behind. In my View Model I am simply setting SelectionLength = 50, I implement this in the View Model like
private int selectionLength;
public int SelectionLength
{
get { return selectionLength; }
set
{
if (selectionLength == value)
return;
selectionLength = value;
Console.WriteLine(String.Format("Selection Length = {0}", selectionLength));
NotifyOfPropertyChange(() => SelectionLength);
}
}
when I set SelectionLength = 50, the DependencyProperty SelectionLengthProperty does not get updated in the MvvmTextEditor class, it is like the TwoWay binding to my control is failing but using Snoop there is no sign of this. I thought this would just work via the binding, but this does not seem to be the case.
Is there something simple I am missing, or will I have to set up and event handler in the MvvmTextEditor class which listens for changes in my View Model and updated the DP itself [which presents it's own problems]?
Thanks for your time.
This is because the Getter and Setter from a DependencyProperty is only a .NET Wrapper. The Framework will use the GetValue and SetValue itself.
What you can try is to access the PropertyChangedCallback from your DependencyProperty and there set the correct Value.
public int SelectionLength
{
get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectionLength. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor), new PropertyMetadata(0,SelectionLengthPropertyChanged));
private static void SelectionLengthPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textEditor = obj as MvvmTextEditor;
textEditor.SelectionLength = e.NewValue;
}
Here is another answer if you are still open. Since SelectionLength is already defined as a dependency property on the base class, rather than create a derived class (or add an already existing property to the derived class), I would use an attached property to achieve the same functionality.
The key is to use System.ComponentModel.DependencyPropertyDescriptor to subscribe to the change event of the already existing SelectionLength dependency property and then take your desired action in the event handler.
Sample code below:
public class SomeBehavior
{
public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(SomeBehavior), new PropertyMetadata(OnIsEnabledChanged));
public static void SetIsEnabled(DependencyObject dpo, bool value)
{
dpo.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject dpo)
{
return (bool)dpo.GetValue(IsEnabledProperty);
}
private static void OnIsEnabledChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
{
var editor = dpo as TextEditor;
if (editor == null)
return;
var dpDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(TextEditor.SelectionLengthProperty,editor.GetType());
dpDescriptor.AddValueChanged(editor, OnSelectionLengthChanged);
}
private static void OnSelectionLengthChanged(object sender, EventArgs e)
{
var editor = (TextEditor)sender;
editor.Select(editor.SelectionStart, editor.SelectionLength);
}
}
Xaml below:
<Controls:TextEditor Behaviors:SomeBehavior.IsEnabled="True">
</Controls:TextEditor>
This is how I did this...
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
if (target.SelectionLength != (int)args.NewValue)
{
target.SelectionLength = (int)args.NewValue;
target.Select(target.SelectionStart, (int)args.NewValue);
}
}));
public new int SelectionLength
{
get { return base.SelectionLength; }
//get { return (int)GetValue(SelectionLengthProperty); }
set { SetValue(SelectionLengthProperty, value); }
}
Sorry for any time wasted. I hope this helps someone else...

Categories