2012-02-28 15 views
12

Creación de una aplicación que tiene un tema personalizado de "Contraste alto" para uso al aire libre que se puede activar y desactivar durante el tiempo de ejecución. Esto funciona bien mediante la fusión y la no-fusión de un diccionario de recursos que contiene estilos, como a continuación ...DynamicResource for Style BasedOn

<Style x:Key="{x:Type MenuItem}" TargetType="{x:Type MenuItem}"> 
    <Setter Property="OverridesDefaultStyle" Value="true"/> 
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/> 
    <Setter Property="Template" Value="{StaticResource Theme_MenuItemTemplate}"/> 
</Style> 

Esto funciona muy bien cuando el uso de un elemento de menú no especifica un estilo. Sin embargo, esto no es realista para muchas situaciones, ya que no hay forma de enlazar ItemsSource generados sin Styles. Por ejemplo:

<ContextMenu.ItemContainerStyle> 
    <Style TargetType="MenuItem"> 
     <Setter Property="Header" Value="{Binding Path=Name}"/> 
     <Setter Property="IsCheckable" Value="True"/> 
     <Setter Property="IsChecked" Value="{Binding Path=Checked}"/> 
     <EventSetter Event="Checked" Handler="HistoryItem_Checked"/> 
    </Style> 
</ContextMenu.ItemContainerStyle> 

Cada otro puesto en StackOverflow dice que sólo tiene que hacer esto ...

<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}"> 
    <!-- Your overrides --> 
</Style> 

Pero esto no funciona para mi situación porque mi BasedOn puede y va a cambiar en tiempo de ejecución (y, por supuesto, no puede usar la extensión DynamicResource en la propiedad BasedOn). Hacer esto en mi aplicación actualmente conduce a controles que anulan su estilo cuando se carga el control, mientras que todos los otros controles cambian correctamente sin recargar.

Así que mi pregunta ...

¿Hay una manera de conseguir la extensión DynamicResource trabajar para BasedOn o hay otro método/hack que puede poner en práctica para conseguir que esto funcione?

Respuesta

7

Finalmente encontrado una solución para un DynamicResouceStyle.BasedOn para el uso de un AttachedDependencyProperty.

Aquí está la solución para ItemsControl.ItemContainerStyle (se puede modificar fácilmente para cambiar FrameworkElement.Style)

public class DynamicContainerStyle 
{ 
    public static Style GetBaseStyle(DependencyObject obj) 
    { 
     return (Style)obj.GetValue(BaseStyleProperty); 
    } 

    public static void SetBaseStyle(DependencyObject obj, Style value) 
    { 
     obj.SetValue(BaseStyleProperty, value); 
    } 

    // Using a DependencyProperty as the backing store for BaseStyle. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty BaseStyleProperty = 
     DependencyProperty.RegisterAttached("BaseStyle", typeof(Style), typeof(DynamicContainerStyle), new UIPropertyMetadata(DynamicContainerStyle.StylesChanged)); 

    public static Style GetDerivedStyle(DependencyObject obj) 
    { 
     return (Style)obj.GetValue(DerivedStyleProperty); 
    } 

    public static void SetDerivedStyle(DependencyObject obj, Style value) 
    { 
     obj.SetValue(DerivedStyleProperty, value); 
    } 

    // Using a DependencyProperty as the backing store for DerivedStyle. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty DerivedStyleProperty = 
     DependencyProperty.RegisterAttached("DerivedStyle", typeof(Style), typeof(DynamicContainerStyle), new UIPropertyMetadata(DynamicContainerStyle.StylesChanged)); 

    private static void StylesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) 
    { 
     if (!typeof(System.Windows.Controls.ItemsControl).IsAssignableFrom(target.GetType())) 
      throw new InvalidCastException("Target must be ItemsControl"); 

     var Element = (System.Windows.Controls.ItemsControl)target; 

     var Styles = new List<Style>(); 

     var BaseStyle = GetBaseStyle(target); 

     if (BaseStyle != null) 
      Styles.Add(BaseStyle); 

     var DerivedStyle = GetDerivedStyle(target); 

     if (DerivedStyle != null) 
      Styles.Add(DerivedStyle); 

     Element.ItemContainerStyle = MergeStyles(Styles); 
    } 

    private static Style MergeStyles(ICollection<Style> Styles) 
    { 
     var NewStyle = new Style(); 

     foreach (var Style in Styles) 
     { 
      foreach (var Setter in Style.Setters) 
       NewStyle.Setters.Add(Setter); 

      foreach (var Trigger in Style.Triggers) 
       NewStyle.Triggers.Add(Trigger); 
     } 

     return NewStyle; 
    } 
} 

Y aquí es un ejemplo ...

<!-- xmlns:ap points to the namespace where DynamicContainerStyle class lives --> 
<MenuItem Header="Recent" 
    ItemsSource="{Binding Path=RecentFiles}" 
    IsEnabled="{Binding RelativeSource={RelativeSource Self}, Path=HasItems}" 
    ap:DynamicContainerStyle.BaseStyle="{DynamicResource {x:Type MenuItem}}"> 
    <ap:DynamicContainerStyle.DerivedStyle> 
     <Style TargetType="MenuItem"> 
      <EventSetter Event="Click" Handler="RecentFile_Clicked"/> 
     </Style> 
    </ap:DynamicContainerStyle.DerivedStyle> 
    <MenuItem.ItemTemplate> 
     <DataTemplate> 
      <TextBlock Text="{Binding}"/> 
     </DataTemplate> 
    </MenuItem.ItemTemplate> 
</MenuItem> 

Aquí está una versión modificada que establece el FrameworkElement.Style en cambio en mi respuesta a otra publicación: Setting a local implicit style different from theme-style/alternative to BasedOn DynamicResource

+0

Hay una manera más fácil de 'copiar' los estilos base. Agregué esto en una nueva respuesta. – aliceraunsbaek

1

Tus estilos deben estar en una etiqueta UIElement.Resources. Esto se puede borrar dinámicamente y repoblar.

hago algo similar pero no tan complejo como esto:

MobileApp.Get().Resources.MergedDictionaries.Clear(); 

Uri uri = new Uri("/Resources/DayModeButton.xaml", UriKind.Relative); 
ResourceDictionary resDict = Application.LoadComponent(uri) as ResourceDictionary; 

resDict["SelectedColor"] = selectedColor; //change an attribute of resdict 

MobileApp.Get().Resources.MergedDictionaries.Add(resDict); 
+0

Eso es más o menos lo que hago para la fusión y diccionarios de recursos de desinstalarlo. No estoy seguro de lo que quieres decir con UIElement.Resoruces tag. – NtscCobalt

+0

He probado colocando el estilo en los UIElement.Resources en lugar de establecer de manera explícita en el estilo pero tiene el mismo efecto y no encuentro utilizando más fácil de leer. – NtscCobalt

3

Tengo un imp ligero rovement para NtscCobalts responder:

#region Type-specific function (FrameworkElement) 
    private static void StylesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) 
    { 
     var mergedStyles = GetMergedStyles<FrameworkElement>(target, GetBaseStyle(target), GetDerivedStyle(target)); // NOTE: change type on copy 

     var element = (FrameworkElement)target; // NOTE: change type on copy 

     element.Style = mergedStyles; 
    } 
    #endregion Type-specific function (FrameworkElement) 


    #region Reused-function 
    public static Style GetMergedStyles<T>(DependencyObject target, Style baseStyle, Style derivedStyle) where T : DependencyObject 
    { 
     if (!(target is T)) throw new InvalidCastException("Target must be " + typeof(T)); 

     if (derivedStyle == null) return baseStyle; 
     if (baseStyle == null) return derivedStyle; 

     var newStyle = new Style { BasedOn = baseStyle, TargetType = derivedStyle.TargetType }; 
     foreach (var setter in derivedStyle.Setters) newStyle.Setters.Add(setter); 
     foreach (var trigger in derivedStyle.Triggers) newStyle.Triggers.Add(trigger); 
     return newStyle; 

    } 
    #endregion Reused-function 

Ves, es posible establecer simplemente el estilo de base, al crear un nuevo estilo. De esta forma, los estilos de base del estilo base se encuentran automáticamente en la jerarquía.