Search code examples
f#elmish-wpf

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


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


Solution

  • 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.