FlexGrid -- column with button and RowDetails problem

Posted by: a.sharov on 24 May 2022, 11:21 am EST

    • Post Options:
    • Link

    Posted 24 May 2022, 11:21 am EST - Updated 3 October 2022, 10:27 pm EST

    Hi.

    I more or less figured out how to add columns with buttons ot grid, but my approach only works when there is no rows with details, namely RowDetailProvider = null. Once I add child grid everything got broken, see img. below (don’t bother with button size). So as you can see, my code adds button to child’s grid rows also, hiding child’s it’s column. So, I need add button to column and preserve child grid functionality. How to do that?

    Here is my code, compilation from chunks I found on this fourm,

    see C1FlexGrid1_AfterDataRefresh handler:

    
     private void LogUserControlLoad(object sender, EventArgs e)
            {
                c1FlexGrid1.Paint += C1FlexGrid1OnPaint;
                c1FlexGrid1.AfterDataRefresh += C1FlexGrid1_AfterDataRefresh;
            }
    
            private void C1FlexGrid1_AfterDataRefresh(object sender, System.ComponentModel.ListChangedEventArgs e)
            {
                
                var colIndex = c1FlexGrid1.Cols[@"enginelog"].Index;
                var i = 0;
                while(i < c1FlexGrid1.Rows.Count)
                {
                    if (i < c1FlexGrid1.Rows.Fixed)
                    {
                        i++;
                        continue;
                    }
    
                    var row = c1FlexGrid1.Rows[i];
                    if (row.DataSource is MyData)
                    {
                        SetupButtons(c1FlexGrid1, colIndex, c1FlexGrid1.Rows[i].Index);
                    }
                    i++;
                }
            }
    
            private void C1FlexGrid1OnPaint(object sender, PaintEventArgs e)
            {
                foreach (ColumnButton bt in _columnBtnHs)
                {
                    bt.UpdateButtonBounds();
                }
    
                
            }
         
            private void SetupButtons(C1FlexGrid sender, int col, int row)
            {
                var bt = new ColumnButton(sender, row, col);
                var isAdded =  _columnBtnHs.Add(bt);
                if(!isAdded) return;
                bt.Click += EngineLogBtnClick;
                bt.Text = @"View log";
            }
    
            private void EngineLogBtnClick(object sender, EventArgs e)
            {
                //throw new NotImplementedException();
                var btn = sender as ColumnButton;
                MessageBox.Show(String.Format("{0}-{1}",btn.Row,btn.Col));
            }
    
    
            public class ColumnButton : Button, IEqualityComparer<ColumnButton>
            {
                #region Constructor
    
                public ColumnButton(C1FlexGrid flex, int row, int col)
                {
                    Grid = flex;
                    Row = Grid.Rows[row];
                    Col = Grid.Cols[col];
                    Grid.Controls.Add(this);
                    Tag = new Point(row, col);
                    Paint += ColumnButton_Paint;
                    BackColor = Color.Aquamarine;
                    
                }
    
                #endregion
    
                #region Properties
    
                internal C1FlexGrid Grid { get; }
                internal Row Row { get; }
                internal Column Col { get; }
    
                #endregion
    
                #region Methods
    
                //paints the border of the button
                private void ColumnButton_Paint(object sender, PaintEventArgs e)
                {
                
               
                    ControlPaint.DrawBorder(e.Graphics, ((Button)sender).ClientRectangle,
                            Color.Green, 1, ButtonBorderStyle.Solid,
                            Color.Green, 1, ButtonBorderStyle.Solid,
                            Color.Green, 1, ButtonBorderStyle.Solid,
                            Color.Green, 1, ButtonBorderStyle.Solid
                    );
                }
    
    
                /// <summary>
                ///     places the button into the position of the cell
                /// </summary>
                internal void UpdateButtonBounds()
                {
                    var r = Row.Index;
                    var c = Col.Index;
                    if (r < 0 || c < 0)
                    {
                        return;
                    }
    
                    var rc = Grid.GetCellRect(r, c, false);
                    Bounds = rc;
                }
    
                #endregion
    		
    	    //NOTE, needed for hash set	
                public bool Equals(ColumnButton x, ColumnButton y)
                {
                    if (ReferenceEquals(x, y))
                    {
                        return true;
                    }
    
                    if (ReferenceEquals(x, null))
                    {
                        return false;
                    }
    
                    if (ReferenceEquals(y, null))
                    {
                        return false;
                    }
    
                    if (x.GetType() != y.GetType())
                    {
                        return false;
                    }
    
                    return Equals(x.Grid, y.Grid) && Equals(x.Row, y.Row) && Equals(x.Col, y.Col);
                }
    
                public int GetHashCode(ColumnButton obj)
                {
                    unchecked
                    {
                        var hashCode = (obj.Grid != null ? obj.Grid.GetHashCode() : 0);
                        hashCode = (hashCode * 397)^(obj.Row != null ? obj.Row.GetHashCode() : 0);
                        hashCode = (hashCode * 397)^(obj.Col != null ? obj.Col.GetHashCode() : 0);
                        return hashCode;
                    }
                }
    
    

    Thanks in advance.

  • Posted 25 May 2022, 12:52 am EST

    Hi,

    You can Host your custom controls in the C1FlexGrid cells by adding them to the Grid’s Controls and updating their size and position according to that of the cells. Kindly refer to the attached sample in which we have implemented the same.

    RowDetailDemo_FG_HostedButtons.zip

    Best Regards,

    Kartik

  • Posted 25 May 2022, 12:08 pm EST

    Thank you for fast reply.

    But still I have issue with my case. Issue with buttons on child grid are gone, but button is shown only on few parent rows.

    I have following code for databinding:

    
      internal SomeType SomeField
            {
               set
                {
                                   
               bindingSource.DataSource = GetData().ToList();
               c1FlexGrid1.RowDetailProvider = (g, r) => new DetailRowContainer();
                 
                   
                }
            }
    
    
    

    and the main part:

    
           private void MonitorProjectLogUserControlLoad(object sender, EventArgs e)
            {
                c1FlexGrid1.Rows.DefaultSize = 36;
                c1FlexGrid1.AreRowDetailsFrozen = false;
                c1FlexGrid1.Paint += C1FlexGrid1OnPaint;
                c1FlexGrid1.AfterDataRefresh += C1FlexGrid1_AfterDataRefresh;
            }
    
            private void C1FlexGrid1_AfterDataRefresh(object sender, ListChangedEventArgs e)
            {
                var colIndex = c1FlexGrid1.Cols[@"enginelog"].Index;
                c1FlexGrid1.BeginUpdate();
                foreach (var hostedControl in _columnBtnHs)
                {
                    hostedControl.ClearGrid();
                }
                _columnBtnHs.Clear();
                for (int i = c1FlexGrid1.Rows.Fixed; i < c1FlexGrid1.Rows.Count; i += 1)
                {
                    Button hostedButton = new Button() { Text = @"View Log", FlatStyle = FlatStyle.Flat, BackColor = Color.CornflowerBlue };
                    hostedButton.Click += EngineLogBtnClick;
    
                    //add hostedbutton in required cells
                    _columnBtnHs.Add(new HostedControl(c1FlexGrid1, hostedButton, i, colIndex));
                }
                c1FlexGrid1.EndUpdate();
            }
    
            private void C1FlexGrid1OnPaint(object sender, PaintEventArgs e)
            {
               if(_columnBtnHs == null || _columnBtnHs.Count == 0 ) return;
                foreach (var bt in _columnBtnHs)
                {
                    bt.UpdatePosition();
                }
            }
    
    

    I change some external entity based on which I fill grid.

    1)I use this grid on UC, so loading(load event) works only once, so in my case Load event is useless in terms of databinding. I trigger data binding via setting property(see code above). So, for test I have 4 rows (each with details). The first time I see control with grid, only first two rows have button, as on my image above but with correct child rows (no button on them). so last two rows for some reason ignored (don’t show button in columns). If I switch to other control and then back (which triggers very same sequance of events) I now see 4 buttons , one for each row, so everything looks fine. The problem is with first appearance of my UC with this grid.

    1. Given my code above, why C1FlexGrid1_AfterDataRefresh raised few times instead of one?

    2. Why we iterate over rows via i+=2 and not i+=1 ?

    4)Maybe I use wrong events give Load event is useless for me?

    PS: I use ORM and IEnumerable types, not DataSet and DataTable.

  • Posted 26 May 2022, 2:59 am EST

    Hi,

    1. When the RowDetails are enabled in the C1FlexGrid, the grid generates a child row for each Data Row in the Grid. This child Row hosts the child C1FlexGrid for each Data Row and is shown when the expand icon is clicked, otherwise, this row stays hidden.

    Therefore, in your code, when you set the DataSource of the Grid, the AfterDataRefresh event is fired and the Hosted buttons are generated for only half rows, because the RowDetails are not generated yet due to the fact that you are setting the RowDetailProvider after setting the DataSource.

    
    internal SomeType SomeField
    {
          set
           {
                  //updated code
                  c1FlexGrid1.RowDetailProvider = (g, r) => new DetailRowContainer();
                  bindingSource.DataSource = GetData().ToList();
           }
    }
    
    
    1. The AfterDataRefresh event is fired for every change in the DataSource. Therefore it can be observed firing multiple times for each change/reset of the DataSource.

    2. Due to the working of the RowDetails in the Grid (explained in point (1) ), we increment the loop by 2 instead of 1, to skip the child detail row of each Data Row in the Grid.

    3. You can also use the DataSourceChanged event of the C1FlexGrid to generate your hosted controls whenever a new DataSource is assigned.

    Please refer to the updated sample showing the same. RowDetailDemo_FG_HostedButton_Updated.zip

    Kind Regards,

    Kartik

  • Posted 26 May 2022, 4:29 pm EST - Updated 3 October 2022, 10:27 pm EST

    Hi.

    The problem is if I excange lines:

    
      //updated code
                  c1FlexGrid1.RowDetailProvider = (g, r) => new DetailRowContainer();
                  bindingSource.DataSource = GetData().ToList();
    
    

    I got following error (ArgumentOutOfRangeEx):

    What can be the cause?

  • Posted 26 May 2022, 11:17 pm EST

    Hi,

    We could not see the issue at our end. Please provide a stripped-down sample showing your implementation and the issue you are facing, so we can investigate accordingly and assist you in the best way possible.

    Best Regards,

    Kartik

  • Posted 27 May 2022, 12:12 pm EST

    Hi. It seems it was local glitch maybe because of resources or smth else, but after I’ve recreated grid from scratch, everything works fine. Thank you once again.

    But still few more questions (main issue is solved, btw):

    Therefore, in your code, when you set the DataSource of the Grid, the AfterDataRefresh event is fired and the Hosted buttons are generated for only half rows, because the RowDetails are not generated yet due to the fact that you are setting the RowDetailProvider after setting the DataSource.

    Then why anything generated et all, for first half of rows? I suppose what you’ve described implies that either everything generated for all rows or for none. Why half?

    1. For now I use DataSourceChanged event of bs. But why if bind this bs to grid, and update it’s ds property,namely:
    
     this.c1FlexGrid1.DataSource = this.BindingSource;
    ...
    //somewhre else in code
    bs.ds = somedata;
    bs.ResetBindings(false)
    
    

    it doesn’t raise grid DataSourceChanged event? I’m in bound mode, change bs, make bs to anounce it’s changes for binded controls like grid, and yet no reaction from grid. Why it is so?

    What is proper choise – use ds prop. of grid directly of work via bs?

    I just can’t understand purpose of bs usage for grid, it seems it only generate columns and nothin more. In case of grid it can be avoided, right?

    PS: As issue is fixed I will mark this thread as answered, but still hope for your reply.

  • Posted 31 May 2022, 4:54 am EST

    Hi,

    • In the Sample, when the DataSource of the C1FlexGrid is set before setting the RowDetailProvider, the Grid generates the Data Rows according to the Count of the DataSource and then calls the SetupHostedButtons method. This Method then generates and sets the Hosted buttons for Rows according to their indices. Currently, the Grid only contains the Data Rows present in the DataSource, therefore the buttons are generated for these Rows.

    After this, the RowDetailProvider is set, which generates a child row for every Data Row present in the Grid, which doubles the Count of the Grid Data Rows. Since the Hosted Buttons were generated for only the first half of the Row indices, the Grid shows buttons for only the first half of the rows.

    • Otherwise, if we set the RowDetailProvider before setting the DataSource, and the DataSource is set afterward, the Grid generates all the Data Rows and the Child Rows before the SetupHostedButtons method is called. Therefore the hosted buttons for all the Rows are generated in this case.

    Please refer to the attached video showing the behavior in both the above cases (halfRows.zip).

    1. If you refer to the description of the DataSourceChanged event of the C1FlexGrid, it states

    “Fires when the value of the C1.Win.C1FlexGrid.C1FlexGridBase.DataSource property changes”.

    Since you have set the DataSource property of the C1FlexGrid to the BindingSource and then only changed the DataSource property of the BindingSource in your code, the Grid will not fire the event because the DataSource property of the Grid is not changed.

    Yes, you can also directly use the DataSource property of the C1FlexGrid if you do not use any specific features of the BindingSource and only use it to Bind the Data.

    halfRows.zip

    Best Regards,

    Kartik

Need extra support?

Upgrade your support plan and get personal unlimited phone support with our customer engagement team

Learn More

Forum Channels