ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

Re: Specify ResourceDictionary and ResourceKey in Binding

13 Apr 2015, 17:22

Hi,

Sorry for the late answer, I couldn't try much further until now.

I've tried to add it as a StaticResource that way:
<Grid.Resources>
    <ResourceDictionary>
        <ResourceDictionary x:Key="Strings" Source="{Binding Localization}"/>
    </ResourceDictionary>
</Grid.Resources>
Localization being a ResourceDictionary* in my DataContext. Then, I interrogate it that way:
<TextBlock
  Text="{Binding Item.ItemNameKey, Converter={StaticResource ResArgConverter}, ConverterParameter={StaticResource Strings}}"/>
The BuildTool does not throw me any error but as soon as I load the file in my application, it stops and throws me this error:
Resource\ResourceSystem\Src\ResourceSystem.cpp (l. 207): Resource ?l?`?`? ¶?pb?P]§\?`o?k?@|?? _???l?ÀKn°Jn?n?n?=n=n`o?°? M?0u??`?`? ¶?pb?P]§?\??d???°d?OConvertBack unimplemented not found
I guess it has to do with the converter I'm using, but so far I was declaring the StaticResource that way:
<Grid.Resources>
    <ResourceDictionary>
        <ResourceDictionary x:Key="Strings" Source=""localization.xaml"/>
    </ResourceDictionary>
</Grid.Resources>
and it was working great.

Do you have an idea of what might go wrong?
 
User avatar
sfernandez
Site Admin
Posts: 1912
Joined: 22 Dec 2011, 19:20

Re: Specify ResourceDictionary and ResourceKey in Binding

14 Apr 2015, 13:09

Bindings can only be used in DependencyProperties of DependencyObject instances, and ResourceDictionary is a simple object and ResourceDictionary.Source is just a CLR property. We will improve the error message so it gets clear what happened.

You have to set the source as in your last code, so parser can convert the string to a valid resource dictionary URI.
 
ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

Re: Specify ResourceDictionary and ResourceKey in Binding

14 Apr 2015, 18:51

How should I do if I want that dictionary to be chosen at runtime?

My goal is basically to reproduce your localization sample: having one dictionary per language available and to choose the language from the C++ side, in order to load the proper strings and provide the proper dictionary to the UI side.
In addition to that, the key is defined by the DataContext and cannot be hardcoded as you do in your localization sample.
 
User avatar
sfernandez
Site Admin
Posts: 1912
Joined: 22 Dec 2011, 19:20

Re: Specify ResourceDictionary and ResourceKey in Binding

17 Apr 2015, 16:28

Hi Alex,

Because the MultiBinding feature is not available, this scenario must be solved using some kind of workaround. As you are using the native SDK you have the option to create a complex converter that is able to directly bind properties from the DataContext, for example, the selected localization dictionary.

I created the following sample that shows how to do it:

LocalizationConverter
class LocalizationConverter : public DependencyObject, public IValueConverter,
    public IUITreeNode // this interface allows the Binding walk up the UI tree to find the DataContext
{
public:
    LocalizationConverter() : mOwner(0) { }
    ~LocalizationConverter() { }

    ResourceDictionary* GetResources() const
    {
        return GetValue<Ptr<ResourceDictionary> >(ResourcesProperty).GetPtr();
    }
    void SetResources(ResourceDictionary* resources)
    {
        SetValue<Ptr<ResourceDictionary> >(ResourcesProperty, resources);
    }

    // From IValueConverter
    //@{
    Ptr<BaseComponent> Convert(BaseComponent* value,
        const Type* targetType, BaseComponent* parameter)
    {
        Ptr<BaseComponent> result;
        if (TryConvert(value, targetType, parameter, result))
            return result;

        NS_ERROR("LocalizationConverter: failed to convert value '%'", value->ToString());
    }
    NsBool TryConvert(BaseComponent* value,
        const Type* targetType, BaseComponent* parameter,
        Ptr<BaseComponent>& result)
    {
        ResourceDictionary* resources = GetResources();
        if (resources != 0)
        {
            Ptr<ResourceKeyString> key = ResourceKeyString::Create(value->ToString().c_str());
            BaseComponent* resource;
            if (resources->Find(key.GetPtr(), resource))
            {
                result.Reset(resource);
                return true;
            }
        }
        result.Reset();
        return false;
    }
    Ptr<BaseComponent> ConvertBack(BaseComponent* value,
        const Type* targetType, BaseComponent* parameter)
    {
        NS_ERROR("ConvertBack not implemented");
    }
    NsBool TryConvertBack(BaseComponent* value,
        const Type* targetType, BaseComponent* parameter,
        Ptr<BaseComponent>& result)
    {
        NS_ERROR("ConvertBack not implemented");
    }
    //@}

    // From IUITreeNode
    //@{
    IUITreeNode* GetNodeParent() const { return mOwner; }
    void SetNodeParent(IUITreeNode* parent) { mOwner = parent; }
    Core::BaseComponent* FindNodeResource(IResourceKey* key) const
    {
        if (mOwner)
            return mOwner->FindNodeResource(key);
        else
            return DependencyProperty::GetUnsetValue();
    }
    Core::BaseComponent* FindNodeName(const NsChar* name) const
    {
        if (mOwner)
            return mOwner->FindNodeName(name);
        else
            return 0;
    }
    ObjectWithNameScope FindNodeNameAndScope(const NsChar* name) const
    {
        if (mOwner)
            return mOwner->FindNodeNameAndScope(name);
        else
            return ObjectWithNameScope();
    }
    //@}

public:
    static const DependencyProperty* ResourcesProperty;

private:
    IUITreeNode* mOwner;

    NS_IMPLEMENT_INLINE_REFLECTION(LocalizationConverter, DependencyObject)
    {
        NsMeta<TypeId>("LocalizationConverter");

        NsImpl<IValueConverter>();
        NsImpl<IUITreeNode>();

        Ptr<DependencyData> data = NsMeta<DependencyData>(TypeOf<SelfClass>());
        data->RegisterProperty<Ptr<ResourceDictionary> >(ResourcesProperty, "Resources",
            PropertyMetadata::Create(Ptr<ResourceDictionary>::Null()));
    }
};

const DependencyProperty* LocalizationConverter::ResourcesProperty;
ViewModel
NS_DECLARE_SYMBOL(Id1)
NS_DECLARE_SYMBOL(Id2)
NS_DECLARE_SYMBOL(Localization)

typedef Delegate<void (BaseComponent* param)> CommandAction;

class DelegateCommand : public BaseCommand
{
public:
    DelegateCommand(const CommandAction& action) : _action(action) { }

    NsBool CanExecute(Core::BaseComponent* param) const { return true; }
    void Execute(Core::BaseComponent* param) const { _action(param); }

private:
    CommandAction _action;
};

class ViewModel : public BaseComponent, public INotifyPropertyChanged
{
public:
    ViewModel()
    {
        // Filled here just for testing, dictionaries can be loaded from a .xaml
        Ptr<ResourceKeyString> key;
        Ptr<BaseComponent> val;

        _localizationEnglish = *new ResourceDictionary();
        _localizationSpanish = *new ResourceDictionary();

        key = ResourceKeyString::Create("hi");
        val = Boxing::Box<NsString>("Hello");
        _localizationEnglish->Add(key.GetPtr(), val.GetPtr());
        val = Boxing::Box<NsString>("Hola");
        _localizationSpanish->Add(key.GetPtr(), val.GetPtr());

        key = ResourceKeyString::Create("bye");
        val = Boxing::Box<NsString>("Goodbye");
        _localizationEnglish->Add(key.GetPtr(), val.GetPtr());
        val = Boxing::Box<NsString>("Adios");
        _localizationSpanish->Add(key.GetPtr(), val.GetPtr());

        _selectedLocalization = _localizationEnglish.GetPtr();

        _switchLanguage = *new DelegateCommand(MakeDelegate(this, &ViewModel::OnSwitchLanguage));
    }

    ~ViewModel()
    {
        _destroyed(this);
    }

    const NsChar* GetId1() const { return "hi"; }
    const NsChar* GetId2() const { return "bye"; }

    ResourceDictionary* GetLocalization() const { return _selectedLocalization; }

    DelegateCommand* GetSwitchLanguage() const { return _switchLanguage.GetPtr(); }

    void OnSwitchLanguage(BaseComponent* param)
    {
        if (_selectedLocalization == _localizationEnglish.GetPtr())
        {
            _selectedLocalization = _localizationSpanish.GetPtr();
        }
        else
        {
            _selectedLocalization = _localizationEnglish.GetPtr();
        }

        _propertyChanged(this, PropertyChangedEventArgs(NSS(Localization)));
        _propertyChanged(this, PropertyChangedEventArgs(NSS(Id1))); // force update bindings to Id1
        _propertyChanged(this, PropertyChangedEventArgs(NSS(Id2))); // force update bindings to Id2
    }

    PropertyChangedEventHandler& PropertyChanged() { return _propertyChanged; }
    DestroyedEventHandler& Destroyed() { return _destroyed; }

private:
    ResourceDictionary* _selectedLocalization;
    Ptr<ResourceDictionary> _localizationEnglish;
    Ptr<ResourceDictionary> _localizationSpanish;
    Ptr<DelegateCommand> _switchLanguage;

    PropertyChangedEventHandler _propertyChanged;
    DestroyedEventHandler _destroyed;

    NS_IMPLEMENT_INLINE_REFLECTION(ViewModel, BaseComponent)
    {
        NsImpl<INotifyPropertyChanged>();

        NsProp("Id1", &ViewModel::GetId1);
        NsProp("Id2", &ViewModel::GetId2);
        NsProp("Localization", &ViewModel::GetLocalization);
        NsProp("SwitchLanguage", &ViewModel::GetSwitchLanguage);
    }
};
Now we can use this classes in a xaml control:

TestControl.xaml
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="TestControl">
    <Grid.Resources>
        <LocalizationConverter x:Key="localizationConverter" Resources="{Binding Localization}"/>
    </Grid.Resources>
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBlock Text="{Binding Id1, Converter={StaticResource localizationConverter}}"/>
        <TextBlock Text="{Binding Id2, Converter={StaticResource localizationConverter}}"/>
        <Button Content="Switch Language" Command="{Binding SwitchLanguage}"/>
    </StackPanel>
</Grid>
TestControl.cpp
struct TestControl : public Grid
{
    void OnPostInit()
    {
        Ptr<ViewModel> vm = *new ViewModel();
        SetDataContext(vm.GetPtr());
    }

    NS_IMPLEMENT_INLINE_REFLECTION(TestControl, Grid)
    {
        NsMeta<TypeId>("TestControl");
    }
}; 
And don't forget to register the components that are used in the xaml file, so parser can create them when building the xaml resource:
void NsRegisterComponents(...)
{
    NS_REGISTER_COMPONENT(LocalizationConverter)
    NS_REGISTER_COMPONENT(TestControl)
}
 
ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

Re: Specify ResourceDictionary and ResourceKey in Binding

27 Apr 2015, 18:12

Finally got time to try your solutionn, it's working exactly as expected. I did not think about adding attribute to converter object.

Thank you :)
 
ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

Re: Specify ResourceDictionary and ResourceKey in Binding

05 Jun 2015, 15:47

Hi,

I've tweaked the system a bit. I've added a generic data context, containing a ResourceDictionary, to the root of my element tree, so that I'm able to use that ResourceDictionary with the Converter you showed me in any Control child of that root.

It works great when I'm adding elements via Xaml but it does not seem to work when adding elements with the C++ API. When interrogating the converter, the Resources are null. However, I've managed to print the Resources by overriding OnPostInit method and it looks correct. But right after that, when the Converter is actually interrogated, the Resources are null.
<UserControl.Resources>
    <ResourceDictionary>
        <ResourceConverter x:Key="AssetsConv" Resources="{Binding Strings}"/>
    </ResourceDictionary>
</UserControl.Resources>

[...]

<TextBlock Text="{Binding ItemNameKey, Converter={StaticResource StrConv}}"/>
Am I missing something? I'm sure the ResourceConverter is properly declared since I managed to print its resources in OnPostInit and the ItemNameKey is correct because I can print it without Converter.
 
User avatar
sfernandez
Site Admin
Posts: 1912
Joined: 22 Dec 2011, 19:20

Re: Specify ResourceDictionary and ResourceKey in Binding

08 Jun 2015, 21:01

Are you saying that ResourceConverter.Resources property returns null when ConvertTo() is called? Or that the dictionary returned is empty?

Probably you can add some log messages to detect when Resources dictionary is set, and when your converter is called.

When are you assigning the DataContext to the UserControl, so Resources="{Binding Strings}" can be resolved?
 
ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

Re: Specify ResourceDictionary and ResourceKey in Binding

09 Jun 2015, 10:35

When the converter is used, ResourceConverter.Resources is null.

[EDIT]
After further investigation, here is what seems to happen:

Create control
Test <= ResourceConverter.Resources null
Add control to UI tree
Test <= ResourceConverter.Resources valid
Converter interrogated but its Resources are null.
 
ZanAlex
Topic Author
Posts: 66
Joined: 16 Jan 2015, 17:46

Re: Specify ResourceDictionary and ResourceKey in Binding

15 Jun 2015, 18:48

Oh sorry, I've missed your last question.

What I'm doing is:
  • Creating the main Control (basically the Root of the element tree), giving it its DataContext;
  • Instancing my UserControl, one of its resources being a ResourceConverter bound to a field of the root's DataContext (at that point, the link is not resolved, which makes sense);
  • Adding my UserControl to the element tree
At that last point, if I have a look at the ResourceConverter, the Resources is correct and contains the correct information, but I can't see any of my strings.

I guess the ResourceConverter is correct but not the reference my UserControl is actually interrogating (if that makes sense). Is there a way to force the update of a binding ?

EDIT: Now that I've detailed my problem, I think it might be fixed the same way you're updating your language file in the sample you provided... Do you think it might be related? I'll give it a try.
 
User avatar
sfernandez
Site Admin
Posts: 1912
Joined: 22 Dec 2011, 19:20

Re: Specify ResourceDictionary and ResourceKey in Binding

15 Jun 2015, 19:31

Thanks, knowing the exact scenario I can try to reproduce what is happening and give you a proper solution.
I'll tell you more as soon as possible.

Who is online

Users browsing this forum: No registered users and 1 guest