22 people like it.

Building a WPF application in functional way

I started to write pure F# + WPF application in about half a year ago. Today, I found a good way to compose WPF controls with dependent values. It's only writing a dependency object type as a class and give it to constructors of GUI controls. In this snippet "Volume","ColorVolume" and "ShapeContainer" has no properties. But works as a View which represents internal Model and allows users to change internal data. You only need calling a constructor of them. It means that you can compose GUI controls and it's functionality as a immutable data structure. (Update 2011/12/02 8:33:00(UTC+09:00) : Removed some user defined operators and renamed a type similar to DependencyObject in this snippet Reactor to SharedValue.) (Update 2011/12/02 9:04:01(UTC+09:00) : renamed some variables..)

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
#r @"PresentationCore"
#r @"PresentationFramework"
#r @"WindowsBase"
#r @"System.Xaml"
#r @"UIAutomationTypes"

open System
open System.Windows          
open System.Windows.Controls  
open System.Windows.Media
open System.Windows.Shapes

/// This operator is similar to (|>). 
/// But, it returns argument as a return value.
/// Then you can chain functions which returns unit.
let ($) x f = f x ; x
/// This operator removes parenthesis..
let (=>) (o:IObservable<_>) f = o.Add f

let (<+>) = Observable.merge
/// Similar to applicative functor in Haskell. But the arguments are flipped
let (|+>) ev f = Observable.map f ev

type StackPanel with
  /// Helper function to compose a GUI
  member o.add x = o.Children.Add x |> ignore

/// This container is used by some controls to share a variable.
/// If the value is changed, it fires changed event.
/// Controls should have this instead of their own internal data
type Reactor<'a when 'a : equality>(value:'a) =
  let mutable _value = value
  let changed = Event<'a>()
  member o.Get       = _value
  member o.Set value =
    let old = _value 
    _value <- value
    if old <> _value then _value |> changed.Trigger
  member o.Changed = changed.Publish

/// Volume control , it shows a value and allows you to change it.
type Volume(title:string, range:int * int, re:Reactor<int>) as this =
  inherit StackPanel(Orientation=Orientation.Horizontal)
  do Label(Content=title,Width=50.) |>this.add 
  let label  = Label(Content=re.Get,Width=50.) $ this.add
  let slider = Slider(Minimum=float(fst range), Maximum=float(snd range), TickFrequency=2., Width=127.) $ this.add
  let changedHandler value =
    label.Content <- string value
    slider.Value  <- float value
  do
    slider.ValueChanged => fun arg -> int arg.NewValue |> re.Set
    re.Get     |> changedHandler
    re.Changed => changedHandler

/// Volume control of a color
type ColorVolume (re:Reactor<Color>) as this =
  inherit StackPanel(Orientation=Orientation.Vertical)
  let alpha = Reactor(int re.Get.A)
  let red   = Reactor(int re.Get.R)
  let green = Reactor(int re.Get.G)
  let blue  = Reactor(int re.Get.B)
  do
    alpha.Changed <+> red.Changed <+> green.Changed <+> blue.Changed => fun _ ->
      re.Set(Color.FromArgb(byte alpha.Get,byte red.Get,byte green.Get,byte blue.Get))
    re.Changed => fun color ->
      alpha.Set (int color.A)
      red.Set   (int color.R)
      green.Set (int color.G)
      blue.Set  (int color.B)

    Volume("Alpha", (0,255), alpha) |> this.add
    Volume("Red"  , (0,255), red  ) |> this.add
    Volume("Green", (0,255), green) |> this.add
    Volume("Blue" , (0,255), blue ) |> this.add


[<RequireQualifiedAccess>]
type MyShapes = Rectangle | Ellipse
/// Shape container control which reacts when properties of a shape is changed.
type ShapeContainer(shapes:Reactor<MyShapes>,width:Reactor<int>,height:Reactor<int>,color:Reactor<Color>) as this =
  inherit Label(Width=250., Height=250.)
  let mutable shape = Ellipse() :> Shape
  let setWidth  width  = shape.Width  <- float width
  let setHeight height = shape.Height <- float height
  let setColor  color  = shape.Fill   <- SolidColorBrush(color)
  let initShape () =
    this.Content <- shape
    setWidth  width.Get
    setHeight height.Get
    setColor  color.Get
  let setShape du =
    match du with
      | MyShapes.Rectangle -> shape <- Rectangle()
      | MyShapes.Ellipse   -> shape <- Ellipse  ()
    initShape ()
  do
    initShape ()
    width.Changed  => setWidth
    height.Changed => setHeight
    color.Changed  => setColor 
    shapes.Changed => setShape


/// This StackPanel contains every controls in this program
let stackPanel = StackPanel(Orientation=Orientation.Vertical)

/// Width reactor object
let width = Reactor(120)
Volume("Width",(50, 240),width) |> stackPanel.add // add a volume to the StackPanel

/// Height reactor object
let height = Reactor(80)
Volume("Height",(50, 200),height) |> stackPanel.add // add a volume to the StackPanel

/// Color reactor object
let color = Reactor(Colors.Blue)
ColorVolume(color) |> stackPanel.add // add volumes to the StackPanel

/// Shape reactor object
let shapes = Reactor(MyShapes.Ellipse)
let ellipseButton   = Button(Content="Ellipse")   $ stackPanel.add
let rectangleButton = Button(Content="Rectangle") $ stackPanel.add
ellipseButton.Click   => fun _ -> shapes.Set MyShapes.Ellipse   // add event handler to fire dependency calculation
rectangleButton.Click => fun _ -> shapes.Set MyShapes.Rectangle

// This is a shape control shown in the bottom of this program's window
ShapeContainer(shapes,width,height,color) |> stackPanel.add

// Make a window and show it
let window = Window(Title="F# is fun!",Width=260., Height=420., Content=stackPanel)
window.Show()
namespace System
namespace System.Windows
namespace System.Windows.Controls
namespace System.Windows.Media
namespace System.Windows.Shapes
val x : 'a
val f : ('a -> unit)
val o : IObservable<'a>
type IObservable<'T> =
  member Subscribe : observer:IObserver<'T> -> IDisposable

Full name: System.IObservable<_>
member IObservable.Add : callback:('T -> unit) -> unit
module Observable

from Microsoft.FSharp.Control
val merge : source1:IObservable<'T> -> source2:IObservable<'T> -> IObservable<'T>

Full name: Microsoft.FSharp.Control.Observable.merge
val ev : IObservable<'a>
val f : ('a -> 'b)
val map : mapping:('T -> 'U) -> source:IObservable<'T> -> IObservable<'U>

Full name: Microsoft.FSharp.Control.Observable.map
Multiple items
type StackPanel =
  inherit Panel
  new : unit -> StackPanel
  member CanHorizontallyScroll : bool with get, set
  member CanVerticallyScroll : bool with get, set
  member ExtentHeight : float
  member ExtentWidth : float
  member HorizontalOffset : float
  member LineDown : unit -> unit
  member LineLeft : unit -> unit
  member LineRight : unit -> unit
  member LineUp : unit -> unit
  ...

Full name: System.Windows.Controls.StackPanel

--------------------
StackPanel() : unit
val o : StackPanel
member StackPanel.add : x:UIElement -> unit

Full name: Script.add


 Helper function to compose a GUI
val x : UIElement
property Panel.Children: UIElementCollection
UIElementCollection.Add(element: UIElement) : int
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
Multiple items
type Reactor<'a (requires equality)> =
  new : value:'a -> Reactor<'a>
  member Set : value:'a -> unit
  member Changed : IEvent<'a>
  member Get : 'a

Full name: Script.Reactor<_>


 This container is used by some controls to share a variable.
 If the value is changed, it fires changed event.
 Controls should have this instead of their own internal data


--------------------
new : value:'a -> Reactor<'a>
val value : 'a (requires equality)
val changed : Event<'a> (requires equality)
Multiple items
module Event

from Microsoft.FSharp.Control

--------------------
type Event<'T> =
  new : unit -> Event<'T>
  member Trigger : arg:'T -> unit
  member Publish : IEvent<'T>

Full name: Microsoft.FSharp.Control.Event<_>

--------------------
type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate)> =
  new : unit -> Event<'Delegate,'Args>
  member Trigger : sender:obj * args:'Args -> unit
  member Publish : IEvent<'Delegate,'Args>

Full name: Microsoft.FSharp.Control.Event<_,_>

--------------------
new : unit -> Event<'T>

--------------------
new : unit -> Event<'Delegate,'Args>
val o : Reactor<'a> (requires equality)
member Reactor.Get : 'a

Full name: Script.Reactor`1.Get
val mutable _value : 'a (requires equality)
Multiple items
member Reactor.Set : value:'a -> unit

Full name: Script.Reactor`1.Set

--------------------
module Set

from Microsoft.FSharp.Collections

--------------------
type Set<'T (requires comparison)> =
  interface IComparable
  interface IEnumerable
  interface IEnumerable<'T>
  interface ICollection<'T>
  new : elements:seq<'T> -> Set<'T>
  member Add : value:'T -> Set<'T>
  member Contains : value:'T -> bool
  override Equals : obj -> bool
  member IsProperSubsetOf : otherSet:Set<'T> -> bool
  member IsProperSupersetOf : otherSet:Set<'T> -> bool
  ...

Full name: Microsoft.FSharp.Collections.Set<_>

--------------------
new : elements:seq<'T> -> Set<'T>
val old : 'a (requires equality)
member Event.Trigger : arg:'T -> unit
member Reactor.Changed : IEvent<'a>

Full name: Script.Reactor`1.Changed
property Event.Publish: IEvent<'a>
Multiple items
type Volume =
  inherit StackPanel
  new : title:string * range:(int * int) * re:Reactor<int> -> Volume

Full name: Script.Volume


 Volume control , it shows a value and allows you to change it.


--------------------
new : title:string * range:(int * int) * re:Reactor<int> -> Volume
val title : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val range : int * int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val re : Reactor<int>
val this : Volume
type Orientation =
  | Horizontal = 0
  | Vertical = 1

Full name: System.Windows.Controls.Orientation
field Orientation.Horizontal = 0
Multiple items
type Label =
  inherit ContentControl
  new : unit -> Label
  member Target : UIElement with get, set
  static val TargetProperty : DependencyProperty

Full name: System.Windows.Controls.Label

--------------------
Label() : unit
val label : Label
property Reactor.Get: int
member StackPanel.add : x:UIElement -> unit


 Helper function to compose a GUI
val slider : Slider
Multiple items
type Slider =
  inherit RangeBase
  new : unit -> Slider
  member AutoToolTipPlacement : AutoToolTipPlacement with get, set
  member AutoToolTipPrecision : int with get, set
  member Delay : int with get, set
  member Interval : int with get, set
  member IsDirectionReversed : bool with get, set
  member IsMoveToPointEnabled : bool with get, set
  member IsSelectionRangeEnabled : bool with get, set
  member IsSnapToTickEnabled : bool with get, set
  member OnApplyTemplate : unit -> unit
  ...

Full name: System.Windows.Controls.Slider

--------------------
Slider() : unit
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
val snd : tuple:('T1 * 'T2) -> 'T2

Full name: Microsoft.FSharp.Core.Operators.snd
val changedHandler : (int -> unit)
val value : int
property ContentControl.Content: obj
property Primitives.RangeBase.Value: float
event Primitives.RangeBase.ValueChanged: IEvent<RoutedPropertyChangedEventHandler<float>,RoutedPropertyChangedEventArgs<float>>
val arg : RoutedPropertyChangedEventArgs<float>
property RoutedPropertyChangedEventArgs.NewValue: float
member Reactor.Set : value:'a -> unit
property Reactor.Changed: IEvent<int>
Multiple items
type ColorVolume =
  inherit StackPanel
  new : re:Reactor<Color> -> ColorVolume

Full name: Script.ColorVolume


 Volume control of a color


--------------------
new : re:Reactor<Color> -> ColorVolume
val re : Reactor<Color>
type Color =
  struct
    member A : byte with get, set
    member B : byte with get, set
    member Clamp : unit -> unit
    member ColorContext : ColorContext
    member Equals : color:Color -> bool + 1 overload
    member G : byte with get, set
    member GetHashCode : unit -> int
    member GetNativeColorValues : unit -> float32[]
    member R : byte with get, set
    member ScA : float32 with get, set
    ...
  end

Full name: System.Windows.Media.Color
val this : ColorVolume
field Orientation.Vertical = 1
val alpha : Reactor<int>
property Reactor.Get: Color
property Color.A: byte
val red : Reactor<int>
property Color.R: byte
val green : Reactor<int>
property Color.G: byte
val blue : Reactor<int>
property Color.B: byte
Color.FromArgb(a: byte, r: byte, g: byte, b: byte) : Color
Multiple items
val byte : value:'T -> byte (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.byte

--------------------
type byte = Byte

Full name: Microsoft.FSharp.Core.byte
property Reactor.Changed: IEvent<Color>
val color : Color
Multiple items
type RequireQualifiedAccessAttribute =
  inherit Attribute
  new : unit -> RequireQualifiedAccessAttribute

Full name: Microsoft.FSharp.Core.RequireQualifiedAccessAttribute

--------------------
new : unit -> RequireQualifiedAccessAttribute
type MyShapes =
  | Rectangle
  | Ellipse

Full name: Script.MyShapes
Multiple items
union case MyShapes.Rectangle: MyShapes

--------------------
type Rectangle =
  inherit Shape
  new : unit -> Rectangle
  member GeometryTransform : Transform
  member RadiusX : float with get, set
  member RadiusY : float with get, set
  member RenderedGeometry : Geometry
  static val RadiusXProperty : DependencyProperty
  static val RadiusYProperty : DependencyProperty

Full name: System.Windows.Shapes.Rectangle

--------------------
Rectangle() : unit
Multiple items
union case MyShapes.Ellipse: MyShapes

--------------------
type Ellipse =
  inherit Shape
  new : unit -> Ellipse
  member GeometryTransform : Transform
  member RenderedGeometry : Geometry

Full name: System.Windows.Shapes.Ellipse

--------------------
Ellipse() : unit
Multiple items
type ShapeContainer =
  inherit Label
  new : shapes:Reactor<MyShapes> * width:Reactor<int> * height:Reactor<int> * color:Reactor<Color> -> ShapeContainer

Full name: Script.ShapeContainer


 Shape container control which reacts when properties of a shape is changed.


--------------------
new : shapes:Reactor<MyShapes> * width:Reactor<int> * height:Reactor<int> * color:Reactor<Color> -> ShapeContainer
val shapes : Reactor<MyShapes>
val width : Reactor<int>
val height : Reactor<int>
val color : Reactor<Color>
val this : ShapeContainer
val mutable shape : Shape
Multiple items
type Ellipse =
  inherit Shape
  new : unit -> Ellipse
  member GeometryTransform : Transform
  member RenderedGeometry : Geometry

Full name: System.Windows.Shapes.Ellipse

--------------------
Ellipse() : unit
type Shape =
  inherit FrameworkElement
  member Fill : Brush with get, set
  member GeometryTransform : Transform
  member RenderedGeometry : Geometry
  member Stretch : Stretch with get, set
  member Stroke : Brush with get, set
  member StrokeDashArray : DoubleCollection with get, set
  member StrokeDashCap : PenLineCap with get, set
  member StrokeDashOffset : float with get, set
  member StrokeEndLineCap : PenLineCap with get, set
  member StrokeLineJoin : PenLineJoin with get, set
  ...

Full name: System.Windows.Shapes.Shape
val setWidth : (int -> unit)
val width : int
property FrameworkElement.Width: float
val setHeight : (int -> unit)
val height : int
property FrameworkElement.Height: float
val setColor : (Color -> unit)
property Shape.Fill: Brush
Multiple items
type SolidColorBrush =
  inherit Brush
  new : unit -> SolidColorBrush + 1 overload
  member Clone : unit -> SolidColorBrush
  member CloneCurrentValue : unit -> SolidColorBrush
  member Color : Color with get, set
  static val ColorProperty : DependencyProperty
  static member DeserializeFrom : reader:BinaryReader -> obj

Full name: System.Windows.Media.SolidColorBrush

--------------------
SolidColorBrush() : unit
SolidColorBrush(color: Color) : unit
val initShape : (unit -> unit)
val setShape : (MyShapes -> unit)
val du : MyShapes
union case MyShapes.Rectangle: MyShapes
Multiple items
type Rectangle =
  inherit Shape
  new : unit -> Rectangle
  member GeometryTransform : Transform
  member RadiusX : float with get, set
  member RadiusY : float with get, set
  member RenderedGeometry : Geometry
  static val RadiusXProperty : DependencyProperty
  static val RadiusYProperty : DependencyProperty

Full name: System.Windows.Shapes.Rectangle

--------------------
Rectangle() : unit
union case MyShapes.Ellipse: MyShapes
property Reactor.Changed: IEvent<MyShapes>
val stackPanel : StackPanel

Full name: Script.stackPanel


 This StackPanel contains every controls in this program
val width : Reactor<int>

Full name: Script.width


 Width reactor object
val height : Reactor<int>

Full name: Script.height


 Height reactor object
val color : Reactor<Color>

Full name: Script.color


 Color reactor object
type Colors =
  static member AliceBlue : Color
  static member AntiqueWhite : Color
  static member Aqua : Color
  static member Aquamarine : Color
  static member Azure : Color
  static member Beige : Color
  static member Bisque : Color
  static member Black : Color
  static member BlanchedAlmond : Color
  static member Blue : Color
  ...

Full name: System.Windows.Media.Colors
property Colors.Blue: Color
val shapes : Reactor<MyShapes>

Full name: Script.shapes


 Shape reactor object
val ellipseButton : Button

Full name: Script.ellipseButton
Multiple items
type Button =
  inherit ButtonBase
  new : unit -> Button
  member IsCancel : bool with get, set
  member IsDefault : bool with get, set
  member IsDefaulted : bool
  static val IsDefaultProperty : DependencyProperty
  static val IsCancelProperty : DependencyProperty
  static val IsDefaultedProperty : DependencyProperty

Full name: System.Windows.Controls.Button

--------------------
Button() : unit
val rectangleButton : Button

Full name: Script.rectangleButton
event Primitives.ButtonBase.Click: IEvent<RoutedEventHandler,RoutedEventArgs>
val window : Window

Full name: Script.window
Multiple items
type Window =
  inherit ContentControl
  new : unit -> Window
  member Activate : unit -> bool
  member AllowsTransparency : bool with get, set
  member Close : unit -> unit
  member DialogResult : Nullable<bool> with get, set
  member DragMove : unit -> unit
  member Hide : unit -> unit
  member Icon : ImageSource with get, set
  member IsActive : bool
  member Left : float with get, set
  ...

Full name: System.Windows.Window

--------------------
Window() : unit
Window.Show() : unit
Next Version Raw view Test code New version

More information

Link:http://fssnip.net/9q
Posted:12 years ago
Author:nagat01
Tags: wpf , gui , reactive