locked
How can I create custom "DisplayMemberPath" properties? RRS feed

  • Question

  • What's the best way to create and hook up custom "DisplayMemberPath" properties in a custom control?

    My scenario is this:  I have a custom control called QuickPickList that has a public property of type IList.  It's basically like an ItemsSource property for an ItemsControl - users can bind to this property with their data object and populate the list.

    I now want to add 2 new properties called "DisplayMemberPathName" and "DisplayMemberPathImage".  The idea would be that users could use these two properties along side the IList property in code to define properties from their data objects that would define the text string and also a display image to show in the list.

    Any ideas on the best way to hook this up??

    I thought of changing my control to derive from ItemsControl so I get the ItemsSource and DisplayMemberPath for free, however it's not really the best solution for me since it induces breaking changes and ALSO provides me only one "DisplayMemberPath" property.

    Tuesday, April 21, 2009 10:37 PM

Answers

  • Since you are not using an ItemsControl to do Items type logic i would need to see more of how your control works when generating each of the visual items.  But if i were to take a guess it would look something like this.

            public string DisplayMemberPath
            {
                get { return (string)GetValue(DisplayMemberPathProperty); }
                set { SetValue(DisplayMemberPathProperty, value); }
            }
    
            // Using a DependencyProperty as the backing store for DisplayMemberPath.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty DisplayMemberPathProperty =
                DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(CustomItemsControl), new UIPropertyMetadata(string.Empty));
    
    
            public string ImageDisplayMemeberPath
            {
                get { return (string)GetValue(ImageDisplayMemeberPathProperty); }
                set { SetValue(ImageDisplayMemeberPathProperty, value); }
            }
    
            // Using a DependencyProperty as the backing store for ImageDisplayMemeberPath.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty ImageDisplayMemeberPathProperty =
                DependencyProperty.Register("ImageDisplayMemeberPath", typeof(string), typeof(CustomItemsControl), new UIPropertyMetadata(string.Empty));
    
    
            private StackPanel GenerateSingleItem(object dataItem)
            {
                StackPanel panel = new StackPanel();
                panel.Orientation = Orientation.Horizontal;
    
                TextBlock text = new TextBlock();
    
                Binding textBinding = new Binding(DisplayMemberPath);
                textBinding.Source = dataItem;
    
                text.SetBinding(TextBlock.TextProperty, textBinding);
    
                panel.Children.Add(text);
    
                Image image = new Image();
    
                Binding imageBinding = new Binding(ImageDisplayMemeberPath);
                imageBinding.Source = dataItem;
    
                image.SetBinding(Image.SourceProperty, imageBinding);
    
                panel.Children.Add(image);
    
                return panel;
            }
    Does this make a little more sense?  What it does is when you generate each item, it uses the individual data object as the source for what its supposed to bind to, but it uses the PropertyPath that was set at the control level.  This may change alot depending on how you are using XAML to define what an individual item should be like, but i think this should help you out a bit.
    If this was helpful, please mark as answered
    Blog: AttachedWPF
    AttachedWPF
    • Marked as answer by Tao Liang Monday, April 27, 2009 2:04 AM
    Wednesday, April 22, 2009 7:43 PM
  • I wouldn't create these properties as strings. They should be of type BindingBases, similar to GridViewColumn.DisplayMemberPath. This permits the user to make use of Binding helpers like IValueConverter instances. Then you can call SetBinding on the UIElements (Your TextBlock and Image) for the appropriate dependency property (i.e. myTextBlock.SetBinding(TextBlock.TextProperty, NameBindingVariable). Set the DataContext of each (or of the container of these elements) to the appropriate item in the IList, and you're good.
    • Proposed as answer by Richard Gavel Thursday, April 23, 2009 8:27 PM
    • Marked as answer by Tao Liang Monday, April 27, 2009 2:04 AM
    Thursday, April 23, 2009 8:27 PM

All replies

  • Hi

    Have you try to use DataTemplate for your data type and apply it automatically.
    Yiling, MVP(Visual C++)
    Wednesday, April 22, 2009 12:51 AM
  • Well I'd like to do this all from within my custom control ... I don't really want users of the control to have to create their own data template to bind to their data objects ...

    does that make sense?  or am i missing your point?
    Wednesday, April 22, 2009 5:10 PM
  • You could expose your custom property, when the property is set you can create a code-behind binding whose path is set to your custom path property and apply the binding to the control in the template that needs to display the content.


    If this was helpful, please mark as answered
    Blog: AttachedWPF
    AttachedWPF
    Wednesday, April 22, 2009 5:32 PM
  • Sorry, I'm having a little trouble following your logic there ... would u be able to supply a couple code snippets to demonstrate what u mean?

    Thanks!!
    Wednesday, April 22, 2009 7:17 PM
  • Since you are not using an ItemsControl to do Items type logic i would need to see more of how your control works when generating each of the visual items.  But if i were to take a guess it would look something like this.

            public string DisplayMemberPath
            {
                get { return (string)GetValue(DisplayMemberPathProperty); }
                set { SetValue(DisplayMemberPathProperty, value); }
            }
    
            // Using a DependencyProperty as the backing store for DisplayMemberPath.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty DisplayMemberPathProperty =
                DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(CustomItemsControl), new UIPropertyMetadata(string.Empty));
    
    
            public string ImageDisplayMemeberPath
            {
                get { return (string)GetValue(ImageDisplayMemeberPathProperty); }
                set { SetValue(ImageDisplayMemeberPathProperty, value); }
            }
    
            // Using a DependencyProperty as the backing store for ImageDisplayMemeberPath.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty ImageDisplayMemeberPathProperty =
                DependencyProperty.Register("ImageDisplayMemeberPath", typeof(string), typeof(CustomItemsControl), new UIPropertyMetadata(string.Empty));
    
    
            private StackPanel GenerateSingleItem(object dataItem)
            {
                StackPanel panel = new StackPanel();
                panel.Orientation = Orientation.Horizontal;
    
                TextBlock text = new TextBlock();
    
                Binding textBinding = new Binding(DisplayMemberPath);
                textBinding.Source = dataItem;
    
                text.SetBinding(TextBlock.TextProperty, textBinding);
    
                panel.Children.Add(text);
    
                Image image = new Image();
    
                Binding imageBinding = new Binding(ImageDisplayMemeberPath);
                imageBinding.Source = dataItem;
    
                image.SetBinding(Image.SourceProperty, imageBinding);
    
                panel.Children.Add(image);
    
                return panel;
            }
    Does this make a little more sense?  What it does is when you generate each item, it uses the individual data object as the source for what its supposed to bind to, but it uses the PropertyPath that was set at the control level.  This may change alot depending on how you are using XAML to define what an individual item should be like, but i think this should help you out a bit.
    If this was helpful, please mark as answered
    Blog: AttachedWPF
    AttachedWPF
    • Marked as answer by Tao Liang Monday, April 27, 2009 2:04 AM
    Wednesday, April 22, 2009 7:43 PM
  • Ok, I think i sort of see where you're going with that ... my only question is, where would this "GenerateSingleItem" method be called??

    I'm not currently adding "items" in code.  Basically I have a ListBox that I bind the ItemsSource to my custom QuickPickList collection property.  So the user populates the QuickPickList and items are automatically added to my internal ListBox via data binding.

    Would i need to somehow tap into the items as they are generated and output something similar to what u have there?
    Wednesday, April 22, 2009 8:00 PM
  • Basically yes.  As items are generated you can create the binding based on the DisplayMemberPath.  This would actually be alot easier to do if your control inherited from ListBox.  Remember even if you inherit from ListBox, in your Template you can add visuals that techincally may be outside of the ListBox itself. 

    If this was helpful, please mark as answered
    Blog: AttachedWPF
    AttachedWPF
    Thursday, April 23, 2009 1:25 PM
  • Actually now that I think about it, I don't believe your solution would work anyways.  In your GenerateSingleItem method you are binding, for example, the Image source property to the actual ImageDisplayMemberPath property.

    The ImageDisplayMemberPath property itself would have no image path defined - it's string would just be something like "ImageLocation" and would somehow actually need to point back to the underlying data object to get the actual path.

    So where you're doing this:

                Binding imageBinding = new Binding(ImageDisplayMemeberPath);
                imageBinding.Source = dataItem;

                image.SetBinding(Image.SourceProperty, imageBinding);

    The end result if the Image source property would be "ImageLocation", instead of something like "..\Images\stuff.png" that the data object defines.

    I think the DisplayMemberPath does some sort of magic behind the scenes that I'm unaware of and unable to replicate.
    Thursday, April 23, 2009 8:17 PM
  • I wouldn't create these properties as strings. They should be of type BindingBases, similar to GridViewColumn.DisplayMemberPath. This permits the user to make use of Binding helpers like IValueConverter instances. Then you can call SetBinding on the UIElements (Your TextBlock and Image) for the appropriate dependency property (i.e. myTextBlock.SetBinding(TextBlock.TextProperty, NameBindingVariable). Set the DataContext of each (or of the container of these elements) to the appropriate item in the IList, and you're good.
    • Proposed as answer by Richard Gavel Thursday, April 23, 2009 8:27 PM
    • Marked as answer by Tao Liang Monday, April 27, 2009 2:04 AM
    Thursday, April 23, 2009 8:27 PM
  • Hi Richard - that's an interesting take, I like the BindingBases thought.  How would u suggest setting the data context to the appropriate item in the IList?  Would that have to be done in code somehow?
    Thursday, April 23, 2009 11:52 PM
  • Hi,

    Are you achieving to display an image and filename on the listview? If Yes, then as what Yiling said you need to
    use DataTemplate.  Here some snippets I created for your case.

            <c:ImageCollection x:Key="ImgColl" />
            <c:ImageConverter x:Key="ImageConverter" x:Name="ImageConverter" />
            <DataTemplate x:Name="TemplateImage" x:Key="TemplateImage"  >
                <Canvas >
                    <TextBlock Text="{Binding Path=ImagePath}" />
                    <Image  Source ="{Binding Path=ImagePath, Converter={StaticResource ImageConverter}}" Width="100" Height="100" />
                </Canvas>
            </DataTemplate>
    then on you window; listview declaration would be.

            <ListView Height="112" Margin="18,0,42,18" Name="ListView1" VerticalAlignment="Bottom" ItemsSource="{StaticResource ImgColl}" >
                <ListView.View>
                    <GridView>
                        <GridViewColumn x:Name="FileName" CellTemplate="{StaticResource TemplateImage}" />
                    </GridView>
                </ListView.View>
            </ListView>

    then XAML above would call the class below. (ImageCollection.vb)

    Imports System.Collections.ObjectModel
    
    Public Class ImagePathClass
        Private _ImagePath As String
    
        Public Sub New(ByVal pImagePath As String)
            _ImagePath = pImagePath
        End Sub
    
        Public Property ImagePath() As String
            Get
                Return _ImagePath
            End Get
            Set(ByVal value As String)
                _ImagePath = value
            End Set
        End Property
    End Class
    
    
    Public Class ImageCollection
        Inherits ObservableCollection(Of ImagePathClass)
    
    
        Public Sub New()
            Me.Add(New ImagePathClass("D:\Images\IMG_1763.JPG"))
            Me.Add(New ImagePathClass("D:\Images\IMG_1764.JPG"))
        End Sub
    
    End Class
    
    and this is the converter used to convert your imagepath or filename to an image.

    Public Class ImageConverter
        Implements IValueConverter
    
    
        Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
            Dim imagePath As String = value
            Return New BitmapImage(New Uri(imagePath))
        End Function
    
        Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
            Throw New NotSupportedException()
        End Function
    
    End Class
    
    
    By the example; you wont be needing binding at code-behind and achieve image display at design-time.

    Other great sample by Bea and Josh.

    Hope this helps.


    vb.net GUI
    Friday, April 24, 2009 3:45 AM
  • Actually this should work because your source is still your object that contains the property where your image is located.  The usage of ImageDisplayMemberPath just defines the "Path" to take to find the property in the Binding.  So its not binding to the image path property directly.

    If you set ImageDisplayMemberPath to something like "ImageURI", then when this is run its going to bind to the ImageURI property on the data object itself, giving you what your are looking for.
    If this was helpful, please mark as answered
    Blog: AttachedWPF
    AttachedWPF
    Friday, April 24, 2009 12:13 PM
  • Ah, ok I see what you're saying ...

    I'm still struggling however, trying to figure out how to apply something like that "GenerateSingleItem" method ... I just don't know where this would be done in code. 

    Any clues?

    Friday, April 24, 2009 8:24 PM