Datarepeater with embedded datagridview's changing tables

1.3k views Asked by At

I have a datarepeater, with a datagridview in each row of the datarepeater.

When I add data to the first datagridview and the second, I get this -

Before scrolling

Then, if I was to scroll down to a datarepeater row further down the page, then scroll to the top, we get this -

After scrolling

Note the items have changed position. If I was to add rows to many of the datagridviews, it gets very messy when I scroll.

Any ideas why this might be happening?

1

There are 1 answers

0
Joel On BEST ANSWER

I have come across a similar problem with following setup:

  • Checkbox per row
  • Search textbox (before repeater control, for filtering items)
  • Many items in repeater (at least a number big enough to be scrollable)

I would filter (or not) my list and select items from repeater. But when I scrolled, my selected rows would not be tracked. Random rows were selected when I scrolled instead.

I think you may be experiencing the same issue. I have no proof about what I'm saying and I could not in that time find the answer on SO either, but I think it's related to how the repeater works.

It specifies your item(row) index based on displayed items (items which are visible at a certain time). This way it is possible for a fast scrollable component, while repeating controls.

Imagine how it would be if you had 10 controls per row and had 30.000 rows to display. What do you think that would happen if the repeater had to create 300.000 controls?

For this reason (and once more I am guessing, these are my assumptions as the documentations is very scarce - as you may know by now) the repeater creates only controls for items that fit your repeater area and recycles them.

This means that if you do some sort of operation on item on index XX and scroll, since the items are not in same index the repeater will flip out, since indexes are recalculated when you scroll.

Ok, now that I answered your question about what's going on, let's see how to fix it:

First, add a Label to the ItemTemplate of your repeater. This label will be used to bind your item Id property (or something of the sort). Also, set your label Visible property to false to keep this hidden.

On my form I add these fields:

// I use an observable collection to be notified when it changes
private ObservableCollection<YourItem> _allItems = 
    new ObservableCollection<YourItem>(); 
private BindingSource _bindingSource;

Then bind your collection to the repeated (I do this in OnLoad)

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    ...

    _bindingSource = new BindingSource();
    _bindingSource.DataSource = _allItems;

    // my hidden label       // All my items have an Id property
    _labelHiddenId.DataBindings.Add("Text", BindingSource, "Id");
    _dataRepeaterList.DataSource = _bindingSource;

    _allItems.CollectionChanged += AllItems_CollectionChanged;
}

my listener:

protected override void AllItems_CollectionChanged(object sender, 
    NotifyCollectionChangedEventArgs e)
{
    RefreshRepeater();
}

my refresh method:

protected void RefreshRepeater(bool search = false)
{
    if (_dataRepeaterList.InvokeRequired)
    {
        _dataRepeaterList.Invoke((Action)(() => { RefreshRepeater(search); }));
        return;
    }

    _bindingSource.DataSource = null; // Clear binding source first
    _bindingSource.DataSource = _allItems.ToList();
    _dataRepeaterList.DataSource = null; // Clear datasource first
    _dataRepeaterList.DataSource = _bindingSource;
    _dataRepeaterList.Refresh();
 }

my draw item method, which is where I fill most of my row info:

protected override void DataRepeater_DrawItem(object sender, 
    DataRepeaterItemEventArgs e)
{
    var dataSourceEntity = GetObjectFromDataSource(e.DataRepeaterItem);
    var checkedComponent = _checkedItems.SingleOrDefault(
         x => x.Equals(dataSourceEntity));

    // Get current item control to fill. Something like
    var grid = e.DataRepeaterItem.Controls["yourgridiew"] as DataGridView;

    // Do stuff, you are messing with the right object :)
}

And my last piece:

protected override T GetObjectFromDataSource(DataRepeaterItem dataRepeaterItem)
{
    if (dataRepeaterItem == null)
        return null;

    var hiddenIdLabel = (Label)dataRepeaterItem.Controls[_labelHiddenId.Name];
    return _allItems.FirstOrDefault((entity) => entity.Id.ToString().Equals(hiddenIdLabel.Text));
}

This code has not seen a compiler, but it should set you on the right track.

To sum it up:

  • Create a hidden label on your item template
  • Create an observable collection and on modified event refresh the repeater
  • Create a binding source, set its datasource to your collection.
  • Have your items share a common identifier and bind that identifier to the repeater.
  • When refreshing your repeater with new data, reset datasource (just in case)
  • Create a method for find you original object (datasource object) based on current repeater row.
  • Get the control for current object (datasource object) and apply operations on it.

This took a long time to find and implement, but hopefully it will be easier for you :)