Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations derfloh on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Instant Messaging/Chat - a simple how to 1

Status
Not open for further replies.

Imaginecorp

IS-IT--Management
Jan 7, 2007
635
US
Here is how you do it:

NOTE: Unlike other samples, this requires Multiple (no limit) networked computers. It will not work on a single machine.This is based upon the following assumptions:

Your application has an employee table that resides on a server. All employees are assigned a unique number (we call it “ordinal”) and every time you authenticate a user you set a flag in the employee table to .T. (we call it “Loggedon”), You also capture the users Machine network name in the employee table using the SYS(0) function (we call it machine_user). Also when the user logs off your app, you reset “loggedon” to .F. and replace "machine_user" with Spaces. (Blank)

*******************************************
Create a class library call it “olevcx.vcx”
Add the Winsock ActiveX control 6.0 to this class. (We call it "Reg_winsock") The easiest way to do this is open a form, drag the Winsock onto the form, and select the Winsock then do a Save-as-Class to the olevck.vcx. Close the form without saving it.
Add a container and within the container add "reg_winsock" and save the container as “oWinsock”. Reg_winsock will automatically be renamed to Reg_winsock1.

Now let’s start designing the Instant messaging module:

1.This comprises of a single class
2.A single message form
3.An employee finder form. We use it as I do not like to put more than 15 or so rows in a drop down. If you have only a few employees/users use a combo box on the message form.

Some fundamentals:

This module uses the UDP protocol as opposed to the TCP one
The size of the packet sent / received is dependent upon your network, (safe to limit it to 29KB)
There is supposed to be a limitation on multiple sends – all data has to be sent in one “Send” is what I have read. I do not know what this means. If it means you cannot send multiple message one after the other – that’s just not true as we will see.
This module will allow you to select multiple recipients and send the same message to each of them as well

In your startup program:

Set the class library to Olevcx
Create 2 public variables, name them “oWinsock1” and “instantmsg”
Create the oWinsock1 object:
Code:
owinsock1  =  newobject(“owinsock”)

NOTE: You may cut and paste the following except for the class. But be aware this was designed using our class libraries. I recreated the forms in the VFP standard controls by cutting and pasting for this post. I may have not removed some of our codes. The error routine is not included as it is handled by a special class which will not be posted. Our Version has been tested throughly and there is No reason to beleave this will not work properly...

oWinsock class code is:

Code:
**************************************************
*-- Class:        owinsock (c:\intelisys\libraries\olevcx.vcx)
*-- ParentClass:  reg_container (c:\intelisys\libraries\reg_controls.vcx)
*-- BaseClass:    container
*-- Time Stamp:   05/04/09 11:37:11 AM
*
DEFINE CLASS owinsock AS container


	Width = 32
	Height = 32
	Name = "owinsock"


	ADD OBJECT reg_winsock1 AS reg_winsock WITH ;
		Top = 1, ;
		Left = 1, ;
		Height = 100, ;
		Width = 100, ;
		Name = "Reg_winsock1"


	PROCEDURE setremote
		Lparameters pRemotehost
		**** lets clear the remote host otherwise
		**** it throws an error saying invalid arguement. This allows the multiple sends.
                **** This is where I was stuck, took me longer to figuire this out, then to write the whole module

		This.reg_winsock1.RemoteHost = ""
		This.reg_winsock1.remoteport = 0
		********** lets reset it again
		This.reg_winsock1.RemoteHost = pRemotehost
		This.reg_winsock1.remoteport = 5000
	ENDPROC


	PROCEDURE startform
		Lparameters pMessage
		If Vartype(instantmsg) = "O"
			*** if form is open lets not keep calling the init()
			instantmsg.recievemsg(pMessage)
		Else
			Do Form instant_msg With pMessage
		Endif
	ENDPROC


	PROCEDURE transmitmsg
		Lparameters pMessage
		This.reg_winsock1.Object.SendData(pMessage)
	ENDPROC


	PROCEDURE reg_winsock1.DataArrival
		*** ActiveX Control Event ***
		Lparameters bytestotal
		*** lets define the buffer the same size as
		*** the message size in bytes
		cData = Space(bytestotal)
		***************************
		This.Object.GetData(@cData)
		?? Chr(7)
		This.Parent.startform(cData)
	ENDPROC


	PROCEDURE reg_winsock1.Init
		**** lets bind the port so no other service
		**** can broadcast on it
		This.Bind(5000)
		*********************************
	ENDPROC


ENDDEFINE
*
*-- EndDefine: owinsock
**************************************************
The reason for not including Employee.dbf in the DE of the form is its needs to be uptodate on who has logged on/off.

Employee Finder form’s code:
Note: there is a public variable here in the select statement called "user_ordinal". This stores the current users ordinal at start up of our app, This prevents the user from selecting himself to send a message to... You may have to recode.
The INIT() has 2 parameters which our system uses, you may remove them But be sure to change the parameters in the "Message Form reg_button1.Click"

Code:
PUBLIC oempfinder

oempfinder=NEWOBJECT("empfinder")
oempfinder.Show
RETURN


	**************************************************
*-- Form:         empfinder (c:\intelisys\screens\employee_finder.scx)
*-- ParentClass:  form
*-- BaseClass:    form
*-- Time Stamp:   05/04/09 02:22:13 PM
*
DEFINE CLASS empfinder AS form


	Height = 356
	Width = 288
	DoCreate = .T.
	AutoCenter = .T.
	BorderStyle = 2
	Caption = "Users currently logged on..."
	FontName = "MS Sans Serif"
	WindowType = 1
	returnvalue = .F.
	Name = "empfinder"


	ADD OBJECT reg_shape2 AS shape WITH ;
		Top = 8, ;
		Left = 6, ;
		Height = 328, ;
		Width = 276, ;
		BorderStyle = 0, ;
		SpecialEffect = 0, ;
		BackColor = RGB(255,255,255), ;
		Name = "Reg_shape2"


	ADD OBJECT reg_shape1 AS shape WITH ;
		Top = 18, ;
		Left = 16, ;
		Height = 22, ;
		Width = 255, ;
		SpecialEffect = 0, ;
		Name = "Reg_shape1"


	ADD OBJECT reg_listbox1 AS listbox WITH ;
		FontName = "MS Sans Serif", ;
		ColumnCount = 3, ;
		ColumnWidths = "115,112,0", ;
		Height = 289, ;
		ColumnLines = .F., ;
		Left = 16, ;
		Top = 37, ;
		Width = 254, ;
		BorderColor = RGB(224,223,227), ;
		Name = "Reg_listbox1"


	ADD OBJECT reg_label1 AS label WITH ;
		AutoSize = .T., ;
		FontName = "Calibri", ;
		BackStyle = 0, ;
		Caption = "Full Name", ;
		Height = 16, ;
		Left = 23, ;
		Top = 21, ;
		Width = 58, ;
		Name = "Reg_label1"


	ADD OBJECT reg_label2 AS label WITH ;
		AutoSize = .T., ;
		FontName = "Calibri", ;
		BackStyle = 0, ;
		Caption = "Computer Name", ;
		Height = 16, ;
		Left = 146, ;
		Top = 21, ;
		Width = 89, ;
		Name = "Reg_label2"


	ADD OBJECT reg_label4 AS label WITH ;
		AutoSize = .T., ;
		FontName = "Calibri", ;
		BackStyle = 0, ;
		Caption = "Double Click to Select", ;
		Height = 16, ;
		Left = 84, ;
		Top = 340, ;
		Width = 120, ;
		Name = "Reg_label4"


	PROCEDURE Unload
		Return This.returnvalue
	ENDPROC


	PROCEDURE Init
		Lparameters pwhocalled,pdatasessionid
		DoDefault()
		With This
			.AddProperty("whocalled",pwhocalled)
			.DataSessionId = pdatasessionid
			Select employee.ordinal,employee.full_name,employee.machine_user ;
				FROM required!employee ;
				WITH (Buffering = .T. );
				WHERE ((employee.loggedon = .T.);
				AND employee.ordinal <> user_ordinal ;
				AND Not Deleted("employee")) ;
				ORDER By employee.full_name ;
				INTO Cursor empcursor
			Select empcursor
			If Reccount("empcursor") < 1
				=Messagebox(mappname+" did not find any employees who are logged on at this time."+Chr(13)+;
					"Please try again later...",64,its_logo)
				Use In employee
				Return .F.
			Endif
			With .reg_listbox1
				Scan
					.AddItem(empcursor.full_name)
					.List(.NewIndex,2) = ;
						PROPER(Substr(empcursor.machine_user,1,At("#",empcursor.machine_user)-1))
					.List(.NewIndex,3) = empcursor.ordinal
				Endscan
				.Value = .TopIndex
			Endwith
		Endwith
		If Select("employee") > 0
			Use In employee
		Endif
	ENDPROC


	PROCEDURE reg_listbox1.DblClick
		Thisform.returnvalue = This.List(This.ListIndex,3)
		Thisform.Release
	ENDPROC


ENDDEFINE
*
*-- EndDefine: empfinder
**************************************************

Message Forms code:

Code:
PUBLIC oinstmsg

oinstmsg=NEWOBJECT("instmsg")
oinstmsg.Show
RETURN


	**************************************************
*-- Form:         instmsg (c:\intelisys\screens\instant_msg.scx)
*-- ParentClass:  form
*-- BaseClass:    form
*-- Time Stamp:   05/04/09 02:52:05 PM
*
DEFINE CLASS instmsg AS form


	DataSession = 2
	Height = 400
	Width = 371
	DoCreate = .T.
	AutoCenter = .T.
	BorderStyle = 2
	Caption = "Instant Messaging Service..."
	FontName = "MS Sans Serif"
	AlwaysOnTop = .T.
	myname = ""
	Name = "instmsg"
	DIMENSION machinearray[1,4]


	ADD OBJECT reg_shape1 AS shape WITH ;
		Top = 5, ;
		Left = 5, ;
		Height = 356, ;
		Width = 360, ;
		BorderStyle = 0, ;
		SpecialEffect = 0, ;
		BackColor = RGB(255,255,255), ;
		Name = "Reg_shape1"


	ADD OBJECT reg_button1 AS commandbutton WITH ;
		Top = 26, ;
		Left = 14, ;
		Height = 23, ;
		Width = 89, ;
		FontName = "Calibri", ;
		Caption = "\<Message To:", ;
		Name = "Reg_button1"


	ADD OBJECT reg_editbox3 AS editbox WITH ;
		FontName = "MS Sans Serif", ;
		BorderStyle = 0, ;
		Height = 43, ;
		Left = 108, ;
		ReadOnly = .T., ;
		TabStop = .F., ;
		Top = 15, ;
		Width = 244, ;
		Name = "Reg_editbox3"


	ADD OBJECT reg_line1 AS line WITH ;
		Height = 0, ;
		Left = 74, ;
		Top = 72, ;
		Width = 279, ;
		BorderColor = RGB(64,128,128), ;
		Name = "Reg_line1"


	ADD OBJECT reg_editbox1 AS editbox WITH ;
		FontName = "MS Sans Serif", ;
		BorderStyle = 0, ;
		Height = 186, ;
		Left = 18, ;
		ReadOnly = .T., ;
		SpecialEffect = 1, ;
		TabStop = .F., ;
		Top = 89, ;
		Width = 335, ;
		Name = "Reg_editbox1"


	ADD OBJECT reg_label4 AS label WITH ;
		AutoSize = .T., ;
		FontName = "Calibri", ;
		BackStyle = 0, ;
		Caption = "Type Message here:", ;
		Height = 16, ;
		Left = 21, ;
		Top = 286, ;
		Width = 110, ;
		Name = "Reg_label4"


	ADD OBJECT reg_line2 AS line WITH ;
		Height = 0, ;
		Left = 138, ;
		Top = 294, ;
		Width = 215, ;
		BorderColor = RGB(64,128,128), ;
		Name = "Reg_line2"


	ADD OBJECT cmdclose1 AS commandbutton WITH ;
		Top = 371, ;
		Left = 250, ;
		Height = 24, ;
		Width = 55, ;
		FontName = "MS Sans Serif", ;
		Caption = "\<Send", ;
		Name = "Cmdclose1"


	ADD OBJECT cmdcancel1 AS commandbutton WITH ;
		Top = 371, ;
		Left = 306, ;
		Height = 24, ;
		Width = 55, ;
		Caption = "Ca\<ncel", ;
		Name = "Cmdcancel1"


	ADD OBJECT reg_editbox2 AS editbox WITH ;
		FontName = "MS Sans Serif", ;
		Height = 45, ;
		Left = 18, ;
		SpecialEffect = 1, ;
		Top = 304, ;
		Width = 335, ;
		BorderColor = RGB(128,128,128), ;
		Name = "Reg_editbox2"


	PROCEDURE Init
		Lparameters pText
		DoDefault()
		*!*	InstantMsg is a public variable declared in its.prg
		InstantMsg = This
		*!*	lets check for winsock1. This is redundant but lets do it anyway
		If Vartype(oWinsock1) # "O"
			=Messagebox("Communication is disabled at this time...",64,its_logo)
			Return .F.
		Endif
		If Vartype(pText) # "L"
			This.recievemsg(pText)
		Endif
		This.reg_button1.SetFocus()
	ENDPROC


	PROCEDURE recievemsg
		Lparameters pMesg
		**** scroll the edit box after adding the message
		With This.reg_editbox1
			cRText = Alltrim(.Value)
			cRText = cRText + Iif(Empty(cRText),"",Chr(13))+pMesg
			nRinsertPoint = Len(cRText)+2
			.Value = cRText
			.SelStart = nRinsertPoint
			.SetFocus()
		Endwith
		*******************************
		This.reg_editbox2.SetFocus()
	ENDPROC


	PROCEDURE reg_button1.Click
		Local cEmp
		Do Form employee_finder With Thisform,Thisform.DataSessionId To cEmp
		If Empty(cEmp)
			Return
		Endif
		Store "" To cName,cPhone,cText3,cMachine
		Store 0 To nLen
		Select 0
		Use required!employee Again Alias employee1
		Set Order To Tag ordinal
		=Indexseek(user_ordinal,.T.)
		Thisform.myname = Alltrim(employee1.full_name)
		Go Top
		If Indexseek(cEmp,.T.)
			With Thisform
				cName = Alltrim(employee1.full_name)
				cPhone = employee1.phone
				cText3 = Alltrim(.reg_editbox3.Value)
				cText3 = cText3 + Iif(Empty(cText3),""," ; ")+cName
				.reg_editbox3.Value = cText3
				cMachine = ;
					SUBSTR(employee1.machine_user,1,Atc("#",employee1.machine_user)-1)
				nLen = Alen(.machineArray,1)
				.machineArray[nlen,1] = Alltrim(cMachine)
				.machineArray[nlen,2] = cName
				.machineArray[nlen,3] = cEmp
				.machineArray[nlen,4] = cPhone
				Dimension .machineArray[nlen+1,4]
			Endwith
		Endif
		Use In employee1
	ENDPROC


	PROCEDURE cmdclose1.Click
		With Thisform
			If Empty(Alltrim(.reg_editbox3.Value))
				=Messagebox("Please select the message Recipient first...",64,its_logo)
				.reg_button1.SetFocus()
				Return
			Endif
			If Empty(.reg_editbox2.Value)
				=Messagebox("Please type in a message first...",64,its_logo)
				.reg_editbox2.SetFocus()
				Return
			Endif
			If Vartype(oWinsock1) # "O"
				=Messagebox("Communication seems to be disabled at this time."+Chr(13)+;
					"Please try again later...",64,its_logo)
				Return
			Endif
			Select 0
			Use required!employee Shared Again Alias employee1
			Set Order To Tag ordinal
			Store "" To cSendText
			cSendText = Alltrim(Thisform.myname)+":--> "+Alltrim(.reg_editbox2.Value)
			Store "" To .reg_editbox2.Value

			cText = Alltrim(.reg_editbox1.Value)
			cText = cText + Iif(Empty(cText),"",Chr(13))+cSendText
			**** scroll the edit box
			nInsertPoint = Len(cText)+2
			With .reg_editbox1
				.Value = cText
				.SelStart = nInsertPoint
				.SetFocus()
			Endwith
			Select employee1
			For x = 1 To Alen(.machinearray,1)
				If !Empty(.machinearray[x,1])
					**** lets check again if this employee
					**** is still logged on
					If Indexseek(.machinearray[x,3])
						Indexseek(.machinearray[x,3],.T.)
						If employee1.loggedon And !Empty(employee1.machine_user)
							cRemoteMachine = Alltrim(.machinearray[x,1])
							oWinsock1.setremote(cRemoteMachine)
							oWinsock1.transmitmsg(cSendText)
						Else
							cEmpName = .machinearray[x,2]
							cEmpPhone = Transform(.machinearray[x,4], "@R (###)###-####")
							=Messagebox(cEmpName+" seems to have logged off "+mappname+Chr(13)+;
								"Your message cannot be sent"+Chr(13)+;
								"Here is "+cEmpName+"'s Telephone # "+cEmpPhone,64,its_logo)
						Endif
					Endif
				Endif
			Endfor
			.Refresh
			.reg_editbox2.SetFocus()
			Use In employee1
		Endwith
	ENDPROC


	PROCEDURE cmdcancel1.Click
		Store "" To Thisform.reg_editbox2.Value ,;
			Thisform.reg_editbox3.Value
		Dimension Thisform.machineArray[1,4]
		Store .F. To Thisform.machineArray
		Thisform.reg_button1.SetFocus()
	ENDPROC


ENDDEFINE
*
*-- EndDefine: instmsg
**************************************************

Thats it...
 
Sorry: Change "its_logo" and "mappname" to whatever you want, these are our variables...
 
One more thing: Multiple (limited only by the power of your PC) instances of the message form can be opened from the main menu and messages sent from each of them to seperate recipients or to the same one (drive the person crazy). This way it is a little less chaotic...
 
Thank You...
 
Thanks for your work.

You might want to consider putting this into one of the VFP FAQ's so that it stays put and is easy to find instead of it rotating onto 'deeper' and 'deeper' posting pages as newer postings are added to the top of this forum.

Thanks,
JRB-Bldr
 
The problem I see is setting a logical field to .T. when someone logs on. If they crash, the flag stays set and and admin has to go in and reset the flag. Would a better method be to create a text file on the server and leave it open with exclusive access. Then record the the filename in the user table. When the user logs out normally, you can close the text file and blank out the field. If they crash, the file lock will be released and then replaced with a new one when they logon the next time.

Craig Berntson
MCSD, Visual FoxPro MVP,
 
Our applications do Not crash :}
Have been doing this for the last 8 years without a single reported incident. The Employee file sits on a central server with the Exe's on local machines.
In the event of a server crash, a logical flag, which merely identifies whether a user is logged on or off is Absolutely the least of the client’s problems… Wouldnt you agree?
 
App doesn't crash .. ummm.... ok, if you say so.

Talk to anyone that used SBT about clearing the logged-on flag. It really is a pain for an admin. Just because no one complained, doesn't mean customers don't like something.

Craig Berntson
MCSD, Visual FoxPro MVP,
 
No seriously, our applications do not crash, they recover gracefully.
The “loggedon” flag is there only to initiate a communication with the machine, if the communication cannot be established the initiator is notified and the routine is cancelled. Ours is by no means, some simplistic shrink wrap system, it has been designed with every known possibility taken into account, even to the extent of sending us an email automatically if a coding error has been detected.
In the event of a workstation crash, well, the admin has to bring it back up… That’s what he is being paid for. Resetting a flag is then again the least of his problems and if this is a headache, maybe he should rethink his career choice.
I don’t see what SBT has to do with our software… Maybe it is a piece of crap, V5 was.

 

>>>I don't see what SBT has to do with our software... Maybe it is a piece of crap, V5 was.

SBT was a fine piece of software written in Foxpro. I think Craig was referring to how the flags get cleared.

>>>>No seriously, our applications do not crash, they recover gracefully.

So power failiures never happen?




Mike Gagnon

If you want to get the best response to a question, please check out FAQ184-2483 first.
ReFox XI (www.mcrgsoftware.com)
 
There is little gizmo that businesses invest in, it’s called a UPS which you plug your computer to... which among its many function during a power failure runs a shutdown routine in our app...
A power failure work station crash has absolutely nothing to do with the application getting corrupted as the tables, the ones susceptible to corruption, reside on a server, and anybody with half a brain would insure that the server is on a heavy duty UPS. In the event there is no UPS on the work station, the power comes back on, our application recovers gracefully…the user logs on and the flag is overwritten to .T.
I somehow fail to see the gravity of the situation in using a logical field as opposed to using text files etc etc… I firmly believe that an application in good old fashioned solid native FoxPro code will run forever without failing…
 

I do want to argue your point, if you say "My applications never crashes" then I'll leave it at that. BTW I have had power surges that blew out UPS and the application did crash. I can see you have been lucky so far, but the point was to suggest you reconsider your logic of programming that feature "in case your application does crash".

Mike Gagnon

If you want to get the best response to a question, please check out FAQ184-2483 first.
ReFox XI (www.mcrgsoftware.com)
 
I do "Not" want to argue your point...

Thank you Mike...

...power surges that blew out UPS...

That, my friend, is a doomsday scenario and no "logic of programming" will save you...
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top