.NET C# Development
Editing Collections Using The Property Grid
I recently hit a problem using the property grid. If your working with the property grid and the data you change needs to work with undo/redo or maybe the data is a proxy for the real data stored inside say a database then you can use the PropertyDescriptor::SetValue(Object component, Object value) method to act on any changes. This works fine until the data is a collection or array. The problem is the CollectionEditors host there own property grid and these get the SetValue calls rather than your property grid. The solution is described here;
.http://dotnetfacts.blogspot.com/2008/05/how-to-take-control-over-collection.html
and a more elegant way to obtain the property grid here
http://www.codeproject.com/KB/miscctrl/VS2005Components.aspx
There is aproblem with this approach, when the user hits the event fires each time the user modifies one of the elements of the collection, this happens during editing and before the user hits the ok or cancel button. So rather than hooking into the property grid you can override the EditValue and CancelChanges methods in CollectionEditor like this...
private bool changeCancelled = false;
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
changeCancelled = false;
object newValue = base.EditValue(context, provider, value);
if (!changeCancelled)
{
if (MyPropertyValueChanged != null)
{
MyPropertyValueChanged(this, new PropertyValueChangedEventArgs(provider as GridItem, null));
}
}
return newValue;
}
protected override void CancelChanges()
{
changeCancelled = true;
base.CancelChanges();
}
}
Editing Fixed Size Collections
Following on from the above, I needed to disable the Add & Remove item buttons from the collection editor. The solution isn't great as it relies on using reflection to find named private fields on the form, but it works...
protected override CollectionForm CreateCollectionForm()
{
// Getting the default layout of the Collection Editor...
CollectionForm collectionForm = base.CreateCollectionForm();
Form form = collectionForm as Form;
//Get the forms type
Type formType = form.GetType();
FieldInfo fieldInfo = formType.GetField("removeButton", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
System.Windows.Forms.Control removeButton = (System.Windows.Forms.Control)fieldInfo.GetValue(form);
removeButton.Hide();
}
fieldInfo = formType.GetField("addButton", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
System.Windows.Forms.Control addButton = (System.Windows.Forms.Control)fieldInfo.GetValue(form);
addButton.Hide();
}
return collectionForm;
}
Every silver lining has a cloud
The approuch above fires the event from the CollectionEditor the problem is there's only one instance of each UITypeEditor so its impossible to tell which collection the collection editor modified. The solution is to create your own collection type and tell it to fire the event when the collection editor has modified it.
class FixedSizeArrayEditor : CollectionEditor
{
// Inherit the default constructor from the standard
// Collection Editor...
public FixedSizeArrayEditor(Type type) : base(type)
{
Console.WriteLine("FixedSizeArrayEditor " + type);
}
// Override this method in order to access the containing user controls
// from the default Collection Editor form or to add new ones...
protected override CollectionForm CreateCollectionForm()
{
// Getting the default layout of the Collection Editor...
CollectionForm collectionForm = base.CreateCollectionForm();
Form form = collectionForm as Form;
//Get the forms type
Type formType = form.GetType();
FieldInfo fieldInfo = formType.GetField("removeButton", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
System.Windows.Forms.Control removeButton = (System.Windows.Forms.Control)fieldInfo.GetValue(form);
removeButton.Hide();
}
fieldInfo = formType.GetField("addButton", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
System.Windows.Forms.Control addButton = (System.Windows.Forms.Control)fieldInfo.GetValue(form);
addButton.Hide();
}
return collectionForm;
}
private bool changeCancelled = false;
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
changeCancelled = false;
object newValue = base.EditValue(context, provider, value);
if (!changeCancelled)
{
ICollectionModified collectionModifiedInterface = newValue as ICollectionModified;
if (collectionModifiedInterface != null)
{
collectionModifiedInterface.OnCollectionModified();
}
}
return newValue;
}
protected override void CancelChanges()
{
changeCancelled = true;
base.CancelChanges();
}
}
[Editor(typeof(FixedSizeArrayEditor), typeof(UITypeEditor))]
public class FixedSizeArray<T> : System.Collections.Generic.List<T>, ICollectionModified
{
public delegate void MyPropertyValueChangedEventHandler(object sender, System.Windows.Forms.PropertyValueChangedEventArgs e);
public event MyPropertyValueChangedEventHandler MyPropertyValueChanged;
public FixedSizeArray(int count) : base(count) { }
public FixedSizeArray(IEnumerable<T> collection) : base(collection) { }
void ICollectionModified.OnCollectionModified()
{
if (MyPropertyValueChanged != null)
{
MyPropertyValueChanged(this, null);
}
}
}
Rendering Into A .NET Form from unmanaged code
http://www.gamedev.net/community/forums/topic.asp?topic_id=495909
public ref class Wrapper
{
// TODO: Add your methods for this class here.
public:
static void Render(Form ^form);
};
void Wrapper::Render(Form ^form)
{
HWND handle = static_cast<HWND> (form->Handle.ToPointer());
}
Open And Save Dialogs
Open
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.Filter = "project files (*.eproj)|*.eproj";
openFileDialog1.FilterIndex = 0;
openFileDialog1.RestoreDirectory = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
}
Save
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialog1.Filter = "project files (*.eproj)|*.eproj";
saveFileDialog1.FilterIndex = 0;
saveFileDialog1.RestoreDirectory = true;
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
}
Optimising Browsing Millions Of Objects Using A Tree Control
The trick is to build only as much of the tree as you need at any one time. If a tree node itself has child nodes just add a dummy node so that the expand icon is displayed. When the user clicks on the tree node to expand it delete the dummy node and create the proper tree nodes.
private void treeView1_BeforeExpand(TreeControl tc, CancelNodeEventArgs e)
{
ObjectProxy proxy = e.Node.Tag as ObjectProxy;
m_expanded.Add(proxy.UniqueRunTimeId, proxy.UniqueRunTimeId);
e.Node.Nodes.Clear();
AddReferences(e.Node.Nodes, proxy);
}
A good plan is to keep a dictionaryof which nodes have been expanded. This way if you need to recreate the tree from scratch it will look the way the user expects.
Also keep a dictionary of your data to nodes, that way if your data changes you know which nodes to update without trawling the tree of nodes. Note, this may need to be a dictionary where one key maps to many values so that your data can be represented multiple times in the tree, Generic MultiDictionary
Using Application Idle to run a Render Loop
Based on the code here, but just in case the link breaks I've copied the code below;
http://einfall.blogspot.com/2006/09/ebichu.html
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace MyApp
{
delegate void LoopHandler();
class RenderLoop
{
[System.Security.SuppressUnmanagedCodeSecurity]
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(
out Message msg,
IntPtr hWnd,
uint messageFilterMin,
uint messageFilterMax,
uint flags);
public event LoopHandler Loop;
private bool AppStillIdle
{
get
{
Message msg;
return !PeekMessage(out msg,
IntPtr.Zero,
0, 0, 0);
}
}
public RenderLoop(LoopHandler loopHandler)
{
Loop += loopHandler;
Application.Idle += new EventHandler(OnApplicationIdle);
}
private void OnApplicationIdle(object sender, EventArgs e)
{
while (AppStillIdle)
{
Loop();
}
}
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form1 form1 = new Form1();
RenderLoop renderLoop = new RenderLoop(delegate()
{
form1.Render();
});
Application.Run(form1);
}
}
}
Links
http://msdn.microsoft.com/en-us/library/w0x726c2.aspx
http://dotnetperls.com/