|
|
Hi all.
I have a curious question. I know how to do this the "official" way, but I want to try to do it backwards, and it seems it should be possible. But I am new to delegates.
I have read a few articles on how to get a button to respond to mouse clicks when the main thread is super busy running some really long task. The methods always revolved around putting that task in a background thread, so that the form was not held up in any way in terms of handling events. I understand these methods fairly well right now.
But I am curious if the reverse is possible? Can the main thread be stuck in the middle of some long task, and can its form have a button that can respond to a delegate in a background task. All the button does is set/reset a public flag. If the button could respond, the flag would get valued, and that would impact what the main thread is doing.
Everything I have tried has failed, because I do not understand how to use a delegate that can support a user-interactive feature, like a button click.
I have a kludge idea that might work, but it is a bit clunky. It would involve a transparent second form on a separate thread that overlays the main thread's form, and has that button on it in the exact position where it WOULD be, if it had been on the main thread's form. So to the user, it just looks like another button on the main form. But I don't like the idea because it has a clunk factor of ten. All that work for a single button, especially since if I want to resize or move the main form, the second form has to move along with it. Just plain messy.
So it just seems that some sort of delegate approach should work to avoid all that mess.
Thanks to anyone who can offer advice on a reverse-logic idea that may be leaving a bad taste in your mouth.
|
|
|
|
|
A button has a PerformClick method - if he button is public, you could call that.
But actually that's not the way to do it. In the event handler of the button click event, you call a method to do the things you want to do on that button click. If that method is public, you could directly call it. However, if UI elements are accessed, you should check for InvokeRequired.
|
|
|
|
|
Bernhard Hiller wrote: However, if UI elements are accessed, you should check for InvokeRequired.
That's exactly where my problem is, I think. I am doing InvokeRequired, but am getting screwed up somewhere. Now, I also just got an error back at one point saying that delegates cannot handle events. I am assuming that means you can pass arguments to a control, but if you need to fire one of its events, like a button click, a delegate won't work. Not sure about that, though.
At any rate, here is my code so far. I took the guts from a demo and attempted to modify it to do the reverse of what it did originally...Run some really long task as a background thread and let the controls be free for use in the main thread. This experiment is attempting to do the opposite...Run the task in the main thread, and work the form's button via a background task. It doesn't toggle the Pause/Continue state just yet...At this point, I was just trying to get the button to respond to a simple click.
Also, the Run method at the very bottom does the Invoke, but it causes the program launch to hang during form load. I figured I might run into problems there, since I may be trying something impossible with a delegate, or I've just got everything set up wrong.
Imports System.Threading
Public Class Form1
Private Delegate Sub Pause_Continue_cmd_ClickDelegateType(_
ByVal sender As System.Object, _
ByVal e As System.EventArgs)
Private Pause_Continue_cmd_ClickDelegate As _
Pause_Continue_cmd_ClickDelegateType
Private BG_Thread As Thread
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
FormIsLoadingFlag = 1
Threading.Thread.CurrentThread.Name = "UI Thread"
ProgramLaunchFlag = 0
FormIsLoadingFlag = 0
Dim New_DelegatePauseContinueButton As New _
Delegate_Pause_Continue_Button(Me, BGThreadNumber:=1)
BG_Thread = New Thread(AddressOf New_DelegatePauseContinueButton.Run)
BG_Thread.IsBackground = True
BG_Thread.Start()
End Sub
Private Sub StartLoop_cmd_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles StartLoop_cmd.Click
Call StartLoop()
End Sub
Private Sub StartLoop()
Dim loopo As Long
Dim loopo2 As Long
For loopo = 1 To 100000000
For loopo2 = 1 To 100000000
loopo = loopo
Next loopo2
Call Pause_Continue()
Counto = Counto + 1
Me.CounterVal_echo.Text = Str(Counto)
Me.CounterVal_echo.Refresh()
Next loopo
End Sub
Public Sub Pause_Continue()
EndlessLoop:
If CountStopFlag = 0 Then Exit Sub
GoTo EndlessLoop
End Sub
Public Sub Pause_Continue_cmd_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Pause_Continue_cmd.Click
CountStopFlag = 1
End Sub
End Class
Public Class Delegate_Pause_Continue_Button
Private OwnerForm As Form1
Private Delegate Sub Pause_Continue_cmd_ClickDelegateType( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs)
Private Pause_Continue_cmd_ClickDelegate As Pause_Continue_cmd_ClickDelegateType
Private PrivateBGThreadNumber As Integer
Public Sub New(ByVal my_form As Form1, _
ByVal BGThreadNumber As Integer)
OwnerForm = my_form
PrivateBGThreadNumber = BGThreadNumber
Pause_Continue_cmd_ClickDelegate = AddressOf OwnerForm.Pause_Continue_cmd_Click
End Sub
Public Sub Run()
Try
Do
SyncLock OwnerForm
If OwnerForm.InvokeRequired() Then
Dim args() As Object = {Nothing, Nothing}
OwnerForm.Invoke(Pause_Continue_cmd_ClickDelegate)
End If
End SyncLock
Loop
Catch ex As Exception
Debug.WriteLine("Unexpected error in thread " & PrivateBGThreadNumber & vbCrLf & ex.Message)
End Try
End Sub
End Class
|
|
|
|
|
Set up an extension method:
Module Extensions
<Runtime.CompilerServices.Extension()> _
Sub SynchronisedInvoke(synchMe As ISynchronizeInvoke, action As Action)
If Not synchMe.InvokeRequired Then
action()
Else
synchMe.Invoke(action, New Object() {})
End If
End Sub
End Module
Then you can call the btnClickety method(which is fired when the button is clicked) in this manner:
btnClickety.SynchronisedInvoke(Sub() btnClickety)
“That which can be asserted without evidence, can be dismissed without evidence.”
― Christopher Hitchens
|
|
|
|
|
I'll give it a try. For now, I have to get to bed...It's almost 3am! I will report back.
Update: I have some homework to do here. I am unfamiliar with action data types.
modified 25-Jun-13 20:19pm.
|
|
|
|
|
After studying this for a while, It seems that setting up an action and an extension is really just a simplification of doing an explicit delegate and attaching a method to it. So my existing code should do the same thing, then, except that I think I have the guts wrong in the Run() procedure and other places. Unless my assumptions are wrong, it seems that before I jump into Actions and Extensions, I need to understand the explicit delegate approach.
UPDATE: I am running your extensions idea, and I can get the b/g thread to run a task, but I am still not getting the button to respond, once I have the main thread caught in a long task. I was thinking that part of my problem is that I don't think the background thread is monitoring mouse behavior. When I start the program, the Form_Load sub starts the background thread, which gets the delegate going and the Run() sub is called. Even if I make the Run() procedure a continuous loop, it is going directly to the button click procedure every time, regardless of whether or not I have clicked the button. That part seems to make sense...Why would I WANT it to keep looping through the click event?! So I think I have one choice in that respect:
1. Have the Run() procedure monitor mouse behavior continuously, and when any button is clicked, check sender and only go to the Pause_Continue button click handler when it was the button in question. A monitor like that sounds like some sort of event listener. Not sure how to do that without some API functions.
UPDATE 2: After researching some more, it looks like maybe something involving WithEvents could work.
UPDATE 3: Now it looks more like AddHandler/RemoveHandler is the way to go.
modified 26-Jun-13 5:21am.
|
|
|
|
|
When I scroll down a DataGrid, then select a row in the DataGrid, then rebind the DataGrid, the DataGrid displays the rows starting with the very first row, DataGrid.Row = 0.
How do I reposition the DataGrid to display the row that was last selected?
The DataGrid is bound to a Command in the DataEnvironment.
I have tried the following code in the DataGrid.SelChange event, using the BookMarks property, where lastRow is declared as a Variant:
DataGrid1.Col = 0
lastRow = DataGrid1.Text
i = DataGrid1.Row
DataGrid1.Bookmark(i) = lastRow
When I run the code I get an error on the last line: "Run-time error '13': Type mismatch"
Any ideas? Thanks.
|
|
|
|
|
Member 9819470 wrote: DataGrid1.Bookmark(i) = lastRow I don't know what type "Bookmark(i)" is expecting, but it won't be a string.
VB6 is deprecated, and no longer supported.
Bastard Programmer from Hell
If you can't read my code, try converting it here[^]
|
|
|
|
|
Hi
A customer of asked us to build him a multi-language based support VB6 scraper, for which we had the need to detect UTF-8 based encoded strings to decode it later for proper displaying in application UI. It's necessary to point out that this need arises based on VB6 limitations to natively support UTF-8 in its controls, contrary to what it happens in .NET where you can tell a control that it should expect UTF-8 encoding. VB6 natively supports ISO 8859-1 and/or Windows-1252 encodings only, for which textboxes, dropdowns, listview controls, others can't be defined to natively support/expect UTF-8 as you can do in .NET considering what we just explained; so we would see weird symbols such as é, è among others, making it a whole mess at the time of displaying.
So, next function contains whole UTF-8 encoded punctuation marks and symbols from languages like Spanish, Italian, German, Portuguese, French and others, based on an excellent UTF-8 based list we got from this link - Ref. http://home.telfort.nl/~t876506/utf8tbl.html
Basically, the function compares if each and one of the listed UTF-8 encoded sentences, separated by | (pipe) are found in our passed string making a substring search first. Whether it's not found, it makes an alternative ASCII value based search to get a match. Say, a string like "Societé" (Society in english) would return FALSE through calling isUTF8("Societé") while it would return TRUE when calling isUTF8("SocietÈ") since È is the UTF-8 encoded representation of é.
Once you got it TRUE or FALSE, you can decode the string through DecodeUTF8() function for properly displaying it, a function we found somewhere else time ago and also included in this post.
[code]
Function isUTF8(ByVal ptstr As String)
Dim tUTFencoded As String
Dim tUTFencodedaux
Dim tUTFencodedASCII As String
Dim ptstrASCII As String
Dim iaux, iaux2 As Integer
Dim ffound As Boolean
ffound = False
ptstrASCII = ""
For iaux = 1 To Len(ptstr)
ptstrASCII = ptstrASCII & Asc(Mid(ptstr, iaux, 1)) & "|"
Next
tUTFencoded = "Ä|Ã…|Ç|É|Ñ|Ö|ÃŒ|á|Ã|â|ä|ã|Ã¥|ç|é|è|ê|ë|Ã|ì|î|ï|ñ|ó|ò|ô|ö|õ|ú|ù|û|ü|â€|°|¢|£|§|•|¶|ß|®|©|â„¢|´|¨|â‰|Æ|Ø|∞|±|≤|≥|Â¥|µ|∂|∑|âˆ|Ï€|∫|ª|º|Ω|æ|ø|¿|¡|¬|√|Æ’|≈|∆|«|»|…|Â|À|Ã|Õ|Å’|Å“|–|—|“|â€|‘|’|÷|â—Š|ÿ|Ÿ|â„|€|‹|›|ï¬|fl|‡|·|‚|„|‰|Â|Ú|Ã|Ë|È|Ã|ÃŽ|Ã|ÃŒ|Ó|Ô||Ã’|Ú|Û|Ù|ı|ˆ|Ëœ|¯|˘|Ë™|Ëš|¸|Ë|Ë›|ˇ" & _
"Å|Å¡|¦|²|³|¹|¼|½|¾|Ã|×|Ã|Þ|ð|ý|þ" & _
"â‰|∞|≤|≥|∂|∑|âˆ|Ï€|∫|Ω|√|≈|∆|â—Š|â„|ï¬|fl||ı|˘|Ë™|Ëš|Ë|Ë›|ˇ"
tUTFencodedaux = Split(tUTFencoded, "|")
If UBound(tUTFencodedaux) > 0 Then
iaux = 0
Do While Not ffound And Not iaux > UBound(tUTFencodedaux)
If InStr(1, ptstr, tUTFencodedaux(iaux), vbTextCompare) > 0 Then
ffound = True
End If
If Not ffound Then
'ASCII numeric search
tUTFencodedASCII = ""
For iaux2 = 1 To Len(tUTFencodedaux(iaux))
'gets ASCII numeric sequence
tUTFencodedASCII = tUTFencodedASCII & Asc(Mid(tUTFencodedaux(iaux), iaux2, 1)) & "|"
Next
'tUTFencodedASCII = Left(tUTFencodedASCII, Len(tUTFencodedASCII) - 1)
'compares numeric sequences
If InStr(1, ptstrASCII, tUTFencodedASCII) > 0 Then
ffound = True
End If
End If
iaux = iaux + 1
Loop
End If
isUTF8 = ffound
End Function
Function DecodeUTF8(s)
Dim i
Dim c
Dim n
s = s & " "
i = 1
Do While i <= Len(s)
c = Asc(Mid(s, i, 1))
If c And &H80 Then
n = 1
Do While i + n < Len(s)
If (Asc(Mid(s, i + n, 1)) And &HC0) <> &H80 Then
Exit Do
End If
n = n + 1
Loop
If n = 2 And ((c And &HE0) = &HC0) Then
c = Asc(Mid(s, i + 1, 1)) + &H40 * (c And &H1)
Else
c = 191
End If
s = Left(s, i - 1) + Chr(c) + Mid(s, i + n)
End If
i = i + 1
Loop
DecodeUTF8 = s
End Function
[/code]
Hope it helps
Regards
Diego Sendra
e-mail: contact@diegosendra.com
http://www.diegosendra.com
*Please note you have to download the function from http://www.diegosendra.com/samples/code/VB6/VB6_isUTF8.txt considering some of the UTF encoded symbols in tUTFencoded variable were lost/deleted at the time of copy/pasting the code into this thread
-- modified 24-Jun-13 20:18pm.
|
|
|
|
|
This forum is for asking questions - hence the big red button labelled "Ask a Question". Your post looks more like it should be a tip/trick[^].
Also, posting your email address in a public forum is a great way to have your inbox flooded with spam.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Hi Richard, maybe we didn't know there was such a tip/tricks section?
|
|
|
|
|
There is an Articles section, where you can submit articles and tips.
Roy.
|
|
|
|
|
Great, I've posted it there now. Didn't know such a section existed in the site
-- modified 24-Jun-13 17:24pm.
|
|
|
|
|
diegosendra wrote: Didn't know such a section existed in the site Strange then that you managed to submit this Tip[^] successfully there, last October!
Use the best guess
|
|
|
|
|
Hi ...
I have a problem to address the stapler of a HP9000 printer. I need a single print job, but the SDK i use produces individual jobs for each format that i use.
I'm already stopped the que with System.Printing, then read the que and trie to get the stream of each job and copy theme into a single job.
Unfortunately, the stream is not readable.
Private Sub CombineJobsInPrintQue(ByVal Printer As String)
Try
Dim serverName As String = Printer.Remove(Printer.LastIndexOf("\"))
Dim printerName As String = Printer.Remove(0, Printer.LastIndexOf("\") + 1)
Dim ps As New PrintServer(serverName, PrintSystemDesiredAccess.AdministrateServer)
Dim queue As New PrintQueue(ps, printerName, PrintSystemDesiredAccess.AdministratePrinter)
Dim Jobs As PrintJobInfoCollection = queue.GetPrintJobInfoCollection
Dim Job As PrintSystemJobInfo
Dim s As PrintSystemJobInfo = queue.AddJob("NewJob")
Dim myStreamWrite As System.IO.Stream = s.JobStream
Dim x As Integer = 0
For Each Job In Jobs
Try
Job.Refresh()
Dim myStreamRead As System.IO.Stream = Job.JobStream
Dim buffer(Job.JobSize) As Byte
myStreamRead.Read(buffer, 0, Job.JobSize)
myStreamWrite.Write(buffer, x, Job.JobSize)
x += Job.JobSize
Job.Cancel()
Catch ex As Exception
ERRlog("CopyJob", ex)
End Try
Next
myStreamWrite.Close()
Catch e As Exception
ERRlog("CombineJobsInPrintQue", e)
End Try
End Sub
Job.JobStream == nothing!
Apparently I can not merge the jobs this way ...
Now I have considered whether there is any other way to combine the jobs from the Que. But reading from the SPL file and then write to the Que does not work, the SPL-File is EMF coded and the data are not in RAW format. The job is generated but always ignored when printing.
Private Sub CombineJobsTest(ByVal Printer As String)
Try
Dim serverName As String = Printer.Remove(Printer.LastIndexOf("\"))
Dim printerName As String = Printer.Remove(0, Printer.LastIndexOf("\") + 1)
Dim ps As New PrintServer(serverName, PrintSystemDesiredAccess.AdministrateServer)
Dim queue As New PrintQueue(ps, printerName, PrintSystemDesiredAccess.AdministratePrinter)
Dim Jobs As PrintJobInfoCollection = queue.GetPrintJobInfoCollection
Dim Job As PrintSystemJobInfo
Dim MyNewJob As PrintSystemJobInfo = queue.AddJob("Neu")
Dim myStreamWrite As System.IO.Stream = MyNewJob.JobStream
Dim myStreamRead As System.IO.Stream = New IO.FileStream("D:\Share\Temp\FP00119.SPL", IO.FileMode.Open)
Dim myStreamRead2 As System.IO.Stream = New IO.FileStream("D:\Share\Temp\FP00120.SPL", IO.FileMode.Open)
Dim length As Integer = myStreamRead.Length + myStreamRead2.Length
Dim buffer(Job.JobSize) As Byte
myStreamRead.Read(buffer, 0, myStreamRead.Length)
myStreamRead2.Read(buffer, myStreamRead.Length, myStreamRead2.Length)
myStreamWrite.Write(buffer, 0, length)
myStreamWrite.Close()
Catch e As Exception
ERRlog("CombineJobsTest", e)
End Try
End Sub
I'm still considering whether you can possibly tell the HP9000 in PCL6 !COLLECT ALL JOBS and then !STAPLE ALL JOBS ... but unfortunately I have not found this anywhere.
Somehow it must be possible to combine the jobs ... I hope someone here has a good idea ...
Thanks and regards
|
|
|
|
|
tolarion wrote: Somehow it must be possible to combine the jobs
I wouldn't expect it to be possible. Think of it this way. Open a command prompt and type COPY WordDoc1.docx + WordDoc2.docx WordDoc.docx , then try and open the WordDoc.docs file. It's not going to work because each doc has it's own header, formatting tables, tail details, checksums, ... which are now no longer valid.
You'd have to read the job streams, interpret the contents of all the jobs, create a new job, reformat the data from all of the jobs you want to combine into a single job with all the data ini the correct places. For example, you can't have the headers from each job spread throughout the target job. The data for each must be combined into a header that is at the beginning of the job and reformatted so that it makes sense.
Oh, and you'd have to put in the details to tell the printer to staple in the correct place and orientation, because I'm guessing that you didn't tell each job to staple.
In short, doing this is a HUGE undertaking.
|
|
|
|
|
The EMF Files are not the same as the WordDoc i think ... it's a kind of Vector Format for each page with BeginPage / EndPage BeginDoc / EndDoc ... so i try to do exactly what you described with the spooling ... and open it with a EMF viewer (Articles/10586/EMF-Printer-Spool-File-Viewer) ... and it worked. The information on page etc are unfortunately lost ... but i give it a hope that it is not so HUGE undertaking as you mid think ?!
Perhaps i need is some kind of EMF reformatting ... getting each single page out of the spool file. Stacking theme again and writing all to the new spool or just print it again ...
The stapling is done by the printer driver ...
But my problem is still: how to get the right spooling filename ! Can not read it from PrintSystemJobInfo ?!
|
|
|
|
|
tolarion wrote: BeginDoc / EndDoc
OK, so is it legal to have more than one pair of Begin/End Doc tags in the same job?
The problem with obtaining the filename is that the Win32 Spooler API doesn't provide it. All you get is a handle to the spool file, which you cannot touch until the job is complete. That's why you only get a Stream object from PrintSystemJobInfo.
|
|
|
|
|
You are right shouldn't be legal, but it works. I certainly will read the files on after the other ... if i get the Filename or a filled JobStream. I only need the Filename because the Stream object is always empty ... regardless of rights and printer type. Could be many files in a spool, so reading each and looking for the jobname would be digging to deep. Anny idea ?!
|
|
|
|
|
The only way to get the filename would be to get the handle that Win32 exposes to you, see the GetSpoolFileHandle[^] function, then enumerate the system handle table, looking for the handle returned, and get the filename. This is a very expensive operation to perform and by the time you get the filename the Spooler may have already closed the file and deleted it.
|
|
|
|
|
Thanks for your help, i will try GetSpoolFileHandle, but i am not sure how to get the system handle table and from there the filename ... and the file will still be there cause i stoped the Queue by using
Dim ps As New PrintServer(serverName, PrintSystemDesiredAccess.AdministrateServer)
Dim queue As New PrintQueue(ps, printerName, PrintSystemDesiredAccess.AdministratePrinter)
queue.Pause()
and after my operations i will start it again
queue.Resume()
Why could it not be all in system.printing ... now i must play around with pinvoke ...
|
|
|
|
|
Enumerating the system file handles requires admin permissions. Unless you're giving your app to a bunch of administrator user account (REALLY bad idea!), you're not going to be able to do this. This is NOT an operation typically done for printing. This is a technique usually used to diagnose system problems.
Quote:
tolarion wrote: and the file will still be there cause i stoped the Queue
Don't count on it.
tolarion wrote: Why could it not be all in system.printing ... now i must play around with pinvoke ...
The filename isn't supplied because Windows doesn't supply any method to get it. It has nothing to do with .NET. .NET is limited by the limitations in Windows.
modified 27-Jun-13 14:05pm.
|
|
|
|
|
Accessing the system spool needs admin permission, so i am already on that way ... not happy with it but we are on a Windows system not on Linux ... so everything needs admin ...
... and ... everything should be in a service, now i realized that printing from a service is *$&*# ... sorry ... but is there a other way ... in some forums they use PDF to print to and then combine the PDF and print it ... some SDKs are available, so there must be a way doing it ... i am not the one who gives up early ... but time is running away
|
|
|
|
|
I give up accessing the spool ... dave is right ... its a waste of time ...
Now i use pdfsharp.com[^]
Printing on a PDF Printer >> Combine the PDF >> Print the combined PDF on the HP9000 ... should be easy ?!
Hope this works out for me ... unfortunately i must use Acrobat Reader for printing ... but therefor it is free !
|
|
|
|
|