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
Related
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
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?
I'm trying to update the content of a combo box (using Griffon 1.2.0, with the JavaFX plugin).
My model:
class MyModel {
List monthList = FXCollections.observableList([new DateMidnight()])
def convertDate = [
fromString: { String s ->
return new DateMidnight(DateTimeFormat.forPattern("yyyy-MM").parseDateTime(s))
},
toString: { DateMidnight d ->
return "2011-10"
}
] as StringConverter
}
My view contains:
comboBox(items: (model.monthList), converter: model.convertDate)
Now I have a controller action which gets invoked when they push a button:
def load = {
execInsideUIAsync {
def months = myService.buildMonthList()
model.monthList.addAll(months)
}
}
The problem is that the combo box content never changes. Can anyone help me understand what I'm missing?
There's no documentation on ComboBox yet http://groovyfx.org/docs/guide/single.html#choiceBoxComboBox
Also, have I implemented the converter correctly?
The problem is that GroovyFX.comboBox creates a new List instead of using the one you pass as argument for items: This problem occurs with tableView as well. A temporary workaround would be to set the items property directly, like this
comboBox(id: 'combo')
noparent { combo.items = model.monthList }
I want to pass parameters to my window when I call window.show().
I added in a listener to my window for the show method but it's displaying the parameter as [Object, object].
#window call
var test = 'hi';
tstWin.show(test);
#listener
listeners : {
'show' : function(test){
alert(test.value);
}
}
In your event handler arguments will not be the same as you put into show() method. In fact they have nothing to do with each other.
Read this: http://docs.sencha.com/ext-js/4-0/#!/api/Ext.AbstractComponent-event-show
Show event handler will receive in the first argument reference to the object.
Update:
You can do something like that:
win = Ext.create(...); // Create your window object here
win.myExtraParams = { a: 0, b: 1, c: 2}; // Add additional stuff
win.on('show', function(win) {
console.log(win.myExtraParams.a, win.myExtraParams.b, win.myExtraParams.c);
});
win.show();
I have a search controller that will look up values and render specific views according to the type of report that should be displayed. There is a weird thing happening. When I issue the $this->render the report view is not rendered. The "catch all" redirect line always is rendered... Code as follows:
public function admin_printReport() {
if (isset($this->request->data['Reports'])) {
$nons = $this->request->data['Reports'];
$res = array();
// lets lookup the noncons.....
foreach ($nons as $dat=>$vdat) {
// skip the ones that are not checked
if ($vdat == 0) {
continue;
}
// this is the temporary array that holds all of the selected report numbers > $res[] = $dat;
}
$this->loadModel('Noncon');
$this->Noncon->recursion = 0;
$results = $this->Noncon->find('all', array('conditions'=>array('Noncon.id'=>$res)));
$this->set('results', $results);
// lets do the selection now...
if (isset($this->request->data['PS'])) {
// Print summary
$this->render('summary', 'print');
} elseif (isset($this->request->data['PD'])) {
// Print detail
$this->render('detail', 'print');
} elseif (isset($this->request->data['PDH'])) {
// Print detail with history
$this->render('detailhistory', 'print');
}
}
// catch all if the render does not work....
$this->redirect(array('controller'=>'noncons', 'action'=>'search','admin'=>true));
}
Any Ideas?
I just figured it out....
for each $this->render, add return. For example:
return $this->render('summary', 'print');
I've just add a similar problem.
In a controller, that has an ajax submit, it was not submitting the render.
$this->render($viewName, 'ajax');
Using return didn't helped.
The problem was that I added
$this->render('add');
at the end of the controller, although it was not necessary since the controller name is add and the autoRender is default (true to automatically render the view with the same name of the controller).
Hope this helps someone else.