Search code examples
wpfdata-bindingresourcesdatatemplate

How can I set a Static/Dynamic resource from a binding value?


I want to add dynamic items with a datatemplate that contains a TextBlock control, but the text of the TextBlock control will be selected from a XAML ResourceDictionary. The staticresource name will be obtained based on the result of the binding value.

How can I do that? I'm trying something like this, but doesn't works.

 <DataTemplate x:Key="languageItemTemplate">
            <ContentControl>
                <StackPanel>                    
                     <TextBlock Text="{StaticResource  {Binding ResourceName}}"></TextBlock>    
                     <TextBlock Text="{DynamicResource  {Binding ResourceName}}"></TextBlock>                    
                </StackPanel>
            </ContentControl>
 </DataTemplate>

UPDATE Thanks to Tobias, the fist option of his answer works. But I need to instance the converter first to get it work. Which one is the best idea to do that?

In the application_startup method and use it for all the application or in the Window.Resources of the window I use the converter?

Maybe a merge of both and do that on the Application.Resources?

thanks for your answer.

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            LoadConverters();
        }
        private void LoadConverters()
        {
            foreach (var t in System.Reflection.Assembly.GetExecutingAssembly().GetTypes())
            {
                if (t.GetInterfaces().Any(i => i.Name == "IValueConverter"))
                {
                    Resources.Add(t.Name, Activator.CreateInstance(t));
                }
            }
        }

OR

<local:BindingResourceConverter x:Key="ResourceConverter"/>
<DataTemplate x:Key="languageItemTemplate">
            <ContentControl>
                <StackPanel>                    
                     <TextBlock Text="{Binding Name, Converter={StaticResource ResourceConverter }}" />                 
                </StackPanel>
            </ContentControl>
 </DataTemplate>


Solution

  • If the resource is an application level resource you could simply use a converter to convert from the resource name to the actual object like this:

    public class BindingResourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string resourceKey = value as string;
            if (!String.IsNullOrEmpty(resourceKey))
            {
                var resource = Application.Current.FindResource(resourceKey);             
                if (resource != null)
                {
                    return resource;
                }
            }
            return Binding.DoNothing;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    And use it like this:

    <TextBlock Text="{Binding ResourceKey, Converter={StaticResource ResourceConverter}}" />
    

    If the resource is in a local scope, we need a reference to the control to search its resources. You can get the resource name and the control by using an attached property:

    public class TextBlockHelper
    {
        public static readonly DependencyProperty TextResourceKeyProperty =
            DependencyProperty.RegisterAttached("TextResourceKey", typeof(string),
                typeof(TextBlockHelper), new PropertyMetadata(String.Empty, OnTextResourceKeyChanged));
    
        public static string GetTextResourceKey(DependencyObject obj)
        {
            return (string)obj.GetValue(TextResourceKeyProperty);
        }
    
        public static void SetTextResourceKey(DependencyObject obj, string value)
        {
            obj.SetValue(TextResourceKeyProperty, value);
        }
    
        private static void OnTextResourceKeyChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            string resourceKey = e.NewValue as string;
            if(d is TextBlock tb)
            {
                var r = tb.TryFindResource(resourceKey);
                if (r != null)
                {
                    tb.Text = r.ToString();
                }
            }
        }
    }
    

    And you can use it like this:

    <Grid>
        <Grid.Resources>
            <sys:String x:Key="SomeLocalResource">LocalResource</sys:String>
        </Grid.Resources>
        <TextBlock h:TextBlockHelper.TextResourceKey="{Binding ResourceKey}" />
    </Grid>