I guess I'll start by saying I am very new to B4A, and to programming in general. I have some very basic java and html exp. but that's it. I do not have any basic4ppc or really any IDE experience. Been using B4A for a few days now and can't get over the hump. Here are my noob questions:
Does having many activities (20-30+) slow down the app? Is there a downside to having a lot of activities?
I can't figure out how to scroll in the designer. I am trying to make a screen that has 25 buttons down in 1 column. However I can't scroll down to add more buttons below. I am able to add buttons programmically and in the fashion that I want (using a for loop), but is it normal to create views at runtime like this?
How do you ensure your app looks the same across all devices? Tablets? I have a scroll view that fits perfect in the emulator, but on my phone (droid x), the bottom of the scroll view is not stretched to the bottom of the phone. I use the code: scvScreen1.Initialize(100%y). Is that not right?
I have a Email screen in which is comprised of an edittext and a Send button, so that the users can send me questions from the app. However the Send button gives me this error on the 'URI =' line: "LastException java.lang.NumberFormatException: mailto:" here is the code:
Sub btnSendEmail_Click
Dim Uri As String
Uri="mailto:me#gmail.com?subject=Test Email&body=" + edtHelpEmail.Text
Dim Intent1 As Intent
Intent1.Initialize(Intent1.ACTION_VIEW,Uri
StartActivity(Intent1)
End Sub
Or is there another way to open the device's default email program?
Regarding last question, how do I copy error messages to clipboard?? I selected the red error message on the bottom right of the IDE and tried ctrl-c, but didn't work.
In B4A, what is a good method of storing persistent data? All I really need to store are some strings. Nothing fancy. These strings are to be stored locally. AI made this easy by using TinyDB.
When using the designer, how do you ensure your views are centered on all devices? For instance, I have a screen that has several rows made up of: (label, edittext, label). And I want each row to be center aligned. Do I do this programmically? I'm thinking I would have to append each row of (label, edittext, label) to a panel, then in the code center the panel. Is this correct?
That's all I got for now, but I'm sure there will be plenty more questions later.
1) The whole idea of android is to small components i.e. Apps working together, so no need to worry about opening lots of activities. Memory is very well managed behind the scenes in Android.
2) Sure. That sounds fine to me. Use the Layout designer as much as you can and then add the dynamic stuff later. It's all about striking a balance between the size of your code and the number of activities.
3) In the Designer there's an option called 'Send to UI Cloud'. This compares your app over multiple screen sizes. You can also scale your design and programmatically resize specific controls within your app in the Activity_Create Lifecycle
4) What you're doing is almost correct. I corrected your code:
Sub MailTo(StrAddress As String, StrSubject As String, StrBody As String)
Dim StrMethod As String = "Sub MailTo(StrAddress As String, StrSubject As String, StrBody As String)"
Try
Dim StrUri As String
StrUri = "mailto:" & StrAddress & "?subject=" & StrSubject & "&body=" & StrBody
Dim Intent As Intent
Intent.Initialize(Intent.ACTION_VIEW, StrUri)
StartActivity(Intent)
Catch
If BlnLoudExceptions Then CdException.Show(StrClass, StrMethod, LastException)
End Try
End Sub
I tend to have a code module called CdIntent.bas for these functions as it both keeps the project organised and makes it easier to implement the same functionality across projects.
Then to call you would use
CdIntent.MailTo("me#yes.no", "Subject!", "Body!")
5) I have a file called CdException.bas
Sub Process_Globals
'These global variables will be declared once when the application starts.
'These variables can be accessed from all modules.
End Sub
Sub Show(StrClass As String, StrMethod As String, Ex As Exception)
LogColor("Exception: " & Ex.Message & " - Class: " & StrClass & " - Method: " & StrMethod, Colors.Magenta)
End Sub
and then wrap functions in the following way:
Sub FunctionName(...Parameters...) as Int
Dim StrMethod As String = "Sub Sleep(LngMilliseconds As Long)"
Dim IntResult As Int = 0
Try
[code here inc. IntResult = ???]
Catch
If BlnLoudExceptions Then CdException.Show(StrClass, StrMethod, LastException)
End Try
Return IntResult
End Sub
BlnLoudExceptions is a global boolean that you'd declare in
Process_Globals that you can switch on an off exception logs.
StrClass is a global String that you'd declare in Process_Globals
that contains the name of the class e.g. "CdIntent.bas"
The exceptions then appear in magenta in the log screen along with the method name and class in which they occurred allowing you to home in on them.
6) I have a table in an SQLLite database called TabletSettings, which has two TEXT colums called 'Name' and 'Value'. It works well and gets you into a (what I think is a) good habit of always having a database available to your app from the get-go.
7) I'll get back to you on this as I haven't done this before.
Until then, the following thread will help you in the B4A forum http://www.basic4ppc.com/android/forum/threads/convert-integer-to-dip.18800/
I agree with Jim's point but will attempt to answer 1.
I'm new to android myself but as I understand it activities on the whole are only running when active. Unless you are using the app to continuously do something there is only one activity at a time. The number of activities is likely to affect the ram available more than anything. Lastly it might be worth walking first rather than running so to speak but trying a single and then add multiple activities.
You could try adding a ListView or ScrollView where the items are the buttons, this seems to be the std way of doing things otherwise a tabbed view.
Related
I've been scrubbing google to find a quick reference for macro coding in Attachmate extra! unsuccessfully. Could someone point me to a URL to learn what I need to know, tell me how to resolve the code below or both.
I took a "Hello World" example and a recorded macro and mushed the below code together, which does what i need with a couple exceptions.
Dim dlgHello as HelloDialog 'Declare the dialog file.
nRet = Dialog (dlgHello)
Select Case nRet 'dlgHello.AllButtons
Case - 1 ' OKButton
Sess0. Screen. Sendkeys ("Show a60:4aug;a")
Sess0. Screen. Sendkeys("<Enter>")
Sess0. Screen.WaitHostQuiet (g HostSettleTime)
System. Timeoutvalue = OldsystemTimeout
Msabox "Done"
Case 0 ' CancelButton
Msgbox "Goodbye! "
End Select
While this code above works, I need to change it to pull system date and subtract two days.
Original code Sess0. Screen. Sendkeys ("Show a60:4aug;a")
Instead of the static 4aug, I need the code to pull the system date then subtract two days.
My failed attempt was Sess0. Screen. Sendkeys ("Show a60:"Today() -2"a")
Would be grateful if anyone could tell me how to resolve this and also point me to a good learning resource. Thanks
I received a working solution from Tom F in the MicroFocus community. Solution below.
Sub Main
Dim myDate
myDate=CVar(Date)-2
myDate = Format(myDate, "dmmm")
End Sub
https://portal.microfocus.com/s/article/KM000008384?language=en_US
I have designed my form in 1280*1024 resulotion. They lookvery nice on my Monitor, but If I see on another Monitors, they look very chaotic . Is there a way that I solve this problem?
It's not so easy. This is where MVC comes in very handy and you can distinguish between different components. You can have different views for different devices. Unfortunately VBA does not support that and you would have to implement your own framework to handle different screen resolutions.
The easiest way to avoid having to re-implement the design of your userform is to actually DESIGN it in your head before writing a single line of code. Think of the different resolutions(devices) your software is going to support, what the language you are using is supporting and what are your choices. Generally, think it over. In VBA I normally just go for the default size to avoid the headache of fitting someone's else screen.
You would have to redesign the entire UserForm. Not visually, but programmatically set both width, and height of the userform and make controls dependable on the current resolution. I do not recommend doing it this way but still this could be a solution.
You can achieve that by accessing the current resolution and modifying your Userform_Initialize() event.
So example, if the current resolution is 1024x768, you set the width and height to currentWidth-100px and currentHeight-100px.
If you open a new workbook and create an empty userform. Go to its code behind and add
Private Sub UserForm_Initialize()
Me.Width = GetCurrent(0) - 600
Me.Height = GetCurrent(1) - 800
End Sub
Then insert a module and add
Private Declare Function GetSystemMetrics Lib "user32.dll" (ByVal nIndex As Long) As Long
Sub A()
UserForm1.Show
Unload UserForm1
End Sub
Function GetCurrent(x As Long) As Long
GetCurrent = GetSystemMetrics(x)
End Function
This will display a different size userform depending on the current resolution.
you can (but I wouldn't recommend it) use that technique. Note: depending on how many controls you have this may be the best approach but if you have lots of controls on the userform I would look for an alternative.
Alternatively, you can use the below code which checks the current screen resolution, warns the users and asks if the user wants to change his resolution.
The below code comes from here and the original author is DRJ
You stick the first part in the Workbook code behind
Option Explicit
Private Sub Workbook_Open()
Call VerifyScreenResolution
End Sub
and the below part in a module
Option Explicit
Private Declare Function GetSystemMetrics Lib "user32.dll" (ByVal nIndex As Long) As Long
Const SM_CXSCREEN = 0
Const SM_CYSCREEN = 1
Sub VerifyScreenResolution(Optional Dummy As Integer)
Dim x As Long
Dim y As Long
Dim MyMessage As String
Dim MyResponse As VbMsgBoxResult
x = GetSystemMetrics(SM_CXSCREEN)
y = GetSystemMetrics(SM_CYSCREEN)
If x = 1024 And y = 768 Then
Else
MyMessage = "Your current screen resolution is " & x & " X " & y & vbCrLf & "This program " & _
"was designed to run with a screen resolution of 1024 X 768 and may not function properly " & _
"with your current settings." & vbCrLf & "Would you like to change your screen resolution?"
MyResponse = MsgBox(MyMessage, vbExclamation + vbYesNo, "Screen Resolution")
End If
If MyResponse = vbYes Then
Call Shell("rundll32.exe shell32.dll,Control_RunDLL desk.cpl,,3")
End If
End Sub
update.
your initialize event is here
To get started, I am inexperienced scripting in perl, or using gtk, but I've been googling and researching how to for the past two or so weeks. It was difficult just figuring out where I could find the PMs for gtk on windows, and then even more so getting it to some semblance of 'working'. However, there have of course still been problems.
Skipping the above, I have two problems. For a slight bit of relevant background, I am trying to port an mirc script over to xchat, but to do that I obviously need to learn a whole 'nother language.. but anyway. The two problems are thus:
The window consists of several buttons, labels, and text areas. However, the window is.. 'frozen' in time unless one clicks on the title bar and holds. Clicking a button does nothing, not even to show it has been clicked, unless of course the title bar is clicked and held.
I have no idea how to initialize multiple instances of the same window. I have of course tried researching, but it's either not out there or I just haven't found it yet. To be specific.. My mirc script requires that multiple instances be allowed to exist, but I need the buttons for the specific instance to only affect that instance.. and so on.
In regards to problem 1, I do not know if the .xml glade file is important, so I won't post it immediately. I will however post the code that calls it:
my $glade_file = "window3.xml";
my $glade = Gtk2::Builder->new();
$glade->add_from_file($glade_file);
sub charopen {
my $window = $glade->get_object('window1');
$glade->connect_signals(undef, $window);
my $hp_cur = $glade->get_object('HP_Cur');
$window->set("title"=>$_[0][1]);
$hp_cur->set("label"=>$ini->val($_[0][1],"HPC"));
$window->show();
}
Graphical interface design relies on event processing. To work properly, it is important to reserve a thread to process user events (keyboard, mouse clicks...). That is the aim of calling Gtk2->main() when user interface is ready to accept user interaction.
To make the event thread exiting the event loop, an event callback method may invoke Gtk2->main_quit()
The Gtk2::Builder creates Gtk widget hierarchy from XML. To get multiple instance of the same window, you have to create a builder for each one.
Then your event callback methods has to get information about which window has sent the event, and the $user_data parameter may be used in that aim.
Here is a code proposal with a simple button click callback which use Perl reference to a hash so you can pass as many information as you want between window creator code and event callbacks:
sub createWindow($)
my $windowTitle = $_[0];
my $windowBuilder = Gtk2::Builder->new();
$windowBuilder->add_from_file($glade_file);
my $window = $windowBuilder->get_object('window1');
my $hp_cur = $windowBuilder->get_object('HP_Cur');
# Create hash with data (alternative: use Class::Struct for better code)
my %window_user_data = {
"title" => $windowTitle,
"window" => $window,
"hp_cur" => $hp_cur };
# Pass hash reference as user data
$windowBuilder->connect_signals(\%window_user_data);
# prepare interface: set data model into view and then...
$window->show();
}
# Click callback method defined on a button in window
sub button_click_callback($$) {
my $button = $_[0];
my $window_user_data_ref = $_[1];
# get back data model from view
print "Click received from button on "
. $window_user_data_ref->{"title"} . "\n";
}
There is another way to handle callbacks per window but it requires more Perl skills: you can design a Perl package to create an object instance for a specific window, and use $windowbuilder->connect_signals ($user_data, $windowcallbackinstance). In that case, such an object is called controller, and you have built your graphical interface based on Model-View-Controller (MVC) pattern.
I we got few TEdit forms inserted on another form, but their labels are not displaying until I change size of the window. This happens just on Windows Vista / Windows 7. Windows XP has everything labeled correctly.
Ive already tested repaint / refresh (just TEdit / all form etc.) with no result.
Delphi 7.
Thank you for your answer
bad version
Correct version
code will be added soon enough :)
May be it is ALT Key press causes controls to disappear under Themes in Vista and XP bug?
I was able to mostly resolve this same problem on my project. It seems to be a painting order problem. The solution ultimately was to call frame.Refresh; on the frame that was not displaying properly. But, figuring out the right place to put that Refresh was a little tricky, I tried several places before I found a spot that worked. Where it worked for me was, in the method where I choose which nested frame to show on the options panel, and physically display the nested frame, to call frame.Refresh; on the inner-most frame surrounding the labels that are not drawing properly. Calling refresh on the inner frame rather than the frame for the entire window seemed to be key.
From the screen-shots you are showing, you look like you probably have a similar complex setup of frames where there are likely to be frames displayed on top of frames, which may change dynamically after the frame is displayed initially. That seemed to be the setup that would create the problem in the first place, the initially shown frame never seemed to have problems.
One note, however, is if the window moves off the screen or is re-sized, another window is dragged in front of it, or mouse-over buttons that are disappearing is that these actions could cause the problem to re-appear spontaneously. There may be additional places, such as in a special handler for window resize, etc. or on a timer where you to call refresh on the frame periodically, similar to some of the solutions mentioned for the ALT Key bug. There seems to be some overlap in the type of problem and how to fix it but does not the exact same cause (this bug seems to happen regardless of the Alt key)
There are bugs in TButton when running with windows Themes.
You can search 'ThemesEnabled' in StdCtrls.pas, remove/comment all its related branch, like this:
procedure TButton.CNCtlColorBtn(var Message: TWMCtlColorBtn);
begin
with ThemeServices do
{
if ThemesEnabled then
begin
DrawParentBackground(Handle, Message.ChildDC, nil, False);
// Return an empty brush to prevent Windows from overpainting we just have created.
Message.Result := GetStockObject(NULL_BRUSH);
end
else
}
inherited;
end;
Then compile it, and replaced lib/StdCtrls.dcu with your patched version.
{ Labels no Windows Vista, 7, 8 to Fix the problem, Delphi 7 32 bits }
on FormShow:
var
i : Integer;
begin
For i := 0 to (Form1.ComponentCount - 1) do
begin
If (Form1.Components[i].ClassType = TLabel) then
TLabel(Form1.Components[i]).Refresh;
end;
end;
Just run this.
I'm getting the same thing, except it only seems to be a problem while the application is themed. If it is unthemed (ie Project->Options->Application->Appearance->Default Style = Windows) it works fine, no refresh or repaint needed.
Seems to be related specifically to the TFrame class so I wonder if something about the redraw handler isn't amok (related to Invalidate). Something in the ChangeNotify process or the Windows message pump in the VCL might not making its way up the parent control chain and responding with a cascading repaint funnelling back down on everything "invalidated".
One other cludge I did try with success was setting the host control Visible property to False on one line and then to true in the next line ie:
procedure TFrame1.UpdatePanel;
Panel1.Visible := False;
Panel1.Visible := True;
end;
Then calling this method where the proper drawing is needed.
All of the other child controls of Panel1 were drawn perfectly. You might have to cache the location of the text cursor if your update occurs while modifying the contents of one of the child controls like a TEdit or TMemo. This should be trivial compared to hours of hunting down the cause of the problem. Maybe looking into the VCL source of the Setter method for the Visible property on the offending control host (such as the TPanel) might provide some insight to the overall problem of why Repaint and Refresh don't seem to work as they should in this case.
Actually it is much simpler than any of the solutions presented so far.
The only thing needed is to respond to the WM_UPDATEUISTATE message.
Add a procedure like the one bellow to the form:
...
protected
procedure WmUpdateUIState(var Msg: TMessage); message WM_UPDATEUISTATE;
...
procedure TForm1.WmUpdateUIState(var Msg: TMessage);
begin
inherited;
Invalidate;
end; { WmUpdateUIState }
Done!
Tested on Windows 10 64 bits.
One can speed things up by creating a unit like this:
unit FixAltKeyForm;
interface
uses
Windows, Messages, Classes, Forms;
type
TForm = class(Forms.TForm)
protected
procedure WmUpdateUIState(var Msg: TMessage); message WM_UPDATEUISTATE;
end; { TForm }
implementation
{ TForm }
procedure TForm.WmUpdateUIState(var Msg: TMessage);
begin
inherited;
Invalidate;
end; { WmUpdateUIState }
end.
Add the unit's name to the uses clause of the interface session of any form where this behavior is wanted and you are done. Only be sure to put the unit's name AFTER 'Forms' in the uses clause. No need to create a package nor to install anything at all. That's what I call Visual Subclassing, for lack of a better term.
I've created an application which contains settings as int values... Basically my app contains multiple layouts. When the user presses the "back" key, the app returns to the first panel (which is the main screen!). If pressed on the main screen, the app will pause/finish.
The integer values that I have are what I use to determine whether the user has done something in the app. They also determine which layout the user is in.
I really need to have these int values for when the user opens the app again. What is the best way that I should go about saving multiple int values so that I can access them if the app is killed?
Thanks
Sorry... I'm finding it really hard to write and read to and from a map file... Here is what I have so far simplified. Can you see if I am missing something... more than likely its really easy.
Sub Activity_Resume
Dim m As Map
m.Initialize
If File.Exists(File.DirInternal, "1.txt") Then
m = File.ReadMap(File.DirInternal,"1.txt")
int1 = m.Get("int1")
int2 = m.Get("int2")
End If
End Sub
Sub Activity_Pause (UserClosed) As Boolean
Dim m As Map
m.Initialize
m.Put("int1", int1)
m.Put("int2", int2)
File.WriteMap(File.DirInternal, "1.txt", m)
End Sub
You have several options. You can use StateManager or you can store the settings in a Map and then in Activity_Pause save the Map with File.WriteMap, and read the Map in Activity_Create if the file exists.