UmmmActually
UmmmActually

Reputation: 217

WPF MVVM DataTemplates - View Not updating

I'm working on a digital signage WPF application and the basic structure is setup as follows:

I have a single view with a grid in it bound to a playlist object. The playlist contains panes, the panes contain playlist items, and the playlist items each contain a content item. I use DataTemplates to build the view for each piece, e.g.

    <!-- This template represents a single content pane, no real display here, just empty space with a height and width -->
    <DataTemplate DataType="{x:Type entities:ContentPane}">
        <ContentControl 
            Content="{Binding CurrentPlaylistItem, Mode=OneWay}" 
            Height="{Binding Height, Mode=OneTime}" 
            Width="{Binding Width, Mode=OneTime}" 
            HorizontalAlignment="Left"
            VerticalAlignment="Top"                
            />
    </DataTemplate>

    <!-- This is a playlist item which is contained within a ContentPane.
         This also has no real display, just a placeholder for the content item, and will likely contain some transition information-->
    <DataTemplate DataType="{x:Type entities:PlaylistItem}">
        <inf:TransitionableContentControl Content="{Binding ContentItem}"></inf:TransitionableContentControl>
    </DataTemplate>


    <!-- image item template 
         the path can be any valid uri e.g. http://... or file://... -->
    <DataTemplate DataType="{x:Type contentTypes:ImageContentType}">
        <Grid Background="Transparent" x:Name="ImageType">
            <Image Source="{Binding Bitmap, Mode=OneTime}"></Image>
            <inf:BusinessAd
                        ContainerHeight="{Binding ImageHeight, Mode=OneTime}"
                        ContainerWidth="{Binding ImageWidth, Mode=OneTime}"                            
                        Visibility="{Binding AdText, Converter={StaticResource _VisibilityConverter}, Mode=OneWay}"
                        Text="{Binding AdText.Text, Mode=OneTime}"
                        AdFontSize="{Binding AdText.TextStyle.FontSize}"
                        AdFontFamily="{Binding AdText.TextStyle.FontFamily}">
                <ContentControl 
                    Content="{Binding AdText, Mode=OneTime}"                         
                    ContentTemplate="{StaticResource BusinessAdTextTemplate}">
                </ContentControl>
            </inf:BusinessAd>
        </Grid>
    </DataTemplate>

As the datacontext changes, the content in each pane transitions from one item to the next. I'm running into a problem where the user is placing the same item in a pane twice in a row, be it a video, image, etc. What happens is the change goes undetected and the UI does not update. In the case of a video, it freezes on the last frame of the first video and the whole application hangs.

I have tried doing an OnPropertyChanged("ContentItem") within the PlaylistItem class, tried setting Shared="False" on my data templates, I tried changing properties on the ContentItem object and raising PropertyChanged events, nothing seems to work. I turned on tracing on the databinding to see what was happening and it all appears to be working correctly. When I change properties on the ContentItem it shows a new hash for the new item, but no change on the UI.

Within the TransitionableContentControl in the PlaylistItem, the OnContentChanged override is never hit when going from one content item to the same one. If I swap that control out with a regular ContentControl, no change.

Upvotes: 1

Views: 3085

Answers (2)

Anton Tykhyy
Anton Tykhyy

Reputation: 20056

Former Employee is correct about the diagnosis. Furthermore, unless you manage to re-assign the inner datacontexts your templates will not restart from the beginning, as you have noticed. You will have to assign your CurrentPlaylistItem to some default empty item first to reset stuff all the way through. To avoid races and nasty flicker, do it on a dispatcher priority higher than UI rendering. Try this:

// CurrentPlaylistItem = pli ; -- not good
Application.Current.Dispatcher.Invoke (new Action (() =>
{
    // clear item
    // null doesn't work, because TransitionableContentControl's
    // inner ContentControl would be empty and the brush created 
    // from it would be a null brush (no visual effect)
    CurrentPlaylistItem = new PlaylistItem () ;
    this.OnPropertyChanged ("CurrentPlaylistItem") ;
    Application.Current.Dispatcher.Invoke (new Action (() =>
    {
        // set new item, possibly same as old item
        // but WPF will have forgotten that because
        // data bindings caused by the assignment above
        // have already been queued at the same DataBind 
        // level and will execute before this assignment
        CurrentPlaylistItem = pli ; 
        this.OnPropertyChanged ("CurrentPlaylistItem") ;
    }), DispatcherPriority.DataBind) ;
})) ;

Upvotes: 1

The issue here is the ContentControl (or rather the underlying DependencyProperty framework) doesn't fire OnContentChanged when .Equals is true for old and new Content. One workaround is to have your own dependency property and transition on CoerceValue. I'll email you the code and you can post it here.

Upvotes: 1

Related Questions