Custom editor works when defined as attribute on property, but not when defined in xaml

Jul 19, 2012 at 8:40 PM
Edited Jul 19, 2012 at 8:42 PM

Hello all,

We have several classes that have HorizontalOrientation and VerticalOrientation properties defined on them. Based on the documentation on this site, we created custom editors for those properties to show the enum values as horizontal buttons instead of a combo box.

If we decorate the properties with the Editor attribute, they work as expected. If we try to define them in xaml, they don't. Can anyone shed any light on this situation? Below are the classes.

Because of pathing, the Generic.xaml file needs to be in a Themes folder under PropertyGrid\Implementation\Editors. AlignmentEditorBase, HorizontalAlignmentEditor and VerticalAlignmentEditor are all under PropertyGrid\Implementation\Editors. Lastly, in a folder named Images under PropertyGrid\Implementation\Editors there needs to be an Images folder with horizontalCenter.bmp, horizontalLeft.bmp, horizontalRight.bmp, horizontalStretch.bmp, verticalBottom.bmp, verticalCenter.bmp, verticalStretch.bmp and verticalTop.bmp files in it.

When applied to dependency property wrappers as below, the custom editors are displayed properly in the propertygrid

[Editor(typeof(HorizontalAlignmentEditor), typeof(HorizontalAlignmentEditor))]
public System.Windows.HorizontalAlignment HorizontalAlignment
{
    get { return (System.Windows.HorizontalAlignment)GetValue(HorizontalAlignmentProperty); }
    set { SetValue(HorizontalAlignmentProperty, value); }
}

[Editor(typeof(VerticalAlignmentEditor), typeof(VerticalAlignmentEditor))]
public System.Windows.VerticalAlignment VerticalAlignment
{
    get { return (System.Windows.VerticalAlignment)GetValue(VerticalAlignmentProperty); }
    set { SetValue(VerticalAlignmentProperty, value); }
}

But when defined in xaml as below, the editors appear blank
    <xctk:EditorDefinition>
        <xctk:EditorDefinition.PropertiesDefinitions>
            <xctk:PropertyDefinition Name="VerticalAlignment" />
        </xctk:EditorDefinition.PropertiesDefinitions>
        <xctk:EditorDefinition.EditorTemplate>
            <DataTemplate>
                <xctk:VerticalAlignmentEditor Value="{Binding Value}" />
            </DataTemplate>
        </xctk:EditorDefinition.EditorTemplate>
    </xctk:EditorDefinition>-->

Below are the classes that make up the custom editors.

Generic.xaml - Restyles a listbox to display horizontally with images

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <!-- ListBoxItem style -->
    <Style x:Key="listBoxItemStyle" TargetType="{x:Type ListBoxItem}">
        <Style.Resources>
            <Style TargetType="Border">
                <Setter Property="CornerRadius" Value="4" />
            </Style>
        </Style.Resources>
        <Setter Property="BorderBrush" Value="Black" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Padding" Value="3" />
        <Setter Property="Margin" Value="5,0,0,0" />
        <Setter Property="Background" Value="WhiteSmoke" />
    </Style>

    <!-- Basic ListBox style without images -->
    <Style x:Key="listBoxStyle" TargetType="{x:Type ListBox}">
        <Setter Property="ItemContainerStyle" Value="{StaticResource listBoxItemStyle}" />
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" IsItemsHost="true" />
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Name="textBlock" Text="{Binding XPath=@Text}" VerticalAlignment="Center" ToolTip="{Binding XPath=Tooltip}"/>
                        <Viewbox Margin="5,0,0,0" Height="{Binding ElementName=textBlock, Path=ActualHeight}" HorizontalAlignment="Center" ToolTip="{Binding XPath=Tooltip}">
                            <Image Width="16" HorizontalAlignment="Center" Height="16" VerticalAlignment="Center" Source="{Binding XPath=@ImageSource}"/>
                        </Viewbox>
                    </StackPanel>
                </DataTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListBox}">
                    <StackPanel Orientation="Horizontal" SnapsToDevicePixels="True">
                        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- ListBox style with vertical alignment images -->
    <Style x:Key="listBoxStyleVertical" TargetType="{x:Type ListBox}" BasedOn="{StaticResource listBoxStyle}">
        <Style.Resources>
            <XmlDataProvider x:Key="data" XPath="EditorItems/EditorItem">
                <x:XData>
                    <!-- Individual buttons and text -->
                    <EditorItems xmlns="">
                        <EditorItem Text="T" Tag="Top"    Tooltip="Align left"   ImageSource="/WPFToolkit.Extended;component/PropertyGrid/Implementation/Editors/Images/verticalTop.bmp" />
                        <EditorItem Text="C" Tag="Center" Tooltip="Align center" ImageSource="/WPFToolkit.Extended;component/PropertyGrid/Implementation/Editors/Images/verticalCenter.bmp"/>
                        <EditorItem Text="B" Tag="Bottom" Tooltip="Align right"  ImageSource="/WPFToolkit.Extended;component/PropertyGrid/Implementation/Editors/Images/verticalBottom.bmp"/>
                        <!--<EditorItem Text="S" Tag="Stretch" Tooltip="Align left and right" ImageSource="/WPFToolkit.Extended;component/PropertyGrid/Implementation/Editors/Images/verticalStretch.bmp"/>-->
                    </EditorItems>
                </x:XData>
            </XmlDataProvider>
        </Style.Resources>
        <Setter Property="ItemsSource" Value="{Binding Source={StaticResource data}}" />
    </Style>

    <!-- ListBox style with horizontal alignment images -->
    <Style x:Key="listBoxStyleHorizontal" TargetType="{x:Type ListBox}" BasedOn="{StaticResource listBoxStyle}">
        <Style.Resources>
            <XmlDataProvider x:Key="data" XPath="EditorItems/EditorItem">
                <x:XData>
                    <!-- Individual buttons and text -->
                    <EditorItems xmlns="">
                        <EditorItem Text="L" Tag="Left"   Tooltip="Align left"   ImageSource="/WPFToolkit.Extended;component/PropertyGrid/Implementation/Editors/Images/horizontalLeft.bmp" />
                        <EditorItem Text="C" Tag="Center" Tooltip="Align center" ImageSource="/WPFToolkit.Extended;component/PropertyGrid/Implementation/Editors/Images/horizontalCenter.bmp"/>
                        <EditorItem Text="R" Tag="Right"  Tooltip="Align right"  ImageSource="/WPFToolkit.Extended;component/PropertyGrid/Implementation/Editors/Images/horizontalRight.bmp"/>
                        <!--<EditorItem Text="S" Tag="Stretch" Tooltip="Align left and right" ImageSource="/WPFToolkit.Extended;component/PropertyGrid/Implementation/Editors/Images/horizontalStretch.bmp"/>-->
                    </EditorItems>
                </x:XData>
            </XmlDataProvider>
        </Style.Resources>
        <Setter Property="ItemsSource" Value="{Binding Source={StaticResource data}}" />
    </Style>

</ResourceDictionary>
    
AlignmentEditorBase class - base class for custom AlignmentEditors
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Xml;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;

namespace Xceed.Wpf.Toolkit.PropertyGrid.Editors
{
    public class AlignmentEditorBase : FrameworkElement, Xceed.Wpf.Toolkit.PropertyGrid.Editors.ITypeEditor
    {
        #region Fields

        protected Style ListBoxEditorStyle = null;
        protected ListBox ListBoxEditor = null;

        #endregion

        #region Constructors

        public AlignmentEditorBase()
        {
            ListBoxEditorStyle = GetListBoxEditorStyle();
        }

        #endregion

        #region Methods

        #region ITypeEditor Members

        public System.Windows.FrameworkElement ResolveEditor(Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem propertyItem)
        {
            ListBoxEditor = new ListBox() { Style = ListBoxEditorStyle };
            ListBoxEditor.SelectionChanged += delegate(object sender, SelectionChangedEventArgs e)
            {
                XmlNode item = (XmlNode)((ListBox)sender).SelectedItem;
                ListBoxEditor.Tag = GetTagValue(item);
            };

            // Create the binding from the bound property item to the editor.
            Binding binding = new Binding("Value")
            {
                Source = propertyItem,
                ValidatesOnDataErrors = true,
                ValidatesOnExceptions = true,
                Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay
            };

            BindingOperations.SetBinding(ListBoxEditor, ListBox.TagProperty, binding);

            return ListBoxEditor;
        }

        protected virtual object GetTagValue(XmlNode item)
        {
            throw new NotImplementedException("GetTagValue method must be overridden in the derived class.");
        }

        #endregion

        private Style GetListBoxEditorStyle()
        {
            if (!UriParser.IsKnownScheme("pack"))
                UriParser.Register(new GenericUriParser(GenericUriParserOptions.GenericAuthority), "pack", -1);

            // Read theme file from resource.
            Uri uri = new Uri("/WPFToolkit.Extended;component/PropertyGrid/Implementation/Editors/Themes/Generic.xaml", UriKind.Relative);
            ResourceDictionary themeDictionary = new ResourceDictionary() { Source = uri };
            //ResourceDictionary themeDictionary = Application.LoadComponent(uri) as ResourceDictionary;
            return GetListBoxEditorStyle(themeDictionary);
        }

        protected virtual Style GetListBoxEditorStyle(ResourceDictionary themeDictionary)
        {
            throw new NotImplementedException("GetListBoxEditorStyle method must be overridden in the derived class.");
        }

        #endregion
    }
}

HorizontalAlignmentEditor - Custom editor that inherits AlignmentEditorBase
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Xml;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;

namespace Xceed.Wpf.Toolkit.PropertyGrid.Editors
{
    public class HorizontalAlignmentEditor : AlignmentEditorBase
    {
        #region Properties

        #region Value

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(HorizontalAlignment), typeof(HorizontalAlignmentEditor), new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));
        
        public HorizontalAlignment Value
        {
            get { return (HorizontalAlignment)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            HorizontalAlignmentEditor editor = o as HorizontalAlignmentEditor;
            if (editor != null)
                editor.OnValueChanged((HorizontalAlignment)e.OldValue, (HorizontalAlignment)e.NewValue);
        }

        protected virtual void OnValueChanged(HorizontalAlignment oldValue, HorizontalAlignment newValue)
        {
            RoutedPropertyChangedEventArgs<HorizontalAlignment> args = new RoutedPropertyChangedEventArgs<HorizontalAlignment>(oldValue, newValue);
            args.RoutedEvent = ValueChangedEvent;
            RaiseEvent(args);
        }

        #endregion

        #endregion

        #region Events

        public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<HorizontalAlignment>), typeof(HorizontalAlignment));

        public event RoutedPropertyChangedEventHandler<HorizontalAlignment> ValueChanged
        {
            add { AddHandler(ValueChangedEvent, value); }
            remove { RemoveHandler(ValueChangedEvent, value); }
        }

        #endregion

        #region Constructors

        public HorizontalAlignmentEditor()
            : base()
        {
        }

        #endregion

        #region Methods

        protected override object GetTagValue(XmlNode item)
        {
            Value = (HorizontalAlignment)Enum.Parse(typeof(HorizontalAlignment), item.Attributes["Tag"].Value);
            return Value;
        }

        protected override Style GetListBoxEditorStyle(ResourceDictionary themeDictionary)
        {
            return (Style)themeDictionary["listBoxStyleHorizontal"];
        }

        #endregion
    }
}
VerticalAlignmentEditor - Custom editor that inherits AlignmentEditorBase
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Xml;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;

namespace Xceed.Wpf.Toolkit.PropertyGrid.Editors
{
    public class VerticalAlignmentEditor : AlignmentEditorBase
    {
        #region Properties

        #region Value

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(VerticalAlignment), typeof(VerticalAlignmentEditor), new FrameworkPropertyMetadata(VerticalAlignment.Top, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));

        public VerticalAlignment Value
        {
            get { return (VerticalAlignment)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            VerticalAlignmentEditor editor = o as VerticalAlignmentEditor;
            if (editor != null)
                editor.OnValueChanged((VerticalAlignment)e.OldValue, (VerticalAlignment)e.NewValue);
        }

        protected virtual void OnValueChanged(VerticalAlignment oldValue, VerticalAlignment newValue)
        {
            RoutedPropertyChangedEventArgs<VerticalAlignment> args = new RoutedPropertyChangedEventArgs<VerticalAlignment>(oldValue, newValue);
            args.RoutedEvent = ValueChangedEvent;
            RaiseEvent(args);
        }

        #endregion

        #endregion

        #region Events

        public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<VerticalAlignment>), typeof(VerticalAlignment));

        public event RoutedPropertyChangedEventHandler<VerticalAlignment> ValueChanged
        {
            add { AddHandler(ValueChangedEvent, value); }
            remove { RemoveHandler(ValueChangedEvent, value); }
        }

        #endregion

        #region Constructors

        public VerticalAlignmentEditor()
            : base()
        {
        }

        #endregion

        #region Methods

        protected override object GetTagValue(XmlNode item)
        {
            Value = (VerticalAlignment)Enum.Parse(typeof(VerticalAlignment), item.Attributes["Tag"].Value);
            return Value;
        }

        protected override Style GetListBoxEditorStyle(ResourceDictionary themeDictionary)
        {
            return (Style)themeDictionary["listBoxStyleVertical"];
        }

        #endregion
    }
}

Developer
Aug 13, 2012 at 7:35 PM

When using an AttributeEditor(a property decorated with the Editor attribute), we know we are working with an ITypeEditor and the method ResolveEditor( PropertyItem propertyItem ) is called. This method will create the binding between the propertyItem.Value and the Editor.

When using a CustomEditor (defined in XAML), we load the DataTemplate defined in XAML and the editor will be what is defined in XAML. That means the binding needs to be defined in XAML, like

 <DataTemplate>
    <TextBox Background="Red"  Text="{Binding Value}" />
 </DataTemplate>

In your case, the ResolveEditor( PropertyItem propertyItem ) method is used to create a ListBox and set a binding on it. It will be the Editor used. This method won't be called when using a CustomEditor. That is why you don't see anything. You could define the listBox in XAML, set its style and use a converter to adapt your values.

<DataTemplate>
         <local:ListBox Style=... SelectedItem="{Binding Value, Converter=...}" />
</DataTemplate>

Aug 15, 2012 at 2:05 PM

Quickly:

An ITypeEditor is not intended to be used as a UIElement or FramworkElement but act as a factory (by calling ITypeEditor.ResolveEditor( PropertyItem propertyItem ) ) to create the actual Control that will be used as editor. 

ITypeEditors can only be used with the the EditorAttribute.

When using the "EditorDefinition.EditorTemplate" property, simply define a standard DataTemplate that will be used as editor. Don't forget to add the a TwoWay binding between the editor control and the "Value" property.