My questions is very specific to ThemeResources in a Windows 10 Store App. Unfortunately several things available in \"classic\" WPF are different or not available here.
I have a solution based on a couple of 'helper' classes. First up is just a container object with a DependencyProperty
Value
that can be bound or set to {ThemeResource …}
:
public class DependencyObjectReference : DependencyObject where T : DependencyObject
{
#region Properties
public T Value
{
get { return (T)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
#endregion
#region Static Data
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value),
typeof(T),
typeof(DependencyObjectReference),
new PropertyMetadata(default(T)));
#endregion
}
Next is the meat of the solution: a 'selector' that contains a bunch of references and selects from them based on an index:
[ContentProperty(Name = nameof(References))]
public class DependencyObjectSelector : DependencyObject where T : DependencyObject
{
#region Constructors
public DependencyObjectSelector()
{
References = new DependencyObjectCollection();
}
#endregion
#region Properties
public DependencyObjectCollection References
{
get { return (DependencyObjectCollection)GetValue(ReferencesProperty); }
set { SetValue(ReferencesProperty, value); }
}
public Int32 SelectedIndex
{
get { return (Int32)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public T SelectedObject
{
get { return (T)GetValue(SelectedObjectProperty); }
set { SetValue(SelectedObjectProperty, value); }
}
#endregion
#region Event Handlers
private void Evt_OnVectorChangedReferences(IObservableVector sender, IVectorChangedEventArgs args)
{
UpdateSelectedObject();
}
#endregion
#region Private Implementation Methods
private void UpdateSelectedObject()
{
if (
References != null
&&
SelectedIndex >= 0
&&
SelectedIndex < References.Count
&&
References[SelectedIndex] is DependencyObjectReference
)
{
BindingOperations.SetBinding
(
this,
SelectedObjectProperty,
new Binding
{
Source = References[SelectedIndex],
Path = new PropertyPath(nameof(DependencyObjectReference.Value))
}
);
}
else
{
ClearValue(SelectedObjectProperty);
}
}
private void OnReferencesPropertyChanged(DependencyObjectCollection oldValue, DependencyObjectCollection newValue)
{
if (oldValue != null)
oldValue.VectorChanged -= Evt_OnVectorChangedReferences;
if (newValue != null)
newValue.VectorChanged += Evt_OnVectorChangedReferences;
}
private static void ReferencesPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
{
DependencyObjectSelector _this = (DependencyObjectSelector)dobj;
_this.OnReferencesPropertyChanged(args.OldValue as DependencyObjectCollection, args.NewValue as DependencyObjectCollection);
}
private static void SelectedIndexPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
{
DependencyObjectSelector _this = (DependencyObjectSelector)dobj;
_this.UpdateSelectedObject();
}
#endregion
#region Static Data
public static readonly DependencyProperty ReferencesProperty =
DependencyProperty.Register(nameof(References),
typeof(DependencyObjectCollection),
typeof(DependencyObjectSelector),
new PropertyMetadata(null, ReferencesPropertyChanged));
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register(nameof(SelectedIndex),
typeof(Int32),
typeof(DependencyObjectSelector),
new PropertyMetadata(-1, SelectedIndexPropertyChanged));
public static readonly DependencyProperty SelectedObjectProperty =
DependencyProperty.Register(nameof(SelectedObject),
typeof(T),
typeof(DependencyObjectSelector),
new PropertyMetadata(default(T)));
#endregion
}
As you can see, this class holds a collection of references and binds its SelectedObject
property to the Value
of the appropriate reference.
This binding is updated when SelectedIndex
changes, and when the reference collection itself changes.
These classes obviously can't be used in XAML as they are parameterized by type T
(which must derive from DependencyObject
). However, it's
a simple matter to subclass them:
public sealed class BrushReference : DependencyObjectReference
{
}
public sealed class BrushSelector : DependencyObjectSelector
{
}
The trick now is to place a BrushSelector
in some accessible ResourceDictionary
(such as your Page
's Resources
) and then bind
to its SelectedObject
property:
Note that it's not necessary to specify a
when defining the BrushSelector
in XAML because of
the [ContentProperty]
attribute on the selector class.
A few other comments -- first, I'd prefer to have the SelectedObject
be a read-only DependencyProperty
, as it should never
be set by markup or code outside the selector, but UWP doesn't yet support that. Second, the References
property must be
of type DependencyObjectCollection
and the property itself must be a DependencyProperty
or the theme changes don't propagate
correctly. Finally, you can even use your own theme resources, and if your app doesn't specify an explicit theme, when you change
the theme in Windows Control Panel (e.g., Light -> Dark or vice-versa), those colors will update as well.