Cookies op Tweakers

Tweakers is onderdeel van DPG Media en maakt gebruik van cookies, JavaScript en vergelijkbare technologie om je onder andere een optimale gebruikerservaring te bieden. Ook kan Tweakers hierdoor het gedrag van bezoekers vastleggen en analyseren. Door gebruik te maken van deze website, of door op 'Cookies accepteren' te klikken, geef je toestemming voor het gebruik van cookies. Wil je meer informatie over cookies en hoe ze worden gebruikt? Bekijk dan ons cookiebeleid.

Meer informatie
Toon posts:

updaten listview houd background worker tegen c#

Pagina: 1
Acties:

Onderwerpen

Vraag


  • cowandchicken
  • Registratie: september 2018
  • Laatst online: 17-06 07:45
Ik heb een C# applicatie, die adhv een xml file een list van objecten bouwt en hier diverse checks op doet.
Dit is best wel intensief dus doe dit in een background worker.
Het resultaat van de list stop ik in het complete event van de backgroundworker in een listview control op mijn main form.
Echter als ik aan het einde van mijn doWork routine van de BGW geen Thread.Sleep(10) toevoeg, dan blijft de progressbar op 90% staan ofzo en is het het updaten van de listview dit die blokkeert.
Ik vind dat vreemd omdat ik verwacht dat het complete event pas optreed als de progress 100% is. Pas dan update ik de listview.

Nu heb ik een kleine sleep toegevoegd aan het einde van de DoWork, en dat zorgt voor een representatieve progressbar. Echter duurt het updaten van de listview nog steeds erg lang.
Heeft iemand een idee, welke denkfout ik maak en hoe ik het updaten van de listview wat "smoother" kan maken?

Beste antwoord (via cowandchicken op 21-09-2018 08:57)


  • RobIII
  • Registratie: december 2001
  • Laatst online: 02:44

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

cowandchicken schreef op woensdag 19 september 2018 @ 21:23:
Ik heb geprobeerd eerst losse listview items in een list te plaatsen en deze met addrange toevoegen, maar daarin zat niet het gros van de tijd.
Dan moet je meten waar 't wél zit (en bepalen wanneer / hoe je die tijd wil betalen; stukje-bij-beetje bij elke stukje werk dat je verzet, of in 1 klap nadat alle werk is verzet).

Het is dat ik even niks anders te doen heb, maar allow me to demonstrate. We gaan even uit van een Customer en een CustomerListviewItem:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }

    private static readonly Random _rng = new Random();
    public static Customer CreateRandomCustomer(int id)
    {
        Thread.Sleep(1); // Fake some work
        return new Customer
        {
            Id = id,
            DateOfBirth = new DateTime(1900, 1, 1).AddDays(_rng.Next(365 * 100)),
            FirstName = "Bob" + _rng.Next(99999),
            LastName = "Doe" + _rng.Next(99999)
        };

    }
}

public class CustomerListViewItem : ListViewItem
{
    public Customer Customer { get; set; }
    public CustomerListViewItem(Customer customer)
        : base(new[] { customer?.FirstName, customer?.LastName, customer?.DateOfBirth.ToString("yyyy-MM-dd") })
    {
        Customer = customer ?? throw new ArgumentNullException(nameof(customer));
    }
}


Maak een form en mik daar 2 buttons op: "AddButton" en "AbortButton". Tevens een ListView en een ProgressBar: "MainListView" en "MainProgressBar". Zet de ListView View op Details en geef 'm 3 kolommen.

Vervolgens plakken we in 't form:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class MainForm : Form
    {
        private BackgroundWorker _bgw;

        public MainForm()
        {
            InitializeComponent();
        }

        private void AddButton_Click(object sender, EventArgs e)
        {
            AddCustomers(2500);
        }

        private void AbortButton_Click(object sender, EventArgs e)
        {
            _bgw?.CancelAsync();
        }
    }
}


De _bgw is onze backgroundworker die we straks aan 't werk gaan zetten. Voeg een methode AddCustomers toe; deze doet niets anders dan een nieuwe BGW instantiëren (als er niet al 1 bezig is), 'configureert' deze en trapt 'm vervolgens aan om aan 't werk te gaan.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void AddCustomers(int count)
{
    // If we currently have no backgroundworker
    if (_bgw == null)
    {
        // Create a backgroundworker, set it up and kick it off
        using (_bgw = new BackgroundWorker() { WorkerReportsProgress = true, WorkerSupportsCancellation = true })
        {
            _bgw.DoWork += DoWork;
            _bgw.RunWorkerCompleted += WorkCompleted;
            _bgw.ProgressChanged += (s, pe) => { MainProgressBar.Value = pe.ProgressPercentage; };
            _bgw.RunWorkerAsync(count);
        }
    }
    else
    {
        throw new Exception("Already in progress");
    }
}


Ok; so far so good. Nu de code waar we mee gaan stoeien: DoWork() en WorkCompleted():

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private void DoWork(object sender, DoWorkEventArgs e)
{
    // Get reference to ourself and prepare a list of results
    var self = (BackgroundWorker)sender;
    var custcount = (int)e.Argument;
    var results = new List<Customer>(custcount);
    // Keep track of last reported progress
    var lastreportedprogress = 0;
    // Add "count" customers while not cancellation pending
    for (var i = 0; i < custcount && !self.CancellationPending; i++)
    {
        // Do actual work
        results.Add(Customer.CreateRandomCustomer(i));

        // Keep track of progress
        var progress = (int)((double)i / custcount * 100);
        // If the actual percentage changed, report it
        if (progress > lastreportedprogress)
        {
            self.ReportProgress(progress);
            lastreportedprogress = progress;
        }
    }
    // If not cancelled, report last status as 100
    if (!self.CancellationPending)
        self.ReportProgress(100);

    // Return whether the action was cancelled
    e.Cancel = self.CancellationPending;
    // Return results
    e.Result = results;
}

private void WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Populate our listview with results (if any)
    MainListView.BeginUpdate();
    MainListView.Items.Clear();
    if (!e.Cancelled)
    {
        var results = (IEnumerable<Customer>)e.Result;
        MainListView.Items.AddRange(results.Select(r => new CustomerListViewItem(r)).ToArray());
    }
    MainListView.EndUpdate();
    // Reset progressbar
    MainProgressBar.Value = 0;
    // Clear backgroundworker so we're ready for a new run
    _bgw = null;
}


Zoals je ziet vullen we hier in DoWork() alleen een list met puur de resultaten, niets anders. Dan, in de WorkCompleted() pakken we die resultaten en transformeren die (dat heet 'projecteren' in LINQ termen) naar onze ListViewItems en mikken die in 1 klap in de listview. Nu gebeurt er op 't moment vrij weinig in de WorkCompleted. Als we echter "veel werk" hebben om de listviewitems te maken (omdat er bijv. nog e.e.a. uitgeplozen moet worden) dan heb je dus alsnog dat je UI "hangt". Je kunt dus de "projectie" ook nog naar de DoWork() halen. Daarvoor verander je in bovenstaande code regel 6 naar:
C#:
1
var results = new List<CustomerListViewItem>(custcount);

en regel 13 naar:
C#:
1
results.Add(new CustomerListViewItem(Customer.CreateRandomCustomer(i)));

(waar je dan eventueel de rest van het werk nog verzet om het listviewitem te maken etc). Regels 41 & 42 kun je dan veranderen naar:
C#:
1
MainListView.Items.AddRange(((IEnumerable<CustomerListViewItem>)e.Result).ToArray());

offtopic:
Helaas wil WinForms altijd arrays of andere vage collecties hebben i.p.v. dat het een IEnumerable<T> accepteert :/


Afhankelijk van waar je "werk" zit en hoe je je applicatie wil opzetten kun je er dus voor kiezen pure results te returnen en die te transformeren naar listviewitems naderhand of meteen listviewitems te returnen.

In beide gevallen zie je je listview (wegens de BeginUpdate en EndUpdate) in 1 keer vol "ploepen". Totaal: 99 regels, waarvan 47 daadwerkelijke code.

Met een virtual listview ziet 't er heel anders uit. Althans: je kunt dan nog steeds een backgroundworker gebruiken en de lijst vullen zoals hierboven gedaan wordt maar dan "op de virtual manier". ECHTER; als je maar 25 listviewitems in beeld hebt, waarom dan wel 't werk van alle 2500 items verzetten? Je kunt op dat moment véél beter het werk pas verzetten op 't moment dat je de listviewitems op je scherm moet zetten wanneer men ze in beeld scrolt. Je hebt in dat geval niet eens een progressbar nodig (of het moet écht heel "duur" zijn om die 25 listviewitems op je scherm te krijgen).

De code uit de link die ik je gaf vind ik zelf vrij omslachtig. Ik zou 't zelf eerder op ongeveer deze manier schrijven (heel de code van MainForm kun je trashen):

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Collections.Concurrent;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class MainForm : Form
    {
        // Cache items; key = index in listview, value = item
        private ConcurrentDictionary<int, CustomerListViewItem> _cache;

        public MainForm()
        {
            InitializeComponent();

            // Set up retrieval of listviewitems
            MainListView.RetrieveVirtualItem += (s, e) =>
            {
                // Check cache, if not in cache create item on demand
                e.Item = _cache.GetOrAdd(e.ItemIndex, (i) =>
                {
                    // Do the work for 1 item here...
                    return new CustomerListViewItem(Customer.CreateRandomCustomer(i));
                });
            };
        }

        private void AddButton_Click(object sender, EventArgs e)
        {
            AddCustomers(2500);
        }

        public void AddCustomers(int count)
        {
            _cache = new ConcurrentDictionary<int, CustomerListViewItem>(Environment.ProcessorCount, count);
            MainListView.VirtualListSize = count;
            MainListView.Refresh();
        }
    }
}

Meer heb je niet nodig; 40 regels, waarvan 17 met daadwerkelijke code. Het neerzetten van een enkel item (of een "hele pagina" van 25 items) is dan, wellicht, wat trager maar je betaalt wél alleen maar voor de items die je ziet. Heel veel eleganter dan dat krijg je 't niet. Probeer 't eens met 1.000.000 customers ;) Wil je overigens niet alle items in-memory houden (in het geval van een miljoen items bijvoorbeeld) dan kun je natuurlijk iets anders (bijvoorbeeld een MemoryCache) gebruiken met een limit van, zeg, 100MB of een soortgelijke structuur en daar gewoon een FIFO-achtig iets op los laten of een andere eviction strategie. Maar we gaan te ver offtopic :P

Vervolgens kun je dit overigens weer makkelijk uitbreiden met een backgroundworker die in de achtergrond alvast "andere pagina's" gaat ophalen en "vooruit werken" (maar dat vereist een beetje nadenkwerk... en de kans dat 't de moeite loont is vrij klein).

Code
Zipfile

En dan tot slot, over een compleet andere boeg: op het moment dat je zoveel items in een listview zet moet je je ook even afvragen of er echt wel noodzaak is om de gebruiker te 'overstelpen' met zoveel data. Ik heb daar ooit een stukkie over geschreven :P

[Voor 14% gewijzigd door RobIII op 20-09-2018 02:14]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Roses are red Violets are blue, Unexpected ‘{‘ on line 32.

Over mij

Alle reacties


  • RobIII
  • Registratie: december 2001
  • Laatst online: 02:44

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Voor een listview heb je een BeginUpdate en EndUpdate methode die iig je listview blokkeert zodat 'ie niet gaat liggen updaten elke keer als je een item toevoegt. Wil je je listview nog sneller bijgewerkt hebben dan wil je virtualmode gebruiken.

Wat betreft je progressbar: als 'ie op 100% gezet wordt en je gaat metéén je listview bijwerken dan is de kans inderdaad groot dat je UI nog geen kans heeft gehad alles bij te werken ("tekenen") en je progressbar dus blijft staan op de laatste waarde. Je kunt een Application.DoEvents inlassen (maar is eigenlijk een beetje ranzig, maar nog altijd beter dan een arbitraire Thread.Sleep). Daarom wil je altijd zoveel mogelijk verzetten op een andere thread dan de UI thread. Als het werk niet anders dan op de UI thread kan worden verzet dan zijn de alternatieven dus: minder werk, het werk verdelen of in 'batches' ophakken of bijv. de virtual mode gebruiken.

[Voor 11% gewijzigd door RobIII op 19-09-2018 11:00]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Roses are red Violets are blue, Unexpected ‘{‘ on line 32.

Over mij


  • cowandchicken
  • Registratie: september 2018
  • Laatst online: 17-06 07:45
Ik wil het analyseren en opbouwen van de object list best in een andere thread proberen te doen, maar had juist een bgw gebruikt omdat ik een progressbar wilde laten zien. Zijn er voorbeelden om die op een betere manier te doen? Heb niet zo'n ervaring met threading en parallel programming, maar de parallel foreach zou ook nog kunnen, maar hoe kun je daarvan de progress status weergeven?
Ik ga morgen iig eerst die virtual mode eens proberen.

  • RobIII
  • Registratie: december 2001
  • Laatst online: 02:44

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

cowandchicken schreef op woensdag 19 september 2018 @ 20:14:
Heb niet zo'n ervaring met threading en parallel programming, maar de parallel foreach zou ook nog kunnen, maar hoe kun je daarvan de progress status weergeven?
Woa woa woa...


Er zijn nogal wat verschillen tussen iets op een andere (non-UI) thread uitvoeren en iets parallel doen. Met de termen zoals je ze hier nu gebruikt demonstreer je in ieder geval (en dat geeft niet, ik hou je alleen even een spiegel voor) dat je nog lang niet klaar bent voor dergelijke zaken; je mist echt een behoorlijk stuk basis.

Geeft niet; je kunt prima vooruit met wat ik je heb gegeven. Je kunt je werk (blijven) verzetten in de BGW, maar het toevoegen van de listviewitems aan de listview zul je op de UI thread moeten doen. Je kunt dus prima een List<Foo> gebruiken om je resultaten van je werk in te zetten en, wanneer het 'zware werk' verzet is (en de progressbar dus op 100% staat en de BGW klaar is) de listviewitems op basis van de results in de List<Foo> toevoegen. Als dat ook aanzienlijk lang duurt (en je dus een behoorlijke bult listviewitems toevoegt) dan kun je eens gaan kijken naar de virtual mode.
cowandchicken schreef op woensdag 19 september 2018 @ 20:14:
Ik wil het analyseren en opbouwen van de object list best in een andere thread proberen te doen
Je kunt dus een List<Foo> bouwen en, als je klaar bent, daar overheen itereren en lietviewitems 'newen' en die toevoegen.

Alternatief bouw je een List<ListviewItem> die je daarna (wederom, als je BGW dus klaar is) middels AddRange in 1 klap toevoegt.

In allebei de beschreven scenario's is de BeginUpdate/EndUpdate sowieso aan te raden. En, zoals gezegd, als het écht heel veel listviewitems zijn dan kun je verder gaan kijken naar virtual modes etc.

[Voor 20% gewijzigd door RobIII op 19-09-2018 20:26]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Roses are red Violets are blue, Unexpected ‘{‘ on line 32.

Over mij


  • cowandchicken
  • Registratie: september 2018
  • Laatst online: 17-06 07:45
Het zijn ongeveer 2500 items geloof ik.
Ik heb geprobeerd eerst losse listview items in een list te plaatsen en deze met addrange toevoegen, maar daarin zat niet het gros van de tijd.

Acties:
  • Beste antwoord
  • +4Henk 'm!

  • RobIII
  • Registratie: december 2001
  • Laatst online: 02:44

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

cowandchicken schreef op woensdag 19 september 2018 @ 21:23:
Ik heb geprobeerd eerst losse listview items in een list te plaatsen en deze met addrange toevoegen, maar daarin zat niet het gros van de tijd.
Dan moet je meten waar 't wél zit (en bepalen wanneer / hoe je die tijd wil betalen; stukje-bij-beetje bij elke stukje werk dat je verzet, of in 1 klap nadat alle werk is verzet).

Het is dat ik even niks anders te doen heb, maar allow me to demonstrate. We gaan even uit van een Customer en een CustomerListviewItem:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }

    private static readonly Random _rng = new Random();
    public static Customer CreateRandomCustomer(int id)
    {
        Thread.Sleep(1); // Fake some work
        return new Customer
        {
            Id = id,
            DateOfBirth = new DateTime(1900, 1, 1).AddDays(_rng.Next(365 * 100)),
            FirstName = "Bob" + _rng.Next(99999),
            LastName = "Doe" + _rng.Next(99999)
        };

    }
}

public class CustomerListViewItem : ListViewItem
{
    public Customer Customer { get; set; }
    public CustomerListViewItem(Customer customer)
        : base(new[] { customer?.FirstName, customer?.LastName, customer?.DateOfBirth.ToString("yyyy-MM-dd") })
    {
        Customer = customer ?? throw new ArgumentNullException(nameof(customer));
    }
}


Maak een form en mik daar 2 buttons op: "AddButton" en "AbortButton". Tevens een ListView en een ProgressBar: "MainListView" en "MainProgressBar". Zet de ListView View op Details en geef 'm 3 kolommen.

Vervolgens plakken we in 't form:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class MainForm : Form
    {
        private BackgroundWorker _bgw;

        public MainForm()
        {
            InitializeComponent();
        }

        private void AddButton_Click(object sender, EventArgs e)
        {
            AddCustomers(2500);
        }

        private void AbortButton_Click(object sender, EventArgs e)
        {
            _bgw?.CancelAsync();
        }
    }
}


De _bgw is onze backgroundworker die we straks aan 't werk gaan zetten. Voeg een methode AddCustomers toe; deze doet niets anders dan een nieuwe BGW instantiëren (als er niet al 1 bezig is), 'configureert' deze en trapt 'm vervolgens aan om aan 't werk te gaan.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void AddCustomers(int count)
{
    // If we currently have no backgroundworker
    if (_bgw == null)
    {
        // Create a backgroundworker, set it up and kick it off
        using (_bgw = new BackgroundWorker() { WorkerReportsProgress = true, WorkerSupportsCancellation = true })
        {
            _bgw.DoWork += DoWork;
            _bgw.RunWorkerCompleted += WorkCompleted;
            _bgw.ProgressChanged += (s, pe) => { MainProgressBar.Value = pe.ProgressPercentage; };
            _bgw.RunWorkerAsync(count);
        }
    }
    else
    {
        throw new Exception("Already in progress");
    }
}


Ok; so far so good. Nu de code waar we mee gaan stoeien: DoWork() en WorkCompleted():

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private void DoWork(object sender, DoWorkEventArgs e)
{
    // Get reference to ourself and prepare a list of results
    var self = (BackgroundWorker)sender;
    var custcount = (int)e.Argument;
    var results = new List<Customer>(custcount);
    // Keep track of last reported progress
    var lastreportedprogress = 0;
    // Add "count" customers while not cancellation pending
    for (var i = 0; i < custcount && !self.CancellationPending; i++)
    {
        // Do actual work
        results.Add(Customer.CreateRandomCustomer(i));

        // Keep track of progress
        var progress = (int)((double)i / custcount * 100);
        // If the actual percentage changed, report it
        if (progress > lastreportedprogress)
        {
            self.ReportProgress(progress);
            lastreportedprogress = progress;
        }
    }
    // If not cancelled, report last status as 100
    if (!self.CancellationPending)
        self.ReportProgress(100);

    // Return whether the action was cancelled
    e.Cancel = self.CancellationPending;
    // Return results
    e.Result = results;
}

private void WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Populate our listview with results (if any)
    MainListView.BeginUpdate();
    MainListView.Items.Clear();
    if (!e.Cancelled)
    {
        var results = (IEnumerable<Customer>)e.Result;
        MainListView.Items.AddRange(results.Select(r => new CustomerListViewItem(r)).ToArray());
    }
    MainListView.EndUpdate();
    // Reset progressbar
    MainProgressBar.Value = 0;
    // Clear backgroundworker so we're ready for a new run
    _bgw = null;
}


Zoals je ziet vullen we hier in DoWork() alleen een list met puur de resultaten, niets anders. Dan, in de WorkCompleted() pakken we die resultaten en transformeren die (dat heet 'projecteren' in LINQ termen) naar onze ListViewItems en mikken die in 1 klap in de listview. Nu gebeurt er op 't moment vrij weinig in de WorkCompleted. Als we echter "veel werk" hebben om de listviewitems te maken (omdat er bijv. nog e.e.a. uitgeplozen moet worden) dan heb je dus alsnog dat je UI "hangt". Je kunt dus de "projectie" ook nog naar de DoWork() halen. Daarvoor verander je in bovenstaande code regel 6 naar:
C#:
1
var results = new List<CustomerListViewItem>(custcount);

en regel 13 naar:
C#:
1
results.Add(new CustomerListViewItem(Customer.CreateRandomCustomer(i)));

(waar je dan eventueel de rest van het werk nog verzet om het listviewitem te maken etc). Regels 41 & 42 kun je dan veranderen naar:
C#:
1
MainListView.Items.AddRange(((IEnumerable<CustomerListViewItem>)e.Result).ToArray());

offtopic:
Helaas wil WinForms altijd arrays of andere vage collecties hebben i.p.v. dat het een IEnumerable<T> accepteert :/


Afhankelijk van waar je "werk" zit en hoe je je applicatie wil opzetten kun je er dus voor kiezen pure results te returnen en die te transformeren naar listviewitems naderhand of meteen listviewitems te returnen.

In beide gevallen zie je je listview (wegens de BeginUpdate en EndUpdate) in 1 keer vol "ploepen". Totaal: 99 regels, waarvan 47 daadwerkelijke code.

Met een virtual listview ziet 't er heel anders uit. Althans: je kunt dan nog steeds een backgroundworker gebruiken en de lijst vullen zoals hierboven gedaan wordt maar dan "op de virtual manier". ECHTER; als je maar 25 listviewitems in beeld hebt, waarom dan wel 't werk van alle 2500 items verzetten? Je kunt op dat moment véél beter het werk pas verzetten op 't moment dat je de listviewitems op je scherm moet zetten wanneer men ze in beeld scrolt. Je hebt in dat geval niet eens een progressbar nodig (of het moet écht heel "duur" zijn om die 25 listviewitems op je scherm te krijgen).

De code uit de link die ik je gaf vind ik zelf vrij omslachtig. Ik zou 't zelf eerder op ongeveer deze manier schrijven (heel de code van MainForm kun je trashen):

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Collections.Concurrent;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class MainForm : Form
    {
        // Cache items; key = index in listview, value = item
        private ConcurrentDictionary<int, CustomerListViewItem> _cache;

        public MainForm()
        {
            InitializeComponent();

            // Set up retrieval of listviewitems
            MainListView.RetrieveVirtualItem += (s, e) =>
            {
                // Check cache, if not in cache create item on demand
                e.Item = _cache.GetOrAdd(e.ItemIndex, (i) =>
                {
                    // Do the work for 1 item here...
                    return new CustomerListViewItem(Customer.CreateRandomCustomer(i));
                });
            };
        }

        private void AddButton_Click(object sender, EventArgs e)
        {
            AddCustomers(2500);
        }

        public void AddCustomers(int count)
        {
            _cache = new ConcurrentDictionary<int, CustomerListViewItem>(Environment.ProcessorCount, count);
            MainListView.VirtualListSize = count;
            MainListView.Refresh();
        }
    }
}

Meer heb je niet nodig; 40 regels, waarvan 17 met daadwerkelijke code. Het neerzetten van een enkel item (of een "hele pagina" van 25 items) is dan, wellicht, wat trager maar je betaalt wél alleen maar voor de items die je ziet. Heel veel eleganter dan dat krijg je 't niet. Probeer 't eens met 1.000.000 customers ;) Wil je overigens niet alle items in-memory houden (in het geval van een miljoen items bijvoorbeeld) dan kun je natuurlijk iets anders (bijvoorbeeld een MemoryCache) gebruiken met een limit van, zeg, 100MB of een soortgelijke structuur en daar gewoon een FIFO-achtig iets op los laten of een andere eviction strategie. Maar we gaan te ver offtopic :P

Vervolgens kun je dit overigens weer makkelijk uitbreiden met een backgroundworker die in de achtergrond alvast "andere pagina's" gaat ophalen en "vooruit werken" (maar dat vereist een beetje nadenkwerk... en de kans dat 't de moeite loont is vrij klein).

Code
Zipfile

En dan tot slot, over een compleet andere boeg: op het moment dat je zoveel items in een listview zet moet je je ook even afvragen of er echt wel noodzaak is om de gebruiker te 'overstelpen' met zoveel data. Ik heb daar ooit een stukkie over geschreven :P

[Voor 14% gewijzigd door RobIII op 20-09-2018 02:14]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Roses are red Violets are blue, Unexpected ‘{‘ on line 32.

Over mij


  • cowandchicken
  • Registratie: september 2018
  • Laatst online: 17-06 07:45
Roblll thanks voor de uitgebreide post! Daar kan ik wel eens mee stoeien.
Ik merk dat ik nog wat achterloop in mijn C# kennis, als ik die verkorte manieren zie van eventhandlers toekennen etc, maar goed ik gebruik het ook maar als bij zaak, het is niet mijn vakgebied.
Qua opzet en wat ik in mijn ui form doe en wat in de bgw zitten we toch al aardig op 1 lijn.
Ik zou nog eens kunnen proberen om de lijst met listview items in de bgw toe te voegen.

Laat ik teveel data zien? Ja ongetwijfeld. Het is een analyse tool voor devellopers, maar vooral voor mezelf. En ja dan wil je natuurlijk alles inzichtelijk ;)
Ik heb vanmorgen even naar het idee van de virtual mode gekeken, maar gezien ik het nu wel erg praktisch vind om per kolom te kunnen sorteren, maakt die optie het me niet makkelijker. Dan maar wat minder vloeiend.
Pagina: 1


Apple iPad Pro (2021) 11" Wi-Fi, 8GB ram Microsoft Xbox Series X LG CX Google Pixel 5a 5G Sony XH90 / XH92 Samsung Galaxy S21 5G Sony PlayStation 5 Nintendo Switch Lite

Tweakers vormt samen met Hardware Info, AutoTrack, Gaspedaal.nl, Nationale Vacaturebank, Intermediair en Independer DPG Online Services B.V.
Alle rechten voorbehouden © 1998 - 2021 Hosting door True