Introduction
I was unable to user Binding
to connect to a Properties.Resources
value with an x:Static
Source
. Apparently using the x:Static
does work for other people, It is interesting because in C# this appears to be a singleton, but using XAML does not seem to work. It seemed like maybe this is a place to use a MarkupExtension
. Apparently using the x:Static
does work for other people, but despite requests for understanding why, I have been unable to determine how to make the x:Static
work for Resources
.
Since the original submittal of this article, I have significantly enhanced the ResourcesExtension
class
.
Implementation for Accessing Properties.Resources
The basic design of the MarkupExtension
for accessing a Properties.Resources
value is very straightforward. It has a few properties for the resource Key
, Converter
and ConverterParameter
, and the ProvideValue
method:
public class ResourcesExtension : MarkupExtension
{
readonly WeakReference _object;
public ResourcesExtension(string key) { Key = key; }
[ConstructorArgument("key")]
public string Key { get; set; }
public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var service = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (service == null) return null;
Debug.Assert(!string.IsNullOrEmpty(Key), "Resource name not provided");
var value = Properties.Resources.ResourceManager.GetObject(Key);
Debug.Assert(!string.IsNullOrEmpty(Key), $"Resource with name '{Key}' not found");
if (Converter != null)
{
value = Converter.Convert(value, ((DependencyProperty)service.TargetProperty).PropertyType,
ConverterParameter, GetCulture()
);
return value;
}
else
{
var type = ((DependencyProperty)service.TargetProperty).PropertyType;
TypeConverter converter = TypeDescriptor.GetConverter(type);
Debug.Assert(converter != null, $"Converter for Type of '{type}' not found");
return converter.ConvertFrom(value);
}
}
return CultureInfo.DefaultThreadCurrentUICulture;
}
There is, however, one serious problem, and that is that a resource are a string
value. That is fine for a lot of applications, but if it is to be bound to a bool
property such as IsEnabled
, or a Visibility
property, this will not work. There is information in the IServiceProvider
about target type, but only as a private
value, and so, unfortunately, the serviceProvider
argument is not particularly useful in figuring out what Type
the value should be.
One of the most important things I learned about that really made this class capaable was GetService
method. The GetService
method of the IServiceProvider
argument exposes all the information required to support use use of IValueConverter and IMultiValueConverter, which means it also provides the target Type.
First the value for the resource is aquired using the Properties.Resources.ResourceManager.GetObject
method. If a Converter
is speicifed, then the Converter
is used to return the value. Otherwise the object returned from the GetService
method is used to ther the PropertyType, which is used to get the TypeConverter
, which is used to convert the string
value (a resource must be a string
) to the proper target Type
.
Using MarkupExtension to Access Resources
Using this for a simple case where the string value can be used directly is really easy:
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center" IsEnabled="False"
Text="{local:Resources FlagTrue}" />
All that is needed is to define a namespace prefix to tell the XAML where to find the MarkupExtension
.
If need to convert the value for a property that does not accept a string
, it becomes a little more complex:
<CheckBox Grid.Row="3" HorizontalAlignment="Center"
VerticalAlignment="Center" IsChecked="{local:Resources FlagFalse, Type=sys:Boolean}"
Content="Resources False Boolean Test" />
Need to add the Type
definition, and have to have another namespace prefix to tell the XAML where to find the Type
, in this case the type bool
is in the System
namespace, so would have to include the following namespace prefix defintion:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Using the x:Static for Resources
It has been suggested that Resources
can be accessed directly using the x:Static
:
<TextBox Grid.Row="5"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsReadOnly="True"
Text="{x:Static resx:Resources.FlagFalse}" />
The namespace (xmlns
) for this binding is:
xmlns:resx="clr-namespace:ResourceMarkupExtensionSample.Properties"
This does not work for me:
ResourceMarkupExtensionSample.Properties.Resources.FlagFalse' StaticExtension value cannot be
resolved to an enumeration, static field, or static property.
Another problem is that automatic conversion to non-string
values does not work.
Implementation for Accessing Properties.Settings
This same concept can be used even better for accessing the Setting
file:
public class SettingsExtension : MarkupExtension
{
private string _key;
public SettingsExtension(string key)
{
_key = key;
}
[ConstructorArgument("key")]
public string Key
{
get { return _key; }
set { _key = value; }
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
Debug.Assert(!string.IsNullOrEmpty(Key), "Settings name not provided");
var value = Properties.Settings.Default.Properties[Key];
Debug.Assert(!string.IsNullOrEmpty(Key), $"Settings with name '{Key}' not found");
TypeConverter converter = TypeDescriptor.GetConverter(value.PropertyType);
Debug.Assert(converter != null, $"Converter for Type of '{value.PropertyType}' not found");
return converter.ConvertFrom(value.DefaultValue);
}
}
Each settings actually has information on the Type
, so can automatically convert to the desired Type
. There is commented code in the solution will prove this. Interestingly, the problem is only discovered when the applicaiton is run, under debug, it seems to work.
Because you can specify type, I did not feel the need to add the complexity that I did for Resources where type canno
Using MarkupExtension to Access Settitngs
Basically the same method is used to use this class
as used for the PropertiesExtension
:
<CheckBox Grid.Row="4" HorizontalAlignment="Center"
VerticalAlignment="Center" IsChecked="{local:Settings SettingsTrue}"
Content="Settings True Boolean Test" />
The Sample
This sample shows Binding
a TextBox
and a CheckBox
to a Resource
(which is always a string
) with the value true
and false
, and a Binding
Setting
to the bool
true
value. There is also commented out code that will for Binding
a TextBox
to a Resource
using x:Static
, and a CheckBox
. As stated above, I commented out the TextBox
because of a run time error. If that works for you, you can see the issue with binding a Resource
to the IsChecked
property of a CheckBox
also.
If you can Help Make this Better
When I created the code to handle the converter, I use the CultureInfo.DefaultThreadCurrentUICulture
to get the culture for the IValueConverter
. I am not sure this is the best way..
Conclusion
Although I would have liked being able to use the x:Static
to get values from the Resources
file, I actually like the simplicity of this better. Also, with the use of GetService
and casting the result to IProvideValueTarget
, a TypeConverter
can be used to covert to the string
value to the right Type
. For Resources, which are strictly string
values, this is a significant advantage.
I was not able to use the x:Static
, but apparently other people can. If x:Static
does not work on your project, at least there is an option.
History
- 05/25/2016: Initial version.
- 07/06/2016: Updated version that eliminates need to specify type for binding to resources.
- 07/15/2016: Updated version adds converter to binding to settings.