|
|
Comments and Discussions
|
|
 |

|
Hi,
Thank you for your kind words.
You are right, it really doesn't clearly explain what to do if you move the resource files to a different folder. As you have probably found you need to set the Custom Tool Namespace on the resources files to correct this. The sample code I does show an example of this though, where the resource files are all in the Cultures folder.
I'm not totally sure what you mean about the Combobox handler - do you mean that the binding needs to have been added before setting the default selected value? I would be interested to see an example of this, a code snippet here would be fine.
Regards,
Andrew
|
|
|
|

|
Just to say that I managed to make it work on WPF with VS2010 SP1 and Windows 7. There where some tricky parts though like having to set the resources namespace and using the event handler to detect changes and doing nothing with the handler. Great help and thank you for a great post (even after three years)
|
|
|
|

|
I have runtime errors with the binding. May you send me your solution?
|
|
|
|

|
I am not sure what sort of problem you seem to be having. Does the sample code work ok? If so then there might be a typo - make sure the Binding matches the string you are trying to bind to, it must match exactly (uppercase characters too). Visual Studio should give you a pointer as to which Binding is affected.
If you are still having problems perhaps you could post some code snippets here - the Binding string, the resource name and any other description about how you are using this code would be helpful.
|
|
|
|

|
Hi, thanks for your answer. The sample works great.
App.Xaml
<Application x:Class="MultiLangTestNeu.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
xmlns:culture="clr-namespace:MultiLangTestNeu.Cultures"
xmlns:local="clr-namespace:MultiLangTestNeu"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ResourceDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
ResourceDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cultures="clr-namespace:MultiLangTestNeu.Cultures"
xmlns:properties="clr-namespace:MultiLangTestNeu.Properties">
<ObjectDataProvider x:Key="res" ObjectType="{x:Type cultures:CultureResources}" MethodName="GetResourceInstance"/>
<!-- CultureResources ODP provides access to list of currently available cultures -->
<ObjectDataProvider x:Key="CultureResourcesDS" ObjectType="{x:Type cultures:CultureResources}"/>
</ResourceDictionary>
MainWindow.xaml
<Window x:Class="MultiLangTestNeu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:MultiLangTestNeu.Properties"
xmlns:cultures="clr-namespace:MultiLangTestNeu.Cultures"
xmlns:projekt="clr-namespace:MultiLangTestNeu"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox Name="cbLanguages" Height="25" Width="100">
<ComboBoxItem Content="Deutsch" IsSelected="True" />
<ComboBoxItem Content="Englisch" />
</ComboBox>
<Label x:Name="labelCultureName" Content="{Binding Path=land, Source={StaticResource res}}"/>
</Grid>
</Window>
The Error is here (I think):
CultureResources.cs
private static ObjectDataProvider m_provider;
public static ObjectDataProvider ResourceProvider
{
get
{
if (m_provider == null)
m_provider = (ObjectDataProvider)App.Current.FindResource("res");
return m_provider;
}
}
If I debug the code "App.Current" has no resource element at this time.
|
|
|
|

|
I got it
My Error was in the CultureInfo. I have added "en-EN" instead of "en-GB" and nothing worked.
|
|
|
|

|
Good thinking, that would have confused me too.
The full list of culture names is available at msdn[^] but you've probably already seen it.
|
|
|
|

|
Works like a charm. Thanks
|
|
|
|

|
Hi Andrew,
Thanks for publishing this solution, I really need it. Just 1 question, I read in this article that there's no need to change from "internal" to "public" what's inside of Resources.Designer.cs. But the solution I downloaded has them all "public" and has a comment that says that it was generated with "ResXFileCodeGeneratorEx".
So, I would like to know if this is the most updated solution and I understood something wrong, or if this is an old solution. If that's the case, could you please let me download the most recent one that matches your article and doesn't need to change the "internal" in that Designer file?
Thank you very much.
|
|
|
|

|
Hi,
When I was talking about the need to change internal to public it is talking about the class itself - the default ResXFileCodeGenerator creates an internal class in Resources.designer.cs. This solution would not work if the resources class was marked internal. Using the ResXFileCodeGeneratorEx is what fixes this, it makes sure the designer class that is generated is public like we need. Sorry if it is a little confusing in the article, what the solution does is change the default behaviour so that the resources can now be used.
You could test this if you like, on one of the resx files change the property with ResXFileCodeGeneratorEx in it to ResXFileCodeGenerator and right click on the resx file, choosing "Run Custom Tool". This will rebuild the designer class, and you would see that it is now internal instead of public. If you went and ran the solution now it would not work, can't remember the error exactly off the top of my head. Change back to ResXFileCodeGeneratorEx and it will work again after a rebuild.
Does that make a little more sense?
|
|
|
|

|
Thanks for your quick answer! Yes, I understood. You helped me a lot! Thanks!
|
|
|
|

|
Hi,
When accessed an image from the resource file using this method, it always shows blank!
<Image Source="{Binding Path=New, Source={StaticResource Resources}}" Height="100" Width="100"></Image>
is there anything I'm doing wrong?
PS: the images are embedded in the resource file.
thanks
modified on Friday, April 9, 2010 2:30 AM
|
|
|
|

|
Hi,
The problem here is that you can't set a System.Drawing.Bitmap as the source of a WPF Image. You should see binding errors in the debug output when you try this that look something like the following:
System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property.;
Value='System.Drawing.Bitmap' BindingExpression:Path=TestImage; DataItem='Resources' (HashCode=50833863);
target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')
Most of what that is telling you is that the target property (Source) is expecting an ImageSource and you gave it a Bitmap.
When using images usually you set the Source property to a Uri, and if I remember correctly behind the scenes wpf is loading that up as a BitmapSource for you.
My quick way around getting localized images working is to localize the image path, as I did in my sample. If you want to be able to embed the image in the resx file, then you need to convert the bitmap to load it as a bitmapsource.
I think this can be done with an IValueConverter on the binding, which will take the bitmap object and load it as a bitmap source. A likely way of doing this would be to use System.Windows.Interop.CreateBitmapSourceFromHBitmap() in the Convert method.
hth
Andrew
|
|
|
|
|

|
Hi,
I'm not sure why, but the text format of the resx files was somehow incorrect. Didn't look too much into why exactly, but deleting & recreating the resx files worked fine. I think the text format had been changed in some way that resulted in a compiled resources dll that then didn't work correctly, and it always resulted in the default value when doing resource lookups. Maybe you copied the base resx file using windows explorer to created the finnish resx file? I have always created and copied my resx files from within the visual studio solution explorer, and that works in this case too.
hth
Andrew
|
|
|
|

|
Hello
I have following resource string in resource file
InformationMessage=Device with name {0} is operated and current status is {1}
here paratmeter will be passed for 0 and 1 parameter.
Now i want to use "InformationMessage" value to label contorl as well as in Messagebox (i.e MessageBox.show(string.format(InformationMessage,device,status)
how do i use in xmal and code behind (cs) file.
regards,
|
|
|
|

|
Hi,
I have two ideas on how you can do this. This code assumes a similar structure to the example app, and that you have added "InformationMessage" to the resources as you describe, and that the local namespace is defined as xmlns:WPFLocalize="clr-namespace:WPFLocalize" in the window xaml header. Note that the code has very little error checking in it for brevity.
1)
This code could for example be added to a window that has x:Name="mainwindow" and has two DependencyProperties, "MessageName" and "MessageStatus".
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource formattedMultiTextConverter}">
<Binding Path="InformationMessage" Source="{StaticResource Resources}"/>
<Binding Path="MessageName" ElementName="mainwindow"/>
<Binding Path="MessageStatus" ElementName="mainwindow"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
And add this MultiValueConverter to a class in your project:
public class FormattedMultiTextConverter : IMultiValueConverter
{
public object Convert(object[] value, Type typeTarget,
object param, CultureInfo culture)
{
if (value.Length > 1)
{
object[] args = new object[value.Length - 1];
Array.Copy(value, 1, args, 0, value.Length - 1);
return String.Format((string)value[0], args);
}
else return null;
}
public object[] ConvertBack(object value, Type[] typeTarget, object param, CultureInfo culture)
{
return null;
}
}
Which is included in the window by adding (assuming it is in the WPFLocalize namespace)
<WPFLocalize:FormattedMultiTextConverter x:Key="formattedMultiTextConverter"/>
to the Window resources.
This adds a TextBlock to the window with the placeholders in InformationMessage replaced with the current values of MessageName and MessageStatus. An advantage to this solution is that any updates to the MessageName or MessageStatus should trigger an update to the value of the TextBlock. The bindings allow this to be very flexbile - you can bind these to just about anything you can access.
2)
To the window xaml header add
xmlns:mscor="clr-namespace:System;assembly=mscorlib"
To the window body add
<TextBlock>
<TextBlock.Text>
<Binding Path="FormattedTextExample" Source="{StaticResource Resources}" Converter="{StaticResource formattedTextConverter}">
<Binding.ConverterParameter>
<x:Array Type="mscor:Object">
<mscor:Int32>message_name</mscor:Int32>
<mscor:Int32>message_status</mscor:Int32>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</TextBlock.Text>
</TextBlock>
And add this ValueConverter to a class in your project:
public class FormattedTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return String.Format((string)value, (object[])parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Which is included in the window by adding (assuming it is in the WPFLocalize namespace)
<WPFLocalize:FormattedTextConverter x:Key="formattedTextConverter"/>
to the Window resources.
This is a simpler solution, but note that the values you set in the array in xaml are now fixed. This might be suitable if you just want to set an initial value to start with, but later only ever update it from code.
Updating from code behind - if you are using the ResXCodeFileGenerator to generate the resx files as described in the article, note that it automatically generates helper functions so you don't have to call string.format(), so for your example string you should be able to call Properties.Resources.InformationMessageFormat("name", "status") to generate the formatted string.
Hope that helps, let me know if there are any big mistakes in there, I haven't tested this too much.
|
|
|
|

|
Thanks for reply.
I will check on this.
On XAML how i can use to assign value to lable?
Label x:Name="mylable" Content="{Binding Path=InformationMessage arg1 arg2 ,Source={StaticResource Resources}}"
Overall I have two project
1)Window Form App
2)WPF user control
I m using wpf user control on Window Form,so if user change language on win form,it should reflect on wpf contorl also.
thanks again.
|
|
|
|

|
You could use one of the methods described above to bind a label to a resource string, it would be very similar to the textblock in the example.
As for which method would be best, number 2 would probably be easier if you only need to set an initial value when you first assign it in xaml.
|
|
|
|

|
Appriciate your help.
I created wpf contorl which contains other wpf controls. It is hosted on window form .
On design time i m getting error when i open window form in VS2008 (.net 3.5).
On user contorl xaml i have put follwoing lines
CultureManager is static clas which has only one method (vb.net)
Public Shared Function GetResource() As My.Resources.Resources
Return New My.Resources.Resources
End Function
i m not sure in constructor of wpf contorl what kind of check i can put...i think in design mode CultureManager can not be present and can not get instance of "CultureManager"
|
|
|
|

|
Sorry for the late reply.
This is not a scenario that I had originally anticipated to try to handle runtime culture updating. Is your Form already able to do runtime updating of localized strings when you change the culture? If not I would think there is no need to use this approach for only the WPF control, and using x:static to bind to the resource strings would be sufficient (described in step #1 - Localizing WPF Applications using Locbaml)
Otherwise I'd suggest that adding the ObjectDataProvider to the UserControl resources would be sufficient to get this working as you describe. You might still need the design-time support for usercontrols described in the article too (but you'd add the dictionary to the UserControl resources instead of the application resources). I have a suspicion that even if you got this working for one usercontrol, it might not work if you have more than one on the same form, and you'd probably need to explicitly update each control when the culture is changed. This approach for runtime updating is based on being able to add an ODP to a location that all controls can see (the Application Resources), which is not available when a control is hosted in a Windows Form and not a WPF Window.
I'm not sure if I'll get the chance to investigate this further anytime soon, sorry.
|
|
|
|

|
Hi,
Thanks for the nice article.
I am facing some issues when i tried to use this code.In my project(Done in VS 2008 WPF) i am using resourcedictionary which has all the styles and control templates for the controls.I have created resx files under folder called Cultures in my project.Please have a look into the following code.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:r="clr-namespace:KioskClientApplication.My.Resources" >
<ControlTemplate x:Key="ButtonPickupStyle" TargetType="{x:Type Button}">
<Button Height="140" Width="180">
<Button.Content>
<Grid x:Name="grdPickup">
<Grid.RowDefinitions>
<RowDefinition Height="60"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"></ColumnDefinition>
<ColumnDefinition Width="130"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Source="../Images/PickupOrParkImage.jpg" Stretch="Fill" Grid.Column="1"
HorizontalAlignment="Center" VerticalAlignment="Top" Grid.Row="0"></Image>
<TextBlock x:Name="txtPickup" FontSize="20" FontWeight="Bold" Grid.Row="2"
HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1"
Text="{x:Static r:Strings.ButtonPickupText}"
></TextBlock>
<!--Text="{Binding ButtonPickupText, Source={StaticResource r}}"-->
</Grid>
</Button.Content>
</Button>
</ControlTemplate>
The "txtPickup" textblock text will just load the value from resource file and if am chnaging the culture at runtime(say i have combo which has different languages and if selecting some other language) then the txtPickup is not chnaging to the other language.I tried adding the below code instead of that
Text="{Binding ButtonPickupText, Source={StaticResource r}}"
but this is throwing exception saying that "cannot find resource file named {r}.
I dont understand what is wrong in this code.
Can you please help me?
|
|
|
|

|
Hi,
In the sample code there is an example for using this in custom controls. The example isn't loading the control style form a resource dictionary (the style is in App.xaml), but the usage is similar. You can see that in ResourceDictionary1.xaml the ObjectDataProvider has been added and given the key name "Resources". This is the resource that you need to bind to in your controltemplate.
Also note that ResourceDictionary1.xaml has been merged in App.xaml, you would need to make sure that the location that the ObjectDataProvider has been added is earlier than the ResourceDictionary that contains your controltemplate, otherwise I think you get xamlparseexceptions.
So if for example you merge ResourceDictionary1.xaml in App.xaml, perhaps then merging the ResourceDictionary containing your control template, you would then bind to the string with
Text="{Binding Path=ButtonPickupText, Source={StaticResource Resources}}"
In your code you have slightly confused the binding syntax, "r" is the namespace of your resources class (which you can use to access string statically), but for dynamic updating using this method you need to bind to a resource object, which we use to trigger updates.
hth,
Andrew
|
|
|
|

|
Thanks a lot for your answer.
It is working as expected now.I merged resourcedictionary1 before styles.It started working perfectly.
Thank you once again.
Great Article.
|
|
|
|

|
Hi Andrew,
Your download example shows how to localize runtime by using ObjectDataProvider in main assembly.
Do you think using ObjectDataProvider in separated assembly also will work for localization?
We create a saperated assembly which include some Usercontrol componenets.
- Add a "ManageUsersResource" resource file in this assembly.
- Add the similar CultureResource class in this assembly as well.
public class CultureResource
{
public static ManageUsersResource GetResourceInstanceManageUsers()
{
return new ManageUsersResource();
}
private static ObjectDataProvider m_rProviderManageUsers;
public static ObjectDataProvider ResourcesProviderManageUsers
{
get
{
if (m_rProviderManageUsers == null)
{
m_rProviderManageUsers = (ObjectDataProvider)System.Windows.Application.Current.FindResource("ResourcesManageUsers");
}
return m_rProviderManageUsers;
}
}
public static void ChangeCulture(CultureInfo culture)
{
//this setting for resource file under properties folder
Properties.Resources.Culture = culture;
// udpate UI
ResourcesProviderManageUsers.Refresh();
}
}
- And DictionaryManageUsers.xaml to make the DataObjectProvider to point to the CultureResource.ManageUsersResource.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prop="clr-namespace:ManageUsers.Properties"
xmlns:cul="clr-namespace:ManageUsers.Cultures">
<ObjectDataProvider
x:Key="ResourcesManageUsers"
ObjectType="{x:Type cul:CultureResource}"
MethodName="GetResourceInstanceManageUsers"/>
</ResourceDictionary>
- In each UserControl.xaml, add the following section:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="/ManageUsers;component/DictionaryManageUsers.xaml" />
</ResourceDictionary.MergedDictionaries>
During run time switching language, the following code complaint that it can not find the resource file.
if (m_rProviderManageUsers == null)
{
m_rProviderManageUsers = (ObjectDataProvider)System.Windows.Application.Current.FindResource("ResourcesManageUsers");
Is there a way we can solve this problem?
do you think the way we implement for separated assembly is making sense?
Is there anothe better way to handle separated assembly for localization by using ObjectDataProvider?
Thx!
|
|
|
|

|
The reason this is failing is that the resource dictionary is being added to the usercontrol resources, not the application resources. One the reasons for doing this is that all controls from the satellite assembly need to be bound to the same instance of the objectdataprovider (for that satellite assembly) for dynamic culture switching to work.
One of the catches is figuring out the best place to add the resourcedictionary to the application resources, it needs to be done before the first instance of a control that needs it is created.
A while ago I played with a few ways of doing this hit a couple of potential limitations, you either need to:
- directly reference the resource dictionary from the main project, making this a compile-time choice and you have to update this when you add a new culture
- or have a convention for the location & naming of the resource dictionary within each satellite assembly, so that the main application can find it in each assembly without needing to add a direct reference to it.
For the first option it's typically easier to make some changes in the main application that adds the resource dictionary from the satellite assembly to the main application resources, you can then FindResource the object data provider for that assembly in order to do culture switching. All this could perhaps be done in the CultureResources type of class - perhaps add the satellite resource dictionaries and get the object data providers in the constructor.
I have most of a solution for the second option, it's a bit messy and I'm not quite sure it's a great way to solve this as it introduces a file naming convention for the resource dictionary in each assembly. If you're interested in being able to do this without adding fixed references into each satellite assembly I can look at sharing this, perhaps making it part of the article.
There are solutions that already provide the ability to localize controls in satellite assemblies, there are links in the comments below that point to some of the other projects out there - those using MarkupExtensions can make it easy to integrate in existing code.
hth
|
|
|
|

|
Hi Andrew,
We are more interested in the second way. Do you have example code to share with us?
we have investigated different ways of localization. Some does not work for Expression blend that well. Some does not work for Datatemplate or Setter. So far we found that the ObjectDataProvider way works most cases. The disadvantage of using ObjectDataProvider is that user need add a lot extra code to support this method.
We have done some for experiment.
In ResourceCulture class, we change the property "ResourcesProviderManageUsers" to be able to get/set
private static ObjectDataProvider m_rProviderManageUsers;
public static ObjectDataProvider ResourcesProviderManageUsers
{
get
{
//if (m_rProviderManageUsers == null)
//{
// m_rProviderManageUsers = (ObjectDataProvider)System.Windows.Application.Current.FindResource("ResourcesManageUsers");
//}
return m_rProviderManageUsers;
}
set
{
m_rProviderManageUsers = value;
}
}
then each UserControl constructor, we duplicate the following code:
if (CultureResource.ResourcesProviderManageUsers == null)
{
CultureResource.ResourcesProviderManageUsers = (ObjectDataProvider)this.Resources["ResourcesManageUsers"];
}
since the UserControl is able to get resource dictionary,
the modified code actually works. Just the implementation does not look pretty.
|
|
|
|

|
Andrew,
I think I've figured out how to make this all work for UserControls in separate assemblies, and also not need the #if DEBUG section in the code-behind to address the UserControls in Blend.
The problem was that the same ObjectDataProvider instance was not used by all of the UserControls that referenced the same Resources. So, construct the ObjectDataProvider in code.
The CultureResources class is made static since we don't need more than one instance, and its only piece of stored data is a single instance of the ObjectDataProvider we want, already attached to the Resources class.
public static class CultureResources
{
static CultureResources()
{
ObjectDataProviderInstance = new ObjectDataProvider();
ObjectDataProviderInstance.ObjectInstance = new Properties.Resources();
}
static ObjectDataProvider ObjectDataProviderInstance;
public static ObjectDataProvider ObjectDataProvider
{
get { return ObjectDataProviderInstance; }
}
}
Sounds simple enough but then there's the chicken-and-egg problem. The ObjectDataProvider needs to be in the UserControl's ResourceDictionary when the control's InitializeComponent() is called, but that ResourceDictionary itself doesn't exist until the InitializeComponent(). So there's no way to put the ObjectDataProvider into the ResourceDictionary, not to mention that the Source={StaticResource Resources} requires the ObjectDataProvider be present at compile time with the x:Key="Resources".
So don't lookup the ObjectDataProvider by resource name!
Change the {Binding ...} so that the Source property is set from the static property above:
Source={x:Static cultures:CultureResources.ObjectDataProvider}
Since CultureResources is a static class with a static constructor, the ObjectDataProvider will be created before it is required. And all references to it will be to the same instance of ObjectDataProvider. Also, there is no lookup of the ObjectDataProvider in the ResourceDictionary, so no StaticResource compile time complaints, or issues with Design Mode.
Further, if you have access from the CultureResources class to the source of the Culture changes and it raises an event on the change, you can centralize the update of the Resources.Culture and calling .Refresh() on the ObjectDataProvider. (In our case they're managed in a singleton class, so the change events are accessible.)
public static class CultureResources
{
static CultureResources()
{
ObjectDataProviderInstance = new ObjectDataProvider();
ObjectDataProviderInstance.ObjectInstance = new Properties.Resources();
LocalizationManager.Instance.CultureChanged +=
new System.EventHandler<CultureChangedEventArgs>(LocalizationManager_CultureChanged);
}
static void LocalizationManager_CultureChanged(object sender, CultureChangedEventArgs e)
{
Properties.Resources.Culture = e.NewCultureInfo;
ObjectDataProvider.Refresh();
}
static ObjectDataProvider ObjectDataProviderInstance;
public static ObjectDataProvider ObjectDataProvider
{
get { return ObjectDataProviderInstance; }
}
}
So far, it has passed all of our informal testing.
|
|
|
|

|
Yes, you've found the issue there - making sure the ODP is added to the resources before any of the controls in the satellite library needs it, but also that all the controls need to be referencing the same instance, and you then need to use that same instance to update the culture.
The first method I described before was to directly get the ODP, where you have eg. a CultureResourceDictionary.xaml in the satellite library (which I called "CustomControlLibrary" in this case):
<ObjectDataProvider x:Key="CustomControlLibrary.Resources" ObjectType="{x:Type cultures:CultureResources}" MethodName="GetResourceInstance">
<ObjectDataProvider.MethodParameters>
-->
<x:Static Member="global:CultureInfo.CurrentCulture"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
And the CultureResources.cs class in the satellite library is then a bit different to that in the main project, it only needs the following:
public Properties.Resources GetResourceInstance(CultureInfo culture)
{
if (culture != null) Properties.Resources.Culture = culture;
return new Properties.Resources();
}
private static ObjectDataProvider m_provider;
public static ObjectDataProvider ResourceProvider
{
get
{
if (m_provider == null)
{
string assemblyname = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location);
m_provider = (ObjectDataProvider)Application.Current.FindResource(assemblyname + ".Resources");
}
return m_provider;
}
}
You can then directly include the resource dictionary in your main project App.xaml because we happen to know where to find the satellite library resource dictionary:
<ResourceDictionary Source="pack://application:,,,/CustomControlLibrary;Component/Cultures/CultureResourceDictionary.xaml"/>
And to the ChangeCulture method in the main CultureResources class, you can then just add:
CustomControlLibrary.Cultures.CultureResources.ResourceProvider.MethodParameters[0] = culture; Which will update the culture for all controls that you have included from the satellite library.
Right, so after all that - this is really just an alternative to your idea above, making sure the ODP is added before any user controls try to bind to it. You can see that knowledge of the location of the xaml file within the satellite library is required, as well as the satellite CultureResources class.
My first guess on how to do this without needing to directly reference code in the satellite assembly is similar, but still requires the resource dictionary to be in the same location - once I grab that out of the satellite library and add it to the application resources, I do a FindResource to get the initial ODP instance for this satellite library - which is then used later to do the culture updating for each satellite assembly. This also takes advantage of the naming convention I included above - the ODP has a key name like ".Resources" to make it easy to find later.
So it's up to you - this is pretty similar to what you are working with by using a static ctor, and the way I see it the example above requires pretty much the same restrictions on naming & location as you would need if implementing the next idea in the above paragraph.
I'm not sure about your LocalizationManager CultureChanged event - is that a class in a library that is then referenced by both the main exe and the satellite assembly? I haven't really looked at ways where the ODP solution in this article could be wrapped up in a library rather than including code in each assembly, first guess is that it might then be smarter to look at a markupextension type method to make it work.
Maybe you can use your CultureChanged event to avoid the need to directly update the ODP as I described above, that might make for a nicer solution.
hth,
Andrew
|
|
|
|

|
It turns out that there is an easier way to reference the ObjectDataProvider using StaticResource lookup.
In the top-level UserControl (or Window, Page, etc.) element, add to the element's resources:
<UserControl.Resources>
<x:Static x:Key="ResourceProvider"
Member="cultures:CultureResources.ObjectDataProvider" />
<!---->
</UserControl.Resources>
Then to actually get the localized resource value, the Source property in the {Binding ...} markup extension is just the simple:
Source={StaticResource ResourceProvider}
So a basic TextBlock becomes:
<TextBlock Text="{Binding Path=TextResourceNameHere, Source={StaticResource ResourceProvider}}" />
Which is much simpler, easier to read, and more normal-looking XAML.
|
|
|
|

|
An annoying problem is that although this works at run-time and in Expression Blend, it fails in the VS2008 design view. The error is:
Property 'Resources' does not support values of type 'ObjectDataProvider'.
Grrrrr!
|
|
|
|

|
Matt's solutions seemed to work for me. However, it doesn't work for templates. So the following code shows the default "Hello" on the button, but will not update when the culture changes, even when calling LocalizedControls...Refresh(). The following code is in Themes/Splitbutton.xaml in the LocalizedControls project I made, and then SplitButton is referenced in my main app. Does anyone know of a workaround?
This is inside a <Style>, <Setter>, <Setter.Value>, <ControlTemplate>
<TextBlock x:Name="PART_Label_Content" Text="{Binding Path=Hello, Source={x:Static cultures:CultureResources.ObjectDataProvider}}" Style="{TemplateBinding TextStyle}">
</TextBlock>
|
|
|
|

|
Been a while since I've looked at this - great to see people are still reading it.
As mentioned in this thread I did play around with a few ideas for extending this to work with satellite libraries. After adding the one described above I also made one that is a little bit simpler, but still requires naming conventions, to add code to the satellite library and also to force-update the culture on the satellite library when changed in the main application.
It's about this point where I figured some of the solutions around that use Markup Extensions to be a tidier way to handle this, where you generally only need to add a reference and to properly construct the bindings, which are usually still very readable. Some of these are linked in other comments.
I'm not sure I can be much help with your problem directly, since it seems like it's getting the default string ok, but it looks like it's not binding to the correct instance of the ObjectDataProvider from your satellite library. Maybe a spelling mistake somewhere? Depending on how you are merging the SplitButton.xaml file, maybe it ends up binding to the ODP in the main project if it also has a 'cultures' xmlns defined and also has a CultureResources class? Maybe try renaming the 'cultures' xmlns definition?
|
|
|
|

|
Thanks for the reply. Also thanks for the article, it was very helpful. I really like this solution, so I made this test project to test if it will be compatible with how our real project is set up. So in this test project, I've got a bunch of C# projects and references floating around, simulating how the real project is built out. With all the projects and references, and it being a Friday, I'm embarrassed to say that I got confused. Clear head on a Monday morning and I've figured it out.
So in my main app, in the CultureResources.cs file, under the ChangeCulture function, I have
if (pSupportedCultures.Contains(culture))
{
Properties.Resources.Culture = culture;
PluginTest.Properties.Resources.Culture = culture;
PluginTest3.Properties.Resources.Culture = culture;
ResourceProvider.Refresh();
PluginTest.CultureResources.ResourceProvider.Refresh();
PluginTest3.Cultures.CultureResources.ResourceProvider.Refresh();
PluginTest3.Cultures.CultureResources_Static.ObjectDataProvider.Refresh();
LocalizedControls.Cultures.CultureResources.ObjectDataProvider.Refresh();
}
Here, I not only set the culture for the main app, but the culture for each of the projects that are included. It doesn't really matter what else is going on in here, it's just that with all of it going on, I am calling update for LocalizedControls, I'm just not setting the culture for LocalizedControls (should be in the first block)...
SO, once I had set the culture, it worked like a charm. Thanks!
|
|
|
|

|
Excellent, great to hear it was an easy solution in the end. Easy to overlook I'm sure.
|
|
|
|

|
Yeah I can't believe I overlooked it. Andrew, as long as I know you're still looking at this, the link to the Extended Strongly Typed Resource Generator in your article leads to the "right" page, but the download links didn't work, for me at least, I can't speak for anyone else. They both just downloaded 0KB files. However, when I googled it I got this page Extended Strongly Typed Resource Generator[^]. It's the same Code Project article with the same posted date and author, but on this page the download links work. Must have been Code Project moving pages around.
|
|
|
|

|
Thanks for your wonderful article!
In our prototype project, we have followed the following link to practise the MVVM pattern
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx"
and we also follow the ObjectDataProvider way to practise the run time switch language.
we have encounter a problem.
if we binding the view and viewmodel in xaml file as:
<DataTemplate DataType="{x:Type vm:ManageAllUsersViewModel}">
<vw:ManageAllUsersView/>
compiling will get error "Could not create an instance of type 'ManageAllUsersView'
We add the "If Debug" section code in view constructor section, the debug compiling pass without problem. However, the release compiling still gets the same problem.
So we change the code to take out the debug checking as followiing and release compiling also pass.
Debug in the release build, everytime opening the view, it will go through the "MergedDictionaries.Add(dictionary)" two times, it looks like the "dictionary" key does not help for checking MergedDictionaries collection.
So the MergedDictionaries include duplicate items. However, running applicaiton seems no problem.
public ManageAllUsersView()
{
//#if DEBUG
//only perform the following fix if we are in the designer
// - the default ctor is not executed when editing the usercontrol,
// but is executed when usercontrol has been added to a window/page
// NB. The Visual Studio designer might return null for Application.Current
// http://msdn.microsoft.com/en-us/library/bb546934.aspx
//if (DesignerProperties.GetIsInDesignMode(this) && Application.Current != null)
if (Application.Current != null)
{
Uri resourceLocater =
new System.Uri("/HDUIProtype1;component/Dictionary2.xaml",
UriKind.Relative);
ResourceDictionary dictionary =
(ResourceDictionary)Application.LoadComponent(resourceLocater);
//add the resourcedictionary containing our Resources ODP to
//App.Current (which is the Designer / Blend)
if (!Application.Current.Resources.MergedDictionaries.Contains(dictionary))
Application.Current.Resources.MergedDictionaries.Add(dictionary);
}
//#endif
InitializeComponent();
We know comment out debug check is not right thing to do.
Any suggestion for handling release build in our case?
Thanks!
|
|
|
|

|
Hi,
I see the problem with using Contains here now - it should only be used to see if the MergeDictionaries collection contains a specific resource key, not a ResourceDictionary instance. This doesn't raise an problem with the code as used in the article as while it will probably keep on adding a copy of the ResourceDictionary to the Designer's resources, this only happens at design-time. Strictly speaking a bug, but not too serious.
Something like the following should sort this out, the error seen in the release version is probably over having duplicate resource keys in the merged dictionary, something that might be being suppressed / ignored when running in debug - maybe only a warning in visual studio?
bool found = false;
foreach (ResourceDictionary dict in Application.Current.Resources.MergedDictionaries)
{
if (dict.Source == resourceLocator)
{
found = true;
break;
}
}
if (!found) Application.Current.Resources.MergedDictionaries.Add(dictionary);
If this doesn't work as-is, maybe you need to compare the relative or absolute Uri.
Andrew
|
|
|
|

|
Thanks for answering our question. Your information is helpful!
comparing (dict.Source == resourceLocator) does not work.
We tried the following code by only using ResourceDictionary file name as its source
since the LoadComponent will set dictionary.Source to null, it works:
if (Application.Current != null)
{
Uri resourceLocater =
new System.Uri("/HDUIProtype1;component/Dictionary2.xaml",
UriKind.Relative);
ResourceDictionary dictionary =
(ResourceDictionary)Application.LoadComponent(resourceLocater);
dictionary.Source = new System.Uri("Dictionary2.xaml",
UriKind.RelativeOrAbsolute);
//add the resourcedictionary containing our Resources ODP to
//App.Current (which is the Designer / Blend)
//if (!Application.Current.Resources.MergedDictionaries.Contains(dictionary))
// Application.Current.Resources.MergedDictionaries.Add(dictionary);
bool found = false;
foreach (ResourceDictionary dict in Application.Current.Resources.MergedDictionaries)
{
if (dict.Source == dictionary.Source)
{ found = true; break; }
}
if (!found)
Application.Current.Resources.MergedDictionaries.Add(dictionary);
}
modified on Thursday, June 25, 2009 3:01 PM
|
|
|
|

|
I like your approach to use ResX files for localizing WPF applications. As long the LocBAML tool is not a "production-ready" tool it isn't a real alternative.
If you don't need to change the language during runtime then you might use a more lightweight approach for localizing WPF applications with the "old" stable ResX files:
http://compositeextensions.codeplex.com/Thread/View.aspx?ThreadId=52910
_
|
|
|
|

|
Hi,
Thanks for the comment. If you are interested in the many different approaches that you can take for localization, you might also consider those using custom markup extensions, they also provide dynamic culture switching while still making use of the benefits of resx files.
Yes, any method that provides dynamic updating will add an extra cost, whether it be in extra objects/references for each localized term, or the need to query for each localized element when you load a page / control. The possible benefit with the approach described in this article is that it is querying a static object and not re-reading the resx file each time, but methods using a markup extension are similar.
I guess it comes down to whether you want to offer the ability to switch cultures at runtime, or whether it's an install-time choice or that a restart with the new culture is acceptable.
A couple of projects worth looking at that you may / may not yet have come across:
http://www.codeplex.com/WPFLocalizeExtension[^]
WPF Localization Using RESX Files[^]
regards,
Andrew
|
|
|
|

|
Hi
I saw the way to add a new culture so I copied Resources.resx => Resources.he-HE.resx.
When I build/rebuild I don't get an extra directory or so on.
This is one of my first project I look into in C# and in the Microsoft Side of the development tools so I probebly mis interpet somthing or I'm missing somthing that is very obvious.
Pleas give me some clues.
ps. I have added (later) an he-HE directory and I have added a new Image also to the project to be used in the hebrew and this still did't help and did't work.
Removed the he-HE it did't help.
jvdn
|
|
|
|
|

|
I have in code strings like "Cancel" which are part of certain
command binding codes,basically strings.Can you show how
I can get something via code.
public static ButtonDropDownCommand x=
new ButtonDropDownCommand("NEED LOCALIZED CANCEL HERE", "Cancel", typeof(XXX.XXX.XXX));
how can I get from the resource ?
normally via xaml Header="{Binding Path=tTheme, Source={StaticResource Resources}}"
but how by code?
Caveat Emptor.
|
|
|
|

|
Hi,
I'm guessing that because you're using a ButtonDropDownCommand that you are working with the DotNetBar Wpf-Ribbon? A look at the docs for that tells me that the first arg to that constructor is for the Header property of the command. This is a string property, so is fixed to whatever value you assign in the constructor and cannot be set as a binding.
One option I think will be to add a binding to the Header property of the ButtonDropDown you would be assigning this command to. I think the following should be similar to what will work here (or you might be able to set the binding in xaml on the ButtonDropDown Header property):
ButtonDropDown btnDropDown = new ButtonDropDown();
Binding b = new Binding("btnDropDownCancel"); //this path is the name of the string resource you are binding to
b.Source = CultureResources.ResourceProvider;
btnDropDown.SetBinding(ButtonDropDown.HeaderProperty, b);
With any luck that will sort it out, but it depends on how you're using the commands I guess - if they're being assigned to buttons at run-time then the bindings to the localized strings would have to be updated at the same time.
hth
Andrew
|
|
|
|

|
Move strings from code to resx and translate their automatically. Try RGreatEx[^] free.
---
Best regards,
Alexander Nesterenko
Safe Develop Team
www.safedevelop.com
|
|
|
|

|
unfortunately not really free
"RGreatEx is only 35 Euro to keep."
|
|
|
|

|
Only today you could to purchase RGreatEx by $19 on the www.fevodiscount.com[^]
---
Best regards,
Alexander Nesterenko
Safe Develop Team
www.safedevelop.com
|
|
|
|

|
Hi,
Great article!
I tried to add a French resource file to the application, which was quite easy, as I just had to create a resource.fr.resx file (well done!).
Although this worked perfectly, I was a bit disappointed that the list of supported culture was still in English...
Here is how I fixed that:
- I made the list of supported culture an ObservableCollection, in CultureResources.cs
- I modified the method CultureResources.ChangeCulture as follow:
static bool IsChangingCulture = false;
public static void ChangeCulture(CultureInfo culture)
{
if (IsChangingCulture)
return;
IsChangingCulture = true;
try
{
if (pSupportedCultures.Contains(culture))
{
Thread.CurrentThread.CurrentUICulture = culture;
CultureInfo selectedCulture = CollectionViewSource.GetDefaultView(pSupportedCultures).CurrentItem as CultureInfo;
RefreshSupportedCultures();
CollectionViewSource.GetDefaultView(pSupportedCultures).MoveCurrentTo(selectedCulture);
Properties.Resources.Culture = culture;
ResourceProvider.Refresh();
}
else
Debug.WriteLine(string.Format("Culture [{0}] not available", culture));
}
finally
{
IsChangingCulture = false;
}
}
With this, the list of available cultures gets displayed properly in the newly selected language, and the current selection remains.
What do you think?
|
|
|
|

|
Hi, Thank you for posting about this, it's an interesting problem. One quick thing I did try was to look at the options on the CultureInfo class for displaying the name of the culture. The NativeName property allows us to display the culture name in it's native language, which seems like a reasonable option. If in the Window1.xaml file you add a datatemplate: <datatemplate x:key="LanguagesDataTemplate"> <textblock text="{Binding Path=NativeName}"/> </datatemplate> and to the cbLanguages ComboBox add: ItemTemplate="{StaticResource LanguagesDataTemplate}" then the NativeName will be used. I wonder if this is a suitable solution to this problem also? Then the user who wants to change to a language can see that option in that culture, and all other cultures are still displayed in their respective language?
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.
|
A WPF localization solution with runtime updating and design-time support in Visual Studio and Expression Blend
| Type | Article |
| Licence | CPOL |
| First Posted | 16 Jan 2008 |
| Views | 144,592 |
| Downloads | 1,606 |
| Bookmarked | 84 times |
|
|