Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

MarkupExtension for Resources and Settings

Rate me:
Please Sign up or sign in to vote.
4.93/5 (11 votes)
25 May 2016CPOL4 min read 16.4K   98   12   7
Presents simple MarkupExtension for accessing the Properties.Resources and Properties.Settings files. Also, with Resources, which are always strings, conversion is supported.

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: 

C#
 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 <font face="Courier New">IServiceProvider</font> about target type, but only as a <font face="Courier New">private</font> value, and so, unfortunately, the <font face="Courier New">serviceProvider</font> argument is not particularly useful in figuring out what <font face="Courier New">Type</font> 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:

XML
<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:

XML
<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:

XML
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:

C#
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:

XML
<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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
QuestionWhy? Pin
SledgeHammer0126-May-16 14:28
SledgeHammer0126-May-16 14:28 
AnswerRe: Why? Because it does not work! Pin
Clifford Nelson1-Jun-16 6:09
Clifford Nelson1-Jun-16 6:09 
GeneralRe: Why? Because it does not work! Pin
SledgeHammer011-Jun-16 7:19
SledgeHammer011-Jun-16 7:19 
AnswerRe: Why? Because it does not work! Pin
Clifford Nelson1-Jun-16 7:33
Clifford Nelson1-Jun-16 7:33 
GeneralRe: Why? Because it does not work! Pin
SledgeHammer011-Jun-16 13:34
SledgeHammer011-Jun-16 13:34 
GeneralRe: Why? Because it does not work! Pin
Clifford Nelson2-Jun-16 5:01
Clifford Nelson2-Jun-16 5:01 
GeneralRe: Why? Because it does not work! Pin
SledgeHammer012-Jun-16 5:30
SledgeHammer012-Jun-16 5:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.