|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis tutorial will walk you through the process of creating a control which has a border that can be changed at design time. The tutorial assumes that you are reasonably familiar with writing controls in C#, and know your way around Visual Studio .NET. This tutorial is a little different in that the border is handled by changing the style properties of your window, meaning that you don't have to paint it yourself. Setting up the projectCreate a new C# windows application project, called "BorderSample". Now add another project – This one will contain the new control you’re going to create. In the File menu, choose "Add Project -> New Project". Choose a C# Windows Control Library as the project type, and call it "BorderControls". At this point, you’re in design mode looking at a blank user control. Rename UserControl1.cs to SimpleControl.cs. In the code, change both the class name and constructor to match. If you look at the control in design mode, you'll notice that it has no border. The default user control doesn't have one, but it's relatively simple to add. When a control is created, the Forms library uses the If you're familiar with Win32 programming, you'll recognize these. override CreateParams
If you hit enter immediately after typing those two words, Visual Studio should fill in the following for you: protected override CreateParams CreateParams
{
get{ return base.CreateParams; }
}
As you can see, it's simply getting the values from the base class and passing them through untouched. This gives us a chance to change the values, but what values do we use? If you look up the windows help topics on // Constant taken from WinUser.h
private const int WS_EX_CLIENTEDGE = 0x00000200;
Now that we know what the value is, we need to apply it: // Provide window style constants to enable control border
protected override CreateParams CreateParams
{
get
{
// Take the default params from the base class
CreateParams p = base.CreateParams;
// Add the extended "3d sunken border" style
p.ExStyle = p.ExStyle | WS_EX_CLIENTEDGE;
return p;
}
}
That's it. Go back to the "BorderSample" project, and open "Form1.cs" in the designer. If you look at the "My User Controls" tab in your toolbox, you should see your " Creating a design-enabled border propertyYou've created a user control with a border, which is cool and all, but what if you want a user to choose what kind of border to give the control? That's a little more involved, and requires using Interop, but it isn't difficult. The first thing you need is a value to hold the type of border you want. Controls in the Forms library use the private System.Windows.Forms.BorderStyle borderStyle = BorderStyle.Fixed3D;
This variable can take one of three values: Now you need a way to translate those values into window styles suitable for use by Looking through "WinUser.h", we find the following values: // These constants were taken from WinUser.h
private const int WS_BORDER = 0x00800000;
private const int WS_EX_CLIENTEDGE = 0x00000200;
Now we need a function to map the three possible // Convert borderStyle to Style and ExStyle values for Win32
private void BorderStyleToWindowStyle(ref int style, ref int exStyle)
{
style &= ~WS_BORDER;
exStyle &= ~WS_EX_CLIENTEDGE;
switch(borderStyle)
{
case BorderStyle.Fixed3D:
exStyle |= WS_EX_CLIENTEDGE;
break;
case BorderStyle.FixedSingle:
style |= WS_BORDER;
break;
case BorderStyle.None:
// No border style values
break;
}
}
Now the // Provide window style constants to enable control border
protected override CreateParams CreateParams
{
get
{
// Get the default values from the base class
CreateParams p = base.CreateParams;
// Store the Style and ExStyle values
int style = p.Style;
int exStyle = p.ExStyle;
// Modify the values to match the desired border style
BorderStyleToWindowStyle(ref style, ref exStyle);
// Store the results back in the CreateParams class
p.Style = style;
p.ExStyle = exStyle;
return p;
}
}
This will use the Now we need a public property to allow users (designers) to change the border style: /// <summary>
/// Gets or sets the border style of the tree view control.
/// </summary>
[Category("Appearance")]
[DescriptionAttribute("Border style of the control")]
[DefaultValue(typeof(System.Windows.Forms.BorderStyle), "Fixed3D")]
public BorderStyle BorderStyle
{
get {return borderStyle;}
set {borderStyle = value;}
}
The problem with this code is that it won't quite work. The window has already been created, and its style values have been set. The only way to change them is through a Win32 call, specifically Using P/Invoke to set window stylesIf you were to write the code to change the border style of a window using native C++, it’d look like this: // Get style and exstyle values
int style = GetWindowLong(hWnd, GWL_STYLE);
int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
// Modify existing style values
style = style & ~WS_BORDER;
exStyle = exStyle | WS_EX_CLIENTEDGE;
// Set new style values
SetWindowLong(hWnd, GWL_STYLE, style);
SetWindowLong(hWnd, GWL_EXSTYLE, exStyle);
// Tell windows that we changed the window frame style
SetWindowPos(hWnd, NULL, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
To make this work in C#, we’ll need the following things:
All of these things can be found in WinUser.h. First we'll do the constants. They're pretty easy, since all we have to do is change the #define values we find into integer constants, like this: // Constants from WinUser.h
const int GWL_STYLE = -16;
const int GWL_EXSTYLE = -20;
const uint SWP_NOSIZE = 0x0001;
const uint SWP_NOMOVE = 0x0002;
const uint SWP_NOZORDER = 0x0004;
const uint SWP_NOREDRAW = 0x0008;
const uint SWP_NOACTIVATE = 0x0010;
const uint SWP_FRAMECHANGED = 0x0020;
const uint SWP_SHOWWINDOW = 0x0040;
const uint SWP_HIDEWINDOW = 0x0080;
const uint SWP_NOCOPYBITS = 0x0100;
const uint SWP_NOOWNERZORDER = 0x0200;
const uint SWP_NOSENDCHANGING = 0x0400;
Now for the fun bit: calling the functions. Calling functions in external DLL's is pretty easy. You tell C# what the function looks like (the prototype), what the arguments and return values are, and where to find it. C# takes care of the rest. The int GetWindowLong(HWND hWnd, DWORD Index);
First, you’ll need to add this line to the top of your file: using System.Runtime.InteropServices; // Needed for DLLImport
Interop is short for Inter-operation, which is yet another name for Platform Invoke. Added to our class, the P/Invoke specification for [DllImport("User32", CharSet=CharSet.Auto)]
private static extern int GetWindowLong(IntPtr hWnd, int Index);
The The The actual function prototype looks like a static member function, except that it has the The other two functions are handled the same way as the first: [DllImport("User32", CharSet=CharSet.Auto)]
private static extern int SetWindowLong(IntPtr hWnd, int Index, int Value);
[DllImport("User32", ExactSpelling=true)]
private static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
int x, int y, int cx, int cy, uint uFlags);
There's one difference here – Once you've added the constants and the function prototypes to your control class, they're ready for use. Change yourBorderStyle property to this: /// <summary>
/// Gets or sets the border style of the tree view control.
/// </summary>
[Category("Appearance")]
[Description ("Border style of the control")]
[DefaultValue(typeof(System.Windows.Forms.BorderStyle), "Fixed3D")]
public BorderStyle BorderStyle
{
get {return borderStyle;}
set
{
borderStyle = value;
// Get Styles using Win32 calls
int style = GetWindowLong(Handle, GWL_STYLE);
int exStyle = GetWindowLong(Handle, GWL_EXSTYLE);
// Modify Styles to match the selected border style
BorderStyleToWindowStyle(ref style, ref exStyle);
// Set Styles using Win32 calls
SetWindowLong(Handle, GWL_STYLE, style);
SetWindowLong(Handle, GWL_EXSTYLE, exStyle);
// Tell Windows that the frame changed
SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE |
SWP_NOZORDER | SWP_NOOWNERZORDER |
SWP_FRAMECHANGED);
}
}
Since we've defined the functions we need using interop, they appear as part of our class, and can be called like any other function. The A brief explanation about the attributes applied to the Using attributes on your property values is a simple way to give the designer hints about how you want the properties presented to the user, and how your control should be persisted in code.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||