Title: Enumerate metafile records in C#
This example shows how you can enumerate metafile records. You can then decide which ones to draw. (Although I'm not sure in general how you make that decision.)
When the program starts, it uses the following code to load the metafile and enumerate metafile records.
// Keep the metafile loaded.
private Metafile TheMetafile;
// A Graphics object used to draw on the current bitmap.
private Graphics TheGraphics;
// The index of the last record we drew.
private int LastRecord;
private void Form1_Load(object sender, EventArgs e)
{
clbRecords.CheckOnClick = true;
picResult.SizeMode = PictureBoxSizeMode.Zoom;
// Load the metafile.
TheMetafile = (Metafile)Metafile.FromFile("Volleyball.wmf");
// Use any Graphics object to enumerate the metafile records.
using (Graphics gr = this.CreateGraphics())
{
gr.EnumerateMetafile(TheMetafile,
new PointF(0, 0), ListRecordCallback);
}
// Initially check all records.
for (int i = 0; i < clbRecords.Items.Count; i++)
clbRecords.SetItemChecked(i, true);
// Display the initial result.
DisplayRecords();
}
// Add a record to the metafile record ListBox.
private bool ListRecordCallback(EmfPlusRecordType record_type,
int flags, int data_size, IntPtr data,
PlayRecordCallback callback_data)
{
clbRecords.Items.Add(record_type.ToString());
return true;
}
This code starts by defining some variables that the program will need to enumerate metafile records. TheMetafile is the loaded metafile. The form's Load event handler loads the metafile and the program keeps it loaded for easy use.
TheGraphics is a Graphics object that the program will use to draw on a bitmap. LastRecord is the index of the last record in the metafile that we drew. You'll see how these variables are used shortly.
The form's Load event handler loads the metafile. It then creates a Graphics object (any object will do) and then calls its EnumerateMetafile method to enumerate metafile records. The final parameter to this method is a callback method that EnumerateMetafile should invoke for each of the metafile's records.
The Load event handler finishes by checking all of the items in the CheckedListBox and then calling DisplayRecords to show the result.
The ListRecordCallback method is called for each record in the metafile. It adds the record type's name to the program's list.
The following code shows the DisplayRecords method that draws the selected metafile records.
// Draw the checked metafile records.
private void DisplayRecords()
{
// Make a bitmap to hold the result.
int wid = TheMetafile.Width;
int hgt = TheMetafile.Height;
Bitmap bm = new Bitmap(wid, hgt);
// Start at the first record.
LastRecord = -1;
// Draw the selected records on the bitmap.
using (TheGraphics = Graphics.FromImage(bm))
{
Rectangle dest = new Rectangle(0, 0, wid, hgt);
TheGraphics.EnumerateMetafile(
TheMetafile, dest, DrawRecordCallback);
}
// Display the result.
picResult.Image = bm;
}
This method creates a bitmap that has size matching that of the metafile. Note that the resolution of the bitmap probably won't match that of the metafile. Metafiles often use a very high resolution such as 1,000 dots per inch. In contrast, a bitmap displayed on the screen usually has a resolution of 96 pixels per inch. You might like to try to make the bitmap big enough to represent each position in the metafile as a pixel. Unfortunately that would give you a really huge bitmap (it could easily be millions of pixels wide and millions of pixels tall) that you probably can't actually create.
Fortunately it won't matter. We'll need to resize the bitmap to fit in the program's PictureBox anyway, so making a bitmap the size of the metafile will be plenty big enough.
Next the method sets LastRecord to -1 so the drawing code starts with the first record.
It then creates a Graphics object associated with the bitmap and saves it in the variable TheGraphics. The code creates a Rectangle to represent the area where we want the metafile drawn, and then it calls the Graphics object's EnumerateMetafile method to enumerate the metafile records again. This time the final parameter is DrawRecord so the method invokes it for each metafile record.
The method finishes by displaying the bitmap.
The following code shows the DrawRecordCallback method.
// Draw a record on TheGraphics.
private bool DrawRecordCallback(EmfPlusRecordType record_type,
int flags, int data_size, IntPtr data,
PlayRecordCallback callback_data)
{
// Consider the next record.
LastRecord++;
// If this record is not selected, skip it.
if (!clbRecords.GetItemChecked(LastRecord))
{
Console.WriteLine("Skipping " + LastRecord +
": " + record_type.ToString());
return true;
}
// Display this record.
byte[] data_array = null;
if (!data.Equals(IntPtr.Zero))
{
// Copy the unmanaged record data into a managed
// byte buffer that we can pass to PlayRecord.
data_array = new byte[data_size];
Marshal.Copy(data, data_array, 0, data_size);
}
// Play the record.
TheMetafile.PlayRecord(record_type, flags,
data_size, data_array);
// Continue the enumeration.
return true;
}
This method increments LastRecord so it contains the index of the next record we are considering. It uses that value to see if the corresponding list box item is checked. If it is not checked, it displays a message in the Console window and continues.
If the item is checked, the method draws the record. To do that, it creates a byte array. If the record has data, the code copies it into the byte array. The method then calls the metafile's PlayRecord method, passing it information about the record. That makes the metafile draw the record onto the graphics object.
This is all pretty awkward because it follows an older code pattern that requires you to pass a callback method into a routine. It also uses older point and byte oriented data. A more modern version might use events instead of callbacks and use a data class instead of pointers and bytes. As it is, I don't know how you would programmatically decide which records to draw as you enumerate metafile records.
Download the example to experiment with it and to see additional details.
|