Problem with a custom Control in Windows Form - winforms

I'm trying to write my own MineSweeper in F#.
I create a type of Button :
type myButton(pos : Point, boum : bool, wnd : Form) =
inherit Button(Parent = wnd,
Bounds = Rectangle(Point((pos.X+1) * size,(pos.Y+1) * size), Size(size,size)),
Visible = true,
Text = if boum = true then "B" else "")
let (bomb : bool) = boum
member this.Bomb with get() = bomb
The buttons are in a Form and I can get these buttons with GethildAtPoint. But I get a Control not a myButton so I can't access to the the field Bomb.
How can I get it ? I tried with the Tag property, but without success.
Thank you.

When you call GetChildAtPoint, you will get back a Control, which you can then type test with a pattern match to see if it's your custom button:
let ctrl = form.GetChildAtPoint pt
match ctrl with
| :? myButton as myb ->
let isBomb = myb.Bomb ()
// Do something here
()
| _ -> () // ignore anything else

Related

How to use a main loop in a Gnome Extension?

I want to display the content of a file in the Gnome top bar and update the display when the content of the file changes.
For now I have just a skeleton extension that writes Hello world in the top bar.
I have tried to make a loop that should update the text:
File: extension.js
const {St, Clutter} = imports.gi;
const Main = imports.ui.main;
let panelButton;
function init () {
// Create a Button with "Hello World" text
panelButton = new St.Bin({
style_class : "panel-button",
});
let panelButtonText = new St.Label({
text : "Hello World",
y_align: Clutter.ActorAlign.CENTER,
});
panelButton.set_child(panelButtonText);
let i = "x"
const Mainloop = imports.mainloop;
let timeout = Mainloop.timeout_add_seconds(2.5, () => {
i = i + "x"
panelButtonText.set_text(i)
});
}
function enable () {
// Add the button to the panel
Main.panel._rightBox.insert_child_at_index(panelButton, 0);
}
function disable () {
// Remove the added button from panel
Main.panel._rightBox.remove_child(panelButton);
}
I expect the Hello world text to change multiple times but it stops at xx:
I have tried to do the same with date and time but it does not work either:
const GLib = imports.gi.GLib;
let now = GLib.DateTime.new_now_local();
let nowString = now.format("%Y-%m-%d %H:%M:%S");
panelButtonText.set_text(nowString)
Date and time does not update!
You'll need to return GLib.SOURCE_CONTINUE (true) for the event to keep looping, or GLib.SOURCE_REMOVE (false) for it to exit. Because you are not returning a value from your callback, it is being coerced from undefined to false and only run once.
More notes:
you will want to use GLib's functions now, not the MainLoop import, which is deprecated
you will want to store the returned GLib.Source ID (i.e. timeout), probably in the same scope as panelButton, so that you can remove it in disable().
There is a guide for using the mainloop on gjs.guide at https://gjs.guide/guides/gjs/asynchronous-programming.html#the-main-loop

How does Parent works in System.Windows.Forms

I have following code which is (almost) working as expected:
Add-Type -AssemblyName System.Windows.Forms
class MyForm : System.Windows.Forms.Form {
MyForm($mystuff) {
#Do-Stuff
$this.Add_Load( $this.MyForm_Load )
}
$MyForm_Load = {
$mlabel = [System.Windows.Forms.Label]::new()
$mlabel.Name = "label"
$mlabel.Text = "disabled"
$mbutton = [System.Windows.Forms.Button]::new()
$mbutton.Name = "button"
$mbutton.Location = [System.Drawing.Point]::new(100,100)
$mbutton.Add_Click( $this.mbutton_click )
$this.Controls.Add($mlabel)
$this.Controls.Add($mbutton)
# ----------------------------------------------
# Now $this.controls has something. We can now access it.
# ----------------------------------------------
if ($this.controls["label"].text -eq "enabled"){
$mbutton.text = "disable"
}else{
$mbutton.text = "enable"
}
}
$mbutton_click = {
if ($this.Parent.Controls["label"].Text -eq "enabled"){
$this.Parent.Controls["label"].Text = "disabled"
$this.Parent.Controls["button"].Text = "enable"
}
else{
$this.Parent.Controls["label"].Text = "enabled"
$this.Parent.Controls["button"].Text = "disable"
}
}
}
$foo = [MyForm]::new("test")
$foo.ShowDialog()
but when I replace following section:
$mbutton_click = {
if ($this.Parent.Controls["label"].Text -eq "enabled"){
$this.Parent.Controls["label"].Text = "disabled"
$this.Parent.Controls["button"].Text = "enable"
}
else{
$this.Parent.Controls["label"].Text = "enabled"
$this.Parent.Controls["button"].Text = "disable"
}
}
For this (missing Parent):
$mbutton_click = {
if ($this.Controls["label"].Text -eq "enabled"){
$this.Controls["label"].Text = "disabled"
$this.Controls["button"].Text = "enable"
}
else{
$this.Controls["label"].Text = "enabled"
$this.Controls["button"].Text = "disable"
}
}
Then my script stops working and I see following error on console:
The property 'Text' cannot be found on this object. Verify that the property exists and can be set.
Why$MyForm_Load works without Parent but $mbutton_click requires Parent? Isn't both $MyForm_Load and $mbutton_click part of same object? How does Parent works in System.Windows.Forms?
That's because in the event handler $this is bound to the sender of the event (the button in this case) instead of your class instance. So something like this should also work:
$mbutton_click = {
if ($this.Text -ne "enable") {
$this.Parent.Controls["label"].Text = "disabled"
$this.Text = "enable"
}
else{
$this.Parent.Controls["label"].Text = "enabled"
$this.Text = "disable"
}
}
Like #mhu says, it's because one is bound to the Form Load event vs an individual control object.
A Form is a class. That means a Form has:
Properties
Properties are objects in the class that can be referenced. They can be simple strings like .Name, or complex like .Controls which return a complex Control.ControlCollection object.
Methods:
Calling a method calls a single function definition that is fully defined at compile time. Calling a method e.g. MyForm.ShowDialog() calls that individual .ShowDialog() function.
Events:
Sometimes we want to do something, but we can't fully define the method call at compile time. But, at the same time, we really like the convenience of calling something that is defined like a method. This is where Event's can be used.
First. We think of a method call for something useful that we want to happen, like MyForm.Load(), and that's all we have to define at compile time. Right now we don't know what we want to do. We know that we want to be able to load a form, but we don't know what it will do or look like. So we put this in as a placeholder that we can call.
After some thought, we figure out what we want to do, and how we want things to look like and we build a function that does something useful. We then subscribe this function to an Event. This is like connecting it.
In the first case:
MyForm($mystuff) {
$this.Add_Load( $this.MyForm_Load )
}
We are registering MyForm_Load to the MyForm.Load event:
MyForm.Load -> MyForm_Load
This means that when we call MyForm.Load() it will call the connected function MyForm_Load that we wrote, and will execute it as if we wrote it as a real method at compile time.
Therefore inside MyForm_Load, $this refers to the MyForm Form object. i.e. No .parent needed, because you are the form.
Therefore to access the MyForm.Controls property, you can access it directly.
MyForm.Load -> MyForm_Load
MyForm.Controls
The second:
$MyForm_Load = {
$mlabel = [System.Windows.Forms.Label]::new()
$mlabel.Name = "label"
$mlabel.Text = "disabled"
$mbutton = [System.Windows.Forms.Button]::new()
$mbutton.Name = "button"
$mbutton.Location = [System.Drawing.Point]::new(100,100)
$mbutton.Add_Click( $this.mbutton_click )
$this.Controls.Add($mlabel)
$this.Controls.Add($mbutton)
}
Adds Controls to the Form.Controls object:
MyForm.Load -> MyForm_Load
MyForm.Controls
|-> mlabel
|-> mbutton
The mbutton control has a click event attached:
$MyForm_Load = {
...
$mbutton.Add_Click( $this.mbutton_click )
...
}
$mbutton_click = {
...
$this.Parent.Controls["label"].Text = "disabled"
...
}
So it now looks like:
MyForm.Load -> MyForm_Load
MyForm.Controls
|-> mlabel.Text
|-> mbutton.Click -> mbutton_click
So to go from MyForm_Load to mlabel.Text is:
$this .Controls["label"] .Text
(MyForm).Controls[(mlabel)].Text
Whereas from mbutton_click, the mbutton doesn't have any controls inside it. You have to go "up" a level to the form to get the mlabel control:
$this .Parent .Controls["label"] .Text
(mbutton).(MyForm).Controls[(mlabel)].Text

Call component method when model property changes

In my Fable app with Elmish I have a view that uses react-slick and a button that should be able to change the slide number on click:
Fable.Import.Slick.slider
[ InitialSlide model.SlideNumber
AfterChange (SlideTo >> dispatch) ]
children
Button.button
[ Button.OnClick (fun _ev -> dispatch (SlideTo 5)) ]
[ str "Go to slide 5" ]
The react component for the slider is defined by react-slick.
The fable wrapper I had to write on my own, so it's not complete because I only defined the properties I need.
module Fable.Import.Slick
open Fable.Core
open Fable.Core.JsInterop
open Fable.Helpers
open Fable.Helpers.React.Props
open Fable.Import.React
type SliderProps =
| InitialSlide of int
| AfterChange of (int -> unit)
interface IHTMLProp
let slickStyles = importAll<obj> "slick-carousel/slick/slick.scss"
let slickThemeStyles = importAll<obj> "slick-carousel/slick/slick-theme.scss"
let Slider = importDefault<ComponentClass<obj>> "react-slick/lib/slider"
let slider (b: IHTMLProp list) c = React.from Slider (keyValueList CaseRules.LowerFirst b) c
So while react-slick defines a property InitialSlide to set the slide that should initially be shown, there's no property to update the slide afterwards. There is a method slickGoTo that should do what I want. But I don't know how or where to call that method while still being compliant with Elmish.
I could imagine that I have to extend the react component and listen to the model property and then call slickGoTo whenever that property changes. But I don't know if that's possible.
So my question is: How can I have a button that changes the slide number on click using slickGoTo that is defined by the component while not hacking the Elmish architecture?
An alternative to storing the reference to the slider object in the model is to use a mutable variable:
let mutable sliderRef : Browser.Element option = None
let gotoSlide n =
match sliderRef with None ->
| None -> ()
| Some slider -> slider?slickGoTo n
the rest is similar:
type Msg =
| CurrentSlide of int
| GotoSlide of int
...
let update msg model =
match msg with
| CurrentSlide n -> { model with slideNumber = n } , []
| GotoSlide n -> gotoSlide n ; model, []
Create your slider like this:
slider
[ Ref (fun slider = sliderRef <- Some slider)
AfterChange (CurrentSlide >> dispatch)
InitialSlide 0
]
slides
To be able to call the method slickGoTo you need to get a reference to the slider object. To do that use the Ref prop that expects a function of type (Browser.Element -> unit). It gets called once. Save that element somewhere, perhaps in your model:
type Model = {
...
slideNumber : int
sliderRef : Browser.Element option
...
}
let gotoSlide sliderRef n =
match sliderRef with None ->
| None -> ()
| Some slider -> slider?slickGoTo n
That way you can call gotoSlide from your update function.
type Msg =
| SetSliderRef of Browser.Element
| CurrentSlide of int
| GotoSlide of int
...
let update msg model =
match msg with
| SetSliderRef e -> { model with sliderRef = Some e } , []
| CurrentSlide n -> { model with slideNumber = n } , []
| GotoSlide n -> gotoSlide model.sliderRef n ; model, []
...
Create your slide like this:
slider
[ Ref (SetSliderRef >> dispatch)
AfterChange (CurrentSlide >> dispatch)
InitialSlide 0
]
slides
I have not tested any of this, so take with a grain of salt.
I just found that within React you previously solved such things using componentWillReceiveProps - that sounds a lot like what I want IMO. But this is now obsolete and there are two recommendations based on my usage:
If you used componentWillReceiveProps to “reset” some state when a prop changes, consider either making a component fully controlled or fully uncontrolled with a key instead.
I don't think I can implement the Fully controlled version (this has to be done by the author of the component, right?), but the fully uncontrolled with a key seems to work. So my slider view now looks like this:
Fable.Import.Slick.slider
[ InitialSlide model.SlideNumber
AfterChange (SlideTo >> dispatch)
Key (string model.SlideNumber) ]
According to the docs everytime the Key changes, the component is recreated. Now this sounds like a lot of overhead and has its own drawbacks (animating the slide doesn't work) but currently it's the simplest solution. Any concerns?

How to set DOM attribute (i.e. style) using Reason/Bucklescript?

How would I write the following JavaScript:
var element = document.querySelector('.element')
element.style.color = 'red'
in Reason?
So far I have:
[###bs.config {no_export: no_export}];
external document : Dom.document = "document" [##bs.val];
external query_selector : Dom.document => string => Dom.element = "querySelector" [##bs.send];
let element = query_selector document ".element";
And that compiles just fine.
But how would I be able to set an attribute (i.e. style) on element?
So first of all, this is available in bs-webapi already. But if you want to recreate (and simplify) it, here's how:
external document : Dom.document = "document" [##bs.val];
external querySelector : string => option Dom.element = "" [##bs.send.pipe: Dom.document] [##bs.return null_to_opt];
external style : Dom.element => Dom.cssStyleDeclaration = "" [##bs.get];
external setColor : Dom.cssStyleDeclaration => string => unit = "color" [##bs.set];
let () =
switch (document |> querySelector ".element") {
| Some element => setColor (style element) "red";
| None => ()
};
You can also throw type-safety out the window and just do it like this:
external document : Js.t {..} = "document" [##bs.val];
let () = {
let element = document##querySelector ".element";
element##style##color #= "red"
};
But then I'm guessing you're doing this to learn, in which case the latter would be a terrible idea.
One way to do it is:
[###bs.config {no_export: no_export}];
external document : Dom.document = "document" [##bs.val];
external query_selector : Dom.document => string => Dom.element = "querySelector" [##bs.send];
external set_attribute : Dom.element => string => string => unit = "setAttribute" [##bs.send];
let element = query_selector document ".element";
set_attribute element "style" "color: red";
However, I'm not sure if there is a better way.
Notes:
[###bs.config {no_export: no_export}]; prevents Bucklescript from exporting ES6 modules.
The Dom module provides a bunch of types.
Unanswered Questions:
How can I take advantage of the Dom module's attr type instead of using string?
How can I take advantage of the Dom module's cssStyleDeclaration type instead of using string?

Find out Button position in array of objects

I've created a Button symbol and export it Export for ActionScript with class name "theButton".
There is an object and i would like to create that Button in myObj constructor as below :
public class myObj extends Sprite {
private var myBtn:theButton = new theButton();
public function myObj() {
x = Math.floor(Math.random() * 300) + 50;
y = Math.floor(Math.random() * 300) + 50;
addChild(myBtn);
}
public function getXPos():uint {
return x;
}
}
I'm trying to create an array of myObj class and getXPos() when i do clicking on each button like so :
var myArray:Array = new Array();
myArray[0] = new myObj();
myArray[0].addEventListener(MouseEvent.CLICK, Clicked);
addChild(myArray[0]);
function Clicked(evt:MouseEvent):void {
var xPos1:uint = myObj(evt.target).getXPos();
trace("Position is in : " + xPos1);
}
When clicking on the buttons appears on the screen, following error has comes up:
Type Coercion failed: cannot convert theButton#2c9dcf99 to myObj.
Please tell me what am i doing wrong ?
evt.target will contain a reference to the clicked display object, which actually is myBtn inside the myObj class (it's the only visible graphics you can click on).
There are two ways to solve this.
Set this.mouseChildren = false inside the myObj() constructor.
This way a click on a child display object in myObj will be
"counted" as a click on myObj, and evt.target will be a reference to
an instance of myObj.
Instead of evt.target use evt.currentTarget. It's the instance
you attached the event listener to, not the instance you clicked on
(That is actually what you want in most cases).

Resources