/// <summary> /// Adds a new localized value. /// </summary> /// <param name="value">The value.</param> /// <exception cref="ArgumentNullException"><paramref name="value"/> is <c>null</c>.</exception> public static void AddLocalizedValue(LocalizedValue value) { InternalAddLocalizedValue(value); // Update the value initially value.UpdateValue(); }
/// <summary> /// Adds a localized value to the manager. /// </summary> /// <param name="value"></param> /// <exception cref="InvalidOperationException">The method is called on a thread different from the <see cref="Dispatcher"/>'s thread.</exception> public void Add(LocalizedValue value) { if (value == null) { throw new ArgumentNullException(nameof(value)); } // This verification is performed by the "RemoveProperty" method //Dispatcher.VerifyAccess(); RemoveProperty(value.TargetObject, value.TargetProperty); var valueNode = _localizedValueList.AddLast(value); if (_localizedValueDict.TryGetValue(value.TargetObject, out object oldValue)) { if (oldValue is LinkedListNode <LocalizedValueBase> oldValueNode) { // A second value of the same DependencyObject has been localized so create a list of values var valueNodeList = new List <LinkedListNode <LocalizedValueBase> >() { oldValueNode, valueNode }; // Replace the old value with the list _localizedValueDict.Remove(value.TargetObject); _localizedValueDict.Add(value.TargetObject, valueNodeList); } else if (oldValue is List <LinkedListNode <LocalizedValueBase> > oldValueNodeList) { oldValueNodeList.Add(valueNode); } else { // This should be impossible throw new NotImplementedException("Unhandled situation."); } } else { // Add the value for the first time _localizedValueDict.Add(value.TargetObject, valueNode); } _currentPurgeValueCount++; // Purge values if more than 1000 values has been added since the last purge if (_currentPurgeValueCount - _lastPurgeValueCount > 1000) { _lastPurgeValueCount = _currentPurgeValueCount; // Trigger a purge PurgeValues(); } }
/// <summary> /// Adds a new localized value. /// </summary> /// <param name="value">The value.</param> /// <exception cref="ArgumentNullException"><paramref name="value"/> is <c>null</c>.</exception> internal static void InternalAddLocalizedValue(LocalizedValue value) { if (value == null) { throw new ArgumentNullException("value"); } if (_localizedValuesPurgeCount > PurgeLimit) { // Remove localized values the owners of which have been garbage collected lock (_localizedValues) { List <LocalizedProperty> keys = null; foreach (var item in _localizedValues.Keys) { if (false == item.IsAlive) { if (keys == null) { keys = new List <LocalizedProperty>(); } keys.Add(item); } } if (keys != null) { foreach (var key in keys) { _localizedValues.Remove(key); } } _localizedValuesPurgeCount = 0; } } lock (_localizedValues) { var count = _localizedValues.Count; _localizedValues[value.Property] = value; if (count < _localizedValues.Count) { // A new value has been added _localizedValuesPurgeCount++; } } }
/// <summary> /// Creates a new localized value. /// </summary> /// <param name="targetObject"></param> /// <param name="targetProperty"></param> /// <param name="binding"></param> /// <param name="bindings"></param> /// <returns></returns> public static LocalizedValue Create( DependencyObjectProperty objectProperty, LocalizationOptions options ) { if (options == null) { throw new ArgumentNullException(nameof(options)); } var localizedValue = new LocalizedValue(objectProperty.TargetObject, objectProperty.TargetProperty) { Key = options.Key, StringFormat = options.StringFormat, Callback = options.Callback, CallbackParameter = options.CallbackParameter, Converter = options.Converter, ConverterParameter = options.ConverterParameter, }; if (options.Binding != null || options.Bindings?.Count > 0) { if (false == (objectProperty.TargetProperty.PropertyObject is DependencyProperty)) { // The what bindings are implemented in WPF provides no way to obtain the value // produced by the binging. The only way is to update the property directly. Therefore, // the extension cannot support bindings on non-dependency properties (same as WPF). throw new ArgumentException("Bindings are supported only on dependency properties.", nameof(options)); } // Create a binding var localizedBinding = new MultiBinding() { Mode = BindingMode.OneWay, }; if (options.Binding != null) { localizedBinding.Bindings.Add(options.Binding); } if (options.Bindings?.Count > 0) { foreach (var item in options.Bindings) { localizedBinding.Bindings.Add(item); } } localizedBinding.Converter = localizedValue; localizedValue.BindingExpression = (BindingExpressionBase)localizedBinding.ProvideValue(objectProperty); } return(localizedValue); }
/// <summary> /// Localizes the specified objectProperty. /// </summary> /// <param name="objectProperty">The objectProperty to localize.</param> /// <param name="options">The options describing how the objectProperty is to be localized.</param> /// <exception cref="ArgumentException">A binding is specified and the objectProperty is a non-dependency objectProperty.</exception> public static void Localize(this DependencyObjectProperty objectProperty, LocalizationOptions options) { var localizedValue = LocalizedValue.Create(objectProperty, options); LocalizationManager.Add(localizedValue); // Set the value initially if (localizedValue.BindingExpression != null) { // The value uses bindings localizedValue.TargetProperty.SetValue(localizedValue.TargetObject, localizedValue.BindingExpression); } else { // The value does not use bindings localizedValue.TargetProperty.SetValue(localizedValue.TargetObject, localizedValue.ProduceValue()); } }
internal static void Add(LocalizedValue value) { if (value == null) { throw new ArgumentNullException(nameof(value)); } var dispatcher = value.Dispatcher; if (dispatcher == null) { // The dependency object has been GC return; } if (false == dispatcher.CheckAccess()) { // COMMENT This restriction can be lifted if the DispatcherHandler.Add method becomes thread-safe throw new InvalidOperationException($"This method must be called on the UI thread of the {nameof(DependencyObject)} whose value is localized."); } GetOrCreateDispatcherHander(dispatcher).Add(value); }
public override object ProvideValue(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new ArgumentNullException(nameof(serviceProvider)); } // Other useful services: // - IDestinationTypeProvider // - IRootObjectProvider if (!(serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget service)) { return(null); } if (service.TargetObject is DependencyObject depObj) { if (Binding != null || _bindings?.Count > 0) { if (false == (service.TargetProperty is DependencyProperty)) { // The what bindings are implemented in WPF provides no way to obtain the value // produced by the binging. The only way is to update the property directly. Therefore, // the extension cannot support bindings on non-dependency properties (same as WPF). throw new NotSupportedException("Bindings are supported only on dependency properties."); } } LocalizableProperty localizableProperty; if (service.TargetProperty is DependencyProperty depProperty) { localizableProperty = new LocalizableDepProperty(depProperty); } else if (service.TargetProperty is PropertyInfo propertyInfo) { localizableProperty = new LocalizableNonDepProperty(propertyInfo); } else { throw new NotSupportedException($"The extension supports only dependency and non-dependency properties of dependency objects. Properties of type {service.TargetProperty.GetType()} are not supported."); } var options = new LocalizationOptions() { Key = Key, StringFormat = StringFormat, Binding = Binding, Bindings = _bindings, Callback = Callback, CallbackParameter = CallbackParameter, Converter = Converter, ConverterParameter = ConverterParameter, }; var localizedValue = LocalizedValue.Create(new DependencyObjectProperty(depObj, localizableProperty), options); LocalizationManager.Add(localizedValue); if (localizedValue.BindingExpression != null) { // The value uses bindings return(localizedValue.BindingExpression); } else { // The value does not use bindings if (localizedValue.IsInDesignMode) { // At design time VS designer does not set the parent of any control // before its properties are set. For this reason the correct values // of inherited attached properties cannot be obtained. // Therefore, to display the correct localized value it must be updated // later after the parent of the control has been set. depObj.Dispatcher.BeginInvoke( DispatcherPriority.ApplicationIdle, new SendOrPostCallback(x => ((LocalizedValue)x).UpdateValue()), localizedValue ); return(localizableProperty.DefaultValue); } else { return(localizedValue.ProduceValue()); } } } else if (service.TargetObject is Setter) { var rootObjectProvider = (IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider)); // IRootObjectProvider is never null // IRootObjectProvider.RootObject is null when the style is located in a separate resource dictionary file // or the application's resource dictionary in App.xaml // Otherwise, the value can be Window or UserControl depending on the where the style is declared. // Therefore, it can be assumed that once root object (if any) is disposed the localized value // can be disposed as well. var rootObject = rootObjectProvider?.RootObject as DependencyObject; if (rootObject == null) { // There is no way to access the resource manager (apart from the resource manager of the entry assembly). // In order to avoid misunderstanding and bugs localizing setters is not supported unless they are delcared inside a Window or a UserControl throw new NotSupportedException("Setters are supported only inside a Window or a UserControl. They are not supported inside a ResourceDictionary file or App.xaml."); } var options = new LocalizationOptions() { Key = Key, StringFormat = StringFormat, Binding = Binding, Bindings = _bindings, Callback = Callback, CallbackParameter = CallbackParameter, Converter = Converter, ConverterParameter = ConverterParameter, }; var localizedValueTuple = LocalizedSetterValue.Create(rootObject, options); LocalizationManager.Add(localizedValueTuple.Item1); return(localizedValueTuple.Item2.ProvideValue(serviceProvider)); } else if (service.TargetProperty is DependencyProperty || service.TargetProperty is PropertyInfo) { // The extension is used in a template and will be evaluated once the template is instantiated return(this); } else { throw new NotSupportedException($"The localization extension can be used only with {nameof(DependencyObject)}s."); } }
public override object ProvideValue(IServiceProvider serviceProvider) { if (serviceProvider == null) { throw new ArgumentNullException(nameof(serviceProvider)); } // Other useful services: // - IDestinationTypeProvider // - IRootObjectProvider if (!(serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget service)) { return(null); } if (service.TargetObject is DependencyObject depObj) { if (Binding != null || _bindings?.Count > 0) { if (false == (service.TargetProperty is DependencyProperty)) { // The what bindings are implemented in WPF provides no way to obtain the value // produced by the binging. The only way is to update the property directly. Therefore, // the extension cannot support bindings on non-dependency properties (same as WPF). throw new NotSupportedException("Bindings are supported only on dependency properties."); } } LocalizedProperty localizedProperty; if (service.TargetProperty is DependencyProperty depProperty) { localizedProperty = new LocalizedDepProperty(depProperty); } else if (service.TargetProperty is PropertyInfo propertyInfo) { localizedProperty = new LocalizedNonDepProperty(propertyInfo); } else { throw new NotSupportedException($"The default property provider supports only dependency and non-dependency properties of dependency objects. Properties of type {service.TargetProperty.GetType()} are not supported."); } var localizedValue = new LocalizedValue(depObj, localizedProperty) { Key = this.Key, StringFormat = this.StringFormat, Callback = this.Callback, CallbackParameter = this.CallbackParameter, Binding = this.Binding, Bindings = this._bindings, Converter = this.Converter, ConverterParameter = this.ConverterParameter, }; LocalizationManager.Add(localizedValue); if (localizedValue.IsInDesignMode) { // At design time VS designer does not set the parent of any control // before its properties are set. For this reason the correct values // of inherited attached properties cannot be obtained. // Therefore, to display the correct localized value it must be updated // later after the parent of the control has been set. depObj.Dispatcher.BeginInvoke( DispatcherPriority.ApplicationIdle, new SendOrPostCallback(x => ((LocalizedValue)x).UpdateValue()), localizedValue ); return(localizedProperty.DefaultValue); } else { return(localizedValue.GetValue(depObj)); } } else if (service.TargetObject is Setter) { /* // DEPRECATED * if (Binding != null || _bindings?.Count > 0) * { * throw new NotSupportedException("Bindings are not supported in style setters."); * } */ var rootObjectProvider = (IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider)); Debug.Assert(rootObjectProvider != null); var rootObject = rootObjectProvider.RootObject as DependencyObject; Debug.Assert(rootObject != null); #if DEPRECATED // Unfortunately "GetDestinationType" throws NullReferenceException therefore, this approach is not applicable for retrieving the property's type var destinationTypeProvider = (IDestinationTypeProvider)serviceProvider.GetService(typeof(IDestinationTypeProvider)); Debug.Assert(destinationTypeProvider != null); var targetPropertyType = destinationTypeProvider.GetDestinationType(); var localizedValue = new SetterLocalizedValue(rootObject, targetPropertyType) { Key = this.Key, Callback = this.Callback, CallbackParameter = this.CallbackParameter, Converter = this.Converter, ConverterParameter = this.ConverterParameter, }; #endif var localizedValue = new LocalizedSetterValue(rootObject) { Key = this.Key, StringFormat = this.StringFormat, Callback = this.Callback, CallbackParameter = this.CallbackParameter, Binding = this.Binding, Bindings = this._bindings, Converter = this.Converter, ConverterParameter = this.ConverterParameter, }; BindingBase finalBinding; if (Binding != null || _bindings?.Count > 0) { var binding = new MultiBinding() { Mode = BindingMode.OneWay, Converter = localizedValue, }; if (Binding != null) { binding.Bindings.Add(Binding); if (_bindings?.Count > 0) { foreach (var item in Bindings) { binding.Bindings.Add(item); } } } finalBinding = binding; } else { finalBinding = new Binding(nameof(LocalizedSetterValue.Value)) { Source = localizedValue, Mode = BindingMode.OneWay, Converter = localizedValue, }; } LocalizationManager.Add(localizedValue); return(finalBinding); } else if (service.TargetProperty is DependencyProperty || service.TargetProperty is PropertyInfo) { // The extension is used in a template and will be evaluated once the template is instantiated return(this); } else { throw new NotSupportedException($"The localization extension can be used only with {nameof(DependencyObject)}s."); } }