This project has moved and is read-only. For the latest updates, please go here.

PropertyGrid usage of CheckComboBox and CheckListBox

Oct 18, 2011 at 2:17 PM

Let say i have an enum which has Flags attribute, means i can combine with | operator etc.

[Flags]
	public enum VisibilityFlags : uint
	{
		//[NonEditableFlag]
		All = 4294967295,
		//[Description("Bit 0 is set or not")]
		Bit0 = 1,
		//[Description("Bit 1 is set or not")]
		Bit1 = 2,
		//[Description("Bit 2 is set or not")]
		Bit2 = 4,
		//[Description("Bit 3 is set or not")]
		Bit3 = 8,
		//[Description("Bit 30 is set or not")]
		Bit30 = 1073741824,
		//[Description("Bit 31 is set or not")]
		Bit31 = 2147483648,
		//[Description("Bit 4 is set or not")]
		Bit4 = 16,
		// [Description("Bit 5 is set or not")]
		Bit5 = 32,
		// [Description("Bit 6 is set or not")]
		Bit6 = 64,
		//[Description("Bit 7 is set or not")]
		Bit7 = 128,
		//[Description("Bit 8 is set or not")]
		Bit8 = 256,
		//[Description("Bit 9 is set or not")]
		Bit9 = 512,
		//[Description("Bit 10 is set or not")]
		Bit10 = 1024,
		//[Description("Bit 11 is set or not")]
		Bit11 = 2048,
		//[Description("Bit 12 is set or not")]
		Bit12 = 4096,
		//[Description("Bit 13 is set or not")]
		Bit13 = 8192,
		//[Description("Bit 14 is set or not")]
		Bit14 = 16384,
		//[Description("Bit 15 is set or not")]
		Bit15 = 32768,
		//[Description("Bit 16 is set or not")]
		Bit16 = 65536,
		//[Description("Bit 17 is set or not")]
		Bit17 = 131072,
		//[Description("Bit 18 is set or not")]
		Bit18 = 262144,
		//[Description("Bit 19 is set or not")]
		Bit19 = 524288,
		//[Description("Bit 20 is set or not")]
		Bit20 = 1048576,
		//[Description("Bit 21 is set or not")]
		Bit21 = 2097152,
		//[Description("Bit 22 is set or not")]
		Bit22 = 4194304,
		//[Description("Bit 23 is set or not")]
		Bit23 = 8388608,
		//[Description("Bit 24 is set or not")]
		Bit24 = 16777216,
		//[Description("Bit 25 is set or not")]
		Bit25 = 33554432,
		//[Description("Bit 26 is set or not")]
		Bit26 = 67108864,
		//[Description("Bit 27 is set or not")]
		Bit27 = 134217728,
		//[Description("Bit 28 is set or not")]
		Bit28 = 268435456,
		//[Description("Bit 29 is set or not")]
		Bit29 = 536870912,
		//[NonEditableFlag]
		None = 0
	}

I this case there should get new enum editor, which can be CheckComboBox or CheckListBox, the changes i've done is this, in PropertyGridUtilities i've added:

 

else if (propertyItem.PropertyType.IsEnum)
			{
				var attribute = GetAttribute<FlagsAttribute>(propertyItem.PropertyType);
				if (attribute != null)
				{
					editor = new EnumFlagsEditor();
				}
				else
				{
					editor = new EnumComboBoxEditor();
				}
			}

And this is the editor code:

public class EnumFlagsEditor : TypeEditor<CheckComboBox>
	{
		protected override void SetValueDependencyProperty()
		{
			ValueProperty = CheckComboBox.SelectedItemProperty;
		}

		protected override void ResolveValueBinding(PropertyItem propertyItem)
		{
			SetItemsSource(propertyItem);
			base.ResolveValueBinding(propertyItem);
		}

		private void SetItemsSource(PropertyItem propertyItem)
		{
			Editor.ItemsSource = GetValues(propertyItem.PropertyType);
		}

		private static object[] GetValues(Type enumType)
		{
			List<object> values = new List<object>();

			var fields = enumType.GetFields().Where(x => x.IsLiteral);
			foreach (FieldInfo field in fields)
			{
				// Get array of BrowsableAttribute attributes
				object[] attrs = field.GetCustomAttributes(typeof(BrowsableAttribute), false);
				if (attrs.Length == 1)
				{
					// If attribute exists and its value is false continue to the next field...
					BrowsableAttribute brAttr = (BrowsableAttribute)attrs[0];
					if (brAttr.Browsable == false) continue;
				}

				values.Add(field.GetValue(enumType));
			}

			return values.ToArray();
		}
	}

The editor appears correctly but it does not gets closed when clicking out of PropetyGrid, could you try to put this into code?

 

Anyway there is bug intro Selector.cs, the line should be (at the moment is typeof(CheckListBox):

 

public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(Selector), new PropertyMetadata((ICommand)null));

Oct 18, 2011 at 3:19 PM
Edited Oct 18, 2011 at 3:20 PM

Does the enum value get updated correctly?  The SelectedItem represents the currently selected/unselected item and not all selected items.  The SelectedValue property is just a delimited string of values, so I do not see how this would correctly update the underlying object.  Could you test to make sure the value is being updated as expected?  What are your observations?

Thanks for the Selector bug.  See what happens when you copy and paste.

EDIT: Almost forgot to address your question.  Yes I will be adding code to close the dropdown of the control.  I just haven't got there yet.  I will be working on that today.

Oct 18, 2011 at 3:27 PM

Yes, when i check some values from CheckComboBox it also sets the property which has the VisibilityFlags enum, once selected and closed (with hack), and when i reopen, seams that the value is checked correctly.

So for example if i pick up up VisibilityFlags.All, the editor should automatically pick all values (to Bit29). see this one (http://www.codeproject.com/KB/edit/flagseditor.aspx).

 

Hope this helps.

Oct 18, 2011 at 5:11 PM

The code that closes the drop down has been checked-in.

Oct 19, 2011 at 12:22 PM

Now the CheckComboBox closes when outside editor, now my question is how can set the selected item getting value from PropertyItem ?


Thanks

Oct 19, 2011 at 3:08 PM

You will need to bind the value to the CheckComboBox.SelectedValue.  Keep in mind that the SelectedValue is meant to be a delimited string of values, so you will probably need to also create a value converter to convert the delimited string to the correct enum values.

Mar 26, 2012 at 9:23 PM

I'm just trying to get this work, but with no success, please help me, when i bind Enum with FlagsAttribute i can't get this work, here is the code:

 

/// <summary>
	/// 
	/// </summary>
	public class EnumFlagsEditor<T> : TypeEditor<CheckComboBox> where T : struct
	{
		protected override void SetValueDependencyProperty()
		{
			ValueProperty = CheckComboBox.SelectedValueProperty;
		}

		protected override void ResolveValueBinding(PropertyItem propertyItem)
		{
			SetItemsSource(propertyItem);
			base.ResolveValueBinding(propertyItem);
		}

		private void SetItemsSource(PropertyItem propertyItem)
		{
			Editor.ItemsSource = GetValues(propertyItem.PropertyType);
		}

		protected override IValueConverter CreateValueConverter()
		{
			return new EnumStringConverter<T>();
		}

		private static object[] GetValues(Type enumType)
		{
			List<object> values = new List<object>();

			var fields = enumType.GetFields().Where(x => x.IsLiteral);
			foreach (FieldInfo field in fields)
			{
				// Get array of BrowsableAttribute attributes
				object[] attrs = field.GetCustomAttributes(typeof(BrowsableAttribute), false);
				if (attrs.Length == 1)
				{
					// If attribute exists and its value is false continue to the next field...
					BrowsableAttribute brAttr = (BrowsableAttribute)attrs[0];
					if (brAttr.Browsable == false) continue;
				}

				values.Add(field.GetValue(enumType));
			}

			return values.ToArray();
		}
	}

	/// <summary>
	/// 
	/// </summary>
	public class EnumStringConverter<T> : ValueConverter<T, string> where T: struct
	{
		protected override string Convert(T value, object parameter, CultureInfo culture)
		{
			string result = value.ToString();
			return result;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		/// <param name="parameter"></param>
		/// <param name="culture"></param>
		/// <returns></returns>
		protected override T ConvertBack(string value, object parameter, CultureInfo culture)
		{
			T result = (T)Enum.Parse(typeof(T), value);
			
			return result;
		}
	}

Coordinator
Mar 27, 2012 at 3:22 PM

Here is the editor I created from yours to make it work.

public sealed class EnumFlagsEditor : TypeEditor<CheckComboBox>
{
  public EnumFlagsEditor( Type enumType )
  {
    if( enumType == null )
      throw new ArgumentNullException( "enumType" );

    if( !enumType.IsEnum )
      throw new ArgumentException( "The specified type is not of type Enum.", "enumType" );

    if( enumType.GetCustomAttributes( typeof( FlagsAttribute ), true ).Length == 0 )
      throw new ArgumentException( "The specified type is not an Enum flag.", "enumType" );

    m_enumType = enumType;
  }

  protected override void SetValueDependencyProperty()
  {
    ValueProperty = CheckComboBox.SelectedValueProperty;
  }

  protected override void ResolveValueBinding( PropertyItem propertyItem )
  {
    SetItemsSource( propertyItem );

    base.ResolveValueBinding( propertyItem );
  }

  protected override IValueConverter CreateValueConverter()
  {
    return new EnumConverter( m_enumType );
  }

  private void SetItemsSource( PropertyItem propertyItem )
  {
    Editor.ItemsSource = GetValues( propertyItem.PropertyType );
  }

  private static object[] GetValues( Type enumType )
  {
    List<object> values = new List<object>();

    var fields = enumType.GetFields().Where( x => x.IsLiteral );
    foreach( FieldInfo field in fields )
    {
      // Get array of BrowsableAttribute attributes
      object[] attrs = field.GetCustomAttributes( typeof( BrowsableAttribute ), false );
      if( attrs.Length == 1 )
      {
        // If attribute exists and its value is false continue to the next field...
        BrowsableAttribute brAttr = ( BrowsableAttribute )attrs[ 0 ];
        if( brAttr.Browsable == false )
          continue;
      }

      values.Add( field.GetValue( enumType ).ToString() );
    }

    return values.ToArray();
  }

  private readonly Type m_enumType;

  private sealed class EnumConverter : IValueConverter
  {
    internal EnumConverter( Type enumType )
    {
      m_enumType = enumType;
    }

    public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
    {
      if( value == null )
        return string.Empty;

      return value.ToString().Replace( " ", string.Empty );
    }

    public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
    {
      var newValue = Enum.Parse( m_enumType, value.ToString() );

      return newValue;
    }

    private readonly Type m_enumType;
  }
}

 

In the previous code, you'll notice that:

  1. The editor's ItemsSource is set to an array of string value instead of your "enum" value.  The CheckComboBox editor will match the selected values using string comparison.
  2. In the converter's Convert method, the space character must be removed from the resulting string.  This is important since the CheckComboBox will split the SelectedValue with the configured delimiter but will not trim the results.  Since " ValueX" isn't the same as "ValueX", we must make sure the converter is creating an appropriate value.

 

This solution is far from perfect because the CheckComboBox may not select some flags even if they are set.  For example, if your enum value is "All", the CheckComboBox will only check the "All" check box even if the flags "Bit0", "Bit1", "Bit2", ..., "Bit31" are all set.

Mar 27, 2012 at 3:30 PM

Anyway, thank for your work, i'll check it later and try to perform the last set about "All" flag and make set of rest of Bit mask, after that i'll share the code here.

 

Thanks again,

Amer