none
DataTemplate doesn't redraw (Possible bug + workaround)

    Question

  • Today I bumped into an issue where the DataTemplate for my custom object didn't get updated when I changed the Application's resources. The situation is as follow:

     

    I have a custom object which is bound to the Content property of the ContentPresenter on my window. This custom object (plain .NET) has a DataTemplate defined in one of the resource dictionaries attached to the current application's Resource property. Now, whenever I change the resource dictionary for the entire application to another dictionary, which contains data templates for the same object, these objects don't get rerendered. My styles/controltemplates update perfectly, but the data templates don't change. So I created a seperate project, the very basic, and was able to simulate the same behaviour there.

     

    Now it might be that I'm missing something, that the datatemplates shouldn't rerender themselves. The project I created is very basic, so I thought I'd copy-paste it here. You can also download the project of my website, here.

     

    Edit: I tried calling InvalidateVisual after changing the resources, but it has no effect either.

    Edit2: I uploaded a second sample illustrating this problem which includes styles, you can clearly see the styles getting updated, while the data templates won't. You can download the updated sample from my website here.

    Edit 3: I found a workaround, if I re-set the dataproperty, after setting it to null first, it draws the content using the new datatemplate. Still looking for a way to let it redraw the complete visual tree. Workaround can be downloaded here.

     

    Thanks in advance !

    Regards, David

     

    MainWindow.xaml

    Code Snippet

    <Window x:Class="DataTemplateSkins.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:System="clr-namespace:System;assembly=mscorlib"

        xmlns:local="clr-namespace:DataTemplateSkins"

        Title="DataTemplate Skins" Height="86" Width="459">

     

        <Window.Resources>

            <ObjectDataProvider x:Key="Skins" MethodName="GetValues" ObjectType="{x:Type System:Enum}">

                <ObjectDataProvider.MethodParameters>

                    <x:Type TypeName="local:Skin" />

                <ObjectDataProvider.MethodParameters>

            <ObjectDataProvider>

        <Window.Resources>

        <Grid>

            <ComboBox Height="29" IsSynchronizedWithCurrentItem="True" Margin="16,12,0,0" Name="skinSelector"

                SelectionChanged="SkinsComboBox_SelectionChanged" VerticalAlignment="Top" ItemsSource="{Binding Mode=OneWay,

                Source={StaticResource Skins}}" HorizontalAlignment="Left" Width="117" />

            <ContentPresenter Margin="144,12,12,12" Content="{Binding RelativeSource={RelativeSource FindAncestor,

                AncestorType={x:Type local:MainWindow}}, Path=Data}" />

        <Grid>

    <Window>

     

    MainWindow.xaml.cs

    Code Snippet

    public enum Skin

    {

        Default,

        Bold

    }

     

    public partial class MainWindow

    {

        internal static readonly DependencyPropertyKey DataKey = DependencyProperty.RegisterReadOnly("Data", typeof(MyData),

            typeof(MainWindow), new PropertyMetadata(new MyData("Our TestData")));

        public static readonly DependencyProperty DataProperty = DataKey.DependencyProperty;

     

        public MainWindow()

        {

            InitializeComponent();

        }

     

        public MyData Data

        {

            get { return (MyData)GetValue(DataProperty); }

        }

     

        private void SkinsComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)

        {

            ResourceDictionary rd;

            switch ((Skin)((ComboBox)sender).SelectedItem)

            {

                case Skin.Bold:

                    rd = Application.LoadComponent(new Uri(@"Themes/Bold.xaml", UriKind.Relative)) as ResourceDictionary;

                    break;

                default:

                    rd = Application.LoadComponent(new Uri(@"Themes/Default.xaml", UriKind.Relative)) as ResourceDictionary;

                    break;

            }

            Application.Current.Resources = rd;

        }

    }

     

     

    MyData.cs 

     

    Code Snippet

    public class MyData

    {

        private readonly string _Data;

     

        public MyData(string data)

        {

            _Data = data;

        }

     

        public string Data

        {

            get

            {

                return _Data;

            }

        }

    }

     

     Default.xaml

    Code Snippet

    <ResourceDictionary

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:local="clr-namespace:DataTemplateSkins">

       

        <DataTemplate DataType="{x:Type local:MyData}">

            <TextBlock Text="{Binding Data}" VerticalAlignment="Center" />

        </DataTemplate>

    </ResourceDictionary>

     

     

    Bold.xaml

    Code Snippet

    <ResourceDictionary

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:local="clr-namespace:DataTemplateSkins">

     

        <DataTemplate DataType="{x:Type local:MyData}">

            <TextBlock FontWeight="ExtraBold" Text="{Binding Data}" VerticalAlignment="Center" />

        </DataTemplate>

    </ResourceDictionary>

     

    Wednesday, July 25, 2007 6:54 PM

Answers

  • This is by design.  ContentPresenter searches for an appropriate data template, but having found one it treats the result similar to a StaticResource - it simply uses the result as if you had set it directly as the value of ContentTemplate.  To do what you want would be fairly expensive - ContentPresenter would have to listen for changes from every resource dictionary along the path to the root, as well as app and system resources.  It didn't seem like a good thing to do - every CP would pay this price, even though 99% of them would never use the feature.

     

    Your workaround is fine.  It's just what I would do, too.

     

    I'll make a note of this as a feature request.  But since there's a simple workaround it's going to get a low priority.  Can you make a compelling argument for this feature?

    Tuesday, July 31, 2007 1:27 AM

All replies

  •  

    Anyone able to confirm this?

     

    All the samples I find regarding templates/skinning in WPF either use the same data templates for all skins, so the issue doesn't come up, or don't use data templates at all.

    Monday, July 30, 2007 5:45 PM
  • Hi

    When you want to change a behaviour of a resource dynamically on runtime you cannot use StaticResource you need to use DynamicResource...

    Hope this helps

    Regards
    Marlon
    Monday, July 30, 2007 5:57 PM
  •  

    Hi Marlon,

     

    thanks for the reply. I don't think I am using either of them in the context though. The StaticResource I use is regarding the drop-down box for showing the available skins. That part was supposed to be static. The second part is an object bound to a dependency property (ContentControl.Context) which is rendered by the data template automatically, because it's a plain .NET type. Is binding to an object considered a StaticResource?

     

    Kind regards,

    David

    Monday, July 30, 2007 6:06 PM
  • Hi Dav,

    I am sorry for not understanding you ... I got it completly wrong Smile

    Anyway, if I am not mistaken a datatemplate is always marked as Sealed so you cannot actually change it.... Again I am not sure about this ....

    Yet what I think you need (I hope I am not misunderstanding again) is this....

    http://www.codeproject.com/useritems/SkinningInWPF.asp?msg=2155537#xx2155537xx


    Hope it helps
    Monday, July 30, 2007 6:30 PM
  •  

    Hi Marlon,

     

    thanks again for replying. I checked out Josh's article this weekend, but he doesn't switch data templates at runtime, only the styles get changed when choosing a different skin. The same data templates are always being included from the base skin file.

     

    I managed to let WPF refresh the way my objects are rendered when a different data template is loaded in the application resources dictionary, so it is possible to change them at runtime, they just don't get redrawn. The workaround is to set content property on the ContentControl to null first and then setting it again to my object.

     

    I put up a very small project on my webspace illustrating the problem (link). Although the data templates are a bit simplistic, it only differs a single property on the textbox so it can easily be done with styles, it shows that they don't get re-rendered at all if you don't use the workaround, which in itself is very dirty.

     

    Kind regards,

    David

    Monday, July 30, 2007 6:49 PM
  • This is by design.  ContentPresenter searches for an appropriate data template, but having found one it treats the result similar to a StaticResource - it simply uses the result as if you had set it directly as the value of ContentTemplate.  To do what you want would be fairly expensive - ContentPresenter would have to listen for changes from every resource dictionary along the path to the root, as well as app and system resources.  It didn't seem like a good thing to do - every CP would pay this price, even though 99% of them would never use the feature.

     

    Your workaround is fine.  It's just what I would do, too.

     

    I'll make a note of this as a feature request.  But since there's a simple workaround it's going to get a low priority.  Can you make a compelling argument for this feature?

    Tuesday, July 31, 2007 1:27 AM
  •  

    Hi Sam, thanks for the info.

     

    I understand that it would have an impact on performance and that this pass is an overkill in most situations. I would however love to see this additional lookup pass occur when calling the InvalidateVisual method. I don't know if that's possible to include in the feature request.

     

    Otherwise I have no real compelling arguments.

     

    Regards,

    David

    Tuesday, July 31, 2007 12:10 PM