Default assembly and dictionary

Jan 13, 2012 at 10:36 AM

Hi, I am using this extension, and I must say it's much better than LocBaml I tried earlier :) But there is one thing I need and it is not implemented.

Since I am using only one dictionary for all the translations in application i need to have an option to declare default assembly and default dictionary, so I could type only key in xaml and value will be found :) So i made following changes to sourcecode:

in LocalizeDictionary.cs:

I added properties for defaults

        /// <summary>
        /// Holds default assembly name
        /// </summary>
        private static string defaultAssembly = ""; 

        /// <summary>
        /// Gets / Sets default assembly
        /// </summary>
        public static string DefaultAssembly 
        { 
            get
            {
                return !String.IsNullOrEmpty(defaultAssembly) ? 
                                                defaultAssembly : 
                                                Instance.GetAssemblyName(System.Reflection.Assembly.GetExecutingAssembly());
            } 
            set
            {
                defaultAssembly = value;
            }
        }

        /// <summary>
        /// Holds default dictionary name
        /// </summary>
        private static string defaultDict = "";

        /// <summary>
        /// Gets / Sets default dictionary
        /// </summary>
        public static string DefaultDict 
        {
            get
            {
                return !String.IsNullOrEmpty(defaultDict) ? defaultDict : ResourcesName;
            }
            set
            {
                defaultDict = value;
            }
        }

Changed ParseKey to:

        /// <summary>
        /// Parses a key ([[Assembly:]Dict:]Key and return the parts of it.
        /// </summary>
        /// <param name="inKey">The key to parse.</param>
        /// <param name="outAssembly">The found or default assembly.</param>
        /// <param name="outDict">The found or default dictionary.</param>
        /// <param name="outKey">The found or default key.</param>
        public static void ParseKey(string inKey, out string outAssembly, out string outDict, out string outKey)
        {
            // reset the vars to null
            outAssembly = null;
            outDict = null;
            outKey = null;

            // its a assembly/dict/key pair
            if (!string.IsNullOrEmpty(inKey))
            {
                string[] split = inKey.Trim().Split(":".ToCharArray(), 3);

                // assembly:dict:key
                if (split.Length == 3)
                {
                    outAssembly = !string.IsNullOrEmpty(split[0]) ? split[0] : null;
                    outDict = !string.IsNullOrEmpty(split[1]) ? split[1] : null;
                    outKey = split[2];
                }
                
                // dict:key
                // assembly = ExecutingAssembly
                if (split.Length == 2)
                {
                    outAssembly = !String.IsNullOrEmpty(DefaultAssembly) ? DefaultAssembly : null;
                    outDict = !string.IsNullOrEmpty(split[0]) ? split[0] : null;
                    outKey = split[1];
                }
                
                // key
                // assembly = ExecutingAssembly
                // dict = standard resourcedictionary
                if (split.Length == 1)
                {
                    outAssembly = !String.IsNullOrEmpty(DefaultAssembly) ? DefaultAssembly : null;
                    outDict = !String.IsNullOrEmpty(DefaultDict) ? DefaultDict : null;
                    outKey = split[0];
                }
            }
            else
            {
                // if the passed value is null pr empty, throw an exception if in runtime
                if (!Instance.GetIsInDesignMode())
                {
                    throw new ArgumentNullException("inKey");
                }
            }
        }

in BaseLocalizeExtension.cs :

changed propetries

        /// <summary>
        /// Gets or sets the name of the Assembly where the .resx is located.
        /// If it's null, the default assembly (where this LocalizeEngine is located at) will get returned
        /// </summary>
        public string Assembly
        {
            get
            {
                return this.assembly ??
                       LocalizeDictionary.DefaultAssembly;
            }

            set { this.assembly = !string.IsNullOrEmpty(value) ? value : null; }
        }

        /// <summary>
        /// Gets or sets the Name of the .resx dictionary.
        /// If it's null, default will get returned
        /// </summary>
        public string Dict
        {
            get { return this.dict ?? LocalizeDictionary.DefaultDict; }

            set { this.dict = !string.IsNullOrEmpty(value) ? value : null; }
        }

And it works for me. I just specify default values on application startup:

WPFLocalizeExtension.Engine.LocalizeDictionary.DefaultAssembly = "DefaultAsembly";
WPFLocalizeExtension.Engine.LocalizeDictionary.DefaultDict = "DefaultDictionary";

and I can get localized values just by key:

<TextBlock Name="TextName" Text="{loc:LocText myTextKey}"/>

 

So my question is: Is that all, or do I have to change something else? I don't know that much about this source code and I don't want to get errors I did not know about.

 

I can send you source code by email, I'm new at CodePlex and don't know if I can share here scode on hosting like Mediafire etc.

Coordinator
Jan 13, 2012 at 11:09 AM

Hi uukasz,

your code looks correct!

Just one thing bugs me with the default values: if a coder do not know that he MAY set the default values just a single time (eg. app startup), the extension has a strange behaviour if it will be set afterwards during runtime.

please introduce an InitializeDefaultValues method that checks if it was called only once and change the properties like this:

private static string defaultAssembly = "";

to

public static string DefaultAssembly {get; private set;}

with this change i probably would implement this change and it would help many coders :)

 

To bring the your code changes into the source please Fork my GitHub project, change the source and send me a pull-request.
https://github.com/SeriousM/WPFLocalizationExtension

 

Thanks!

Coordinator
Jan 19, 2012 at 8:02 AM

Hi all,

I liked the idea of default settings for the assembly and dictionary but would like to make the following proposal: Why not using dependency properties, to allow us changing the defaults freely throuout the visual tree?

Refer to my fork on GitHub under https://github.com/MrCircuit/WPFLocalizationExtension

I've implemented this leading to a nice XAML code like this:

<Window x:Class="AssemblyTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Engine="clr-namespace:WPFLocalizeExtension.Engine;assembly=WPFLocalizeExtension"
        Engine:LocalizeDictionary.DesignCulture="de"
        Engine:LocalizeDictionary.DefaultAssembly="AssemblyTest"
        Engine:LocalizeDictionary.DefaultDictionary="Strings"
        xmlns:lex="clr-namespace:WPFLocalizeExtension.Extensions;assembly=WPFLocalizeExtension"
        Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="114" d:DesignWidth="248" SizeToContent="WidthAndHeight">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />               
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Content="{lex:LocText TestText}" Height="28" HorizontalAlignment="Center"/>
        <StackPanel Name="TestPanel" Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Engine:LocalizeDictionary.DefaultDictionary="Strings2">
            <Button Content="{lex:LocText ButtonDE}" Height="23" Width="75" Click="ButtonDE_Click" Engine:LocalizeDictionary.DefaultAssembly="AssemblyTestResourceLib" />
            <Button Content="{lex:LocText ButtonEN}" Height="23" Width="75" Click="ButtonEN_Click" />
        </StackPanel>
        <Button HorizontalAlignment="Center" Name="ButtonAssembly" Content="Assembly" Grid.Row="2" Height="23" Width="75" Click="ButtonAssembly_Click" />
    </Grid>
</Window>

Best,

Uwe

Coordinator
Jan 20, 2012 at 2:34 PM

Hi Uwe,

this could work, yes.
does this work well with data templates?

best regards,
bernhard

Coordinator
Jan 23, 2012 at 6:56 AM

After small adaptations now, yes ;-)

Mar 16, 2012 at 3:23 PM

Hi,

I took the source code from https://github.com/MrCircuit/WPFLocalizationExtension and built it. I had to include the XAMLMarkupExtensions in my project now to continue to use the WPFLocalizeExtension. Is this correct, or am I building it incorrectly?

Assuming that I do need to include the new reference, everything seems to work...except the designer in VS2010 won't load anymore, and I get this exception and stack trace. I do not reference the XAMLMarkupExtensions assembly in my XAML, but it was required to get the project to compile. It does run correctly, I just have this designer issue...

 

Any thoughts?

-Dan

 

at System.Reflection.Adds.AssemblyProxy.GetResolvedAssembly() at System.Reflection.Adds.AssemblyProxy.GetHashCode() at Microsoft.Windows.Design.Metadata.ReflectionMetadataContext.Reflectable`1.GetHashCode() at System.Collections.Generic.GenericEqualityComparer`1.GetHashCode(T obj) at System.Collections.Generic.HashSet`1.InternalGetHashCode(T item) at System.Collections.Generic.HashSet`1.Contains(T item) at Microsoft.Windows.Design.Metadata.ReflectionMetadataContext.CanParentContextSupportType(Reflectable`1 type) at Microsoft.Windows.Design.Metadata.ReflectionMetadataContext.GetTypeForReflectableType(Reflectable`1 reflectableType) at MS.Internal.Metadata.ClrType.GetTypeMetadata(Type type) at MS.Internal.Metadata.ClrType.get_BaseType() at MS.Internal.Design.Metadata.Xaml.XamlType.<GetAllAttachableProperties>d__7.MoveNext() at MS.Internal.Design.Metadata.Xaml.XamlType.<FindAttachableProperties>d__0.MoveNext() at Microsoft.Windows.Design.Metadata.Xaml.XamlExtensionImplementations.<FindAttachableProperties>d__7.MoveNext() at MS.Internal.VirtualModel.VirtualModelPropertyCollection.<GetUncachedProperties>d__0.MoveNext() at System.Linq.Buffer`1..ctor(IEnumerable`1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source) at MS.Internal.VirtualModel.VirtualModelPropertyCollection.GetEnumerator() at MS.Internal.VirtualModel.VirtualModelUtilities.WrapProperties(IVirtualModelItem item) at MS.Internal.VirtualModel.VirtualModelItem.System.ComponentModel.ICustomTypeDescriptor.GetProperties() at System.ComponentModel.TypeDescriptor.MergedTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetProperties() at System.ComponentModel.TypeDescriptor.GetPropertiesImpl(Object component, Attribute[] attributes, Boolean noCustomTypeDesc, Boolean noAttributes) at System.Windows.PropertyPath.ResolvePropertyName(String name, Object item, Type ownerType, Object context, Boolean throwOnError) at MS.Internal.Data.PropertyPathWorker.GetInfo(Int32 k, Object item, SourceValueState& svs) at MS.Internal.Data.PropertyPathWorker.ReplaceItem(Int32 k, Object newO, Object parent) at MS.Internal.Data.PropertyPathWorker.UpdateSourceValueState(Int32 k, ICollectionView collectionView, Object newValue, Boolean isASubPropertyChange) at MS.Internal.Data.ClrBindingWorker.AttachDataItem() at System.Windows.Data.BindingExpression.Activate(Object item) at System.Windows.Data.BindingExpression.AttachToContext(AttachAttempt attempt) at System.Windows.Data.BindingExpression.AttachOverride(DependencyObject target, DependencyProperty dp) at System.Windows.Data.MultiBindingExpression.AttachBindingExpression(Int32 i, Boolean replaceExisting) at System.Windows.Data.MultiBindingExpression.AttachOverride(DependencyObject d, DependencyProperty dp) at System.Windows.Data.BindingExpressionBase.Attach(DependencyObject target, DependencyProperty dp) at System.Windows.Data.BindingExpressionBase.OnAttach(DependencyObject d, DependencyProperty dp) at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal) at System.Windows.Data.BindingOperations.SetBinding(DependencyObject target, DependencyProperty dp, BindingBase binding) at MS.Internal.GridControl.GridRowAdornerBase..ctor(ModelItem gridItem, ModelItem rowItem) at MS.Internal.GridControl.GridRowSelectAdorner..ctor(ModelItem grid, ModelItem row) at MS.Internal.GridControl.GridDesigner.EnsureRowAdorners(ModelItem rowDefModel, Boolean withHandle) at MS.Internal.GridControl.GridDesigner.EnsureAllRowAdorners() at MS.Internal.GridControl.GridDesigner.InitGridDesigner() at MS.Internal.GridControl.GridDesigner.GetDesigner(ModelItem gridItem) at MS.Internal.GridControl.AlwaysVisibleGridAdornerProvider.Activate(ModelItem item) at Microsoft.Windows.Design.Interaction.AdornerProvider.InvokeActivate(EditingContext context, ModelItem item) at MS.Internal.Features.AdornerProviderFeatureConnector.FeatureProvidersAdded(ModelItem item, IEnumerable`1 extensions) at Microsoft.Windows.Design.Policies.PolicyDrivenFeatureConnector`1.UpdateFeatureProviders() at MS.Internal.Features.PolicyDrivenToolFeatureConnector`1.UpdateCurrentTool(Tool newTool) at MS.Internal.Features.PolicyDrivenToolFeatureConnector`1.<.ctor>b__0(Tool newTool) at Microsoft.Windows.Design.ContextItemManager.SubscribeProxy`1.SubscribeContext(ContextItem item) at Microsoft.Windows.Design.SubscribeContextCallback.Invoke(ContextItem item) at Microsoft.Windows.Design.EditingContext.DefaultContextItemManager.OnItemChanged(ContextItem item) at Microsoft.Windows.Design.EditingContext.DefaultContextItemManager.SetValue(ContextItem value) at MS.Internal.Host.ToolSubsystem.ActivateTool(ToolFactory toolFactory) at MS.Internal.Host.ToolSubsystem..ctor(EditingContext editingContext, DesignerContext designerContext) at MS.Internal.Host.Designer.Load() at MS.Internal.Designer.VSDesigner.Load() at MS.Internal.Designer.VSIsolatedDesigner.VSIsolatedView.Load() at MS.Internal.Designer.VSIsolatedDesigner.VSIsolatedDesignerFactory.Load(IsolatedView view) at MS.Internal.Host.Isolation.IsolatedDesigner.BootstrapProxy.LoadDesigner(IsolatedDesignerFactory factory, IsolatedView view) at MS.Internal.Host.Isolation.IsolatedDesigner.BootstrapProxy.LoadDesigner(IsolatedDesignerFactory factory, IsolatedView view) at MS.Internal.Host.Isolation.IsolatedDesigner.Load() at MS.Internal.Designer.DesignerPane.LoadDesignerView(Boolean isReload)

 

 

System.Reflection.Adds.UnresolvedAssemblyException

Type universe cannot resolve assembly: XAMLMarkupExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.

Coordinator
Mar 16, 2012 at 3:41 PM

You are right - you need the XAMLMarkupExtensions library. I suggest to include this library with the nuget-package that comes along with the XAMLMarkupExtensions project. It should all work then properly.

Best regards,

Uwe

Coordinator
Mar 16, 2012 at 3:46 PM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Mar 16, 2012 at 4:00 PM

Yes, you need a reference to the XAMLMarkupExtensions library in your project just to get it to compile. I have that, and the application RUNS fine.

But even though it compiles and runs fine, the designer in VS2010 gives me the above exception and will not load. Do I need to add something to the XAML to help this go away?

-Dan

Coordinator
Mar 16, 2012 at 4:27 PM

The XAML code needs no extra reference to this libtrary.

Could you please paste the particular XAML that crashes? I will try to resolve this issue.

Mar 16, 2012 at 4:35 PM

Here's the file. I'm just doing a proof-of-concept at this point, to see how easy it is to swap languages on the fly.

I really like the ability to set the default assembly and dictionary, so the "binding" isn't as cluttered.

<Window x:Class="LanguageTester.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:localizeEngine="clr-namespace:WPFLocalizeExtension.Engine;assembly=WPFLocalizeExtension"
		xmlns:localExt="clr-namespace:WPFLocalizeExtension.Extensions;assembly=WPFLocalizeExtension"
		localizeEngine:LocalizeDictionary.DesignCulture="en"
		localizeEngine:LocalizeDictionary.DefaultAssembly="LanguageTester"
		localizeEngine:LocalizeDictionary.DefaultDictionary="Strings"
		Title="MainWindow"
		Height="350"
		Width="525">
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition />
			<RowDefinition />
			<RowDefinition />
			<RowDefinition />
			<RowDefinition />
			<RowDefinition />
			<RowDefinition />
			<RowDefinition />
		</Grid.RowDefinitions>
		<Button Grid.Row="0"
				Content="{localExt:LocText Button}" />
		<TextBlock Grid.Row="1"
				   HorizontalAlignment="Center"
				   VerticalAlignment="Center"
				   Text="{localExt:LocText TextBlock}" />
		<Label Grid.Row="2"
			   HorizontalAlignment="Center"
			   VerticalAlignment="Center"
			   Content="{localExt:LocText Label}" />
		<GroupBox Grid.Row="3"
				  HorizontalAlignment="Stretch"
				  VerticalAlignment="Stretch"
				  Header="{localExt:LocText GroupBox}" />
		<CheckBox Grid.Row="4"
				  HorizontalAlignment="Center"
				  VerticalAlignment="Center"
				  Content="{localExt:LocText CheckBox}" />
		<RadioButton Grid.Row="5"
					 HorizontalAlignment="Center"
					 VerticalAlignment="Center"
					 Content="{localExt:LocText RadioButton}" />
		<ComboBox x:Name="myComboBox"
				  Grid.Row="6"
				  HorizontalAlignment="Center"
				  VerticalAlignment="Center"
				  MinWidth="150" />
		<Button Grid.Row="7"
				Content="Change Language"
				Click="Button_Click" />
	</Grid>
</Window>
-Dan
Coordinator
Mar 16, 2012 at 4:53 PM

I'm sorry, but your code looks well and runs perfectly in my Visual Studio. Therefore I would like to ask you to send me the solution via pm?

Mar 16, 2012 at 5:48 PM

Hi,

Okay, nevermind. I went to check on the DLL in Windows, and it was marked as "blocked", since it wasn't compiled locally and was actually included in the ZIP that I downloaded from codeplex.

After unblocking, I no longer get the error.

Sorry, and thanks for the helpful info!

-Dan

Apr 7 at 1:25 AM
I just stumbled across this thread and wonder what happened to the automatic assembly detection?
Using reflection, we could avoid magic strings in our XAML that break when we rename an assembly.
It could also fix some problems where the attached properties can not be found because a parent property is missing (GridViewColumns etc.).