I am creating a custom drawer for a custom attribute that helps me initialize the value of a field marked with the attribute SerializeReference.
Basically, the custom drawer will show a dropdown menu that allows me to select the Type to create and assign to the field.
I have the following code so far, to test the different scenarios:
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var targetObject = property.serializedObject.targetObject;
var field = targetObject.GetType().GetField(property.name, FieldBindingFlags)!;
if (field.GetValue(targetObject) == null)
field.SetValue(targetObject, new OperaSinger());
else
{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(position, property, label, true);
EditorGUI.EndProperty();
}
}
And those are the test classes:
interface ISinger
{
void Sing();
}
[System.Serializable]
class OperaSinger : ISinger
{
[SerializeField] private string name;
void Sing(){}
}
class StreetPerformer : MonoBehaviour, ISinger
{
[SerializeField] private string streetName;
void Sing(){}
}
The above code seems to work fine, it initializes the property to a new instance of the OperaSinger and shows the editor.
But, when I try to do the same with the MonoBehaviour implementation, I get an error:
Attempt #1:
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var targetObject = property.serializedObject.targetObject;
var field = targetObject.GetType().GetField(property.name, FieldBindingFlags)!;
if (field.GetValue(targetObject) == null)
{
var x = ((Component)targetObject).gameObject.AddComponent<StreetPerformer>();
field.SetValue(targetObject, x);
}
else
{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(position, property, label, true);
EditorGUI.EndProperty();
}
}
Error: [SerializeReference] cannot serialize objects that derive from Unity.Object.
Attempt #2:
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var targetObject = property.serializedObject.targetObject;
var field = targetObject.GetType().GetField(property.name, FieldBindingFlags)!;
if (field.GetValue(targetObject) == null)
{
var x = ((Component)targetObject).gameObject.AddComponent<StreetPerformer>();
property.objectReferenceValue = x;
}
else
{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(position, property, label, true);
EditorGUI.EndProperty();
}
}
Error: type is not a supported pptr value
Attempt #3:
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var targetObject = property.serializedObject.targetObject;
var field = targetObject.GetType().GetField(property.name, FieldBindingFlags)!;
if (field.GetValue(targetObject) == null)
{
var x = ((Component)targetObject).gameObject.AddComponent<StreetPerformer>();
property.objectReferenceInstanceIDValue = x.GetInstanceID();
}
else
{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(position, property, label, true);
EditorGUI.EndProperty();
}
}
Error: type is not a supported pptr value
What am I doing wrong, and how can I fix it?
As far as I understand, the error from attempts 2 and 3 is because the field is defined as the interface type ISinger.
Well, after trying the following:
[SerializeReference] private ISinger singer;
private void Reset()
{
singer = gameObject.AddComponent<StreetPerformer>()
}
And getting the same error as attempt 1, I realized that it is not possible to do it.
So, the solution I came up with is creating a wrapper-class to the StreetPerformer class that will delegate all methods to it like so:
internal sealed class StreetPerformer_ISing_Wrapper : ISing
{
[SerializeField] private StreetPerformer _instance;
public void Sing()
{
_instance.Sing();
}
}
Obviously, doing so to every class that I need will be tedious so I simply wrote a code generator that creates (and updates) this class for me.
So now I can have an interface field in my script that references a regular class, a ScriptableObject or a MonoBehaviour with minimum effort 😁
Related
I have a singleton class called KeyManager that is used to define and change player keybinds. It needs references to Text objects that will display what the currently set Keybind is. The singleton and text objects are in different scenes so I want to set the object definitions using a separate script that is attached to the (parent of) the Text objects themselves.
Here is the Singleton in its basic implementation.
public class KeyManager : MonoBehaviour
{
public static KeyManager Instance { get; private set; }
public static Dictionary<string, KeyCode> Keybinds = new Dictionary<string, KeyCode>();
private GameObject currentKey;
//the Text objects that need references
public static Text PitchUp { get { return pitchUp; } set { pitchUp = value; } }
private static Text pitchUp;
public static Text PitchDown { get { return pitchDown; } set { pitchDown = value; } }
private static Text pitchDown;
public static Text RotateLeft { get { return rotateLeft; } set { rotateLeft = value; } }
private static Text rotateLeft;
public static Text RotateRight { get { return rotateRight; } set { rotateRight = value; } }
private static Text rotateRight;
//...irrelevant singleton logic continues
}
Here is the script attached to the parent GameObject of each Text object to be referenced (parent is a button). The GameObject name of the button matches the Singleton property names.
public class KeybindCRData : MonoBehaviour
{
string buttonName;
Text childText;
void Start()
{
buttonName = this.name; //collect name of GameObject as set in Editor as a string
childText = this.transform.GetChild(0).GetComponent<Text>(); // collect Text object that will be assigned
//set Singleton property to this objects child Text
KeyManager.-INSERT PROPERTY NAME HERE- = childText;
//KeyManager.(Text)buttonName = childText;
// ^ this doesn't work of course, but this is the idea/goal
}
}
What is needed so I can dynamically change which property of the Singleton I am referencing based on the name of the Text object? I want to be able to just slap the KeybindCRData script on each button and be done.
The alternative that I see is specifically defining the property and related button for each keybind, but with 30+ keybinds it would not be ideal to end up with 30 different scripts each tailored to only 1 button.
Thanks for any help.
Thanks for the previous replies, they show other ways to do this more efficiently.
I figured out how to answer the crux of my original question though, so here is the code if it is useful to anyone else.
In the Manager Singleton, I add this public method.
public void SetProperty(string propertyName, TextMeshProUGUI value)
{
this.GetType().GetProperty(propertyName).SetValue(this, value);
}
In the KeybindCRData script that is attached to each individual button(used to rebind), I added this method call to the Awake() function.
void Awake()
{
//collect name of GameObject as set in Editor as a string
buttonName = this.name;
// collect Text object that will be assigned
childText = this.transform.GetChild(0).GetComponent<TextMeshProUGUI>();
//set Singleton property to this objects child Text
KeyManager.Instance.SetProperty(buttonName, childText);
}
This allows me to "spell out" the Property value using a String, grabbed from the matching Button-name. Thank you everyone, cheers!
I would try to redesign the code, so that your manager does not need to know about all the different text components. To keep your manager code simple and focused, it's better to invert the flow and have all your text components subscribe to an event in the manager to get notified whenever a key binding changes.
public static class KeyBindings
{
public static event Action<string, KeyCode> Changed;
private static readonly Dictionary<string, KeyCode> bindings = new Dictionary<string, KeyCode>();
public static KeyCode Get(string name) => bindings[name];
public static void Set(string name, KeyCode key)
{
bindings[name] = key;
Changed?.Invoke(name, key);
}
}
public class KeyBindingDisplayer : MonoBehaviour
{
[SerializeField]
private string keyBindingName;
private void Reset() => keyBindingName = name;
private void OnEnable()
{
KeyBindings.Changed += OnKeyBindingsChanged;
UpdateText(KeyBindings.Get(keyBindingName));
}
private void OnDisable()
{
KeyBindings.Changed -= OnKeyBindingsChanged;
}
private void OnKeyBindingsChanged(string name, KeyCode keyCode)
{
UpdateText(keyCode);
}
private void UpdateText(KeyCode keyCode)
{
GetComponent<Text>().text = keyCode.ToString();
}
}
Instead of creating new property for each Text object, use a Dictionary<string, Text>. The Key can be the buttonName and the value can be childText.
public class KeyManager : MonoBehaviour
{
public static KeyManager Instance { get; private set; }
public static Dictionary<string, KeyCode> Keybinds = new Dictionary<string, KeyCode>();
private GameObject currentKey;
//Key: buttonName (pitchUp, pitchDown, rotateLeft.. etc )
public static Dictionary<string, Text> Texts;
}
In KeybindCRData class, set the value to the dictionary.
public class KeybindCRData: MonoBehaviour
{
string buttonName;
Text childText;
void Start()
{
buttonName = this.name;
childText = this.transform.GetChild(0).GetComponent<Text>();
//Add appropriate logic as per requirement
if(!KeyManager.Texts.ContainsKey(buttonName)) {
KeyManager.Texts.Add(buttonName, childText)
}
}
}
I have a column class with 2 properties Name and Value, and another class(ColumnList) which inherit it as a collection, I want to raise an event from parent class to child class means change in column value should be triggered in ColumnList class.
below is my code
public class Column
{
private string colName;
private string colValue;
public string Name
{
get
{
return colName;
}
set
{
colName = value;
if (ColumnValueChangedEvent != null)
ColumnValueChangedEvent(null, null);
}
}
public string Value
{
get
{
return colValue;
}
set
{
colValue = value;
if (ColumnValueChangedEvent != null)
ColumnValueChangedEvent(null, null);
}
}
public event ColumnValueChangedDelegate ColumnValueChangedEvent;
public delegate void ColumnValueChangedDelegate(object sender, EventArgs e);
}
public class AddColumn : List<Column>
{
public AddColumn()
: base()
{
}
}
What You Need To do is:
Change The List To ObservableCollection like follows :
public class AddColumn : ObservableCollection<Column>
{
public AddColumn()
: base()
{
}
}
then in your code u can Call it like Follows:
var addColumn = new AddColumn();
addColumn.CollectionChanged += Test_CollectionChanged;
press Tab Vs will generate a function Like Follows:
private static void Test_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// put what ever u need here
}
****this doesn't have to be static ****
Update:
If You Want To change An filed in an item:
For example Lets say you want to change the name field at addColumn[0].
You do it as follows:
var item = addColumn[0];
item.Name = "New VAlue";
addColumn[0] = item.Name;
this code will fire the event.
Note its better to warp it in a function
If I understood you correctly, you want to subscribe to ColumnValueChangedEvent for each added Column item to your AddColumn class?
If yes, it is not possible because List<T> does not provide any mechanism to tell inherited class when item is added to list.
You should create a new type which not inherits from List<T>. It should implement IList<T> or something else depends on your requirements. Then in implementation in the Add(Column column) method just subscribe to events.
Why the MessageBox and CheckBox apprears in Design-Time when changing value of the First property and do not when adding item to Second?
private string _first;
[Description(""), Category("GostcompSettings"), DefaultValue("27017")]
public string First
{
get { return __first; }
set
{
_searchAreasChceckBoxList.Clear();
pPanelWithCheckboxies.Controls.Clear();
int x = 10;
int y = 10;
CheckBox _tempCheck = new CheckBox();
_tempCheck.Checked = true;
_tempCheck.Location = new Point(x, y);
_searchAreasChceckBoxList.Add(_tempCheck);
pPanelWithCheckboxies.Controls.Add(_tempCheck);
MessageBox.Show("zmiana");
_first = value;
}
}
private Collection<bool> _second= new Collection<bool>();
[Description(""), Category("*")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Collection<bool> Second
{
get
{
return _second;
}
set
{
_searchAreasChceckBoxList.Clear();
pPanelWithCheckboxies.Controls.Clear();
int x = 10;
int y = 10;
CheckBox _tempCheck = new CheckBox();
_tempCheck.Checked = true;
_tempCheck.Location = new Point(x, y);
_searchAreasChceckBoxList.Add(_tempCheck);
pPanelWithCheckboxies.Controls.Add(_tempCheck);
MessageBox.Show("*");
_second= value;
}
}
It's the same scenario when I change Collection to List...
Values are kept (or added to Collection in Second case) and designer generate code for InitializeComponent().
EDIT after #taffer answear
public class SearchAreaInfo
{
public SearchAreasEnum searchArea
{
get; set;
}
}
public class SearchAreaInfoCollection : Collection<SearchAreaInfo>
{
private Panel _checkboxParent;
public SearchAreaInfoCollection(Panel checkboxParent) : base()
{
_checkboxParent = checkboxParent;
}
// called on Add/Insert
protected override void InsertItem(int index, SearchAreaInfo item)
{
base.InsertItem(index, item);
RepaintChackboxPanel();
}
// called on Remove/RemoveAt
protected override void RemoveItem(int index)
{
base.RemoveItem(index);
RepaintChackboxPanel();
}
// called when an element is set by the indexer
protected override void SetItem(int index, SearchAreaInfo item)
{
base.SetItem(index, item);
RepaintChackboxPanel();
}
private void RepaintChackboxPanel()
{
//_searchAreasChceckBoxList.Clear();
_checkboxParent.Controls.Clear();
int x = 0;
int y = 0;
foreach (var item in this)
{
CheckBox _tempCheck = new CheckBox();
_tempCheck.Checked = true;
_tempCheck.Location = new Point(x, y);
_tempCheck.BringToFront();
//_searchAreasChceckBoxList.Add(_tempCheck);
_checkboxParent.Controls.Add(_tempCheck);
x += 5;
y += 5;
}
_checkboxParent.Invalidate();
}
}
private SearchAreaInfoCollection _searchAreas;
[Description(""), Category("GostcompSettings")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SearchAreaInfoCollection SearchAreas
{
get
{
return _searchAreas;
}
}
Now the prooblem is that: when I add let's say third item to the collection in Editor it draws only one checkbox but should draw 3 checkboxes...
Moreover in debugging I see that foreach loop goes then 3 times: 1st collection has 1 item , 2nd time collection has 2 items and third time collection has 3 items, but finally I see just one checkbox in _checkboxPanel.
Because the setter of Second property is executed only when you replace the whole collection. When you add/remove an item, the getter returns your collection instance (_second), and the Add/Remove method will be called on that object instance.
If you want to perform checks on element addition/removal, create a custom collection type instead:
public class MyBoolCollection: Collection<bool>
{
// called on Add/Insert
protected override void InsertItem(int index, bool item)
{
// do some checks here
base.InsertItem(index, item);
}
// called on Remove/RemoveAt
protected override void RemoveItem(int index)
{
// do some checks here
base.RemoveItem(index, item);
}
// called when an element is set by the indexer
protected override void SetItem(int index, bool item)
{
// do some checks here
base.SetItem(index, item);
}
}
The built-in collection editor that you are using now only changes the content of your collection object. Which works fine, but does not get your setter called at all. In other words, it never creates a new collection object, nor does it know how to do that.
To get your property setter called, you have to create your own UITypeEditor and have it return a new collection from its EditValue() method override. Pretty easy to do, first add a reference to System.Design, then make your code look similar to this:
using System.Drawing.Design;
...
[Editor(typeof(MyEditor), typeof(System.Drawing.Design.UITypeEditor))]
public Collection<bool> Second {
// etc...
}
private class MyEditor : UITypeEditor {
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
var editor = new System.ComponentModel.Design.CollectionEditor(typeof(Collection<bool>));
var retval = (Collection<bool>)editor.EditValue(context, provider, value);
return new Collection<bool>(retval);
}
}
You probably want to improve this a bit, like implementing your own editor UI so all of those bools are easier to interpret.
I'm doing some training project right now. It's supposed to convert numbers into different strings.
Heres the converted Control, and in the bottom way I use it in my Main Window.
So the first problem is that I want to create instance of converter based on value I pass to OutputFormatProperty so in this case I create converter that should be type OctalConverter but instead I get the default one, why is that?
Another thing is that I wan't to change InputValue in the converter by binding it to CurrentValue, which works with NotifyPropertyChanged, but it doesn't seem to work that way.
public partial class ConverterDisplay : UserControl {
private const int DEFAULT_INPUT_VALUE = 0;
private readonly ObservableCollection <DisplayField> _displayFields;
private AbstractNumberConverter _converter;
public static readonly DependencyProperty InputValueProperty = DependencyProperty.Register (
"InputValue",
typeof(int),
typeof(ConverterDisplay),
new PropertyMetadata (DEFAULT_INPUT_VALUE));
public static readonly DependencyProperty OutputFormatProperty = DependencyProperty.Register (
"OutputFormat",
typeof(NumberSystems),
typeof(ConverterDisplay),
new FrameworkPropertyMetadata (NumberSystems.Binary));
public int InputValue {
get {
return (int) GetValue (InputValueProperty);
}
set {
SetValue (InputValueProperty, value);
UpdateDisplay ();
}
}
public NumberSystems OutputFormat {
get {
return (NumberSystems) GetValue (OutputFormatProperty);
}
set {
SetValue (OutputFormatProperty, value);
}
}
public ObservableCollection <DisplayField> DisplayFields {
get { return _displayFields; }
}
public ConverterDisplay () {
_displayFields = new ObservableCollection<DisplayField> ();
InitializeComponent ();
CreateConverter ();
}
private void UpdateDisplay () {
var convertedNumberString = _converter.GetString (InputValue);
if (_displayFields.Count > convertedNumberString.Length)
ResetDisplayFields ();
while (_displayFields.Count < convertedNumberString.Length)
AddDisplayField ();
UpdateValues (convertedNumberString);
}
private void UpdateValues (string convertedString) {
if (_displayFields.Count == 0) return;
for (int i = 0; i < _displayFields.Count; i++) {
_displayFields [i].NumberValue = convertedString [i];
}
}
private void AddDisplayField () {
_displayFields.Insert (
0,
new DisplayField ((int)OutputFormat, _displayFields.Count));
}
private void ResetDisplayFields () {
_displayFields.Clear ();
}
private void CreateConverter () {
switch (OutputFormat) {
case NumberSystems.Binary:
_converter = new BinaryConverter ();
break;
case NumberSystems.Octal:
_converter = new OctalConverter ();
break;
case NumberSystems.Hexadecimal:
_converter = new HexadecimalConverter ();
break;
}
}
}
public enum NumberSystems {
Binary = 2,
Octal = 8,
Hexadecimal = 16
}
And then in the Main Window I'm trying to use that control
<converters:ConverterDisplay x:Name="octConverter"
InputValue="{Binding ElementName=Window,Path=CurrentValue}"
OutputFormat="Octal"/>
Just in case
public int CurrentValue {
get { return _currentValue; }
set {
if (value == _currentValue)
return;
ValidateNewValue (value);
OnPropertyChanged ();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
}
===========================
Edit #1
I don't really like that solution but I created public method in ConverterDisplay to create converter, it's being called after MainWindow is initialized so now the converters are correct.
Another thing is that how do i bind my UpdateDisplay method to InputValueProperty? I found through validation that it's getting correct value, but I can't see way how I can run that method without creating static stuff.
Concerning your second problem (binding the UpdateDisplay method to InputValueProperty: In general, it's not the best idea to call any method within a dependency property's setter, since this setter is never invoked when using data binding to fill the dependency property's value, as pointed out at MSDN:
The WPF XAML processor uses property system methods for dependency
properties when loading binary XAML and processing attributes that are
dependency properties. This effectively bypasses the property
wrappers. When you implement custom dependency properties, you must
account for this behavior and should avoid placing any other code in
your property wrapper other than the property system methods GetValue
and SetValue.
Instead, create a callback method that is invoked whenever InputValue's content changes, and call UpdateDisplay from there:
public static readonly DependencyProperty InputValueProperty = DependencyProperty.Register (
"InputValue",
typeof(int),
typeof(ConverterDisplay),
new PropertyMetadata (DEFAULT_INPUT_VALUE, InputValueChangedCallback));
private static void InputValueChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var userControl = dependencyObject as ConverterDisplay;
if (userControl != null)
userControl.UpdateDisplay();
}
Just stumbled upon propertyGrid and its awesome! However, i have one task that i cant find how to do with it:
I have a class which has a type. Based on type, it has different properties available. I keep it in one class (not multiple inherited classes) for simplicity sake (there are ten types but they mostly have the same properties).
For example, i have a class MapObject which can have string type equal "player" or "enemy". For "enemy", property "name" is used, but for "player" it is blank (not used).
When i select two objects, one of which is of type "player" and other of type "enemy", i want property "name" to only "count" for the "enemy". So, i want propertyGrid to show the name of the object that has type="enemy", and when it (name property) is changed in Grid, only assign it to the object of type "enemy".
Is this possible to do?
Depending on whose PropertyGrid you are using, toggling the Browsable attribute may do what you want. See my answer here for how to do that at runtime.
If, like me, you are using the Xceed PropertyGrid then only changing the Browsable attribute at runtime once the control has loaded doesn't do anything. Instead, I also had to modify the PropertyDefinitions collection. Here is an extension method for doing that:
/// <summary>
/// Show (or hide) a property within the property grid.
/// Note: if you want to initially hide the property, it may be
/// easiest to set the Browable attribute of the property to false.
/// </summary>
/// <param name="pg">The PropertyGrid on which to operate</param>
/// <param name="property">The name of the property to show or hide</param>
/// <param name="show">Set to true to show and false to hide</param>
public static void ShowProperty(this PropertyGrid pg, string property, bool show)
{
int foundAt = -1;
for (int i=0; i < pg.PropertyDefinitions.Count; ++i)
{
var prop = pg.PropertyDefinitions[i];
if (prop.Name == property)
{
foundAt = i;
break;
}
}
if (foundAt == -1)
{
if (show)
{
pg.PropertyDefinitions.Add(
new Xceed.Wpf.Toolkit.PropertyGrid.PropertyDefinition()
{
Name = property,
}
);
}
}
else
{
if (!show)
{
pg.PropertyDefinitions.RemoveAt(foundAt);
}
}
}
In case the above does not work for you, the following may work better and is simpler anyway. It also doesn't use deprecated properties like the code above did...
public static void ShowProperty(this PropertyGrid pg, string property, bool show)
{
for (int i = 0; i < pg.Properties.Count; ++i)
{
PropertyItem prop = pg.Properties[i] as PropertyItem;
if (prop.PropertyName == property)
{
prop.Visibility = show ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
break;
}
}
}
This is a design pattern known as the state pattern. It is pretty easy to implement and you do not need property grids. http://www.dofactory.com/Patterns/PatternState.aspx
I made it with my own custom attribute:
public class VisibilityAttribute : Attribute
{
public bool IsVisible { get; set; }
public VisibilityAttribute(bool isVisible)
{
IsVisible = isVisible;
}
}
Then my data model:
public abstract class BaseSettings: INotifyPropertyChanged
{
public void SetVisibilityProperty(string propertyName, bool isVisible)
{
var theDescriptor = TypeDescriptor.GetProperties(this.GetType())[propertyName];
var theDescriptorVisibilityAttribute = (VisibilityAttribute)theDescriptor.Attributes[typeof(VisibilityAttribute)];
if (theDescriptorVisibilityAttribute == null) return;
theDescriptorVisibilityAttribute.IsVisible = isVisible;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class ThrottleSettings : BaseSettings
{
private ThrottleType _throttleType = ThrottleType.SmartThrottle;
[PropertyOrder(1)]
public ThrottleType ThrottleType
{
get
{
return _throttleType;
}
set
{
if (value == ThrottleType.FullThrottle)
{
SetVisibilityProperty(nameof(FullThrottleTimeInMilliseconds), true);
SetVisibilityProperty(nameof(ThumbnailThrottleTimeInMilliseconds), false);
}
else if (value == ThrottleType.SmartThrottle)
{
SetVisibilityProperty(nameof(FullThrottleTimeInMilliseconds), false);
SetVisibilityProperty(nameof(ThumbnailThrottleTimeInMilliseconds), true);
}
else if (value == ThrottleType.NoThrottle)
{
SetVisibilityProperty(nameof(FullThrottleTimeInMilliseconds), false);
SetVisibilityProperty(nameof(ThumbnailThrottleTimeInMilliseconds), false);
}
_throttleType = value;
}
}
private int _fullThrottleTime = 100;
[PropertyOrder(2)]
[Visibility(false)]
[Description("Specifies full throttle time (in milliseconds).")]
public int FullThrottleTimeInMilliseconds
{
get
{
return _fullThrottleTime;
}
set
{
if (value < 0) return;
_fullThrottleTime = value;
}
}
private int _thumbnailThrottleTime = 0;
[PropertyOrder(3)]
[Visibility(true)]
[Description("Specifies thumbnail throttle time (in milliseconds).")]
public int ThumbnailThrottleTimeInMilliseconds
{
get
{
return _thumbnailThrottleTime;
}
set
{
if (value < 0) return;
_thumbnailThrottleTime = value;
}
}
}
Finally I subscribed to 'propertyGrid_PropertyValueChanged' event and call there my method:
private void _propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
RefreshVisibility(_propertyGrid.Properties);
}
Here is method itself:
private void RefreshVisibility(IList properties)
{
foreach (PropertyItem property in properties)
{
var visibilityAttribute = GetVisibilityAttribute(property);
if (visibilityAttribute != null)
{
if (visibilityAttribute.IsVisible)
property.Visibility = Visibility.Visible;
else
property.Visibility = Visibility.Collapsed;
}
RefreshVisibility(property.Properties);
}
}