Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

BasedOn and theme switching => DynamicBasedOn extension.

17 views
Skip to first unread message

Olivier

unread,
May 18, 2009, 3:27:31 AM5/18/09
to
Hello,

I'm currently working on a (yet another) theming library for WPF and I
have a problem.

I'm loading the theme at runtime, which means that I cant really use
BasedOn tag on style.

So far so good, I created a "DynamicBasedOn" tag that accept a
DynaimcResource binding. This extension is working quite well except
with data binding.
It seems that if a style which will be "dynamically" basedon use
databinding, the databinding is not refreshed / created / I dont
know :).

Here is the source code of my extension :


public static class Helper
{
public static readonly DependencyProperty
DynamicBasedOnProperty = DependencyProperty.RegisterAttached
("DynamicBasedOn", typeof(Style), typeof(Helper), new
UIPropertyMetadata(null, Helper.OnDynamicBasedOnChanged));

public static readonly DependencyProperty BaseStyleProperty =
DependencyProperty.RegisterAttached("BaseStyle", typeof(Style), typeof
(Helper), new UIPropertyMetadata(null));

public static Style GetDynamicBasedOn(DependencyObject obj)
{
return (Style)obj.GetValue(DynamicBasedOnProperty);
}

public static void SetDynamicBasedOn(DependencyObject obj,
Style value)
{
obj.SetValue(DynamicBasedOnProperty, value);
}

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

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

private static void OnDynamicBasedOnChanged(DependencyObject
d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement frameworkElement = d as FrameworkElement;
if (frameworkElement == null)
{
throw new InvalidOperationException("Cannot used
DynamicBasedOn on a non FrameworkElement object");
}

Style style = Helper.GetBaseStyle(d);
if (style == null)
{
style = frameworkElement.Style;
Helper.SetBaseStyle(d, style);
}

if (e.NewValue == style)
{
try
{
FrameworkElement parent =
frameworkElement;
while (parent != null)
{
if (parent.Resources.Contains
(style.TargetType))
{
// queue the work so that we are not
recursivly calling
// OnDynamicBasedOnChanged.
Dispatcher.CurrentDispatcher.BeginInvoke
(new Action(() =>
{
parent.Resources.Remove
(style.TargetType);
frameworkElement.SetResourceReference
(e.Property, style.TargetType);
}));
return;
}
parent = VisualTreeHelper.GetParent(parent) as
FrameworkElement;
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.TraceError(ex.Message);
}
}

if (style == null)
{
frameworkElement.Style = e.NewValue as Style;
}

if (style != null)
{
Style newStyle = new Style
(style.TargetType);
newStyle.Resources = style.Resources;
style.Setters.ForEach(a => newStyle.Setters.Add(a));
style.Triggers.ForEach(a => newStyle.Triggers.Add(a));
Style basedOnStyle = e.NewValue as Style;
newStyle.BasedOn = basedOnStyle;
frameworkElement.Style = newStyle;
}

}
}

And a little sample of how it can be used in XAML:

<TreeView ItemsSource="{Binding Path=Children}"
KeyboardNavigation.TabNavigation="Cycle">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding
Path=Expanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding
Path=Selected, Mode=TwoWay}" />
<Setter Property="Visibility" Value="{Binding
Path=Visibility, Mode=TwoWay}" />
<Setter Property="HorizontalContentAlignment"
Value="Stretch"/>
<Setter Property="VerticalContentAlignment"
Value="Stretch"/>
<Setter Property="funcom:Helper.DynamicBasedOn"
Value="{DynamicResource {x:Type TreeViewItem}}"/>
</Style>

<Style TargetType="ItemsPresenter">
<Setter Property="Grid.IsSharedSizeScope"
Value="True" />
</Style>

<HierarchicalDataTemplate DataType="{x:Type
local:EntityNode}"
ItemsSource="{Binding
Path=Children}">
<Grid Margin="{Binding RelativeSource=
{RelativeSource FindAncestor, AncestorType={x:Type
TreeViewItem}},Converter={StaticResource HeaderIndentConverter}}"
HorizontalAlignment="Stretch"
ToolTip="{Binding Path=Traits.Tooltip}" >
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="30"
Width="Auto" SharedSizeGroup="HeaderSizeGroup"/>
<ColumnDefinition Width="*" />
</
Grid.ColumnDefinitions>
<TextBlock TextTrimming="CharacterEllipsis"
Grid.Column="0"
Text="{Binding Path=Name}"
Margin="{Binding RelativeSource=
{RelativeSource FindAncestor, AncestorType={x:Type
TreeViewItem}},Converter={StaticResource HeaderUnindentConverter}}"/>
<GridSplitter Grid.Column="0" Width="5"
Margin="-5,0,0,0"/>
<ContentPresenter Grid.Column="1"
Content="{Binding Path=Editor}" />
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>


My main problem is that the

<Setter Property="IsExpanded" Value="{Binding
Path=Expanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding
Path=Selected, Mode=TwoWay}" />
<Setter Property="Visibility" Value="{Binding
Path=Visibility, Mode=TwoWay}" />

Are not updated anymore if I use the DynamicBasedOn tag.

Does anyone have an idea by any chance ? For my it seems I need to
find a way to "reset" the binding.

Olivier

unread,
May 18, 2009, 5:43:42 AM5/18/09
to
I found my problem :).

The problem was that I was actually removing the style from the
resource dictionnary if the style was defined in a parent element
dictionnary which was bad since then every item generated did not get
the style.

I'm still doing that if the style is defined in the element resource
dictionnary (other wise it does not get updated if you switch the
style).

So the new code looks like this (is anyone is interrested) :

private static void OnDynamicBasedOnChanged(DependencyObject
d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement frameworkElement = d as FrameworkElement;
if (frameworkElement == null)
{
throw new InvalidOperationException("Cannot used
DynamicBasedOn on a non FrameworkElement object");
}

Style style = Helper.GetBaseStyle(d);
if (style == null)
{
style = frameworkElement.Style;
Helper.SetBaseStyle(d, style);
}

Style dynamicStyle = e.NewValue as Style;
if (dynamicStyle == style)
{
// two cases :
// 1 - the style of the element is actually defined in
it's own resource. We need then to
// remove it from the element resource dictionnary and
set the "binding" of the style so that
// it get updated whenever the style for this type is
changed.
if (frameworkElement.Resources.Contains
(style.TargetType))
{
// we need to execute that after not in this
function, if not we will recurse (well actually not since we are
removing
// the dependence but the framework will think so
and raise an exception.
Dispatcher.CurrentDispatcher.BeginInvoke(new Action
(() =>
{
frameworkElement.Resources.Remove


(style.TargetType);
frameworkElement.SetResourceReference
(e.Property, style.TargetType);
}));
return;
}

// 2 - the style is defined on one of the parent, walk
the hierachy and try to find the style that is defined
// in a parent of the parent. At last resort, get the
default style.
else


{
FrameworkElement parent = frameworkElement;
while (parent != null)
{
if (parent.Resources.Contains

(style.TargetType) && parent.Resources[style.TargetType] != style)
{
dynamicStyle = parent.Resources
[style.TargetType] as Style;
break;


}
parent = VisualTreeHelper.GetParent(parent) as
FrameworkElement;
}
}

// no style found, get the default one.
if (dynamicStyle == style)
{
dynamicStyle = Application.Current.FindResource
(style.TargetType) as Style ;
}
}

// no style apply on this item, we should normally
traverse the visualtree to get the style.
// here we are just getting the global style for the
target type.


if (style == null)
{
frameworkElement.Style = e.NewValue as Style;
}

if (style != null)
{
Style newStyle = new Style
(style.TargetType);
newStyle.Resources = style.Resources;
style.Setters.ForEach(a => newStyle.Setters.Add(a));
style.Triggers.ForEach(a => newStyle.Triggers.Add(a));

newStyle.BasedOn = dynamicStyle;
frameworkElement.Style = newStyle;
}

}

0 new messages