Update dependency property when child property has been updated - c#

I have a class inheriting canvas with the following dependency property
public class StorageCanvas : Canvas
{
public readonly static DependencyProperty StorageProperty = DependencyProperty.Register(
"Storage",
typeof(Polygon),
typeof(StorageCanvas));
public Polygon Storage
{
get { return (Polygon) GetValue(StorageProperty); }
set { SetValue(StorageProperty, value); }
}
}
Can I somehow make the dependency property "update" when the Storage polygon Points has been altered/updated, instead of requiring the polygon to be replaced with a new instance?

Well Polygon.Points is a PointCollection, so you could just subscribe to the Changed event of it and then call InvalidateVisual() as suggested by #dowhilefor
public class StorageCanvas : Canvas {
public static readonly DependencyProperty StorageProperty = DependencyProperty.Register(
"Storage",
typeof(Polygon),
typeof(StorageCanvas),
new FrameworkPropertyMetadata(null, PropertyChangedCallback));
public Polygon Storage {
get {
return (Polygon)GetValue(StorageProperty);
}
set {
SetValue(StorageProperty, value);
}
}
private static void PropertyChangedCallback(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) {
var currentStorageCanvas = dependencyObject as StorageCanvas;
if (currentStorageCanvas == null)
return;
var oldPolygon = args.OldValue as Polygon;
if (oldPolygon != null)
oldPolygon.Points.Changed -= currentStorageCanvas.PointsOnChanged;
var newPolygon = args.NewValue as Polygon;
if (newPolygon == null)
return;
newPolygon.Points.Changed += currentStorageCanvas.PointsOnChanged;
// Just adding the following to test if updates are fine.
currentStorageCanvas.Children.Clear();
currentStorageCanvas.Children.Add(newPolygon);
}
private void PointsOnChanged(object sender, EventArgs eventArgs) {
InvalidateVisual();
}
}
So now if any individual Point in Storage changed, without actually recreating the entire object, InvalidateVisual() will be fired.
The concept is just about subscribing to the Changed event of PointsCollection. Whether it's the right thing to do for you is a question you need to address yourself based on your requirements and logic.

Register the dependency property with Affects render meta data option.
http://msdn.microsoft.com/en-us/library/system.windows.frameworkpropertymetadata.affectsrender.aspx

Related

Convert to DependencyProperty

I have a question, someone could help transform this code (used in codebehind ) for use by dependyProperty ?
This code gives the focus to the first item of listview . THX!!!!!!
private void ItemContainerGeneratorOnStatusChanged(object sender, EventArgs eventArgs)
{
if (lvResultado.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
var index = lvResultado.SelectedIndex;
if (index >= 0)
{
var item = lvResultado.ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
if (item != null)
{
item.Focus();
}
}
}
}
Specifically, I want to write something like: local:FocusFirstElement.Focus="True" in my XAML instead of writing this code for every list view.
What you are is really an attached behavior, which is implemented via an attached property which is really a special dependency property (which you seem to have hit upon already).
First, create an attached property. This is most easliy accomplished using the propa snippet:
public static bool GetFocusFirst(ListView obj)
{
return (bool)obj.GetValue(FocusFirstProperty);
}
public static void SetFocusFirst(ListView obj, bool value)
{
obj.SetValue(FocusFirstProperty, value);
}
public static readonly DependencyProperty FocusFirstProperty =
DependencyProperty.RegisterAttached("FocusFirst", typeof(bool),
typeof(ListViewExtension), new PropertyMetadata(false));
I'm assuming this is in a static class called ListViewExtenstion. Then, handle the property changed event:
public static readonly DependencyProperty FocusFirstProperty =
DependencyProperty.RegisterAttached("FocusFirst", typeof(bool),
typeof(ListViewExtension), new PropertyMetadata(false, HandleFocusFirstChanged));
static void HandleFocusFirstChanged(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
}
In that handler you would check the current value (in e) and register or deregister for the appropriate event on the ListView contained in depObj. Then you would use your existing code to set the focus. Something like:
static void HandleFocusFirstChanged(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ListView lv = (ListView)debObj;
if ((bool)e.NewValue)
lv.StatusChanged += MyLogicMethod;
}

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...

Binding to custom Dependency Property fails

As the SelectedItems property of the ListBox control is a normal property and not a dependency property to bind to, I have derived of the ListBox and created a new dependency property SelectedItemsEx.
But my XAML compiler keeps giving me the error
A 'Binding' cannot be set on the 'SelectedItemsEx' property of the
type 'MyListBox'. A 'Binding' can only be set on a DependencyProperty
of a DependencyObject.
Why my property is not recognized as a dependency property? Any help is appreciated, thank you!
XAML:
<MyListBox ItemsSource="{Binding MyData}" SelectedItemsEx="{Binding SelectedEx}"
SelectionMode="Extended"> ... </MyListBox>
ListBox' implementation:
public class MyListBox : ListBox
{
public readonly DependencyProperty SelectedItemsExProperty =
DependencyProperty.Register("SelectedItemsEx",
typeof(ObservableCollection<MyItemsDataType>),
typeof(MyListBox),
new PropertyMetadata(default(ObservableCollection<MyItemsDataType>)));
public ObservableCollection<MyItemsDataType> SelectedItemsEx
{
get
{
var v = GetValue(SelectedItemsExProperty);
return (ObservableCollection<MyItemsDataType>)v;
}
set { SetValue(SelectedItemsExProperty, value); }
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
if (SelectedItemsEx != null)
{
SelectedItemsEx.Clear();
foreach (var item in base.SelectedItems)
{
SelectedItemsEx.Add((MyItemsDataType)item);
}
}
}
The DependencyProperty field must be static:
public static readonly DependencyProperty SelectedItemsExProperty = ...
Note also that in order to make your derived ListBox a little more reusable, you should not constrain the type of the SelectedItemsEx property. Use IEnumerable (or IList like SelectedItems) instead. Moreover, there is no need to specify a default value by property metadata, as it is null already and default(<any reference type>) is also null.
You will however have to get notified whenever the SelectedItemsEx property has changed. Therefore you have to register a change callback via property metadata:
public static readonly DependencyProperty SelectedItemsExProperty =
DependencyProperty.Register(
"SelectedItemsEx", typeof(IEnumerable), typeof(MyListBox),
new PropertyMetadata(SelectedItemsExPropertyChanged));
public IEnumerable SelectedItemsEx
{
get { return (IEnumerable)GetValue(SelectedItemsExProperty); }
set { SetValue(SelectedItemsExProperty, value); }
}
private static void SelectedItemsExPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var listBox = (MyListBox)obj;
var oldColl = e.OldValue as INotifyCollectionChanged;
var newColl = e.NewValue as INotifyCollectionChanged;
if (oldColl != null)
{
oldColl.CollectionChanged -= listBox.SelectedItemsExCollectionChanged;
}
if (newColl != null)
{
newColl.CollectionChanged += listBox.SelectedItemsExCollectionChanged;
}
}
private void SelectedItemsExCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
...
}
}

Storyboard doesn't animate custom FrameworkElement property

I have class that is derived from FrameworkElement, and I want WPF to update its Location property by using DoubleAnimation. I register the property as DependendencyProperty:
public class TimeCursor : FrameworkElement
{
public static readonly DependencyProperty LocationProperty;
public double Location
{
get { return (double)GetValue(LocationProperty); }
set
{
SetValue(LocationProperty, value);
}
}
static TimeCursor()
{
LocationProperty = DependencyProperty.Register("Location", typeof(double), typeof(TimeCursor));
}
}
Following code sets up the storyboard.
TimeCursor timeCursor;
private void SetCursorAnimation()
{
timeCursor = new TimeCursor();
NameScope.SetNameScope(this, new NameScope());
RegisterName("TimeCursor", timeCursor);
storyboard.Children.Clear();
DoubleAnimation animation = new DoubleAnimation(LeftOffset, LeftOffset + (VerticalLineCount - 1) * HorizontalGap + VerticalLineThickness,
new Duration(TimeSpan.FromMilliseconds(musicDuration)), FillBehavior.HoldEnd);
Storyboard.SetTargetName(animation, "TimeCursor");
Storyboard.SetTargetProperty(animation, new PropertyPath(TimeCursor.LocationProperty));
storyboard.Children.Add(animation);
}
Then I call storyboard.Begin(this) from another method of the object which contains the above SetCursorAnimation() method and this object is derived from Canvas. However the Location property is never updated(set accessor of Location is never called) and no exception is thrown. What am I doing wrong?
When a dependency property is animated (or set in XAML, or set by a Style Setter, etc.), WPF does not call the CLR wrapper, but instead directly accesses the underlying DependencyObject and DependencyProperty objects. See the Implementing the "Wrapper" section in Checklist for Defining a Dependency Property and also Implications for Custom Dependency Properties.
In order to get notified about property changes, you have to register a PropertyChangedCallback:
public class TimeCursor : FrameworkElement
{
public static readonly DependencyProperty LocationProperty =
DependencyProperty.Register(
"Location", typeof(double), typeof(TimeCursor),
new FrameworkPropertyMetadata(LocationPropertyChanged)); // register callback here
public double Location
{
get { return (double)GetValue(LocationProperty); }
set { SetValue(LocationProperty, value); }
}
private static void LocationPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var timeCursor = obj as TimeCursor;
// handle Location property changes here
...
}
}
And note also that animating a dependency property does not necessarily require a Storyboard. You could simply call the BeginAnimation method on your TimeCursor instance:
var animation = new DoubleAnimation(LeftOffset,
LeftOffset + (VerticalLineCount - 1) * HorizontalGap + VerticalLineThickness,
new Duration(TimeSpan.FromMilliseconds(musicDuration)),
FillBehavior.HoldEnd);
timeCursor.BeginAnimation(TimeCursor.LocationProperty, animation);

setting Dependency property from two different sources: want different postprocessing

I have the following WPF code. You can see in the comments there that I have a problem with my OnValueChanged handler. I need the code there to differentiate between a Value set from the UI (through various bindings) and one set from the manager class. I had hoped that DependencyPropertyChangedEventArgs would have some kind of source that I could use to differentiate this, but I don't see anything like that. Ideas? Is there some way to set a WPF DependencyProperty without triggering its PropertyChanged handler? Thanks for your time.
public class GaugeBaseControl : UserControl
{
protected readonly AssetModelManager Manager;
public GaugeBaseControl(AssetModelManager mgr)
{
Manager = mgr;
if(mgr != null)
mgr.TelemetryValueChanged += MgrOnTelemetryValueChanged; // coming on background thread
}
private void MgrOnTelemetryValueChanged(KeyValuePair<string, object> keyValuePair)
{
if(_localTelemetryId != keyValuePair.Key)
return;
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
if (!Equals(Value, keyValuePair.Value))
Value = keyValuePair.Value;
}));
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var gbc = (GaugeBaseControl) d;
var id = gbc.TelemetryId;
if (!string.IsNullOrEmpty(id))
{
// this is the problem:
// I need to always set gbc.Manager[id] if this event was triggered from the UI (even when equal)
// however, if it was triggered by TelemetryValueChanged then we don't want to go around in circles
if (!Equals(gbc.Manager[id], e.NewValue))
gbc.Manager[id] = e.NewValue;
}
}
private string _localTelemetryId; // to save us a cross-thread check
private static void OnTelemetryIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var gbc = (GaugeBaseControl)d;
var tid = gbc.TelemetryId;
gbc._localTelemetryId = tid;
gbc.Value = string.IsNullOrEmpty(tid) ? null : gbc.Manager[tid];
}
public static readonly DependencyProperty TelmetryIdProperty = DependencyProperty.Register("TelemetryId", typeof(string), typeof(GaugeBaseControl), new PropertyMetadata(OnTelemetryIdChanged));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(GaugeBaseControl), new PropertyMetadata(OnValueChanged));
public object Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value);}
}
public string TelemetryId
{
get { return (string)GetValue(TelmetryIdProperty); }
set { SetValue(TelmetryIdProperty, value); }
}
}
It seems a bit hackish, but it is the best shot i could come up with without changing the architecture. You could stop listening on the TelemetryValueChanged event while doing your internal update to stop the roundtrip like so:
internal void SetManagerIdInternal(string id, object value)
{
if(mgr != null)
{
mgr.TelemetryValueChanged -= MgrOnTelemetryValueChanged;
mgr[id] = value;
mgr.TelemetryValueChanged += MgrOnTelemetryValueChanged;
}
}
And use it like this:
if (!Equals(gbc.Manager[id], e.NewValue))
SetManagerIdInternal(id, e.NewValue);
You could also use a private field to just skip doing work without unregistering/reregistering the event in MgrOnTelemetryValueChanged wich might be better performance wise, but i haven't tested it.

Categories