How to capture an int value in a closure? - vala

I have an app with an arbitrary number of elements that all call a function that needs to take an argument, defined on creation. This is a simplified example but here I'd be hoping to make 3 buttons that print 0,1,2 but just makes 3 buttons that print 3.
var application_window = new Gtk.ApplicationWindow (this);
var grid = new Gtk.Grid ();
for (int i = 0; i < 3; i++) {
var button = new Gtk.Button() {expand=true};
button.clicked.connect (() => {
print(i.to_string());
});
grid.add(button);
}
application_window.add(grid);
application_window.show_all ();
How can I change my app to print 123 instead?

Here is my base code:
public class MyApplication : Gtk.Application {
public MyApplication () {
Object(application_id: "testing.my.application",
flags : ApplicationFlags.FLAGS_NONE);
}
protected override void activate () {
var application_window = new Gtk.ApplicationWindow (this);
var grid = new Gtk.Grid ();
for (int i = 0; i < 3; i++) {
var button = new Gtk.Button() {expand=true};
button.clicked.connect (() => {
print(i.to_string());
});
grid.add(button);
}
application_window.add(grid);
application_window.show_all ();
}
public static int main (string[] args) {
MyApplication app = new MyApplication ();
return app.run (args);
}
}
If you execute it like that, you get 333 as stdout.
The problem is in the capturing code:
for (int i = 0; i < 3; i++) {
var button = new Gtk.Button() {expand=true};
button.clicked.connect (() => {
print(i.to_string());
});
The closure is capturing the variable i by location. That means when you change the i variable after creating the closures the change will be visibile in the closure as well.
Other programming languages (e. g. C++) have explicit capture lists to avoid this problem.
A quick and dirty solution would be using a local variable inside the scope of the loop:
for (int i = 0; i < 3; i++) {
var captured_i = i;
var button = new Gtk.Button() {expand=true};
button.clicked.connect (() => {
print(captured_i.to_string());
});
This prints: 012 as intended.
A better solution would be using a function that returns the closure as a delegate. I just tried that, but for some reason it does not work:
public class MyApplication : Gtk.Application {
public MyApplication () {
Object(application_id: "testing.my.application",
flags : ApplicationFlags.FLAGS_NONE);
}
delegate void ButtonClick();
private ButtonClick make_print_event (int i) {
return () => print (i.to_string());
}
protected override void activate () {
var application_window = new Gtk.ApplicationWindow (this);
var grid = new Gtk.Grid ();
for (int i = 0; i < 3; i++) {
var button = new Gtk.Button() { expand=true };
var print_event = make_print_event (i);
button.clicked.connect (print_event);
grid.add(button);
}
application_window.add (grid);
application_window.show_all ();
}
public static int main (string[] args) {
MyApplication app = new MyApplication ();
return app.run (args);
}
}
The compiler (valac-0.52) warns:
three_buttons.vala:20.37-20.47: warning: copying delegates is not supported
three_buttons.vala:20.37-20.47: warning: Connecting delegates to signals is experimental
button.clicked.connect (print_event);
^^^^^^^^^^^

Related

Is Dart/Flutter has a listener function?

The listener function can listen to any parameter type(not only listener type). This has nothing related to widgets (this should work successfully in https://dartpad.dev without using the flutter).
ex.
int a = 0;
listener((a>0)=>print("A = $a"));
a= 1; //A = 1
a= -1; //
a= 2; //A = 2
You can use ValueNotifier for this. It's a ChangeNotifier that is triggered when the value is replaced with something that is not equal to the old value as evaluated by the equality operator ==.
Here is a nice tutorial about this approach.
The basic method is to create the function for updating the parameter that you want to add to the listener.
void test() {
int a = 0;
void updateA(newA) {
if(newA is! int) return;
a = newA;
if (a > 0) print("A = $a");
}
updateA(1);
updateA(-1);
updateA(2);
}
A better way is to create parameters with class.
void main() {
ParameterWithListener a = ParameterWithListener(data: 0);
a.listener = () {
if (a.data is int && a.data > 0) print("A = ${a.data}");
};
a.update(1);
a.update(-1);
a.update(2);
}
class ParameterWithListener {
ParameterWithListener({this.data, this.listener});
dynamic data;
Function()? listener;
Future update(data) async {
this.data = data;
if (listener is Function()) await listener!();
}
}
result:
A = 1
A = 2

xamarin.forms webview.focused event raised on Android, but not on iOS

I'm using a Xamarin.Forms grid application to show a couple of html elements as WebViews in the cells of the grid CardGrid:
private async void CreateCardView()
{
CardGrid.Children.Clear();
// idx over all count elements of html snippets
for (idx = 0; idx < count; idx++)
{
string html = AuthoringCard(idx);
RenderingCard(html, idx);
}
}
AuthoringCard() creates the html code snippet.
RenderingCard() creates the WebView inside the grid cell.
private void RenderingCard(string htmlCard, int index)
{
int CardWidth = 300;
int CardHeight = 150;
int CardNoHorizontally = 3;
WebView uiCard = new WebView();
uiCard.HeightRequest = CardHeight - 5;
uiCard.WidthRequest= CardWidth - 5;
uiCard.VerticalOptions = LayoutOptions.Start;
uiCard.HorizontalOptions = LayoutOptions.Center;
uiCard.Margin = new Thickness(0);
uiCard.AutomationId = index.ToString();
uiCard.Focused += Card_Tapped;
uiCard.InputTransparent = false;
var htmlSource1 = new HtmlWebViewSource
{
Html = htmlCard,
};
uiCard.Source = htmlSource1;
CardGrid.Children.Add(uiCard);
int row = (int)Math.Floor((double)(index / CardNoHorizontally));
int column = index - (row * CardNoHorizontally);
Grid.SetRow(uiCard, row);
Grid.SetColumn(uiCard, column);
}
I want to catch the Focused event, when the user it tapping on the card (WebView) and using the AutomationId to get the index of the card (html code snippet):
private void Card_Tapped(object sender, EventArgs e)
{
WebView card = (WebView)sender;
int index = Convert.ToInt16(card.AutomationId));
}
This works fine with Android. Under iOS the event is never raised. Any idea for a solution?
Cause:
the property Focus in Forms correspond method that we called becomeFirstResponder in native iOS platform.Unfortunately,UIWebView and WKwebview do not support the method becomeFirstResponder.This method is only available in some 'input-controls' Such as UITextField and UITextView(Entry in Forms).So even if you set the event on a webview.It will not work in iOS.
Workaround:
You can add a TapGestureRecognizer on the webview.And you have to implement it by using CustomRenderer.Because it will create conflict if you add TapGestureRecognizer in forms.
Refer to the following code.
in Forms
public MainPage()
{
if (Device.RuntimePlatform == "iOS")
{
MessagingCenter.Subscribe<Object,string>(this,"webview_click", (sender,arg)=> {
// int index = Convert.ToInt16(arg));
});
}
}
in iOS
using System;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using xxx;
using xxx.iOS;
using ObjCRuntime;
[assembly:ExportRenderer(typeof(WebView),typeof(MyWebViewRenderer))]
namespace xxx.iOS
{
public class MyWebViewRenderer : WebViewRenderer, IUIGestureRecognizerDelegate
{
public bool isFirstLoad = true;
public MyWebViewRenderer()
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e != null)
{
UITapGestureRecognizer tap = new UITapGestureRecognizer(this, new Selector("Tap_Handle:"));
tap.WeakDelegate = this;
this.AddGestureRecognizer(tap);
}
}
[Export("gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:")]
public bool ShouldRecognizeSimultaneously(UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer)
{
return true;
}
[Export("Tap_Handle:")]
void Tap_Handle(UITapGestureRecognizer tap)
{
if(isFirstLoad)
{
isFirstLoad = false;
MessagingCenter.Send<Object,string>(this, "webview_click",Element.AutomationId);
}
}
}
}

Dart: a closure study

This snippet is of no practical significance. I've been just getting used to the closure idea.
var cls = () {
var x = 5;
return {
'x': x,
'inc': () {x++;},
};
} ();
void main() {
print(cls['x']);
print(cls['inc']);
cls['inc']();
print(cls['x']);
}
DartPad output:
5
Closure '_closure'
5
Error compiling to JavaScript:
main.dart:18:13:
Error: The method 'call' isn't defined for the class 'dart.core::Object'.
The desired output would have been something like
5
6
What'd be the cleanest approach to this kind of exercise?
UPD:
The working example, courtesy of Günter Zöchbauer:
var cls = () {
var x = 5;
var map = <String, dynamic>{
'x': x,
};
map['inc'] = () {map['x']++;};
return map;
} ();
void main() {
print(cls['x']);
print(cls['inc']);
cls['inc']();
print(cls['x']);
}
DartPad output:
5
Closure '_closure'
6
You have to declare the 'x' entry as a Function.
In your code, you set 'x' to the value of the 'x' variable (5) when you return the map. The value will always be 5 and will not update.
var cls = () {
var x = 5;
return {
'x': () => x,
'inc': () {x++;},
};
}();
void main() {
print(cls['x']()); // 5
print(cls['x']); // Closure: () => int
print(cls['inc']); // Closure: () => Null
cls['inc']();
print(cls['x']()); // 6
}
var x = 5;
var cls = () {
return {
'x': x,
'inc': () {x++;},
};
} ();
You would need to move out the variable declaration, otherwise you'd re-declare and re-initialize it to 5 at every call.
update
var cls = () {
var x = 5;
var map = {
'x': x,
// 'inc': () {map['val']++;}, // not possible to reference the `map` variable that is just declared here so we need to move this out
};
map['inc'] = () {map['x']++;};
} ();

keeping track of a series of simple multiple choice web form answers

This is the code I'm trying to use, which seems logical. But doesn't seem to be working.
MyAsFileName.prototype.getTotalScore = function() {
var totalScore = 0;
for (var i = 0; i < allQuestions.length; i++) {
totalScore += allQuestions[i].getCalculatedScore();
if (currentModule.allQuestions[i].parent.questionCorrect == true) {
knowledgePoints++;
} else {
knowledgePoints--;
}
}
debugLog("Total score: " + totalScore);
debugLog(knowledgePoints);
return totalScore;
}
I have allQuestions defined as below:
var allQuestions = Array();
I have knowledgePoints defined as:
this.knowledgePoints = 10;
I have questionCorrect defined as:
this.questionCorrect = false;
Second fresh attempt made with new class as answer below suggested (commented out for now until I figure out how to get working):
// package
// {
/*public class Quiz {
//public
var knowledgePoints: int = 10;
//public
var allQuestions: Array = new Array;
//public
var questionCorrect: Boolean = false;
//public
function getTotalScore(): int {
var totalScore: int = 0;
for (var i = 0; i < allQuestions.length; i++) {
totalScore += allQuestions[i].getCalculatedScore();
if (currentModule.allQuestions[i].parent.questionCorrect) {
knowledgePoints++;
} else {
knowledgePoints--;
}
}
debugLog("Total score: " + totalScore);
debugLog(knowledgePoints);
return totalScore;
}
}*/
//}
This code above outputs two errors in flash console:
Error 1. Attribute used outside of class.
Error 2. 'Int' could not be loaded.
It's a weird (and actually non-AS3 way) way to do this. Instead of creating a unnamed closure which refers weird variables from who-knows where, you should make it a normal AS3 class, something like that (in a file named Quiz.as):
package
{
public class Quiz
{
public var knowledgePoints:int = 10;
public var allQuestions:Array = new Array;
public var questionCorrect:Boolean = false;
public function getTotalScore():int
{
var totalScore:int = 0;
// Your code does not explain how you will that Array.
// It is initially an empty Array of length 0.
for (var i = 0; i < allQuestions.length; i++)
{
totalScore += allQuestions[i].getCalculatedScore();
if (currentModule.allQuestions[i].parent.questionCorrect)
{
knowledgePoints++;
}
else
{
knowledgePoints--;
}
}
// Not sure what it is.
debugLog("Total score: " + totalScore);
debugLog(knowledgePoints);
return totalScore;
}
}
}

Custom container not drawing

I've been reading about Vala over the past couple of days and decided to dive into it and make some Clutter widgets along the way. I'm currently trying to draw a private actor from my custom actor subclass. Here is a simplified version of what I've got so far.
public class MyContainer : Clutter.Actor, Clutter.Container {
private Clutter.Group group;
public MyContainer() {
group = new Clutter.Group();
group.set_parent(this);
}
public void add_actor(Clutter.Actor actor) {
group.add_actor(actor);
actor.show();
set_size(group.width, group.height);
actor_added(actor);
queue_redraw();
}
public void foreach(Clutter.Callback callback) {
group.foreach(callback);
queue_redraw();
}
public override void get_preferred_height(
float for_width,
out float min_height_p,
out float natural_height_p) {
group.get_preferred_height(
for_width,
out min_height_p,
out natural_height_p);
}
public override void get_preferred_width(
float for_height,
out float min_width_p,
out float natural_width_p) {
group.get_preferred_width(
for_height,
out min_width_p,
out natural_width_p);
}
public override void paint() {
group.paint();
}
public void remove_actor(Clutter.Actor actor) {
group.remove_actor(actor);
set_size(group.width, group.height);
actor_removed(actor);
queue_redraw();
}
public void sort_depth_order() {
group.sort_depth_order();
queue_redraw();
}
}
int main(string [] args) {
// Start clutter.
var result = Clutter.init(ref args);
if (result != Clutter.InitError.SUCCESS) {
stderr.printf("Error: %s\n", result.to_string());
return 1;
}
var stage = Clutter.Stage.get_default();
// Build a MyCollection object.
var myc = new MyContainer();
myc.x = 100;
myc.y = 100;
var r1 = new Clutter.Rectangle();
r1.width = 50;
r1.height = 50;
r1.color = Clutter.Color.from_string("rgb(255, 0, 0)");
var t1 = new Clutter.Text();
t1.text = "The red square.";
t1.y = r1.height;
// Build a Group object similar to the previous.
var group = new Clutter.Group();
group.x = 300;
group.y = 100;
var r2 = new Clutter.Rectangle();
r2.width = 50;
r2.height = 50;
r2.color = Clutter.Color.from_string("rgb(255, 0, 0)");
var t2 = new Clutter.Text();
t2.text = "The red square.";
t2.y = r2.height;
// Display.
myc.add_actor(r1);
myc.add_actor(t1);
group.add_actor(r2);
group.add_actor(t2);
stage.add_actor(myc);
stage.add_actor(group);
stage.show_all();
Clutter.main();
return 0;
}
The example paints the group added directly to the stage, but not the group wrapped by the custom collection that is added to the stage. How can I get this to work and what is wrong with the above?
I've been working on ubuntu 11.10 with valac --pkg clutter-1.0 above_code_example.vala.
This answer is from buz on gnome.irc's #clutter room.
The problem is a missing override for the allocate function.

Resources