Setting or modifying ThemeResource in code

后端 未结 5 660
野趣味
野趣味 2021-02-06 04:14

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.

5条回答
  •  灰色年华
    2021-02-06 05:01

    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.

提交回复
热议问题