WPF: Support custom RelativeSource on bindings
This issue has been moved from a ticket on Developer Community.
I'm not sure if this is the proper place for a feature request for WPF, please redirect me if not.
I would like to be able to implement custom RelativeSource for bindings. This is not currently possible as the RelativeSource class only holds the configuration, while the actual resolution to a source object happens in RelativeObjectRef, which is sealed and internal.
My concrete use case is that I would like a RelativeSource that searches for the type of the DataContext, not the element itself.
Original Comments
Feedback Bot on 1/14/2022, 00:32 PM:
(private comment, text removed)
Here is a suggestion how you could do something similar with already available features:
Just add a custom markup extension that looks something like this:
namespace CustomBinding;
public class RelativeDataContextBinding : MarkupExtension
{
public RelativeDataContextBinding(Type dataContextType)
{
DataContextType = dataContextType;
}
public PropertyPath Path { get; set; }
public Type DataContextType { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (target is null)
return DependencyProperty.UnsetValue;
// is in template?
if (target.TargetObject?.GetType().Name == "SharedDp")
return this;
var source = GetFrameworkElementByDataContext(target.TargetObject);
return new Binding { Source = source, Path = Path, Mode = BindingMode.OneWay }.ProvideValue(serviceProvider);
}
private object? GetFrameworkElementByDataContext(object? referenceElement)
{
var current = referenceElement as DependencyObject;
while (current is not null)
{
if (current is FrameworkElement element && element.DataContext?.GetType() == DataContextType)
return element;
current = VisualTreeHelper.GetParent(current);
}
return null;
}
}
Here's a view model with child view models:
namespace CustomBinding;
public class MainViewModel
{
public MainViewModel()
{
Items.Add(new ItemViewModel { Name = "Item 1" });
Items.Add(new ItemViewModel { Name = "Item 2" });
Items.Add(new ItemViewModel { Name = "Item 3" });
}
public ObservableCollection<ItemViewModel> Items { get; } = new();
public int OtherProperty => 999;
}
public class ItemViewModel
{
public required string Name { get; init; }
}
It could be used in XAML like this:
<Window x:Class="CustomBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomBinding">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:ItemViewModel">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name, Mode=OneWay}" Margin="0,0,8,0" />
<TextBlock Text="{local:RelativeDataContextBinding local:MainViewModel, Path=DataContext.OtherProperty}" Foreground="Blue" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
Which then would result in this:
This markup extension is just some idea how it could be done, it has several limitation like not handling DataContext changes. Hope this helps a bit.