My control has a child control collection of TabPage type. I want to create a new child instance assigning a name according to its index. To do this, I need to know the current state of the collection. But how? CreateInstance only gives the item type, not the collection reference.
public class MyEditor : CollectionEditor
{
public MyEditor(Type type) : base(type)
{
}
protected override Type[] CreateNewItemTypes()
{
return new[]
{
typeof(TabPage)
};
}
protected override object CreateInstance(Type itemType)
{
return new TabPage("Page 1"); //<-- How to know current index?
}
}
With the lack of examples, I resorted to decompiling the .NET Framework and seeing how Microsoft did it. Microsoft's TabPageCollectionEditor was inside "System.Design.dll".
You can access the control which has the collection like this:
var control = Context?.Instance as MyTabControl;
Then it was a trivial thing to get current index:
int currentIndex = control.TabPages.Count;
Related
I was trying to implement ListViewCachingStrategy=RecycleElement in a Xamarin.Forms xaml page and everything displays correctly until I try to scroll. Then I get an exception that says that INativeElementView needs to be implemented. I designed the views in each native platform's ui designer and have a custom renderer for each. This crash does not happen if I use RetainElement instead of RecycleElement. Any ideas or suggestions?
Edit: My view that I created in the designer is called FeeViewCell and if I understand it right that is where INativeElementView is supposed to be implemented (in FeeViewCell.cs). My problem is now that casting to an element returns a System.InvalidCastException saying that the specified cast is not valid. Here is my code implementing INativeElementView:
public Element Element
{
get
{
return this.ToView() as Element;
}
}
I also have this as a question on the Xamarin forum here.
The INativeNativeView interface needs to be implemented on the custom cell class. In my example in iOS I in the custom renderer provides an item that comes from the pcl in my case a FeeCell.
This is part of my custom renderer
public class FeeCellRenderer : ViewCellRenderer
{
public override UIKit.UITableViewCell GetCell(Cell item, UIKit.UITableViewCell reusableCell, UIKit.UITableView tv)
{
var x = (FeeCell)item;
//Do whatever you need to here.
}
}
This is part of my custom ui class:
public partial class FeeViewCell : UITableViewCell, INativeElementView
{
public static readonly NSString Key = new NSString(nameof(FeeViewCell));
Element _element;
public Element Element
{
get { return _element; }
set { _element = value; }
}
}
It also works to do this:
public Element Element
{
get { return new FeeCell(); }
}
Short description:
I have a UserControl with a DataGridView on it. I
want to expose the DataGridView Columns collection to the designer, so
I can change the columns on my User Control at design time.
Question: Which designer attributes do I need for this?
For those interested in the longer version:
I have a UserControl with the following features:
a DataGridView that shows "pages" of items from a collection.
a NumericUpdown control to select which page to show.
page up / page down buttons that will disable when the first / last page is shown
Changes to the displayed items are visually marked
Buttons to save / discard the changes.
This user control can work autonomic. It has one function to be used by the parent control:
Show page (collection of items to show)
The UserControl raises two events:
Event Page changed (with a page number). Should result in loading a new page
Event Save items (with the collection of changed items)
I have to show this user control on several forms. The only difference is that the collection of DataGridViewColumn differs per form.
I could add the columns programmatically, but it would be easier to create them using the designer.
Usually it's enough to register a suitable UITypeEditor using [Editor] attribute. The editor which is used by the DataGridView is DataGridViewColumnCollectionEditor. But in this case, if we use this editor directly, the editor expect the the property belong to a DataGridView and tries to convert value of ITypeDescriptorContext.Instance to DataGridVeiew and since our editing Columns property belongs to our user control we will receive an exception:
Unable to cast object of type 'Type of Control' to type
'System.Windows.Forms.DataGridView'.
To solve the problem, we need to create a custom UITypeEditor and override EditValue and edit Columns property of the private DataGridView field of your user control.
To do so, we create an instance of ITypeDescriptorContext containing the DataGridView and it's Columns property and pass it to EditValue method of the editor. This way the editor will edit our Columns property.
We also decorate our property using [DesignerSerializationVisibility] attribute to serialize the collection contents.
Here is the implementations.
MyUserControl
I suppose you add a DataGridView at design-time to the user control and its name would be dataGridView1.
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
[Editor(typeof(MyColumnEditor), typeof(UITypeEditor))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public DataGridViewColumnCollection Columns
{
get { return this.dataGridView1.Columns; }
}
}
Editor
public class MyColumnEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context,
IServiceProvider provider, object value)
{
var field = context.Instance.GetType().GetField("dataGridView1",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
var dataGridView1 = (DataGridView)field.GetValue(context.Instance);
dataGridView1.Site = ((Control)context.Instance).Site;
var columnsProperty = TypeDescriptor.GetProperties(dataGridView1)["Columns"];
var tdc = new TypeDescriptionContext(dataGridView1, columnsProperty);
var editor = (UITypeEditor)columnsProperty.GetEditor(typeof(UITypeEditor));
var result = editor.EditValue(tdc, provider, value);
dataGridView1.Site = null;
return result;
}
}
ITypeDescriptionContext Implementation
public class TypeDescriptionContext : ITypeDescriptorContext
{
private Control editingObject;
private PropertyDescriptor editingProperty;
public TypeDescriptionContext(Control obj, PropertyDescriptor property)
{
editingObject = obj;
editingProperty = property;
}
public IContainer Container
{
get { return editingObject.Container; }
}
public object Instance
{
get { return editingObject; }
}
public void OnComponentChanged()
{
}
public bool OnComponentChanging()
{
return true;
}
public PropertyDescriptor PropertyDescriptor
{
get { return editingProperty; }
}
public object GetService(Type serviceType)
{
return editingObject.Site.GetService(serviceType);
}
}
I have a app that makes use of the PropertyGrid in C#/.NET
the PropertGrid holds onto the MyAppObject class/object shown below..
class MyAppObject
{
private List<MyObject> oItems;
public List<MyObject> Items
{
get { return this.oItems; }
}
}
And so far it works well, nice and simple. I want the property grid to allow users to view the items, which it does well, however when you select the property in the PropertyGrid the dialog also allows to add more List<MyObject> items.
I do not want this, I only want to have the ability to show the items, not edit them.
I thought by not providing the setter (set { this.oItems = value; }):
then it wouldnt allow the add button.
Hope this makes sense, The screenshots shows the dialog, and I circled the buttons I want to remove.
thanks
If you expose it as a read-only list, it should do what you need:
[Browsable(false)]
public List<MyObject> Items
{
get { return this.oItems; }
}
// this (below) is the one the PropertyGrid will use
[DisplayName("Items")]
public ReadOnlyCollection<MyObject> ReadOnlyItems
{
get { return this.oItems.AsReadOnly(); }
}
Note that the members of individual objects (MyObject instances) will still be editable, unless you decorate them as [ReadOnly(true)].
As you note, the setter is not necessary to add/remove/edit items. That is because the grid still has full access to the .Add, .Remove and indexer (list[index]) operations.
This is a slightly tricky one; the solution involves building with the full .NET Framework (since the client-only framework doesn't include System.Design). You need to create your own subclass of CollectionEditor and tell it what to do with the temporary collection after the UI is finished with it:
public class MyObjectEditor : CollectionEditor {
public MyObjectEditor(Type type) : base(type) { }
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
return ((MyObject)context.Instance).Items;
}
}
Then you have to decorate your property with the EditorAttribute:
[Editor(typeof(MyObjectEditor), typeof(UITypeEditor))]
public List<MyObject> Items{
// ...
}
Reference: What's the correct way to edit a collection in a property grid
Alternative:
return new ReadOnlyCollection(oItems);
OR
return oItems.AsReadOnly();
When I add an item to the CheckedListBox list box I also want to store a reference to another object. I tried adding a new instance of this object to the CheckedListBox.
public class CheckedListBoxExtention : CheckedListBox
{
private ReferenceItem _referenceItem;
public ReferenceItem storedItem
{
get { return _referenceItem; }
set { _referenceItem = value; }
}
public CheckedListBoxExtention(ReferenceItem storedItem)
{
_referenceItem = storedItem;
}
}
This works in that later when I foreach though the items in CheckedListBox I have a reference to the _referenceItem object. However, when I add items like this, CheckedListBox shows up as blank (the list in the GUI itself). So I am trying to find a way to override the item text or something like that.
This is the code I used to fix the problem
class ReferenceItemWrapper
{
private ReferenceItem _item;
public ReferenceItemWrapper(ReferenceItem item)
{
_item = item;
}
public ReferenceItem getItem
{get {return _item;}}
public override string ToString()
{
return _item.ToString();
}
}
I am a bit new to wrappers. Why exactly did it work after it was wrapped when it did not work when I added the ReferenceItem directly to the CheckedListBox?
The CheckedListBox uses the ToString method of the objects in the list to populate the captions in the box. Rather than extend the CheckedListBox, just create a wrapper class that lets you store both your reference and a caption, and implements a ToString method which returns your caption. Just create one of your wrapper objects, stick the text in it, stick your reference in it, then add the wrapper object to the list box.
As you can see in the pic below, for a ListView Control you can add Items using the Properties pane.
How do I enable this kind of stuff for my UserControl?
I'm not getting anything when I search Google, but I'm probably not using the correct terms.
Does anybody know?
Thanks
You need to create a class that defines the object type that the collection ids composed of. A listView has ListViewItem objects. A TabControl has TabPage objects. Your control has objects which are defined by you. Let's call it MyItemType.
You also need a wraper class for the collection. A simple implementation is shown below.
public class MyItemTypeCollection : CollectionBase
{
public MyItemType this[int Index]
{
get
{
return (MyItemType)List[Index];
}
}
public bool Contains(MyItemType itemType)
{
return List.Contains(itemType);
}
public int Add(MyItemType itemType)
{
return List.Add(itemType);
}
public void Remove(MyItemType itemType)
{
List.Remove(itemType);
}
public void Insert(int index, MyItemType itemType)
{
List.Insert(index, itemType);
}
public int IndexOf(MyItemType itemType)
{
return List.IndexOf(itemType);
}
}
Finally you need to add a member variable for the collection to your user control and decorate it properly:
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public MyItemTypeCollection MyItemTypes
{
get { return _myItemTypeCollection; }
}
and you now have a simple interface that allows you to browse and edit the collection. Leaves a lot to be desired still but to do more you will have to learn about custom designers which can be difficult to understand and implement.