Archive for August, 2006
How to use an inline delegate with the Find method on collections
C# provides a very powerful and concise method of locating items in collections. The find Method on collections and generic lists (and other collection type classes) take a predicate as the parameter. To the beginner programmer the power of this parameter is not obvious.
Lets take a simple collection of objects, MyClass defined as follows:
class MyClass
{
public int Id;
public String Name;
}
and an associated collection, in this cas a simple generic list.
class MyClassCollection : List<MyClass>
{
...
}
Implementing a coupl of methods to Find by Id or name is really simple with the Find method. We create an inline delegate that returns a boolean true of false if a match occurs. e.g.
public MyClass FindByID(int id)
{
return this.Find(
delegate(MyClass itemInCollection)
{
return (itemInCollection.Id == id);
}
);
}
The FindByID method returns an instance of MYClass that the Find method locates.
The FindMethod takes and inline delegate that expects the current instance that the find method is looking at to be passed in (itemInCollection).
As the delegate is inline, it can reference the id parameter passed into the FindByID method and this can be used in the comparison.
An alternative to method of implementation is to pass as the predicate, a equality method that is defiend on the collection class, but this would mean that the value you are trying to find would have to be set as public field on the collecttion class, and therefore mutliple threads could not search the collection at the same time.
Below is an example of the class with a FindByName delegate as well.
class MyClassCollection : List<MyClass>
{
public MyClass FindByID(int id)
{
return this.Find(
delegate(MyClass itemInCollection)
{
return (itemInCollection.Id == id);
}
);
}
public MyClass FindByID(string name)
{
return this.Find(
delegate(MyClass itemInCollection)
{
return (itemInCollection.Name == name);
}
);
}
}
Gettting Visual Studion 2005 Help to work with a Proxy Server
For some reason Visual Studio Help does not correctly determine the proxy server setttings from Internet Explorer, or if it does it is very slow.
To get it working add the following to the dexplore.exe.config file located in “C:\Program Files\Common Files\Microsoft Shared\Help 8″
<system.net>
…
<defaultProxy enabled=”true” useDefaultCredentials=”true”>
<proxy bypassonlocal=”True” proxyaddress=”http://yourProxyServer:PortNo”/>
</defaultProxy>
…
</system.net>
How to provide Dragging or Resizing of any control.
The WM_SYSCOMMAND Message gives us various options for manipulating windows and replicating user input, but there are some undocumented parameters that are very useful.
The MSDN Documentation provides various usefull valid parameters that can be passed in the WParam of the message.
These are as follows:
- SC_CLOSE – Closes the window.
- SC_CONTEXTHELP – Changes the cursor to a question mark with a pointer. If the user then clicks a control in the dialog box, the control receives a WM_HELP message.
- SC_DEFAULT – Selects the default item; the user double-clicked the window menu.
- SC_HOTKEY – Activates the window associated with the application-specified hot key. The lParam parameter identifies the window to activate.
- SC_HSCROLL – Scrolls horizontally.
- SC_KEYMENU – Retrieves the window menu as a result of a keystroke. For more information, see the Remarks section.
- SC_MAXIMIZE – Maximizes the window.
- SC_MINIMIZE – Minimizes the window.
- SC_MONITORPOWER – Sets the state of the display. This command supports devices that have power-saving features, such as a battery-powered personal computer.
The lParam parameter can have the following values:
1 – the display is going to low power
2 – the display is being shut off - SC_MOUSEMENU – Retrieves the window menu as a result of a mouse click.
- SC_MOVE – Moves the window.
- SC_NEXTWINDOW – Moves to the next window.
- SC_PREVWINDOW – Moves to the previous window.
- SC_RESTORE – Restores the window to its normal position and size.
- SC_SCREENSAVE – Executes the screen saver application specified in the [boot] section of the System.ini file.
- SC_SIZE – Sizes the window.
- SC_TASKLIST – Activates the Start menu.
- SC_VSCROLL – Scrolls vertically.
In addition there are some undocumented constants that can be used as well.
- SC_DragMove = $F012;
Will start a Drag operation to move the window. This is similar tot he effect you get when you drag the caption of a winodw, however using this in the mouse down on a form, instantly gives you a form that can be dragged by clicking anywhere. - SC_DRAGSIZE_N = $F003;
Starts a resize operation on the top edge of the form. The window then supports either keyboard or mouse resizing. - SC_DRAGSIZE_S = $F006;
Starts a resize operation on the bottom edge of the form. The window then supports either keyboard or mouse resizing. - SC_DRAGSIZE_E = $F002;
Starts a resize operation on the right edge of the form. The window then supports either keyboard or mouse resizing. - SC_DRAGSIZE_W = $F001;
Starts a resize operation on the left edge of the form. The window then supports either keyboard or mouse resizing. - SC_DRAGSIZE_NW = $F004;
Starts a resize operation on the Top left corner of the form. The window then supports either keyboard or mouse resizing. - SC_DRAGSIZE_NE = $F005;
Starts a resize operation on the top right corner of the form. The window then supports either keyboard or mouse resizing. - SC_DRAGSIZE_SW = $F007;
Starts a resize operation on the bottom left corner of the form. The window then supports either keyboard or mouse resizing. - SC_DRAGSIZE_SE = $F008;
Starts a resize operation on the bottom right corner of the form. The window then supports either keyboard or mouse resizing.
To use these constants we simply send a perform message to the window we wish to work with.
e.g.
AFormMouseDown(…)
begin
ReleaseCapture; // releases the capture of mouse events for this form
Perform(WM_SYSCOMMAND,SC_DragMove,0); // send the drag move message
end;
It is important to use the release capture method to tell windows not to send the mouse messages to the form that has just received the mouse down.
Runtime Resizable Panel Control
Below is a complete resizable panel the can be resized on the right and bottom. It uses the windows SysCommand message and some little know options to cause the window (control) to resize.
If you want to allow moving or resizing in other directions, then extend the mouse location check and call the appropriate syscommand values.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; namespace YourAssembly { public partial class ResizablePanel : Panel { private Boolean _ResizeParent; private class NativeCalls { [DllImport("USER32.DLL", EntryPoint = "SendMessage")] public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref int lParam); [DllImport("user32")] public static extern int ReleaseCapture(IntPtr hwnd); public const int WM_SYSCOMMAND = 0×0112; public const int SC_DRAGMOVE = 0xF012; public const int SC_DRAGSIZE_N = 0xF003; public const int SC_DRAGSIZE_S = 0xF006; public const int SC_DRAGSIZE_E = 0xF002; public const int SC_DRAGSIZE_W = 0xF001; public const int SC_DRAGSIZE_NW = 0xF004; public const int SC_DRAGSIZE_NE = 0xF005; public const int SC_DRAGSIZE_SW = 0xF007; public const int SC_DRAGSIZE_SE = 0xF008; } public Boolean ResizeParent { get { return _ResizeParent; } set { _ResizeParent = value; } } public ResizablePanel() { InitializeComponent(); MinimumSize = new Size(50, 50); Margin = new Padding(0, 0, 0, 0); Padding = new Padding(0, 0, 3, 3); BackColor = SystemColors.ControlLight; } private enum MousePos {NoWhere, Right, Bottom, BottomRight} private MousePos GetMousePos(Point location) { MousePos result = MousePos.NoWhere; Rectangle TestRect; int RightSize = Padding.Right; int BottomSize = Padding.Bottom; // Resize right border TestRect = new Rectangle(Width – RightSize, 0, Width – RightSize, Height – BottomSize); if (TestRect.Contains(location)) result = MousePos.Right; // Resize bottom border TestRect = new Rectangle(0, Height – BottomSize, Width – RightSize, Height); if (TestRect.Contains(location)) result = MousePos.Bottom; // Resize bottom Corner TestRect = new Rectangle(Width – RightSize, Height -BottomSize, Width, Height); if (TestRect.Contains(location)) result = MousePos.BottomRight; return result; } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); IntPtr hwnd = this.Handle; if ((ResizeParent) && (this.Parent != null) && (this.Parent.IsHandleCreated)) { hwnd = Parent.Handle; } int nul = 0; MousePos mousePos = GetMousePos(e.Location); switch (mousePos) { case MousePos.Right: { NativeCalls.ReleaseCapture(hwnd); NativeCalls.SendMessage(hwnd, NativeCalls.WM_SYSCOMMAND, NativeCalls.SC_DRAGSIZE_E, ref nul); } break; case MousePos.Bottom: { NativeCalls.ReleaseCapture(hwnd); NativeCalls.SendMessage(hwnd, NativeCalls.WM_SYSCOMMAND, NativeCalls.SC_DRAGSIZE_S, ref nul); } break; case MousePos.BottomRight: { NativeCalls.ReleaseCapture(hwnd); NativeCalls.SendMessage(hwnd, NativeCalls.WM_SYSCOMMAND, NativeCalls.SC_DRAGSIZE_SE, ref nul); } break; } } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); MousePos mousePos = GetMousePos(e.Location); switch (mousePos) { case MousePos.Right: Cursor = Cursors.SizeWE; break; case MousePos.Bottom: Cursor = Cursors.SizeNS; break; case MousePos.BottomRight: Cursor = Cursors.SizeNWSE; break; default: Cursor = Cursors.Default; break; } } protected override void OnResize(EventArgs eventargs) { base.OnResize(eventargs); if (this.Width < this.MinimumSize.Width) this.Width = this.MinimumSize.Width; if (this.Height < this.MinimumSize.Height) this.Height = this.MinimumSize.Height; } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); Cursor = Cursors.Default; } } }
How to create an Expandable Object Converter that will drop down a list of Class Types
This article provides an example of a TypeConverter which, when displayed in a Property Grid, will allow the user to select an class type from the drop down list. When an item is selected, it creates and instance of that class type. This can be used, for example, if you had a property of type Shape, and wanted to allow the user to choose a shape to assign to that property, e.g. Circle, Rectangle, etc. The property has a type of the base class Shape, but ends up with the appropriate instance of a descendant class. In the following example, I have a abstract base class of GridStyleBorder which is a property on an object.
[Category("Appearance")] [Browsable(true)] [TypeConverter(typeof(GridBorderTypeConverter))] [RefreshProperties(RefreshProperties.All)] [Description("Change the Border Style")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] public GridStyleBorder Border { get { return _border; } set { _border = value; } }
Notice the TypeConvert is set to GridBorderTypeConverter this is a new TypeConvert descended from ExpandableObjectConverter. The code is below:
using System; using System.Collections.Generic; using System.Text; using System.ComponentModel; using System.Collections; using System.Drawing; using System.Drawing.Drawing2D; using System.Reflection; namespace SomeAssemby { /// /// Provides the type converter that displays a list of types. /// When the user selects an item from the list, an instance of that type is created. /// Note : requires customization to work with other property types. (see comments) /// class GridBrushTypeConverter : ExpandableObjectConverter { // set the next array to be an array of types you wish to display in the drop down private static readonly Type[] TypesToDisplay = new Type[] { typeof(SolidBrush), typeof(LinearGradientBrush) }; // Change the modify region to If Else tests for each type you support and return appropriate instances of the types. public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { if (value.GetType() == typeof(string)) { string fullClassName = (string)value; #region —- need to modify this section ——————— // check the full classname and return a new instance of that class for each of the types you support if (fullClassName == typeof(LinearGradientBrush).FullName) return new LinearGradientBrush(new Point(0,0), new Point(0,100),SystemColors.Control, SystemColors.ControlDark); else return new SolidBrush(SystemColors.Control); #endregion } else return base.ConvertFrom(context, culture, value); } #region —- No need to change this code ————————————— private ArrayList TypesToDisplayArray = new ArrayList(TypesToDisplay); public override bool GetStandardValuesSupported(System.ComponentModel.ITypeDescriptorContext context) { return true; } public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(System.ComponentModel.ITypeDescriptorContext context) { return new StandardValuesCollection(TypesToDisplayArray); } public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Type sourceType) { if (sourceType == typeof(string)) return true; else return base.CanConvertFrom(context, sourceType); } #endregion } }