Assuming I have an attached property defined like that :
public static string GetMyProperty(DependencyObject obj)
{
return (string)obj.GetValue(MyPropertyProperty);
}
public static void SetMyProperty(DependencyObject obj, string value)
{
obj.SetValue(MyPropertyProperty, value);
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.RegisterAttached("MyProperty", typeof(string), typeof(MyClass), new UIPropertyMetadata(0));
I can write the documentation for the property identifier (MyPropertyProperty) and for the accessors (GetMyProperty and SetMyProperty), but I have no idea where to put the documentation for MyClass.MyProperty attached property, since it is not an actual code element.
The MSDN library contains such documentation (see for instance Grid.Row), so it must be possible...
Where should I put the XML documentation comments for an attached property ?
There's an article about that for Sandcastle:
/// <summary>
/// This defines the <see cref="P:TestDoc.TestClass.IsBroughtIntoViewWhenSelected"/>
/// attached property.
/// </summary>
///
/// <AttachedPropertyComments>
/// <summary>This attached property indicates whether or not a tree view item is
/// brought into view when selected.
/// </summary>
/// <value>The default value is false</value>
/// </AttachedPropertyComments>
public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty =
DependencyProperty.RegisterAttached("IsBroughtIntoViewWhenSelected",
typeof(bool), typeof(TestClass),
new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged));
Even though the answer is a bit late I have found a solution for the appearance of the documentation during runtime of Visual Studio.
If you use ReSharper and press CTRLQ then the XML-Documentation that is added above the SetXXX-method is used to show for the Quick-Documentation.
Related
I am currently trying to add the following line of code to my XAML file:
<MouseBinding Command="helix:ViewportCommands.Rotate" Gesture="{Binding ViewportRotateGesture}" />
The problem is that this doesn't work. Whenever I try to bind a string to the "Gesture" property, I get the following exception:
System.Windows.Markup.XamlParseException: 'A 'Binding' cannot be set on the 'Gesture' property of type 'MouseBinding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.'
Using the "MouseAction" property instead of the "Gesture" property does work, however this doesn’t allow you to add any modifiers. Does anybody know how to bind a mouse gesture in combination with a modifier (for example Shift+LeftClick)?
This is not a DependencyProperty, but a normal CLR property. You can't bind him. See public virtual InputGesture Gesture.
You can create a Behavior or an Attached Property for this purpose.
Example:
using System;
using System.Windows;
using System.Windows.Input;
namespace Core2022.SO.Chris.AttachedProperties
{
public static class InputBinding
{
/// <summary>Returns the value of the attached property Gesture for the <paramref name="inputBinding"/>.</summary>
/// <param name="inputBinding"><see cref="System.Windows.Input.InputBinding"/> whose property value will be returned.</param>
/// <returns>Property value <see cref="InputGesture"/>.</returns>
public static InputGesture GetGesture(System.Windows.Input.InputBinding inputBinding)
{
return (InputGesture)inputBinding.GetValue(GestureProperty);
}
/// <summary>Sets the value of the Gesture attached property to <paramref name="inputBinding"/>.</summary>
/// <param name="inputBinding"><see cref="System.Windows.Input.InputBinding"/> whose property is setting to a value..</param>
/// <param name="value"><see cref="InputGesture"/> value for property.</param>
public static void SetGesture(System.Windows.Input.InputBinding inputBinding, InputGesture value)
{
inputBinding.SetValue(GestureProperty, value);
}
/// <summary><see cref="DependencyProperty"/> for methods <see cref="GetGesture(System.Windows.Input.InputBinding)"/>
/// and <see cref="SetGesture(System.Windows.Input.InputBinding, InputGesture)"/>.</summary>
public static readonly DependencyProperty GestureProperty =
DependencyProperty.RegisterAttached(
nameof(GetGesture).Substring(3),
typeof(InputGesture),
typeof(InputBinding),
new PropertyMetadata(null, OnGestureChanged));
private static void OnGestureChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not System.Windows.Input.InputBinding inputBinding)
throw new NotImplementedException($"Implemented only for the \"{typeof(System.Windows.Input.InputBinding).FullName}\" class");
inputBinding.Gesture = (InputGesture)e.NewValue;
}
}
}
<MouseBinding Command="helix:ViewportCommands.Rotate"
ap:InputBinding.Gesture="{Binding ViewportRotateGesture}"/>
I am going over the differences between regular dependency properties and attached properties.
Using ILSpy, I had a look at how Register and RegisterAttached are implemented.
Register:
// System.Windows.DependencyProperty
/// <summary>Registers a dependency property with the specified property name, property type, owner type, property metadata, and a value validation callback for the property. </summary>
/// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
/// <param name="name">The name of the dependency property to register.</param>
/// <param name="propertyType">The type of the property.</param>
/// <param name="ownerType">The owner type that is registering the dependency property.</param>
/// <param name="typeMetadata">Property metadata for the dependency property.</param>
/// <param name="validateValueCallback">A reference to a callback that should perform any custom validation of the dependency property value beyond typical type validation.</param>
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback)
{
DependencyProperty.RegisterParameterValidation(name, propertyType, ownerType);
PropertyMetadata defaultMetadata = null;
if (typeMetadata != null && typeMetadata.DefaultValueWasSet())
{
defaultMetadata = new PropertyMetadata(typeMetadata.DefaultValue);
}
DependencyProperty dependencyProperty = DependencyProperty.RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
if (typeMetadata != null)
{
dependencyProperty.OverrideMetadata(ownerType, typeMetadata);
}
return dependencyProperty;
}
RegisterAttached:
// System.Windows.DependencyProperty
/// <summary>Registers an attached property with the specified property type, owner type, property metadata, and value validation callback for the property. </summary>
/// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
/// <param name="name">The name of the dependency property to register.</param>
/// <param name="propertyType">The type of the property.</param>
/// <param name="ownerType">The owner type that is registering the dependency property.</param>
/// <param name="defaultMetadata">Property metadata for the dependency property. This can include the default value as well as other characteristics.</param>
/// <param name="validateValueCallback">A reference to a callback that should perform any custom validation of the dependency property value beyond typical type validation.</param>
public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
{
DependencyProperty.RegisterParameterValidation(name, propertyType, ownerType);
return DependencyProperty.RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
}
It looks like the only difference is how the PropertyMetaData is handled. Register has some extra logic using the passed in typeMetaData parameter while RegisterAttached just passes the PropertyMetaData along. Both methods then call RegisterCommon.
When it comes to implementing attached properties, is this the only difference? I looked at the Set/GetValue methods on DependencyObject. They are quite complicated (so I very easily could have missed some key bit of code that would explain things) but I couldn't find anything that would cause different code paths to be taken depending on what Register or RegisterAttached did.
Question:
Can someone explain to me what the difference I described above between Register and RegisterAttached is actually doing and how it allows attached properties to be used as a type of global property that is settable on any object? Maybe the place where the actual work is done is in the Set/GetValue methods on DependencyObject?
TLDR: the differences you see in your code example are indeed the only differences between "attached" and regular dependency properties, and after that code is run they are treated the same by WPF, there is no such concept as "attached" property internally at all (like some flag DependencyProperty.IsAttached or anything like that).
You are right that the difference between "attached" and regular dependency properties is just metadata handling.
First, it's perfectly possible to use regular property as attached one. For example suppose we have following property (note it's not "attached" in a sense RegisterAttached is not used):
public class TestProperties {
public static readonly DependencyProperty TestProperty = DependencyProperty.Register(
"Test",
typeof(Boolean),
typeof(TestProperties)
);
public static void SetTest(UIElement element, Boolean value) {
element.SetValue(TestProperty, value);
}
public static Boolean GetTest(UIElement element) {
return (Boolean) element.GetValue(TestProperty);
}
}
And suppose we have TextBlock like this:
<TextBlock x:Name="tb" local:TestProperties.Test="True" />
Because we have correct GetTest and SetTest methods - it's valid syntax. Now we can get the value of our properties as usual for attached properties:
// this returns true
var value = tb.GetValue(TestProperties.TestProperty);
Note that those GetTest and SetTest are not required either. They are needed only to be able to get\set "attached" property value from xaml. So far as you see regular and "attached" properties are exactly the same, there is no difference whatoever.
When we start to pass metadata - there are some differences though. When you register attached property, metadata you pass will be the default one and will apply to all types.
public class TestProperties {
public static readonly DependencyProperty TestProperty = DependencyProperty.RegisterAttached(
"Test",
typeof(Boolean),
typeof(TestProperties),
new PropertyMetadata(true, OnTestChanged)
);
private static void OnTestChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
}
public static void SetTest(UIElement element, Boolean value) {
element.SetValue(TestProperty, value);
}
public static Boolean GetTest(UIElement element) {
return (Boolean) element.GetValue(TestProperty);
}
}
If we now do:
var meta = TestProperties.TestProperty.GetMetadata(typeof(TextBlock));
// or any other type
We will see that metadata for any type is the default one you passed, with callback and other stuff. What that means, for example, is your value changed callback will be called whenever value of your attached property changes for any type (like TextBlock in our example above).
When we register property with metadata with regular Register - it behaves differently:
PropertyMetadata defaultMetadata = null;
if (typeMetadata != null && typeMetadata.DefaultValueWasSet())
{
defaultMetadata = new PropertyMetadata(typeMetadata.DefaultValue);
}
DependencyProperty dependencyProperty = DependencyProperty.RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
First it copies the default value (if any) and creates new metadata with only the default value. Then it creates property with that metadata but only default value is part of default metadata now. All callbacks are not part of default metadata. What that means is that now your callback will not be called when value of dependency property changes on arbitrary type (though default value is still preserved).
if (typeMetadata != null)
{
dependencyProperty.OverrideMetadata(ownerType, typeMetadata);
}
Now it takes full metadata you passed (with callbacks) and registers it only for the type you passed. In our example that means callbacks will be called only when value changes for type TestProperties itself (which must inherit DependencyObject from now on:
public class TestProperties : DependencyObject { // must inherit dependency object
public static readonly DependencyProperty TestProperty = DependencyProperty.Register(
"Test",
typeof(Boolean),
typeof(TestProperties),
new PropertyMetadata(true, OnTestChanged)
);
private static void OnTestChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
// this will be called ONLY when we do something like
// var prop = new TestProperties();
// prop.SetValue(TestProperty, true);
// but will NOT be called when we set value for TextBlock
}
public static void SetTest(UIElement element, Boolean value) {
element.SetValue(TestProperty, value);
}
public static Boolean GetTest(UIElement element) {
return (Boolean) element.GetValue(TestProperty);
}
}
If we want callback to be called for TextBlock - we can add that manually:
static TestProperties() {
TestProperties.TestProperty.OverrideMetadata(typeof(TextBlock), new PropertyMetadata(OnTestChanged));
}
But we have lost the (very useful) ability of "attached" properties to be able to track changes to values defined on any types (TextBlock, Button, whatever).
I hope this answers your question.
How can I create a simple bool dependency property IsInput. This value can only be set to true or false when the class is created in code. Seems rather simple but ive searched around online and haven't found a clear example.
I've seen examples like this one below online but I'm not quite clear on what I would duplicate to create my own bool dependency property correctly.
public static readonly DependencyProperty AncestorProperty =
DependencyProperty.Register("Ancestor", typeof(FrameworkElement), typeof(MyItem),
new FrameworkPropertyMetadata(Ancestor_PropertyChanged));
/// <summary>
/// Event raised when 'Ancestor' property has changed.
/// </summary>
private static void Ancestor_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyItem c = (MyItem)d;
c.UpdateHotspot();
}
The second parameter of the Register method is the type of the property, i.e. bool, while the third parameter is the so-called owner type, which is the type that declares the property (MyControl in the example below).
For a complete dependency property declaration you also need to declare the "wrapper" property with a getter and a setter that call the dependency property's GetValue and SetValue methods.
public static readonly DependencyProperty IsInputProperty =
DependencyProperty.Register("IsInput", typeof(bool), typeof(MyControl),
new FrameworkPropertyMetadata(IsInputPropertyChanged));
/// <summary>
/// CLR wrapper for the 'IsInput' dependency property.
/// </summary>
public bool IsInput
{
get { return (bool)GetValue(IsInputProperty); }
set { SetValue(IsInputProperty, value); }
}
/// <summary>
/// Callback called when 'IsInput' property has changed.
/// </summary>
private static void IsInputPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool b = (bool)e.NewValue;
//TODO
}
I'm trying to create a lazy property with Catel framework. Is there a way to do it?
When I'm creating a property in ViewModel like this:
#region Photos property
/// <summary>
/// Gets or sets the Photos value.
/// </summary>
public FastObservableCollection<Photo> Photos
{
get
{
var temp = GetValue<FastObservableCollection<Photo>>(PhotosProperty);
if (temp == null)
Photos = SelectedPatient.GetPhotos();
return GetValue<FastObservableCollection<Photo>>(PhotosProperty);
}
set { SetValue(PhotosProperty, value); }
}
/// <summary>
/// Photos property data.
/// </summary>
public static readonly PropertyData PhotosProperty = RegisterProperty("Photos", typeof (FastObservableCollection<Photo>));
#endregion
the get function is called even without binding, so my lazy property initializes while ViewModel is initializing.
Is there a way to do it?
There is only 1 way to implement "lazy properties", and that is by using the Lazy<> class. The reason for this is that for some mappings (such as view model to model, etc), Catel uses SetValue directly instead of the property wrapper (compare Catel properties with dependency properties).
I am tring to extend an existing microsoft control called the PivotViewer.
This control has an existing property that I want to expose to my ViewModel.
public ICollection<string> InScopeItemIds { get; }
I have created an inherited class called CustomPivotViewer and I want to create a Dependency Property that I can bind to that will expose the values held in InScopeItemIds in the base class.
I have spent a fair while reading up about DependencyPropertys and am becomming quite disheartened.
Is this even possible?
You only need a DependencyProperty is you want it to be bindable, meaning: if you want to have, for example, a MyBindableProperty property in your control, with which you want to be able to do:
MyBindableProperty={Binding SomeProperty}
if, however, you want other DependencyProperties to bind to it, any property (either a DependencyProperty or a normal one) can be used.
I'm not sure what you really need, maybe you can clarify more, but if it's the first scenario that you want to implement, you can do it as follows:
create a DependencyProperty, let's call it BindableInScopeItemIds, like so:
/// <summary>
/// BindableInScopeItemIds Dependency Property
/// </summary>
public static readonly DependencyProperty BindableInScopeItemIdsProperty =
DependencyProperty.Register("BindableInScopeItemIds", typeof(ICollection<string>), typeof(CustomPivotViewer),
new PropertyMetadata(null,
new PropertyChangedCallback(OnBindableInScopeItemIdsChanged)));
/// <summary>
/// Gets or sets the BindableInScopeItemIds property. This dependency property
/// indicates ....
/// </summary>
public ICollection<string> BindableInScopeItemIds
{
get { return (ICollection<string>)GetValue(BindableInScopeItemIdsProperty); }
set { SetValue(BindableInScopeItemIdsProperty, value); }
}
/// <summary>
/// Handles changes to the BindableInScopeItemIds property.
/// </summary>
private static void OnBindableInScopeItemIdsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (CustomPivotViewer)d;
ICollection<string> oldBindableInScopeItemIds = (ICollection<string>)e.OldValue;
ICollection<string> newBindableInScopeItemIds = target.BindableInScopeItemIds;
target.OnBindableInScopeItemIdsChanged(oldBindableInScopeItemIds, newBindableInScopeItemIds);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the BindableInScopeItemIds property.
/// </summary>
protected virtual void OnBindableInScopeItemIdsChanged(ICollection<string> oldBindableInScopeItemIds, ICollection<string> newBindableInScopeItemIds)
{
}
in the OnBindableInScopeItemIdsChanged, you can update the inner collection (InScopeItemIds)
remember that the property you want to expose is read-only (it has no "setter"), so you might need to update it as so:
protected virtual void OnBindableInScopeItemIdsChanged(ICollection<string> oldBindableInScopeItemIds, ICollection<string> newBindableInScopeItemIds)
{
InScopeItemIds.Clear();
foreach (var itemId in newBindableInScopeItemIds)
{
InScopeItemIds.Add(itemId);
}
}
Hope this helps :)
EDIT:
I realized misunderstandings and here is a new version (in the context of the original question):
So, you can use the property you need for the binding, with following circumstances having in mind:
as this property is read-only, you will not be able to use it for 2-way binding.
as far as the containing type does not implement INotifyPropertyChanged, your target control used to display the data will not be notified about the changes to the property value.
as far as the returned by this property value does not implement INotifyCollectionChanged (one example is ObservableCollection<T>), the changes to the collection will not be affected on the target control which is used to display it.