在Elmish.WFP中,如何为DataGrid创建Indexed-Binding? (绑定到列表元素)

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

在下面的 DataGrid 用户控件中,行:

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

通过索引对列执行绑定,如何在 Elmish.WPF 中进行此绑定?

假设此行的 DataContext 是 Rows,在 C# 中,Row 定义为:

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; }
        }
    }

提前致谢!

全面披露,这是完整的 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>

事实证明,当 F# 中的支持模块正确时,Elmish.WPF 将正确使用索引 [] 表示法,如以下所示:https://github.com/awaynemd/MyDataGrid

工作部分是:

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