Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set language of the DocumentViewer to German (from code, not XAML)

I am trying to change the language of the DocumentViewer from default English to German but with no success.

Being new to WPF, I really struggle to do this.

IMPORTANT: DocumentViewer is created in code behind, in response to the menu item click, and then it is added as main window's Content.

I have tried doing the following, but it seems to do nothing:

myDocumentViewer.Language = System.Windows.Markup.XmlLanguage.GetLanguage("de-DE");

No changes are made, DocumentViewer keeps English.

Googling for proper usage of the Language property, I found nothing useful.

QUESTION:

How can I set the language of the DocumentViewer (created with code) to German?

like image 935
AlwaysLearningNewStuff Avatar asked Nov 12 '15 22:11

AlwaysLearningNewStuff


1 Answers

What you are trying to accomplish can be done, but not very easily.

I'll start by pointing out that your test machine needs to have the appropriate language resources installed to permit DocumentViewer to show you tooltips etc. in German. In practice, this means that you'll need to have German (Germany) language pack installed on your computer. See Language Packs for details.

What comes below is what I know to the best of my understanding:

WPF does not quite have an in-built infrastructure, as far as I can tell, to dynamically adapt to changes in either Thread.CurrentThread.CurrentUILanguage or to changes in xml:lang(which is equivalent to FrameworkElement.Language property.

WPF controls primarily utilize xml:lang to determine their UI language (assuming that the corresponding UI resources are available), and it is up to the application developer to hook that up with Thread.CurrentThread.CurrentUILanguage if so desired. This in itself is not very hard to do using data-binding, like this:

<DocumentViewer Language="{Binding UILanguage, ConverterCulture={x:Static glob:CultureInfo.InvariantCulture}}" />

That still does not mean that the control thus data-bound would adapt its UI language to changes in Thread.CurrentThread.CurrentUILanguage. Every time you need the UI language to be changed, you need to recreate the control, remove the old control from the visual tree, and add the new one. Roughly, the code would look somewhat like this:

private void ChangeCulture()
{
    string ietfLanguageTag = "de-DE";

    var cultureInfo = CultureInfo.GetCultureInfo(ietfLanguageTag);

    Thread.CurrentThread.CurrentCulture = cultureInfo;
    Thread.CurrentThread.CurrentUICulture = cultureInfo;

    UILanguage = ietfLanguageTag;

    var parent = VisualTreeHelper.GetParent(_documentViewer) as Grid;
    int index = parent.Children.IndexOf(_documentViewer);

    parent.Children.Remove(_documentViewer);
    _documentViewer = new DocumentViewer();
    parent.Children.Add(_documentViewer);
}

The above snippet assumes that the visual parent of the DocumentViewer is a Grid, and it is backed by the variable _documentViewer.

Generally, the above solution is too simplistic and is not well suited for MVVM scenarios (which is often the case in WPF applications). You might have data bindings to the DocumentViewer instance, and creating new instances would require that those bindings be recreated in code (if, on the other hand, there happen to be no data-bindings involved, and all settings are set in code, then the above approach would just work, I think).

You can further improve this by creating a simple user control that encapsulates a DocumentViewer, along with any interesting bindings you might wish to preserve. Your control would look like this:

XAML:

<UserControl x:Class="LocalizedDocumentViewer.CultureAwareDocumentViewer"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"
         xmlns:local="clr-namespace:LocalizedDocumentViewer"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         d:DesignHeight="300"
         d:DesignWidth="300"
         mc:Ignorable="d">
<Grid>
    <DocumentViewer DataContext="{Binding}" Language="{Binding UILanguage, ConverterCulture={x:Static glob:CultureInfo.InvariantCulture}}" />
</Grid>

XAML.cs

using System.Windows.Controls;

namespace LocalizedDocumentViewer
{
    public partial class CultureAwareDocumentViewer : UserControl
    {
        public CultureAwareDocumentViewer()
        {
            InitializeComponent();
        }
    }
}

Now, you can easily include this in your main application UI, like shown below. The XAML below includes a couple of additional UI elements (buttons and labels) that would help show a complete example:

MainWindow XAML:

<Window x:Class="DocViewerLoc.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:DocViewerLoc"
    xmlns:localizedDocumentViewer="clr-namespace:LocalizedDocumentViewer;assembly=LocalizedDocumentViewer"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="DocumentViewer Culture Change Demo"
    Width="525"
    Height="350"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    mc:Ignorable="d">
<Grid>
    <!--  Row and Column Definitions  -->
    <!--  Define a small row on the top of the window to place buttons  -->
    <Grid.RowDefinitions>
        <RowDefinition Height="25" />
        <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100" />
        <ColumnDefinition Width="100" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>


    <!--  Controls  -->

    <Button Grid.Row="0"
            Grid.Column="0"
            Command="{Binding CultureChanger}"
            CommandParameter="{Binding RelativeSource={RelativeSource Self},
                                       Path=Content}">
        en-us
    </Button>
    <Button Grid.Row="0"
            Grid.Column="1"
            Command="{Binding CultureChanger}"
            CommandParameter="{Binding RelativeSource={RelativeSource Self},
                                       Path=Content}">
        de-DE
    </Button>
    <Label Grid.Row="0" Grid.Column="2">&lt;-- Click on one of these buttons to change UI culture</Label>
    <Grid Grid.Row="1" Grid.ColumnSpan="3">
        <localizedDocumentViewer:CultureAwareDocumentViewer x:Name="_documentViewer" DataContext="{Binding}" />
    </Grid>

</Grid>

The corresponding code-behind has a couple of dependency properties used to help communicate with the bindings in the above XAML.

MainWindow.xaml.cs

using System;
using System.Globalization;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace DocViewerLoc
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            CultureChanger = new SimpleCommand(ChangeCulture);
            InitializeComponent();
        }

        /// <summary>
        ///  ChangeCulture is called when one of the buttons with caption 
        /// 'en-us' or 'de-DE' is pressed. 
        /// </summary>
        /// <param name="parameter">
        /// A string containing the caption 'en-us' or 'de-DE'. 
        /// </param>
        private void ChangeCulture(object parameter)
        {
            string ietfLanguageTag = parameter as string;
            if (ietfLanguageTag == null) return;

            var cultureInfo = CultureInfo.GetCultureInfo(ietfLanguageTag);

            Thread.CurrentThread.CurrentCulture = cultureInfo;
            Thread.CurrentThread.CurrentUICulture = cultureInfo;

            // This will ensure that CultureAwareDocumentViewer's Language property
            // binds to the updated value set here when it is instantiated next.
            UILanguage = ietfLanguageTag;

            // Remove the old instance of _documentViewer from the UI. 
            var parent = VisualTreeHelper.GetParent(_documentViewer) as Grid;
            int index = parent.Children.IndexOf(_documentViewer);
            parent.Children.Remove(_documentViewer);

            // Create a new instance of CultureAwareDocumentViewer. This will 
            // use the updated value of UILanguage bind it to its Language (xml:lang)
            // property, thus resulting in the appropriate language resources being 
            // loaded. 
            _documentViewer = new LocalizedDocumentViewer.CultureAwareDocumentViewer();

            // Now, add the _documentViewer instance back to the UI tree. 
            parent.Children.Add(_documentViewer);
        }

        /// <summary>
        /// ICommand used to bind to en-us and de-DE buttons in the UI
        /// </summary>
        #region CultureChange
        public SimpleCommand CultureChanger
        {
            get { return (SimpleCommand)GetValue(CultureChangerProperty); }
            set { SetValue(CultureChangerProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CultureChanger.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CultureChangerProperty =
            DependencyProperty.Register("CultureChanger", typeof(SimpleCommand), typeof(MainWindow), new PropertyMetadata(default(SimpleCommand)));

        #endregion

        /// <summary>
        /// UILanguage property used to bind to the FrameworkElement.Language (xml:lang) property
        /// in the DocumentViewer object within the CultureAwareDocumentViewer control. 
        /// </summary>
        #region UILanguage

        public string UILanguage
        {
            get { return (string)GetValue(UILanguageProperty); }
            set { SetValue(UILanguageProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UILanguage.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UILanguageProperty =
            DependencyProperty.Register("UILanguage", typeof(string), typeof(MainWindow), new PropertyMetadata(Thread.CurrentThread.CurrentUICulture.IetfLanguageTag));

        #endregion
    }

    /// <summary>
    /// Simple implementation of the ICommand interface that delegates 
    /// Execute() to an Action<object>. 
    /// </summary>
    public class SimpleCommand : ICommand
    {
#pragma warning disable 67 
        public event EventHandler CanExecuteChanged;
#pragma warning restore 67 
        public SimpleCommand(Action<object> handler)
        {
            _handler = handler;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            _handler?.Invoke(parameter);
        }

        private Action<object> _handler;
    }
}

The below screenshot show the resulting application UI. Note that the resources in DocumentViewer would switch between English and German, but the rest of the UI would not (because we did not try to localize our application!).

Application showing en-us resources in DocumentViewer:

Application showing en-us resources in DocumentViewer

Application showing de-DE resources in DocumentViewer:

Application showing de-DE resources in DocumentViewer

like image 172
Vatsan Avatar answered Oct 19 '22 16:10

Vatsan