[ cesare @ 12.02.2011. 09:14 ] @
Pozdrav svima !!!

U WinForm aplikaciji koju sam pravio, u ListView se učitavaju i prikazuju slike iz odabranog foldera. Učitavanje i prikaz su izmešteni u jedan BackgroundWorker. Na formi gde je lista nalazi se i jedan Label u kome jedan Timer svake sekunde pokazuje sistemsko vreme. Kada korisnik odabere željeni folder započne ucitavanje i prikaz slika. Dok traje učitavanje, prikaz sistemskog vremena se ne zamrzava. Sve ovo sam hteo da napravim u WPF-u. Tu se javlja problem. Dok traje učitavanje slika, prikaz sistemskog vremena je zamrznut. Zašto? Postoji li način da se ovo napravi da i u WPF radi isto kao i u WinForm aplikaciji ?

Unapred hvala ...
[ Boris B. @ 12.02.2011. 11:15 ] @
Postavi kako ti izgleda Timer handler i kako kreiras i stratujes taj background thread.
[ cesare @ 12.02.2011. 12:20 ] @
Tajmer je kreiran ovako:

Code:
 private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            DispatcherTimer dispatcherTimer = new DispatcherTimer();
            dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
            dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
            dispatcherTimer.Start();
        }
 


Ucitavanje i prikaz slika ovako:
(Broj slika u brojacu je stavljen samo probe radi - hteo sam da proverim koliko vremena treba da ucita tih 600 slika)

Code:

private void button1_Click(object sender, RoutedEventArgs e)
        {
            progressBar1.Minimum = 0;
            progressBar1.Maximum = 100;

            worker.WorkerReportsProgress = true;
            worker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                this.Dispatcher.Invoke((IzvrsavanjeGlavnogThreada)delegate
                {
                    UcitajSlike();
                    worker.ReportProgress(BrojUcitanihSlika / 600 * 100);
                });

            };

            worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
            {
                progressBar1.Value = args.ProgressPercentage;
            };

            worker.RunWorkerAsync();
        }

private void UcitajSlike()
        {
            for (int Brojac = 0; Brojac < 601; Brojac++)
            {
                Image i = new Image();
                BitmapImage src = new BitmapImage();
                src.BeginInit();
                src.UriSource = new Uri(LokacijaNekeSlike, UriKind.RelativeOrAbsolute);
                src.DecodePixelWidth = 120;
                src.DecodePixelHeight = 120;
                src.EndInit();
                i.Source = src;
                i.Stretch = Stretch.Fill;
                listView1.Items.Add(i);

                BrojUcitanihSlika = Brojac;
            }
        }

[ Dusan Kondic @ 12.02.2011. 12:27 ] @
Pogledaj
http://www.codeproject.com/KB/WPF/AsynchronousWPF.aspx

Mislim da poziv treba da bude sa BeginInvoke umesto sa Invoke.
[ ravni @ 12.02.2011. 12:45 ] @
deluje kao da ti sve sve desava na UI tredu i onda ti zato tajmer ne osvezava vreme
Code:
worker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                this.Dispatcher.Invoke((IzvrsavanjeGlavnogThreada)delegate
                {
                    UcitajSlike();...
do work radi u background threadu, a Dispatcher.Invoke te opet vraca na UI thread.
pored toga ne vidim ni kako bi ti progres bar radio jer se samo jednom poziva report progres.

trebalo bi da u dowork eventu ucitas slike u kolekciju BitmapImage objekata, a onda u RunWorkerCompleted eventu napunis Image kontrole
[ cesare @ 12.02.2011. 20:33 ] @
Iskoristio sam kod sa codeproject sajta. S tim sto sam napravio jednu user kontrolu StavkaListe koja ima na sebi sliku i textblock. Ta kontrola ima jednu metodu koja ubacuje sliku i tekst. Ova stavka se ubacuje u listu. Ali kada procedura za ubacivanje stavke naidje na red
Code:

StavkaListe stavka = new StavkaListe;

prijavljuje se greska u kodu stavke kod Initializecomponent(); - kaze da thread mora biti postavljen na STA.
Kako to uraditi ili postoji neko drugo resenje ?

Unapred hvala ...
[ Boris B. @ 13.02.2011. 01:43 ] @
Kad sam ti napisao "Postavi kako ti izgleda Timer handler" mislio sam da postavis kod od timer handlera, tj. sta radi ono dispatcherTimer_Tick.
Ne znam sta si posle radio sa tim codeproject kodom, ali u ovnom prvom kodu sto si postavio su ona dva problema sto ih je pomenuo ravni, tj. ucitavanje slika se vrsi na glavnom threadu i worker.ReportProgress se poziva na samom kraju.

Quickfix:
1. Napravi da ti metod UcitajSlike prima worker kao parametar i onda neka taj metod poziva Worker.ReportProgress unutar for petlje.
2. Popravi
Code:

worker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                this.Dispatcher.Invoke((IzvrsavanjeGlavnogThreada)delegate
                {
                    UcitajSlike();
                    worker.ReportProgress(BrojUcitanihSlika / 600 * 100);
                });

            };


u

Code:

worker.DoWork += delegate(object s, DoWorkEventArgs args)
            {
                  UcitajSlike();
                  worker.ReportProgress(BrojUcitanihSlika / 600 * 100);
            };

Znaci bez Invoke ni BeginInvoke.

3. Unutar metode UcitajSlike popravi listView1.Items.Add(i) u listView1.Dispatcher.Invoke(new Action(() => listView1.Items.Add(i))), posto ne mozes da menjas objekat kreiran na UI threadu iz drugog threada, ovako sa listView1.Dispatcher.Invoke ces postaviti akciju u queue dispecera na UI threadu i bice ok.


[Ovu poruku je menjao Boris B. dana 13.02.2011. u 03:27 GMT+1]
[ Dusan Kondic @ 13.02.2011. 14:58 ] @
Prilikom odabira foldera startujemo metodu za učitavanje slika i tajmer u novim thread-ovima.
Code:

        private void btnUcitaj_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.FolderBrowserDialog dlg = new System.Windows.Forms.FolderBrowserDialog();
            System.Windows.Forms.DialogResult result = dlg.ShowDialog();
            if (result == System.Windows.Forms.DialogResult.OK)
            {
                Ucitao = false;
                string IzabraniFolder = dlg.SelectedPath;
                string[] fajlovi = System.IO.Directory.GetFiles(IzabraniFolder, "*", System.IO.SearchOption.TopDirectoryOnly);
                BrSlika = fajlovi.Length;
                AsyncMethodHandler caller = default(AsyncMethodHandler);
                caller = new AsyncMethodHandler(UcitajSlike);
                caller.BeginInvoke(fajlovi, UcitaoSam, null);

                System.Threading.Thread thr1 = new System.Threading.Thread(
                    new System.Threading.ThreadStart(KreirajIStartujTajmer));
                thr1.Start();
            }
        }

Što se tiče tajmera, njega instanciramo i startujemo, a on prilikom svakog odbrojavanja proveri da li su slike učitane, i
ažurira labelu i progresnu liniju pozivajući metodu iz glavnog thread-a.
Code:

        void KreirajIStartujTajmer()
        {
            tajmer = new System.Timers.Timer(100);
            tajmer.Elapsed += new System.Timers.ElapsedEventHandler(tajmer_Elapsed);
            tajmer.Start();
        }
        void tajmer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            tajmer.Stop();
            if (Ucitao)
            {
                BrTekuceSlike = BrSlika;
            }
            else
            {
                tajmer.Start();
            }
            UpdatePrgBarILbl();
        }
        private void UpdatePrgBarILbl()
        {
            if (prgBar.Dispatcher.CheckAccess())
            {
                double vr = 100 * (double)BrTekuceSlike / (double)BrSlika;
                prgBar.SetValue(ProgressBar.ValueProperty, vr);
                lblVreme.Content = DateTime.Now.Hour.ToString() + ":" + DateTime.Now.Minute.ToString() + ":" +
                    DateTime.Now.Second.ToString() + " - " + (String.Format("{0:0}", vr)).ToString() + "%";
            }
            else
            {
                prgBar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                    new PrgBarUpdate(UpdatePrgBarILbl));
            }
        }

Učitavanje slika se vrši jednim delom u novom thread-u, a drugim delom u osnovnom UI thread-u.
Bilo bi dobro da može da se iz drugog thread-a, kao parametar funkcije, vrati čitav niz Image-a, ali
nije mi to pošlo za rukom.
U svakom slučaju, ovde se pripremi niz bajtova koji se šalje sa ostalim parametrima u novu metodu.
Code:

        private bool UcitajSlike(string[] fajlovi)
        {
            bool UcitaoBezProblema = true;
            try
            {
                System.IO.FileInfo fi;
                for (BrTekuceSlike = 0; BrTekuceSlike < BrSlika; BrTekuceSlike++)
                {
                    fi = new System.IO.FileInfo(fajlovi[BrTekuceSlike]);
                    if (fi.Extension == ".JPG")
                    {
                        Byte[] ByteArray;
                        FileStream fs = new FileStream(fajlovi[BrTekuceSlike], FileMode.Open);
                        using (BinaryReader br = new BinaryReader(fs))
                        {
                            ByteArray = br.ReadBytes(Convert.ToInt32(fs.Length));
                        }
                        lvSlicice.Dispatcher.Invoke(new DodavanjeUNiz(DodajElemUNiz), 
                            System.Windows.Threading.DispatcherPriority.Normal,
                                new object[] { fajlovi[BrTekuceSlike], BrTekuceSlike, ByteArray });
                    }
                    if (BrTekuceSlike == BrSlika - 1) { Ucitao = true; }
                }
            }
            catch
            {
                UcitaoBezProblema = false;
            }
            return UcitaoBezProblema;
        }

Ova metoda (DodajElemUNiz) se startuje iz osnovnog UI thread-a.
Code:

        private void DodajElemUNiz(string fajl, int RBr, Byte[] ByteArray)
        {
            BitmapImage Slicica = new BitmapImage();
            Slicica.BeginInit();
            Slicica.UriSource = new Uri(fajl);
            Slicica.StreamSource = new System.IO.MemoryStream(ByteArray);
            Slicica.DecodePixelWidth = 200;
            Slicica.EndInit();
            ImageSource imgsrc = (ImageSource)Slicica;
            Image img = new Image();
            img.Source = imgsrc;
            lvSlicice.Items.Add(img);
        }

Na kraju, kada se završi učitavanje svih slika, izvršava se metoda UcitaoSam jer je naznačena
kao callback metoda prilikom pozivanja metode UcitajSlike.
Code:

        private void UcitaoSam(IAsyncResult ar)
        {
            AsyncResult result = (AsyncResult)ar;
            AsyncMethodHandler caller = (AsyncMethodHandler)result.AsyncDelegate;

            bool returnValue = caller.EndInvoke(ar);
            Ucitao = true;
        }

Ovde podižemo flag Ucitao = true, tako da tajmer prekida svoj rad.
U ovom primeru nedostaje postavljanje podrazumevanih vrednosti na kraju učitavanja i prilikom
ponovnog klika na button, rad sa slikama drugih ekstenzija, enable-ovanje i disable-ovanje button-a
i mnogo šta drugo, ali ovo je samo primer.
Evo i deklaracija ...
Code:

        private delegate void Tajmer();
        private delegate void PrgBarUpdate();
        private delegate void DodavanjeUNiz(string fajl, int RBr, Byte[] ByteArray);
        delegate bool AsyncMethodHandler(string[] fajlovi);
        delegate void PopuniListuHandler(bool ImgSrcList);
        Image[] NizSlicica = new Image[] { };
        private Image img = new Image();
        private System.Timers.Timer tajmer;
        private bool Ucitao;
        private int BrSlika = 0;
        private int BrTekuceSlike = 0;

... i XAML-a
Code:
<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <ListView Name="lvSlicice" Grid.Row="0" Grid.RowSpan="4"></ListView>
        <Button Name="btnUcitaj" Grid.Row="0" Grid.Column="1" Click="btnUcitaj_Click">Učitaj</Button>
        <Label Name="lblVreme" Grid.Row="1" Grid.Column="1"></Label>
        <ProgressBar Name="prgBar" Grid.Row="2" Grid.Column="1"></ProgressBar>
    </Grid>

Da bi kod radio, mora još da se doda i referenca na Szstem.Windows.Forms.
Pozdrav.
[ Boris B. @ 13.02.2011. 19:41 ] @
@Dusan Kondic - šta ti radi ovaj kod?
Code (csharp):

 System.Threading.Thread thr1 = new System.Threading.Thread(
                    new System.Threading.ThreadStart(KreirajIStartujTajmer));
                thr1.Start();
 

Pravis thread samo da bi u njemu kreirao tajmer ?
Code (csharp):

 void KreirajIStartujTajmer()
        {
            tajmer = new System.Timers.Timer(100);
            tajmer.Elapsed += new System.Timers.ElapsedEventHandler(tajmer_Elapsed);
            tajmer.Start();
        }
 

Koja je logika iza takvog instanciranja tajmera, baš me zanima. Isto tako me zanima da li misliš da je dobra ideja koristiti globalne objekte za komunikaciju između threadova i vođenje object state-a(ono private bool Ucitao, private int BrSlika, private int BrTekuceSlike).

@cesare
U pravu si za STA thread exception. BackgroundWorker nije STA i ne može se naterati da bude, ali zato običan thread može. Jedini zez je korišćenje freezable objekata, imaš dole u kodu:
Code (csharp):

    var dlg = new System.Windows.Forms.OpenFileDialog { Filter = "JPEG files(*.jpg)|*.jpg|PNG files (*.png)|*.png", Multiselect = true };
    if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK)
        return;
    var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(10), IsEnabled = true };
    timer.Tick += (send, args) => progressLabel.Content = DateTime.Now.ToString("hh:mm:ss:fff");
    progressBar.Value = 0;
    progressBar.Maximum = dlg.FileNames.Length;
    var thread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
        {
            for (var i = 0; i < dlg.FileNames.Length; i++)
            {
                var bmp = new BitmapImage(new Uri(dlg.FileNames[i]));
                bmp.Freeze(); //Kljucna stvar, omogucava prenos WPF objekta iz threda u thread
                imagesListView.Dispatcher.Invoke(new Action(() =>
                    {
                        imagesListView.Items.Add(new Image { Width = 128, Height = 128, Source = bmp });
                        progressBar.Value = i+1;                                
                    }));
                System.Threading.Thread.Sleep(1000); //Simulacija dugog ucitavanja
            }                    
        }));
    thread.SetApartmentState(System.Threading.ApartmentState.STA);
    thread.Start();
 


Imaš kompletan projekat u attachmentu.
[ Dusan Kondic @ 14.02.2011. 07:44 ] @
Pokušao sam da što više stvari izbacim iz osnovnog thread-a, pa sam i tajmer prebacio u nov thread.
Ova akcija je sobom povukla kreiranje pomenutih globalnih objekata.
Idealno bi bilo kada bih BitmapImage, ili čitav niz BitmapImage-a mogao
kao parametar da vratim u osnovni thread.
[ Boris B. @ 14.02.2011. 09:22 ] @
Citat:
Dusan Kondic: Pokušao sam da što više stvari izbacim iz osnovnog thread-a, pa sam i tajmer prebacio u nov thread.

Ali System.Timers.Timer jeste nov thread, cela klasa Timer je u stvari enkapsulirani nov thread koji radi Thread.Sleep(Interval) i onda okida Elapsed, ti u stvari pravis thread samo da bi napravio thread.

Vođenje state-a threadova u globalnim varijablama (polja objekta) nije dobra ideja, zato i jesu pravili ParameterizedThreadStart, AsyncResult i ostale framework elemente za komunikaciju sa threadovima. Nije dobra ideja zbog toga što to otvara vrata za race conditions koje je često teško naći, bilo je već reči o tome u prethodnim temama, i treba izbeći kad god je moguće.

Citat:
Dusan Kondic: Idealno bi bilo kada bih BitmapImage, ili čitav niz BitmapImage-a mogao kao parametar da vratim u osnovni thread.

To se postiže sa BitmapImage.Freeze(), pogledaj moj prethodni post. A vraćati čitav niz BitmapImage-a je daleko od idealnog, idealno je slati BitmapImage u listview kako se koji učita ili ih bar baferovati ako su slike dovoljno male, a ne učitavati svih potencijalno milion slika u privremenu kolekciju.