Using Dependency Properties With Multiple Instances - c#

I basically have the same problem as this guy here: Error: " 'Subjects' property was already registered by 'Period' " is raised when more than one control is placed on the form
The main difference between us is that I want to subscribe to an event that has access to the local instance when the xaml changes the property.
So I have my UserControl:
public partial class BoardSquare : UserControl
{
public BoardSquare()
{
InitializeComponent();
Location = new BoardLocation(Int32.MinValue, Int32.MinValue);
XPositionProperty =
DependencyProperty.Register("XPosition", typeof(int),
typeof(BoardSquare), new PropertyMetadata(
new PropertyChangedCallback((value, args) =>
{
Location.X = (int)args.NewValue;
resetBackgroundColorToPosition();
})));
YPositionProperty=
DependencyProperty.Register("YPosition", typeof(int),
typeof(BoardSquare), new PropertyMetadata(
new PropertyChangedCallback((value, args)=>
{
Location.Y = (int)args.NewValue;
resetBackgroundColorToPosition();
})));
}
private void resetBackgroundColorToPosition()
{
this.Background = (Brush)(new ColorEnumToBrushesConverter()).Convert(Location.GetSquareColor(), typeof(BlackWhiteColor), null, null);
}
public readonly DependencyProperty XPositionProperty;
public readonly DependencyProperty YPositionProperty;
public int XPosition
{
get
{
return (int)GetValue(XPositionProperty);
}
set
{
SetValue(XPositionProperty, value);
}
}
public int YPosition
{
get
{
return (int)GetValue(YPositionProperty);
}
set
{
SetValue(YPositionProperty, value);
}
}
public BoardLocation Location { get; set; }
}
Here is my XAML:
<local:BoardSquare Grid.Column="3" Grid.Row="0" XPosition="3" YPosition="0"/>
<local:BoardSquare Grid.Column="4" Grid.Row="0" XPosition="4" YPosition="0"/>
From what I understand, the solution is to make XPositionProperty static and then register it in a static constructor. My problem then is I can't access the local instance of the class when my PropertyChangeCallback event happens.
How can I set the property in the XAML and still get an on property changed event in the C# code?
Is there a better solution than dependency properties?
Below is the working code of BoardSquare after I implemented the answer.
public partial class BoardSquare : UserControl
{
static BoardSquare()
{
XPositionProperty =
DependencyProperty.Register("XPosition", typeof(int),
typeof(BoardSquare), new PropertyMetadata(
new PropertyChangedCallback((objectInstance, args) =>
{
BoardSquare boardSquare = (BoardSquare)objectInstance;
boardSquare.Location.X = (int)args.NewValue;
boardSquare.resetBackgroundColorToPosition();
})));
YPositionProperty =
DependencyProperty.Register("YPosition", typeof(int),
typeof(BoardSquare), new PropertyMetadata(
new PropertyChangedCallback((objectInstance, args) =>
{
BoardSquare boardSquare = (BoardSquare)objectInstance;
boardSquare.Location.Y = (int)args.NewValue;
boardSquare.resetBackgroundColorToPosition();
})));
}
public BoardSquare()
{
InitializeComponent();
Location = new BoardLocation(Int32.MinValue, Int32.MinValue);
}
private void resetBackgroundColorToPosition()
{
this.Background = (Brush)(new ColorEnumToBrushesConverter()).Convert(Location.GetSquareColor(), typeof(BlackWhiteColor), null, null);
}
public static readonly DependencyProperty XPositionProperty;
public static readonly DependencyProperty YPositionProperty;
public int XPosition
{
get
{
return (int)GetValue(XPositionProperty);
}
set
{
SetValue(XPositionProperty, value);
}
}
public int YPosition
{
get
{
return (int)GetValue(YPositionProperty);
}
set
{
SetValue(YPositionProperty, value);
}
}
public BoardLocation Location { get; set; }
}

The first argument of PropertyChangedCallback is the local instance (btw. you better name it obj than value to avoid confusion). You have to cast this DependencyObject to BoardSquare and that's all.
public static readonly DependencyProperty XPositionProperty =
DependencyProperty.Register("XPosition", typeof(int), typeof(BoardSquare),
new PropertyMetadata(new PropertyChangedCallback((obj, args) => {
BoardSquare bs = obj as BoardSquare;
bs.Location.X = (int)args.NewValue;
bs.resetBackgroundColorToPosition();
})));

If I understand your question correctly, you are trying to override already WPF certificate property with your own. I don't think that's necessary here. If XPosition is already WPF certificate property, all you have to do is create a property in your usercontrol with getter/setter with setter calling INotifypropertychanged. you don't need dependency property in this case unless you want only same name but behave differently, and why do you want that?.

Related

HelixToolkit.WPF Additional DependencyProperty not working

I am creating an application based on one of the example applications in the HelixToolkit (SurfacePlot) found in the ExampleBrwoser.
The SurfacePlotVisual3D class has three dependency properties, I tried to add another one simply by copying/renaming an existing one, but it does not work.
XAML
<h:HelixViewport3D ZoomExtentsWhenLoaded="True" ShowCoordinateSystem="True">
<local:SurfacePlotVisual3D CurrentX="{Binding CurrentX}" Points="{Binding Data}" ColorValues="{Binding ColorValues}" SurfaceBrush="{Binding SurfaceBrush}" />
</h:HelixViewport3D>
The "CurrentX" property is the one I tried to add.
SurfacePlotVisual3D.cs
public class SurfacePlotVisual3D : ModelVisual3D
{
public static readonly DependencyProperty CurrentXProperty =
DependencyProperty.Register("CurrentX", typeof(double), typeof(SurfacePlotVisual3D),
new UIPropertyMetadata(ModelChanged));
public static readonly DependencyProperty PointsProperty =
DependencyProperty.Register("Points", typeof(Point3D[,]), typeof(SurfacePlotVisual3D),
new UIPropertyMetadata(null, ModelChanged));
public static readonly DependencyProperty ColorValuesProperty =
DependencyProperty.Register("ColorValues", typeof(double[,]), typeof(SurfacePlotVisual3D),
new UIPropertyMetadata(null, ModelChanged));
public static readonly DependencyProperty SurfaceBrushProperty =
DependencyProperty.Register("SurfaceBrush", typeof(Brush), typeof(SurfacePlotVisual3D),
new UIPropertyMetadata(null, ModelChanged));
...
...
public Brush SurfaceBrush
{
get { return (Brush)GetValue(SurfaceBrushProperty); }
set { SetValue(SurfaceBrushProperty, value); }
}
public double CurrentX
{
get { return (double)GetValue(CurrentXProperty); }
set { SetValue(CurrentXProperty, value); }
}
private static void ModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((SurfacePlotVisual3D)d).UpdateModel();
}
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.DataContext = new MainViewModel();
}
}
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
...
...
public Func<double, double, double> Function { get; set; }
public Point3D[,] Data { get; set; }
public double[,] ColorValues { get; set; }
public double CurrentX { get; set; }
public MainViewModel()
{
CurrentX = 6;
MinX = 0;
MaxX = 3;
MinY = -2;
MaxY = 2;
Rows = 100;
Columns = 50;
Function = (x, y) => Math.Pow(Math.E, (-1) * Math.Pow(y, 2)) * 3*Math.Sin(2*x) + 2;
ColorCoding = ColorCoding.ByGradientX;
UpdateModel();
}
...
...
}
I can change "Data", "ColorValues" and "Function" in the MainViewModel and the effect is immediately visible in the plot, but the CurrentX property wont do anything.
As the comment of Anton suggested the missing default value was the problem.
public static readonly DependencyProperty CurrentXProperty =
DependencyProperty.Register("CurrentX", typeof(double), typeof(SurfacePlotVisual3D),
new UIPropertyMetadata((double)0, ModelChanged));
This fixed the problem. The (double)0 cast is needed because without it you will get an exception because "object" is not "double" (in my case). So thanks a lot Anton, made my day!

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

Module not rendering

In my application i'm trying to load the main module trough code. Everything works up to the point of rendering and i have NO clue why it isn't rendering. The content has the right values and everything.. My guess is that something went wibbly wobbly and I seem to be missing it.
Control that holds the view
[ContentPropertyAttribute("ContentView")]
public class ContentControlExtened : ContentControl
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(
"Type",
typeof(Type),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentTypeChanged));
public static readonly DependencyProperty ContentViewProperty =
DependencyProperty.Register(
"View",
typeof(FrameworkElement),
typeof(ContentControl),
new FrameworkPropertyMetadata(null, ContentViewChanged));
private static void ContentViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//throw new NotImplementedException();
}
public ContentControlExtened()
{
this.Loaded += ContentControlLoaded;
}
private void ContentControlLoaded(object sender, RoutedEventArgs e)
{
this.LoadContent();
}
private void LoadContent()
{
UserControl u = null;
if (Type != null)
{
u = (UserControl)ServiceLocator.Current.GetInstance(this.Type);
}
u.Background = new SolidColorBrush(Color.FromRgb(0, 0, 255));
this.View = u;
}
public Type Type
{
get { return (Type)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
}
Method in shell to load the main view of the given moduleInfo
private void OpenMainView(ModuleInfo module)
{
Type moduleType = Type.GetType(module.ModuleType);
var moduleMainViewType = moduleType.Assembly.GetType(moduleType.Namespace + ".Views.MainView");
this.ContentHolder.MainContent.Type = moduleMainViewType;
}
The contructor of the mainview is straight forward. Just standard control InitializeComponent() and Datacontext is being set trough the servicelocator.
public FrameworkElement View
{
get { return (FrameworkElement)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
Here you are getting and setting the ContentProperty. It should be ContentViewProperty.

Shared dependency property update issue in binding

I have a read-only dependency property which is shared between two WPF classes. So I made this property shared property. While binding this property on XAML, It takes its default value and can't be updated. But If I use the main owner of this property, its value can be updated.
So what is your advice?
public class A
{
private static readonly DependencyPropertyKey XPropertyKey =
DependencyProperty.RegisterReadOnly("X",
typeof(Point), typeof(A),
new FrameworkPropertyMetadata(new Point(0, 0),
FrameworkPropertyMetadataOptions.Inherits |
FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty XProperty =
RelativeMousePointPropertyKey.DependencyProperty;
public Point X
{
get { return (Point)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public void SetRelativeMousePoint(Point point)
{
SetValue(XPropertyKey, point);
}
}
public class B
{
//I use this class for binding
public static readonly DependencyProperty XProperty =
A.XProperty.AddOwner(typeof(B));
public Point X
{
get { return (Point)GetValue(X); }
}
}

How can I create a read-only Boolean dependency property that returns `And` operation between two other dependency properties

How can I create a custom read-only Boolean dependency property that returns And operation between two custom Boolean dependency properties, for example (A, B),
And when A or B changes, I want the result property to trigger.
Any help to achieve that!
Part 1: dependencies.
public static readonly DependencyProperty Source1Property =
DependencyProperty.Register(
"Source1",
typeof(bool),
typeof(MyControl),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(UpdateTarget)));
public bool Source1
{
get { return (bool)GetValue(Source1Property); }
set { SetValue(Source1Property, value); }
}
void UpdateTarget(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyControl self = (MyControl)d;
d.Target = d.Source1 && d.Source2;
}
Part 2: read-only
internal static readonly DependencyPropertyKey TargetPropertyKey =
DependencyProperty.RegisterReadOnly(
"Target",
typeof(bool),
typeof(MyControl),
new PropertyMetadata(false));
public static readonly DependencyProperty TargetProperty =
TargetPropertyKey.DependencyProperty;
public bool Target
{
get { return (bool)GetValue(TargetProperty); }
protected set { SetValue(TargetPropertyKey, value); }
}
Disclaimer: I didn't try the part 2.
Part 3:
if the source dependency properties are not defined by you, you can do the following trick:
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(
MyControl.Source1Property,
typeof(MyControl)));
if (dpd != null)
dpd.AddValueChanged(this, UpdateTarget);
You can do this by defining your two dependency properties A and B (for the sake of the example, I guess), and define a callback to be executed whenever these changes, using PropertyMetaData in the DependencyProperty constructor. In this callback, simply perform the calculation you want and set the Result depdendency property to that value. Here is a working example:
public class Data : DependencyObject
{
public static readonly DependencyProperty AProperty = DependencyProperty.Register("A", typeof(Boolean), typeof(Data), new PropertyMetadata(false,HandleValueChanged));
public static readonly DependencyProperty BProperty = DependencyProperty.Register("B", typeof(Boolean), typeof(Data), new PropertyMetadata(false, HandleValueChanged));
public static readonly DependencyProperty ResultProperty = DependencyProperty.Register("Result",typeof (Boolean), typeof (Data));
private static void HandleValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetValue(ResultProperty, ((Data)d).Result);
Debug.WriteLine("Value change");
}
public bool Result
{
get { return A & B; }
}
public bool A
{
get { return (bool) GetValue(AProperty); }
set
{
if ( A != value )
SetValue(AProperty, value);
}
}
public bool B
{
get
{
return (bool) GetValue(BProperty);
}
set
{
if (B != value)
SetValue(BProperty, value);
}
}
}

Categories