Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / Windows Forms / WinForm Controls / June 2008

Tip: Looking for answers? Try searching our database.

DataGridViewImageButtonColumn, custom column for DataGridView, image with mouse-overs

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Patrick B. - 20 Jun 2008 20:47 GMT
Dear DataGridView lovers,

I want a DataGridViewColumn that acts like a button but that shows an
image. Furthermore, when the user mouse-overs the image or clicks on it,
I want the image to change.

I've written some code the works, KINDA. Actually it works well, except
it generates error messages in the designer. I can't figure out why.

So, I'm going to post my code for 2 reasons:

1- To solicit help on perfecting the code. If you are a master at C#
custom controls, I need thee. Most importantly, there is an error that
happens not at run-time but in the designer, and I can't figure it out.

2- To share the code with others who are trying to write similar controls.

Problems with my code:

1- If you use the control in a DataGridView, close the form, then
re-open the form, you get an "Object reference not set to an instance of
an object" error message. You get 3 instances of that error, one for
each of the Images associated with the control. I'll paste a sample call
stack at the bottom of this message. (If you ignore the error and
continue then everything works properly. This error only appears in the
designer, not at run-time.)

2- I wanted a traditional Click event for the button. (The normal way of
testing for clicks within a DataGridView is a pain.) The way I got this
to work may is somewhat inelegant. It works though.

3- In order to get the mouseovers to work properly, I made
MouseEnterUnsharesRow always return true. This is probably bad design,
because it could lead to lots of memory being used by the DataGridView.

Without further ado, I give you the DataGridViewImageButtonColumn!
<cue applause>

******************************************************************
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace CustomControls
{
    public class DataGridViewImageButtonCell : DataGridViewTextBoxCell
    {
        public DataGridViewImageButtonCell()
            : base()
        {
        }
        enum MultiStateImageStates
        {
            StateNormal,
            StateMouseOver,
            StateClicked
        }
        Image imageToDraw = null;
        void SetImageFromState(MultiStateImageStates state, int rowIndex)
        {
            DataGridViewImageButtonColumn parent =
(DataGridViewImageButtonColumn)this.OwningColumn;
            if (parent != null) {
                switch (state) {
                    case MultiStateImageStates.StateNormal:
                        imageToDraw = parent.ImageNormal;
                        break;
                    case MultiStateImageStates.StateMouseOver:
                        imageToDraw = parent.ImageMouseOver;
                        break;
                    case MultiStateImageStates.StateClicked:
                        imageToDraw = parent.ImageClicked;
                        break;
                }
            }
            if (imageToDraw != null) {
                parent.DataGridView.InvalidateCell(parent.Index, rowIndex);
            }
        }
        protected override void Paint(System.Drawing.Graphics graphics,
System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle
cellBounds, int rowIndex, DataGridViewElementStates elementState, object
value, object formattedValue, string errorText, DataGridViewCellStyle
cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle,
DataGridViewPaintParts paintParts)
        {
            const int padding = 5;
            base.Paint(graphics, clipBounds, cellBounds, rowIndex,
elementState, value, String.Empty, errorText, cellStyle,
advancedBorderStyle, paintParts);
            if (imageToDraw == null) {
                DataGridViewImageButtonColumn parent =
(DataGridViewImageButtonColumn)this.OwningColumn;
                if (parent != null) {
                    imageToDraw = parent.ImageNormal;
                }
            }
            if (imageToDraw != null) {
                graphics.DrawImage(imageToDraw, cellBounds.Left +
padding, cellBounds.Top + padding);
            }
        }
        protected override void OnMouseEnter(int rowIndex)
        {
            SetImageFromState(MultiStateImageStates.StateMouseOver,
rowIndex);
            base.OnMouseEnter(rowIndex);
        }
        protected override void OnMouseLeave(int rowIndex)
        {
            SetImageFromState(MultiStateImageStates.StateNormal, rowIndex);
            base.OnMouseLeave(rowIndex);
        }
        protected override void
OnMouseDown(DataGridViewCellMouseEventArgs e)
        {
            SetImageFromState(MultiStateImageStates.StateClicked,
e.RowIndex);
            base.OnMouseDown(e);
        }
        protected override void OnClick(DataGridViewCellEventArgs e)
        {
            DataGridViewImageButtonColumn parent =
(DataGridViewImageButtonColumn)this.OwningColumn;
            if (parent != null) {
                parent.CellWasClicked(this);
            }
            base.OnClick(e);
        }
        protected override bool MouseEnterUnsharesRow(int rowIndex)
        {
            return true;
        }
    }
    public class DataGridViewImageButtonColumn : DataGridViewTextBoxColumn
    {
        public DataGridViewImageButtonColumn()
            : base()
        {
            this.ReadOnly = true;
            DataGridViewImageButtonCell template = new
DataGridViewImageButtonCell();
            base.CellTemplate = template;
        }
        void SetRowHeightFromImage(Image image)
        {
            const int verticalPadding = 10;
            if (image != null) {
                int minimumHeight = image.Height + verticalPadding;
                if (this.DataGridView.RowTemplate.Height < minimumHeight) {
                    this.DataGridView.RowTemplate.Height = minimumHeight;
                }
            }
        }
        Image imageNormal = null;
        public Image ImageNormal
        {
            get { return imageNormal; }
            set
            {
                imageNormal = value;
                SetRowHeightFromImage(imageNormal);
            }
        }
        Image imageMouseOver = null;
        public Image ImageMouseOver
        {
            get { return imageMouseOver; }
            set
            {
                imageMouseOver = value;
                SetRowHeightFromImage(imageMouseOver);
            }
        }
        Image imageClicked = null;
        public Image ImageClicked
        {
            get { return imageClicked; }
            set
            {
                imageClicked = value;
                SetRowHeightFromImage(imageClicked);
            }
        }
        internal void CellWasClicked(DataGridViewImageButtonCell cell)
        {
            if (CellClick != null) {
                EventArgs eventArgs = new EventArgs();
                CellClick(cell, eventArgs);
            }
        }
        public event EventHandler CellClick;
    }
}

******************************************************************

Sample call stack for the error in the designer:

Object reference not set to an instance of an object
at System.ComponentModel.ReflectPropertyDescriptor.SetValue(Object
component, Object value)
at
System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializePropertyAssignStatement(IDesignerSerializationManager
manager, CodeAssignStatement statement, CodePropertyReferenceExpression
propertyReferenceEx, Boolean reportError)
at
System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializeAssignStatement(IDesignerSerializationManager
manager, CodeAssignStatement statement)
at
System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializeStatement(IDesignerSerializationManager
manager, CodeStatement statement)
Patrick B. - 20 Jun 2008 22:25 GMT
If anyone else out there other than me actually needs a
"DataGridViewImageButtonColumn," here's the follow up to my last post.

I figured out the problem with my code that was causing the error
message. The function SetRowHeightFromImage() needs to be changed to the
following:

        void SetRowHeightFromImage(Image image)
        {
            const int verticalPadding = 10;
            if ((image != null) && (this.DataGridView != null)) {
                int minimumHeight = image.Height + verticalPadding;
                if (this.DataGridView.RowTemplate.Height < minimumHeight) {
                    this.DataGridView.RowTemplate.Height = minimumHeight;
                }
            }
        }

Also, I just found an article on CodeProject from somebody trying to do
nearly the same thing. His approach is different. (For example, he
derives his class from DataGridViewButtonCell.) See the article here:

http://www.codeproject.com/KB/grid/DGV_ImageButtonCell.aspx

Thanks!

Patrick
(pls respond only in group)

Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.