Re: Specify ResourceDictionary and ResourceKey in Binding
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:
Localization being a ResourceDictionary* in my DataContext. Then, I interrogate it that way:
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:
I guess it has to do with the converter I'm using, but so far I was declaring the StaticResource that way:
and it was working great.
Do you have an idea of what might go wrong?
Sorry for the late answer, I couldn't try much further until now.
I've tried to add it as a StaticResource that way:
Code: Select all
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary x:Key="Strings" Source="{Binding Localization}"/>
</ResourceDictionary>
</Grid.Resources>
Code: Select all
<TextBlock
Text="{Binding Item.ItemNameKey, Converter={StaticResource ResArgConverter}, ConverterParameter={StaticResource Strings}}"/>
Code: Select all
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
Code: Select all
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary x:Key="Strings" Source=""localization.xaml"/>
</ResourceDictionary>
</Grid.Resources>
Do you have an idea of what might go wrong?
-
sfernandez
Site Admin
- Posts: 2991
- Joined:
Re: Specify ResourceDictionary and ResourceKey in Binding
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.
You have to set the source as in your last code, so parser can convert the string to a valid resource dictionary URI.
Re: Specify ResourceDictionary and ResourceKey in Binding
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.
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.
-
sfernandez
Site Admin
- Posts: 2991
- Joined:
Re: Specify ResourceDictionary and ResourceKey in Binding
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
ViewModel
Now we can use this classes in a xaml control:
TestControl.xaml
TestControl.cpp
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:
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
Code: Select all
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;
Code: Select all
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);
}
};
TestControl.xaml
Code: Select all
<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>
Code: Select all
struct TestControl : public Grid
{
void OnPostInit()
{
Ptr<ViewModel> vm = *new ViewModel();
SetDataContext(vm.GetPtr());
}
NS_IMPLEMENT_INLINE_REFLECTION(TestControl, Grid)
{
NsMeta<TypeId>("TestControl");
}
};
Code: Select all
void NsRegisterComponents(...)
{
NS_REGISTER_COMPONENT(LocalizationConverter)
NS_REGISTER_COMPONENT(TestControl)
}
Re: Specify ResourceDictionary and ResourceKey in Binding
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
Thank you
Re: Specify ResourceDictionary and ResourceKey in Binding
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.
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.
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.
Code: Select all
<UserControl.Resources>
<ResourceDictionary>
<ResourceConverter x:Key="AssetsConv" Resources="{Binding Strings}"/>
</ResourceDictionary>
</UserControl.Resources>
[...]
<TextBlock Text="{Binding ItemNameKey, Converter={StaticResource StrConv}}"/>
-
sfernandez
Site Admin
- Posts: 2991
- Joined:
Re: Specify ResourceDictionary and ResourceKey in Binding
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?
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?
Re: Specify ResourceDictionary and ResourceKey in Binding
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.
[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.
Re: Specify ResourceDictionary and ResourceKey in Binding
Oh sorry, I've missed your last question.
What I'm doing is:
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.
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
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.
-
sfernandez
Site Admin
- Posts: 2991
- Joined:
Re: Specify ResourceDictionary and ResourceKey in Binding
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.
I'll tell you more as soon as possible.
Who is online
Users browsing this forum: Ahrefs [Bot] and 16 guests