495 lines
18 KiB
C#
495 lines
18 KiB
C#
#region Copyright information
|
|
// <copyright file="FELoc.cs">
|
|
// Licensed under Microsoft Public License (Ms-PL)
|
|
// https://github.com/XAMLMarkupExtensions/WPFLocalizationExtension/blob/master/LICENSE
|
|
// </copyright>
|
|
// <author>Bernhard Millauer</author>
|
|
// <author>Uwe Mayer</author>
|
|
#endregion
|
|
|
|
namespace WPFLocalizeExtension.Extensions
|
|
{
|
|
#region Usings
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
using System.Windows;
|
|
using System.Windows.Data;
|
|
using System.Windows.Markup.Primitives;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using WPFLocalizeExtension.Engine;
|
|
using WPFLocalizeExtension.TypeConverters;
|
|
using XAMLMarkupExtensions.Base;
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// A localization utility based on <see cref="FrameworkElement"/>.
|
|
/// </summary>
|
|
public class FELoc : FrameworkElement, IDictionaryEventListener, INotifyPropertyChanged, IDisposable
|
|
{
|
|
#region PropertyChanged Logic
|
|
/// <summary>
|
|
/// Informiert über sich ändernde Eigenschaften.
|
|
/// </summary>
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
/// <summary>
|
|
/// Notify that a property has changed
|
|
/// </summary>
|
|
/// <param name="property">
|
|
/// The property that changed
|
|
/// </param>
|
|
internal void RaisePropertyChanged(string property)
|
|
{
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
|
}
|
|
#endregion
|
|
|
|
#region Private variables
|
|
private static readonly object ResourceBufferLock = new object();
|
|
private static Dictionary<string, object> _resourceBuffer = new Dictionary<string, object>();
|
|
|
|
private ParentChangedNotifier _parentChangedNotifier;
|
|
private TargetInfo _targetInfo;
|
|
#endregion
|
|
|
|
#region Resource buffer handling.
|
|
/// <summary>
|
|
/// Clears the common resource buffer.
|
|
/// </summary>
|
|
public static void ClearResourceBuffer()
|
|
{
|
|
lock (ResourceBufferLock)
|
|
{
|
|
_resourceBuffer?.Clear();
|
|
_resourceBuffer = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds an item to the resource buffer (threadsafe).
|
|
/// </summary>
|
|
/// <param name="key">The key.</param>
|
|
/// <param name="item">The item.</param>
|
|
internal static void SafeAddItemToResourceBuffer(string key, object item)
|
|
{
|
|
lock (ResourceBufferLock)
|
|
{
|
|
if (!LocalizeDictionary.Instance.DisableCache && !_resourceBuffer.ContainsKey(key))
|
|
_resourceBuffer.Add(key, item);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an item from the resource buffer (threadsafe).
|
|
/// </summary>
|
|
/// <param name="key">The key.</param>
|
|
internal static void SafeRemoveItemFromResourceBuffer(string key)
|
|
{
|
|
lock (ResourceBufferLock)
|
|
{
|
|
if (_resourceBuffer.ContainsKey(key))
|
|
_resourceBuffer.Remove(key);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region DependencyProperty: Key
|
|
/// <summary>
|
|
/// <see cref="DependencyProperty"/> Key to set the resource key.
|
|
/// </summary>
|
|
public static readonly DependencyProperty KeyProperty =
|
|
DependencyProperty.Register(
|
|
"Key",
|
|
typeof(string),
|
|
typeof(FELoc),
|
|
new PropertyMetadata(null, DependencyPropertyChanged));
|
|
|
|
/// <summary>
|
|
/// The resource key.
|
|
/// </summary>
|
|
public string Key
|
|
{
|
|
get => this.GetValueSync<string>(KeyProperty);
|
|
set => this.SetValueSync(KeyProperty, value);
|
|
}
|
|
#endregion
|
|
|
|
#region DependencyProperty: Converter
|
|
/// <summary>
|
|
/// <see cref="DependencyProperty"/> Converter to set the <see cref="IValueConverter"/> used to adapt to the target.
|
|
/// </summary>
|
|
public static readonly DependencyProperty ConverterProperty =
|
|
DependencyProperty.Register(
|
|
"Converter",
|
|
typeof(IValueConverter),
|
|
typeof(FELoc),
|
|
new PropertyMetadata(new DefaultConverter(), DependencyPropertyChanged));
|
|
|
|
/// <summary>
|
|
/// Gets or sets the custom value converter.
|
|
/// </summary>
|
|
public IValueConverter Converter
|
|
{
|
|
get => this.GetValueSync<IValueConverter>(ConverterProperty);
|
|
set => this.SetValueSync(ConverterProperty, value);
|
|
}
|
|
#endregion
|
|
|
|
#region DependencyProperty: ConverterParameter
|
|
/// <summary>
|
|
/// <see cref="DependencyProperty"/> ConverterParameter.
|
|
/// </summary>
|
|
public static readonly DependencyProperty ConverterParameterProperty =
|
|
DependencyProperty.Register(
|
|
"ConverterParameter",
|
|
typeof(object),
|
|
typeof(FELoc),
|
|
new PropertyMetadata(null, DependencyPropertyChanged));
|
|
|
|
/// <summary>
|
|
/// Gets or sets the converter parameter.
|
|
/// </summary>
|
|
public object ConverterParameter
|
|
{
|
|
get => this.GetValueSync<object>(ConverterParameterProperty);
|
|
set => this.SetValueSync(ConverterParameterProperty, value);
|
|
}
|
|
#endregion
|
|
|
|
#region DependencyProperty: ForceCulture
|
|
/// <summary>
|
|
/// <see cref="DependencyProperty"/> ForceCulture.
|
|
/// </summary>
|
|
public static readonly DependencyProperty ForceCultureProperty =
|
|
DependencyProperty.Register(
|
|
"ForceCulture",
|
|
typeof(string),
|
|
typeof(FELoc),
|
|
new PropertyMetadata(null, DependencyPropertyChanged));
|
|
|
|
/// <summary>
|
|
/// Gets or sets the forced culture.
|
|
/// </summary>
|
|
public string ForceCulture
|
|
{
|
|
get => this.GetValueSync<string>(ForceCultureProperty);
|
|
set => this.SetValueSync(ForceCultureProperty, value);
|
|
}
|
|
#endregion
|
|
|
|
#region DependencyProperty: Content - used for value transfer only!
|
|
///// <summary>
|
|
///// <see cref="DependencyProperty"/> ForceCulture.
|
|
///// </summary>
|
|
//public static readonly DependencyProperty ContentProperty =
|
|
// DependencyProperty.Register(
|
|
// "Content",
|
|
// typeof(object),
|
|
// typeof(FELoc));
|
|
|
|
///// <summary>
|
|
///// Gets or sets the content.
|
|
///// </summary>
|
|
//public object Content
|
|
//{
|
|
// get { return GetValue(ContentProperty); }
|
|
// set { SetValue(ContentProperty, value); }
|
|
//}
|
|
|
|
private object _content;
|
|
/// <summary>
|
|
/// Gets or sets the content.
|
|
/// </summary>
|
|
public object Content
|
|
{
|
|
get => _content;
|
|
set
|
|
{
|
|
if (_content != value)
|
|
{
|
|
_content = value;
|
|
RaisePropertyChanged(nameof(Content));
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Indicates, that the key changed.
|
|
/// </summary>
|
|
/// <param name="obj">The FELoc object.</param>
|
|
/// <param name="args">The event argument.</param>
|
|
private static void DependencyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
|
|
{
|
|
if (obj is FELoc loc)
|
|
loc.UpdateNewValue();
|
|
}
|
|
|
|
#region Parent changed event
|
|
/// <summary>
|
|
/// Based on http://social.msdn.microsoft.com/Forums/en/wpf/thread/580234cb-e870-4af1-9a91-3e3ba118c89c
|
|
/// </summary>
|
|
/// <param name="element">The target object.</param>
|
|
/// <returns>The list of DependencyProperties of the object.</returns>
|
|
private IEnumerable<DependencyProperty> GetDependencyProperties(object element)
|
|
{
|
|
var properties = new List<DependencyProperty>();
|
|
var markupObject = MarkupWriter.GetMarkupObjectFor(element);
|
|
|
|
foreach (var mp in markupObject.Properties)
|
|
if (mp.DependencyProperty != null)
|
|
properties.Add(mp.DependencyProperty);
|
|
|
|
return properties;
|
|
}
|
|
|
|
private void RegisterParentNotifier()
|
|
{
|
|
_parentChangedNotifier = new ParentChangedNotifier(this, () =>
|
|
{
|
|
_parentChangedNotifier.Dispose();
|
|
_parentChangedNotifier = null;
|
|
var targetObject = Parent;
|
|
if (targetObject != null)
|
|
{
|
|
var properties = GetDependencyProperties(targetObject);
|
|
foreach (var p in properties)
|
|
{
|
|
if (targetObject.GetValue(p) == this)
|
|
{
|
|
_targetInfo = new TargetInfo(targetObject, p, p.PropertyType, -1);
|
|
|
|
var binding = new Binding("Content")
|
|
{
|
|
Source = this,
|
|
Converter = Converter,
|
|
ConverterParameter = ConverterParameter,
|
|
Mode = BindingMode.OneWay
|
|
};
|
|
|
|
BindingOperations.SetBinding(targetObject, p, binding);
|
|
UpdateNewValue();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
#endregion
|
|
|
|
#region Constructors & Dispose
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="BLoc"/> class.
|
|
/// </summary>
|
|
public FELoc()
|
|
{
|
|
LocalizeDictionary.DictionaryEvent.AddListener(this);
|
|
RegisterParentNotifier();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="BLoc"/> class.
|
|
/// </summary>
|
|
/// <param name="key">The resource identifier.</param>
|
|
public FELoc(string key)
|
|
: this()
|
|
{
|
|
Key = key;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the listener from the dictionary.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
LocalizeDictionary.DictionaryEvent.RemoveListener(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The finalizer.
|
|
/// </summary>
|
|
~FELoc()
|
|
{
|
|
Dispose();
|
|
}
|
|
#endregion
|
|
|
|
#region Forced culture handling
|
|
/// <summary>
|
|
/// If Culture property defines a valid <see cref="CultureInfo"/>, a <see cref="CultureInfo"/> instance will get
|
|
/// created and returned, otherwise <see cref="LocalizeDictionary"/>.Culture will get returned.
|
|
/// </summary>
|
|
/// <returns>The <see cref="CultureInfo"/></returns>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// thrown if the parameter Culture don't defines a valid <see cref="CultureInfo"/>
|
|
/// </exception>
|
|
protected CultureInfo GetForcedCultureOrDefault()
|
|
{
|
|
// define a culture info
|
|
CultureInfo cultureInfo;
|
|
|
|
// check if the forced culture is not null or empty
|
|
if (!string.IsNullOrEmpty(ForceCulture))
|
|
{
|
|
// try to create a valid cultureinfo, if defined
|
|
try
|
|
{
|
|
// try to create a specific culture from the forced one
|
|
// cultureInfo = CultureInfo.CreateSpecificCulture(this.ForceCulture);
|
|
cultureInfo = new CultureInfo(ForceCulture);
|
|
}
|
|
catch (ArgumentException ex)
|
|
{
|
|
// on error, check if designmode is on
|
|
if (LocalizeDictionary.Instance.GetIsInDesignMode())
|
|
{
|
|
// cultureInfo will be set to the current specific culture
|
|
cultureInfo = LocalizeDictionary.Instance.SpecificCulture;
|
|
}
|
|
else
|
|
{
|
|
// tell the customer, that the forced culture cannot be converted propperly
|
|
throw new ArgumentException("Cannot create a CultureInfo with '" + ForceCulture + "'", ex);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// take the current specific culture
|
|
cultureInfo = LocalizeDictionary.Instance.SpecificCulture;
|
|
}
|
|
|
|
// return the evaluated culture info
|
|
return cultureInfo;
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// This method is called when the resource somehow changed.
|
|
/// </summary>
|
|
/// <param name="sender">The sender.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
public void ResourceChanged(DependencyObject sender, DictionaryEventArgs e)
|
|
{
|
|
UpdateNewValue();
|
|
}
|
|
|
|
private void UpdateNewValue()
|
|
{
|
|
Content = FormatOutput();
|
|
}
|
|
|
|
#region Resource loopkup
|
|
/// <summary>
|
|
/// This function returns the properly prepared output of the markup extension.
|
|
/// </summary>
|
|
public object FormatOutput()
|
|
{
|
|
object result = null;
|
|
|
|
if (_targetInfo == null)
|
|
return null;
|
|
|
|
var targetObject = _targetInfo.TargetObject as DependencyObject;
|
|
|
|
// Get target type. Change ImageSource to BitmapSource in order to use our own converter.
|
|
var targetType = _targetInfo.TargetPropertyType;
|
|
|
|
if (targetType == typeof(ImageSource))
|
|
targetType = typeof(BitmapSource);
|
|
|
|
// Try to get the localized input from the resource.
|
|
string resourceKey = LocalizeDictionary.Instance.GetFullyQualifiedResourceKey(Key, targetObject);
|
|
|
|
var ci = GetForcedCultureOrDefault();
|
|
|
|
// Extract the names of the endpoint object and property
|
|
var epName = "";
|
|
var epProp = "";
|
|
|
|
if (targetObject is FrameworkElement element)
|
|
epName = element.GetValueSync<string>(NameProperty);
|
|
else if (targetObject is FrameworkContentElement)
|
|
epName = ((FrameworkContentElement)targetObject).GetValueSync<string>(FrameworkContentElement.NameProperty);
|
|
|
|
if (_targetInfo.TargetProperty is PropertyInfo info)
|
|
epProp = info.Name;
|
|
else if (_targetInfo.TargetProperty is DependencyProperty)
|
|
epProp = ((DependencyProperty)_targetInfo.TargetProperty).Name;
|
|
|
|
// What are these names during design time good for? Any suggestions?
|
|
if (epProp.Contains("FrameworkElementWidth5"))
|
|
epProp = "Height";
|
|
else if (epProp.Contains("FrameworkElementWidth6"))
|
|
epProp = "Width";
|
|
else if (epProp.Contains("FrameworkElementMargin12"))
|
|
epProp = "Margin";
|
|
|
|
var resKeyBase = ci.Name + ":" + targetType.Name + ":";
|
|
string resKeyNameProp = LocalizeDictionary.Instance.GetFullyQualifiedResourceKey(epName + LocalizeDictionary.GetSeparation(targetObject) + epProp, targetObject);
|
|
string resKeyName = LocalizeDictionary.Instance.GetFullyQualifiedResourceKey(epName, targetObject);
|
|
|
|
// Check, if the key is already in our resource buffer.
|
|
object input = null;
|
|
|
|
if (!string.IsNullOrEmpty(resourceKey))
|
|
{
|
|
// We've got a resource key. Try to look it up or get it from the dictionary.
|
|
lock (ResourceBufferLock)
|
|
{
|
|
if (_resourceBuffer.ContainsKey(resKeyBase + resourceKey))
|
|
result = _resourceBuffer[resKeyBase + resourceKey];
|
|
else
|
|
{
|
|
input = LocalizeDictionary.Instance.GetLocalizedObject(resourceKey, targetObject, ci);
|
|
resKeyBase += resourceKey;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Try the automatic lookup function.
|
|
// First, look for a resource entry named: [FrameworkElement name][Separator][Property name]
|
|
lock (ResourceBufferLock)
|
|
{
|
|
if (_resourceBuffer.ContainsKey(resKeyBase + resKeyNameProp))
|
|
result = _resourceBuffer[resKeyBase + resKeyNameProp];
|
|
else
|
|
{
|
|
// It was not stored in the buffer - try to retrieve it from the dictionary.
|
|
input = LocalizeDictionary.Instance.GetLocalizedObject(resKeyNameProp, targetObject, ci);
|
|
|
|
if (input == null)
|
|
{
|
|
// Now, try to look for a resource entry named: [FrameworkElement name]
|
|
// Note - this has to be nested here, as it would take precedence over the first step in the buffer lookup step.
|
|
if (_resourceBuffer.ContainsKey(resKeyBase + resKeyName))
|
|
result = _resourceBuffer[resKeyBase + resKeyName];
|
|
else
|
|
{
|
|
input = LocalizeDictionary.Instance.GetLocalizedObject(resKeyName, targetObject, ci);
|
|
resKeyBase += resKeyName;
|
|
}
|
|
}
|
|
else
|
|
resKeyBase += resKeyNameProp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no result was found, convert the input and add it to the buffer.
|
|
if (result == null && input != null)
|
|
{
|
|
result = Converter.Convert(input, targetType, ConverterParameter, ci);
|
|
SafeAddItemToResourceBuffer(resKeyBase, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|