WPF interaction command refer to local static resource for converter

Viewed 1057

I wish to create a "reusable" control item which has a behaviour attribute. As described in this blog

For the data binding the data has to be converted. The problem is; as StaticResource it can only see the top level dictionary (app), and dynamic resources don't work ("can only be used with a dependency property").

The simple (working) xaml (window):

<Window x:Class="testit.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Rectangle x:Name="blah" Fill="Yellow" Stroke="Black" Width="100" Height="100">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseEnter" >
                    <i:InvokeCommandAction Command="{Binding MouseEnterCommand}">
                        <i:InvokeCommandAction.CommandParameter>
                            <Binding Path="Name"
                                     Converter="{StaticResource SelectionConverter}"
                                     RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=Rectangle}" />
                        </i:InvokeCommandAction.CommandParameter>
                    </i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Rectangle>
        <TextBlock x:Name="namefield" Text="{Binding Data}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
    </Grid>
</Window>

app:

<Application x:Class="testit.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:testit"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <local:SelectionConverter x:Key="SelectionConverter" />
    </Application.Resources>
</Application>

And finally the viewmodel:

using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;

namespace testit
{
    class ViewModel : INotifyPropertyChanged
    {
        private string _data;
        private readonly DelegateCommand<string> _mouseEnterCommand = null;

        public ViewModel() {
            _data = "hello ";
            _mouseEnterCommand = new DelegateCommand<string>(
                (s) => {
                    var a = s ?? "world ";
                    Data += s;
                    return;
                });
        }

        public DelegateCommand<string> MouseEnterCommand => _mouseEnterCommand;

        public string Data {
            get { return _data; }
            set {
                if (value == _data) return;
                _data = value;
                OnPropertyChanged("Data");
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public class SelectionConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
            if (value == null) return "";
            char[] charArray = ((string)value).ToCharArray();
            Array.Reverse(charArray);
            return new string(charArray);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
            throw new NotImplementedException();
        }
    }
}

Now as said, this "works". However it doesn't do what I wish: it clutters the top level resource dictionary. I wish to remove the resource from app. and put it under the Grid. (Or whatever panel I use).

<Grid.Resources>
    <local:SelectionConverter x:Key="SelectionConverter" />
</Grid.Resources>

EDIT:If I add above line to the grid inside the window the following error is thrown on compilation (the line and position refer to the Converter="{StaticResource SelectionConverter}")

"'Provide value on 'System.Windows.StaticResourceExtension' threw an exception.' Line number '13' and line position '38'."

With the inner exception:

Cannot find resource named 'SelectionConverter'. Resource names are case sensitive.

For clarity, this is the modified window.xaml:

<Window x:Class="testit.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:testit"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Rectangle x:Name="blah" Fill="Yellow" Stroke="Black" Width="100" Height="100">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseEnter" >
                    <i:InvokeCommandAction Command="{Binding MouseEnterCommand}">
                        <i:InvokeCommandAction.CommandParameter>
                            <Binding Path="Name"
                                     Converter="{StaticResource SelectionConverter}"
                                     RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=Rectangle}" />
                        </i:InvokeCommandAction.CommandParameter>
                    </i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Rectangle>
        <TextBlock x:Name="namefield" Text="{Binding Data}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Grid.Resources>
            <local:SelectionConverter x:Key="SelectionConverter" />
        </Grid.Resources>
    </Grid>
</Window>
1 Answers

The StaticResource markup extension resolves the resource immediately, when ProvideValue is called:

Lookup behavior for that resource is analogous to load-time lookup, which will look for resources that were previously loaded from the markup of the current XAML page

If Grid.Resources is defined after the content of the Grid, those resources haven't been "previously loaded" when the content of the Grid is parsed.

So put the resources at the top of the grid, so anything in there is defined before you use it:

<Grid>
    <Grid.Resources>
        <local:SelectionConverter x:Key="SelectionConverter" />
    </Grid.Resources>

    <Rectangle x:Name="blah" Fill="Yellow" Stroke="Black" Width="100" Height="100">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseEnter" >
                <i:InvokeCommandAction Command="{Binding MouseEnterCommand}">
                    <i:InvokeCommandAction.CommandParameter>
                        <Binding Path="Name"
                                 Converter="{StaticResource SelectionConverter}"
                                 RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=Rectangle}" />
                    </i:InvokeCommandAction.CommandParameter>
                </i:InvokeCommandAction>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Rectangle>
    <TextBlock x:Name="namefield" Text="{Binding Data}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Grid>

If you were able to use DynamicResource, you'd be fine, but of course you can't, for reasons you discovered.

Related