How to find a parent visual in WPF using VisualTreeHelper in F#? - wpf

(Newbie question). I have need to climb the visual tree of a wpf xaml until finding a parent Grid of x:Name "FindMe". Assuming I have a visual child of "FindMe" called "grid", also a grid, how would I use the VisualTreeHelper in F# ?
let parent = VisualTreeHelper.GetParent(grid) as Grid <-- clearly wrong!
Thanks in advance for any suggestions!

Just call VisualTreeHelper.GetParent() recursively until you find it.
let rec getParent dp =
match VisualTreeHelper.GetParent dp with
| null -> None
| :? Grid as grid when grid.Name = "FindMe" -> Some grid
| parent -> getParent parent
Obviously this function could be made more general by passing in the type and name of the control you're looking for.

There is no as operator in F# but you could try this:
let parent = VisualTreeHelper.GetParent(grid)
if parent :? Grid then
...
else
...

You could introduce a few type extensions to DependencyObject for climbing the visual tree parent hierarchy, then use them to search for a parent Grid with a specified name.
First define:
let inline isNull (x:^T when ^T : not struct) = obj.ReferenceEquals (x, null)
type DependencyObject with
member t.GetVisualParents() =
t |> Seq.unfold (fun state -> if isNull state then None else Some(state, VisualTreeHelper.GetParent(state)))
|> Seq.skip(1)
member t.GetVisualParents<'T when 'T :> DependencyObject>() =
System.Linq.Enumerable.OfType<'T>(t.GetVisualParents())
member t.GetVisualParentByName<'T when 'T :> FrameworkElement>(name) =
t.GetVisualParents<'T> ()|> Seq.filter (fun x -> x.Name = name) |> Seq.tryHead
And then you can do:
let name = "FindMe"
let parent = grid.GetVisualParentByName<Grid>(name)
Notes:
There is no Seq.ofType so I just used Enumerable.OfType<'T>(). For other options see System.Linq.Enumerable.OfType<T> - is there a F# way? and F# Equivalent to Enumerable.OfType<'a>.
Seq.tryHead was introduced in f# 4.0. If you are using an earlier version, you could use Enumerable.FirstOrDefault<'T> from System.Linq.

Related

FSharp Charting and Winforms

I'm having some trouble combining F# Charting with Winforms.
I have a chart showing in the form fine initially, however I can't get it to "update" when the underlying data changes based on the GUI interaction.
recalcb.MouseClick
|> Event.add (fun evArgs ->
let chart1 = DataUpdate 1 |> Chart.BoxPlotFromData
let chart2 = DataUpdate 2 |> Chart.BoxPlotFromData
let urControl = new ChartControl(chart1, Dock=DockStyle.Fill, AutoSize=true)
let lrControl = new ChartControl(chart2, Dock=DockStyle.Fill, AutoSize=true)
urpanel.Controls.Add(urControl)
lrpanel.Controls.Add(lrControl)
printfn "tried to update"
)
The data is updating correctly (i.e. chart1 and chart2 show their updated values), but the chart isn't updated in the actual form.
I tried defining the control outside the event with initial data and calling Refresh() within the event but it doesn't help.
Here is a minimal example showing the issue (ideally the graph would update on each button press):
[<EntryPoint>]
[<STAThread>]
let main args =
let form = new Form(Visible = true, Width = 1310, Height = 730,FormBorderStyle=FormBorderStyle.FixedSingle,MaximizeBox = false)
let recalcb = new Button(AutoSize=true,Text="Recalculate")
form.Controls.Add(recalcb)
recalcb.MouseClick
|> Event.add (fun evArgs -> let nm = System.Random().Next(100)
let urControl = new ChartControl(Chart.Point[1 .. nm], Dock=DockStyle.Fill, AutoSize=true)
form.Controls.Add(urControl)
urControl.Refresh()
printfn "%A" nm
)
do Application.Run(form) |> ignore
0
UPDATE
Making the Chart a child of another control (i.e. Panel) and calling Clear() on the parent before readding will get this to work.

Multi-column lists in WPF with rich cell contents

I'm trying to write an automated test harness using WPF and F#. I'd like to display aggregate test results as rows in a multi-column list. Ideally, I would like to present rich content in each cell, e.g. highlighting issues with specific tests using color or making details available via tooltips. However, I cannot figure out how to put anything richer than a plain string into a multi-column ListView.
Furthermore, I'm not a fan of XAML or data binding and prefer to write vanilla F# code. The simplest program I have been able to write that displays a multi-column WPF ListView is:
open System.Windows
let gridView = Controls.GridView()
let listView = Controls.ListView(View=gridView)
type SystemParams = { Name: string; Value: obj }
[<System.STAThread>]
do
let column header binding =
let binding = Data.Binding binding
Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding)
for header, binding in ["Name", "Name"; "Value", "Value"] do
column header binding
|> gridView.Columns.Add
for prop in typeof<SystemParameters>.GetProperties() do
if prop.PropertyType <> typeof<ResourceKey> then
{ Name = prop.Name; Value = prop.GetValue(null, null) }
|> listView.Items.Add
|> ignore
Application().Run(Window(Content=listView)) |> ignore
Although this works, I don't like the way it requires the field names to be duplicated both in the type definition and as strings that are fed to WPF which presumably then uses reflection to resolve them at run-time (yuk!). Ideally, I would like to Add an obj array giving the WPF controls for each cell.
Is ListView capable of this? If so, how do you write a function that accepts a 2D array of controls and returns a ListView that visualizes them?
If not, I will probably use a Grid instead. I have tried DataGrid before and it is just a world of pain in comparison...
EDIT:
Thanks to the answers below, I have been able to come up with a solution. The multiColumnList function in the program below creates list of controls with the given headers and content with selectable rows:
open System.Windows
let multiColumnList columns contents onSelection =
let gridView = Controls.GridView()
let list = Controls.ListView(View=gridView)
let column index header =
let binding = Data.Binding(sprintf "[%d]" index)
Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding)
|> gridView.Columns.Add
Seq.iteri column columns
list.ItemsSource <-
[|for row in contents ->
[|for elt in row ->
box elt|]|]
list.SelectionChanged.Add onSelection
list
[<System.STAThread>]
do
let columns = ["Name"; "Value"]
let contents =
[ for prop in typeof<SystemParameters>.GetProperties() do
if prop.PropertyType <> typeof<ResourceKey> then
yield [box prop.Name; prop.GetValue(null, null)] ]
Application().Run(Window(Content=multiColumnList columns contents ignore))
|> ignore
Yes, it possible but it's a little tricky, but once you've mastered the approach it's quite flexible. WFP has a flexible templating system that this available both though code and XAML, except there are far less examples of how to do this in code.
It basically involves working how to to use the FrameworkElementFactory to override the list box default template and show the UI elements you want. Then using the Binding class to specify how the controls should be bound to the data.
I wrote a twitter client in WPF and F# and I use this approach to display the columns of tweets in list boxes. Take a look at how the createTweetContainerTemplate function works.
https://github.com/robertpi/TwitMemento/blob/master/Strangelights.TwitMemento.WPF/View.fs
Then again unless you really need a high level of control over how each row in the list box should be laid out, it maybe simpler to use a datagrid.
Your specific problem of duplicating the field names could be avoided by using an index based binding, instead of member name. See the changes to your example here:
open System
open System.Windows
let gridView = Controls.GridView()
let listView = Controls.ListView(View=gridView)
[<System.STAThread>]
do
let column index header =
let binding = Data.Binding(sprintf "[%d]" index)
Controls.GridViewColumn(Header=header, DisplayMemberBinding=binding)
["Name"; "Value"]
|> List.mapi column
|> List.iter gridView.Columns.Add
for prop in typeof<SystemParameters>.GetProperties() do
if prop.PropertyType <> typeof<ResourceKey> then
([| prop.Name; prop.GetValue(null, null) |] : Object array)
|> listView.Items.Add
|> ignore
Application().Run(Window(Content=listView)) |> ignore
Regarding giving the ListView a sequence of sequences of controls, that is somewhat lower level than ListView is intended to be used. ListView and DataGrid both assume that you have some roughly homogeneous collection of objects that you want show (generally as rows) and some idea of what information you want to show about those objects (the column definitions). Both controls will help in that situation, although I do agree that their general assumption that you want to use reflection over the members of a type can be annoying.
If you want to be able to specify a grid of any controls, then as you mention the Grid panel layout is probably more suitable.
I made a simple combinator library to build WPF UI thru code, I use this pattern in my pit project for creating HTML elements.
namespace FSharp.WPF
open System
open System.Windows
open System.Windows.Controls
[<AutoOpen>]
module Combinator =
type XDef =
| Attr of string * obj
| Tag of Type * XDef list
//| Element of FrameworkElement
[<AutoOpen>]
module Operators =
let (#=) (p:string) (v:obj) : XDef = Attr(p,v)
module internal Helpers =
let createEl (ty:Type) = new FrameworkElementFactory(ty)
let tag name attr = Tag(name,attr)
//let el dom = Element(dom)
let rec build (tag:XDef) =
match tag with
| Tag(ty,defs) ->
let attrs = defs |> List.choose(fun t -> match t with | Attr(k,v) -> Some(k,v) | _ -> None)
let tags = defs |> List.choose(fun t -> match t with | Tag(k,v) -> Some(t) | _ -> None)
/// create the element and set attributes
let el = Helpers.createEl(ty)
let rec setProps (d:(string*obj) list) =
match d with
| [] -> ()
| (p,v) :: t ->
let dp = System.ComponentModel.DependencyPropertyDescriptor.FromName(p, el.Type,el.Type)
el.SetValue(dp.DependencyProperty,v)
setProps attrs
let rec gen (d:XDef list) =
match d with
| [] -> ()
| h::t ->
let childEl = build(h)
el.AppendChild(childEl)
gen(t)
gen tags
el
//| Element(el) -> el
| Attr(key,value) -> failwith "Unrecognized sequence"
let make xdef =
let fEl = build xdef
let contentEl = new ContentControl()
contentEl.ContentTemplate <- new DataTemplate(VisualTree=fEl)
contentEl :> FrameworkElement
Its very low profile now, just create objects, but it could be extended to do much more with databinding and other things etc., and a bit of type checking should find errors in object creation.
Usage:
module Test =
open System.Windows.Controls
let create() =
tag typeof<System.Windows.Controls.Button> ["Content"#="Hello World"]
|> Combinator.make
let create2() =
tag typeof<StackPanel> [
tag typeof<Button> ["Content"#="Button 1"]
tag typeof<Button> ["Content"#="Button 2"]
]
|> Combinator.make
[<STAThread>]
[<EntryPoint>]
let main(_) =
let el = Test.create2() // Test.create1()
let window = new Window(Content = el, Height = 600.0, Width = 800.0, Title = "WpfApplication1")
let app = new Application()
app.Run(window)
As you see, nesting elements means Panel elements, but there could some extra leverage that allows the type to identify panel elements or content elements. But you get the idea, this could be useful. What do you think?
-Fahad

F# Display a WPF window asynchronously

This problem has been driving me mad. Here's the general gist:
I have two projects in a solution: the first is an F# console application and the second is a C# library with a C#+XAML class called DisplayWindow inheriting from the WPF Window. DisplayWindow has a method public void SetMessage(string s) {...} that makes the window display the text passed to it in big shiny letters that will probably also flash and spin arround and do everything else WPF is good at.
The problem is: From my F# program I need to make a function let openAWindow text = ??? so that it will open a new DisplayWindow asynchronously every time its called with the text. What is the best way to do this? Using async {} or System.Threading.Thread? Thanks for the help :)
Edit: I found this blog post http://deanchalk.com/2010/10/08/f-interacting-with-wpf-dispatcher-via-f-interactive-window/ that works but can sometimes (?) cause an ArgumentException with the error text "An entry with the same key already exists." so I have no idea whats going on there :(
I did this for our F# for Visualization library and then described the technique I used in my book Visual F# 2010 for Technical Computing.
Firstly, I wrote a lazy thunk that initializes WPF (including an STA UI thread and Application) when its evaluation is forced:
> let ui =
let mk() =
let wh = new ManualResetEvent(false)
let application = ref null
let start() =
let app = Application()
application := app
ignore(wh.Set())
app.Run() |> ignore
let thread = Thread start
thread.IsBackground <- true
thread.SetApartmentState ApartmentState.STA
thread.Start()
ignore(wh.WaitOne())
!application, thread
lazy(mk());;
val ui : Lazy<Application * Thread> = <unevaluated>
Then I wrote a spawn function that dispatches the application of a function f to an argument x such that it is run on the UI thread:
> let spawn : ('a -> 'b) -> 'a -> 'b =
fun f x ->
let app, thread = ui.Force()
let f _ =
try
let f_x = f x
fun () -> f_x
with e ->
fun () -> raise e
let t = app.Dispatcher.Invoke(DispatcherPriority.Send, System.Func<_, _>(f), null)
(t :?> unit -> 'b)();;
val spawn : ('a -> 'b) -> 'a -> 'b
Now it is just a case of invoking your openAWindow function on the UI thread with:
let openAWindow text =
DisplayWindow().SetMessage text
spawn openAWindow text

Game Using F# and Silverlight: Avoiding Mutable State

I'm trying to write a game using F# and Silverlight and am struggling a bit with immutability.
I want to decouple the game from the view a little, so I put it in a module and made its update function return a new instance of the world-state, thus providing immutability.
The view (AppControl) is responsible for drawing the world.
However, I see no way around making the world a ref cell in the view.
Now, I think the mutable state is local enough to not cause any problems (please correct me, if I'm wrong), I am just curious, if someone can think of a way to avoid mutable state completely?
Here's the outline of the application, I tried to reduce the problem down to the essence:
open System
open System.Windows
open System.Windows.Controls
open System.Windows.Media
module Game =
type World = { State : int }
let init() =
{ State = 0 }
// immutable update loop
let updateWorld world =
{ State = world.State + 1 }
type AppControl() =
inherit UserControl()
let canvas = new Canvas()
let textBlock = new TextBlock()
let world = Game.init() |> ref // mutable world
let drawWorld (world : Game.World) =
textBlock.Text <- world.State.ToString()
// mutating game loop
let gameLoop world =
world := Game.updateWorld !world
drawWorld !world
()
do
canvas.Children.Add(textBlock)
base.Content <- canvas
CompositionTarget.Rendering.Add (fun _ -> gameLoop world)
type App() as this =
inherit Application()
let main = new AppControl()
do this.Startup.Add(fun _ -> this.RootVisual <- main)
The structure of your code looks fine - the mutable state is localized in the user interface (which is mutable anyway), so it is fine. You're not mutating the field from any closure, so you could use a mutable field (declared using let mutable world = ..) instead of ref cell.
To completely avoid the mutation, you can use asynchronous workflow (running on the GUI thread):
type AppControl() =
inherit UserControl()
let canvas = new Canvas()
let textBlock = new TextBlock()
let drawWorld (world : Game.World) =
textBlock.Text <- world.State.ToString()
// Asynchronous loop that waits for 'Rendering', updates
// the world & draws it and then continues waiting
let gameLoop world = async {
let! _ = Async.AwaitEvent CompositionTarget.Rendering
let newWorld = Game.updateWorld world
drawWorld newWorld
return! gameLoop newWorld }
do
canvas.Children.Add(textBlock)
base.Content <- canvas
gameLoop Game.init() |> Async.StartImmediate
The gameLoop function is asynchronous, so it doesn't block any thread. It is started using Async.StartImmediate, which means that it will run only on GUI thread (so accessing GUI elements & events from the body is safe). Inside the function, you can wait for event occurrence (using Async.AwaitEvent) and then do some action. The last line (return!) is a tail-call, so the function will continue running until the application is closed.

Drag and Drop in Silverlight with F# and Asynchronous Workflows

I'm trying to implement drag and drop in Silverlight using F# and asynchronous workflows.
I'm simply trying to drag around a rectangle on the canvas, using two loops for the the two states (waiting and dragging), an idea I got from Tomas Petricek's book "Real-world Functional Programming", but I ran into a problem:
Unlike WPF or WinForms, Silverlight's MouseEventArgs do not carry information about the button state, so I can't return from the drag-loop by checking if the left mouse button is no longer pressed. I only managed to solve this by introducing a mutable flag.
Would anyone have a solution for this, that does not involve mutable state?
Here's the relevant code part (please excuse the sloppy dragging code, which snaps the rectangle to the mouse pointer):
type MainPage() as this =
inherit UserControl()
do
Application.LoadComponent(this, new System.Uri("/SilverlightApplication1;component/Page.xaml", System.UriKind.Relative))
let layoutRoot : Canvas = downcast this.FindName("LayoutRoot")
let rectangle1 : Rectangle = downcast this.FindName("Rectangle1")
let mutable isDragged = false
do
rectangle1.MouseLeftButtonUp.Add(fun _ -> isDragged <- false)
let rec drag() = async {
let! args = layoutRoot.MouseMove |> Async.AwaitEvent
if (isDragged) then
Canvas.SetLeft(rectangle1, args.GetPosition(layoutRoot).X)
Canvas.SetTop(rectangle1, args.GetPosition(layoutRoot).Y)
return! drag()
else
return()
}
let wait() = async {
while true do
let! args = Async.AwaitEvent rectangle1.MouseLeftButtonDown
isDragged <- true
do! drag()
}
Async.StartImmediate(wait())
()
Thank you very much for your time!
The way to solve this issue is to use an overloaded AwaitEvent that allows you to wait for two events. Instead of just waiting for MouseMove, you can also wait for the MouseUp event - in the first case, you can continue moving and in the second case, you can return from the loop and stop drag&drop (this is actually discussed later in the book in section 16.4.5).
Here is the code - it actually uses AwaitObservable variant of the method (see below), which is a better choice in general, because it works with Observable.map and similar combinators (in case you wanted to use these).
let! args = Async.AwaitObservable(layoutRoot.MouseMove, layoutRoot.MouseUp)
match args with
| Choice1Of2(args) ->
// Handle the 'MouseMove' event with 'args' here
Canvas.SetLeft(rectangle1, args.GetPosition(layoutRoot).X)
Canvas.SetTop(rectangle1, args.GetPosition(layoutRoot).Y)
return! drag()
| Choice2Of2(_) ->
// Handle the 'MouseUp' event here
return()
As far as I know, the overloaded AwaitObservable method is not available in the F# libraries (yet), but you can get it from the book's web site, or you can use the following code:
// Adds 'AwaitObservable' that takes two observables and returns
// Choice<'a, 'b> containing either Choice1Of2 or Choice2Of2 depending
// on which of the observables occurred first
type Microsoft.FSharp.Control.Async with
static member AwaitObservable(ev1:IObservable<'a>, ev2:IObservable<'b>) =
Async.FromContinuations((fun (cont,econt,ccont) ->
let rec callback1 = (fun value ->
remover1.Dispose()
remover2.Dispose()
cont(Choice1Of2(value)) )
and callback2 = (fun value ->
remover1.Dispose()
remover2.Dispose()
cont(Choice2Of2(value)) )
// Attach handlers to both observables
and remover1 : IDisposable = ev1.Subscribe(callback1)
and remover2 : IDisposable = ev2.Subscribe(callback2)
() ))

Resources