7

I am using the WPF Extended Toolkit to display the properties of a Team object. Now one of these properties is a collection Persons. No problem I get a nice drop down, which when I click on shows me the names and ages of each of these people.

enter image description here

Now the problem is that I don't actually want to expose my Collection as public. However as soon as I make its setter private the property is disabled preventing the user from seeing the Person collection and the person details:

enter image description here

How should I display my Person Collection when its setter is private? Can I do this with a XAML template? If so how? I'm using MVVM so I don't want to put anything in the code behind.

Update

OK so the solution by @tencntraze got me most of the way there - thanks. However it doesn't work for Collections of objects which is what I've got in my case. In addition it can also be simplified to use the CollectionControlDialog instead of the custom ReadOnlyCollectionViewer that's been implemented below.

XAML

<UserControl x:Class="DevExpressTreeList.ReadOnlyCollectionEditor"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Name="MyUserControl"
             >
    <DockPanel>
        <Button Click="Button_OnClick" DockPanel.Dock="Right">
            <Label Content="˅" Padding="2,0,2,0" />
        </Button>
        <Label Name="CollectionLabel" Content="(Collection)" Padding="2,2,2,0" />
    </DockPanel>
</UserControl>

Code-Behind

public partial class ReadOnlyCollectionEditor : UserControl, ITypeEditor
{
    public ReadOnlyCollectionEditor()
    {
        InitializeComponent();
    }

    // Use typeof(object) to allow for any Collection<T>
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
        "Value", typeof(object), typeof(ReadOnlyCollectionEditor), new PropertyMetadata(default(object)));

    public object Value
    {
        // We are now using object so no need to cast
        get { return GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public FrameworkElement ResolveEditor(Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem propertyItem)
    {
        var binding = new Binding("Value")
        {
            Source = propertyItem,
            Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay
        };
        BindingOperations.SetBinding(this, ValueProperty, binding);
        return this;
    }

    private void Button_OnClick(object sender, RoutedEventArgs e)
    {
        var collectionControlDialog = new CollectionControlDialog
        {
            ItemsSource = (IList)this.Value
        };
        collectionControlDialog.ShowDialog();
    }
}
John Cummings
  • 1,949
  • 3
  • 22
  • 38
openshac
  • 4,966
  • 5
  • 46
  • 77
  • Have you tried using a read-only collection? – Alberto Jan 03 '14 at 16:21
  • @Alberto yes I have although it makes no difference. – openshac Jan 03 '14 at 16:40
  • why don't you want your property to be public? What exactly are you trying to accomplish. Do you want to see the list but not be able to edit it, or maybe not change the selection. Can you elaborate a bit please. – J King Jan 06 '14 at 19:37
  • Fair question. I'm working with an interface I can't really change. It does makes sense though for that interface have only read-only properties. I just need to be able to show the user the contents of the collection. Telling them simply that "it is a collection" isn't very helpful. – openshac Jan 06 '14 at 20:32
  • Did you check here under the section Customer ItemsSource? https://wpftoolkit.codeplex.com/wikipage?title=PropertyGrid&referringTitle=Home – Xcalibur37 Jan 07 '14 at 02:04
  • @Xcalibur37 Yes I tried adding the ItemsSource attribute but that didn't work. – openshac Jan 07 '14 at 08:10

2 Answers2

10

I think that your best bet here is to implement your own editor, as per the Xceed Documentation. You are then able to provide whatever UI you would like to display to the user without needing to commit the values back to the underlying object. Note that this approach works for both private setters as well as properties without any setter.

ReadOnlyCollectionEditor

XAML

<UserControl x:Class="WpfApplication2.ReadOnlyCollectionEditor"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Name="uc">
    <Button Click="Button_OnClick" Height="20" />
</UserControl>

Code-Behind

public partial class ReadOnlyCollectionEditor : UserControl, ITypeEditor
{
    public ReadOnlyCollectionEditor()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
        "Value", typeof (IList<string>), typeof (ReadOnlyCollectionEditor), new PropertyMetadata(default(IList<string>)));

    public IList<string> Value
    {
        get { return (IList<string>)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public FrameworkElement ResolveEditor(Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem propertyItem)
    {
        var binding = new Binding("Value")
        {
            Source = propertyItem,
            Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay
        };
        BindingOperations.SetBinding(this, ValueProperty, binding);
        return this;
    }

    private void Button_OnClick(object sender, RoutedEventArgs e)
    {
        ReadOnlyCollectionViewer viewer = new ReadOnlyCollectionViewer {DataContext = this};
        viewer.ShowDialog();
    }
}

ReadOnlyCollectionViewer

<Window x:Class="WpfApplication2.ReadOnlyCollectionViewer"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ReadOnlyCollectionViewer" Height="300" Width="300">
    <ListBox ItemsSource="{Binding Value}" />
</Window>

Sample Properties Class

public class MyDataObjects
{
    public MyDataObjects()
    {
        this.CollectionProperty = new Collection<string> {"Item 1", "Item 2", "Item 3"};            
        this.StringProperty = "Hi!";
    }

    public string StringProperty { get; set; }

    [Editor(typeof(ReadOnlyCollectionEditor), typeof(ReadOnlyCollectionEditor))]
    public ICollection<string> CollectionProperty { get; private set; } 
}   

Assigning to the property grid

this.propertyGrid.SelectedObject = new MyDataObjects();

Results

Main Window

enter image description here

EDIT

I realize that you want to use MVVM, which I strongly encourage when using WPF, but for purposes of this sample I believe that keeping it simple helps illustrate the point, otherwise it brings up other questions like showing a modal dialog from MVVM, so I'm just showing the dialog with a button click.

John Cummings
  • 1,949
  • 3
  • 22
  • 38
Matt
  • 2,682
  • 1
  • 17
  • 24
  • This is a good answer, however as you mentioned, not MVVM friendly. I've just asked something that would go in the same direction, I'd like to pass control of that button click to the ViewModel of the View that contains the PropertyGrid... link here if you know the answer plz help :) https://stackoverflow.com/questions/45125563/xceed-wpftoolkit-propertygrid-custom-typeeditor-bindable-command – IgorMF Jul 16 '17 at 16:39
1
public Collection<Person> People
{
    get { return _people; }
    set { throw new NotSupportedException(); }
}

Perhaps not the nicest solution but it will work with the PropertyGrid while preventing users setting a new collection.

Iain
  • 2,500
  • 1
  • 20
  • 24