EventArgs and derived classes

A

ACS

Hello,

I'm wondering for certain events, such as a MouseClick event which
uses MouseEventArgs, how are these EventArgs classes' members
populated? i.e. how do the .X and .Y members get assigned?

I ask because for a project I'm working on, I'm making a custom
ListView control with custom everything: ColumnHeaders, ListViewItems
and ListViewSubItems.

My DrawSubItem delegate method by default gets passed a
DrawListViewSubItemEventArgs class, but the .SubItem member points to
a standard ListViewItem.ListViewSubItem class, where instead I want it
to point to my custom SubItem class (in this case called SubItemEx.)

I presume I would have to create my own EventArgs classes, derived
from DrawListViewSubItemEventArgs, which I've already done. However I
have no idea how to assign the proper SubItemEx class, since I don't
know WHERE the SubItem class is assigned.

Anyone know how to do this?
 
P

Peter Duniho

Hello,

I'm wondering for certain events, such as a MouseClick event which
uses MouseEventArgs, how are these EventArgs classes' members
populated? i.e. how do the .X and .Y members get assigned?

The same way they'd be assigned/initialized in any other class.
[...]
I presume I would have to create my own EventArgs classes, derived
from DrawListViewSubItemEventArgs, which I've already done. However I
have no idea how to assign the proper SubItemEx class, since I don't
know WHERE the SubItem class is assigned.

Anyone know how to do this?

Have you looked at the constructor for the DrawListViewSubItemEventArgs
class?
http://msdn2.microsoft.com/en-us/li...emeventargs.drawlistviewsubitemeventargs.aspx

Assuming you are raising the event yourself, you don't need to create your
own subclass of DrawListViewSubItemEventArgs. Just pass the appropriate
value for the "subItem" parameter when instantiating the
DrawListViewSubItemEventArgs object.

Pete
 
A

ACS

I'm wondering for certain events, such as a MouseClick event which
uses MouseEventArgs, how are these EventArgs classes' members
populated? i.e. how do the .X and .Y members get assigned?

The same way they'd be assigned/initialized in any other class.
[...]
I presume I would have to create my own EventArgs classes, derived
from DrawListViewSubItemEventArgs, which I've already done. However I
have no idea how to assign the proper SubItemEx class, since I don't
know WHERE the SubItem class is assigned.
Anyone know how to do this?

Have you looked at the constructor for the DrawListViewSubItemEventArgs
class?http://msdn2.microsoft.com/en-us/library/system.windows.forms.drawlis...

Assuming you are raising the event yourself, you don't need to create your
own subclass of DrawListViewSubItemEventArgs. Just pass the appropriate
value for the "subItem" parameter when instantiating the
DrawListViewSubItemEventArgs object.

Pete

Hi Pete,

Yes I've looked at the constructor. My problem is I don't know WHERE
that class is created. My DrawSubItem event handler is called and that
parameter, which is created and assigned behind-the-scenes, just gets
passed. I need to know how and where it's populated.
 
P

Peter Duniho

Yes I've looked at the constructor. My problem is I don't know WHERE
that class is created. My DrawSubItem event handler is called and that
parameter, which is created and assigned behind-the-scenes, just gets
passed. I need to know how and where it's populated.

Maybe you could be more clear on how you've actually overridden the
default behavior then. You wrote in your original post that you've got
"custom everything", including a custom ListViewSubItem.

If you're not raising the event yourself, then presumably you've left the
original ListView implementation alone for that. But assuming you've
replaced the ListViewSubItem instances in the ListView with your own
sub-class, then those should already be in the
DrawListViewSubItemEventArgs as the SubItem property.

The type of the property is ListViewSubItem, but assuming you've correctly
overridden the rest of the implementation, then that should actually be an
instance of your sub-class of ListViewSubItem. Just cast it to your
sub-class of ListViewSubItem and it should work.

If that doesn't work, then you're not actually replacing the default
implementation with your own as implied by your original post. A
concise-but-complete code sample provided by you would go a long way
toward helping others understand your question better.

Pete
 
A

ACS

Maybe you could be more clear on how you've actually overridden the
default behavior then. You wrote in your original post that you've got
"custom everything", including a custom ListViewSubItem.

If you're not raising the event yourself, then presumably you've left the
original ListView implementation alone for that. But assuming you've
replaced the ListViewSubItem instances in the ListView with your own
sub-class, then those should already be in the
DrawListViewSubItemEventArgs as the SubItem property.

The type of the property is ListViewSubItem, but assuming you've correctly
overridden the rest of the implementation, then that should actually be an
instance of your sub-class of ListViewSubItem. Just cast it to your
sub-class of ListViewSubItem and it should work.

If that doesn't work, then you're not actually replacing the default
implementation with your own as implied by your original post. A
concise-but-complete code sample provided by you would go a long way
toward helping others understand your question better.

Pete

Hi Peter,

Ok, I understand what you mean. Since my ListViewSubItemEx class is
derived directly from ListViewSubItem, then assuming I've overriden
the latter correctly, the former should already be assigned as
the .SubItem member.

I tested this out by merely casting from SubItem to SubItemEx in my
DrawSubItem handler, with curious results. Just by merely performing
the cast the function partially fails; before I was able to draw a
particular bitmap in the subitem, but after I casted with:

ListViewSubItemEx subItem = (ListViewSubItemEx)e.SubItem;

Just this line by itself causes a completely unrelated failure. Very
strange...

Anyways, here's my code: http://rafb.net/p/sqA5mr60.html

I test this code out by adding the ListViewEx control, a few columsn,
and a couple of items.
Then I set one of the subitem columns to have checkboxes, thusly:

lvxTest.Columns[1].HasCheckBox = true;

Now this works fine; the checkbox appears for each subitem in the
specified column.
But if I uncomment Line 53, then Line 60 completely fails.
Of course this is a completely different problem, although it seems
like it's related.

Any ideas?
 
A

ACS

Maybe you could be more clear on how you've actually overridden the
default behavior then. You wrote in your original post that you've got
"custom everything", including a custom ListViewSubItem.
If you're not raising the event yourself, then presumably you've left the
original ListView implementation alone for that. But assuming you've
replaced the ListViewSubItem instances in the ListView with your own
sub-class, then those should already be in the
DrawListViewSubItemEventArgs as the SubItem property.
The type of the property is ListViewSubItem, but assuming you've correctly
overridden the rest of the implementation, then that should actually be an
instance of your sub-class of ListViewSubItem. Just cast it to your
sub-class of ListViewSubItem and it should work.
If that doesn't work, then you're not actually replacing the default
implementation with your own as implied by your original post. A
concise-but-complete code sample provided by you would go a long way
toward helping others understand your question better.

Hi Peter,

Ok, I understand what you mean. Since my ListViewSubItemEx class is
derived directly from ListViewSubItem, then assuming I've overriden
the latter correctly, the former should already be assigned as
the .SubItem member.

I tested this out by merely casting from SubItem to SubItemEx in my
DrawSubItem handler, with curious results. Just by merely performing
the cast the function partially fails; before I was able to draw a
particular bitmap in the subitem, but after I casted with:

ListViewSubItemEx subItem = (ListViewSubItemEx)e.SubItem;

Just this line by itself causes a completely unrelated failure. Very
strange...

Anyways, here's my code:http://rafb.net/p/sqA5mr60.html

I test this code out by adding the ListViewEx control, a few columsn,
and a couple of items.
Then I set one of the subitem columns to have checkboxes, thusly:

lvxTest.Columns[1].HasCheckBox = true;

Now this works fine; the checkbox appears for each subitem in the
specified column.
But if I uncomment Line 53, then Line 60 completely fails.
Of course this is a completely different problem, although it seems
like it's related.

Any ideas?

Ok well, I found out the reason uncommenting Line 53 causes Line 60 to
fail is, that if Line 53 is executed, the program will quite that
function without processing anything else. I put some checkpoints in
the method to verify this...
 
P

Peter Duniho

Ok, I understand what you mean. Since my ListViewSubItemEx class is
derived directly from ListViewSubItem, then assuming I've overriden
the latter correctly, the former should already be assigned as
the .SubItem member.
Correct.

I tested this out by merely casting from SubItem to SubItemEx in my
DrawSubItem handler, with curious results. Just by merely performing
the cast the function partially fails; before I was able to draw a
particular bitmap in the subitem, but after I casted with:

ListViewSubItemEx subItem = (ListViewSubItemEx)e.SubItem;

Just this line by itself causes a completely unrelated failure. Very
strange...

Anyways, here's my code: http://rafb.net/p/sqA5mr60.html

I wouldn't say that's a "concise-but-complete" example. It's neither
concise (has lots of stuff that's not necessary), nor complete (can't be
compiled and run by itself).

See Jon Skeet's article http://www.yoda.arachsys.com/csharp/complete.html
for more details on what an appropriate sample would actually look like.

I will say that at first glance, the code you posted "smells funny". It's
unusual for hiding a base method or property to be the right thing to do
(e.g. lines 83, 87, 213, 224, etc.). Also, the cast on line 217 looks
suspicious (but that may just be because there's so much other stuff in
the example, that I can't find the other part of the code that makes that
work). Why should you be able to cast the return value from the base
class implementation to your own sub-class? How does the base class know
to instantiate your sub-class?

It would be more appropriate for you to be explicitly instantiating your
sub-classes, ListViewItem with the appropriate ListViewSubItem attached,
adding the ListViewSubItem explicitly to the ListViewItem's sub-items, and
then adding that ListViewItem explicitly to the ListView. Once you've
done that, then those will be the instances used for drawing, which you
can then reliably cast back to your own sub-classes.

From a design/maintainability standpoint, since the base classes don't
offer virtual methods for adding things, obviously the interface wasn't
designed with the intent that you be able to override those methods.
Rather than hiding those methods, you should just provide alternative ways
to populate the ListView (for example, new versions of the methods
overloaded to take your custom sub-classes). You can hide the methods,
but since that doesn't actually ensure that the base class methods don't
get used somewhere else, it's a poor way to ensure that your sub-classed
versions get called. It's better to just go ahead and be explicit about
it, so that it's obvious elsewhere what's going on.

Anyway, if you can pare down your example to the bare minimum
demonstrating the basic sub-classing of the ListViewItem and
ListViewSubItem (leave out the column header stuff for sure), and provide
a complete example, then maybe more useful advice can be provided.

Pete
 
P

Peter Duniho

[...]
It would be more appropriate for you to be explicitly instantiating your
sub-classes, ListViewItem with the appropriate ListViewSubItem attached,
adding the ListViewSubItem explicitly to the ListViewItem's sub-items,
and then adding that ListViewItem explicitly to the ListView. Once
you've done that, then those will be the instances used for drawing,
which you can then reliably cast back to your own sub-classes.

I've posted below an example of what I mean. I've only sub-classed
ListViewSubItem, but the basic idea applies generally. I didn't bother
putting any actual custom behavior in my sub-class, as it's sufficient for
the example to demonstrate that the sub-class is indeed passed in the
DrawListViewSubItemEventArgs class.

Also note that the DrawListViewSubItem event will be raised for each
column in the details view, including the first one representing the item
itself. The ListViewItem's SubItems collection always includes a
ListViewSubItem representing itself as the first element in the
collection. It's possible you could replace this element with your own
sub-class, but I didn't try that and it seems hazardous to me. As long as
in your handler for DrawListViewSubItem you make sure that you're dealing
with that case properly, by only casting to your own class when
appropriate, I don't think it's necessary to replace the ListViewSubItem
that the ListViewItem puts there itself.

I hope the example helps. As you can see, there's not really any need to
override the various collection and main control classes, if you are not
actually wanting to override the behavior of those classes. Just override
the classes you really need to override, and let the other classes do
their jobs as normal.

Pete


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace TestCustomListView
{
public class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
if (textBox1.Text != "")
{
ListViewItem lvi = new ListViewItem(textBox1.Text);

lvi.SubItems.Add(new MyListViewSubItem(lvi, "sub item ("
+ textBox1.Text + ")"));

listView1.Items.Add(lvi);
}
}

private void listView1_DrawSubItem(object sender,
DrawListViewSubItemEventArgs e)
{
MyListViewSubItem mysubitem = e.SubItem as MyListViewSubItem;

if (mysubitem != null)
{
Debug.WriteLine("mysubitem class: "
+ mysubitem.GetType().Name);
}

e.DrawBackground();
e.DrawText();
}

private void listView1_DrawColumnHeader(object sender,
DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}

/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be
disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
this.listView1 = new System.Windows.Forms.ListView();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(13, 13);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new
System.EventHandler(this.button1_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(94, 15);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 1;
//
// columnHeader1
//
this.columnHeader1.Text = "Item";
//
// columnHeader2
//
this.columnHeader2.Text = "Sub-Item";
this.columnHeader2.Width = 93;
//
// listView1
//
this.listView1.Anchor =
((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top
| System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.listView1.Columns.AddRange(new
System.Windows.Forms.ColumnHeader[] {
this.columnHeader1,
this.columnHeader2});
this.listView1.Location = new System.Drawing.Point(13, 43);
this.listView1.Name = "listView1";
this.listView1.OwnerDraw = true;
this.listView1.Size = new System.Drawing.Size(399, 254);
this.listView1.TabIndex = 2;
this.listView1.UseCompatibleStateImageBehavior = false;
this.listView1.View = System.Windows.Forms.View.Details;
this.listView1.DrawSubItem += new
System.Windows.Forms.DrawListViewSubItemEventHandler(this.listView1_DrawSubItem);
this.listView1.DrawColumnHeader += new
System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.listView1_DrawColumnHeader);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(424, 309);
this.Controls.Add(this.listView1);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();

}

#endregion

private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.ColumnHeader columnHeader1;
private System.Windows.Forms.ColumnHeader columnHeader2;
private System.Windows.Forms.ListView listView1;
}

class MyListViewSubItem : ListViewItem.ListViewSubItem
{
public MyListViewSubItem(ListViewItem owner, string strItem) :
base(owner, strItem)
{
}
}

static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
 
A

ACS

Hi Peter,

Ah, I see what you mean.

Well the only reason I overrided ListViewItem and the collections is
that I wanted this whole thing to be transparent-- that is, when using
the ListViewEx control, all the methods, members and properties remain
exactly the same to the user, it's just the underlying functionality
that's different.
But I suppose you have a point-- override only what I need change and
leave everything else intact. It would certainly make my code a lot
easier.

I have a question though about the way you performed your cast--

MyListViewSubItem mysubitem = e.SubItem as MyListViewSubItem;

I don't recognize the "as" keyword. I would perform that cast as
follows:

MyListViewSubItem mysubitem = (MyListViewSubItem)e.SubItem;

Do they not do the same thing? I've used the latter casting method in
some of my other programs and it seems to work properly. If there is a
difference, what is it?

[...]
It would be more appropriate for you to be explicitly instantiating your
sub-classes, ListViewItem with the appropriate ListViewSubItem attached,
adding the ListViewSubItem explicitly to the ListViewItem's sub-items,
and then adding that ListViewItem explicitly to the ListView. Once
you've done that, then those will be the instances used for drawing,
which you can then reliably cast back to your own sub-classes.

I've posted below an example of what I mean. I've only sub-classed
ListViewSubItem, but the basic idea applies generally. I didn't bother
putting any actual custom behavior in my sub-class, as it's sufficient for
the example to demonstrate that the sub-class is indeed passed in the
DrawListViewSubItemEventArgs class.

Also note that the DrawListViewSubItem event will be raised for each
column in the details view, including the first one representing the item
itself. The ListViewItem's SubItems collection always includes a
ListViewSubItem representing itself as the first element in the
collection. It's possible you could replace this element with your own
sub-class, but I didn't try that and it seems hazardous to me. As long as
in your handler for DrawListViewSubItem you make sure that you're dealing
with that case properly, by only casting to your own class when
appropriate, I don't think it's necessary to replace the ListViewSubItem
that the ListViewItem puts there itself.

I hope the example helps. As you can see, there's not really any need to
override the various collection and main control classes, if you are not
actually wanting to override the behavior of those classes. Just override
the classes you really need to override, and let the other classes do
their jobs as normal.

Pete

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace TestCustomListView
{
public class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender,EventArgse)
{
if (textBox1.Text != "")
{
ListViewItem lvi = new ListViewItem(textBox1.Text);

lvi.SubItems.Add(new MyListViewSubItem(lvi, "sub item ("
+ textBox1.Text + ")"));

listView1.Items.Add(lvi);
}
}

private void listView1_DrawSubItem(object sender,
DrawListViewSubItemEventArgs e)
{
MyListViewSubItem mysubitem = e.SubItem as MyListViewSubItem;

if (mysubitem != null)
{
Debug.WriteLine("mysubitem class: "
+ mysubitem.GetType().Name);
}

e.DrawBackground();
e.DrawText();
}

private void listView1_DrawColumnHeader(object sender,
DrawListViewColumnHeaderEventArgs e)
{
e.DrawDefault = true;
}

/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be
disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows Form Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
this.listView1 = new System.Windows.Forms.ListView();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(13, 13);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new
System.EventHandler(this.button1_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(94, 15);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 20);
this.textBox1.TabIndex = 1;
//
// columnHeader1
//
this.columnHeader1.Text = "Item";
//
// columnHeader2
//
this.columnHeader2.Text = "Sub-Item";
this.columnHeader2.Width = 93;
//
// listView1
//
this.listView1.Anchor =
((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top
| System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.listView1.Columns.AddRange(new
System.Windows.Forms.ColumnHeader[] {
this.columnHeader1,
this.columnHeader2});
this.listView1.Location = new System.Drawing.Point(13, 43);
this.listView1.Name = "listView1";
this.listView1.OwnerDraw = true;
this.listView1.Size = new System.Drawing.Size(399, 254);
this.listView1.TabIndex = 2;
this.listView1.UseCompatibleStateImageBehavior = false;
this.listView1.View = System.Windows.Forms.View.Details;
this.listView1.DrawSubItem += new
System.Windows.Forms.DrawListViewSubItemEventHandler(this.listView1_DrawSubItem);
this.listView1.DrawColumnHeader += new
System.Windows.Forms.DrawListViewColumnHeaderEventHandler(this.listView1_DrawColumnHeader);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(424, 309);
this.Controls.Add(this.listView1);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();

}

#endregion

private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.ColumnHeader columnHeader1;
private System.Windows.Forms.ColumnHeader columnHeader2;
private System.Windows.Forms.ListView listView1;
}

class MyListViewSubItem : ListViewItem.ListViewSubItem
{
public MyListViewSubItem(ListViewItem owner, string strItem) :
base(owner, strItem)
{
}
}

static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}





}
 
P

Peter Duniho

Hi Peter,

Ah, I see what you mean.

Well the only reason I overrided ListViewItem and the collections is
that I wanted this whole thing to be transparent-- that is, when using
the ListViewEx control, all the methods, members and properties remain
exactly the same to the user, it's just the underlying functionality
that's different.

Well, you can build on what I showed and do that. But you'll run into
issues wherever the base class wasn't designed to be overridden. That may
not be so bad when you're just returning stuff, but methods that add
instances will always have the possibility of your class being bypassed by
an upcast to the base type.
But I suppose you have a point-- override only what I need change and
leave everything else intact. It would certainly make my code a lot
easier.

I think so. :) It also makes it clearer as to what's really going on,
given the lack of support for overriding the base class behaviors. Since
you can't guarantee that your subclasses will always be used, you might as
well just be very explicit about doing so. That way the contract is more
clear.
I have a question though about the way you performed your cast--

MyListViewSubItem mysubitem = e.SubItem as MyListViewSubItem;

I don't recognize the "as" keyword. I would perform that cast as
follows:

MyListViewSubItem mysubitem = (MyListViewSubItem)e.SubItem;

Do they not do the same thing? I've used the latter casting method in
some of my other programs and it seems to work properly. If there is a
difference, what is it?

They do _almost_ the same thing. The difference is, a straight cast will
throw an exception if the original reference cannot be validly cast to the
target type. With the "as" keyword, the result will simply be a null
reference if the cast is invalid.

I used it there because not all of the e.SubItem references will be
castable to MyListViewSubItem. So I use "as", and check for null.

Other than that, it's the same as casting. For references that are valid
to cast to the target type, there is no difference.

Generally speaking, I use a regular cast when it's an error for the
original reference to fail to be cast (i.e. absent any bugs, no exception
will ever be thrown), and I use "as" when it's not an error (i.e. I'm
going to include some code that checks for a null result and handles it
appropriately).

Pete
 
A

ACS

Hi Peter,

Alright, well this is certainly helping, my code is much cleaner
now! :)

The problem is, the DrawSubItem event handler won't work when drawing
the main ListView item.
Now I know that SubItem[0] is SUPPOSED to be the main ListView item,
in which case the event handler should work the same way.

However I'm finding that the method won't properly draw the first
item, because of course the cast to MyListViewSubItem fails for that
column.
Other than creating an if-else clause to check if the cast fails, and
then write a duplicate piece of code to work with e.Item rather than
e.SubItem, is there a way around this?
 
A

ACS

Hi Peter,

Alright, well this is certainly helping, my code is much cleaner
now! :)

The problem is, the DrawSubItem event handler won't work when drawing
the main ListView item.
Now I know that SubItem[0] is SUPPOSED to be the main ListView item,
in which case the event handler should work the same way.

However I'm finding that the method won't properly draw the first
item, because of course the cast to MyListViewSubItem fails for that
column.
Other than creating an if-else clause to check if the cast fails, and
then write a duplicate piece of code to work with e.Item rather than
e.SubItem, is there a way around this?
 
P

Peter Duniho

Hi Peter,

Alright, well this is certainly helping, my code is much cleaner
now! :)

The problem is, the DrawSubItem event handler won't work when drawing
the main ListView item.
Now I know that SubItem[0] is SUPPOSED to be the main ListView item,
in which case the event handler should work the same way.

However I'm finding that the method won't properly draw the first
item, because of course the cast to MyListViewSubItem fails for that
column.
Other than creating an if-else clause to check if the cast fails, and
then write a duplicate piece of code to work with e.Item rather than
e.SubItem, is there a way around this?

Well, that depends on what your code looks like. If you have some code
that should work equally well whether or not the cast succeeds, then that
code doesn't really need a MyListViewSubItem instance, but rather can work
with a regular ListViewSubItem. For that code, just always execute it on
the e.SubItem property directly regardless of the result of the cast.
Then, whatever code really does need a MyListViewSubItem, only make that
part conditional on the success of the cast (and of course, use the result
of the cast in that case).

Without more specifics, I'm afraid I can't really comment on how exactly
you'd handle the situation. I really don't think you should _need_ to
duplicate code, but it's certainly possible that the way you've structured
your code now that that's something that looks like you'd have to do.

If you post the event handler, maybe I can offer more specific advice.

Pete
 
A

ACS

Hi Pete,

That's what I'm trying to do at the moment. I'm only using
MyListViewSubItem when absolutely needed.
Basically I'm trying to implement checkboxes for every SubItem, not
just the main Item.

Actually I find that the event IS handling the main Item properly,
because if I create a new ListViewSubItemEx and assign it to subItem
instead of e.subItem, the method DOES work.
Furthermore I tried checking if subItem == null after a cast and it
NEVER seems to fail. So something else is going on here.

Here's my event handler code...


// Method for handling drawing of the subitems (and the main item,
which is index 0)
private void ListViewEx_DrawSubItem(object sender,
DrawListViewSubItemEventArgs e)
{
Graphics g = e.Graphics;
StringFormat fmt = new StringFormat();
ListViewSubItemEx subItem;

fmt.Trimming = StringTrimming.EllipsisCharacter; // Ensure ellipses
appear if the text won't fit
if ((this.Columns[e.ColumnIndex] as ColumnHeaderEx).HasCheckbox ==
true)
{
subItem = e.SubItem as ListViewSubItemEx;
if (subItem.Checked == true)
g.DrawImage(new Bitmap(GetType(), "checked.bmp"), e.Bounds.Left +
1, e.Bounds.Top + 1);
else
g.DrawImage(new Bitmap(GetType(), "unchecked.bmp"), e.Bounds.Left +
1, e.Bounds.Top + 1);
g.DrawString(e.SubItem.Text, this.Font, new SolidBrush(Color.Black),
new Rectangle(e.Bounds.Left + 15, e.Bounds.Top,
e.Bounds.Width - 15, e.Bounds.Height), fmt);
}
else
e.DrawText(TextFormatFlags.EndEllipsis);
}



Hi Peter,
Alright, well this is certainly helping, my code is much cleaner
now! :)
The problem is, the DrawSubItem event handler won't work when drawing
the main ListView item.
Now I know that SubItem[0] is SUPPOSED to be the main ListView item,
in which case the event handler should work the same way.
However I'm finding that the method won't properly draw the first
item, because of course the cast to MyListViewSubItem fails for that
column.
Other than creating an if-else clause to check if the cast fails, and
then write a duplicate piece of code to work with e.Item rather than
e.SubItem, is there a way around this?

Well, that depends on what your code looks like. If you have some code
that should work equally well whether or not the cast succeeds, then that
code doesn't really need a MyListViewSubItem instance, but rather can work
with a regular ListViewSubItem. For that code, just always execute it on
the e.SubItem property directly regardless of the result of the cast.
Then, whatever code really does need a MyListViewSubItem, only make that
part conditional on the success of the cast (and of course, use the result
of the cast in that case).

Without more specifics, I'm afraid I can't really comment on how exactly
you'd handle the situation. I really don't think you should _need_ to
duplicate code, but it's certainly possible that the way you've structured
your code now that that's something that looks like you'd have to do.

If you post the event handler, maybe I can offer more specific advice.

Pete
 
A

ACS

Another thing--

I realize the standard ListViewItem already has a .Checked property,
however since I want to draw my own checkboxes I'd rather handle the
drawing in the above method. If push comes to shove I can always
custom-draw it in another handler method for ListViewItem.
However it would be nice if I could figure out WHY this one isn't
working.


Hi Pete,

That's what I'm trying to do at the moment. I'm only using
MyListViewSubItem when absolutely needed.
Basically I'm trying to implement checkboxes for every SubItem, not
just the main Item.

Actually I find that the event IS handling the main Item properly,
because if I create a new ListViewSubItemEx and assign it to subItem
instead of e.subItem, the method DOES work.
Furthermore I tried checking if subItem == null after a cast and it
NEVER seems to fail. So something else is going on here.

Here's my event handler code...

// Method for handling drawing of the subitems (and the main item,
which is index 0)
private void ListViewEx_DrawSubItem(object sender,
DrawListViewSubItemEventArgs e)
{
Graphics g = e.Graphics;
StringFormat fmt = new StringFormat();
ListViewSubItemEx subItem;

fmt.Trimming = StringTrimming.EllipsisCharacter; // Ensure ellipses
appear if the text won't fit
if ((this.Columns[e.ColumnIndex] as ColumnHeaderEx).HasCheckbox ==
true)
{
subItem = e.SubItem as ListViewSubItemEx;
if (subItem.Checked == true)
g.DrawImage(new Bitmap(GetType(), "checked.bmp"), e.Bounds.Left +
1, e.Bounds.Top + 1);
else
g.DrawImage(new Bitmap(GetType(), "unchecked.bmp"), e.Bounds.Left +
1, e.Bounds.Top + 1);
g.DrawString(e.SubItem.Text, this.Font, new SolidBrush(Color.Black),
new Rectangle(e.Bounds.Left + 15, e.Bounds.Top,
e.Bounds.Width - 15, e.Bounds.Height), fmt);
}
else
e.DrawText(TextFormatFlags.EndEllipsis);

}

Hi Peter,
Alright, well this is certainly helping, my code is much cleaner
now! :)
The problem is, the DrawSubItem event handler won't work when drawing
the main ListView item.
Now I know that SubItem[0] is SUPPOSED to be the main ListView item,
in which case the event handler should work the same way.
However I'm finding that the method won't properly draw the first
item, because of course the cast to MyListViewSubItem fails for that
column.
Other than creating an if-else clause to check if the cast fails, and
then write a duplicate piece of code to work with e.Item rather than
e.SubItem, is there a way around this?
Well, that depends on what your code looks like. If you have some code
that should work equally well whether or not the cast succeeds, then that
code doesn't really need a MyListViewSubItem instance, but rather can work
with a regular ListViewSubItem. For that code, just always execute it on
the e.SubItem property directly regardless of the result of the cast.
Then, whatever code really does need a MyListViewSubItem, only make that
part conditional on the success of the cast (and of course, use the result
of the cast in that case).
Without more specifics, I'm afraid I can't really comment on how exactly
you'd handle the situation. I really don't think you should _need_ to
duplicate code, but it's certainly possible that the way you've structured
your code now that that's something that looks like you'd have to do.
If you post the event handler, maybe I can offer more specific advice.
 
P

Peter Duniho

Hi Pete,

That's what I'm trying to do at the moment. I'm only using
MyListViewSubItem when absolutely needed.
Basically I'm trying to implement checkboxes for every SubItem, not
just the main Item.

Actually I find that the event IS handling the main Item properly,
because if I create a new ListViewSubItemEx and assign it to subItem
instead of e.subItem, the method DOES work.
Furthermore I tried checking if subItem == null after a cast and it
NEVER seems to fail. So something else is going on here.

Well, you haven't been very specific about what's going wrong. In your
previous post, you said that the cast was failing because the first
ListViewSubItem isn't your own class. But now you are saying that even
for the first column, there's no problem.

Can you confirm that all of your column headers are your own
ColumnHeaderEx class, and that they always have "true" for the HasCheckbox
property? If the first condition doesn't hold, I would expect you to get
a null-reference exception on the first if() in your handler. If the
first condition holds, but the second condition doesn't for the first
column, then the first column is always going to wind up just calling
e.DrawText(), rather than going through the block of code that draws a
checkbox.

Basically, the code you posted and your current description do not match
what you wrote before. So it's not an example that helps understand your
previous question.

At this point, I'm back to suggesting that you post a concise-but-complete
sample of code that reliably demonstrates whatever your question is asking
about. Again, please make sure it's really concise. _Nothing_ extra
that's not immediately related to your question. Otherwise, it's much
less likely I or anyone else will bother trying to do anything with it.

I'll take as granted that there's something about the checkboxes that the
built-in class doesn't provide that you really need. Generally speaking,
you should think twice before customing a UI like this, because it can
potentially be confusing to the user. I'm assuming you've already gone
through the process of determining that this is really necessary.

Pete
 
A

ACS

Hi Pete,

Ok, here's a (hopefully) concise-but-complete listing.

http://utilitybase.com/paste/6152

I've tried to remove everything extra, leaving in only what's
necessary. This single-file code will compile all on its own. The only
stipulation is that two files, named "checked.bmp" and "unchecked.bmp"
should be in the same directory. They can be any valid image, really;
it's enough to demonstrate the problem.

I'll try to clarify things a bit further:

The event handler ListViewEx_DrawSubItem is correctly being called for
all subitems, including the subitem at index 0 (which is the main
item.)
However, as you will see upon running this program, the main item will
NEVER draw properly. It always draws the default text without any
image, despite my repeated efforts to make it draw as just another
subitem.

In regards to your question, I'm sure that the first if statement
(line 116) is always executing, since the else clause (displaying "No
check." in RED) is never executed. I've further verified this by
replacing Line 127 with a MessageBox statement, and I saw no message
box.

I've even tried stepping through the program line-by-line and found
that when it comes to the if statement on Line 119, the program will
jump back to the beginning of the ListViewEx_DrawSubItem handler, and
then draw the default text in the main item. I don't see why it would
do this-- it seems to be "crashing" at that point (I know not the
right terminology, but I don't know what else to call it.)
 
P

Peter Duniho

Hi Pete,

Ok, here's a (hopefully) concise-but-complete listing.

http://utilitybase.com/paste/6152

I've tried to remove everything extra, leaving in only what's
necessary. This single-file code will compile all on its own. The only
stipulation is that two files, named "checked.bmp" and "unchecked.bmp"
should be in the same directory. They can be any valid image, really;
it's enough to demonstrate the problem.

Well, that's not concise or complete. Unless the bitmaps are directly
related to the specific question (and I doubt they are), they don't belong
in the sample, nor does any code that depends on them. That breaks
"concise", and the fact that the images are not included breaks "complete".

It also appears that you're doing customization of the column headers and
sub items. Again, that breaks "concise", since it doesn't really appear
that the customization of the column headers has anything to do with the
customization of the sub items (after all, I was able to post a working
example of customized sub items that didn't touch the column headers).

Also, rather than posting your code somewhere else, you should be
including it in your message. I'll grant that it makes it slightly less
convenient to reference specific lines in the code, but these threads go
in archives, and it's much better for the lifetime of the post to match
the lifetime of any referenced material. The only way to guarantee that
is for all referenced material to be a part of the post.
I'll try to clarify things a bit further:

The event handler ListViewEx_DrawSubItem is correctly being called for
all subitems, including the subitem at index 0 (which is the main
item.)
However, as you will see upon running this program, the main item will
NEVER draw properly. It always draws the default text without any
image, despite my repeated efforts to make it draw as just another
subitem.

If the main item is not represented by a ListViewSubItemEx instance, then
why would you expect it to draw otherwise? And how is the default drawing
not considered "properly", considering that the main "0 column" sub item
isn't your custom class?
In regards to your question, I'm sure that the first if statement
(line 116) is always executing, since the else clause (displaying "No
check." in RED) is never executed. I've further verified this by
replacing Line 127 with a MessageBox statement, and I saw no message
box.

I've even tried stepping through the program line-by-line and found
that when it comes to the if statement on Line 119, the program will
jump back to the beginning of the ListViewEx_DrawSubItem handler, and
then draw the default text in the main item. I don't see why it would
do this-- it seems to be "crashing" at that point (I know not the
right terminology, but I don't know what else to call it.)

Well, that sounds to me as though an exception is happening, but it's
being caught. This isn't necessarily all that unusual. It's possible
that the ListView class is catching the exception, and upon doing so
reverts to default behavior. It's protecting itself from your bug.

So, what is your bug? Well, the most obvious issue is that you're using
"as" but you never bother to check whether the cast succeeded. You might
as well just do a straight cast (and the code likely would be just as
wrong in that case).

It _seems_ to me that a better way to write the handler might be like this:

private void ListViewEx_DrawSubItem(object sender,
DrawListViewSubItemEventArgs e)
{
Graphics g = e.Graphics;
StringFormat fmt = new StringFormat();

fmt.Trimming = StringTrimming.EllipsisCharacter; // Ensure
ellipses appear if the text won't fit
if ((this.Columns[e.ColumnIndex] as ColumnHeaderEx).HasCheckbox==
true)
{
ListViewSubItemEx subItem = e.SubItem as ListViewSubItemEx;

if (subItem != null && subItem.Checked == true)
g.DrawImage(Image.FromFile(".\checked.bmp"), e.Bounds.Left
+ 1, e.Bounds.Top + 1);
else
g.DrawImage(Image.FromFile(".\unchecked.bmp"),
e.Bounds.Left + 1, e.Bounds.Top + 1);
g.DrawString(e.SubItem.Text, this.Font, new
SolidBrush(Color.Black),
new Rectangle(e.Bounds.Left + 15, e.Bounds.Top,
e.Bounds.Width - 15, e.Bounds.Height), fmt);
}
else
g.DrawString("No check.", this.Font, new
SolidBrush(Color.Red), e.Bounds);
}

However, it seems a bit odd to me that you'd have a column that has the
"HasCheckbox" property set to "true" if the items in that column aren't
your ListViewSubItemEx instances. In fact, why put a special
"ColumnHeaderEx" instance in a column header that won't have a
"ListViewSubItemEx" as the items in that column?

If you made the column headers and sub items correlate better, such that
any column that has an extended header will always have extended sub items
in the column, you'd wind up with code that's more like this:

private void ListViewEx_DrawSubItem(object sender,
DrawListViewSubItemEventArgs e)
{
Graphics g = e.Graphics;
StringFormat fmt = new StringFormat();
ColumnHeaderEx header = Columns[e.ColumnIndex] as ColumnHeaderEx;

fmt.Trimming = StringTrimming.EllipsisCharacter; // Ensure
ellipses appear if the text won't fit
if (header != null && header.HasCheckbox)
{
ListViewSubItemEx subItem = (ListViewSubItemEx)e.SubItem;

using(Image img = subItem.Checked ?
Image.FromFile(".\checked.bmp") :
Image.FromFile(".\unchecked.bmp"))
{
g.DrawImage(img, e.Bounds.Left + 1, e.Bounds.Top + 1);
}

g.DrawString(e.SubItem.Text, this.Font, new
SolidBrush(Color.Black),
new Rectangle(e.Bounds.Left + 15, e.Bounds.Top,
e.Bounds.Width - 15, e.Bounds.Height), fmt);
}
else
g.DrawString("No check.", this.Font, new
SolidBrush(Color.Red), e.Bounds);
}

Finally, in rearranging the above code, I noticed you are a) creating a
new image each time you call this method (inefficient) and b) failing to
dispose the image (VERY bad). I fixed the lack of disposal, but really
you should just be loading the bitmaps once as static elements in the
class that uses them, and then refer to those static instances. Creating
a new image each time the sub item is drawn is just not a good approach.

Hope that helps.

Pete
 
A

ACS

Hi Peter,

Well thanks to all your help I've finally figured out what's going
on! :)

Turns out that I was assuming SubItem[0] was automatically a SubItemEx
when actually it was just a plain old SubItem.
So I added in some code to ensure that SubItem[0] will always be a
SubItemEx, and now it works perfectly!

Thanks for all your help.

Ok, here's a (hopefully) concise-but-complete listing.

I've tried to remove everything extra, leaving in only what's
necessary. This single-file code will compile all on its own. The only
stipulation is that two files, named "checked.bmp" and "unchecked.bmp"
should be in the same directory. They can be any valid image, really;
it's enough to demonstrate the problem.

Well, that's not concise or complete. Unless the bitmaps are directly
related to the specific question (and I doubt they are), they don't belong
in the sample, nor does any code that depends on them. That breaks
"concise", and the fact that the images are not included breaks "complete".

It also appears that you're doing customization of the column headers and
sub items. Again, that breaks "concise", since it doesn't really appear
that the customization of the column headers has anything to do with the
customization of the sub items (after all, I was able to post a working
example of customized sub items that didn't touch the column headers).

Also, rather than posting your code somewhere else, you should be
including it in your message. I'll grant that it makes it slightly less
convenient to reference specific lines in the code, but these threads go
in archives, and it's much better for the lifetime of the post to match
the lifetime of any referenced material. The only way to guarantee that
is for all referenced material to be a part of the post.
I'll try to clarify things a bit further:
The event handler ListViewEx_DrawSubItem is correctly being called for
all subitems, including the subitem at index 0 (which is the main
item.)
However, as you will see upon running this program, the main item will
NEVER draw properly. It always draws the default text without any
image, despite my repeated efforts to make it draw as just another
subitem.

If the main item is not represented by a ListViewSubItemEx instance, then
why would you expect it to draw otherwise? And how is the default drawing
not considered "properly", considering that the main "0 column" sub item
isn't your custom class?
In regards to your question, I'm sure that the first if statement
(line 116) is always executing, since the else clause (displaying "No
check." in RED) is never executed. I've further verified this by
replacing Line 127 with a MessageBox statement, and I saw no message
box.
I've even tried stepping through the program line-by-line and found
that when it comes to the if statement on Line 119, the program will
jump back to the beginning of the ListViewEx_DrawSubItem handler, and
then draw the default text in the main item. I don't see why it would
do this-- it seems to be "crashing" at that point (I know not the
right terminology, but I don't know what else to call it.)

Well, that sounds to me as though an exception is happening, but it's
being caught. This isn't necessarily all that unusual. It's possible
that the ListView class is catching the exception, and upon doing so
reverts to default behavior. It's protecting itself from your bug.

So, what is your bug? Well, the most obvious issue is that you're using
"as" but you never bother to check whether the cast succeeded. You might
as well just do a straight cast (and the code likely would be just as
wrong in that case).

It _seems_ to me that a better way to write the handler might be like this:

private void ListViewEx_DrawSubItem(object sender,
DrawListViewSubItemEventArgs e)
{
Graphics g = e.Graphics;
StringFormat fmt = new StringFormat();

fmt.Trimming = StringTrimming.EllipsisCharacter; // Ensure
ellipses appear if the text won't fit
if ((this.Columns[e.ColumnIndex] as ColumnHeaderEx).HasCheckbox ==
true)
{
ListViewSubItemEx subItem = e.SubItem as ListViewSubItemEx;

if (subItem != null && subItem.Checked == true)
g.DrawImage(Image.FromFile(".\checked.bmp"), e.Bounds.Left
+ 1, e.Bounds.Top + 1);
else
g.DrawImage(Image.FromFile(".\unchecked.bmp"),
e.Bounds.Left + 1, e.Bounds.Top + 1);
g.DrawString(e.SubItem.Text, this.Font, new
SolidBrush(Color.Black),
new Rectangle(e.Bounds.Left + 15, e.Bounds.Top,
e.Bounds.Width - 15, e.Bounds.Height), fmt);
}
else
g.DrawString("No check.", this.Font, new
SolidBrush(Color.Red), e.Bounds);
}

However, it seems a bit odd to me that you'd have a column that has the
"HasCheckbox" property set to "true" if the items in that column aren't
your ListViewSubItemEx instances. In fact, why put a special
"ColumnHeaderEx" instance in a column header that won't have a
"ListViewSubItemEx" as the items in that column?

If you made the column headers and sub items correlate better, such that
any column that has an extended header will always have extended sub items
in the column, you'd wind up with code that's more like this:

private void ListViewEx_DrawSubItem(object sender,
DrawListViewSubItemEventArgs e)
{
Graphics g = e.Graphics;
StringFormat fmt = new StringFormat();
ColumnHeaderEx header = Columns[e.ColumnIndex] as ColumnHeaderEx;

fmt.Trimming = StringTrimming.EllipsisCharacter; // Ensure
ellipses appear if the text won't fit
if (header != null && header.HasCheckbox)
{
ListViewSubItemEx subItem = (ListViewSubItemEx)e.SubItem;

using(Image img = subItem.Checked ?
Image.FromFile(".\checked.bmp") :
Image.FromFile(".\unchecked.bmp"))
{
g.DrawImage(img, e.Bounds.Left + 1, e.Bounds.Top + 1);
}

g.DrawString(e.SubItem.Text, this.Font, new
SolidBrush(Color.Black),
new Rectangle(e.Bounds.Left + 15, e.Bounds.Top,
e.Bounds.Width - 15, e.Bounds.Height), fmt);
}
else
g.DrawString("No check.", this.Font, new
SolidBrush(Color.Red), e.Bounds);
}

Finally, in rearranging the above code, I noticed you are a) creating a
new image each time you call this method (inefficient) and b) failing to
dispose the image (VERY bad). I fixed the lack of disposal, but really
you should just be loading the bitmaps once as static elements in the
class that uses them, and then refer to those static instances. Creating
a new image each time the sub item is drawn is just not a good approach.

Hope that helps.

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top