I want to create an attached property that can be used with this syntax:
<Button>
<Image .../>
<ui:ToolbarItem.DisplayFilter>
<TabItem .../>
<TabItem .../>
<TabItem .../>
</ui:ToolbarItem.DisplayFilter>
</Button>
This is my attempt at doing so:
public class ToolbarItem
{
/// <summary>
/// Identifies the DisplayFilter attached property.
/// </summary>
public static readonly DependencyProperty DisplayFilterProperty =
DependencyProperty.RegisterAttached(
"DisplayFilter",
typeof( IList ),
typeof( ToolbarItem )
);
public static IList GetDisplayFilter( Control item ) {
return (IList)item.GetValue( DisplayFilterProperty );
}
public static void SetDisplayFilter( Control item, IList value ) {
item.SetValue( DisplayFilterProperty, value );
}
}
This, however, is causing an exception at parse-time -- System.ArgumentException: TabItem is not a valid value for property 'DisplayFilter'. So how do I configure my attached property so that I can use the desired XAML syntax?
An attached property is a Extensible Application Markup Language (XAML) concept. Attached properties enable extra property/value pairs to be set on any XAML element that derives from DependencyObject, even though the element doesn't define those extra properties in its object model.
Attached properties are properties which can be set on any wpf object (basically, at least a DependencyObject) via the DependencyObject. SetValue method. The purpose for this mechanism is to "attach" to other objects information needed by parent objects, not the child objects themselves. For example, the Grid.
Attached properties enable an object to assign a value for a property that its own class doesn't define.
A Dependency Property is a property whose value depends on the external sources, such as animation, data binding, styles, or visual tree inheritance. Not only this, but a Dependency Property also has the built-in feature of providing notification when the property has changed, data binding and styling.
Remember that XAML is basically just a shorthand form of object creation. So to create a collection/list as the value for the attached DisplayFilter
property you would have to enclose those TabItems
inside another collection tag. If you don't want to do that, which is understandable, you have to initialize the collection the first time the property is accessed.
There is just one problem with this: The getter method is skipped by the XAML reader as an optimization. You can prevent this behavior by choosing a different name for the name argument to the RegisterAttached
call:
DependencyProperty.RegisterAttached("DisplayFilterInternal", ...)
Then the property getter will be called and you can check for null
. You can read more about that in this blog post.
Edit: Seems like the linked blog post isn't that clear. You change only the name of the string passed to RegisterAttached
, not the name of the static get/set methods:
public static readonly DependencyProperty DisplayFilterProperty =
DependencyProperty.RegisterAttached(
"DisplayFilterInternal",
typeof(IList),
typeof(ToolbarItem));
public static TabItemCollection GetDisplayFilter(Control item)
{ ... }
public static void SetDisplayFilter(Control item, IList value)
{ ... }
You have to initialize the collection in the GetDisplayFilter
method:
public static TabItemCollection GetDisplayFilter(Control item)
{
var collection = (IList)item.GetValue(DisplayFilterProperty);
if (collection == null) {
collection = new List<object>();
item.SetValue(DisplayFilterProperty, collection);
}
return collection;
}
It seems that you only add TabItem
elements to that collection. Then you can make the collection type-safe, but using IList<T>
does not work since the XAML parser cannot invoke the generic method Add(T)
. Collection<T>
and List<T>
also implement the non-generic IList
interface and can be used in this case. I would suggest to create a new collection type in case you want to do some changes to the collection in the future:
public class TabItemCollection : Collection<TabItem>
{
}
If you don't care about setting the collection explicitly like this:
<ui:ToolbarItem.DisplayFilter>
<ui:TabItemCollection>
<TabItem/>
</ui:TabItemCollection>
</ui:ToolbarItem.DisplayFilter>
you can remove the SetDisplayFilter
method.
To summarize:
public class TabItemCollection : Collection<TabItem>
{
}
public class ToolbarItem
{
public static readonly DependencyProperty DisplayFilterProperty =
DependencyProperty.RegisterAttached(
"DisplayFilterInternal", // Shadow the name so the parser does not skip GetDisplayFilter
typeof(TabItemCollection),
typeof(ToolbarItem));
public static TabItemCollection GetDisplayFilter(Control item)
{
var collection = (TabItemCollection)item.GetValue(DisplayFilterProperty);
if (collection == null) {
collection = new TabItemCollection();
item.SetValue(DisplayFilterProperty, collection);
}
return collection;
}
// Optional, see above note
//public static void SetDisplayFilter(Control item, TabItemCollection value)
//{
// item.SetValue(DisplayFilterProperty, value);
//}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With