Author Topic: F# - Use WPF dialog button to draw a line  (Read 6260 times)

0 Members and 1 Guest are viewing this topic.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
F# - Use WPF dialog button to draw a line
« on: May 19, 2015, 07:25:23 PM »
Hi All,
this is a discussion on an F# version from this thread http://www.theswamp.org/index.php?topic=49471.0

I'm interested in the best way to do this as WPF and F# are not as advanced as one would like.
It's quite doable but takes a bit more of a lower level way of doing things.

Here's my quick interpretation to get the ball rolling, cheers. (Using Bricscad V15, change assembly ref's as required for AutoCAD)

The Xaml
Code - XML: [Select]
  1. <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2.        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3.        xmlns:g='clr-namespace:WpfDialogSample;assembly=WpfDialogSample'
  4.        Title="Window1" Height="100" Width="200">
  5.  
  6.     <Grid>
  7.         <Button x:Name="DrawLine" Content="Draw Line" Margin="10" Width="100" Height="30"/>
  8.     </Grid>
  9. </Window>
  10.  

The F# Code
Code - F#: [Select]
  1. namespace WpfDialogSample
  2.  
  3. open System
  4. open System.IO
  5. open System.Windows
  6. open System.Windows.Controls
  7. open System.Windows.Markup
  8.  
  9. open Teigha.Runtime
  10. open Teigha.DatabaseServices
  11. open Teigha.Geometry
  12. open Bricscad.EditorInput
  13. open Bricscad.ApplicationServices
  14.  
  15. type AcAp = Bricscad.ApplicationServices.Application
  16.  
  17. // this should be refactored out to another file
  18. module CadWorker =
  19.     let DrawLine (window:Window) =
  20.         let doc = AcAp.DocumentManager.MdiActiveDocument;
  21.         let db = doc.Database;
  22.  
  23.         let p1 = new PromptPointOptions("Specify base point :");
  24.         let rep1 = doc.Editor.GetPoint(p1);
  25.         let pt1 = rep1.Value;
  26.  
  27.         if (rep1.Status <> PromptStatus.Cancel) then
  28.             let mutable p2 = new PromptPointOptions("Next point :")
  29.             p2.BasePoint <- pt1
  30.             p2.UseBasePoint <- true
  31.             let rep2 = doc.Editor.GetPoint(p2)
  32.             let pt2 = rep2.Value
  33.             if (rep2.Status <> PromptStatus.Cancel) then
  34.                 use tr = db.TransactionManager.StartTransaction()
  35.                 let mSpace = tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForWrite) :?> BlockTableRecord
  36.                 let mutable line = new Line(pt1, pt2);
  37.                 line.ColorIndex <- 6;
  38.                 mSpace.AppendEntity(line) |> ignore
  39.                 tr.AddNewlyCreatedDBObject(line, true)
  40.                 tr.Commit()
  41.         // a bit of a hack to close the dialog when done...
  42.         window.Close()
  43.  
  44. type CommandMethods() =
  45.  
  46.     [<CommandMethod("TESTWPF", CommandFlags.Modal)>]
  47.     member this.test() =
  48.         // create the dialog by loading the xaml as a Window
  49.         let win = XamlReader.Load(File.OpenRead("Window1.xaml")) :?> Window
  50.  
  51.         // Hook into xaml button element here
  52.         let btn = win.FindName("DrawLine") :?> Button
  53.  
  54.         // Handle click event on 'DrawLine' button, we pass the window so we can close it when done (i.e. a hack)
  55.         do btn.Click.Add( fun _ -> CadWorker.DrawLine win)
  56.         win.Show()
  57.  
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: F# - Use WPF dialog button to draw a line
« Reply #1 on: May 21, 2015, 02:58:01 AM »
Nice Mick !

Starting from your example, I tried to make it closer to the MVVM pattern.

The Xaml
The button is bound to a Command which parameter is set to the window so that the command can close the window
Code - XML: [Select]
  1. <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2.        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3.        xmlns:ViewModel="clr-namespace:ViewModel;assembly=AcadFsWpf"
  4.        Name="mainWindow"
  5.        Title="Draw Line"
  6.        WindowStartupLocation="CenterOwner" Height="100" Width="200">
  7.     <Window.DataContext>
  8.         <ViewModel:ViewModel/>
  9.     </Window.DataContext>
  10.     <Grid>
  11.         <Button x:Name="drawLine" Content="Draw Line" Margin="10" Width="100" Height="30"
  12.                Command="{Binding DrawLineCommand}" CommandParameter="{Binding ElementName=mainWindow}"/>
  13.     </Grid>
  14. </Window>

Model
Code - F#: [Select]
  1. namespace Model
  2.  
  3. open System.Windows
  4. open Autodesk.AutoCAD.ApplicationServices
  5. open Autodesk.AutoCAD.DatabaseServices
  6. open Autodesk.AutoCAD.EditorInput
  7. open Autodesk.AutoCAD.Geometry
  8. open Autodesk.AutoCAD.Runtime
  9.  
  10. type AcAp = Autodesk.AutoCAD.ApplicationServices.Application
  11.  
  12. module CadWorker =
  13.     let drawLine (param : obj) =
  14.         let doc = AcAp.DocumentManager.MdiActiveDocument
  15.         let db = doc.Database
  16.         let ed = doc.Editor
  17.         Autodesk.AutoCAD.Internal.Utils.SetFocusToDwgView();
  18.         let ppr = ed.GetPoint("\nStart point: ")
  19.         if ppr.Status = PromptStatus.OK then
  20.             let p1 = ppr.Value
  21.             let opts = PromptPointOptions("\nEnd point: ", BasePoint = p1, UseBasePoint = true)
  22.             let ppr = ed.GetPoint(opts)
  23.             if ppr.Status = PromptStatus.OK then
  24.                 use docLock = doc.LockDocument()
  25.                 use tr = db.TransactionManager.StartTransaction()
  26.                 let btr = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) :?> BlockTableRecord
  27.                 let line = new Line(p1, ppr.Value)
  28.                 btr.AppendEntity(line) |> ignore
  29.                 tr.AddNewlyCreatedDBObject(line, true)
  30.                 tr.Commit();
  31.                 (param |> unbox<Window>).Close()

View
Code - F#: [Select]
  1. namespace View
  2.  
  3. open System
  4. open System.IO
  5. open System.Windows
  6. open System.Windows.Markup
  7. open System.Xaml
  8. open Autodesk.AutoCAD.ApplicationServices
  9. open Autodesk.AutoCAD.Runtime
  10.  
  11. type AcAp = Autodesk.AutoCAD.ApplicationServices.Application
  12.  
  13. type CommandMethods() =
  14.     [<CommandMethod("Test")>]
  15.     member x.Test () =
  16.         let win = XamlReader.Load(File.OpenRead("MainWindow.xaml")) :?> Window
  17.         AcAp.ShowModelessWindow(win)

ViewModel
Code - F#: [Select]
  1. namespace ViewModel
  2.  
  3. open System
  4. open System.Windows.Input
  5. open Model
  6.  
  7. type RelayCommand (canExecute:(obj -> bool), action:(obj -> unit)) =
  8.     let event = new DelegateEvent<EventHandler>()
  9.     interface ICommand with
  10.         [<CLIEvent>]
  11.         member x.CanExecuteChanged = event.Publish
  12.         member x.CanExecute arg = canExecute(arg)
  13.         member x.Execute arg = action(arg)
  14.  
  15. type ViewModel() =
  16.      member x.DrawLineCommand =
  17.         new RelayCommand(
  18.             (fun _ -> true),
  19.             (fun p -> CadWorker.drawLine p))  
Speaking English as a French Frog

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: F# - Use WPF dialog button to draw a line
« Reply #2 on: May 21, 2015, 03:08:36 AM »
Very nice!

I'll have to absorb that for a bit but it looks pretty straight forward.

I tried to use
Code - F#: [Select]
  1. let win = XamlReader.Load(File.OpenRead("MainWindow.xaml")) :?> Window
  2. AcAp.ShowModelessWindow(win)
  3.  

but got a cast error with the win object not being of type 'Form'. I'll re-check my references with yours, maybe it's a Bricscad thing(?).

Thanks for your input Gile.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: F# - Use WPF dialog button to draw a line
« Reply #3 on: May 21, 2015, 03:34:01 AM »
Attached a screenshot with my references.

The next step should be to add a base type in the ViewModel which implements INotifyPropertyChanged so that the bound properties can notify a changed property.

Code - F#: [Select]
  1. namespace ViewModel
  2.  
  3. open System
  4. open System.Windows.Input
  5. open System.ComponentModel
  6. open Model
  7.  
  8. type ViewModelBase() =
  9.     let propertyChangedEvent = new DelegateEvent<PropertyChangedEventHandler>()
  10.     interface INotifyPropertyChanged with
  11.         [<CLIEvent>]
  12.         member x.PropertyChanged = propertyChangedEvent.Publish
  13.     member x.OnPropertyChanged propertyName =
  14.         propertyChangedEvent.Trigger([| x; new PropertyChangedEventArgs(propertyName) |])
  15.  
  16. type RelayCommand (canExecute:(obj -> bool), action:(obj -> unit)) =
  17.     let event = new DelegateEvent<EventHandler>()
  18.     interface ICommand with
  19.         [<CLIEvent>]
  20.         member x.CanExecuteChanged = event.Publish
  21.         member x.CanExecute arg = canExecute(arg)
  22.         member x.Execute arg = action(arg)
  23.  
  24. type ViewModel() =
  25.     inherit ViewModelBase()
  26.     member x.DrawLineCommand =
  27.         new RelayCommand(
  28.             (fun _ -> true),
  29.             (fun p -> CadWorker.drawLine p))  
Speaking English as a French Frog

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: F# - Use WPF dialog button to draw a line
« Reply #4 on: May 22, 2015, 05:36:17 PM »
Hi,

Mick, I really thank you.
You have motivated me to get back a little to F# that I did not have much time to learn these days.
As I also try to learn WPF, mixing the two is fun.

Here's my last attempt. I tried to go a little further with WPF binding features and include some F# helpers (thanks to kaefer for this).
This time the window is run as a modal dialog, closed using DialogResult.

The Xaml
Code - XML: [Select]
  1. <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2.        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3.        xmlns:ViewModel="clr-namespace:ViewModel;assembly=AcadFsWpf2013"
  4.        Name="mainWindow"
  5.        Title="Draw Line"
  6.        WindowStartupLocation="CenterOwner" Height="120" Width="280" ResizeMode="NoResize">
  7.     <Window.DataContext>
  8.         <ViewModel:ViewModel/>
  9.     </Window.DataContext>
  10.     <Grid>
  11.         <StackPanel Orientation="Vertical">
  12.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
  13.                 <Label Content="Layer:" VerticalAlignment="Center"/>
  14.                 <ComboBox x:Name="layers" Margin="10" Width="200" Height="25"
  15.                          ItemsSource="{Binding Layers}" SelectedItem="{Binding Layer}"/>
  16.             </StackPanel>
  17.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
  18.                 <Button x:Name="btnOK" Content="OK" Margin="10" Width="80" Height="25"
  19.                        Command="{Binding OkCommand}" CommandParameter="{Binding ElementName=mainWindow}"/>
  20.                 <Button x:Name="btnCancel" Content="Cancel" Margin="10" Width="80" Height="25"
  21.                        Command="{Binding CancelCommand}" CommandParameter="{Binding ElementName=mainWindow}"/>
  22.             </StackPanel>
  23.         </StackPanel>
  24.     </Grid>
  25. </Window>

Model
Code - F#: [Select]
  1. namespace Model
  2.  
  3. open System
  4. open Autodesk.AutoCAD.ApplicationServices
  5. open Autodesk.AutoCAD.DatabaseServices
  6. open Autodesk.AutoCAD.EditorInput
  7. open Autodesk.AutoCAD.Geometry
  8. open Autodesk.AutoCAD.Runtime
  9.  
  10. type AcAp = Autodesk.AutoCAD.ApplicationServices.Application
  11. type AcEx = Autodesk.AutoCAD.Runtime.Exception
  12.  
  13. module Helpers =
  14.  
  15.     let getObject<'a when 'a :> DBObject> (id : ObjectId) =
  16.         id.GetObject(OpenMode.ForRead) :?> 'a
  17.  
  18.    let getObjects<'a when 'a :> DBObject>  : System.Collections.IEnumerable -> _ =
  19.        let rxc = RXClass.GetClass(typeof<'a>)
  20.         Seq.cast<ObjectId>
  21.         >> Seq.choose (function
  22.             | id when id.ObjectClass.IsDerivedFrom(rxc) -> Some(getObject<'a> id)
  23.            | _ -> None)
  24.  
  25.    let addEntity (ent : #Entity) (btr : BlockTableRecord) =
  26.        if not btr.IsWriteEnabled then btr.UpgradeOpen()
  27.        let id = btr.AppendEntity(ent)
  28.        btr.Database.TransactionManager.AddNewlyCreatedDBObject(ent, true)
  29.        id
  30.  
  31.    type OptionBuilder() =
  32.        member b.Bind(x, f) = Option.bind f x
  33.        member b.Return(x) = Some x
  34.        member b.Zero() = None
  35.  
  36.    let opt = new OptionBuilder()
  37.  
  38.    let failIfNotOk (pr : #PromptResult) =
  39.        opt { if pr.Status = PromptStatus.OK then return pr }
  40.  
  41.    type Editor with
  42.        member ed.GetPoint(pt, msg) =
  43.            ed.GetPoint(new PromptPointOptions(msg, BasePoint = pt, UseBasePoint = true))
  44.  
  45. open Helpers
  46.  
  47. module CadWorker =
  48.  
  49.    let getLayers () =
  50.        let db = HostApplicationServices.WorkingDatabase
  51.        use tr = db.TransactionManager.StartTransaction()
  52.        db.LayerTableId
  53.        |> getObject<LayerTable>
  54.        |> getObjects<LayerTableRecord>
  55.        |> Seq.map (fun l -> l.Name)
  56.        |> Seq.toArray
  57.  
  58.    let drawLine (layer) =
  59.        let doc = AcAp.DocumentManager.MdiActiveDocument
  60.        let db = doc.Database
  61.        let ed = doc.Editor
  62.        let result = opt {
  63.            let! pr1 = failIfNotOk (ed.GetPoint("\nStart point: "))
  64.            let! pr2 = failIfNotOk (ed.GetPoint(pr1.Value, "\nEnd point: "))
  65.            return (pr1, pr2) }
  66.        match result with
  67.        | None -> ()
  68.        | Some (pr1, pr2) ->
  69.            use tr = db.TransactionManager.StartTransaction()
  70.            db.CurrentSpaceId
  71.            |> getObject<BlockTableRecord>
  72.            |> addEntity (new Line(pr1.Value, pr2.Value, Layer = layer)) |> ignore
  73.            tr.Commit();

View
Code - F#: [Select]
  1. namespace View
  2.  
  3. open System
  4. open System.IO
  5. open System.Windows
  6. open System.Windows.Markup
  7. open System.Xaml
  8. open Autodesk.AutoCAD.ApplicationServices
  9. open Autodesk.AutoCAD.Runtime
  10.  
  11. type AcAp = Autodesk.AutoCAD.ApplicationServices.Application
  12.  
  13. type CommandMethods() =
  14.     [<CommandMethod("Test")>]
  15.     member x.Test () =
  16.         let win = XamlReader.Load(File.OpenRead("MainWindow.xaml")) :?> Window
  17.         AcAp.ShowModalWindow(win) |> ignore
  18.  
  19. [<assembly: CommandClass(typeof<CommandMethods>)>]
  20. do ()

ViewModel
Code - F#: [Select]
  1. namespace ViewModel
  2.  
  3. open System
  4. open System.ComponentModel
  5. open System.Windows
  6. open System.Windows.Input
  7.  
  8. type ViewModelBase() =
  9.     let propertyChangedEvent = new DelegateEvent<PropertyChangedEventHandler>()
  10.     interface INotifyPropertyChanged with
  11.         [<CLIEvent>]
  12.         member x.PropertyChanged = propertyChangedEvent.Publish
  13.     member x.OnPropertyChanged propertyName =
  14.         propertyChangedEvent.Trigger([| x; new PropertyChangedEventArgs(propertyName) |])
  15.  
  16. type RelayCommand (canExecute:(obj -> bool), action:(obj -> unit)) =
  17.     let event = new DelegateEvent<EventHandler>()
  18.     interface ICommand with
  19.         [<CLIEvent>]
  20.         member x.CanExecuteChanged = event.Publish
  21.         member x.CanExecute arg = canExecute(arg)
  22.         member x.Execute arg = action(arg)
  23.  
  24. open Model.CadWorker
  25.  
  26. type ViewModel() =
  27.     inherit ViewModelBase()
  28.  
  29.     let mutable layer = "0"
  30.  
  31.     let resultOk param =
  32.         (unbox<Window> param).DialogResult <- Nullable(true)
  33.         drawLine(layer)
  34.  
  35.     let resultCancel param =
  36.         (unbox<Window> param).DialogResult <- Nullable(false)
  37.  
  38.     member x.Layer
  39.         with get () = layer
  40.         and set v = layer <- v
  41.  
  42.     member x.Layers
  43.         with get () = getLayers()
  44.  
  45.     member x.OkCommand =
  46.         new RelayCommand((fun _ -> true), resultOk)  
  47.  
  48.     member x.CancelCommand =
  49.         new RelayCommand((fun _ -> true), resultCancel)
  50.  
  51.  
Speaking English as a French Frog

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: F# - Use WPF dialog button to draw a line
« Reply #5 on: May 22, 2015, 07:32:17 PM »
Thank You Gile,
There is not much out there for F# WPF or with AutoCAD so every bit helps!

I'll try to take a closer look later today, there's a bit to absorb and test with here, thanks.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: F# - Use WPF dialog button to draw a line
« Reply #6 on: May 26, 2015, 08:21:42 AM »
Code - XML: [Select]
  1. <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2.        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3.        xmlns:ViewModel="clr-namespace:ViewModel;assembly=AcadFsWpf2013"
  4.        Name="mainWindow"
  5.        Title="Draw Line"
  6.        WindowStartupLocation="CenterOwner" Height="120" Width="280" ResizeMode="NoResize">
  7.     <Window.DataContext>
  8.         <ViewModel:ViewModel/>
  9.     </Window.DataContext>
  10.     <Grid>
  11.         <StackPanel Orientation="Vertical">
  12.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
  13.                 <Label Content="Layer:" VerticalAlignment="Center"/>
  14.                 <ComboBox Margin="10" Width="200" Height="25"
  15.                          ItemsSource="{Binding Layers}" SelectedItem="{Binding Layer}"/>
  16.             </StackPanel>
  17.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
  18.                 <Button Content="OK" Margin="10" Width="80" Height="25"
  19.                        Command="{Binding OkCommand}" CommandParameter="{Binding ElementName=mainWindow}"/>
  20.                 <Button Content="Cancel" Margin="10" Width="80" Height="25" IsCancel="True"/>
  21.             </StackPanel>
  22.         </StackPanel>
  23.     </Grid>
  24. </Window>

Some tweaks as you're learning WPF
1. Don't give names to Controls that you're not referencing somewhere else.  You're just causing WPF to declare a bunch of variables in code behind that you're never using.
2. You can set your Cancel Button's IsCancel property to true and achieve the same result without creating a Command Binding. All bindings take overhead and until .NET 2015 are created at runtime taking more processor cycles.

Personally I set my OK button's click event in the code behind then you just set DialogResult to True and your done.  In the spirit of MVVM the OK buttons action is part of the window's behavior and even if the model changes the OK button's behavior will always be the same. That's just my personal preference.
Revit 2019, AMEP 2019 64bit Win 10

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: F# - Use WPF dialog button to draw a line
« Reply #7 on: May 26, 2015, 03:19:22 PM »
Thank you very much for your advices MexicanCustard.

I do agree for the control's names and the IsCancel property.

In this case, about the OK button, I thaught it was a good thing to set the action in the ViewModel because it requires the selected layer which is set in the ViewModel too.

Another thing to keep in mind is that F# doesn't natively provides the same features as C# or VB to build WPF objects (neither WinForms) so we have to use some tricks. The one used here (nicely provided by MickD) is to add a xaml file to the F# project and load it with XamlReader.Load() method. This way doesn't provide any "*.xaml.fs" file for the code behind.

Anyway, thanks again, your knowledge is very helpfull.
« Last Edit: May 26, 2015, 04:22:13 PM by gile »
Speaking English as a French Frog

Jeff H

  • Needs a day job
  • Posts: 6150
Re: F# - Use WPF dialog button to draw a line
« Reply #8 on: May 26, 2015, 10:10:59 PM »
Personally I set my OK button's click event in the code behind

I'm so MVVM this is my typical code behind file
Code - C#: [Select]
  1. using System;
  2. namespace MVVM.Master
  3. {
  4.     public partial class MVVMWpf : Window
  5.     {
  6.  
  7.     }
  8. }
  9.  

kaefer

  • Guest
Re: F# - Use WPF dialog button to draw a line
« Reply #9 on: May 30, 2015, 02:51:35 PM »
ViewModel
Code - F#: [Select]
  1. type ViewModelBase() =
  2.     let propertyChangedEvent = new DelegateEvent<PropertyChangedEventHandler>()
  3.     interface INotifyPropertyChanged with
  4.         [<CLIEvent>]
  5.         member x.PropertyChanged = propertyChangedEvent.Publish
  6.     member x.OnPropertyChanged propertyName =
  7.         propertyChangedEvent.Trigger([| x; new PropertyChangedEventArgs(propertyName) |])

Nice WPF demo. Let me suggest to use the standard F# event here, Control.Event<'Delegate,'Args>MSDN.

Code - F#: [Select]
  1. type ViewModelBase() =
  2.     let propertyChangedEvent = Event<_,_>()
  3.     interface INotifyPropertyChanged with
  4.         [<CLIEvent>] member x.PropertyChanged = propertyChangedEvent.Publish
  5.     member x.OnPropertyChanged propertyName =
  6.         propertyChangedEvent.Trigger(x, PropertyChangedEventArgs propertyName)

ViewModel
Code - F#: [Select]
  1. type ViewModel() =
  2.     inherit ViewModelBase()
  3.  
  4.     let mutable layer = "0"
  5.  
  6.     member x.Layer
  7.         with get () = layer
  8.         and set v = layer <- v

F# supports automatically implemented propertiesMSDN.

Code - F#: [Select]
  1. type ViewModel() =
  2.     inherit ViewModelBase()
  3.  
  4.     member val Layer = "0" with get,set

Another thing to keep in mind is that F# doesn't natively provides the same features as C# or VB to build WPF objects (neither WinForms) so we have to use some tricks. The one used here (nicely provided by MickD) is to add a xaml file to the F# project and load it with XamlReader.Load() method.

We're missing partial classes in F# and therefore Code-Behind. I'd like to solicit opinions on the advisability of embedding xaml-files into the assembly, to be loaded as Referenced Assembly Resource FileMSDN.

Code - F#: [Select]
  1. type CommandMethods() =
  2.     [<CommandMethod("Test")>]
  3.     member x.Test () =
  4.         let uri = Uri @"pack://application:,,,/AcadFsWpf2013;component/MainWindow.xaml"
  5.         let info = Application.GetResourceStream uri
  6.         info.Stream
  7.         |> XamlReader.Load
  8.         |> unbox<Window>
  9.         |> AcAp.ShowModalWindow
  10.         |> ignore
  11.  
  12. [<assembly: CommandClass(typeof<CommandMethods>)>]
  13. ()

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: F# - Use WPF dialog button to draw a line
« Reply #10 on: May 30, 2015, 04:10:49 PM »
Nice suggestions, thanks kaefer.
Speaking English as a French Frog

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: F# - Use WPF dialog button to draw a line
« Reply #11 on: August 14, 2015, 03:33:47 AM »
It's been a while since I've had time to play but I've strung most of what you guys have here into something that works ok.

Some things didn't work out but they were only minor.
Things like DialogResult and ShowModalDialog I couldn't get to work.
Also, setting IsCancel="true" in the xaml rather than binding a command didn't seem to work out either. I think this has more to do with the fact that the xaml is a partial class to the view model (under the hood) and we can't have a partial class in F#, I tried everything I could but what I have now works.

I also added some code that loads the xaml as a resource at runtime rather than having to load it from text and compile it.
You will need to set the 'Build Action' property of the .xaml file to 'Resource'
Thanks for the help!

Here's the main changes I made to the ViewModel class:
Code - F#: [Select]
  1.     let resultOk param =
  2.         let win = unbox<Window> param
  3.         win.Hide()
  4.         drawLine(layer)
  5.         win.Show()
  6.  
  7.     let resultCancel param =
  8.         (unbox<Window> param).Close()
  9.  

And here's how I loaded the xaml resource:
Code - F#: [Select]
  1. (*--- Helper Functions for wpf ---*)
  2. module FsWpfHelpers =
  3.     let (?) (this : Control) (prop : string) : 'T =
  4.            this.FindName(prop) :?> 'T
  5.  
  6.     let LoadXaml<'a>(appName : string, xamlFileName : string) =
  7.        let uri = @"/" + appName + ";component/" + xamlFileName + ""
  8.        Application.LoadComponent(
  9.            new Uri(uri, UriKind.Relative)) :?> 'a
  10.  
  11. open FsWpfHelpers
  12. type CommandMethods() =
  13.     [<CommandMethod("Test")>]
  14.     member x.Test () =
  15.         let win = LoadXaml<Window>("WpfDialogSample", "Window1.xaml")
  16.         win.Show()
  17.  
  18. [<assembly: CommandClass(typeof<CommandMethods>)>]
  19. do ()
  20.  

And the xaml:
Code - XML: [Select]
  1. <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2.        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3.        xmlns:ViewModel="clr-namespace:WpfDialogSample;assembly=WpfDialogSample"
  4.        Name="mainWindow"
  5.        Title="Draw Line"
  6.        WindowStartupLocation="CenterOwner" Height="120" Width="280" ResizeMode="NoResize">
  7.     <Window.DataContext>
  8.         <ViewModel:ViewModel/>
  9.     </Window.DataContext>
  10.     <Grid>
  11.         <StackPanel Orientation="Vertical">
  12.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
  13.                 <Label Content="Layer:" VerticalAlignment="Center"/>
  14.                 <ComboBox Margin="10" Width="200" Height="25"
  15.                          ItemsSource="{Binding Layers}" SelectedItem="{Binding Layer}"/>
  16.             </StackPanel>
  17.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
  18.                 <Button Content="OK" Margin="10" Width="80" Height="25"
  19.                        Command="{Binding OkCommand}" CommandParameter="{Binding ElementName=mainWindow}"/>
  20.                 <Button Content="Cancel" Margin="10" Width="80" Height="25"
  21.                        Command="{Binding CancelCommand}" CommandParameter="{Binding ElementName=mainWindow}"/>
  22.             </StackPanel>
  23.         </StackPanel>
  24.     </Grid>
  25. </Window>
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MexicanCustard

  • Swamp Rat
  • Posts: 705
Re: F# - Use WPF dialog button to draw a line
« Reply #12 on: August 14, 2015, 09:48:01 AM »
It's been a while since I've had time to play but I've strung most of what you guys have here into something that works ok.

Some things didn't work out but they were only minor.
Things like DialogResult and ShowModalDialog I couldn't get to work.
Also, setting IsCancel="true" in the xaml rather than binding a command didn't seem to work out either. I think this has more to do with the fact that the xaml is a partial class to the view model (under the hood) and we can't have a partial class in F#, I tried everything I could but what I have now works.

Interesting.  Another reason to delay my exploration of F#.
Revit 2019, AMEP 2019 64bit Win 10

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: F# - Use WPF dialog button to draw a line
« Reply #13 on: August 14, 2015, 07:38:47 PM »
true, it can be frustrating but it will only be a matter of time until F# tools catch up.

<off topic mumbling>
My interest in F# comes from my frustrations writing boiler plate code all the time in C#, F# is much more concise and easier to read (once you get over what all the 'let's are about).
Every time I write an if statement or for loop it drives me nuts and learning a functional language in my spare time has been very rewarding.
Ideally I'd use a Lisp but the similarities between F# and a Lisp are close enough and I've grown to like the pipe operator in preference to digging into paren's to see what's going on.
I love the way you can build very simple building block functions and string them together to build more interesting ones.

Having said that though, from learning a functional language I now have a much better understanding of Linq and Lambdas, the dangers/problems of immutability and how to code in a more expressive style in C#. Any C# I write/edit from now on will be a lot better (than what I used to write at least).

Even C++ (C++11 on) is getting more functional to cope with the onslaught of multi-core processors.

OO has dug itself into a pretty big hole and only by introducing more functional paradigms (such as immutability and higher order functions) will old code bases be able to be refactored to take advantage of new hardware technology.

It's not just hardware on a single machine either, think passing portions of your calculations to multi pc's in the cloud.

Here's an interesting article worth a read by Joel Spolsky (written nearly 10 years ago!) that explains a lot better my interest in this field
http://www.joelonsoftware.com/items/2006/08/01.html

No wonder people coming from lisp have a hard time learning OO, they took the 'telling' the program what to do into 'describing' what to do (i.e. expressions vs statements).
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Jeff H

  • Needs a day job
  • Posts: 6150
Re: F# - Use WPF dialog button to draw a line
« Reply #14 on: August 15, 2015, 12:25:00 AM »
I thought F# was not used much in actual "designer-tooling code" UI and would use C# if not a as*hole and VB if a di*k, but the libraries used to dynamically build UI, creating, transforming, etc.. data would be F#.

I remember one guy who gave a interview for .NET Rocks worked for some kind casino type app that was very UI oriented but were using C# and would create new game about every week and F# cut it down to about a day. I think C# was still used in designer code of UI but F# libraries used to create UI controls, components, etc....