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.
CodePudding user response:
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>