Closed abughuffa closed 3 years ago
Hi @abughuffa, I can't remember exactly how WPF implements right-to-left language support, but perhaps you need to get access to the top-level Window
object, and set its flow direction like this:
Application.Current.MainWindow.FlowDirection = FlowDirection.RightToLeft;
I believe you can do this with in a XAML with the FrameworkElement.Language
property. I haven't specifically tested it with RTL languages, but I believe it should work.
In my application, I added a property in MainWindowViewModel.XmlLanguage
and added the property in the <Window>
main element in my custom MainWindowView.xaml
. The code looks a little like this:
MainWindowView.xaml
<mahapps:MetroWindow
x:Class="YourApp.Modules.MainWindow.Views.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mahapps="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:cal="http://www.caliburnproject.org"
WindowTransitionsEnabled="False"
Title="{Binding Title}"
Icon="{Binding Icon}"
Width="{Binding Width, Mode=TwoWay}"
Height="{Binding Height, Mode=TwoWay}"
WindowStartupLocation="CenterScreen"
WindowState="{Binding WindowState}"
ResizeMode="CanResizeWithGrip"
UseLayoutRounding="True"
Language="{Binding XmlLanguage}"
Style="{DynamicResource MainWindowStyle}"
>
MainWindowViewModel.cs:
private CultureInfo _currentCulture;
public CultureInfo CurrentCulture
{
get => _currentCulture ?? CultureInfo.CurrentCulture;
}
private string _cultureName;
public string CultureName
{
get => _cultureName;
set
{
try
{
var oldCultureInfo = _currentCulture ?? CultureInfo.CurrentCulture;
_log.Info("CultureName: Changing from {OldCultureName} to {CultureName}", _cultureName, value);
_cultureName = !string.IsNullOrEmpty(value) ? value : "en-US";
_currentCulture = new CultureInfo(value);
_xmlLanguage = XmlLanguage.GetLanguage(value);
CultureHelper.SetCulture(value);
NotifyOfPropertyChange(nameof(CurrentCulture));
NotifyOfPropertyChange(nameof(XmlLanguage));
}
catch (Exception ex)
{
_log.Error(ex, "set_CultureName failed: {CultureName} - {ErrorMessage}", value, ex.Message);
}
}
}
private XmlLanguage _xmlLanguage;
public XmlLanguage XmlLanguage
{
get => _xmlLanguage
?? XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name);
}
CultureHelper.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Windows;
using System.Windows.Markup;
public static class CultureHelper
{
private static readonly NLog.Logger _log = NLog.LogManager.GetCurrentClassLogger();
public static bool OverrideMetadata { get; set; }
public static bool IsOverrideSpecified
{
get { return !string.IsNullOrEmpty(_cultureName); }
}
private static string _cultureName;
private static CultureInfo _cultureInfo;
public static CultureInfo CultureInfo
{
get { return _cultureInfo; }
}
public static void SetCulture(string name)
{
try
{
var oldCulture = CultureInfo.CurrentCulture;
var newCulture = new CultureInfo(name);
if (EqualityComparer<CultureInfo>.Default.Equals(oldCulture, newCulture))
{
_cultureInfo = oldCulture;
_log.Info("SetCulture: No change {0} ({1})",
oldCulture.Name, oldCulture.LCID);
}
else
{
_log.Info("SetCulture: Old={0} ({1}), New={2} ({3})",
oldCulture.Name, oldCulture.LCID,
newCulture.Name, newCulture.LCID);
_cultureInfo = newCulture;
Thread.CurrentThread.CurrentCulture = newCulture;
Thread.CurrentThread.CurrentUICulture = newCulture;
CultureInfo.DefaultThreadCurrentCulture = newCulture;
CultureInfo.DefaultThreadCurrentUICulture = newCulture;
if (OverrideMetadata && !IsOverrideSpecified)
{
_log.Info("SetCulture: updating FrameworkPropertyMetadata - {0}", name);
_cultureName = name;
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(name)));
}
else
{
_log.Info("SetCulture: did not update FrameworkPropertyMetadata");
}
}
}
catch (Exception ex)
{
_log.Error(ex, "SetCulture: {0}", ex.Message);
throw;
}
}
public static void UpdateCurrentThread()
{
try
{
if (_cultureInfo != null)
{
var culture = Thread.CurrentThread.CurrentCulture;
if (!EqualityComparer<CultureInfo>.Default.Equals(_cultureInfo, culture))
{
Thread.CurrentThread.CurrentCulture = _cultureInfo;
}
culture = Thread.CurrentThread.CurrentUICulture;
if (!EqualityComparer<CultureInfo>.Default.Equals(_cultureInfo, culture))
{
Thread.CurrentThread.CurrentUICulture = _cultureInfo;
}
}
}
catch (Exception ex)
{
_log.Error(ex, "UpdateCurrentThread: {0}", ex.Message);
throw;
}
}
}
@ryanvs thank you, that’s a much better answer!
@abughuffa hopefully that helps you resolve your issue, but please re-open if not.
It looks like you may need FlowDirection
in addition to Language
. I tried a quick test and had strange results. If you put FlowDirection
at the MainWindow level, almost everything is changed to RTL, including the toolbar and the images in the toolbar are reversed as well!
Also, there is an issue with the resources in Inspector that throws an exception:
System.Windows.Markup.XamlParseException
HResult=0x80131501
Message=Input string was not in a correct format.
Source=PresentationFramework
StackTrace:
at System.Windows.Markup.XamlReader.RewrapException(Exception e, IXamlLineInfo lineInfo, Uri baseUri)
at System.Windows.Markup.XamlReader.RewrapException(Exception e, Uri baseUri)
at System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlReader templateReader, XamlObjectWriter currentWriter)
at System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(DependencyObject container, IComponentConnector componentConnector, IStyleConnector styleConnector, List`1 affectedChildren, UncommonField`1 templatedNonFeChildrenField)
at System.Windows.FrameworkTemplate.LoadContent(DependencyObject container, List`1 affectedChildren)
at System.Windows.StyleHelper.ApplyTemplateContent(UncommonField`1 dataField, DependencyObject container, FrameworkElementFactory templateRoot, Int32 lastChildIndex, HybridDictionary childIndexFromChildID, FrameworkTemplate frameworkTemplate)
at System.Windows.FrameworkTemplate.ApplyTemplateContent(UncommonField`1 templateDataField, FrameworkElement container)
at System.Windows.FrameworkElement.ApplyTemplate()
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at System.Windows.Controls.StackPanel.StackMeasureHelper(IStackMeasure measureElement, IStackMeasureScrollData scrollData, Size constraint)
at System.Windows.Controls.StackPanel.MeasureOverride(Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at System.Windows.Controls.Border.MeasureOverride(Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at System.Windows.Controls.Control.MeasureOverride(Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at System.Windows.Controls.Control.MeasureOverride(Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at System.Windows.Controls.Border.MeasureOverride(Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at System.Windows.Controls.Grid.MeasureCell(Int32 cell, Boolean forceInfinityV)
at System.Windows.Controls.Grid.MeasureCellsGroup(Int32 cellsHead, Size referenceSize, Boolean ignoreDesiredSizeU, Boolean forceInfinityV, Boolean& hasDesiredSizeUChanged)
at System.Windows.Controls.Grid.MeasureOverride(Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at System.Windows.Controls.StackPanel.StackMeasureHelper(IStackMeasure measureElement, IStackMeasureScrollData scrollData, Size constraint)
at System.Windows.Controls.StackPanel.MeasureOverride(Size constraint)
at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
at System.Windows.UIElement.Measure(Size availableSize)
at System.Windows.ContextLayoutManager.UpdateLayout()
at System.Windows.ContextLayoutManager.UpdateLayoutCallback(Object arg)
at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
at System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(Object resizedCompositionTarget)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.Run()
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run()
at Gemini.Demo.App.Main()
This exception was originally thrown at this call stack:
[External Code]
Inner Exception 1:
FormatException: Input string was not in a correct format.
I assumed the issue was either ChangeObjectValueActionFormat
or ResetObjectValueActionFormat
that have formatting specifiers (e.g. Reset {0} from {1} to {2}
) in Gemini.Modules.Inspector.Properties.Resources.resx
, but I wasn't able to verify. I did not create a new Resources.ar.resx
but just relied on default fallback behavior. Unfortunately I don't have time to investigate thoroughly. In addition, the resources (once the exception is resolved) probably will need Unicode directional codes: https://stackoverflow.com/a/6255490/29762
I have added a resorces.ar.resx file to all modules, now i can change the language to Arabic language but the application layout still stuck in LTR ,,
do you have any solution to this issue?
and thank you in advance