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

3

6

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?

Scouting answered 12/11, 2015 at 22:41 Comment(4)
i think you need to do something like this in myDocumentViewer https://mcmap.net/q/487868/-how-to-change-currentculture-at-runtimeFacet
What is the CurrentCulture and CurrentUICulture?Guesthouse
What is the purpose of trying to set xml:lang on your DocumentViewer instance? In other words, what do you expect to observe when the xml:lang property gets successfully updated to "de-DE"?Hoover
@Vatsan: I want tooltips in toolbar to change text from English to German...Scouting
H
4

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

Hoover answered 20/11, 2015 at 0:4 Comment(4)
I have tried to implement your solution, but have failed to achieve desired result. Visual Studio reported error in the following line _handler?.Invoke(parameter); but after Googling, I found out that you used C# 6 syntax so I have managed to change it so error disappears. When clicking on de-DE button, language did not change. I have then went to Control Panel. I have tried installing German keyboard and it succeeded but that did not solve the problem.Scouting
German keyboard will not help - you need German lanuage_pack. Go to C:\windows\Microsoft.NET\Framework\v4.0.30319\WPF\de and verify that various localized DLL's exist. If you don't find the WPF\de folder, of if the folder doesn't have approx. 16 *.resources.dll files, then your system isn't configured to load localized German resources for WPF's in-built controls.Hoover
I do not have the folder you mentioned. I do not want to force the users to download language pack, that option will be a "dealbreaker" for them. I guess I will have to find another way... Thank you for helping...Scouting
I have tried this example for displaying "sl-SI" language, but It didn't work either. And I have installed language pack (WIN 10). Biggest problem is "PART_FindToolBarHost" (a search box), which you cannot edit in Blend. Everything else can be fixed manually with Blend.Gourd
A
0

AFAIK you are setting it correctly.

I do not have experience with DocumentViewer, but setting CurrentUICulture does not translate. Setting CurrentUICulture selects between resources that you have in your application for different languages. See https://mcmap.net/q/99642/-how-to-use-localization-in-c for an example. I assume that Language of a DocumentViewer is the same.

Arborescent answered 17/11, 2015 at 1:10 Comment(0)
O
0

Unluckily setting the Language property of your DocumentViewer won't work. The reason of this issue is related to the PresentationUI assembly (it is a standard one), which contains resources which affect DocumentViewer.

The main point is that tooltips and labels are hardcoded in these resources and they are only in english (at least considering .NET 4).

For example, this is how the DocumentViewer's print button is defined (in the themes/generic.baml resource):

<Button Name="PrintButton" x:Uid="Button_14" ToolTip="Print (Ctrl+P)" ToolTipService.ShowOnDisabled="True" Width="24px" Padding="2,2,2,2" Margin="2,2,2,2" VerticalAlignment="Center" Command="ApplicationCommands.Print" IsTabStop="True" TabIndex="0" Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type ui:PresentationUIStyleResources}, ResourceId=PUIDocumentViewerButtonStyle}}" Background="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type ui:PresentationUIStyleResources}, ResourceId=PUIDocumentViewerPrintButton}}" CommandTarget="{Binding Path=TemplatedParent, RelativeSource={RelativeSource TemplatedParent}}" />

As you can see is defined as "Print (Ctrl+P)". You will find the same situation for other labels which you would localize. Even if x:Uid properties are defined LocBaml won't work since PresentationUI is not a satellite assembly.

So a first solution could be: write your own DocumentViewer style and you can use the language that you prefer. The problem is that inside a DocumentViewer there is a control named FindToolBar. It is declared as internal, so probably it would be hard to redefine its style.

Then I propose an alternative solution to you. My idea is based on the fact that localizable children of a DocumentViewer have a name (you can use ILSpy to establish it). So you need just to extend DocumentViewer in this way:

public class LocalizedDocumentViewer : DocumentViewer
{
    public LocalizedDocumentViewer()
    {
        Loaded += new RoutedEventHandler(OnLoaded);
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        Button button = FindChild<Button>(this, "PrintButton");
        button.ToolTip = Properties.Resources.PrintToolTip;

        button = FindChild<Button>(this, "CopyButton");
        button.ToolTip = Properties.Resources.CopyToolTip;

        button = FindChild<Button>(this, "FindPreviousButton");
        button.ToolTip = Properties.Resources.FindPreviousToolTip;

        button = FindChild<Button>(this, "FindNextButton");
        button.ToolTip = Properties.Resources.FindNextToolTip;

        Label label = FindChild<Label>(this, "FindTextLabel");
        label.Content = Properties.Resources.FindTextLabel;

        /* and so on... */
    }

    public static T FindChild<T>(DependencyObject parent, string childName)
        where T : DependencyObject
    {
        /* see the link for the code */
    }
}

You can find the code of FindChild method here (take a look to CrimsonX's answer).

I know, it is a unelegant solution. I do not like it too. But I guess it is fast and it allows you to preserve the default style look.

Oruntha answered 19/11, 2015 at 18:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.