-
Mar 23rd, 2020, 01:50 PM
#1
Thread Starter
Member
[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.
-
Mar 24th, 2020, 12:59 AM
#2
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:
PicView.Children.Add(Pic)
with this:
and then add this:
vb.net Code:
Private Sub AddPicture(Pic As Image) If Me.PicView.Dispatcher.CheckAccess() Then Me.PicView.Children.Add(Pic) Else Me.PicView.Dispatcher.Invoke(New Action(AddressOf AddPicture), Pic) End If End Sub
I think that you should be able to simply replace Invoke with BeginInvoke.
Last edited by jmcilhinney; Mar 24th, 2020 at 01:04 AM.
-
Mar 24th, 2020, 01:08 AM
#3
Re: Trouble gettign image loader onto a background thread.
Originally Posted by Mars729
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.
-
Mar 27th, 2020, 09:39 AM
#4
Thread Starter
Member
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
-
Mar 27th, 2020, 09:41 AM
#5
Thread Starter
Member
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|