With Elmish.wpf/F#, how to display in WPF a string option as the string or null, not "Some(string)"?

117 views Asked by At

I am a newbie to F#. In WPF, I am using DisplayMemberBinding within a Datagrid as:

<DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <local:AppointmentListView ItemsSource="{Binding Columns[0].AppointmentKeys}" Height="140" Background="Bisque">
                                    <ListView.View>
                                        <GridView>
                                            <GridViewColumn Header="First"     DisplayMemberBinding="{Binding FirstName}" Width="100"/>
                                            <GridViewColumn Header="Last"      DisplayMemberBinding="{Binding LastName}"  Width="120"/>
                                            <GridViewColumn Header="BirthDate" DisplayMemberBinding="{Binding BirthDate, StringFormat=d}" Width="100"/>
                                        </GridView>
                                    </ListView.View>
                                </local:AppointmentListView>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>

The (complete) backing F# module (in Elmish.wpf) is:

module MyDataGrid.DataGrid

open Elmish
open Elmish.WPF
open System

type Visit = 
    {   ServiceTime: DateTime option
        DoNotSee: Boolean option
        ChartNumber: int option
        LastName: string option
        FirstName: string option
        Mi: string option
        BirthDate: DateTime option
        PostingTime: DateTime option 
        AppointmentTime: DateTime option }

type Cell = 
  {RowNumber: int
   ColumnNumber: int
   AppointmentKeys: Visit list
   ColumnTime: TimeSpan
   AppointmentCount: int
   AppointmentTime: DateTime option  // all lines in the cell have the same appointment time.     
  }

let SetCell (rowNumber: int, columnNumber: int) =
    let AppointmentsPerCell = 4
    {RowNumber = rowNumber
     ColumnNumber = columnNumber
     AppointmentKeys = [for x in 1 .. AppointmentsPerCell -> 
                           {
                             ServiceTime = Some System.DateTime.Now 
                             DoNotSee = Some false 
                             ChartNumber = Some 8812 
                             LastName= Some ("LastName" + string x)
                             FirstName= Some ("FirstName" + string x)
                             Mi = Some "J" 
                             BirthDate = Some(DateTime(2020,09,14))
                             PostingTime = Some DateTime.Now
                             AppointmentTime = Some DateTime.Now
                         }]      
     ColumnTime = System.TimeSpan.FromMinutes(float(columnNumber * 15))
     AppointmentCount = 4
     AppointmentTime = Some(DateTime.Now)
     }

type Row =
  {RowTime: string
   Columns: Cell list}

let SetRow (rowNumber: int, startTime: System.TimeSpan)= 
    let columnCount = 4
    let hr = System.TimeSpan.FromHours(1.0)
    let rowTime = startTime + System.TimeSpan.FromTicks(hr.Ticks * int64(rowNumber))
    { RowTime = rowTime.ToString("h':00'")
      Columns = [for columnNumber in 1 .. columnCount -> SetCell(rowNumber, columnNumber) ]
    }

type Model =
  { AppointmentDate: DateTime
    Rows: Row list
    SelectedRow: Row option}

type Msg =
  | SetAppointmentDate of DateTime
  | SetSelectedRow of Row option

let init =
      let rowCount = 9
      let startTime = TimeSpan.FromHours(float(8))
      { AppointmentDate = DateTime.Now 
        Rows = [for rowNumber in 0 .. rowCount -> SetRow(rowNumber, startTime)]
        SelectedRow = None
      }

let update msg m =
  match msg with
  | SetAppointmentDate d -> {m with AppointmentDate = d}
  | SetSelectedRow r -> {m with SelectedRow = r}

let bindings () : Binding<Model, Msg> list = [
  "SelectedAppointmentDate" |> Binding.twoWay( (fun m -> m.AppointmentDate), SetAppointmentDate)
  "Rows" |> Binding.oneWay( fun m -> m.Rows)
  "SelectedRow" |> Binding.twoWay( (fun m -> m.SelectedRow), SetSelectedRow)
]

let designVm = ViewModel.designInstance init (bindings ())


let main window =
  Program.mkSimpleWpf (fun () -> init) update bindings
  |> Program.withConsoleTrace
  |> Program.runWindowWithConfig
    { ElmConfig.Default with LogConsole = true; Measure = true }
    window

The DisplayMememberBindings show LastName as "Some(LastName1)" and BirthDate as "Some(09/14/2020 00:00:00)".

How can I get the LastName: string option to return either null or the value of the string so the display shows "LastName1" and not "Some(LastName1)?

The same goes for the birth date, how to show BirthDate as "9/14/2020" and not "Some(09/14/2020 00:00:00)?

TIA

Full source code at: Example DataGrid

2

There are 2 answers

1
Tyson Williams On BEST ANSWER

Your code only has three bindings. You should have a binding for every individual piece of data. Specifically, you should change your Rows binding from a OneWay binding to a SubModel binding. Then repeat this for all your other types.

Then, the question you specifically asked about is how to display LastName1 instead of Some(LastName1) and 9/14/2020 instead of Some(09/14/2020 00:00:00). Create the bindings for these individual pieces of optional data with Binding methods that ends in Opt like Binding.oneWayOpt or Binding.twoWayOpt.

0
Alan Wayne On

For newbies like me, here is my full F# working solution in Elmish.WPF/F#:

module MyDataGrid.DataGrid

open Elmish
open Elmish.WPF
open System

module Visit = 
    
    type Model =
       { ServiceTime: DateTime option
         DoNotSee: Boolean option
         ChartNumber: int option
         LastName: string option
         FirstName: string option
         Mi: string option
         BirthDate: DateTime option
         PostingTime: DateTime option 
         AppointmentTime: DateTime option 
         Id: int}

    let SetVisits appointmentsPerCell  = [for x in 1 .. appointmentsPerCell -> 
                                                {
                                                  ServiceTime = Some System.DateTime.Now 
                                                  DoNotSee = Some false 
                                                  ChartNumber = Some 8812 
                                                  LastName= Some ("LastName" + string x)
                                                  FirstName= Some ("FirstName" + string x)
                                                  Mi = Some "J" 
                                                  BirthDate = Some(DateTime(2020,09,14))
                                                  PostingTime = Some DateTime.Now
                                                  AppointmentTime = Some DateTime.Now
                                                  Id = x
                                              }]   
    
    let bindings() = [
        "FirstName" |> Binding.oneWayOpt( fun (_, m) -> m.FirstName)
        "LastName"  |> Binding.oneWayOpt( fun (_, m) -> m.LastName)
        "BirthDate" |> Binding.oneWayOpt( fun (_, m) -> m.BirthDate) 
        "ServiceTime" |> Binding.oneWayOpt( fun (_, m) -> m.ServiceTime)
    ]

module Cell =

    type Model =
        { RowNumber: int
          ColumnNumber: int
          AppointmentKeys: Visit.Model list
          ColumnTime: TimeSpan
          AppointmentCount: int
          AppointmentTime: DateTime option  // all lines in the cell have the same appointment time.  
          Id: int
        }

    let SetCell (rowNumber: int, columnNumber: int) =
        let AppointmentsPerCell = 4
        {RowNumber = rowNumber
         ColumnNumber = columnNumber
         AppointmentKeys =  Visit.SetVisits AppointmentsPerCell  
         ColumnTime = System.TimeSpan.FromMinutes(float(columnNumber * 15))
         AppointmentCount = 4
         AppointmentTime = Some(DateTime.Now)
         Id=rowNumber*10 + columnNumber
         }

    let bindings() =[
        "AppointmentKeys"  |> Binding.subModelSeq(
                                (fun (_, m) -> m.AppointmentKeys),
                                (fun v -> v.Id),
                                 Visit.bindings                            
                              )
    ]

module Row =

    type Model =
      { RowTime: string
        Columns: Cell.Model list 
        Id: int }

    let SetRow (rowNumber: int, startTime: System.TimeSpan)= 
        let columnCount = 4
        let hr = System.TimeSpan.FromHours(1.0)
        let rowTime = startTime + System.TimeSpan.FromTicks(hr.Ticks * int64(rowNumber))
        { RowTime = rowTime.ToString("h':00'")
          Columns = [for columnNumber in 1 .. columnCount -> Cell.SetCell(rowNumber, columnNumber) ]
          Id = rowNumber
        }

    
    let bindings () = [
        "RowTime" |> Binding.oneWay( fun (_,r) -> r.RowTime)
        "Columns" |> Binding.subModelSeq(
                                            (fun (_, m) -> m.Columns),
                                            (fun c -> c.Id),
                                             Cell.bindings                            
                                          )
    
    ] 
    

type Model =
  { AppointmentDate: DateTime
    Rows: Row.Model list
    SelectedRow: Row.Model option}

type Msg =
  | SetAppointmentDate of DateTime
  | SetSelectedRow of Row.Model option

let init () =
    let rowCount = 9
    let startTime = TimeSpan.FromHours(float(8))
    { AppointmentDate = DateTime.Now 
      Rows = [for rowNumber in 0 .. rowCount -> Row.SetRow(rowNumber, startTime)]
      SelectedRow = None
    }

let update msg m =
  match msg with
  | SetAppointmentDate d -> {m with AppointmentDate = d}
  | SetSelectedRow r -> {m with SelectedRow = r}

let bindings () : Binding<Model, Msg> list = [
  "SelectedAppointmentDate" |> Binding.twoWay( (fun m -> m.AppointmentDate), SetAppointmentDate)
  "Rows" |> Binding.subModelSeq(
                                 (fun m -> m.Rows),
                                 (fun r -> r.Id),
                                  Row.bindings                          
                               )
  "SelectedRow" |> Binding.twoWay( (fun m -> m.SelectedRow), SetSelectedRow)
]

let main window =
  Program.mkSimpleWpf init update bindings
  |> Program.withConsoleTrace
  |> Program.runWindowWithConfig
    { ElmConfig.Default with LogConsole = true; Measure = true }
    window
    enter code here