How do I overload operators for WPF containers? - wpf

type AdderType() =
/// Appends to the container.
static member (+)
(cont:DockPanel,child:#UIElement) =
cont.Children.Add child |> ignore
child
When I make the class like the above and try something like this.
let dock = DockPanel()
let win = Window(Title = "Check the Window Style", Content = dock)
let menu = dock + Menu()
I get the error None of the types 'DockPanel,Menu' support the operator '+'. I was inspired to make the above by Phil Trelford's binding example which goes like this:
type DependencyPropertyValuePair(dp:DependencyProperty,value:obj) =
member this.Property = dp
member this.Value = value
static member (+)
(target:#UIElement,pair:DependencyPropertyValuePair) =
target.SetValue(pair.Property,pair.Value)
target
The above for some reason works. I have no idea why. Would it be possible to overload the + or some other operator so that I could elegantly add controls to containers?

Operators defined inside a class only work if one of the arguments is an instance of the class, but you can define your operator as a global operator:
let (++) (cont:DockPanel) (child:#UIElement) =
cont.Children.Add child |> ignore
child
The following should then work:
let dock = DockPanel()
let win = Window(Title = "Check the Window Style", Content = dock)
let menu = dock ++ Menu()
But to be honest, I don't think this kind of problem is a good place for using custom operators. Using + here is confusing, because you are not really adding two things in any sense. Your operator is not commutative, e.g. (a ++ b) <> (b ++ a).
I think a more idiomatic code would be to define a named function and use the |>:
let appendTo (cont:DockPanel) (child:#UIElement) =
cont.Children.Add child |> ignore
child
let dock = DockPanel()
let win = Window(Title = "Check the Window Style", Content = dock)
let menu = Menu() |> appendTo dock

The way to do it similarly as in the last example would be like this:
type ContainerType(con:Panel) =
member this.Children = con.Children
static member (+)
(child:#UIElement,cont:ContainerType) =
cont.Children.Add child |> ignore
child
let toCon = ContainerType
Actually I do like Tomas's solution better. And Phil Trelford's example looks so much like it is a part of .NET that it slipped my mind that one of the arguments in the static member (+) has DependencyPropertyValuePair as the type which is why the + operator could be overloaded in the first place. So that answers that.

Related

move SpriteKit nodes dynamically to an array

I am working on a GameScene and I set up some nodes in the scene editor, since I want to place them visually in the level.
The nodes have all names going from "platform1" to "platform5". Depending on the level there are more or less platform nodes.
when the level is loaded I want to enumerate over all nodes with the title like "platform*" and put them into an array.
For now I use enumerateChildNodesWithName but I dont get the code in the block working correct.
This is what I have so far:
enumerateChildNodesWithName("//platform*", usingBlock: {node, _ in
if let platform = node as? PlatformNode {
print(platform.name)
}
})
And it prints out following error message:
CUICatalog: Invalid Request: requesting subtype without specifying
idiom
But the platform name isn't printed out.
Any ideas how to achieve this?
My next goal would be to out every single platform into an array, so I can access the properties of every platform though the array.
Does someone have a helping hand?
Thanks in advance...
If you add the platforms to an SKNode container in the scene editor, they are automatically added to the container's children array (in the order they are added). You can then access the platforms with
if let platforms = childNodeWithName("platforms") {
for platform in platforms.children {
print ("\(platform.name)")
}
}
To add the platforms to an SKNode in the scene editor, add an SKNode (listed as an Empty) to the editor, set the SKNode's name appropriately (e.g., platforms), and set the parent of each platform by setting platform's parent property in the editor (see image below).
Your search string is not correct. You can do it like this:
class PlatformNode:SKSpriteNode{
}
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let p1 = PlatformNode()
p1.name = "platform1"
let p2 = PlatformNode()
p2.name = "platform2"
let p3 = PlatformNode()
p3.name = "platform3"
let p4 = PlatformNode()
p4.name = "platform4"
let p5 = PlatformNode()
p5.name = "platform5"
addChild(p1)
addChild(p2)
addChild(p3)
addChild(p4)
addChild(p5)
enumerateChildNodesWithName("platform[1-3]", usingBlock: {node, _ in
if let platform = node as? PlatformNode {
print(platform.name)
}
})
}
}
This will give you all the platforms named platform1,platform2 and platform3. To retrieve nodes named platform4 and platform5, you will change search string to this:
"platform[1-5]"

Trouble finding the correct syntax creating vars in objects

Up until now I have been creating var inside the classes I made. e.g.
var backpack:Array = new Array("food", "water");
I want to create objects dynamically now like:
player = {};
player.backpack = ("food", "water"); // not the right syntax
OR
player = {backpack:Array = new Array("food", "water")} // not right either.
Any help? Thanks in advance. I can do this with simple vars like int, but can't find the answer to arrays.
ActionScript's generic object properties don't have any variable type associated with them. You assign them one of the following ways.
Example 1
player = {backpack: new Array("food", "water")};
Example 2
player.backpack = new Array("food", "water");
Example 3
player["backpack"] = new Array("food", "water");
You can use square brackets to define literal arrays. Not only is it shorter, but it's also faster (see this post).
The correct syntax for your two examples are
player = {};
player.backpack = ["food", "water"];
and
player = {backpack: ["food", "water"]};
Also, if you find it easier, you can use it in the first line of code you wrote.
var backpack:Array = ["food", "water"];

Create button handler in F# for WinForms app

I'm trying to make a very simple application in F#. I want just to make a tiny button with the click event, which will call the MessageBox.Show() method.
open System
open System.Windows.Forms
module FSI =
[<System.Runtime.InteropServices.DllImport("user32.dll")>]
extern bool ShowWindow(nativeint hWnd, int flags)
let HideConsole() =
let proc = System.Diagnostics.Process.GetCurrentProcess()
ShowWindow(proc.MainWindowHandle, 0)
FSI.HideConsole()
let form1 = new Form()
form1.Text <- "App"
let Test() = ( MessageBox.Show("Clicked.") )
let button1 = new Button( Text = "Click Me" )
button1.Click.AddHandler(fun _ _ -> Test())
form1.Controls.Add(button1)
[<STAThread>]
do
Application.Run(form1)
The errors occurred for the:
FSI.HideConsole() // warning
button1.Click.AddHandler(fun _ _ -> Test()) // compiler error
And they are similar.
For the 1-st:
Warning This expression should have type 'unit', but has type 'bool'. Use 'ignore' to discard the result of the expression, or 'let' to bind the result to a name.
For the 2-nd:
This expression was expected to have type unit but here has type DialogResult
F# functions implicitly return the result of their last expression, so currently your function Test() returns the result of MessageBox.Show(string). To create a function that returns void/unit, you can use the ignore operator.
let Test() = ignore (MessageBox.Show("Clicked."))
It's a bit cumbersome to have to do the extra parenthesis wrapping, so typically one uses the pipeforward (|>) operator, which doesn't require that:-
let Test() = MessageBox.Show("Clicked.") |> ignore
This can also be applied to your call of HideConsole. The compiler uses the warning to inform you that the return value of HideConsole is not used.

How to use string indexing with IDataReader in F#?

I'm new to F# and trying to dive in first and do a more formal introduction later. I have the following code:
type Person =
{
Id: int
Name: string
}
let GetPeople() =
//seq {
use conn = new SQLiteConnection(connectionString)
use cmd = new SQLiteCommand(sql, conn)
cmd.CommandType <- CommandType.Text
conn.Open()
use reader = cmd.ExecuteReader()
let mutable x = {Id = 1; Name = "Mary"; }
while reader.Read() do
let y = 0
// breakpoint here
x <- {
Id = unbox<int>(reader.["id"])
Name = unbox<string>(reader.["name"])
}
x
//}
let y = GetPeople()
I plan to replace the loop body with a yield statement and clean up the code. But right now I'm just trying to make sure the data access works by debugging the code and looking at the datareader. Currently I'm getting a System.InvalidCastException. When I put a breakpoint at the point indicated by the commented line above, and then type in the immediate windows reader["name"] I get a valid value from the database so I know it's connecting to the db ok. However if I try to put reader["name"] (as opposed to reader.["name"]) in the source file I get "This value is not a function and cannot be applied" message.
Why can I use reader["name"] in the immediate window but not in my fsharp code? How can I use string indexing with the reader?
Update
Following Jack P.'s advice I split out the code into separate lines and now I see where the error occurs:
let id = reader.["id"]
let id_unboxed = unbox id // <--- error on this line
id has the type object {long} according to the debugger.
Jack already answered the question regarding different syntax for indexing in F# and in the immediate window or watches, so I'll skip that.
In my experience, the most common reason for getting System.InvalidCastException when reading data from a database is that the value returned by reader.["xyz"] is actually DbNull.Value instead of an actual string or integer. Casting DbNull.Value to integer or string will fail (because it is a special value), so if you're working with nullable columns, you need to check this explicitly:
let name = reader.["name"]
let name_unboxed : string =
if name = DbNull.Value then null else unbox name
You can make the code nicer by defining the ? operator which allows you to write reader?name to perform the lookup. If you're dealing with nulls you can also use reader?name defaultValue with the following definition:
let (?) (reader:IDataReader) (name:string) (def:'R) : 'R =
let v = reader.[name]
if Object.Equals(v, DBNull.Value) then def
else unbox v
The code then becomes:
let name = reader?name null
let id = reader?id -1
This should also simplify debugging as you can step into the implementation of ? and see what is going on.
You can use reader["name"] in the immediate window because the immediate window uses C# syntax, not F# syntax.
One thing to note: since F# is much more concise than C#, there can be a lot going on within a single line. In other words, setting a breakpoint on the line may not help you narrow down the problem. In those cases, I normally "expand" the expression into multiple let-bindings on multiple lines; doing this makes it easier to step through the expression and find the cause of the problem (at which point, you can just make the change to your original one-liner).
What happens if you pull the item accesses and unbox calls out into their own let-bindings? For example:
while reader.Read() do
let y = 0
// breakpoint here
let id = reader.["id"]
let id_unboxed : int = unbox id
let name = reader.["name"]
let name_unboxed : string = unbox name
x <- { Id = id_unboxed; Name = name_unboxed; }
x

How do I create an F# list containing objects with a common superclass?

I have two functions, horizontal and vertical, for laying out controls. They work like this:
let verticalList = vertical [new TextBlock(Text = "one");
new TextBlock(Text = "two");
new TextBlock(Text = "three")]
Now verticalList is a control that displays the three textblocks vertically:
one
two
three
Here are the definitions:
let horizontal controls =
let wrap = new WrapPanel() in
List.iter (wrap.Children.Add >> ignore) controls ;
wrap
let vertical controls =
let stack = new StackPanel() in
List.iter (stack.Children.Add >> ignore) controls ;
stack
A problem occurs when I combine different types:
let foo = vertical [new TextBlock(Text = "Title"); vertical items]
This complains that the elements of the list are not of the same type. That is true, but they have a common supertype (UIElement).
I know I can use :> UIElement to upcast both items in the list, but this is an ugly solution. Can F# infer the common supertype. If not, why not?
It would be great if the nice looking
vertical [X; Y; Z]
doesn't have to become
vertical [(X :> UIElement); (Y :> UIElement); (Z :> UIElement)]
There are a few ways, including
type Animal() = class end
type Cat() =
inherit Animal()
type Dog() =
inherit Animal()
let animals1 : list<Animal> = [upcast new Cat(); upcast new Dog()]
let animals2 = ([upcast new Cat(); upcast new Dog()] : list<Animal>)
let animals3 = [(new Cat() :> Animal); upcast new Dog()]
animals1: type annotation at the var declaration, upcast each element
animals2: type annotation on the list expression, upcast each element
animals3: explicit type on first element, upcast rest
In a future version of F#, the upcast is likely to become unnecessary.
(See also http://cs.hubfs.net/forums/thread/9953.aspx , but nothing 'new' there.)
If you agree to sacrifice type safety for readability here you are the workaround:
open System
let to_list (tuple: Object) =
let rec list_after_index (n: int) =
let prop = tuple.GetType().GetMethod("get_Item"+n.ToString())
match prop with
| null -> []
| _ -> prop.Invoke(tuple, [||]) :: list_after_index(n+1)
match tuple with
| :? unit -> []
| _ when tuple.GetType().FullName.Contains(".Tuple`") -> list_after_index(1)
| _ -> [tuple]
then you can use it like that:
> to_list ();;
val it : obj list = []
> to_list (1);;
val it : obj list = [1]
> to_list([], 1, "2", 3.0);;
val it : obj list = [[]; 1; "2"; 3.0]
e.g. inside your vertical function.
I like my previous answer better, but building on that, if you have an app where you're constantly creating non-homogenous lists of animals, you can always do something like this:
let Animalize (x:Animal) = x // define a function to upcast
let animals4 = [ Animalize <| new Cat(); Animalize <| new Dog()]
// or even
let (~++) = Animalize // define a prefix operator to upcast (~ makes it prefix)
let animals5 = [ ++ new Cat(); ++ new Dog()]
The last is almost certainly an abuse of operators, unless you're in a very highly specialized domain and you constantly need some coercion like this and you're willing to sacrifice inherent readability for terseness.

Resources