1

I am writing a custom adorner for one of my controls. I want to add a context menu to this adorner and fill one of its items with the values of an enum. I know how to do such a binding in XAML:

<ContextMenu>
    <MenuItem Header="Color" ItemsSource={Binding Source={local:EnumBindingSource {x:Type local:MyColorEnum}, ReturnStrings=True}, Mode=OneTime}/>
</ContextMenu>

where EnumBindingSource is a custom MarkupExtension I wrote for that purpose:

public class EnumBindingSource : MarkupExtension
{
    private Type enumType;
    public Type EnumType
    {
        get => enumType;
        set
        {
            if (enumType != value)
            {
                if (value != null)
                {
                    Type type = Nullable.GetUnderlyingType(value) ?? value;
                    if (!type.IsEnum)
                        throw new ArgumentException("Type must be an enum type");
                }
                enumType = value;
            }
        }
    }

    public bool ReturnStrings { get; set; }

    public EnumBindingSource() { }

    public EnumBindingSource(Type enumType)
    {
        this.enumType = enumType;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        // code to turn enum values into strings, not relevant to the problem
    }
}

In this case, I can't create the binding in XAML because the adorner does not have any XAML code. I want to instead create the binding in C#, which I try to do like this:

internal class MyAdorner : Adorner, INotifyPropertyChanged
{
    (...)
    
    public MyAdorner(UIElement adornedElement) : base(adornedElement)
    {
        (...)
        
        ContextMenu contextMenu = new ContextMenu();
        MenuItem color = new MenuItem()
        {
            Header = "Color"
        };
        color.SetBinding(ItemsControl.ItemsSourceProperty, new Binding()
        {
            Source = new EnumBindingSource(typeof(MyColorEnum))
            {
                ReturnStrings = true
            },
            Mode = BindingMode.OneTime
        });
        contextMenu.Items.Add(color);
        this.ContextMenu = contextMenu;
    }
}

but this fails, it creates nothing. Unfortunately any sources I found online on creating bindings in code do not offer sufficient information for this particular case.

Any help is, as always, greatly appreciated.

flibbo
  • 125
  • 10
  • You should reformulate your question to simplify it, the fact that it is an adorner or a context menu or that you have a really complicated `MarkupExtension` derived class as nothing to do with you issue. – Orace Jun 17 '22 at 14:56
  • Does this answer your question? [Set custom MarkupExtension from code](https://stackoverflow.com/questions/7489789/set-custom-markupextension-from-code) – Orace Jun 17 '22 at 15:15
  • @Orace wanted to be precise and complete so people won't have to wonder what I'm doing or ask for code X but I agree the ProvideValue method is not relevant. I was on the fence about adding it to the question. I edited it out now. – flibbo Jun 20 '22 at 08:13

1 Answers1

1

When you write this:

ItemsSource="{Binding Source={local:MyMarkup}}"

The xaml engine doesn't directly put the MyMarkup object in the source property.
It calls the MarkupExtension.ProvideValue method with an IProvideValueTarget as argument.
The returned object is the Source.

So the equivalent C# code is:

var ebs = new EnumBindingSource(typeof(MyColorEnum)) { ReturnStrings = true };
color.SetBinding(ItemsControl.ItemsSourceProperty, new Binding()
{
    Source = ebs.ProvideValue(null), // provide a IServiceProvider is hard
    Mode = BindingMode.OneTime
});

By the way, you don't need a binding since the source will not change (MarkupExtension doesn't override INotifyPropertyChanged).
Then you can write:

<ContextMenu>
    <MenuItem Header="Color" ItemsSource="{local:EnumBindingSource {x:Type local:MyColorEnum}, ReturnStrings=True}" />
</ContextMenu>

Working demo of a MRE available here.

Orace
  • 7,822
  • 30
  • 45
  • Thank you very much, this works. Didn't realize simply passing null for an IServiceProvider would work. – flibbo Jun 20 '22 at 08:12