Results 1 to 5 of 5

Thread: [RESOLVED] Trouble gettign image loader onto a background thread.

  1. #1

    Thread Starter
    Member
    Join Date
    Aug 2011
    Posts
    51

    Resolved [RESOLVED] Trouble gettign image loader onto a background thread.

    Code:
    Public Sub DirItem_PreviewMouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs)
            If sender.DirName <> e.Source.DirName Then Exit Sub
            Dim DBM As New DatabaseManager(MySettings.Default.Database1ConnectionString)
            Dim SQl As String
            Dim Result As List(Of List(Of Object))
            Dim TagLen As String = CStr(Len(e.Source.DirName))
            Dim Tag As String = Replace(e.Source.DirName, "\", "/")
            SQl = "select SourceFile from Media where left(SourceFile, " + TagLen + ") = '" + Tag + "'"
            Result = DBM.RunCommand(SQl)
            Dim mw As MainWindow = CType(Application.Current.MainWindow, MainWindow)
    
            Call RenderImages(Result, mw.PicView)
    End Sub
    
    Sub RenderImages(Result As List(Of List(Of Object)), PIcView As Canvas)
            Dim Render1 As Renderer = New Renderer(Result, PIcView)
            Dim t1 As Thread = New Thread(AddressOf Render1.RenderImages)
            t1.SetApartmentState(ApartmentState.STA)
            t1.Start()
            Try
                t1.Join()
            Catch e As Exception
                Console.Write(e.ToString)
            End Try
    End sub
    
    Public Class Renderer
            Private Result As List(Of List(Of Object))
            Private PicView As Canvas
            Public Sub New(ByRef Result As List(Of List(Of Object)), ByRef PIcView As Canvas)
                MyBase.New
                Me.Result = Result
                Me.PicView = PIcView
            End Sub
            Public Sub RenderImages()
                Dim Pic As New Image
                Dim B As New BitmapImage
                Dim Index As Integer
                Dim PB As New PBClass
                Dim IB As New IBClass
                Dim Thumbs As String = System.AppDomain.CurrentDomain.BaseDirectory() + "thumbs"
                Dim Temp As String, FileNameExtPos As Integer, FileName As String
    
                PB.Width = 1900
                PB.MinSpacing = 10
                PB.Ratio = 1.3333
                PB.IBWidth = 200
                For t = 0 To Result.Count - 1
                    If t > 500 Then Exit Sub
                    Temp = Thumbs + "\" + Replace(Left(Result(t)(0), 1) + Mid(Result(t)(0), 3), "/", "\")
                    FileNameExtPos = InStrRev(Temp, "\")
                    FileName = Mid(Temp, FileNameExtPos + 1)
                    Temp = Left(Temp, FileNameExtPos) + "240\" + FileName
    
                    Result(t)(0) = Temp
    
                    If Right(Result(t)(0), 3) = "jpg" Or Right(Result(t)(0), 3) = "JPG" Then
                        ' Transform result to a thumbnail
                        Pic = New Image
                        AddHandler Pic.MouseDown, New MouseButtonEventHandler(AddressOf _a_MenuEvents.Pic_MouseDown)
                        B = New BitmapImage
                        B.BeginInit()
                        B.UriSource = New Uri(Result(t)(0))
                        B.EndInit()
                     
                        Pic.Source = B
                        PB.Index = Index
                        IB = PositionImage(PB)
                        PicView.Children.Add(Pic)
                        Pic.Margin = New Thickness(IB.Left, IB.Top, IB.Right, IB.Bottom)
                        If IB.Bottom > PicView.Height Then PicView.Height = IB.Bottom + PB.MinSpacing
                        Pic.MaxWidth = IB.Width
                        Pic.MaxHeight = IB.Width / PB.Ratio
                        Index = Index + 1
                        Debug.Print(Pic.Width)
                        Debug.Print(Pic.Height)
                    End If
    
                Next
            End Sub
    I am writing a desktop application that combines a person's sightings from several online citizen science portals into a personal viewer.

    Currently, I have a partly working image viewer. A major problem is it is on the UI thread, so that all the images load before they are displayed. Obviously, this is too slow if a folder has over a 100 thumbnails. This code above gives me the error "The calling thread cannot access this object because a different thread owns it" at the line
    Code:
    PicView.Children.Add(Pic)
    I have attempted to use a delegate to fix this problem, but I have done something wrong as the pictures are not showing, and the UI is still frozen when a lot of pictures are loaded, even though they are not showing up.

    I put
    Code:
    Public Delegate Sub DelRender(ByVal Pic As Image)
    at the top. I also put
    Code:
    Dim dlg As DelRender = New DelRender(AddressOf FinishImage)
        Dispatcher.CurrentDispatcher.BeginInvoke(Windows.Threading.DispatcherPriority.Background, dlg, Pic)
    just above PicView.Children.Add(PIc). And then put
    Code:
    sub FinishImage(byval Pic as Image)
        PIcView.Children.Add(Pic)
    End sub
    at the end. Any suggestions?

    If I can get the picture viewer onto a different thread successfully, I may looking into creating placeholders for the images.
    Last edited by Mars729; Mar 23rd, 2020 at 02:00 PM.

  2. #2
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,297

    Re: Trouble gettign image loader onto a background thread.

    Based on the pattern I describe here and the WPF example provided in post #16, you would replace this:
    vb.net Code:
    1. PicView.Children.Add(Pic)
    with this:
    vb.net Code:
    1. AddPicture(Pic)
    and then add this:
    vb.net Code:
    1. Private Sub AddPicture(Pic As Image)
    2.     If Me.PicView.Dispatcher.CheckAccess() Then
    3.         Me.PicView.Children.Add(Pic)
    4.     Else
    5.         Me.PicView.Dispatcher.Invoke(New Action(AddressOf AddPicture), Pic)
    6.     End If
    7. End Sub
    I think that you should be able to simply replace Invoke with BeginInvoke.

  3. #3
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,297

    Re: Trouble gettign image loader onto a background thread.

    Quote Originally Posted by Mars729 View Post
    If I can get the picture viewer onto a different thread successfully, I may looking into creating placeholders for the images.
    It's not a case of getting anything onto a different thread. Any controls are owned by the UI thread and that is where you access them. you need to marshal a method call to the UI thread and then access your control there. That's what the AddPicture method does. You call it on the secondary thread and it invokes itself again on the UI thread.

  4. #4

    Thread Starter
    Member
    Join Date
    Aug 2011
    Posts
    51

    Re: Trouble gettign image loader onto a background thread.

    I attempted to use a BackgroundWorker by putting the UI updating in the ProgressChanged event. That helped by not freezing the UI, but not all the pictures showed up and if I tried to load a new set of thumbnails before the thumbnails were done loading it, the BackgroundWorker was usually busy and an error would trip. I briefly considered the updated Task.Async structured but then tried a timer for kicks.

    It worked, and it was much simpler.

    The basic idea is to render a single image, then invoke a timer which would recall the subroutine to do the next image. This unfreezes the UI while the pictures are loaded. I can also start a new set of thumbnails without an error (an unwanted image may still occur but would quickly be visually overwritten). I wasted quite a bit of time getting this solution, but I know more now about multitasking, in case I ever need it. For my software, there are very few places where it can benefit from multithreading, as it is consists mainly of loading files, SQL queries and updating the UI.

    Code:
    Public PicCounter As Integer, PicIndex As Integer
    Public FileList As List(Of List(Of Object))
    Sub RenderImages(Result As List(Of List(Of Object)), PicView As Canvas)
            PicCounter = 0
            PicIndex = 0
            PicView.Children.Clear()
            Thread.Sleep(50)
            Call RenderImages2(Result, PicView)
        End Sub
        Sub RenderImages2(Result As List(Of List(Of Object)), PIcView As Canvas)
            Dim Pic As New Image
            Dim B As New BitmapImage
            Dim PB As New PBClass
            Dim IB As New IBClass
            Dim Thumbs As String = System.AppDomain.CurrentDomain.BaseDirectory() + "thumbs"
            Dim Temp As String = "", FileNameExtPos As Integer, FileName As String
    
            PB.Width = 1900
            PB.MinSpacing = 10
            PB.Ratio = 1.3333
            PB.IBWidth = 200
    
            If PicCounter > Result.Count - 1 Then
    
                Exit Sub
            End If
    
            Temp = Thumbs + "\" + Replace(Left(Result(PicCounter)(0), 1) + Mid(Result(PicCounter)(0), 3), "/", "\")
            FileNameExtPos = InStrRev(Temp, "\")
            FileName = Mid(Temp, FileNameExtPos + 1)
            If PB.IBWidth < 120 Then
                Temp = Left(Temp, FileNameExtPos) + "120\" + FileName
            ElseIf PB.IBWidth > 240 Then
                Temp = Left(Temp, FileNameExtPos) + "480\" + FileName
            Else
                Temp = Left(Temp, FileNameExtPos) + "240\" + FileName
            End If
    
            Result(PicCounter)(0) = Temp
    
            If Right(Result(PicCounter)(0), 3) = "jpg" Or Right(Result(PicCounter)(0), 3) = "JPG" Then
                ' Transform result to a thumbnail
                Pic = New Image
                AddHandler Pic.MouseDown, New MouseButtonEventHandler(AddressOf _a_MenuEvents.Pic_MouseDown)
                PIcView.Children.Add(Pic)
                B = New BitmapImage
                B.BeginInit()
                B.UriSource = New Uri(Result(PicCounter)(0))
                B.EndInit()
    
                Pic.Source = B
                PB.Index = PicIndex
                IB = PositionImage(PB)
    
                Pic.Margin = New Thickness(IB.Left, IB.Top, IB.Right, IB.Bottom)
                If IB.Bottom > PIcView.Height Then PIcView.Height = IB.Bottom + PB.MinSpacing
                B.DecodePixelHeight = IB.Bottom - IB.Top
    
                Pic.MaxWidth = IB.Width
                Pic.MaxHeight = IB.Width / PB.Ratio
                Pic.Visibility = Visibility.Visible
                PicIndex = PicIndex + 1
            End If
            FileList = Result
            PicCounter = PicCounter + 1
            Dim dpTimer As DispatcherTimer = New DispatcherTimer
            dpTimer.Interval = TimeSpan.FromMilliseconds(50)
            AddHandler dpTimer.Tick, AddressOf Timer1_Timer
            dpTimer.Start()
    
        End Sub
        Public Sub Timer1_Timer()
            Dim mw As MainWindow = CType(Application.Current.MainWindow, MainWindow)
            Call RenderImages2(FileList, mw.PicView)
        End Sub

  5. #5

    Thread Starter
    Member
    Join Date
    Aug 2011
    Posts
    51

    Re: Trouble gettign image loader onto a background thread.

    I attempted to use a BackgroundWorker by putting the UI updating in the ProgressChanged event. That helped by not freezing the UI, but not all the pictures showed up and if I tried to load a new set of thumbnails before the thumbnails were done loading it, the BackgroundWorker was usually busy and an error would trip. I briefly considered the updated Task.Async system that replaces the Backgrounder but then tried a timer for kicks.

    It worked, and it was much simpler.

    The basic idea is to render a single image, then invoke a timer which would recall the subroutine to do the next image. This unfreezes the UI while the pictures are loaded. I can also start a new set of thumbnails without an error (an unwanted image may still occur but would quickly be visually overwritten). I wasted quite a bit of time getting this solution, but I know more now about multitasking, in case I ever need it. For my software, there are very few places where it can benefit from multithreading, as it is consists mainly of loading files, SQL queries and updating the UI.

    Code:
    Public PicCounter As Integer, PicIndex As Integer
    Public FileList As List(Of List(Of Object))
    Sub RenderImages(Result As List(Of List(Of Object)), PicView As Canvas)
            PicCounter = 0
            PicIndex = 0
            PicView.Children.Clear()
            Thread.Sleep(50)
            Call RenderImages2(Result, PicView)
    End Sub
    Sub RenderImages2(Result As List(Of List(Of Object)), PIcView As Canvas)
            Dim Pic As New Image
            Dim B As New BitmapImage
            Dim PB As New PBClass
            Dim IB As New IBClass
            Dim Thumbs As String = System.AppDomain.CurrentDomain.BaseDirectory() + "thumbs"
            Dim Temp As String = "", FileNameExtPos As Integer, FileName As String
    
            PB.Width = 1900
            PB.MinSpacing = 10
            PB.Ratio = 1.3333
            PB.IBWidth = 200
    
            If PicCounter > Result.Count - 1 Then
    
                Exit Sub
            End If
    
            Temp = Thumbs + "\" + Replace(Left(Result(PicCounter)(0), 1) + Mid(Result(PicCounter)(0), 3), "/", "\")
            FileNameExtPos = InStrRev(Temp, "\")
            FileName = Mid(Temp, FileNameExtPos + 1)
            If PB.IBWidth < 120 Then
                Temp = Left(Temp, FileNameExtPos) + "120\" + FileName
            ElseIf PB.IBWidth > 240 Then
                Temp = Left(Temp, FileNameExtPos) + "480\" + FileName
            Else
                Temp = Left(Temp, FileNameExtPos) + "240\" + FileName
            End If
    
            Result(PicCounter)(0) = Temp
    
            If Right(Result(PicCounter)(0), 3) = "jpg" Or Right(Result(PicCounter)(0), 3) = "JPG" Then
                ' Transform result to a thumbnail
                Pic = New Image
                AddHandler Pic.MouseDown, New MouseButtonEventHandler(AddressOf _a_MenuEvents.Pic_MouseDown)
                PIcView.Children.Add(Pic)
                B = New BitmapImage
                B.BeginInit()
                B.UriSource = New Uri(Result(PicCounter)(0))
                B.EndInit()
    
                Pic.Source = B
                PB.Index = PicIndex
                IB = PositionImage(PB)
    
                Pic.Margin = New Thickness(IB.Left, IB.Top, IB.Right, IB.Bottom)
                If IB.Bottom > PIcView.Height Then PIcView.Height = IB.Bottom + PB.MinSpacing
                B.DecodePixelHeight = IB.Bottom - IB.Top
    
                Pic.MaxWidth = IB.Width
                Pic.MaxHeight = IB.Width / PB.Ratio
                Pic.Visibility = Visibility.Visible
                PicIndex = PicIndex + 1
            End If
            FileList = Result
            PicCounter = PicCounter + 1
            Dim dpTimer As DispatcherTimer = New DispatcherTimer
            dpTimer.Interval = TimeSpan.FromMilliseconds(50)
            AddHandler dpTimer.Tick, AddressOf Timer1_Timer
            dpTimer.Start()
    
     End Sub
     Public Sub Timer1_Timer()
            Dim mw As MainWindow = CType(Application.Current.MainWindow, MainWindow)
            Call RenderImages2(FileList, mw.PicView)
     End Sub

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width