DataGridView DataGridViewButtonColumn doesn't notice a real Button

1.4k views Asked by At

thanks for looking at my question. I have a Object called UIChoice

namespace uitest
{
  public class UIChoice
  {
      public String name {get; set;}
      public Button yes { get; set; }      
  }
}

then I have a Form1 with a DataGridView (I call it grdChoice) like this

namespace uitest
{
  public class Form1 : Form
  {
    public BindingList<UIChoice> Choices = new BindingList<UIChoice>();
    public Form1 ()
    {
        InitializeComponent();

        DataGridViewTextBoxColumn colName = new DataGridViewTextBoxColumn();
        colName.HeaderText = "Name:";
        colName.Name = "yestext";
        colName.DataPropertyName = "name";
        grdChoice.Columns.Add(colName);

        DataGridViewButtonColumn colYes = new DataGridViewButtonColumn();
        colYes.HeaderText = "Yes:";
        colYes.Name = "yesbutton";
        colYes.DataPropertyName = "yes";
        colYes.FlatStyle = FlatStyle.Popup;
        grdChoice.Columns.Add(colYes);

        grdChoice.DataSource = Choices;
        grdChoice.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
        grdChoice.Show();  

        FillData();
    }

    private void FillData()
    {
        UIChoice myChoice = new UIChoice();
        Button Yes = new Button();
        Yes.Click += ((sender, args) =>
        {
                MessageBox.Show("try this yes works");
        });
        Yes.Text = "yes";
        myChoice.name = "try this";
        myChoice.yes = Yes;
        Choices.Add(myChoice);

        UIChoice myChoiceA = new UIChoice();
        Button YesA = new Button();
        YesA.Click += ((sender, args) =>
        {
                MessageBox.Show("try A yes works");
        });
        YesA.Text = "yes";
        myChoiceA.name = "try A";
        myChoiceA.yes = YesA;
        Choices.Add(myChoiceA);
    }
  }
}

what should happen (in my imagination) is that the DataGridView should notice that it is a DataGridViewButtonColumn and notice it has been given a Button and use it. But it doesn't. The problem for me is this is a replacement code. In real life the UIChoice object is heavily fortified with methods and the DataGridView was (originally) merely meant to replace a hand cranked panel of horizontally incrementing buttons (which became too "massive") the sad part is as I have written it this DataGridView doesn't work i.e. when I construct a BindingList object it doesn't seem to "notice" or "care" that I am giving it a button .. how do I make the DataGridView care that I already sent it the Buttons it needs?

2

There are 2 answers

0
TaW On BEST ANSWER

Close.

The Buttons, hopefully are in the Cell Values.

However you can't Click them just like that.

Instead you code the grdChoice.CellClick event, maybe like this:

if (e.ColumnIndex == yourButtonColumnIndex )
{
   Button btn = gradesDataGridView[yourButtonColumnIndex , e.RowIndex].Value as Button;
   if (btn != null) buttons_Click(btn, null);
}

This will trigger a common event buttons_Click where you can use the sender parameter to discern the buttons and to call the appropriate code.

This may be OK but maybe you'd rather see the right Clicks get triggered automatically..?

With a little Reflection Magic (found here on CodeProject) this works as well:

if (e.ColumnIndex == yourButtonColumnIndex )
{
   Button btn = gradesDataGridView[yourButtonColumnIndex , e.RowIndex].Value as Button;
   if (btn != null) 
   {
     var handler = (EventHandler)GetDelegate(btn, "EventClick");
     if (handler != null) btn.Invoke(handler);
   }

}

Here is the modified code to get at the event handler of the Buttons:

private static object GetDelegate(Control issuer, string keyName)
{
    // Get key value for a Click Event
    var key = typeof(Control)
        .GetField(keyName, BindingFlags.Static | BindingFlags.NonPublic | 
                               BindingFlags.FlattenHierarchy)
        .GetValue(null);
    var events = typeof(Component)
        .GetField("events", BindingFlags.Instance | BindingFlags.NonPublic)
        .GetValue(issuer);
    // Find the Find method and use it to search up listEntry for corresponding key
    var listEntry = typeof(EventHandlerList)
        .GetMethod("Find", BindingFlags.NonPublic | BindingFlags.Instance)
        .Invoke(events, new object[] { key });
    // Get handler value from listEntry 
    var handler = listEntry
        .GetType()
        .GetField("handler", BindingFlags.Instance | BindingFlags.NonPublic)
        .GetValue(listEntry);
    return handler;
}

Kudos to the guys on CodeProject, Mr. Kostikov and the anonymous user #2573523 who added the changes for Controls as opposed to Components..

Edit I noticed that the Buttons Column comes up without text. To display the individual buttons' Texts you need to code the DataGridView_CellFormattingevent like this:

private void gradesDataGridView_CellFormatting(
                                 object sender, DataGridViewCellFormattingEventArgs e)
{
   if (e.ColumnIndex == yourBottonColumnIndex )
   {
     Button btn = gradesDataGridView[yourButtonColumnIndex , e.RowIndex].Value as Button;
     if (btn != null) e.Value = btn.Text;
   }
}

Note that e.Value is not the cell.Value but only the displayed text.

0
Mr Heelis On

Thank you for the answer, being the weekend and away from work I had actually finished this 4 hours after posting but was made to wait 8 hours - nevertheless you shall have the points! :-) (I also borrowed your use of the e.value part of the button text display because it was more elegant than mine!)

ok so, me personaly, I fixed it by realising that the DataGridView CellClick (DataGridView sender).CurrentCell.Value is in fact a (Button). This is how I did it: (read the OP slightly & adjust the bottom of the constructor of Form1 thus):

grdChoice.DataSource = Verdicts;
grdChoice.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
grdChoice.CellClick += grdChoice_CellClick; //<-- add this line ***
grdChoice.Show();  

the next part merely Watches any click on any cell and if it is a button column artificially fire the button "inside" the button

private void grdChoice_CellClick(object sender, DataGridViewCellEventArgs e)
{
    if (e.ColumnIndex == grdChoice.Columns["yesbutton"].Index)
    {
        ((Button)((DataGridView)sender).CurrentCell.Value).PerformClick();
    }
}

the last part is the display the button text which I did thusly:

private void grdVerdict_CellFormat(object sender, DataGridViewCellFormattingEventArgs e)
{
   if (e.ColumnIndex == grdChoice.Columns["yesbutton"].Index)
   {
       e.Value = ((Button)grdChoice[e.ColumnIndex, e.RowIndex].Value).Text;
   }
}

with this handler

grdVerdict.CellFormatting += grdVerdict_CellFormat;  //<-- add this line near where the 3 stars are above***