BindingProxy ru - Leksiqq/WpfMarkupExtension GitHub Wiki

Класс BindingProxy

Универсальный ресурс, который можно использовать в различных ситуациях.

Свойства

  • Value - получает и возвращает любой объект, может быть как целевым свойством, так и источником привязки.
  • Name - необязательное свойство типа String, может использоваться для различения нескольких объектов, например, при передаче в качестве параметра в конвертер (смотри Пример 4).
  • Type - необязательное свойство типа Type, может использоваться для передачи информации об ожидаемом типе значения. Если установлено, то Value конвертируется в этот тип.

Примеры

Пример 1.

Разместить ресурс в ресурсах окна, связав его с каким-либо свойством DataContext окна. В ресурсах DataGrid или ComboBox при разметке шаблонов при необходимости получить привязку к этому свойству, не занимаясь поиском DataContext окна (так как в шаблонах DataContext - это текущий элемент коллекции).

В MainWindow.xaml.cs:

...
public RemoveCommand RemoveCommand { get; init; }
public ObservableCollection<DataHolder> Datas { get; init; } = new(); 
public bool IsDatasEditable { get; set; }
...
public MainWindow()
{
    DatasViewSource.Source = Datas;
    RemoveCommand = new RemoveCommand(Datas);
    ...
}

В словаре ресурсов MainWindow.xaml:

<Window.Resources>
    <ResourceDictionary>
        ...
        <l:BindingProxy x:Key="RemoveCommand" Value="{Binding RemoveCommand}"/>
        <l:BindingProxy x:Key="IsEditable" Value="{Binding IsDatasEditable}"/>
        ...
    </ResourceDictionary>
</Window.Resources>

В шаблоне DataGrid в MainWindow.xaml:

<DataGrid ItemsSource="{Binding DatasViewSource.View}" AutoGenerateColumns="False" Margin="10,10,10,10" 
                          CanUserAddRows="False" x:Name="DataGrid">
    <DataGrid.Resources>
        <DataTemplate x:Key="Actions">
            <StackPanel Orientation="Horizontal">
                <Button Content="➖" Command="{Binding Value, Source={StaticResource RemoveCommand}}" 
                        ToolTip="Remove row">
                    <Button.CommandParameter>
                         <MultiBinding Converter="{l:ParameterizedResource DataConverter}" 
                                       ConverterParameter="RemoveCommandCanExecute">
                             <Binding Path="."/>
                             <Binding Path="Value" Source="{StaticResource IsEditable}"/>
                         </MultiBinding>
                    </Button.CommandParameter>
                </Button>
            </StackPanel>
        </DataTemplate>
    </DataGrid.Resources>
    <DataGrid.Columns>
        ...
        <DataGridTemplateColumn Header="Actions" CellTemplate="{StaticResource Actions}"/>
    </DataGrid.Columns>
</DataGrid>

Здесь мы в каждом ряду размещаем кнопку «Удалить», которая связана с командой RemoveCommand, которая является свойством окна, а в качестве параметра передаём мультипривязку на текущий элемент коллекции и свойство окна, показывающее, является ли коллекция в данный момент редактируемой.

Пример 2.

Использование в качестве ячейки для передачи какого-то текущего состояния или объекта в разметку и из неё. Например, в демо на вкладке «Demo2» в таблице каждое поле представляет собой не только значение, но и текущий тип данных. Чтобы конвертер мог узнать, как конвертировать строку и значение поля при редактировании ячейки, вводится ресурс окна:

<Window.Resources>
    <ResourceDictionary>
        ...
        <l:BindingProxy x:Key="CurrentEditedItem"/>
        ...
    </ResourceDictionary>
</Window.Resources>

В MainWindow.xaml.cs этот ресурс передаётся конвертеру, у которого для этого предусмотрено свойство CurrentEditedItem:

public MainWindow()
{
    ...
    InitializeComponent();
    (FindResource("DataConverter") as DataConverter)!.CurrentEditedItem = 
        FindResource("CurrentEditedItem") as BindingProxy;
}

При включении редактирования ячейки вызывается DataTemplateSelector, который знает, что в данный момент редактируется и помещает ссылку в ресурс CurrentEditedItem. В TableDataTemplateSelector.cs:

public string FieldName { get; set; } = null!;
...
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
    if (item is DataHolder row 
        && typeof(DataHolder).GetProperty(FieldName.ToString()!)?.GetValue(row) is FieldHolder field)
    {
        if(container is ContentPresenter cp && cp.FindResource("CurrentEditedItem") is BindingProxy bp)
        {
            bp.Value = field;
        }
        return EditValue;
    }
    return base.SelectTemplate(item, container);
}

Пример 3

Этот вариант использования в демо отсутствует, но может оказаться полезным. Если применяется внедрение зависимости, то через ресурс BindingProxy можно передавать в разметку IServiceProvider.

В MainWindow.xaml:

<Window.Resources>
    <ResourceDictionary>
        ...
        <l:BindingProxy x:Key="ServiceProvider"/>
        ...
    </ResourceDictionary>
</Window.Resources>

В MainWindow.xaml.cs:

public MainWindow(IServiceProvider services)
{
    ...
    InitializeComponent();
    (FindResource("ServiceProvider") as BindingProxy)!.Value = services;
}

Где-то в расширении разметки:

public override object ProvideValue(IServiceProvider serviceProvider)
{
    IServiceProvider hostServiceProvider = 
            ((FindResource("ServiceProvider") as BindingProxy)!.Value as IServiceProvider)!;
    ....
}

Очевидно, лишнего IServiceProvider здесь нет, так как аргумент метода ProvideValue не предоставляет те сервисы, которые конфигурируются перед созданием и запуском Host.

Пример 4

Этот вариант использования в демо отсутствует, но может оказаться полезным. Здесь BindingProxy используется для передачи параметра или даже массива параметров в конвертер: В MainWindow.xaml:

...
<Window.Resources>
    <ResourceDictionary>
        ...
        <l:BindingProxy x:Key="BoundPropertyResourceKey" Name="BoundPropertyParameterName" 
           Value={Binding BoundProperty}/>
        ...
    </ResourceDictionary>
</Window.Resources>
...
<TextBlock ...>
    <TextBlock.Text>
        <Binding Path="SomePath" Converter="{local:SomeConverter}">
            <Binding.ConverterParameter>
                <x:Array Type="l:BindingProxy">
                    <!-- Параметр, по которому выбираем способ обработки -->
                    <l:BindingProxy Value="SelectorParameter"/> 
                    <!-- Параметр, содержащий значение BoundProperty -->
                    <StaticResource ResourceKey="BoundPropertyResourceKey"/> 
                    <!-- Параметр с логическим флагом,  -->
                    <l:BindingProxy Name="BooleanFlag" Value="True" Type="{x:Type sys:Boolean}"/>
                </x:Array>
            </Binding.ConverterParameter>
        </Binding>
    </TextBlock.Text>
</TextBlock>

В SomeConverter.cs:

...
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    if(parameter is BindingProxy[] parameters && parameters.Length > 0)
    {
        switch(parameters[0].Value)
        {
            case "SelectorParameter":
                if(parameters.Where(p => "BooleanFlag".Equals(p.Name)).Select(p => ).FirstValueOrDefault()
                break;
            ....
        }
        Dictionary<string, object?> parameters = TakeParameters(parameter);
        object selector = parameters[string.Empty]!;
        return Convert(value, targetType, selector, parameters, culture);
    }
}
...

Вероятно, есть ещё варианты.

Раньше: (XamlServiceProviderCatcher) Начало:(Обзор) Дальше:(BindingProxyMarkup)

⚠️ **GitHub.com Fallback** ⚠️