views:

101

answers:

1

Hi.

I'm building a program in WPF which must feature multi-language support, with the ability to switch language at run-time. My question concerns the image part of the localization.

I've built a solution which does not work the way I had hoped it would work and I would like some help fixing these problems. The code posted below is only a demonstration of the concept I'm trying to achieve. My real program has quite many pictures, so I want to avoid putting them all in a list, updating them one-by-one.

My idea is to name the images according to what language they belong to. The OriginalSource property (in lack of a better name) is formatted like "Koala.(lang).jpg", and the two images for English and French are called "Koala.en-GB.jpg" and "Koala.fr-FR.jpg".

My problem is that without the code which is commented at (1), the images will not be assigned a "real" Source (in the Image class).

Also, after having used the code at (1) (which violates my wish not to use an enumeration of all images), the "real" source is not updated at (2) at the click on the Button. My hopes were that (3) and (4) would solve these problems but apparently they don't.

Help would be much appreciated. Code follows:

MainWindow.xaml (incorrect)

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="700" Width="525">
    <Window.Resources>
        <local:LanguageCodeSetter x:Key="CodeSetter" LanguageCodeValue="en-GB" />
    </Window.Resources>

    <StackPanel>
        <local:LocalizedImage x:Name="imgKoala" LanguageCode="{Binding Source={StaticResource CodeSetter}, Path=LanguageCodeValue, Mode=OneWay}" OriginalSource="Koala.(lang).jpg" Height="300" Stretch="Uniform" />
        <local:LocalizedImage x:Name="imgPenguins" LanguageCode="{Binding Source={StaticResource CodeSetter}, Path=LanguageCodeValue, Mode=OneWay}" OriginalSource="Penguins.(lang).jpg" Height="300" Stretch="Uniform" />
        <Button Content="Don't click here!" Click="Button_Click" />
    </StackPanel>
</Window>

MainWindow.xaml.cs (incorrect)

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private string LanguageCodeResource
        {
            get
            {
                return ((LanguageCodeSetter)Resources["CodeSetter"]).LanguageCodeValue;
            }
            set
            {
                ((LanguageCodeSetter)Resources["CodeSetter"]).LanguageCodeValue = value;
            }
        }

        public MainWindow()
        {
            InitializeComponent();

            //(1)
            //imgKoala.OriginalSource = imgKoala.OriginalSource;
            //imgPenguins.OriginalSource = imgPenguins.OriginalSource;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            LanguageCodeResource = "fr-FR";

            //(2)
            //imgKoala.LanguageCode = imgKoala.LanguageCode;
            //imgPenguins.LanguageCode = imgPenguins.LanguageCode;
        }
    }

    public class LocalizedImage : Image
    {
        public static readonly DependencyProperty LanguageCodeProperty = DependencyProperty.Register("LanguageCode", typeof(string), typeof(LocalizedImage));
        public static readonly DependencyProperty OriginalSourceProperty = DependencyProperty.Register("OriginalSource", typeof(string), typeof(LocalizedImage));

        public string LanguageCode
        {
            get
            {
                return (string)GetValue(LanguageCodeProperty);
            }
            set
            {
                SetValue(LanguageCodeProperty, value);
                //(3)
                SetValue(SourceProperty, new BitmapImage(new Uri(OriginalSource.Replace("(lang)", value), UriKind.RelativeOrAbsolute)));
            }
        }

        public string OriginalSource
        {
            get
            {
                return (string)GetValue(OriginalSourceProperty);
            }
            set
            {
                SetValue(OriginalSourceProperty, value);
                //(4)
                SetValue(SourceProperty, new BitmapImage(new Uri(value.Replace("(lang)", LanguageCode), UriKind.RelativeOrAbsolute)));
            }
        }
    }

    public class LanguageCodeSetter : INotifyPropertyChanged
    {
        private string _languageCode;

        public event PropertyChangedEventHandler PropertyChanged;

        public string LanguageCodeValue
        {
            get
            {
                return _languageCode;
            }
            set
            {
                _languageCode = value;
                NotifyPropertyChanged("LanguageCodeValue");
            }
        }

        private void NotifyPropertyChanged(string info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
    }
}

@NVM

  • Point taken about confusing names. I've updated my code.
  • The reason that I use an INotifyPropertyChanged object is that I want the changes in one variable, namely the resource called CodeSetter, to propagate to all instances of LocalizedImage. The reason for this is that I'm building a WPF application with quite a lot of images, and I do not want to be forced to add them all in a list in code-behind (thus forgetting to add some images, and making future refactoring of the application more tedious). At a click on the button, the value of "LanguageCode" does change in all instances of LocalizedImage, so the propagation part seems to work. However, setting the "real" source at (3) does not. I've also tried setting base.Source to the same value (new BitmapImage(...)) but with the same result.
  • The property (LanguageCodeResource) is only for brevity in the Button_Click event handler.

Maybe I'm aiming in the wrong direction to solve this problem? Additional feedback would be much appreciated.

@NVM That did the trick. Thank you very much!

For anyone interested, I attach my correct code. The somewhat cumbersome DataContext datatype is because I need "two datacontexts" for my images and texts (which come from an XML file) in my real program.

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication2"
    Title="MainWindow" Height="700" Width="525">
<Window.Resources>
    <local:LocalizedImageSourceConverter x:Key="localizedImageSourceConverter" />
</Window.Resources>
<StackPanel x:Name="layoutRoot">
    <Image x:Name="imgKoala" Source="{Binding Path=LanguageCode, Converter={StaticResource localizedImageSourceConverter}, ConverterParameter='Koala.(lang).jpg'}" Height="300" Stretch="Uniform" />
    <Image x:Name="imgPenguins" Source="{Binding Path=LanguageCode, Converter={StaticResource localizedImageSourceConverter}, ConverterParameter='Penguins.(lang).jpg'}" Height="300" Stretch="Uniform" />
    <Button Content="Don't click here!" Click="Button_Click" />
</StackPanel>

MainWindow.cs.xaml

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

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        private string LanguageCodeValue
        {
            set
            {
                layoutRoot.DataContext = new
                {
                    LanguageCode = value
                };
            }
        }

        public MainWindow()
        {
            InitializeComponent();

            LanguageCodeValue = "en-GB";
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            LanguageCodeValue = "fr-FR";
        }
    }

    public class LocalizedImageSourceConverter : IValueConverter
    {
        public object Convert(object values, Type targetType, object parameter, CultureInfo culture)
        {
            return ((string)parameter).Replace("(lang)", (string)values);
        }

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

Firstly you ought to stop using 'LanguageCode' to name just about everything. It is really confusing :D

Secondly for

    <Window.Resources>
    <local:LanguageCodeSetter x:Key="LanguageCode" LanguageCode="en-GB" />
</Window.Resources>

to make any sense

        public string LanguageCode
    {
        get
        {
            return _languageCode;
        }
        set
        {
            _languageCode = value;
            NotifyPropertyChanged("LanguageCode");
        }
    }

ought to be a dependency property and not a clr property backed by INotify...

EDIT

I still dont get how the LanguageCode property will work in the resources section.

Anyway having understood what you are trying to achieve here, there is a very simple solution

<Image Source="{Binding Path=LanguageCode, Converter={StaticResource localizedImageSourceConverter}, ConverterParameter='Koala.jpg'}"/>

public class LocalizedImageSourceConverter : IValueConverter
{
    public object Convert(object values, Type targetType, object parameter, CultureInfo culture)
    {
        string fileName = Path.GetFileNameWithoutExtension((string)parameter);
        string extension = Path.GetExtension((string)parameter);
        string languageCode = (string)values;

        return string.Format("{0}{1}{2}", fileName, languageCode, extension);
    }

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

Instead of binding the Source property to a filePath(URI), I am binding it to the LanguageCode property. The LanguageCode property should be in your ViewModel or whatever datacontext object you are binding to.

The converter will take the path to the base image as a parameter and combines it with the bound LanguageCodeProperty to give you a localized path. And since you are binding to the LanguageCode property in your datacontext whenever it changes all images will be automatically updated. Note that the converter parameter cannot be bound. If you want to bind both the filePath and the language code use a multibinding.

*There might be syntax errors in the code, I am only trying to convey the concept

NVM