How do I modify the setter value of a dependency property? - c#

I have a simple dependency property. I have a user control with a button. I want the caller to set a property called MyName to "Hello". I want the dependency property code to append the string " World" to the end. Here's my code that attempts this:
#region MyNAME DEPENDENCY PROPERTY
private string _myName;
public string MyName
{
get { return this._myName; } //a breakpoint here never trips
set
{
this._myName = value;
this.OnPropertyChanged("MyName");
}
}
public string MyName2 { get { return this._myName; } } //a breakpoint here will trip
public static readonly DependencyProperty MyNameProperty = DependencyProperty.Register("MyName", typeof(string), typeof(MyTabControl), new PropertyMetadata("", OnMyNamePropertyChanged));
private static void OnMyNamePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
MyTabControl myUserControl = dependencyObject as MyTabControl;
myUserControl.OnMyNamePropertyChanged(e);
myUserControl.OnPropertyChanged("MyName");
}
private void OnMyNamePropertyChanged(DependencyPropertyChangedEventArgs e)
{
this.MyName = (string)e.NewValue + " World";
}
#endregion //end MyNAME
And my button XAML code looks like:
<Button Content="{Binding Path=MyName, Mode=OneWay}" />
When I load a page that has this control like this:
<local:MyTabControl MyName="Hello" />
I get a button that says "Hello" rather than a button that says "Hello World". The strange part is if I change my button XAML code to use MyName2 like so:
<Button Content="{Binding Path=MyName2, Mode=OneWay}" />
it works. The getter code is the same (I included it above). I'm guessing that the code that gets _myName is getting called earlier than I expect. What I also don't expect is that the MyName getter never seems to get called. When I put a breakpoint there, it doesn't trip. When I change the button binding path to use MyName2 however, a breakpoint put on that getter will trip.
What am I doing wrong here? How can I use the MyName property and have my button show "Hello World" (without using a second getter of course)
EDIT: Adding new code.
I took both of your suggestions and changed my C# code to simply this:
public string MyName
{
get { return (string)GetValue(MyNameCustomProperty); }
set { SetValue(MyNameCustomProperty, value + " World"); }
}
public static DependencyProperty MyNameCustomProperty = DependencyProperty.Register("MyName", typeof(string), typeof(MyTabControl));
(I'm still new to depency properties) I am still having the same problem as I had with my previous code. Can I do something with this code?

It looks like I need to coerce the value. Here is how I do that:
public string MyName
{
get { return (string)GetValue(MyNameCustomProperty); }
set { SetValue(MyNameCustomProperty, value); }
}
public static DependencyProperty MyNameCustomProperty = DependencyProperty.Register("MyName", typeof(string), typeof(MyTabControl), new PropertyMetadata("", MyPropertyChanged, CoerceCurrentReading));
private static void MyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//contains coerced value
}
private static object CoerceCurrentReading(DependencyObject d, object value)
{
MyTabControl tab = (MyTabControl)d;
return (string)value + " World";
}
This looks like the cleanest way to change the value entered by the XAML code from "Hello" to "Hello World".

Part of the problem here is that you're not using a real dependency property. In order to use a dependency property you need to use the actual dependency property storage and not a field. This backing storage is where items like binding go to for their values. Change your code to the following and delete the backing field
public string MyName
{
get { return (string)this.GetValue(MyNameProperty); }
set { this.SetValue(MyNameProperty, value); }
}

You want to change what value is set before the notification occurs. The simplest solution is to set the value of the backing field to what you want, then notify:
public string MyName
{
get { return this._myName; } //a breakpoint here never trips
set
{
this._myName = (string)value + "World!";
this.OnPropertyChanged("MyName");
}
}
The problem you are having is that you set the backing field.
Then you notify, which tells the UI to update, as well as triggering OnNotifyPropertyChanged.
OnNotifyPropertyChanged is called after the property has changed, where you try to change the backing field.

Related

Determine if a property value is set by user (i.e. binding)

Preamble: I am making a logging tool for our company needs. We use MVVM - pattern in our projects via Caliburn. At this moment my tool is capable of weaving compiled code via Mono.Cecil assembly and injecting call of a logging method in public properties set methods.
Fabula: Let's say, I have a class Person
public class Person
{
public string Name
{
get { return _name; }
set
{
Logger.MyLogMethod(_name, value, "Person");
_name = value;
}
}
private string _name;
}
and a class Logger
public static class Logger
{
public static void MyLogMethod(string oldvalue, string newvalue, string propertyname)
{
//condition to log only user changes
}
}
And an instance of a person has its Name property binded to textbox
<TextBox Text="{Binding SelectedPerson.Name}"/>
And we have a button, which click calls a code, which will set new value to SelectedPerson.
So, we have two ways of setting our Person's Name property at a runtime. It's either by user typing in textbox, or a user clicking a button, therefore calling some code.
I would like to know: how to distinct these two cases. In other words - when a set method of a property is called - how to determine, if it was called by binding engine or by our code.
So far I have one approach that is doable in my opinion - to take a look at StackTrace. Not sure how to do it thought.
You can make the Name property private. Then you expose a public property for binding and another for the rest of the application.
You can know differentiate a call from binding from a call from somewhere else
public class Person
{
string NameForOthers
{
get { return Name; }
set
{
Logger.MyLogMethod(_name, value, "Person name set by other place");
Name= value;
}
}
public string NameForBinding
{
get { return Name; }
set
{
Logger.MyLogMethod(_name, value, "Person name set by binding");
Name= value;
}
}
string Name
{
get { return _name; }
set
{
Logger.MyLogMethod(_name, value, "Person");
_name = value;
}
}
private string _name;
}
Then you use it for your binding
<TextBox Text="{Binding SelectedPerson.NameForBinding}"/>

Two-way SelectedText binding on TextBox using inheritance

I'm trying to replace the SelectedText of a TextBox with a new value by binding on the custom property 'Selected'. Currently, updating Selected through binding doesn't change the actual SelectedText. I'm almost there I think; at least mouse-selecting text is updating Selected.
I'd prefer solutions based on inheriting from TextBox if possible.
Can anybody tell me what's missing please?
class SelectionTextbox : TextBox
{
public static readonly DependencyProperty SelectionProperty = DependencyProperty.Register("Selection", typeof(string), typeof(SelectionTextbox));
public string Selection
{
get
{
return (string)base.GetValue(SelectionProperty);
}
set
{
base.SetValue(SelectionProperty, value);
}
}
protected override void OnSelectionChanged(RoutedEventArgs e)
{
base.OnSelectionChanged(e);
Selection = SelectedText;
}
}
The problem is, that you never actually do anything with the value you assign to Selection. You need to actually make it the selected text.
public string Selection
{
get
{
return (string)base.GetValue(SelectionProperty);
}
set
{
base.SetValue(SelectionProperty, value);
if(value != SelectedText)
SelectedText = value;
}
}
For Binding to update the source you have to specify Mode=TwoWay if you want to reflect changes back to code. This can be done by two ways:
Selection="{Binding Path=MyProperty, Mode=TwoWay}"
or by
public static readonly DependencyProperty SelectionProperty =
DependencyProperty.Register("Selection",
typeof(string),
typeof(SelectionTextbox),
new FrameworkPropertyMetadata(default(string),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
By using the second method you have that all bindings to Selection are done TwoWay and you do not have to specify it explicitly.

Dependency Property Call in code behind

I have created a custom UserControl with some Dependency properties.
This custom control is hosted on a Window.
When I try to get a value from a DependecyProperty in code behind it doesn't work.
public static readonly DependencyProperty ValueDp = DependencyProperty.Register("Value", typeof(string), typeof(MyCustomUserControl), new FrameworkPropertyMetadata(string.Empty, OutputHandler));
public string Value
{
get { return (string)GetValue(ValueDp); }
set { SetValue(ValueDp, value); }
}
private static void OutputHandler(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var temp= dependencyObject as MyCustomUserControl;
if (temp!= null)
{
dependencyObject.SetValue(ValueDp,temp._conversionValue);
}
}
On the host I have put a button and when I click on it, I want to read the value stored in the DP, but I will always get the default value set in DP.
Any ideas what I`m doing wrong here?
Regards
I think that in the OutputHandler method you are always discarding the new value assigned to the property (dependencyPropertyChangedEventArgs.NewValue)
As #Alberto has said the OldValue and NewValue are the properties which hold the value of the DependencyProperty. The above properties are found in dependencyPropertyChangedEventArgs. In your Handler the member dependencyObject and temp refer to the same object.

WPF Dependency Property not working

I have a custom Dependency Property defined like so:
public static readonly DependencyProperty MyDependencyProperty =
DependencyProperty.Register(
"MyCustomProperty", typeof(string), typeof(MyClass));
private string _myProperty;
public string MyCustomProperty
{
get { return (string)GetValue(MyDependencyProperty); }
set
{
SetValue(MyDependencyProperty, value);
}
}
Now I try set that property in XAML
<controls:TargetCatalogControl MyCustomProperty="Boo" />
But the setter in DependencyObject never gets hit! Although it does when I change the property to be a regular property and not a Dep Prop
Try this..
public string MyCustomProperty
{
get
{
return (string)GetValue(MyCustomPropertyProperty);
}
set
{
SetValue(MyCustomPropertyProperty, value);
}
}
// Using a DependencyProperty as the backing store for MyCustomProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyCustomPropertyProperty =
DependencyProperty.Register("MyCustomProperty", typeof(string), typeof(TargetCatalogControl), new UIPropertyMetadata(MyPropertyChangedHandler));
public static void MyPropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// Get instance of current control from sender
// and property value from e.NewValue
// Set public property on TaregtCatalogControl, e.g.
((TargetCatalogControl)sender).LabelText = e.NewValue.ToString();
}
// Example public property of control
public string LabelText
{
get { return label1.Content.ToString(); }
set { label1.Content = value; }
}
It doesn't, unless you call it manually. There's a property-changed handler you can add to the DependancyProperty constructor call to be notified of when the property changes.
Call this constructor:
http://msdn.microsoft.com/en-us/library/ms597502.aspx
With a PropertyMetadata instance created by this constructor:
http://msdn.microsoft.com/en-us/library/ms557327.aspx
EDIT: Also, you are not implementing the dependancy property correctly. Your get and set should use GetValue and SetValue respectively, and you should not have a class member to store the value. The member name of the DP should also be {PropertyName}Property, e.g. MyCustomPropertyProperty if the get/set and property name as registered is MyCustomProperty. See http://msdn.microsoft.com/en-us/library/ms753358.aspx for more information.
Hope that helps.
Maybe you are using MVVM, and overriding the DataContext of your View ?
If you do, then the event for changing MyCustomProperty will be raised on the original DataContext and not on the new ViewModel.

Wrapped WPF Control

I'm trying to create a GUI (WPF) Library where each (custom) control basically wraps an internal (third party) control. Then, I'm manually exposing each property (not all of them, but almost). In XAML the resulting control is pretty straightforward:
<my:CustomButton Content="ClickMe" />
And the code behind is quite simple as well:
public class CustomButton : Control
{
private MyThirdPartyButton _button = null;
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public CustomButton()
{
_button = new MyThirdPartyButton();
this.AddVisualChild(_button);
}
protected override int VisualChildrenCount
{
get
{ return _button == null ? 0 : 1; }
}
protected override Visual GetVisualChild(int index)
{
if (_button == null)
{
throw new ArgumentOutOfRangeException();
}
return _button;
}
#region Property: Content
public Object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
"Content", typeof(Object),
typeof(CustomButton),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeContent))
);
private static void ChangeContent(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as CustomButton).UpdateContent(e.NewValue);
}
private void UpdateContent(Object sel)
{
_button.Content = sel;
}
#endregion
}
The problem comes after we expose MyThirdPartyButton as a property (in case we don't expose something, we would like to give the programmer the means to use it directly). By simply creating the property, like this:
public MyThirdPartyButton InternalControl
{
get { return _button; }
set
{
if (_button != value)
{
this.RemoveVisualChild(_button);
_button = value;
this.AddVisualChild(_button);
}
}
}
The resulting XAML would be this:
<my:CustomButton>
<my:CustomButton.InternalControl>
<thirdparty:MyThirdPartyButton Content="ClickMe" />
</my:CustomButton.InternalControl>
And what I'm looking for, is something like this:
<my:CustomButton>
<my:CustomButton.InternalControl Content="ClickMe" />
But (with the code I have) its impossible to add attributes to InternalControl...
Any ideas/suggestions?
Thanks a lot,
--
Robert
WPF's animation system has the ability to set sub-properties of objects, but the XAML parser does not.
Two workarounds:
In the InternalControl property setter, take the value passed in and iterate through its DependencyProperties copying them to your actual InternalControl.
Use a build event to programmatically create attached properties for all internal control properties.
I'll explain each of these in turn.
Setting properties using the property setter
This solution will not result in the simplified syntax you desire, but it is simple to implement and will probably solve the main problem with is, how to merge values set on your container control with values set on the internal control.
For this solution you continue to use the XAML you didn't like:
<my:CustomButton Something="Abc">
<my:CustomButton.InternalControl>
<thirdparty:MyThirdPartyButton Content="ClickMe" />
</my:CustomButton.InternalControl>
but you don't actually end up replacing your InternalControl.
To do this, your InternalControl's setter would be:
public InternalControl InternalControl
{
get { return _internalControl; }
set
{
var enumerator = value.GetLocalValueEnumerator();
while(enumerator.MoveNext())
{
var entry = enumerator.Current as LocalValueEntry;
_internalControl.SetValue(entry.Property, entry.Value);
}
}
}
You may need some additional logic to exclude DPs not publically visible or that are set by default. This can actually be handled easily by creating a dummy object in the static constructor and making a list of DPs that have local values by default.
Using a build event to create attached properties
This solution allows you to write very pretty XAML:
<my:CustomButton Something="Abc"
my:ThirdPartyButtonProperty.Content="ClickMe" />
The implementation is to automatically create the ThirdPartyButtonProperty class in a build event. The build event will use CodeDOM to construct attached properties for each property declared in ThirdPartyButton that isn't already mirrored in CustomButton. In each case, the PropertyChangedCallback for the attached property will copy the value into the corresponding property of InternalControl:
public class ThirdPartyButtonProperty
{
public static object GetContent(...
public static void SetContent(...
public static readonly DependencyProperty ContentProperty = DependencyProperty.RegisterAttached("Content", typeof(object), typeof(ThirdPartyButtonProperty), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((CustomButton)obj).InternalControl.Content = (object)e.NewValue;
}
});
}
This part of the implementation is straightforward: The tricky part is creating the MSBuild task, referencing it from your .csproj, and sequencing it so that it runs after the precompile of my:CustomButton so it can see what additional properties it needs to add.

Categories

Resources