Property edit style as DropDown in a VS .NET custom component - c#

I'd like to use the functionality of a ComboBox as edit option for a var in the properties window of a custom control / component. Not the ComboBox component itself.
As example:
private string[] _stringArray = { "string0", "string1" };
public string[] StringArray
{
get { return _stringArray; }
//callback
//set { _stringArray = value; }
}
As you know this will give me the object browser as view/edit option in the property window.
Funny thing that I can edit the values even with no setter.
In my researches I found out that it is possible ("UITypeEditorEditStyle.DropDown"). But I have no idea how to implement that.
Or what [Instructions] I could set for the "StringArray".
My final goal is a copy of the object selector drop-down of visual studio as a property parameter:
With custom event handling of course. But as you see I'm far away to realize that. :(
I have been looking for a tutorial on the following topics for a long time:
[Designer] instructions reference
A basic tutorial how to manage the display style of properties ✔
However I'm tired of my unsuccessful researches. Some good links are always welcome.

UPDATE:
After I more or less understood the principle (from the link in the comments, thanks) I came to an interim solution.
I realized that I need at least an int var to set a selected `index`. I thought / hoped that VS can do this automatically. Like it does with enums. And my lack of knowledge concerning [Instructions].
I could define a string variable as a placeholder for the selected index of the array in order to do without the TypeConverter, but that would make even less sense. I really don't need another abstract variable for nothing.
So the basis drop-down, which e.g. can display enums directly, does not appear to be applicable. So they use a trick with "UITypeEditorEditStyle.DropDown", which actually isn't a drop-down. It's just a button where you can place the control of your choice. In my case a ListView. Since the "drop" of the "down" already exists. Looks like cheating. ;)
//...
[TypeConverter(typeof(StringArrayConverter))]
public interface IStringArray
{
int SelectedIndex { get; set; }
string[] StringArray { get; set; }
}
public class DropDownStringArray : IStringArray
{
private string[] _stringArray = { "string0", "string1", "string2", "string3", "string4", "string5", "string6" };
public int SelectedIndex { get; set; }
public string[] StringArray
{
get { return _stringArray; }
set { _stringArray = value; }
}
}
private DropDownStringArray _ddsa = new DropDownStringArray();
[Editor(typeof(StringArrayTypeEditor), typeof(UITypeEditor))]
public DropDownStringArray MyDropDownStringArray
{
get { return _ddsa; }
set { _ddsa = value; }
}
//...
public class StringArrayConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
var sa = value as IStringArray;
if (sa != null) { return sa.StringArray[sa.SelectedIndex]; }
}
return "(none)";
}
}
public class StringArrayTypeEditor : UITypeEditor
{
private IWindowsFormsEditorService _editorService;
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) { return UITypeEditorEditStyle.DropDown; }
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
_editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
DropDownStringArray ddsa = (DropDownStringArray)value;
ListBox lb = new ListBox();
lb.SelectionMode = SelectionMode.One;
for (int i = 0; i < ddsa.StringArray.Length; i++) { lb.Items.Add(ddsa.StringArray[i]); }
lb.SetSelected(ddsa.SelectedIndex, true);
lb.SelectedValueChanged += OnListBoxSelectedValueChanged;
_editorService.DropDownControl(lb);
if (lb.SelectedItem != null) { ddsa.SelectedIndex = lb.SelectedIndex; }
return value;
}
private void OnListBoxSelectedValueChanged(object sender, EventArgs e)
{
_editorService.CloseDropDown();
}
}
Which actually copy the entire class just to change the SelectedIndex. The right thing would be to abuse the SelectedIndex and convert it to a string or something like that. I think I do not care about that anymore. Rather to catch some fresh air. ;)
Maybe that will help someone else.
Note: This is not a practical propose. As example SelectedIndex will not be updated if you change the (length) of the array. I've choosen string[] because it's a really basic and well known type. I am aware that my "program" has no real use. It was just about understanding the principle.

Related

Team Foundation Server plugin WorkItemChangedEvent

I am trying to create a TFS plugin that binds to the WorkItemChangedEvent and prevents the change based on some rules I will implement later. I have found some example code online and this is what I've got so far, however I would expect this to prevent all changes to work items but it doesn't seem to have any effect. There is no errors in the event viewer for TFS.
public class CwoWorkItemChangedEventHandler : ISubscriber
{
public Type[] SubscribedTypes()
{
return new[] { typeof(WorkItemChangedEvent) };
}
public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType,
object notificationEventArgs, out int statusCode, out string statusMessage,
out ExceptionPropertyCollection properties)
{
statusCode = 0;
properties = null;
statusMessage = String.Empty;
return EventNotificationStatus.ActionDenied;
}
public string Name
{
get { return "CwoWorkItemChangedEventHandler"; }
}
public SubscriberPriority Priority
{
get { return SubscriberPriority.High; }
}
}
}
The work item changed event is not a decision out and you can't deny it.
But the time that you have the event it has already happened. Only some events have decision points.

How do I create a Modal UITypeEditor for a string property?

I have a property which is stored as a string and is to be edited via a PropertyGrid It is kept as a set of comma separated values e.g ABC, DEF, GHI. My users could edit the string directly but the values come from a closed set of values. So it is easier and safer for them to pick from a list.
I have written a simple custom UITypeEditor based on the excellent answer here
I also reviewed this but I did not really understand it might relate to my problem. 8-(
I set up a simple form that allows the user to pick from a list and the picked values are added to value to be returned. My problem is that the value is not being returned. I wondered if this is something to do with the immutability of strings. However it is probably something simple and stupid that I am doing.
Here is my code for the type editor based on Marc's answer:
public class AirlineCodesEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
var svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
var codes = value as string;
if (svc != null && codes != null)
{
using (var form = new AirlineCodesEditorGUI())
{
form.Value = codes;
if (svc.ShowDialog(form) == DialogResult.OK)
{
codes = form.Value; // update object
}
}
}
return value; // can also replace the wrapper object here
}
}
The form is trivial for my test I just edited the textbox with some new values:
public partial class AirlineCodesEditorGUI : Form
{
public AirlineCodesEditorGUI()
{
InitializeComponent();
}
public string Value {
get
{
return airlineCodes.Text;
}
set
{
airlineCodes.Text = value;
}
}
private void OnCloseButtonClick(object sender, System.EventArgs e)
{
DialogResult = DialogResult.OK;
Close();
}
}
Perhaps someone would put me out of my misery.
You just need to return the value from the form, like this:
if (svc.ShowDialog(form) == DialogResult.OK)
{
value = form.Value;
}

Cause xamDataGrid to repaint

I need a way for viewmodel to instruct XamDataGrid on the view to just reread and repaint its cells with minimal hassle. I do not wish to mess with the source and do some unsustainable workaround with raising its events (the source might change).
To make it more understandable, I have a global static class that holds some visual cues configuration that do not affect data but only they way its represented in the grid (scaling, formatting, etc). The visual action happens in the IValueConverter implementation attached to the field which works just fine. There is a static event which fires when cues change and viewmodels subscribe to it and events fire properly. Now I just need to have the event handler cause the grid to repaint.
Any suggestions?
EDIT: some code, if it helps:
converter:
public class ScaleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof(double) || targetType == typeof(double?))
{
if (value == null && targetType == typeof(double?)) return null; // short circuit for null->null, nothing to scale
if (value is double) return (double)value / DisplayScale.Scale; // shortcut for direct scaling, no need to waste time on conversion
try
{
return System.Convert.ToDouble(value) / DisplayScale.Scale; // for convertible values, eat conversion exception
}
catch (Exception) {};
// if all esle fails return NaN
return double.NaN;
}
// fallthrough, return null, this should not happen, if it does it means converter is incomplete
return null;
}
...
}
DisplayScale global
public class DisplayScale: NotificationObject
{
private static KeyValuePair<string, double> _ActiveScaling;
private static readonly object _lockHandle = new object(); // note: should not contest, but just to be on the safe side
public static event Action ScalingChanged;
public static List<KeyValuePair<string, double>> ScaleList { get; private set; }
static DisplayScale()
{
ScaleList = new List<KeyValuePair<string, double>>()
{
new KeyValuePair<string, double>("No scaling (1's)", 1d),
new KeyValuePair<string, double>("Thousands (1,000's)", 1000d),
new KeyValuePair<string, double>("Millions (1,000,000's)", 1000000d),
new KeyValuePair<string, double>("Billions (1,000,000,000's)", 1000000000d)
};
ActiveScaling = ScaleList.First(); // OPTION: could be in per-user config
}
public static double Scale { get { return ActiveScaling.Value; } }
public static KeyValuePair<string, double> ActiveScaling
{
get
{
lock (_lockHandle) return _ActiveScaling;
}
set
{
lock (_lockHandle)
{
_ActiveScaling = value;
var eventCall = ScalingChanged;
if (eventCall != null) eventCall();
}
}
}
}
fields are defined as
// resource
<inf:ScaleConverter x:Key="ScaleConverter" />
// field
<igDP:Field Name="Deposit" Converter="{StaticResource ScaleConverter}">
if you have a collectionview than call simply Refresh() to it.
public class YourViewModel
{
private ObservableCollection<YourDataClass> yourColl;
public void YourViewModel()
{
yourColl = new ObservableCollection<YourDataClass>();
YourCollectionView = CollectionViewSource.GetDefaultView(yourColl);
DisplayScale.ScalingChanged += () => YourCollectionView.Refresh();
}
var ICollectionView yourCollView;
public ICollectionView YourCollectionView
{
get { yourCollView; }
set {
yourCollView = value;
RaisePropertyChanged("YourCollectionView");
}
}
}
I was having the same issue, and it turns out even though I had my ViewModel implement INotifyPropertyChanged, I was not implementing INotifyPropertyChanged on the class that the grid rows were binding to in my ObservableCollection (i.e. the YourDataClass in punker76's answer). Once I implemented it on that class as well everything started updating as expected.
I know that you mentioned "I do not wish to mess with the source" so I'm not sure if this solution is acceptable for you, but I thought I would share for others that find this post as well.

DataGridView List<T> column?

I have a datagridView, that is bound to a List. This List is made up of my class which contains 2 public properties, a String Name, and another List CustomList. See below:
public class MyClass2
{
public string Name
{ get; set;}
public string Description
{
get;
set;
}
}
public class MyClass
{
List<MyClass2> myList;
public string Name
{
get;
set;
}
public List<MyClass2> CustomList
{
get { return myList ?? (myList= new List<MyClass2>()); }
}
}
And then in my designer page:
List<MyClass> myClassList = new List<MyClass>();
dataGridView.DataSource = myClassList;
As it is right now, the only column that appears in the grid, is the MyClass:Name column, and the CustomList column does not show up. What I'd like is the CustomList column to show and to display something like "Collection" with the "..." button showing, and when it is clicked to have the "Collection Editor" to popup.
Does anyone know if this is possible and how to enable it? If there's a tutorial or anything that would help me out I'd appreciate that too. Thanks.
Using generics, I think, is a clean solution:
public class Sorter<T>: IComparer<T>
{
public string Propiedad { get; set; }
public Sorter(string propiedad)
{
this.Propiedad = propiedad;
}
public int Compare(T x, T y)
{
PropertyInfo property = x.GetType().GetProperty(this.Propiedad);
if (property == null)
throw new ApplicationException("El objeto no tiene la propiedad " + this.Propiedad);
return Comparer.DefaultInvariant.Compare(property.GetValue(x, null), property.GetValue(y, null));
}
}
Usage example:
string orderBy = "propertyName";
bool orderAsc = true;
List<MyExampleClass> myClassList = someMethod();
if (!string.IsNullOrEmpty(orderBy))
{
myClassList.Sort(new Sorter<MyExampleClass>(orderBy));
if (!orderAsc) myClassList.Reverse();
}
Short answer: Yes, you can do it with some code.
Long answer: To write the code is gonna be a pain in the ass, as you would have to know not only how the DataGridView behaves with custom columns, but you would need to know how to expose design time elements at runtime, which requires quite a bit of plumbing. Extensive knowledge about the PropertyGrid must also be known.
Note: This might a fun component to write. (I might actually tackle it if I get some time)
So using the 'button' approach posted by Dave, and some code that I found that implements the CollectionEditor, I can edit the CustomList in MyClass2
Here's my solution, although not quite as clean as I'd like:
Put this class somewhere:
class MyHelper : IWindowsFormsEditorService, IServiceProvider, ITypeDescriptorContext
{
public static void EditValue(IWin32Window owner, object component, string propertyName)
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(component)[propertyName];
if (prop == null) throw new ArgumentException("propertyName");
UITypeEditor editor = (UITypeEditor)prop.GetEditor(typeof(UITypeEditor));
MyHelper ctx = new MyHelper(owner, component, prop);
if (editor != null && editor.GetEditStyle(ctx) == UITypeEditorEditStyle.Modal)
{
object value = prop.GetValue(component);
value = editor.EditValue(ctx, ctx, value);
if (!prop.IsReadOnly)
{
prop.SetValue(component, value);
}
}
}
private readonly IWin32Window owner;
private readonly object component;
private readonly PropertyDescriptor property;
private MyHelper(IWin32Window owner, object component, PropertyDescriptor property)
{
this.owner = owner;
this.component = component;
this.property = property;
}
#region IWindowsFormsEditorService Members
public void CloseDropDown()
{
throw new NotImplementedException();
}
public void DropDownControl(System.Windows.Forms.Control control)
{
throw new NotImplementedException();
}
public System.Windows.Forms.DialogResult ShowDialog(System.Windows.Forms.Form dialog)
{
return dialog.ShowDialog(owner);
}
#endregion
#region IServiceProvider Members
public object GetService(Type serviceType)
{
return serviceType == typeof(IWindowsFormsEditorService) ? this : null;
}
#endregion
#region ITypeDescriptorContext Members
IContainer ITypeDescriptorContext.Container
{
get { return null; }
}
object ITypeDescriptorContext.Instance
{
get { return component; }
}
void ITypeDescriptorContext.OnComponentChanged()
{ }
bool ITypeDescriptorContext.OnComponentChanging()
{
return true;
}
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
{
get { return property; }
}
#endregion
Add a button column to the data grid:
DataGridViewButtonColumn butt = new DataGridViewButtonColumn();
butt.HeaderText = "CustomList";
butt.Name = "CustomList";
butt.Text = "Edit CustomList...";
butt.UseColumnTextForButtonValue = true;
dataGridView.Columns.Add(butt);
dataGridView.CellClick += new DataGridViewCellEventHandler(dataGridView_CellClick);
Then call it in the button handler of the cell click.
if (e.RowIndex < 0 || e.ColumnIndex != dataGridView.Columns["CustomList"].Index)
return;
//get the name of this column
string name = (string)dataGridView[dataGridView.Columns["Name"].Index, e.RowIndex].Value;
var myClassObject= myClassList.Find(o => o.Name == name);
MyHelper.EditValue(this, myClassObject, "CustomList");
I'd still be interested in hearing other approaches, and not having to implement my own CollectionEditor. And I'm still interested in having it look more like what the TabControl uses to add TabPages in the PropertyGrid...by displaying the "..." button...but this might work for now.
What you want to do is add a column template with a button in it:
http://geekswithblogs.net/carmelhl/archive/2008/11/11/126942.aspx
In the handler for the button, get the selected MyClass item from the collection and bind its list property to a grid in your popup.

Cast with GetType()

Is it possible to cast an object to the type returned from GetType()? I'd like a generic method that can accept an object (for anonymous types) but then return an object cast as the anonymous type. I've been thinking of using the LCG DynamicMethod to build a method on a container class, but I can't exactly figure out what that would look like. The idea to cast with the GetType() method was to be able to get the anonymous type and cast an object to its actual type without actually knowing the type.
The overarching goal is to stick anonymous-typed objects into a container, that I could then share and pass between methods.
I can't think of why you'd want to cast as GetType(), because you wouldn't be able to do anything to useful with the result, without knowing the type at compile time anyway.
Perhaps what you are looking for, is being able to Convert. If that is the case, the following should work for you:
object input = GetSomeInput();
object result = Convert.ChangeType(input, someOtherObject.GetType());
We use this when reading values from the registry which are all stored as strings, and then stuffing them into properties using reflection.
Your intent is very unclear; however, one option is generics and MakeGenericMethod in particular. What do you want to do with this? For example:
static class Program
{
static void Main()
{
object obj = 123.45;
typeof(Program).GetMethod("DoSomething")
.MakeGenericMethod(obj.GetType())
.Invoke(null, new object[] { obj });
}
public static void DoSomething<T>(T value)
{
T item = value; // well... now what?
}
}
So now we have the value, typed as double via generics - but there still isn't much we can do with it except for calling other generic methods... what was it you want to do here?
You can use the Activator.CreateInstance method to create an instance from a type.
FYI reflection is SLOOWWWW, so if you need to do this cast many times in a row, it may be better to define your types in an enum or something like this then create instances without using reflection.
I have a class that I use for change tracking in my Windows Forms app because not all items were databound. Most items were TextBox controls, but there were ComboBox and DateTimePicker controls as well.
For simplicity, my HasChanged property tests the generic Windows.Forms.Control to see if it is a ComboBox, but you could test whatever types of controls you add to your Windows Form.
Below is that class - if it helps for anyone.
internal class DataItem
{
private static Color originalBackColor, changedBackColor, originalForeColor, changedForeColor;
private static Font originalFont, changedFont;
static DataItem()
{
originalBackColor = SystemColors.Control;
changedBackColor = SystemColors.HighlightText;
originalForeColor = Color.Black;
changedForeColor = Color.Red;
originalFont = new Font(FontFamily.GenericSansSerif, 12.5f);
changedFont = new Font(originalFont, FontStyle.Bold);
}
public static void ChangeSetup(Control original, Color changedBackgroundColor)
{
originalBackColor = original.BackColor;
originalForeColor = original.ForeColor;
originalFont = original.Font;
changedBackColor = changedBackgroundColor;
changedFont = new Font(originalFont, FontStyle.Bold);
}
private bool changeTracking;
public DataItem(Control control, Object value)
{
this.Control = control;
var current = String.Format("{0}", Control.Text).Trim();
if (Control is ComboBox)
{
var cbo = (ComboBox)Control;
current = cbo.StateGet();
}
this.OriginalValue = current;
this.Control.TextChanged += Control_TextChanged;
changeTracking = true;
}
public Control Control { get; private set; }
private void Control_TextChanged(Object sender, EventArgs e)
{
if (TrackingChanges)
{
if (HasChanged)
{
this.Control.BackColor = originalBackColor;
this.Control.Font = originalFont;
this.Control.ForeColor = originalForeColor;
}
else
{
this.Control.BackColor = changedBackColor;
this.Control.Font = changedFont;
this.Control.ForeColor = changedForeColor;
}
}
}
public bool HasChanged
{
get
{
var current = String.Format("{0}", Control.Text).Trim();
if (Control is ComboBox)
{
var cbo = (ComboBox)Control;
current = cbo.StateGet();
}
return !current.Equals(OriginalValue);
}
}
public String OriginalValue { get; private set; }
public void Reset()
{
changeTracking = false;
this.OriginalValue = String.Empty;
this.Control.Text = String.Empty;
this.Control.BackColor = originalBackColor;
this.Control.Font = originalFont;
this.Control.ForeColor = originalForeColor;
}
public bool TrackingChanges
{
get
{
return changeTracking;
}
}
}
You can use getClass() which returns a Class object then use the cast method in the Class object to cast the object to an object of that Class's type, like so:
myObj.getClass().cast(myObj)

Categories

Resources