I love PropertyGrid, well, at least the concept behind it - use reflection and attributes to edit your objects without writing much UI code.
My excitement died out pretty quickly though, the default PropertyGrid shipping with WinForms flat-out sucks. Well, it's fine for editing simple objects and such, but that's as far as it goes.
It doesn't display appropriate UITypeEditors for dynamic properties which have type "Object".
As soon as your objects contain collections, you might be able to edit them with so called CollectionEditor. However, it won't fire PropertyValueChanged event. So once you need to add undo functionality, you're screwed.
And I still haven't found an elegant way to add validation for CollectionEditor.
It's also problematic to implement undo if you have multiple objects selected, because in that case PropertyValueChanged event args ChangedItem is null.
I soon found myself writing hacks to address those issues with less than agreeable results.
What would you do?
Is there an elegant solution to at least the first three issues?
Is there an alternative propertygrid? Preferably free & without PInvokes?
A lot of the PropertyGrid's elegance comes from its simplicity. Above all else, it's designed to play nice with Visual Studio, and i'd expect to see it used primarily in custom UITypeEditors and extensions, rather than in application code.
Presumably the objects you are attaching to the PropertyGrid are classes of your own design? I've found that, in order to make good use of the property grid, you have to heavily decorate your classes and members with attributes.
You may find some joy in writing your own subclasses of CollectionEditor (and other types of editors) and attaching them to class members using the [Editor] attribute - if you can attach this attribute to your dynamic properties, you can force the use of a particular editor.
The only way I can think of adding validation to CollectionEditor is to override the CreateCollectionForm() method, returning an instance of your own, custom subclass of CollectionEditor.CollectionForm. There's a chance you will be able to fire the change events from here.
Unfortunately all I can do is nod and agree with the assertion about implementing undo. You might have to resort to 'backing up' the affected objects via cloning or serialization in order to implement undo.
I've seen alternatives to the built-in property grid control, but they exist mainly to offer different visual styles.
If someone is interested - here is a workaround for the PropertyValueChanged problem that simulates a change by invoking the MemberwiseClone function of System.Object if the CollectionEditor's PropertyValueChanged had been fired ...
public class FixedCollectionEditor : CollectionEditor
{
bool modified;
public FixedCollectionEditor(Type type)
: base(type)
{ }
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
value = base.EditValue(context, provider, value);
if (value != null && modified)
{
value = value.GetType()
.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic)
.Invoke(value, new object[] { });
}
modified = false;
return value;
}
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
foreach (Control table in collectionForm.Controls)
{
if (!(table is TableLayoutPanel)) { continue; }
foreach (Control c1 in table.Controls)
{
if (c1 is PropertyGrid)
{
PropertyGrid propertyGrid = (PropertyGrid)c1;
propertyGrid.PropertyValueChanged += new PropertyValueChangedEventHandler(GotModifiedHandler);
}
if (c1 is TableLayoutPanel)
{
foreach (Control c2 in c1.Controls)
{
if (!(c2 is Button)) { continue; }
Button button = (Button)c2;
if (button.Name == "addButton" || button.Name == "removeButton")
{
button.Click += new EventHandler(GotModifiedHandler);
if (button.ContextMenuStrip != null)
{
button.ContextMenuStrip.ItemClicked += new ToolStripItemClickedEventHandler(GotModifiedHandler);
}
}
}
}
}
}
return collectionForm;
}
void GotModifiedHandler(object sender, EventArgs e)
{
modified = true;
}
}
Visualhint sells a replacement for the property grid that may help. As I have never used it in a real project, I don't know how well it works.
Related
I've just started on my first WPF project and I ran into a problem this morning.
There is this large collection of locations (50.000) I want to bind to a GridControl.
public void BindData()
{
//disabling the control seemed to shorten the UI lock.
gcLocations.IsEnabled = false;
Task.Factory.StartNew(() =>
{
gcLocations.SetPropertyThreadSafe("ItemsSource", OceanData.OceanPorts.Values);
});
gcLocations.IsEnabled = true;
}
public static void SetPropertyThreadSafe(this Control control, string propertyName, object value)
{
Type type = control.GetType();
var prop = type.GetProperty(propertyName);
if(prop == null)
{
throw new Exception(string.Format("No property has been found in '{0}' with the name '{1}'", control, propertyName));
}
object[] param = new object[] { propertyName, prop.PropertyType, value };
if(prop.PropertyType != typeof(object) && prop.PropertyType != value.GetType())
{
throw new Exception(string.Format("Property types doesn't match - property '{0}' (type:{1}) and value '{2}'(type:)", param));
}
if(control.Dispatcher.CheckAccess())
{
prop.SetValue(control, value);
}
else
{
control.Dispatcher.BeginInvoke(new Action(() =>
{
prop.SetValue(control, value);
}), DispatcherPriority.ContextIdle, null);
}
}
Because I want my applications to remain responsive to the user I've been looking for an alternative method to bind this data in one go. So i came to this idea.. Is it possible to pause the binding operation when a lock occurs in the interface so that the interface can update itself? I'm pretty new to programming so excuse my ignorance :)
thanks ~~
The DevExpress GridControl supports data virtualization, whereby only the controls/items that are visible on screen are constructed and added to the visual tree. Furthermore, those visible controls are usually recycled as you scroll down your list, saving the costs of rebuilding them.
If you're not familiar with virtualization, consider a simple list view example: You may bind a data source containing, say, 10,000 items to it, but only 20 items might be visible to the user at any one time. The virtualization mechanism ensures that only the list items that are visible are created. Without this feature, the list box would have to create 10,000 WPF list items (which in turn may contain several controls or UI elements), and hold those in memory AND add them to the visual tree, even though they are not visible. WPF controls can only be added to the visual tree on the UI thread, which is what will be causing your code to hang.
It appears that the DevExpress GridControl supports virtualization out of the box, but it may be enhanced by use of their own DevExpress Datasource classes. Check out this documentation ... it may be you need to make use of the LinqServerModeDataSource or LinqInstantFeedbackDataSource classes to give you the performance you're looking for.
Am trying to get up to speed with Code Contracts. Here is another issue that doesn't make sense to me:
This is the Invariant:
[ContractInvariantMethod]
void Invariant() {
Contract.Invariant(this._uiRoot.RowDefinitions!=null);
}
Then, in a Method is this code:
int colunmn = 0;
foreach (UIElement uiElement in row.Where(element => element!=null))
{
if (uiElement != null)
{
uiElement.SetValue(Grid.ColumnProperty, colunmn++);
uiElement.SetValue(Grid.RowProperty, _uiRoot.RowDefinitions.Count - 1);
_uiRoot.Children.Add(uiElement);
}
}
I then get a warning that _uiRoot.RowDefinitions may be null, despite the Invariant. I don't see why CodeContracts would think that if it is checked after every public method call and the constructor. The code in question is a custom form designer, and it uses the uiRoot.RowDefinitions in a number of different methods, which is why I wanted to put it into the Invariant. I thought this would be enough to stop the warnings on it.
I think invariant only runs on public property access. Is it possible to add a contract to the RowDefinitions property itself? Your invariant can't prove that property won't be null; you could call a method on that type which sets it to null after your invariant runs.
I am using a datasource to populate my datagridview with the data. However, im trying to find a way for the user to be able to hide columns that he does not want to see.
I am able to hide and show columns before the program runs using:
[Browsable(false)]
public string URL
{
get
{
return this._URL;
}
set
{
this._URL = value;
this.RaisePropertyChnaged("URL");
}
}
I cannot seem to figure out how to change the [Browsable(false)] at run time.
Any ideas how I could accomplish this?
Basically, I want to bind an "on/off" to a menu.
Apologies if im not using the right terminology when explaining my problem, I am self taught and started a few weeks ago - so still very newbie :)
Edit:
Cant hide the column because when i run my update function all columns appear again. Here is my function for updating:
private void UpdateResults()
{
Invoke(new MethodInvoker(
delegate
{
this.dgvResults.SuspendLayout();
this.dgvResults.DataSource = null;
this.dgvResults.DataSource = this._mySource;
this.dgvResults.ResumeLayout();
this.dgvResults.Refresh();
}
));
}
At run time, you can just specify the column as being invisible:
dgv.Columns["ColumnName"].Visible = false;
The way to do this properly at runtime is to provide a custom ITypedList implementation on the collection, or provide a TypeDescriptionProvider for the type, or (for single-object bindings, not lists), to implement ICustomTypeDescriptor. Additionally, you would need to provide your own filtered PropertyDescriptor implementation. Is it really worth it? In most cases: no. It is much easier to configure the grid properly, showing (or not) the appropriate columns by simply choosing which to add.
Indeed, as others had mention the purpose of BrowsableAttribute is different, but I understand what you want to do:
Let's suppose that we want to create a UserControl than wraps a DataGridView and gives the user the ability to select which columns to display, allowing for complete runtime binding. A simple design would be like this (I'm using a ToolStrip, but you can always use a MenuStrip if that's what you want):
private void BindingSource_ListChanged(object sender, ListChangedEventArgs e) {
this.countLabel.Text = string.Format("Count={0}", this.bindingSource.Count);
this.columnsToolStripButton.DropDownItems.Clear();
this.columnsToolStripButton.DropDownItems.AddRange(
(from c in this.dataGrid.Columns.Cast<DataGridViewColumn>()
select new Func<ToolStripMenuItem, ToolStripMenuItem>(
i => {
i.CheckedChanged += (o1, e2) => this.dataGrid.Columns[i.Text].Visible = i.Checked;
return i;
})(
new ToolStripMenuItem {
Checked = true,
CheckOnClick = true,
Text = c.HeaderText
})).ToArray());
}
In this case, bindingSource is the intermediary DataSource of the dataGrid instance, and I'm responding to changes in bindingSource.ListChanged.
I am binding a properties Grid to a bunch of custom Objects that are being written by other developers. These objects are constantly being changed and updated, so they include properties that just throw NotImplemented Exceptions. Sometimes they include properties like
[Obsolete("use thingy instead to get other thing", true)]
Instead of annoying the other developers. with things that I know will be changed later. What can I do to make sure my properties Grid doesn't break on those specific properties?
Thanks for the help. the other Developers appreciate it ;)
I imagine that you are trying to bind the PropertyGrid to the objects at runtime, not in the designer. If you mean the propertygrid in the winform designer, the answer would be different, and you should look at the postFilterEvents method of ControlDesigner.
The simplest solution would be to set the BrowsableAttribute to false for the properties that you want to hide. This means that when the other developers add the ObsoleteAttribute, they should add [Browsable(false)], too. But I understand that you'd like something more "automatic". You could write a method that changes the browsable attributes of the properties of an object before passing it to the PropertyGrid. This can be done getting the TypeDescriptor for each property, then getting its BrowsableAttribute, and setting its value according to the fact that there is an ObsoleteAttribute, or that it throws an exception (this has to be done via reflection, since browsable is private). The code could be something like this:
private static void FilterProperties(object objToEdit)
{
Type t = objToEdit.GetType();
PropertyInfo[] props = t.GetProperties();
// create fooObj in order to have another instance to test for NotImplemented exceptions
// (I do not know whether your getters could have side effects that you prefer to avoid)
object fooObj = Activator.CreateInstance(t);
foreach (PropertyInfo pi in props)
{
bool filter = false;
object[] atts = pi.GetCustomAttributes(typeof(ObsoleteAttribute), true);
if (atts.Length > 0)
filter = true;
else
{
try
{
object tmp = pi.GetValue(fooObj, null);
}
catch
{
filter = true;
}
}
PropertyDescriptor pd = TypeDescriptor.GetProperties(t)[pi.Name];
BrowsableAttribute bAtt = (BrowsableAttribute)pd.Attributes[typeof(BrowsableAttribute)];
FieldInfo fi = bAtt.GetType().GetField("browsable",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
fi.SetValue(bAtt, !filter);
}
}
This should work, but it's got a limit. There must be at least a BrowsableAttribute in the class you are editing (it doesn't matter if it's set to true or false), otherwise the PropertyGrid will always be empty.
I am trying to get a StringElement's 'Value' to update in the UI when I set it after already setting up the DVC.
e.g:
public partial class TestDialog : DialogViewController
{
public TestDialog() : base (UITableViewStyle.Grouped, null)
{
var stringElement = new StringElement("Hola");
stringElement.Value = "0 Taps";
int tapCount = 0;
stringElement.Tapped += () => stringElement.Value = ++tapCount + " Taps";
Root = new RootElement("TestDialog")
{
new Section("First Section")
{
stringElement,
},
};
}
}
However the StringElement.Value is just a public field, and is only written to the UICell during initialization when Element.GetCell is called.
Why isn't it a property, with logic in the setter to update the UICell (like the majority of Elements, e.g. EntryElement.Value):
public string Value
{
get { return val; }
set
{
val = value;
if (entry != null)
entry.Text = value;
}
}
EDIT :
I made my own version of StringElement, derived from Element (basically just copied the source code from here verbatim)
I then changed it to take a class scoped reference to the cell created in GetCell, rather than function scoped. Then changed the Value field to a property:
public string Value
{
get { return val; }
set
{
val = value;
if (cell != null)
{
// (The below is copied direct from GetCell)
// The check is needed because the cell might have been recycled.
if (cell.DetailTextLabel != null)
cell.DetailTextLabel.Text = Value == null ? "" : Value;
}
}
}
It works in initial testing. However I am not sure on whether taking a reference to the cell is allowed, none of the other elements seem to do it (they only take references to control's placed within the cells). Is it possible that multiple 'live'* cell's are created based on the one MonoTouch.Dialog.Element instance?
*I say live to indicate cells currently part of the active UI. I did notice when navigating back to the dialog from a child dialog the GetCell method is invoked again and a new cell created based on the Element, but this is still a 1-1 between the element and the live cell.
For the main question:
Why does MonoTouch.Dialog use public fields for some Element options, and public properties for others?
I've been through the code, and I don't think there's a consistent reason for use of either.
The Dialog project was not part of the MonoTouch project initially - I don't think Miguel knew how useful it was going to turn out when he started wrote and grew it - I think he was more focussed on writing other apps like TweetStation at the time.
I know of several people (including me!) who have branched the code and adapted it for their purposes. I would guess at some future point Xamarin might write a 2.0 version with stricter coding standards.
Taking references to live cells
For limited use you can do this... but in general don't.
The idea of the table view is that cells get reused when the user scrolls up and down - especially in order to save memory and ui resources. Because of this is a long list, multiple elements might get references to the same cell.
If you do want to cache a cell reference then you probably should override GetCell() so that it never tries to reuse existing cells (never calls DequeueReusableCell)
Alternatively, you could try to change some code in the base Element class in order to find out if the Element has a current attached cell - this is what CurrentAttachedCell does in my branch of Dialog https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross.Dialog/Dialog/Elements/Element.cs (but that branch has other added functions and dependencies so you probably won't want to use it for this current work!)