[RESOLVED] Wait for remote user input with pagination
Hi All,
I have a project that monitors a 3rd party chat program through API calls for the text from users in the chat room. One of the functions of my program allows a key word search through the database that my vb app is linked to. The problem is, sometimes the results are large enough to overload the input maximum for the chat program. I think its a total of 350 chars or so. And I want to paginate the resulting search responses so that 10 or 15 responses get sent back to the user at a time and that user can decide if they want more or not.
I believe I have several options for this but nothing seems to really work well. I think the easiest way to do this is to just take the first 15 results and send them, then say that there were more but you need to refine your search. Or I could check the record count and if its more than 10 or so send it to a separate process but that still leaves me with the pagination problem. I would like to allow the user to type say a "more" command and they get their responses in groups of 10 or 15 at a time till either they don't want any more responses and type a "stop" command or the resulting searched recordset is eof or a timer has elapsed.
The communication between my app and the chat room is not an issue. That works perfectly fine. The only problem I am running into is breaking up a resulting recordset into chunks that can be held for a specific user for a specific amount of time or the recordset is eof.
I am looking for any suggestions, examples, advice. I have considered sleepex or waitfor but not sure how they would react. I would really like the app active for other users while this search result is held for a specific user.
An adequate answer to that would greatly depend on the implementation of your program.
It is easy to say:
Well, I'd put all lines in a dynamic string array and let the user type "more" to get them in tenth or fifteenth. You keep a counter tracking how many you have already sent.
Or:
Write a class which has such an array to hold the result strings, a counter and a pointer (or reference) to the user who requested that search.
Maybe you need a timer which frequently looks if the user has typed such a request and sends the next 15 lines. It also can destroy this object if the user is through with it.
I can't give more useful suggestion when not knowing more about your program structure, because in the end there lies the crux for a sophisticated implementation of what you want.
Private Sub KeyWordSearch()
Dim tmpStr, SQLStr, sString As String
Dim sPath As String
Dim Counter As Long
Dim iHnd As Long
iHnd = getPalSubForm(WindowClass, Combo1.Text, RoomOutboundTextBoxClass, SendTxtIndex)
iHnd = SendTextHnd
Trigger = "$key"
cmdPos = InStr(1, RichTextBox2.Text, Trigger, vbBinaryCompare)
If cmdPos = 0 Then Exit Sub
cmdVer = Trim$(Right$(RichTextBox2.Text, (Len(RichTextBox2.Text) - cmdPos) - 4))
RichTextBox4.Text = Right(cmdVer, Len(cmdVer))
sString = RichTextBox4.Text
SQLStr = "Select tblBook.*,tblQUOTE.* From tblBook,tblQUOTE Where (tblQUOTE.Quote Like '%" & sString & "%') And tblBook.Book_ID=tblQUOTE.Book_ID Order by tblQUOTE.Book_ID,tblQUOTE.Chapter ASC"
Dim db1 As Connection
Set db1 = New Connection
db1.CursorLocation = adUseClient
sPath = App.Path & "\kjvbible.mdb"
db1.Open "PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=" & sPath
Set adoPrimaryRS1 = New Recordset
Set adoPrimaryRS1 = db1.Execute(SQLStr, , adCmdText)
While Not adoPrimaryRS1.EOF
tmpStr = adoPrimaryRS1!Book_Title & " " & adoPrimaryRS1!Chapter & ":" & adoPrimaryRS1!Verse
adoPrimaryRS1.MoveNext
RichTextBox1.Text = RichTextBox1.Text & tmpStr & " $$ "
Counter = (Counter + 1)
If Counter > 14 Then
GoTo Sendit:
Else
End If
Wend
RTB5 = Text1.Text & " Your Keyword or Phrase Search Results Are: " & Counter & " Verse(s) " & RichTextBox1.Text
RTB5.SelLength = Len(RTB5.Text)
With RTB5
.SelBold = True
.SelFontSize = 8
.SelColor = RGB(101, 0, 192)
End With
Call SendMessageByString(iHnd, WM_SETTEXT, 0&, RTB5)
Call SendMessage(iHnd, WM_KEYDOWN, 13, 0)
' Close RecordSet and Database
adoPrimaryRS1.Close
db1.Close
RTB5 = ""
Trigger = ""
RichTextBox1.Text = ""
RichTextBox2.Text = ""
RichTextBox4.Text = ""
Exit Sub
Sendit:
RTB5 = Text1.Text & " Your Keyword or Phrase Search Results Are: " & Counter & " Verse(s) " & RichTextBox1.Text & " There were more responses, please narrow your search"
RTB5.SelLength = Len(RTB5.Text)
With RTB5
.SelBold = True
.SelFontSize = 8
.SelColor = RGB(101, 0, 192)
End With
Call SendMessageByString(iHnd, WM_SETTEXT, 0&, RTB5)
Call SendMessage(iHnd, WM_KEYDOWN, 13, 0)
' Close RecordSet and Database
adoPrimaryRS1.Close
db1.Close
RTB5 = ""
Trigger = ""
RichTextBox1.Text = ""
RichTextBox2.Text = ""
RichTextBox4.Text = ""
Exit Sub
End Sub
What I have chosen to do so far is to count up and if it exceeds 14 to send it to the bit of code with adds the tag that there were more results and to narrow the search.
Also, as you can see, I use a case statement in my timer event that watches for the particular command and then launches the sub. I can add the $more command to the timer sub. That is not a problem. I also do capture and separate the user name in a different container. Now I just need help getting started with something to utilize everything I do get into some code that will accomplish the idea of being able to allow them to use the more command.
Last edited by intercepter; May 3rd, 2009 at 09:54 AM.
Reason: clarification
Ok.
If this routine is reentered from another users search, it seems the previous results are lost to the user who requested it in the first place.
What I'd do is:
Write a class SearchResult which has a dynamic string array, a counter reflecting the lines already sent.
Declare a public collection where you can store objects of this class.
In your routine, before you get the search results, you create an object of this class and put it into the collection. Then you read all of the results into this object and then you send the first 15 lines.
The object definition (class) will contain a dynamic string array for the results, and a counter.
You need a Private colSearches as New Collection where you store the searchresults.
Code:
'after establishing the database connection
Dim sr as new SearchResult
Set sr = New SearchResult
colSearches.add sr, Username
While Not adoPrimaryRS1.EOF
tmpStr = adoPrimaryRS1!Book_Title & " " & adoPrimaryRS1!Chapter & ":" & adoPrimaryRS1!Verse
sr.Add tmpStr
adoPrimaryRS1.MoveNext
Wend
adding and counting might be handled within the class.
When sending another 15 lines you can do this in a sub
Code:
Sub SendMore(Username as string)
dim i%, r$
for i=1 to 15
a$ = colSearches(Username).NextLine
'then your procedure to send the line to the user
next
End Sub
When all lines were sent, or the user quits it, you simply remove the object from the collection.
Such handling allows to deal with multiple search requests of any number of users without interfering.
If you feel unsure in writing a class with these properties such a SearchResult object should have, I will surely help you with it.
Thanks for your help. I have a feeling that this next bit of code is missing something, but I am not sure what yet.
I have created SearchResult.cls and here is the code:
Code:
Option Explicit
'local variable to hold collection
Private colSearches As New Collection
Public Function Add(colSearchResult As String, Optional sKey As String) As SearchResult
'create a new object
Dim objNewMember As SearchResult
Set objNewMember = New SearchResult
'set the properties passed into the method
objNewMember.colSearchResult = colSearchResult
If Len(sKey) = 0 Then
colSearches.Add objNewMember
Else
colSearches.Add objNewMember, sKey
End If
'return the object created
Set Add = objNewMember
Set objNewMember = Nothing
End Function
Public Property Get Item(vntIndexKey As Variant) As SearchResult
'used when referencing an element in the collection
'vntIndexKey contains either the Index or Key to the collection,
'this is why it is declared as a Variant
'Syntax: Set foo = x.Item(xyz) or Set foo = x.Item(5)
Set Item = colSearches(vntIndexKey)
End Property
Public Property Get Count() As Long
'used when retrieving the number of elements in the
'collection. Syntax: Debug.Print x.Count
Count = colSearches.Count
End Property
Public Sub Remove(vntIndexKey As Variant)
'used when removing an element from the collection
'vntIndexKey contains either the Index or Key, which is why
'it is declared as a Variant
'Syntax: x.Remove(xyz)
colSearches.Remove vntIndexKey
End Sub
Public Property Get NewEnum() As IUnknown
'this property allows you to enumerate
'this collection with the For...Each syntax
Set NewEnum = colSearches.[_NewEnum]
End Property
Private Sub Class_Initialize()
'creates the collection when this class is created
Set colSearches = New Collection
End Sub
Private Sub Class_Terminate()
'destroys collection when this class is terminated
Set colSearches = Nothing
End Sub
I am a bit confused because I thought you set the properties in the class and then call them in the various subs. Also, do I set up something to show the different items for each search sorted by the username?
Hmmmm... hmmm.
No, no. You misunderstood some of my suggestions. The collection of search results may never be part of the class SearchResult at all. It should be defined within your main program.
It is a means of storing many search results ordered by independant users, handled by your program.
The class SearchResult is supposed to be rather simple instead.
Look.
I attached a little demo program, where I defined a class SearchResult like I was proposing it.
It will hold any number of search result strings and return them in any amount of lines as you wish.
To make this demo work, you have to create a file "C:\Test\Test.txt" which contains lots of lines as searchresults, or figure out how to work this example into your own program, because I have no database giving search results.
Clicking the first button will create a dummy search result object in the collection and indicate it in the ListBox.
Clicking on the entry in the ListBox requires a page of search results to be displayed in the left hand TextBox.
Repeatedly clicking (or clicking on the second button) gets the next page of results to be shown.
If you have further problems in understanding, please let me know. I try to explain closer.
Please check the attached sample.
To make it work you create a file "C:\Test\Test.txt", containing more than 30 lines of text, simply.
Thanks again for all your help. I believe I have it now. Here is the code I came up with.
This is the initial keyword search code I modified:
Code:
Dim sr As New SearchResult
Dim a$
UserName = Text1.Text
colSearches.Add sr, UserName
While Not adoPrimaryRS1.EOF
tmpStr = adoPrimaryRS1!Book_Title & " " & adoPrimaryRS1!Chapter & ":" & adoPrimaryRS1!Verse
a$ = tmpStr
sr.Add a$
adoPrimaryRS1.MoveNext
RichTextBox1.Text = RichTextBox1.Text & tmpStr & " $$ "
Wend
Count = sr.LinesCount
If Count > 14 Then
Call SendMore((Text1.Text), 15)
Else
End If
Text1.text holds the UserName
Here is the SendMore Sub:
Code:
Private Sub SendMore(UserName As String, NumLines As Integer)
Dim r$, i%
Dim a$, sr As SearchResult
If UserName = "" Then Exit Sub
On Error GoTo NoResultsForThisUserName
Set sr = colSearches(UserName)
For i = 1 To NumLines
a$ = sr.NextLine
r$ = r$ + a$ + " $$ "
Next
RichTextBox1.Text = r$
NoResultsForThisUserName:
End Sub
And here is my more command:
Code:
Private Sub MoreVer()
Dim iHnd As Long
iHnd = getPalSubForm(WindowClass, Combo1.Text, RoomOutboundTextBoxClass, SendTxtIndex)
iHnd = SendTextHnd
Dim u$
u$ = Text1.Text
If u$ <> "" Then SendMore u$, 15
RTB5 = Text1.Text & " Your Keyword or Phrase Search Results Are: " & Count & " Verse(s) " & RichTextBox1.Text & " Type $more for more results, "
RTB5.SelLength = Len(RTB5.Text)
With RTB5
.SelBold = True
.SelFontSize = 8
.SelColor = RGB(101, 0, 192)
End With
Call SendMessageByString(iHnd, WM_SETTEXT, 0&, RTB5)
Call SendMessage(iHnd, WM_KEYDOWN, 13, 0)
RichTextBox1.Text = ""
End Sub
My only problem is code to remove a usernames search from the collection.
This is what I have tried:
Code:
Private Sub StopVer()
colSearches.Remove (Text1.Text)
set colSearches(UserName) = Nothing
End Sub
I have also tried sr.remove and just set sr(username) = Nothing without success. I get an object error 424 for most of the things I have tried. Or I get object required error 424.
Any ideas?
The other thing I noticed was in your SearchResults.cls, it loops back around on me so I just changed this property:
Code:
Public Property Get NextLine() As String
If LinesSent < LinesCount Then
NextLine = Lines(LinesSent)
LinesSent = LinesSent + 1
LinesLeft = LinesSent - LinesCount
Else
NextLine = "End of Results." + vbCrLf
LinesSent = LinesCount + 1
End If
End Property
The way you had it, started the whole thing over again and caused a loop, so I changed it so that it at least would not start over. I also added a LinesLeft for implantation a bit later when I clean some other things up.
Yep, I see where your coming from. However, I did get it working for now. I have not tested the stop command with more than one search running. But I was able to do this,
Code:
Set colSearches = Nothing
However, I believe this would kill the entire collection, and thus any other users search in the collection would also be lost.
But the value is when I just tried it Val(Text1.Text) is 0 and when I hover over it, the username is in there.
If you .Add something, KeyString
then you can always .Remove KeyString, provided it is the same string.
You must not put the text in brackets, though.
If Text1.Text holds your key string, then simply colSearches.Remove Text1.Text. No brackets.
This must remove the so named item.
Certainly each item has an index, but we don't use it to delete the item, because we have used the Key to identify it.
If Text1.Text were to hold "User1", then a .Remove Val(Text1.Text) will always remove the collections first item colSearches(0). That's not what we want.
You have to understand that the items in a collection can be either addressed by their index, or by their key.
colSearches(0) delivers the first item
colSearches("User1") delivers the item we put in with this name of "User1"
colSearches.Remove "User1" removes the very same
colSearches.Remove Text1.Text removes the item with the key stored in Text1.Text
When running with multiple users, the contents of the collection might hold any number of SearchResults.
Say it contains 5 such items and you .Remove 0, then the first item is deleted and the index of all other items is changed so that we have no gaps in the sequence of indexes.
Therefore we better use the Key to identify items in the collection and do not have to care for the numerical index. That's why the collection git this feature.
Only thing is you cannot have 2 items with the same Key. But that's ok, because one user will only be allowed to have one SearchResult object.
Re: [RESOLVED] Wait for remote user input with pagination
Fine.
And, @David, Lists ARE zero based (starting with element 0), collections, however are NOT. Their first element is col(1).
So in my above explanation about the collection I'd have to correct this, too: colSearches.Remove 0 will give an error index out of range. colSearches.Remove 1 would remove the first item.
* The Best Reasons to Target Windows 8
Learn some of the best reasons why you should seriously consider bringing your Android mobile development expertise to bear on the Windows 8 platform.