Using a ContentControl and DataTemplate to indicate new and/or modified data March 6, 2007
Posted by Karl Hulme in .Net, C#, WPF, XAML.trackback
I was familiar with using DataTemplate’s for multiple items, say for items in a ListBox, but what if you want to use a DataTemplate for a single item?
Why would I want to do that? I can declare the UIElements I want, and bind the properties that I want. You don’t need a DataTemplate here!!
Well, not strictly true! If you want to be able to use properties of your business object within conditions that control the layout, then you’re going to need the services of DataTrigger’s. And although most UIElements have a Triggers property, DataTriggers are only supported on a DataTemplate. So this blog entry is an example of how to set that up…
Think about Visual Studio tabs for a second. Whenever you change the content of a document, a small asterisk appears after the filename to indicate to you that it’s been changed, as illustrated below.
I wanted to do a similar thing in my application. I wanted to..
- Show an asterisk whenever the data has been changed since the last save, and
- Show if an item was new (that is has never been saved.
For me, the second point is particularly important because my application deals with Customer entities, and until the customer is saved they don’t have the all-important customer number.
The first step is to add the required properties to the business object being bound to, and implement INotifyPropertyChanged so that we know when they’re changing. A heavily abridged/shell-like version of my Entity class is below. (Please note, the code below is only intended to highlight the need to define the properties and inplement INotifyPropertyChanged on the business object in use.)
using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; namespace MyNamespace { /// <summary> /// Represents an entity loaded from the database. /// </summary> public class Entity: INotifyPropertyChanged { #region properties private Int32? id; /// <summary> /// Gets the unique id of the entity. /// </summary> public Int32? Id { get { return id; } internal set { id = value; OnPropertyChanged("Id"); OnPropertyChanged("IsNew"); } } private EntityDefinition definition; /// <summary> /// Gets the type of the entity. /// </summary> public EntityDefinition Definition { get { return definition; } } /// <summary> /// Gets a value that indicates if the entity has been modified /// since it was loaded. /// </summary> public Boolean IsModified { get { return changeHistory.Count > 0; } } private EntityDataFieldChangeCollection changeHistory = new EntityDataFieldChangeCollection(); public EntityDataFieldChangeCollection ChangeHistory { get { return changeHistory; } } #endregion #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(String propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion } }
Then in XAML, define a ContentControl and set the Content of that control to be an instance of your business object. Clearly the ContentControl doesn’t know how to render said business object, so I’ve provided a DataTemplate. This DataTemplate uses a horizontal StackPanel to lay out a series of TextBlock elements. I then use the Triggers to turn the visibility on and off, dependent upon the IsNew and IsModified properties.
<ContentControl Grid.Row="0" Content="{Binding Source={StaticResource entity}}"> <ContentControl.ContentTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Name="newBlock" Text="New " Visibility="Visible"/> <TextBlock Name="nameBlock" Text="{Binding Path=Definition.Name}" Visibility="Visible"/> <TextBlock Name="spacer" Text=" " Visibility="Collapsed"/> <TextBlock Name="idBlock" Text="{Binding Path=Id}" Visibility="Collapsed" /> <TextBlock Name="modifiedBlock" Text="*" Visibility="Collapsed"/> </StackPanel> <DataTemplate.Triggers> <DataTrigger Binding="{Binding Path=IsNew}" Value="False"> <DataTrigger.Setters> <Setter TargetName="newBlock" Property="UIElement.Visibility" Value="Collapsed" /> <Setter TargetName="idBlock" Property="UIElement.Visibility" Value="Visible" /> <Setter TargetName="spacer" Property="UIElement.Visibility" Value="Visible" /> </DataTrigger.Setters> </DataTrigger> <DataTrigger Binding="{Binding Path=IsModified}" Value="True"> <DataTrigger.Setters> <Setter TargetName="modifiedBlock" Property="UIElement.Visibility" Value="Visible" /> </DataTrigger.Setters> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ContentControl.ContentTemplate> </ContentControl>
So the tab headers throughout the life of one of my Customer entities would start out as follows…
Then, I would expect something to be changed…
Once the first set of changes can be made, it should be saved. This will result in an Id being returned from the database…
Obviously the data could be modified again at this point…
I originally implemented this feature using IValueConverters (and the like) but I believe the XAML solution is much nicer.
So if you need to display information related to a business object and you have some conditions associated with the display, then you could try using a data-templated content control.




Comments»
No comments yet — be the first.