WPF: Binding large collections to GridControl - c#

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.

Related

How to get all ComboBox ListItem values in a .NET 4.8 WinForms application with UIAutomation?

I am hoping this is not a silly question with a very obvious solution I'm just not seeing, but with some GUI testing my functional test team is responsible for at work for a software project, I'm encountering a change in behavior with ComboBoxes while updating our GUI automation library's behavior with how we interact with GUI elements using UIAutomation. The newest development version WinForms application that our software product is using .NET 4.8. With this update, I am noticing changes with the behavior of elements.
For example, with menu bar items, in order to get the children elements of a menu selection, the item needs to be expanded before any children are visible. Inspect observes the same behavior. We were able to overcome this hurdle fairly easily with a slight code modification to our GUI library. I can understand to a degree why Microsoft made this change with .NET 4.8.
Searching through all descendants of a top level window can be disastrous on test time.
With comboboxes, however, it seems the behavior is slightly different. With comboboxes, there are two children when not expanded:
-Combobox
|-Text item
|-button item
The ComboBox has an ExpandCollapsePattern. Intuition tells me that much like the menu bar items, if I expand it and then try to get a list of children, or even descendants, I should see more than 2 elements in the collection. Even with a decent delay after performing an Expand() on the ExpandCollapsePattern for the ComboBox and refocusing on the ComboBox (I've read that is sometimes necessary for certain children items), I still only see 2 elements, even if I'm searching for descendants.
When I look at the ComboBox when expanded with Inspect, I see the following:
-Combobox
|-List item
| |-List item
| |-List item
| |-List item
| |-List item
| |-List item
| |-List item
|-Text item
|-button item
The updated method for handling comboboxes with this new behavior currently looks like this:
public List<AutomationElement> GetComboBoxEntries(AutomationElement parentElement)
{
List<AutomationElement> items = new List<AutomationElement>();
try
{
var expandCollapsePattern = (ExpandCollapsePattern)parentElement.GetCurrentPattern(ExpandCollapsePatternIdentifiers.Pattern);
expandCollapsePattern.Expand();
//Slight delay
DelayFor(1000);
//Set focus to the combobox
parentElement.SetFocus();
//perform a FindAll() searching all descendants of the parentElement
var collection = FindAllListItems(parentElement);
//Add every element to the list
foreach (AutomationElement element in collection)
{ items.Add(element); }
}
catch (Exception e)
{
Console.WriteLine("There was an error performing the operation.");
Console.WriteLine("Error: " + e.Message);
Console.WriteLine("Stack Trace: " + e.StackTrace);
}
return items;
}
A solution I perceive, although not practical when a significant number of items is present in the ComboBox, that I foresee to fix our problem is to use keyboard/click input to iterate through every member of a ComboBox and capture the current value pattern for the element. We have had to do some dirty voodoo under the hood before, but I really don't like that kind of code. I prefer to be clean with UIA where possible. Additionally, it just seems quite tedious.
Before this .NET 4.8 change, we were able to get the child list item elements, examine the value for each one to verify nothing invalid as a part of our GUI smoke test. Now, it seems there are a lot of hoops you have to jump through just to get the end result.
Am I missing something here that is obvious? I'd prefer to keep whatever solution within the realm of UIA. Much like coming to the realization of how the behavior changed with menu bar items, I am sure there is a solution with ComboBoxes.
I'm just not seeing it. Hopefully one of you with a little more UIA expertise can point me in the right direction. Thank you.
Here some suggestions.
Since you're dealing with a WinForms ComboBox, you might as well use dedicated Win32 API to get the handle of the List Control (a NativeWindow derived object here, so UI Automation sees it as a Win32 Control, not a WinForm Control).
You can use the GetComboBoxInfo() function, passing a COMBOBOXINFO struct and the handle of the ComboBox (returned by [AutomationElement].Current.NativeWindowHandle).
If the function succeeds, it returns the handle of the NativeWindow container.
You can use then generate an AutomationElement with AutomationElement.FromHandle().
This will allow to retrieve the ListItems of the List Control without an ExpandCollapsePattern, since you access the List Control directly:
private AutomationElementCollection GetWinFormComboBoxListItems(AutomationElement comboBox)
{
if (comboBox is null) return null;
if (comboBox.Current.FrameworkId != "WinForm") {
throw new ArgumentException("Not a WinForm Control");
}
var cboInfo = new COMBOBOXINFO();
cboInfo.Init();
if (GetComboBoxInfo((IntPtr)comboBox.Current.NativeWindowHandle, ref cboInfo)) {
var listElement = AutomationElement.FromHandle(cboInfo.hwndList);
if (listElement != null) {
var items = listElement.FindAll(TreeScope.Children, Automation.RawViewCondition);
return items;
}
}
return null;
}
Win32 declarations:
[StructLayout(LayoutKind.Sequential)]
internal struct COMBOBOXINFO {
public int cbSize;
public RECT rcItem;
public RECT rcButton;
public ComboBoxButtonState buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
public void Init() => this.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
}
internal enum ComboBoxButtonState : int {
STATE_SYSTEM_NONE = 0,
STATE_SYSTEM_INVISIBLE = 0x00008000,
STATE_SYSTEM_PRESSED = 0x00000008
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
If you want to go all the way UI Automation, you can try a first time to get the child List Control from the ComboBox Element. If this fails, try again using the RootElement as the Parent Element. This will retrieve the AutomationElement of the List Control boxed in a the NativeWindow.
This will use the ExpandCollapsePatter to expand the List Control, making it visible to UI Automation.
You could also create an Automation event handler, using Automation.AddAutomationPropertyChangedEventHandler(), passing a Handler initialized with ExpandCollapsePattern.ExpandCollapseStateProperty.
In case you want to know when a User opens or closes the DropDownList of a ComboBox, or what has been selected.
private AutomationElementCollection GetComboBoxListItems(AutomationElement comboBox)
{
if (comboBox is null) return null;
AutomationElementCollection items = null;
bool wasCollapsed = false;
if (comboBox.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out object exp)) {
var expPattern = exp as ExpandCollapsePattern;
var state = expPattern.Current.ExpandCollapseState;
if (state == ExpandCollapseState.PartiallyExpanded) {
Thread.Sleep(50);
}
if (state == ExpandCollapseState.Collapsed) {
expPattern.Expand();
wasCollapsed = true;
}
var condition = new AndCondition(
new PropertyCondition(AutomationElement.ClassNameProperty, "ComboLBox", PropertyConditionFlags.IgnoreCase),
new PropertyCondition(AutomationElement.IsEnabledProperty, true),
new PropertyCondition(AutomationElement.ProcessIdProperty, comboBox.Current.ProcessId));
AutomationElement listElement = comboBox.FindFirst(TreeScope.Children, condition);
if (listElement is null && comboBox.Current.FrameworkId == "WinForm") {
listElement = AutomationElement.RootElement.FindFirst(TreeScope.Children, condition);
}
if (listElement != null) {
items = listElement.FindAll(TreeScope.Children, Automation.RawViewCondition);
}
if (wasCollapsed) expPattern.Collapse();
}
return items;
}

Browsable(false) at run time?

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.

Why does MonoTouch.Dialog use public fields for some Element options, and public properties for others

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!)

Two-way bind a "virtual" list of strings to a column

I have a list of Strings.
Well, conceptually. They are stored somewhere else, but I want provide an object which acts like a list (and provides any necessary events on top of that), with properties that I could bind to.
I want to establish a two-way binding over this data, to display it as a modifiable column in a DataGrid. I have the following problems with that:
I can't make a two-way binding because the binding needs a path (i.e. I can't have it look like {Binding} or {Binding Path=.} in the column, must be {Binding Path=someField"} to be made modifiable if I got this right, which sounds reasonable).
I don't exactly know how the proxy collection object should look like, in terms of interfaces (would IEnumerable + INotifyCollectionChanged sufficient?)
Is there any solution which doesn't involve creating one proxy object per every String in the collection? Could you suggest an efficient design?
To keep the discussion on the rails, let's assume I want to bind to something like this:
class Source {
public String getRow(int n);
public void setRow(int n, String s);
public int getCount();
public void addRow(int position, String s);
public void removeRow(int position);
}
That's not exactly my case, but when I know how to bind to this, I think I'll be able to handle any situation like this.
I'm OK with having to provide an adapter object on top of that Source, with any necessary interfaces and events, but I don't want to have one adapter object per row of data.
While making an adapter for the Source is relatively clear, then, unfortunatelly, the core of the second problem ('not wrapping every string in a miniobject') is a clash built into the .Net and WPF..
The first thing is that the WPF does provide you with many ways of registering 'on data modified' callbacks, but provides no way of registering callbacks that would provide a value. I mean, the "set" phase is only extendable, not interceptable, and the "get" - nothing at all. WPF will simply keep and return whatever data it has once cached.
The second thing is that in .Net the string is ... immutable.
Now, if ever you provide a string directly as a pathless binding or as a datacontext to any control, you are screwed in a dead end. The problem is, that WPF actually passes only the actual value of the binding, without the information of "where it came from". The underlying control will be simply given the string instance, and will have no sane way of modifying it as the string cannot change itself. You will not be even notified about such attempt, just like with read-only properties. What's more - if you ever manage to intercept such a modification attempt, and if you produce a proper new string, the WPF will never ask you again for the new value. To update the UI, you'd have to mannually, literally, force the WPF to re-ask you by for example changing the original binding so it points elsewhere (to the new value) or set the datacontext (to the new instance). It is doable with some VisualTree scanning, as every 'changed' callback gives you the DependencyObjects (Controls!), so yo ucan scan upwards/downwards and tamper with their properties.. Remember that option - I'll refer to this in a minute.
So, everything boils down to the fact that to get a normal 2-way binding you do not have to have a Path, you "just" have to have a mutable underlying data object. If you have immutable one - then you have to use a binding to a mutable property that holds the immutable value..
Having said that, you simply have to wrap the strings some how if you want to modify them.
The other question is, how to do that. There's a plenty of ways to do it. Of course, you can simply wrap them like Joe and Davio suggested (note to Joe: INotify would be needed there also), or you can try to do some XAML tricks with attached properties and/or behaviours and/or converters to do that for you. This is completely doable, see for example my other post - I've shown there how to "inject a virtual property" that pulled the data completely from elsewhere (one binding+converter performed the wrapping on the fly, second binding extracted the values from the attached-wrapper). This way you could create a "Contents" property on the string, and that property could simply return the string itself, and it'd be completely 2-way bindable with no exceptions.
But.. it would NOT work 2-way-ish.
Somewhere at the root of your binding/behaviour/conveter chain, there will be an immutable string. Once your smart autowrapping binding chain fires with 'on modified' callback you will be notified with pair of old/new values. You will be able to remap the values to new and old strings. If you implemented everything perfectly, the WPF will simply use the new value. If you tripped somewhere, then you will have to push the new value artificially back to the UI (see the options I'd asked you to remember). So, it's ok. No wrapper, old value was visible, it was changeable, you've got new value, the UI displays new value. How about storage?
Somewhere in the meantime you've been given a old/new value pair. If you analyze them, you'll get old/new strings. But how do you update the old immutable string? Can't do. Even if autowrapping worked, even if UI worked, even if editing seemed to work, you are now standing with the real task: you onmodified callback was invoked and you have to actually update that immutable string piece.
First, you need your Source. Is it static? Phew. What a luck! So surely it is instanced. In the on-modified callback we got only a old+new string.. how to get the Source instance? Options:
scan the VisualTree and search for it in the datacontexts and use whatever was found..
add some more attached properties and binding to bind a virtual "Source" property to every string and read that property from the new value
Well doable, but smells, but no other options.
Wait, there's more: not only the old/new value and an instance of Source are needed! You also need the ROW INDEX. D'oh! how to get that from the bound data? Again, options:
scan the VisualTree and search for it (blaargh)...
add some more attached properties and bindings to bind a virtual "RowIndex" property to every (blaaergh)...
At this point of time, while I see that all of this seems implementable and actually might be working properly, I really think that wrapping each string in a small
public class LocalItem // + INotifyPropertyChanged
{
public int Index { get; }
public Source Source { get; }
public string Content
{
get { Source...}
set { Source... }
}
}
will simply be more readable, elegant and .. SHORTER to implement. And less error-prone, as more details will be explicit instead of some WPF's binding+attached magic..
I find your approach a little weird.
DataGrids are usually used to display Rows. Rows consist of data that belongs together.
You could for instance easily map a row to a certain class. This means that the columns in your datagrid represent properties in your class.
What you're trying to do is the opposite, you're trying to get a relation between the column values instead of the row values.
Wouldn't it be easier to have a collection of your class which you can then bound the column to?
For instance
class MyClass : INotifyPropertyChanged
{
// Remember to actually implement INotifyPropertyChanged
string Column;
}
If you would have an ObservableCollection of MyClass you could bind the DataGrid to this collection. Whenever the property which I called "Column" changes, you could update your special list.
You can do this by hooking up some events. With the implementation of INotifyPropertyChanged, your columns will be updated if you update the "Column"-value directly.
I have this bit of code I use to bind a list of custom object to a DataContextMenu. You can alter it to use a list of strings and bind it to what you need
class SampleCode
{
class Team
{
private string _TeamName = "";
private int _TeamProperty1 = 0;
ObservableCollection<Territory> _Territories = new ObservableCollection<Territory>();
public Team(string tName)
{
this.TeamName = tName;
}
public ObservableCollection<Territory> Territories
{
get { return _Territories; }
set { _Territories = value; }
}
public string TeamName
{
get { return _TeamName; }
set { _TeamName = value; }
}
public int TeamProperty1
{
get { return _TeamProperty1; }
set { _TeamProperty1 = value; }
}
}
class Territory
{
private string _TerritoryName = "";
Team _AssociatedTeam = null;
public Territory(string tName, Team team)
{
this.TerritoryName = tName;
this.AssociatedTeam = team;
}
public Team AssociatedTeam
{
get { return _AssociatedTeam; }
set { _AssociatedTeam = value; }
}
public string TerritoryName
{
get { return _TerritoryName; }
set { _TerritoryName = value; }
}
public void Method1()
{
//Do Some Work
}
}
class MyApplication
{
ObservableCollection<Team> _Teams = new ObservableCollection<Team>();
ContextMenu _TeritorySwitcher = new ContextMenu();
public MyApplication()
{
}
public void AddTeam()
{
_Teams.Add(new Team("1"));
_Teams.Add(new Team("2"));
_Teams.Add(new Team("3"));
_Teams.Add(new Team("4"));
foreach (Team t in _Teams)
{
t.Territories.Add(new Territory("1", t));
t.Territories.Add(new Territory("2", t));
t.Territories.Add(new Territory("3", t));
}
SetContextMenu();
}
private void SetContextMenu()
{
HierarchicalDataTemplate _hdtTerritories = new HierarchicalDataTemplate();
_hdtTerritories.DataType = typeof(Territory);
HierarchicalDataTemplate _hdtTeams = new HierarchicalDataTemplate();
_hdtTeams.DataType = typeof(Team);
FrameworkElementFactory _TeamFactory = new FrameworkElementFactory(typeof(TreeViewItem));
_TeamFactory.Name = "txtTeamInfo";
_TeamFactory.SetBinding(TreeViewItem.HeaderProperty, new Binding("TeamProperty1"));
FrameworkElementFactory _TerritoryFactory = new FrameworkElementFactory(typeof(TreeViewItem));
_TerritoryFactory.Name = "txtTerritoryInfo";
_TerritoryFactory.SetBinding(TreeViewItem.HeaderProperty, new Binding("TerritoryProperty1"));
_hdtTeams.ItemsSource = new Binding("Territories");
_hdtTeams.VisualTree = _TeamFactory;
_hdtTerritories.VisualTree = _TerritoryFactory;
_hdtTeams.ItemTemplate = _hdtTerritories;
_TeritorySwitcher.ItemTemplate = _hdtTeams;
_TeritorySwitcher.ItemsSource = this._Teams;
}
}
}
Lazy solution
Derive from ObservableCollection<string> and let that collection be populated from the Source. In the derived class, register to collection change events and update the source accordingly. Bind the DataGrid column to the observable collection.
This should be pretty straightforward to write, but has a big drawback of duplicating all data in the collection.
More efficient solution
Create an adapter (as you suggested) and implement IList<string> and INotifyCollectionChanged. Let the list operations fall through directly to the source. Bind the DataGrid column to the adapter.
This approach would require some tedious boilerplate, but it's a thin layer between the WPF control and your Source.
This really depends on how you're implementing the UI. Bea Stollnitz did an excellent post of virtualizing the ItemsSource for the WPF DataGrid at http://bea.stollnitz.com/blog/?p=344 . With work I used this to edit as well as display data.
The easiest way is by placing the string in a wrapper class.
public class Wrapper
{
public string Content{get;set;}
}
Then you use the string via the wrapper class. This was the list items remain the same but the content changes.
The problem is when you do this without that then an old string is being deleted and a new one is created and the collection is confused.
Start with an ObservableCollection<string>. Then set the bindable control's ItemsSource to the ObservableCollection.

PropertyGrid alternatives

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.

Categories

Resources