I have figured out a good way of applying the NameTemplate DataTemplate to all instances of my derived PropertyGridIconPropertyItem class. I do it in code, instead of with XAML. I would still like to know how to declare and apply this template using XAML, for curiosity's sake, but am quite proud that I was able to figure it out using C# code. First, as I stated before, I set the Property Item template of the Grid to my class:
<propgrid:PropertyGrid.ItemTemplate>
<DataTemplate>
<local:PropertyGridIconPropertyItem />
</DataTemplate>
</propgrid:PropertyGrid.ItemTemplate>
Then I add the setting of a property in my class called ImageFileName to a string literal, as in the folowing XAML declaration of one of my simpler property items:
<local:PropertyGridIconPropertyItem Category="Information" DisplayName="MSI Type Number" Value="{Binding MSITypeNumber}" Description="{x:Static r:CustomActionViewControl.Custom_Action_MSITypeNumber_Attribute_Description}" IsReadOnly="True" ImageFileName="CAView_Prop_MSITypeNumber.gif"/>
That is all I have to do in XAML. Here is my PropertyGridIconPropertyItem class, derived from PropertyGridPropertyItem:
public class PropertyGridIconPropertyItem : ActiproSoftware.Windows.Controls.PropertyGrid.PropertyGridPropertyItem
{
private static DataTemplate nameTemplateInstance;
public PropertyGridIconPropertyItem () : base ()
{
this.Loaded += PropertyGridIconPropertyItem_Loaded;
}
void PropertyGridIconPropertyItem_Loaded(object sender, RoutedEventArgs e)
{
NameTemplate = NameTemplateInstance;
}
public object ImageURI { get { return new Uri("pack://application:,,,/WiXBench.UI.UserControls;component/Images/" + ImageFileName, UriKind.RelativeOrAbsolute); } }
public string ImageFileName { get; set; }
private DataTemplate NameTemplateInstance
{
get
{
if (nameTemplateInstance == null)
{
System.Windows.Data.Binding binding;
nameTemplateInstance = new DataTemplate();
nameTemplateInstance.DataType = typeof(PropertyGridIconPropertyItem);
FrameworkElementFactory spFactory = new FrameworkElementFactory(typeof(StackPanel));
spFactory.SetValue(StackPanel.OrientationProperty, System.Windows.Controls.Orientation.Horizontal);
FrameworkElementFactory imFactory = new FrameworkElementFactory(typeof(Image));
imFactory.SetValue(Image.WidthProperty, 16.0);
imFactory.SetValue(Image.HeightProperty, 16.0);
imFactory.SetValue(Image.MarginProperty, new Thickness(0.0, 0.0, 5.0, 0.0));
binding = new System.Windows.Data.Binding();
binding.Path = new PropertyPath("DataContext.ImageURI");
binding.RelativeSource = new RelativeSource(System.Windows.Data.RelativeSourceMode.FindAncestor, typeof(IPropertyDataAccessor), 1);
imFactory.SetValue(Image.SourceProperty, binding);
spFactory.AppendChild(imFactory);
FrameworkElementFactory tbFactory = new FrameworkElementFactory(typeof(TextBlock));
tbFactory.SetValue(TextBlock.PaddingProperty, new Thickness(0.0, 0.0, 0.0, 0.0));
binding = new System.Windows.Data.Binding();
binding.Path = new PropertyPath("DisplayName");
binding.RelativeSource = new RelativeSource(System.Windows.Data.RelativeSourceMode.FindAncestor, typeof(IPropertyDataAccessor), 1);
tbFactory.SetValue(TextBlock.TextProperty, binding);
spFactory.AppendChild(tbFactory);
nameTemplateInstance.VisualTree = spFactory;
}
return nameTemplateInstance;
}
}
}
Note that I am using a singleton instance of the DataTemplate, so as to avoid creating numerous DataTemplate objects, when only one is necessary. In the constructor, I set up a handler for the Loaded event, as load time seems to be the best time to assign the DataTemplate I have created to the NameTemplate property. I assign the NameTemplate property in the Loaded event handler.
When I first wrote the code to create the DataTemplate object, it did not work correctly. By carefully testing with hard coded values for the Image SourceURI and TextBlock Text, I determined that the layout was correct. I could not, however, get the property binding to work properly. After some investigation into the System.Windows.Data.Binding class, I was able to figure out that the Binding required setting both the Path and RelativeSource properties, and I figured out how to do that for both the TextBlock Text and Image Source. It works flawlessly, and, unless there is a very simple XAML technique to do this, I think I will stick with what I have.
You mentioned vertically centering the TextBlock Text, but I have tried it in several System Font DPI settings, and the text is already centered perfectly. I noticed, however, that the Icon Image is actually not vertically centered properly. I tried setting the VerticalAlignment property of the Image to Center, and it made no difference. I even set it to Bottom, and it did not affect the vertical position. It is slightly biased toward being in the top of the cell.
Thank you for all your help. Again, if you can demonstrate to me a good, simple XAML solution, I would really like to know how to do this, and wish to learn more. I am going to mark your first comment that offered the derived PropertyGridPropertItem class as a solution.
Mark W. Stroberg