Chat window with Gtk+ - gtk

I'm writing some sort of chat in Ada using Gtk+ (technically GtkAda). And I have of problem with some Gtk. My window consists of an Entry, TextView and Button ("Send").
The hard part is in handler On_Button_Send_Clicked (procedure that deals with signal 'clicked' on button). I want to read text form Entry and place it in TextView, but how can I access TextView and Entry from a procedure that has only access to Button, as I connect the signal with a handler in this way:
package Handlers is new Gtk.Handlers.Callback
(Widget_Type => Gtk_Widget_Record);
procedure On_Button_Send_Clicked
(Object : access Gtk_Widget_Record'Class);
...
Handlers.Connect
(Button, "clicked", Handlers.To_Marshaller (On_Button_Send_Clicked'access);
My question is: are there any methods like Get_Gtk_Entry or Get_Text_View, which would be the simples way? Or is there another way, but still simple?
I have also come across a solution in which I declare a record:
type Widget_Collection_Record is new Glib.Object.GObject_Record with record
Terminal : Gtk.GEntry.Gtk_Entry;
Text_Field : Gtk.Text_View.Gtk_Text_View;
end record;
and make the callback this way:
package Widget_Collection_Cb is new Gtk.Handlers.Callback
(Widget_Type => Widget_Collection_Record);
procedure On_Button_Send_Clicked
(Object : access Widget_Collection_Record'Class);
But now I have another question: how do I connect a signal from a Button with a handler, since the widget Button is not a part of my Widget_Collection_Record?
I'm not sure whether I sound clear...
So please, if you know something that may solve my problem, please post - it could be C, C++, Python - I'll try to convert it to Ada ;D
And the summary of my problem is:
How can I write a handler to read from an Entry and write on a Text_View when a Button clicked?
Edit: Question closed. I'm aware that it's not clear what I asked for, and that's way I've chosen the way to pass record of User_Data to callback... and now my new problem is here

Usually I use this reference : http://www.univ-orleans.fr/sciences/info/ressources/webada/doc/gtkada/gtkada_rm/index.html
You didn't provide much informations about the organization of your project.
But if you have a simple procedure where you declare everything then :
procedure foo is
-- variables
E : GTk_GEntry;
T : Gtk_Text_View;
...
procedure On_Button_Send_Clicked (Object : access Gtk_Widget_Record'Class) is
begin
S : String := Get_Text (E);
B : Gtk_Text_Buffer := Get_Buffer (T);
begin
Set_Text (B, S);
...
end On_Button_Send_Clicked;
begin
...
Handlers.Connect
(Button, "clicked", Handlers.To_Marshaller (On_Button_Send_Clicked'access);
...
end foo

Related

Access object on another form with the form as a variable

I'm writing a program in Delphi which includes creating the same dynamic object on multiple forms (never simultaneously), and then a procedure in another unit writes certain text to it.
How the object (TMemo) is created:
memHulp := TMemo.Create(frmHome);
with memHulp do
begin
Parent := frmHome;
Top := 208;
Left := 88;
Height := 98;
Width := 209;
ReadOnly := True;
end;
The properties aren't that important, it's just to show the creation of the object and how it is referred to.
Now, I need to read certain text into the memo from a text file, which there is no problem with, but the problem comes when there are different forms involved that all use that same self-defined procedure.
It's easy to say frmHome.memHulp.Lines.Add() in this particular case, but when I need it to display the text on the memo named exactly the same in all cases, but on a different form, I'm having some trouble.
The frmHome part needs to be a variable. So I tried this:
var
Form: TForm;
begin
Form := Application.FindComponent('frmHome') as TForm;
end;
That doesn't warn me or give an error, but as soon as I try to say Form.memHulp.Lines.Add(), it does not work, and I understand that it probably doesn't have any properties for Form, but how do I make it look at the correct place? I need to be able to tell the program to look on whichever form name I pass as a parameter into the FindComponent() part.
If this is completely not possible, please suggest other solutions to achieve the same.
Form.memHulp doesn't work because Form is a plain vanilla TForm pointer, and TForm doesn't have a memHulp member. You could use Form.FindComponent('memHulp') instead, since you are assigning the TForm object as the Memo's Owner, but that would require you to assign a Name to the Memo, eg:
memHulp := TMemo.Create(frmHome);
with memHulp do
begin
Parent := frmHome;
Name := 'memHulp';
...
end;
Alternatively, since you say you are creating only 1 Memo object at a time, you could simply make memHulp be a global variable in some unit's interface section, and then you would have direct access to it without having to hunt for it.

How to explicitly save or close a recordset when a form deactivates?

I apologize if this is too vague a question, but we're having the following problem:
We use a search form to find a record, then load it in a bound-control form where changes are made. Then we return to the search form and open another record. When we do that, the form's BeforeUpdate property fires a 3020 error, "Update without Add New or Edit" and stepping through the code it's referring to the FIRST opened record. This is strange because there is no explicit update call, but after much trial and error I think the error is thus:
Record #1 is opened via the form and changes are made. Record #2 is opened on that same form without closing the first recordset. Even though we now re-opened the form with the second record, Access still assumes we're editing record 1, i.e. that we're trying to edit 2 records concurrently. Same as though we had a datasheet form and we edited one row and then tried to edit a second row without saving the first. What I want to be able to do is have it automatically do an update on the first record when the form deactivates so loading a new record doesn't cause this conflict.
So the bottom line is this: **Is there a way, on say the form's Dirty or Deactivate event, that we can force the form's recordset to update and close ** before loading a second record?
I hope I made this clear enough, it's a complex problem, so any small guidance would help. Btw, you may ask, "Why are you running the same code to open the same form twice?" Good question! Hey it's an old and badly written app (the thing has GoSubs in it for Pete's sake) but I have no choice but to make the best of a bad situation.
EDIT: I was asked to post code, which is reasonable, but it's in several different places. So I have the data form, it has a "Search" button to go back to the search form for opening another record. The search button on the data entry form is:
Private Sub CommandSearch_Click()
On Error GoTo Err_CommandSearch_Click
DoCmd.OpenForm "Reference Form", acNormal 'This is the form that does the searching
Exit_CommandSearch_Click:
Exit Sub
Err_CommandSearch_Click:
MsgBox Err.Description
Resume Exit_CommandSearch_Click
End Sub
When a record is selected on that search form, then the new form is opened with this code. Now this is where it gets tricky. I'm not the original programmer, as I said I think it was written in Access 97 by someone after taking an hour to read "Access for Dummies" :). But it always looks like only one copy of the form is open, so maybe it's re-opening it?
Public Sub CommandLoadCase_Click()
Dim LoadUTUCaseNumber As String, lengthUTUCaseNumber As Integer
lengthUTUCaseNumber = InStr(Forms![Reference Form]![Reference Query SubForm]![UTU Case Number], "-") - 1
If (lengthUTUCaseNumber = 0) Then
LoadUTUCaseNumber = ""
Else
LoadUTUCaseNumber = Left$(Forms![Reference Form]![Reference Query SubForm]![UTU Case Number], lengthUTUCaseNumber)
End If
On Error Resume Next
Forms![Case Appeal_Add-On Form].UTUCaseKeyToFind = LoadUTUCaseNumber
DoCmd.OpenForm "Case Appeal_Add-On Form", acNormal, , , , , LoadUTUCaseNumber
'Case Appeal Add On Form referred to here is the data entry form I refer to above.
End Sub
Finally, the Error 3020 (Update without Add/Edit) is occurring after it executes this line on the data entry form. (I know the code is complicated which is why I didn't enter it at first).
Private Sub Form_BeforeUpdate(Cancel As Integer)
[UTU Claim Sequence Key] = [UTU Claim Alpha Sequence] & CStr([UTU Claim Numeric Sequence])
End Sub

Access 2007 form: event AFTER undo

I have a form in Access 2007, which has an "update" routine, that enables or disables certain textboxes based on values in other fields (textboxes, checkboxes, comboboxes). The regular operation of that routine works well.
Now I found that pressing ESC calls the undo function, that restores the original values in all fields. But this undo does not call the events on those fields, so the form is in a wrong state, where textboxes are disabled/enabled although they shouldn't.
I also found that there is an undo-event, but that is useless for me because it is called before undo. I need an event after undo. What can I do here to update the fields when ESC is pressed?
I like this solution more, because it works not only on the "ESC"-Key:
private Sub form_Undo(cancel as integer)
afterUndo = true
TimerInterval = 1
end Sub
private Sub Form_Timer()
if afterUndo then
'do something after the Undo-Event
end if
TimerInterval = 0
end Sub
Well, like many times before I have an idea for a solution after postion the question.
The solution here is enabling KeyPreview on the form and using the KeyUp event. The undo is called on KeyDown, so when KeyUp is raised, the form already has the restored values again and the update routine works.
Another solution to this problem is to use each control's OldValue property. Through experimentation, I've found that the three different value properties of controls come into play in different situations:
Control.Value or simply Control
The control's current value most of the time
When the control has focus, it is the value the control had before gaining focus
During a Form.Undo event, it is the value the control prior to the Undo
Relevant during the Control.AfterUpdate and Control.Undo events
Control.Text
The control's value while it has focus
Relevant during events that occur as the user types, such as Control.Change, Control.KeyUp, Control.KeyDown
Control.OldValue
The value the control had when the current record was first opened
Also the value that a form-level Undo will reset the control to
Relevant during the Form.Undo event
The timer-based answer to this question is my go-to solution, but if you're already handling events as the user types (e.g., for live validation), then code like this can be sensible:
Private Sub LastName_Change()
ValidateLastName SourceProperty:="Text"
End Sub
Private Sub LastName_Undo(Cancel As Integer)
ValidateLastName SourceProperty:="Value"
End Sub
Private Sub Form_Undo(Cancel As Integer)
ValidateLastName SourceProperty:="OldValue"
End Sub
Private Sub ValidateLastName(SourceProperty As Variant)
Dim LastName As String
Select Case SourceProperty
Case "LastName"
LastName = Nz(Me.LastName.Text, "")
Case "Value"
LastName = Nz(Me.LastName.Value, "")
Case "OldValue"
LastName = Nz(Me.LastName.OldValue, "")
Case Else
Debug.Print "Invalid case in ValidateLastName"
Exit Sub
End Select
' <Do something to validate LastName>
End Sub
Note that this method does not get you access to the post-Form-Undo value of Control.Column(x) of combo/list boxes. You can get it by using DLOOKUP on Control.OldValue, but you're probably better off just using the timer-based solution instead.

How to send event to multiple forms in Delphi

In my application I have multiple forms that can be visible at the same time and they all show disk space (files, hard disk size etc.) in the same size units. So all of them show disk space in Bytes, KB, MB, GB or TB. I also have a seperate settings form in which the user can change the display size, he wants in the other forms. Once the user clicks OK in the settings form, I want all the other (open) forms to immediately change their size settings.
Every form has a protected procedure SetViewSettings, which takes care of the job. They are all descendants of an ancestor form which defines SetViewSettings as virtual and abstract. The actual displayed forms override the SetViewSettings method of the ancestor. So far no problems.
Because I don't want to call every individual form (FormX.SetViewSetttings, FormY.SetViewSettings, etc.), I am using the following solution:
procedure TApplicationForms.SetUnits;
var
I: Integer;
begin
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I] is TfrAncestorInfo then
with Screen.Forms[I] as TfrAncestorInfo do
acSetUnits.Execute;
end;
This procedure is called from the SettingsForm as the user clicks OK.
TFrAncestorInfo is a descendant of TForm, declaring the SetViewSettings method as virtual and abstract. acSetUnits is an Action, declared in TfrAncestorInfo, which only calls SetViewSettings. This all works fine, but the risk lies in creating a new descendant form of TFrAncestorInfo, whilest forgetting to override the SetViewSettings method, in which case you will run into an 'Abstract Error' exception.
Are there any alternatives to calling the SetViewSettings method in the forms, without listing (calling) all the descendant forms individually? I know of messages and events, but I don't know how to use these in a multiple forms situation. In general: how can I directly send a message to or generate an event for all TFrAncestorInfo descendant forms, without listing them individually?
One option (there are many other possible options) would be to send a custom message to every Form. No need for worry about virtual/abstract overriding, type checking, etc. Only the Forms that implement a message handler will react to the message, the rest will simply ignore it.
const
WM_SETTINGS_UPDATED = WM_APP + 1;
procedure TApplicationForms.SetUnits;
var
I: Integer;
begin
for I := 0 to Screen.FormCount - 1 do
Screen.Forms[I].Perform(WM_SETTINGS_UPDATED, 0, 0);
end;
type
TSomeForm = class(TBaseForm)
private
procedure WMSettingsUpdated(var Message: TMessage); message WM_SETTINGS_UPDATED;
protected
procedure SetViewSettings;
end;
procedure TSomeForm.WMSettingsUpdated(var Message: TMessage);
begin
SetViewSettings;
end;
procedure TSomeForm.SetViewSettings;
begin
//...
end;

Error 1004 - Vlookup in vba - Unable to get the Vlookup property of the WorksheetFunction class

I've browsed the various questions already asked with this issue other users have faced and none of the solutions seem to fix the error code coming up.
I have a form which prompts the user for a reference number - they input this into the text field and then press OK
'OK button from form1
Public Sub CommandButton1_Click()
refInput = refTextBox.Value
InputRef.Hide
ExportShipForm.Show
End Sub
Once this has been pressed, the next form appears which I would like to be populated with data based on the reference number input on the first form. I have an update button which will update the "labels" on the form to show the data - this is where I am getting an error.
The first label to update is through a Vlookup:
Below the users clicks the update button the 2nd form:
Public Sub btnUpdate_Click()
Call ICS_Caption
lbl_ICS.Caption = Label_ICS
End Sub
This calls a function below:
Public Sub ICS_Caption()
Dim ws1 As Worksheet
refInput = InputRef.refTextBox.Value
Set ws1 = Worksheets("MACRO")
dataRef = Worksheets("Shipping Data").Range("A:K")
Label_ICS = WorksheetFunction.VLookup(refInput, dataRef, 7, False)
End Sub
The error continues to come up each time - I have ran the vlookup manually in a cell outside of VBA and it works fine.
I have typed the range in the Vlookup whilst also using named ranges but each variation shows the same error.
Eventually, I would want the label on form 2 to update with the result of the Vlookup.
Any ideas?
You need to Dim dataRef as Range and then Set it.
See code Below:
Dim DataRef as Range
Set dataRef = Worksheets("Shipping Data").Range("A:K")
Just like a Workbook or Worksheet you need to Set the Range
Just as Grade 'Eh' Bacon suggest in comments its always best to Dim every reference.
The best way to do so is to put Option Explicit all the way at the top of your code. This forces you to define everything which helps it preventing mistakes/typo's etc.
Update edit:
The problem was you are looking for a Reference number in your Sheet (thus an Integer) but refInput is set as a String this conflicts and .VLookup throws an error because it can't find a match.
I have reworked your code:
Your sub is now a function which returns the .Caption String
Function ICS_Caption(refInput As Integer)
Dim dataRef As Range
Set dataRef = Worksheets("Shipping Data").Range("A:K")
ICS_Caption = WorksheetFunction.VLookup(refInput, dataRef, 7, False)
End Function
The update Button calls your Function and provides the data:
Public Sub btnUpdate_Click()
lbl_ICS.Caption = ICS_Caption(InputRef.refTextBox.Value)
End Sub
By using a Function you can provide the Integer value and get a return value back without the need of having Global Strings or Integers.
Which would have been your next obstacle as you can only transfer Variables between Modules/Userforms by using a Global Variable.
I would even advice to directly use the function in the Initialize Event of your 2nd Userform to load the data before the Userform shows this is more user friendly then needing to provide data and then still needing to push an update button.
Verify that you have no missing libraries in VBA IDE > Tools > References
Try using a worksheet cell as the place to store and retrieve refTextBox.Value, rather than refInput (which I assume is a global variable):
Public Sub CommandButton1_Click()
...
Worksheets("Shipping Data").Range($M$1).Value=refTextBox.Value
End Sub
Public Sub ICS_Caption()
Dim refInput as Long'My assumption
...
refInput=Worksheets("Shipping Data").Range($M$1).Value
...
End Sub
Make sure you have Option Explicit at the top of all of your code windows.