In the following code snippet:
<ItemsControl helpers:EnumHelper.Enum="order:TShirtSizeEnum" ... >
...
</ItemsControl>
what helpers:EnumHelper.Enum attribute means?
Next is implementation of EnumHelper class
public class EnumHelper : DependencyObject
{
public static Type GetEnum(DependencyObject obj)
{
return (Type)obj.GetValue(EnumProperty);
}
public static void SetEnum(DependencyObject obj, string value)
{
obj.SetValue(EnumProperty, value);
}
// Using a DependencyProperty as the backing store for Enum. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnumProperty =
DependencyProperty.RegisterAttached("Enum", typeof(Type), typeof(EnumHelper), new PropertyMetadata(null, OnEnumChanged));
private static void OnEnumChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as ItemsControl;
if (control != null)
{
if (e.NewValue != null && e.NewValue as Type != null)
{
var _enum = Enum.GetValues(e.NewValue as Type);
control.ItemsSource = _enum;
}
}
}
}
Presumably an attached property. What it does depends on what helpers refers to (as it's probably not part of .NET i cannot tell you anything about it, but i would guess it uses reflection to get the enum values and sets them as ItemsSource).
Looks like it means the following: find class EnumHelper in namespace referenced above as helpers. This class defines an attached property Enum. Set the value of attached property for this object to ...
Related
In background property for instance we can do like this
<cs:CustomControl.Background>
<SolidColorBrush Color="{Binding BackGround}"/>
</cs:CustomControl.Background>
SolidColorBrush is a separate class and contains separate dependency properties, and it working fine and the background property is updating as expected.
I'm trying to do the same concept on my CustomControl.
I have create a subclass with dependency property like this
public class SubClass : DependencyObject
{
public double SubProperty1
{
get { return (double)GetValue(SubProperty1Property); }
set { SetValue(SubProperty1Property, value); }
}
// Using a DependencyProperty as the backing store for SubProperty1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SubProperty1Property =
DependencyProperty.Register("SubProperty1", typeof(double), typeof(SubClass), new PropertyMetadata(10.0d, OnSubProperty1Changed));
private static void OnSubProperty1Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//never called;
}
public string SubProperty2
{
get { return (string)GetValue(SubProperty2Property); }
set { SetValue(SubProperty2Property, value); }
}
// Using a DependencyProperty as the backing store for SubProperty1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SubProperty2Property =
DependencyProperty.Register("SubProperty2", typeof(string), typeof(SubClass), new PropertyMetadata("test", OnSubProperty2Changed));
private static void OnSubProperty2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//never called;
}
}
And in my main class for my custom tool, I created dependency property as follows:
public SubClass MainProperty
{
get { return (SubClass)GetValue(MainPropertyProperty); }
set { SetValue(MainPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for MainProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MainPropertyProperty =
DependencyProperty.Register("MainProperty", typeof(SubClass), typeof(CustomControl), new PropertyMetadata((SubClass)null, OnMainPropertyChanged));
private static void OnMainPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// called when object created only and not called again when the property changed on the UI
}
Then in my WPF window I did as following
<cs:CustomControl.MainProperty>
<cs:SubClass SubProperty1="{Binding Length}" SubProperty2="{Binding Title}"/>
</cs:CustomControl.MainProperty>
my code compiles but dependecy properties not updating and callback function not called when the viewmodel update
how can I make this work? and what do I miss
I hope there is any idea with detailed code
thanks in advance
In the Output Window in Visual Studio you should have seen something like
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. ...
when you debug your application. This is because your control and its DependencyObject property do not form a logical tree.
In order to build a logical tree, SubClass must derive from Freezable
public class SubClass : Freezable
{
...
protected override Freezable CreateInstanceCore()
{
return new SubClass();
}
}
and CustomControl must add SubClass instances to its logical children collection:
private static void OnMainPropertyChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
((CustomControl)o).RemoveLogicalChild(e.OldValue);
}
if (e.NewValue != null)
{
((CustomControl)o).AddLogicalChild(e.NewValue);
}
}
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);
}
}
I am implementing an attached behavior in a WPF application. I need to pass a type parameters to the behavior, so I can call a method void NewRow(Table<T> table) on SqliteBoundRow. If I was instantiating an object in XAML, I would pass a type parameters using x:TypeArguments, but I don't see a way to do this when setting an attached behavior, because it uses a static property.
Code for the attached behavior looks like this:
public abstract class SqliteBoundRow<T> where T : SqliteBoundRow<T>
{
public abstract void NewRow(Table<T> table);
}
public class DataGridBehavior<T> where T:SqliteBoundRow<T>
{
public static readonly DependencyProperty IsEnabledProperty;
static DataGridBehavior()
{
IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(DataGridBehavior<T>),
new FrameworkPropertyMetadata(false, OnBehaviorEnabled));
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
private static void OnBehaviorEnabled(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var dg = dependencyObject as DataGrid;
dg.InitializingNewItem += DataGrid_InitializingNewItem;
}
private static void DataGrid_InitializingNewItem(object sender,
InitializingNewItemEventArgs e)
{
var table = (sender as DataGrid).ItemsSource as Table<T>;
(e.NewItem as T).NewRow(table);
}
}
XAML looks like this:
<DataGrid DataGridBehavior.IsEnabled="True">
<!-- DataGridBehavior needs a type parameter -->
</DataGrid>
My current solution is to wrap DataGridBehavior in a a derived class which specifies the type parameters.
The simplest solution is for you to declare another Attached Property, but of type Type to hold the parameter value for you. In this case, you would set the Type property before your IsEnabled Attached Property:
<DataGrid DataGridBehavior.TypeParameter="{x:Type SomePrefix:SomeType}"
DataGridBehavior.IsEnabled="True" ... />
Looking again at your code, it seems as though your IsEnabled property does nothing except adding a new row to your table... in that case, there's no reason why you couldn't replace it with the TypeParameter Attached Property and use that one to add the new row instead.
I don't think WPF provides an elegant syntactic way to do what you want. So I was just about to post a similar answer to the one from Sheridan. That is you can provide an additional property of type Type to determine the generic type. However, Sheridan beat me to it. Below is some sample code of how you can do this with reflection:
Xaml
<DataGrid behaviors:DataGridBehavior.InnerType="namespace:SqliteBoundRow"
behaviors:DataGridBehavior.IsEnabled="True">
</DataGrid>
Code behind
public abstract class DataGridBehavior
{
private static readonly ConcurrentDictionary<Type, DataGridBehavior> Behaviors = new ConcurrentDictionary<Type, DataGridBehavior>();
public static readonly DependencyProperty IsEnabledProperty;
public static readonly DependencyProperty InnerTypeProperty;
static DataGridBehavior()
{
IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool), typeof(DataGridBehavior),
new FrameworkPropertyMetadata(false, OnBehaviorEnabled));
InnerTypeProperty = DependencyProperty.RegisterAttached("InnerType",
typeof(Type), typeof(DataGridBehavior));
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetInnerType(DependencyObject obj, Type value)
{
obj.SetValue(InnerTypeProperty, value);
}
public static Type GetInnerType(DependencyObject obj)
{
return (Type)obj.GetValue(InnerTypeProperty);
}
private static void OnBehaviorEnabled(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var innerType = GetInnerType(dependencyObject);
if (innerType == null)
throw new Exception("Missing inner type");
var behavior = Behaviors.GetOrAdd(innerType, GetBehavior);
behavior.OnEnabled(dependencyObject);
}
private static DataGridBehavior GetBehavior(Type innerType)
{
var behaviorType = typeof(DataGridBehavior<>).MakeGenericType(innerType);
var behavior = (DataGridBehavior)Activator.CreateInstance(behaviorType);
return behavior;
}
protected abstract void OnEnabled(DependencyObject dependencyObject);
}
public class DataGridBehavior<T> : DataGridBehavior
where T : SqliteBoundRow
{
protected override void OnEnabled(DependencyObject dependencyObject)
{
//dg.InitializingNewItem += DataGrid_InitializingNewItem;
}
private static void DataGrid_InitializingNewItem(object sender,
InitializingNewItemEventArgs e)
{
//var table = (sender as DataGrid).ItemsSource as Table<T>;
//(e.NewItem as T).NewRow(table);
}
}
public class SqliteBoundRow
{
}
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...
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)
{
...
}
}