在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
在下面的 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