|
|
You have just spent several days writing a beautiful looking custom control but
are faced with one last problem that is spoiling the whole effect. Whenever the
control is redrawn it flickers. This is most obvious when the control is being
resized and so redrawn many times in succession.
Solving this problem is very easy with the .NET Framework. If you come from a
C++ and GDI background then the solution was to create a memory based bitmap,
draw the control onto the bitmap and then blit this to the screen (otherwise
known as double buffering). This is such a common requirement that the
UserControl class actually implements this functionality for you. All you
need to do is include the following two lines of code into your class constructor.
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
The first line will request that double buffering be used whenever the
OnBackground or OnPaint methods are called. This will reduce the
amount of flicker but may not remove it completely as painting the whole control
still results in two separate blitting operations.
The second line of code above is used to ensure that only a single blitting
operation occurs when drawing. This occurs when the underlying windows WM_PAINT
message is processed. When this happens it will create the memory bitmap, call
the OnBackground method, call the OnPaint method and then finally blit
the result to the screen.
The only drawback to this technique is the greater use of resources. However,
most controls are relatively small in screen size and so this is unlikely to be
an issue.
|
|
|
|
|
|
Usually a bitmap will be embedded into your assembly automatically, just by
being associated with a property of a Form using the designer. But
there are many circumstances where you may want to embed a bitmap (or other
resource) without associating it with any particular Form.
You can do this in Visual Studio .NET by right clicking the project of interest
and selecting the 'Add Existing Item' option. Navigate to your bitmap and
selecting it will make it appear in your project details. Now select the bitmap
and modify the 'Build' property to become 'Embed as resource'. Rebuild your
project and then use the ILDAsm.exe utility to examine the manifest for
your built assembly. You will now see that the bitmap has been added as a resource.
To extract this resource at runtime is not obvious but only involves three
steps: -
// Get the assembly we are built into
Assembly myAssembly =
Assembly.GetAssembly(Type.GetType("MyNamespace.ThisClass"));
// Get the resource stream containing the embedded resource
Stream imageStream =
myAssembly.GetManifestResourceStream("Example.bmp");
// Load the bitmap from the stream
Bitmap pics = new Bitmap(imageStream);
The first line of code is used to get a reference to the assembly this code is
built into. In your own code you should substitute the 'MyNamespace.ThisClass'
string for the fully qualified name of the class the code is inside.
The second line requests from the assembly a stream that contains the contents
of the named resource. This name will need to match the name that appears when
using the ILDAsm.exe utility. Visual Studio will create this name as the
default namespace appended with the name of the file. If your code generates an
exception at this point then double check the name you provide exactly matches
that inside the manifest.
The last line of code is obvious and simply uses the Bitmap constructor
that takes as input a Stream.
If you prefer to build your projects manually without Visual Studio then you
can still use the same technique. Just use the /resource:Example.bmp
option in your csc command to cause the bitmap to be embedded. In which
case the name in the manifest will exactly match the resource filename rather than
be modified by Visual Studio.
|
|
|
|
|
|
Often when writing your own class you need to expose a collection of items to
the caller. Usually the caller will also need to modify that collection at
runtime. For example, a TabControl has a collection of TabPage
objects that the caller can modify to add and remove pages.
Although creating a collection class is not difficult it can be time consuming.
Rather than begin from scratch it is much easier to derive from the framework
class CollectionBase. This class will handle the management of the
collection for us by using an ArrayList instance internally.
Two tasks however remain. First we need to generate events when the contents of
the collection change, so that our class can perform appropriate actions. For
example, when a new TabPage is added to a pages collection the TabControl
would need to be notified of this so it can change the appearance appropriately.
To achieve this we define a new class called CollectionWithEvents as shown
below: -
public class CollectionWithEvents : CollectionBase
{
// Declare the event signatures
public delegate void CollectionClear();
public delegate void CollectionChange(int index, object value);
// Collection change events
public event CollectionClear Clearing;
public event CollectionClear Cleared;
public event CollectionChange Inserting;
public event CollectionChange Inserted;
public event CollectionChange Removing;
public event CollectionChange Removed;
// Overrides for generating events
protected override void OnClear()
{
if (Clearing != null) Clearing();
}
protected override void OnClearComplete()
{
if (Cleared != null) Cleared();
}
protected override void OnInsert(int index, object value)
{
if (Inserting != null) Inserting(index, value);
}
protected override void OnInsertComplete(int index, object value)
{
if (Inserted != null) Inserted(index, value);
}
protected override void OnRemove(int index, object value)
{
if (Removing != null) Removing(index, value);
}
protected override void OnRemoveComplete(int index, object value)
{
if (Removed != null) Removed(index, value);
}
}
Our second task is to ensure that the collection class is type specific.
So we derive a type specific class from the CollectionWithEvents
base class and expose the set of methods the caller needs to manipulate it.
For example, a collection to manipulate objects of type MyType would
look like the following: -
public class MyTypeCollection : CollectionWithEvents
{
public int Add(MyType value)
{
return base.List.Add(value as object);
}
public void Remove(MyType value)
{
base.List.Remove(value as object);
}
public void Insert(int index, MyType value)
{
base.List.Insert(index, value as object);
}
public bool Contains(MyType value)
{
return base.List.Contains(value as object);
}
public MyType this[int index]
{
get { return (base.List[index] as MyType); }
}
}
The advantage of this design is that any additional collections we need, involve
creating just a single class identical to the one above but with the MyType
replaced with whichever new type we desire.
Using the collection is very simple. Here is an example class that creates and
exposes a collection for manipulation by the caller. The constructor hooks into
some of the events it exposes in order to perform whatever implementation
actions it needs.
class Example
{
protected MyTypeCollection _collection;
class Example()
{
// Create the new but empty collection
_collection = new MyTypeCollection();
// Hookup to whichever events are of interest
_tabPages.Cleared +=
new CollectionWithEvents.CollectionClear(OnClearedPages);
_tabPages.Inserted +=
new CollectionWithEvents.CollectionChange(OnInsertedPage);
_tabPages.Removed +=
new CollectionWithEvents.CollectionChange(OnRemovedPage);
}
public MyTypeCollection MyTypes
{
get { return _collection; }
}
public MyType this[int index]
{
get { return _collection[index]; }
}
protected void OnClearedPages()
{
// TODO...your actions
}
protected void OnInsertedPage(int index, object value)
{
MyType obj = value as MyType;
// TODO...your actions
}
protected void OnRemovedPage(int index, object value)
{
MyType obj = value as MyType;
// TODO...your actions
}
}
The above example only hooks into the events that occur after the collection
has been altered. In practice you will probably also want to hook into those
that occur before the collection is modified.
|
|
|
|
|
|
Having written a new Control you now want it to be drawn in the
correct colors depending on the current display settings. You can easily get
hold of colors using the SystemColors class and other display
values using SystemInformation. But you also need to be notified
when these values change because the user has selected a different scheme or
theme. Using the following code to hook into the event generated when this
change occurs: -
public MyConstructor()
{
// Hook into system event for users preferences changes
Microsoft.Win32.SystemEvents.UserPreferenceChanged +=
new UserPreferenceChangedEventHandler(OnPreferenceChanged);
}
protected void OnPreferenceChanged(object sender,
UserPreferenceChangedEventArgs e)
{
switch(e.Category)
{
case UserPreferenceCategory.Color:
// Update my colors, brushes, pens etc...
break
}
}
|
|
|
|