5

I have a winform in c# that I dynamically created two comboboxes and a textbox. When a user selects say the month and year and enters a value in the text box I want get the related comboboxes values when the button is clicked to save. By default the month and year combobox will have the current month and year selected.

There is also another part in the same screen where data will be populated for the previous month like Jan to March for the current year in the comboboxes and the text boxes if available.

I am not sure whether this approach is correct or I should go with a datagridview. Below is the screenshot and my code. Any suggestions on how I can do this.

Screenshot enter image description here

Code

private void createComboMonths()
{
   int width = 79;
   int height = 24;
   int spacing = 28;
   ComboBox[] SubMonths = new ComboBox[12];
   for (int i = 0; i <= 11; ++i)
   {
       SubMonths[i] = new ComboBox();
       SubMonths[i].Name = "SubMonths";
       SubMonths[i].DropDownStyle = ComboBoxStyle.DropDownList;
       SubMonths[i].Size = new Size(width, height);
       SubMonths[i].Location = new Point(56, (i * height) + spacing);
       SubMonths[i].Items.Add("January");
       SubMonths[i].Items.Add("February");
       SubMonths[i].Items.Add("March");
       SubMonths[i].Items.Add("April");
       SubMonths[i].Items.Add("May");
       SubMonths[i].Items.Add("June");
       SubMonths[i].Items.Add("July");
       SubMonths[i].Items.Add("August");
       SubMonths[i].Items.Add("September");
       SubMonths[i].Items.Add("October");
       SubMonths[i].Items.Add("November");
       SubMonths[i].Items.Add("December");
       SubMonths[i].SelectedItem = DateTime.Today.ToString("MMMM");
       plSubscription.Controls.Add(SubMonths[i]);

    }
}


private void createComboYears()
{
   int width = 79;
   int height = 24;
   int spacing = 28;
   ComboBox[] SubYears = new ComboBox[12];
   for (int i = 0; i <= 11; ++i)
   {
       SubYears[i] = new ComboBox();
       SubYears[i].Name = "SubYears";
       SubYears[i].DropDownStyle = ComboBoxStyle.DropDownList;
       SubYears[i].Size = new Size(width, height);
       SubYears[i].Location = new Point(145, (i * height) + spacing);
       plSubscription.Controls.Add(SubYears[i]);
       fillComboData(SubYears[i]); // Function to fill the last 5 years
    }
}



private void createTextBoxes()
{
   int width = 79;
   int height = 24;
   int spacing = 28;
   TextBox[] subAmt = new TextBox[12];
   for (int i = 0; i <= 11; ++i)
   {
      subAmt[i] = new TextBox();
      subAmt[i].Name = "SubAmt" + i;
      subAmt[i].Border.Class = "TextBoxBorder";
      subAmt[i].Size = new Size(width, height);
      subAmt[i].Margin = new Padding(10, 10, 10, 10);
      subAmt[i].Location = new Point(279, (i * height) + spacing);
      subAmt[i].KeyPress += new KeyPressEventHandler(txtJanAmt_KeyPress);
      plSubscription.Controls.Add(subAmt[i]);

    }
 }


private void btnSave_Click(object sender, EventArgs e)
{
    DataTable dtSubs = new DataTable();
    dtSubs.Columns.Add("SubscriberID", typeof(string));
    dtSubs.Columns.Add("Month", typeof(string));
    dtSubs.Columns.Add("Year", typeof(string));
    dtSubs.Columns.Add("SubAmt", typeof(string));
    DataRow row = dtSubs.NewRow(); 
    foreach (Control c in plSubscription.Controls)
    {
        //<- Not sure how do I get the selected row as in the screenshot
    }
}

EDIT 1

I used the below code that gets the data from the datatable but not able to do the following

  1. How do I set the value of the comboboxes as selected when I get it from the datatable
  2. For the textbox how do I get the value from the datatable
  3. Whenever there is data available I want that row to be readonly.

Thanks in advance

Changed code

for (int i = 0; i < dt.Rows.Count; i++)
{
   #region Grid Column Names
   DataGridViewComboBoxColumn mntCmb = new DataGridViewComboBoxColumn();
   mntCmb.HeaderText = "Month";
   mntCmb.Name = "Month";
   mntCmb.DataSource = dt;
   mntCmb.DisplayMember = "paidformonth";
   mntCmb.ValueMember = "paidformonth";
   // <-How do I set the column as selected.

   DataGridViewComboBoxColumn yearCmb = new DataGridViewComboBoxColumn();
   yearCmb.HeaderText = "Year";
   yearCmb.Name = "Year";
   yearCmb.DisplayMember = "paidforyear";
   yearCmb.ValueMember = "paidforyear";
   // <-How do I set the column as selected.

   DataGridViewTextBoxColumn amount = new DataGridViewTextBoxColumn();
   amount.HeaderText = "Subscription Amount";
   amount.Name = "Subscription Amount";
   amount.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
   // <-How do I set this column with the value from the datatable

   #endregion

   dgvSubscriptions.Columns.AddRange(mntCmb, yearCmb, amount);
}

Edit 2

I seem to be confused as to why I am getting 3 rows with 6 columns. The datatable only has 2 rows and 3 columns. I am using the above code. I added the ID column just to see what happens. See the screenshot below. I have

dgvSubscriptions.AllowUserToAddRows = true

as I want the user to add rows and enter more data to save. What I am trying to do here is to get a subscription list of months/years that were paid and allow users to add subscription for say May, June assuming Jan to April was paid.

enter image description here

Edit 3

I even set the DataPropertyName instead of the ValueMember still no changes

Edited Code

DataGridViewComboBoxColumn yearCmb = new DataGridViewComboBoxColumn();
yearCmb.HeaderText = "Year";
yearCmb.Name = "Year";
//yearCmb.DataSource = dt;
yearCmb.DisplayMember = "paidforyear";
//yearCmb.ValueMember = "paidforyear";
yearCmb.DataPropertyName= "paidforyear";
yearCmb.DefaultCellStyle.NullValue = dt.Rows[i][2].ToString();
yearCmb.ReadOnly = true;
dgvSubscriptions.Columns.Add(yearCmb);

Edit 4

Below is the actual code that is causing the duplicate columns

dgvSubscriptions.AutoGenerateColumns = false;
dgvSubscriptions.ColumnCount = 1;
dgvSubscriptions.Columns[0].Name = "ID";
dgvSubscriptions.Rows.Clear();
for (int i = 0; i <dt.Rows.Count; i++)
{
     dgvSubscriptions.Rows.Add();
     #region Grid Column Names
     DataGridViewComboBoxColumn mntCmb = new DataGridViewComboBoxColumn();
     mntCmb.HeaderText = "Month";
     mntCmb.Name = "Month";
     //mntCmb.DataSource = dt;
     mntCmb.DisplayMember = "paidformonth";
     mntCmb.DataPropertyName = "paidformonth";
     //mntCmb.ValueMember = "paidformonth";
     mntCmb.DefaultCellStyle.NullValue = dt.Rows[i][1].ToString();
     mntCmb.ReadOnly = true;
     dgvSubscriptions.Columns.Add(mntCmb);


     DataGridViewComboBoxColumn yearCmb = new DataGridViewComboBoxColumn();
     yearCmb.HeaderText = "Year";
     yearCmb.Name = "Year";
     //yearCmb.DataSource = dt;
     yearCmb.DisplayMember = "paidforyear";
     //yearCmb.ValueMember = "paidforyear";
     yearCmb.DataPropertyName= "paidforyear";
     yearCmb.DefaultCellStyle.NullValue = dt.Rows[i][2].ToString();
     yearCmb.ReadOnly = true;
     dgvSubscriptions.Columns.Add(yearCmb);

     DataGridViewTextBoxColumn amount = new DataGridViewTextBoxColumn();
     amount.HeaderText = "Subscription Amount";
     amount.Name = "Subscription Amount";
     amount.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
     //amount.DataPropertyName = dt.Rows[i][2].ToString();
     amount.DataPropertyName="subamount";
     amount.DefaultCellStyle.NullValue = dt.Rows[i][0].ToString();
     amount.ReadOnly = true;
     dgvSubscriptions.Columns.Add(amount);

     #endregion

}

Edit 5

I used IRSOG code with some modification and below is the full working code.

Working Code

public struct Data
{
    public List<string> Mon { get; set; }
    public List<string> Year { get; set; }
}

private void fillGridData(DataTable dt)
{
  List<string> Mon = new List<string>() { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };

  List<string> Year = new List<string>();
  int CurrentYear = DateTime.UtcNow.Year;
  int NextYear = CurrentYear + 1;
  int LastFiveYears = CurrentYear - 5;
  for (int i = LastFiveYears; i <= NextYear; i++)
  {
     Year.Add(i.ToString());
  }
  List<Data> _Data = new List<Data>();
  for (int i = 1; i <= 12; i++)
  {
     _Data.Add(new Data() { Mon = Mon, Year = Year });
  }

   dgvSubscriptions.Rows.Clear();
   dgvSubscriptions.Refresh();
   dgvSubscriptions.Visible = true;
   dgvSubscriptions.ColumnHeadersDefaultCellStyle.Font = new Font("Trebuchet MS", 8F, FontStyle.Regular);
   dgvSubscriptions.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
   dgvSubscriptions.AutoResizeColumns();
   dgvSubscriptions.AllowUserToResizeColumns = true;
   dgvSubscriptions.AllowUserToOrderColumns = true;
   dgvSubscriptions.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
   dgvSubscriptions.Dock = DockStyle.None;
   dgvSubscriptions.BackgroundColor = this.BackColor;
   dgvSubscriptions.BorderStyle = BorderStyle.None;
   dgvSubscriptions.AllowUserToAddRows = true;


  // If dt.Rows.Count > 0 then show the data - do not allow to change existing data
  if (dt.Rows.Count > 0)
  {

    dgvSubscriptions.Rows.Clear();
    dgvSubscriptions.Refresh();

    #region Grid Column Names
    dgvSubscriptions.AutoGenerateColumns = false;
    dgvSubscriptions.Rows.Clear();
    DataGridViewComboBoxColumn mntCmb = new DataGridViewComboBoxColumn();
    mntCmb.HeaderText = "Month";
    mntCmb.Name = "Month";
    mntCmb.DataSource = Mon;
    mntCmb.DefaultCellStyle.NullValue = "";
    dgvSubscriptions.Columns.Add(mntCmb);

    DataGridViewComboBoxColumn yearCmb = new DataGridViewComboBoxColumn();
    yearCmb.HeaderText = "Year";
    yearCmb.Name = "Year";
    yearCmb.DataSource = Year;
    yearCmb.DefaultCellStyle.NullValue = "";
    dgvSubscriptions.Columns.Add(yearCmb);

    DataGridViewTextBoxColumn amount = new DataGridViewTextBoxColumn();
    amount.HeaderText = "Subscription Amount";
    amount.Name = "Subscription Amount";
    amount.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
    amount.DefaultCellStyle.NullValue = "";
    dgvSubscriptions.Columns.Add(amount);
    #endregion

    #region Populate Grid
    for (int i = 0; i <dt.Rows.Count; i++)
    {
       dgvSubscriptions.Rows.Add();

       dgvSubscriptions.Rows[i].Cells[0].Value = dt.Rows[i][1].ToString();  // Month
       dgvSubscriptions.Rows[i].Cells[0].ReadOnly = true; // do not allow the user to make changes
       dgvSubscriptions.Rows[i].Cells[1].Value = dt.Rows[i][2].ToString(); // Year
       dgvSubscriptions.Rows[i].Cells[1].ReadOnly = true; // do not allow the user to make changes
       dgvSubscriptions.Rows[i].Cells[2].Value = dt.Rows[i][0].ToString();  // Subscription amount
       dgvSubscriptions.Rows[i].Cells[2].ReadOnly = true; // do not allow the user to make changes

    }
    #endregion

  }
  else // We come here if dt.Rows.Count is 0 we allow the user to select and save 
  {

    #region Grid Column Names
    DataGridViewComboBoxColumn mntCmb = new DataGridViewComboBoxColumn();
    mntCmb.HeaderText = "Month";
    mntCmb.Name = "Month";
    mntCmb.DataSource = Mon;

    DataGridViewComboBoxColumn yearCmb = new DataGridViewComboBoxColumn();
    yearCmb.HeaderText = "Year";
    yearCmb.Name = "Year";
    yearCmb.DataSource = Year;

    DataGridViewTextBoxColumn amount = new DataGridViewTextBoxColumn();
    amount.HeaderText = "Subscription Amount";
    amount.Name = "Subscription Amount";
    amount.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
    #endregion
    dgvSubscriptions.Columns.AddRange(mntCmb, yearCmb, amount);

    dgvSubscriptions.DataSource = _Data;
  }
  dgvSubscriptions.RowsDefaultCellStyle.Font = new Font("Trebuchet MS", 8F, FontStyle.Regular);

}
Adrian
  • 333
  • 6
  • 20
  • You can simply cast the control c as TextBox, and check if it is not null, then get the text from textbox – Rajeev Kumar Apr 15 '13 at 06:20
  • 1
    Have you considered using a [DataGridView](http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.aspx) Instead? – Jens Kloster Apr 15 '13 at 06:21
  • @RajeevKumar I was able to do that but again if you see my screenshot I want the value comboboxes for the value entered in the textboxes – Adrian Apr 15 '13 at 06:22
  • @JensKloster I think that would be the only way for my requirement. Was hoping to see if there is some solution other that using a datagridview :) – Adrian Apr 15 '13 at 06:24
  • Instead of using this,use DataGridView and set unic id for each row,then you can easlly get each row data. – KF2 Apr 15 '13 at 06:29
  • @IRSOG not sure what you mean can you show me some sample code of what you mean – Adrian Apr 15 '13 at 06:37
  • @Adrian: i don't have vs.net now,but you can create this structure in a datagridview,and get each selected row data. – KF2 Apr 15 '13 at 06:42

3 Answers3

2

Instead of using this,use DataGridView Try this:

With Calling GetCurrentRowValues method you can get selected row information's.

Complete Code

public Form1()
        {
            InitializeComponent();
            dataGridView1.MultiSelect = false;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            List<string> Mon = new List<string>() { "January", "February", "March", "April", "May", " June", "July", "August", "September", "October", "November", "December" };
            List<string> Year = new List<string>() { "2001", "2002", "2003", "2004", "2005", "2006" };
            List<Data> _Data = new List<Data>();
            for (int i = 1; i <= 12; i++)
            {
                _Data.Add(new Data() { Mon = Mon, Year = Year });
            }
            DataGridViewComboBoxColumn moonCmb = new DataGridViewComboBoxColumn();
            moonCmb.HeaderText = "Month";
            moonCmb.Name = "Month";
            moonCmb.DataSource = Mon;

            DataGridViewComboBoxColumn yearCmb = new DataGridViewComboBoxColumn();
            yearCmb.HeaderText = "Year";
            yearCmb.Name = "Year";
            yearCmb.DataSource = Year;
            DataGridViewTextBoxColumn amount = new DataGridViewTextBoxColumn();
            amount.HeaderText = "Amount";
            amount.Name = "Amount";
            dataGridView1.Columns.AddRange(moonCmb, yearCmb, amount);

            dataGridView1.DataSource = _Data;

        }

        private void GetCurrentRowValues()
        {
            var mon = dataGridView1.CurrentRow.Cells["Month"].Value;
            var year = dataGridView1.CurrentRow.Cells["Year"].Value;
            var amont = dataGridView1.CurrentRow.Cells["Amount"].Value;
        }

    }
    public struct Data
    {
        public List<string> Mon { get; set; }
        public List<string> Year { get; set; }
    }

Result

enter image description here

New edit-From data table

        #region Grid Column Names
        dgvSubscriptions.AutoGenerateColumns = false;
        dgvSubscriptions.ColumnCount = 1;
        dgvSubscriptions.Columns[0].Name = "ID";
        dgvSubscriptions.Rows.Clear();

        DataGridViewComboBoxColumn mntCmb = new DataGridViewComboBoxColumn();
        mntCmb.HeaderText = "Month";
        mntCmb.Name = "Month";
        //mntCmb.DataSource = dt;
        mntCmb.DisplayMember = "paidformonth";
        mntCmb.DataPropertyName = "paidformonth";
        //mntCmb.ValueMember = "paidformonth";
        mntCmb.DefaultCellStyle.NullValue = "";
        mntCmb.ReadOnly = true;
        mntCmb.Items.Add("april");
        mntCmb.Items.Add("jun");
        mntCmb.Items.Add("jull");
        dgvSubscriptions.Columns.Add(mntCmb);

        DataGridViewComboBoxColumn yearCmb = new DataGridViewComboBoxColumn();
        yearCmb.HeaderText = "Year";
        yearCmb.Name = "Year";
        //yearCmb.DataSource = dt;
        yearCmb.DisplayMember = "paidforyear";
        //yearCmb.ValueMember = "paidforyear";
        yearCmb.DataPropertyName = "paidforyear";
        yearCmb.DefaultCellStyle.NullValue = "";
        yearCmb.Items.Add("2001");
        yearCmb.Items.Add("2002");
        yearCmb.Items.Add("2003");
        yearCmb.ReadOnly = true;
        dgvSubscriptions.Columns.Add(yearCmb);
        DataGridViewTextBoxColumn amount = new DataGridViewTextBoxColumn();
        amount.HeaderText = "Subscription Amount";
        amount.Name = "Subscription Amount";
        amount.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
        //amount.DataPropertyName = dt.Rows[i][2].ToString();
        amount.DataPropertyName = "subamount";
        amount.DefaultCellStyle.NullValue = "";
        amount.ReadOnly = true;
        dgvSubscriptions.Columns.Add(amount);
        #endregion

Fill it

       for (int i = 0; i < dt.Rows.Count; i++)
        {
            dgvSubscriptions.Rows.Add();
            dgvSubscriptions.Rows[i].Cells[0].Value = dt.Rows[i][0].ToString();
            dgvSubscriptions.Rows[i].Cells[1].Value = dt.Rows[i][1].ToString();
            dgvSubscriptions.Rows[i].Cells[2].Value = dt.Rows[i][2].ToString();
            dgvSubscriptions.Rows[i].Cells[3].Value = dt.Rows[i][0].ToString();
        }
KF2
  • 9,887
  • 8
  • 44
  • 77
  • Hmm... I started like this way and got lost halfway:). Let me try with your code and see. Just one question - The list string Year can I want to have the last 5 years plus the current and next year – Adrian Apr 15 '13 at 12:14
  • Your codes works just need to find out how to restrict the user from entering numbers and decimal points in the Amount column – Adrian Apr 15 '13 at 12:38
  • @Adrian:first question is not hard to implement,and sec question:You must create custom dataGridView column where place any control you want:http://msdn.microsoft.com/en-us/library/7tas5c80.aspx – KF2 Apr 16 '13 at 02:38
  • I used your code and it works, this is ok if there is no data in the datatable. But when there is data in the datatable I assign the datasource as the data table. I edited my question with some new questions. Hope you can guide me some more :) – Adrian Apr 16 '13 at 07:16
  • @Adrian:insert struct of your datatable – KF2 Apr 16 '13 at 08:30
  • can you check my edit 2 section not sure what I am doing wrong there – Adrian Apr 16 '13 at 12:23
  • I have updated the actual code check Edit 4. Thanks in advance – Adrian Apr 17 '13 at 07:58
  • @Adrian:i need your data table,i what your project,if you can upload and send me link of your project. – KF2 Apr 17 '13 at 08:25
  • The datatable consists of 3 columns that I get from a database. paidformonth - varchar(20), paidforyear int, subamt decimal(8,2). This is the structure of the database table. – Adrian Apr 17 '13 at 08:53
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/28362/discussion-between-adrian-and-irsog) – Adrian Apr 17 '13 at 09:12
  • @Adrian:i write a complete example see it – KF2 Apr 17 '13 at 09:34
  • seems to show an error while loading the grid System.Argument.Exception: DataGridComboBoxcell value not valid. To replace the default dialog please handle the DataError Event. Thanks once again – Adrian Apr 17 '13 at 09:49
  • @Adrian:this is because you donot init your comboboxes with years and month,come to chat to explain – KF2 Apr 17 '13 at 09:51
1

The forms ActiveControl property can help you.

var focusedCtrl = this.ActiveControl;
var siblings = Controls.Where(c => c.Location.Y == focusedCtrl.Location.Y).ToList();
foreach (Control c in siblings)
{
   // all thise controls are on the same row, providede that the allign
}

Still I would sugget the DataGridView. It seems to be a better choise for a structure that looks like yours.

Jens Kloster
  • 11,099
  • 5
  • 40
  • 54
  • Thanks let me see if I can get it work with the datagridview. My issue that I had when I tried with the DataGridView was how to populate the combobox column dynamically. I will try and see – Adrian Apr 15 '13 at 06:39
  • @Adrian I [see](http://stackoverflow.com/questions/7648648/dynamic-combobox-in-a-datagridview) your problem. Perhaps you need a better GridView. I Always use [XtraGrid](http://www.devexpress.com/Products/NET/Controls/WinForms/Grid/) but it is not free. – Jens Kloster Apr 15 '13 at 06:45
1

I’d go with the DataGridView but if you still want to keep this structure you might want to update KeyPressed event handler to look something like this

private string lastUsedTextBox = string.Empty;
private string lastEnteredValue = string.Empty;

private void txtJanAmt_KeyPress(object sender, KeyPressEventArgs e)
{
lastUsedTextBox = (sender as TextBox).Name;                      
lastEnteredValue = (sender as TextBox).Text;                       
}

And then when you press the save button you have everything stored here.

If you need to save all the data you can use dictionary or something like that.

Igor Voplov
  • 973
  • 9
  • 7
  • Thanks I did go with the datagridview but still some issue check my edited question. Not sure how to set the values from the DataTable of the comboboxes – Adrian Apr 16 '13 at 08:42