wpff#elmish-wpf

In Elmish.WFP, how is an Indexed-Binding made for a DataGrid? (Binding to a list element)


In the below UserControl for a DataGrid, the line:

<customcontrols:AppointmentListView ItemsSource="{Binding columns[0].appointmentKeys}" Height="140" Background="Bisque">

performs a binding to a column by its index, how is this binding made in Elmish.WPF?

Assuming the DataContext for this line is Rows, in C# the Row is defined as:

public class Row : IRow
    {
        public string rowTime { get; set; }          
        public List<ICell> columns { get; set; }     
        private const int columnCount = 4;

        public Row(int rowNumber, string rowTime)
        {
            this.rowTime = rowTime;
            this.columns = new List<ICell>();

            for (int i = 0; i < columnCount; i++)
            {
                var t = TimeSpan.FromMinutes(i * 15);
                columns.Add(new Cell(rowNumber, i, t));
            }
        }

        public Row()
        {
        }

        // Indexer declaration. If index is out of range, the rows array will throw the exception.
        public ICell this[int index]
        {
            get { return columns[index]; }
            set { columns[index] = value; }
        }
    }

Thanks in Advance!

In full disclosure, here is the full UserControl XAML:

    <!--
        Assign the ROOT element of a user control a name. Usual name is LayoutRoot or Root. This will be used as reference with 
        ElementName. Do not set the DataContext of a usercontrol 
    -->

    <UserControl.Resources>
        <local:IsNullConverter x:Key="isNullConverter"/>

        <!-- DataContext is AppointmentKey -->
        <Style TargetType="{x:Type ListViewItem}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding tservice,  Converter={StaticResource isNullConverter}}" Value="False">
                    <Setter Property="Background" Value="LawnGreen" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>

    <Border  BorderBrush="Black" BorderThickness="4" >
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition  Height="30"/>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>

            <!--The DataContext for this user control is the AppointmentEditor. 
                A customcontrol for the DataGrid is being used to listen for events from the selected item. Selecting an item raises an 
                custom routed event from the customcontrols:AppointmentListView. The Cells of the datagrid consists of AppointmentListViews -->
            <TextBlock Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="4" Text="{Binding SelectedAppointmentDate, StringFormat=D}" 
                               FontSize="24" FontWeight="ExtraBold" TextAlignment="Center"/>

            <TextBlock Grid.Row="0" Grid.Column="6" x:Name="CurrentTime" Width="60"
                   HorizontalAlignment="Right" Margin="2" FontWeight="SemiBold" FontSize="14"/>

            <!-- 
                A customcontrol for the DataGrid is being used to listen for events from the selected item. Selecting an item raises a 
                custom routed event from the customcontrols:AppointmentListView. The Cells of the datagrid consists of AppointmentListView.
                customcontrols:AppointmentListView.ScheduledAppointment is defined as a routed event. This syntax attaches the routed event
                to the AppointmentDataGrid_ScheduledAppointment handler found in the code-behind. The AppointmentListView is being attached
                to each Cell of the AppointmentDataGrid.
            -->
            <customcontrols:AppointmentDataGrid x:Name="AppointmentTable" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="4" Grid.RowSpan="6"
                      ItemsSource="{Binding Rows}" AutoGenerateColumns="False" 
                      CanUserAddRows="False"  AlternationCount="2" AlternatingRowBackground="LightYellow"
                      customcontrols:AppointmentListView.ScheduledAppointment ="AppointmentDataGrid_ScheduledAppointment" 
                      SelectedItem="{Binding SelectedRow}"                          
                      >

                <DataGrid.Columns>
                    <!--The DataContext for DataGrid Columns is from the itemsSource Rows.
                        The Syntax is to address the content objects within a cell is Row[columindex] or Row["column header"]  -->
                    <DataGridTextColumn Header="Time" Binding="{Binding RowTime}" />

                    <DataGridTemplateColumn Header="0:00" Width="354" IsReadOnly="True">
                        <DataGridTemplateColumn.HeaderStyle>
                            <Style TargetType="{x:Type DataGridColumnHeader}">
                                <Setter Property="HorizontalAlignment" Value="Center"/>
                            </Style>
                        </DataGridTemplateColumn.HeaderStyle>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <!--A custom control is required here to detect mouse events and raise a routed event. A Behavior cannot raise a routed event.-->
                                <!--The DataContext here is from the ItemsSource Row-->
                                <customcontrols: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>
                                </customcontrols:AppointmentListView>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>

                    <DataGridTemplateColumn Header="0:15" Width="354" IsReadOnly="True">
                        <DataGridTemplateColumn.HeaderStyle>
                            <Style TargetType="{x:Type DataGridColumnHeader}">
                                <Setter Property="HorizontalAlignment" Value="Center"/>
                            </Style>
                        </DataGridTemplateColumn.HeaderStyle>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <customcontrols:AppointmentListView ItemsSource="{Binding columns[1].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>
                                </customcontrols:AppointmentListView>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>

                    <DataGridTemplateColumn Header="0:30" Width="354" IsReadOnly="True" >
                        <DataGridTemplateColumn.HeaderStyle>
                            <Style TargetType="{x:Type DataGridColumnHeader}">
                                <Setter Property="HorizontalAlignment" Value="Center"/>
                            </Style>
                        </DataGridTemplateColumn.HeaderStyle>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <customcontrols:AppointmentListView ItemsSource="{Binding columns[2].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>
                                </customcontrols:AppointmentListView>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>

                    <DataGridTemplateColumn Header="0:45" Width="354" IsReadOnly="True">
                        <DataGridTemplateColumn.HeaderStyle>
                            <Style TargetType="{x:Type DataGridColumnHeader}">
                                <Setter Property="HorizontalAlignment" Value="Center"/>
                            </Style>
                        </DataGridTemplateColumn.HeaderStyle>
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <customcontrols:AppointmentListView ItemsSource="{Binding columns[3].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>
                                </customcontrols:AppointmentListView>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </customcontrols:AppointmentDataGrid>
        </Grid>
    </Border>
</UserControl>

Solution

  • As it turns out, when the supporting Module in F# is correct, Elmish.WPF will correctly use the index [] notation as demonstrated in: https://github.com/awaynemd/MyDataGrid

    The working parts are:

    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