This project has moved. For the latest updates, please go here.

PropertyGrid: Dependencies of properties within a grid

Apr 7, 2012 at 10:55 AM
Edited Apr 7, 2012 at 11:02 AM

Hi,

I need to update properties within a grid depending on the current selection of an other property within the same grid.

In this example i have a controller class which contains a list of possible names and a list of possible values (just as an example).

Now in my propertygrid i have a property which is called ActiveProperty and which has custom ItemSource 'ControllerItemsSource'. Further the propertygrid has two more properties, an 'ActiveName' and 'ActiveValue'. This two properties should have its custom itemsource, but depending on the selected controller. 

As an example, if i select ActiveController to "Controller A", i need to have a list for ActiveName of all available nams of the selected "Controller A", e.g. "A1" and "A2". But if i change the ActiveController to "Controller B" the itemsource of "ActiveName" should change to an other itemsource, which provides the possible names of "Controller B", e.g. "B1" and "B2.

 

 

public class Controller
{
	public List<string> ToolNames { get; set; }

	public List<int> Signals { get; set; }
}

public class ItemSet 
{
	[ItemsSource(typeof(ControllerItemsSource))]
	public Controller ActiveController { get; set; }

	public string ActiveToolName { get; set; }

	public int ActiveValue { get; set; }
}

public class ControllerItemsSource : IItemsSource
{
	public ItemCollection GetValues()
	{
		ItemCollection values = new ItemCollection();

		Controller ctrlA = new Controller();
		ctrlA.ToolNames = new List<string> { "A1", "A2", "A3", "A4" };
		ctrlA.Signals = new List<int> { 11, 12, 13, 14 };

		Controller ctrlB = new Controller();
		ctrlB.ToolNames = new List<string> { "B1", "B2", "B3", "B4" };
		ctrlB.Signals = new List<int> { 21, 22, 23, 24 };

		values.Add(ctrlA, "Controller Type A");
		values.Add(ctrlB, "Controller Type B");

		return values;
	}
}

propertyGrid.SelectedObject = new ItemSet();

Result should be :

http://www.tiikoni.com/tis/view/?id=8728d6b

Using old PropertyGrid it was possible to have a custom TypeEditor and access its context (in this case a custom editor for ActiveToolName and within this editor i am able to access its context, in this case the ItemSet instance and thus get the ActiveController and access its available tool names).

Any help is appreciated,

Apo.

Coordinator
Apr 12, 2012 at 2:14 PM

Hi,

 

Your solution using a custom editor is still viable.  The ITypeEditor.ResolveEditor method of your custom editor has a parameter of type PropertyItem.  What you are looking for is the PropertyItem.Instance property.  In your case, it contains your ItemSet instance.

Apr 12, 2012 at 3:16 PM

Hi,

 

yea that s great. I did it before using ugly static classes where i updated some lists etc. Now i am going to do that using propertyItem.Instance - much better design :)

Apr 13, 2012 at 5:40 AM

Hi,

I was looking for exactly the same solution.
In my custom editor class I can access another Property by:

ModelItem MI = (ModelItem)myPropertyItem.Instance;
ModelProperty prop = MI.Properties["MyProperty"];

I can change the Value but how can I change its ItemsSource? I need to change the list as well like in apo's example (B1, B2...)
Could you post a sample? Thank you

Apr 14, 2012 at 4:48 PM
Edited Apr 14, 2012 at 4:55 PM

Hej nepomuk12,

 

therefore i provide a custom editor for this property, which needs to update its ItemSource:

public class MyNamesEditor : ITypeEditor
{
	public FrameworkElement ResolveEditor(PropertyItem propertyItem)
	{
		ComboBox box = new ComboBox();

		// e.g. MyObjectWhichHasListOfObjects has an ObservableList property 'Names'
		MyObjectWhichHasListOfObjects source = propertyItem.Instance as MyObjectWhichHasListOfObjects;
		Debug.Assert(source != null);

		var itemSourcebinding = new Binding("Names")
		{
			Source = propertyItem,
			ValidatesOnExceptions = true,
			ValidatesOnDataErrors = true,
			Mode = BindingMode.OneWay
		};

		var selBinding = new Binding("Value")
		{
			Source = propertyItem,
			ValidatesOnExceptions = true,
			ValidatesOnDataErrors = true,
			Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay
		};

		BindingOperations.SetBinding(box, ItemsControl.ItemsSourceProperty, itemSourcebinding);
		BindingOperations.SetBinding(box, Selector.SelectedValueProperty, selBinding);

		return box;
	}
}
Tell me if you need a sample project.
Apr 14, 2012 at 5:23 PM
Edited Apr 14, 2012 at 5:24 PM

A minimalistic example

 

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
      <xctk:PropertyGrid x:Name="_propertyGrid" SelectedObject="{Binding MyItemSet}"/>
   </Grid>
</Window>


using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using Xceed.Wpf.Toolkit.PropertyGrid;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
using ItemCollection = Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection;

namespace WpfApplication
{
   /// <summary>
   /// Interaction logic for MainWindow.xaml
   /// </summary>
   public partial class MainWindow : INotifyPropertyChanged
   {
      private ItemSet _myItemSet;
      public ItemSet MyItemSet
      {
         get { return _myItemSet; }
         set
         {
            _myItemSet = value;
            RaisePropertyChanged("MyItemSet");
         }
      }

      public MainWindow()
      {
         MyItemSet = new ItemSet();
         InitializeComponent();
         DataContext = this;
      }

      public event PropertyChangedEventHandler PropertyChanged;

      protected virtual void RaisePropertyChanged(string propertyName)
      {
         var handler = PropertyChanged;
         if (handler != null)
         {
            handler(this, new PropertyChangedEventArgs(propertyName));
         }
      }
   }

   public class ItemSet 
   {
      [ItemsSource(typeof(ObjectWithNamesItemsSource))]
      public ObjectWithNames ActiveObject { get; set; }

      [Editor(typeof(ActiveNameEditor), typeof(ActiveNameEditor))]
      public string ActiveName { get; set; }
   }

   public class ObjectWithNames 
   {
      public ObservableCollection<string> Names { get; set; }
   }

   public class ObjectWithNamesItemsSource : IItemsSource
   {
      public ItemCollection GetValues()
      {
         ItemCollection values = new ItemCollection();

         ObjectWithNames objA = new ObjectWithNames { Names = new ObservableCollection<string> { "A1", "A2", "A3", "A4" } };
         ObjectWithNames objB = new ObjectWithNames { Names = new ObservableCollection<string> { "B1", "B2" } };
         ObjectWithNames objC = new ObjectWithNames { Names = new ObservableCollection<string> { "C1", "C2", "C3" } };

         values.Add(objA, "Object A");
         values.Add(objB, "Object B");
         values.Add(objC, "Object C");

         return values;
      }
   }

   public class ActiveNameEditor : ITypeEditor
   {
      public FrameworkElement ResolveEditor(PropertyItem propertyItem)
      {
         ComboBox box = new ComboBox();

         ItemSet source = propertyItem.Instance as ItemSet;
         Debug.Assert(source != null);

         var itemSourcebinding = new Binding("ActiveObject.Names")
         {
            Source = propertyItem.Instance,
            ValidatesOnExceptions = true,
            ValidatesOnDataErrors = true,
            Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay
         };

         var selBinding = new Binding("Value")
         {
            Source = propertyItem,
            ValidatesOnExceptions = true,
            ValidatesOnDataErrors = true,
            Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay
         };

         BindingOperations.SetBinding(box, ItemsControl.ItemsSourceProperty, itemSourcebinding);
         BindingOperations.SetBinding(box, Selector.SelectedValueProperty, selBinding);

         return box;
      }
   }
}

Hope that helps.

Apr 16, 2012 at 6:19 AM

Thanks a lot!

In your ItemsSource you declare those ObjectWithNames once. What if new objects are created during run-time, so the list of ComboBox in PropertyGrid is growing. How do you refresh ItemsSource?

nepomuk

Apr 16, 2012 at 8:26 PM
Edited Apr 16, 2012 at 8:27 PM

In fact, this is what i am using (a variing list as source). In this case, use a custom Editor for ObjectWithNames  instead of [ItemsSource(typeof(ObjectWithNamesItemsSource))] and use binding. Tell me if you need a sample.

 

/apo.

Apr 17, 2012 at 7:05 AM

I guess it would help me understanding Bindings, so your sample would be appreciated.
Thank you

Apr 18, 2012 at 6:32 PM
Edited Apr 18, 2012 at 7:23 PM

I updated the sample above and replaced the ItemSource with a custom editor where i bind a ItemSource of a combobox to a static list. If you press the button below the property grid it will add a new item to the list which then contains some random items.

XAML:

<Window x:Class="WpfPopGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="*"/>
			<RowDefinition Height="Auto"/>
		</Grid.RowDefinitions>
		<xctk:PropertyGrid x:Name="_propertyGrid" SelectedObject="{Binding MyItemSet}" Grid.Row="0"/>
		<StackPanel  Grid.Row="1">
			<Button Content="Add new Item" Name="btnA" Click="OnBtnAClick"/>
		</StackPanel>
	</Grid>
</Window>

CS:

namespace WpfPopGrid
{
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	public partial class MainWindow : INotifyPropertyChanged
	{
		public static ObservableCollection<ObjectWithNames> GrowingCollection { get; set; }

		private ItemSet _myItemSet;
		public ItemSet MyItemSet
		{
			get { return _myItemSet; }
			set
			{
				_myItemSet = value;
				RaisePropertyChanged("MyItemSet");
			}
		}

		public MainWindow()
		{
			GrowingCollection = new ObservableCollection<ObjectWithNames>();
			ObjectWithNames objA = new ObjectWithNames { ObjectName = "InitialItem", Names = new ObservableCollection<string> { "A1", "A2", "A3", "A4" } };
			GrowingCollection.Add(objA);

			MyItemSet = new ItemSet();
			InitializeComponent();
			DataContext = this;
		}

		public event PropertyChangedEventHandler PropertyChanged;

		protected virtual void RaisePropertyChanged(string propertyName)
		{
			var handler = PropertyChanged;
			if (handler != null)
			{
				handler(this, new PropertyChangedEventArgs(propertyName));
			}
		}

		private static int counter = 0;
		private void OnBtnAClick(object sender, RoutedEventArgs e)
		{
			// create some random data and add to list
			ObjectWithNames obj = new ObjectWithNames
			{
				ObjectName = string.Format("A New Item_{0}", counter),
				Names = new ObservableCollection<string>()
			};
			int max = new Random().Next(10);
			for (int i = 0; i < max; i++)
			{
				obj.Names.Add(RandomString(4));
			}
			GrowingCollection.Add(obj);
			counter++;
		}

		private static Random random = new Random((int)DateTime.Now.Ticks);//thanks to McAden
		private string RandomString(int size)
		{
			// source of random generator: http://stackoverflow.com/questions/1122483/c-sharp-random-string-generator
			StringBuilder builder = new StringBuilder();
			for (int i = 0; i < size; i++)
			{
				char ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
				builder.Append(ch);
			}

			return builder.ToString();
		}
	}

	public class ItemSet
	{
		[Editor(typeof(ActiveObjectListEditor), typeof(ActiveObjectListEditor))]
		[DisplayName("1 Select Object")]
		public ObjectWithNames ActiveObject { get; set; }

		[Editor(typeof(ActiveNameEditor), typeof(ActiveNameEditor))]
		[DisplayName("2 Select Name")]
		public string ActiveName { get; set; }
	}

	public class ObjectWithNames
	{
		public ObservableCollection<string> Names { get; set; }
		public string ObjectName { get; set; }
	}

	public class ActiveObjectListEditor : ITypeEditor
	{
		public FrameworkElement ResolveEditor(PropertyItem propertyItem)
		{
			ComboBox box = new ComboBox
			{
				DisplayMemberPath = "ObjectName"
			};

			var itemSourcebinding = new Binding("")
			{
				Source = MainWindow.GrowingCollection,
				ValidatesOnExceptions = true,
				ValidatesOnDataErrors = true,
				Mode = BindingMode.OneWay
			};

			var selBinding = new Binding("Value")
			{
				Source = propertyItem,
				ValidatesOnExceptions = true,
				ValidatesOnDataErrors = true,
				Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay
			};

			BindingOperations.SetBinding(box, ItemsControl.ItemsSourceProperty, itemSourcebinding);
			BindingOperations.SetBinding(box, Selector.SelectedValueProperty, selBinding);


			return box;
		}
	}

	public class ActiveNameEditor : ITypeEditor
	{
		public FrameworkElement ResolveEditor(PropertyItem propertyItem)
		{
			ComboBox box = new ComboBox();

			ItemSet source = propertyItem.Instance as ItemSet;
			Debug.Assert(source != null);

			var itemSourcebinding = new Binding("ActiveObject.Names")
			{
				Source = propertyItem.Instance,
				ValidatesOnExceptions = true,
				ValidatesOnDataErrors = true,
				Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay
			};

			var selBinding = new Binding("Value")
			{
				Source = propertyItem,
				ValidatesOnExceptions = true,
				ValidatesOnDataErrors = true,
				Mode = propertyItem.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay
			};

			BindingOperations.SetBinding(box, ItemsControl.ItemsSourceProperty, itemSourcebinding);
			BindingOperations.SetBinding(box, Selector.SelectedValueProperty, selBinding);

			return box;
		}
	}
}

Let me know if you need some more comments on it.

/apo

Aug 20, 2012 at 6:33 PM

The following issue has been created based on this discussion:

http://wpftoolkit.codeplex.com/workitem/18507

Jun 12, 2014 at 9:35 AM
The code shared was working with one issue. It is not showing data of "ActiveName" from ItemSet. That is exactly what i want for my application.
Display two comboboxes reading data from an xml. Based on first combox box selection change, change the itemsource in the second combobox.

Please help me to resolve this issue.

Thanks,
PrasannaRani