DatePicker 和新的 DataGrid 行 用户与 DataGrid 中日期列的交互给我形成了很大的麻烦。 我经过将一个 Data Source 对象拖动到 WPF 窗口上,建立了一个 DataGrid。 设计器的默认行为是为该对象中的每一个 DateTime 值建立一个 DatePicker。 例如,下面是为一个 DateScheduled 字段建立的列: <DataGridTemplateColumn x:Name=" dateScheduledColumn" Header="DateScheduled" Width="100"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DatePicker SelectedDate="{Binding Path=DateScheduled, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> 这一默认行为对编辑形成不便。 编辑时现有行不会更新。 DatePicker 不会在 DataGrid 中触发编辑,这表示数据绑定功能不会将所作的更改推送至基础对象。 经过向 Binding 元素添加 UpdateSourceTrigger 属性并将属性值设置为 PropertyChanged,能够解决这个特定的问题: <DatePicker SelectedDate="{Binding Path= DateScheduled, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged}" /> 不过,添加这些新行后,DatePicker 不能触发 DataGrid 编辑模式的问题变得更加严重。 在 DataGrid 中,新行由 NewRowPlaceHolder 表示。 首次编辑新行中的单元格时,编辑模式会在数据源中触发插入(再次说明,不是在数据库中,而是在内存中的基础源中)。 由于 DatePicker 不触发编辑模式,因此这不会发生。 由于个人行中的日期列刚好是第一列,因此我发现了这个问题。 我原本想使用它来触发该行的编辑模式的。 图 1 所示为一个新行,在其中的第一个可编辑列中输入了日期。 图 1 在新行占位符中输入日期值 但在编辑下一列中的值以后,前一编辑值丢失,如图 2 所示。 图 2 修改新行中 Task 列值以后日期值丢失 第一列中的键值变为 0,刚刚输入的日期值变为 1/1/0001。 编辑 Task 列最终会使 DataGrid 在数据源中添加一个新实体。 ID 值变为整数(默认值 0),日期值变为 .NET 默认的最小日期 1/1/0001。 若是我为此类指定过默认日期,则用户输入的日期将变为此类的默认日期,而不是 .NET 的默认日期。 请注意,Date Performed 列中的日期没有更改成其默认值。 这是由于 DatePerformed 是能够为 Null 的属性。 那么,如今用户是否是必须回去从新修复 Scheduled Date? 我相信用户确定不肯意这样作。 这个问题困扰了我一段时间。 我甚至曾将该列改为 DataTextBoxColumn,但以后我必须处理 DatePicker 起保护做用的验证问题。 最后,WPF 团队的 Varsha Mahadevanset 给我指出了正确的道路。 经过利用 WPF 的组合性质,能够对此列使用两个元素。 DataGridTemplateColumn 不只有 CellTemplate 元素,还有 CellEditingTemplate。 我没有要求 DatePicker 控件触发编辑模式,而只在已经编辑时使用 DatePicker。 为了在 CellTemplate 中显示日期,我切换到了 TextBlock。 下面是 dateScheduledCoumn 的新 XAML: <DataGridTemplateColumn x:Name="dateScheduledColumn" Header="Date Scheduled" Width="125"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Path= DateScheduled, StringFormat=\{0:d\}}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <DatePicker SelectedDate="{Binding Path=DateScheduled, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" /> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> </DataGridTemplateColumn> 请注意,我再也不须要指定 UpdateSourceTrigger。 我对 DatePerformed 列也进行了一样的更改。 如今,这些日期列一开始是简单文本,在您进入该单元格后才切换到 DatePicker,如图 3 所示。 图 3 DateScheduled 列同时使用 TextBlock 和 DatePicker 在新行上面的行中没有 DatePicker 日历图标。 但这仍是有点不对。 开始编辑这一行时仍然会获得默认的 .NET 值。 这时,您就能够从在基础类中定义默认值受益。 我修改了 ScheduleItem 类的构造函数,使之将新对象初始化为当天日期。 若是从数据库检索到数据,它将覆盖该默认值。 个人项目使用了实体框架,所以类会自动生成。 不过,生成的类是分部类,这样,我就能够将此构造函数添加到附加的分部类中: public partial class ScheduleItem { public ScheduleItem() { DateScheduled = DateTime.Today; } } 如今,当我经过修改 DateScheduled 列开始在新行占位符中输入数据时,DataGrid 会为我建立一个新的 ScheduleItem,而且在 DatePicker 控件中显示默认值(当天日期)。 如今,当用户继续编辑此行时,输入的值会继续有效。 减小用户在编辑时须要点击的次数 两部分模板的一个弊端是必须点击单元格两次才能触发 DatePicker。 这会对数据输入人员形成不便,特别是对那些习惯于使用键盘输入数据而不使用鼠标的人员。 由于 DatePicker 位于编辑模板中,因此除非触发编辑模式,不然它不会得到焦点(这是默认行为)。 这是针对 TextBox 进行的设计,很适合 TextBox 使用。 但这种设计不太适用于 DatePicker。 能够结合使用 XAML 和代码来强制 DatePicker 在用户切换到该单元格时准备好键入。 首先,须要在 CellEditingTemplate 中添加一个 Grid 容器,使它成为 DatePicker 的容器。 而后,可使用 WPF FocusManager 强制此 Grid 在用户进入该单元格时成为单元格焦点。 下面是做为 DatePicker 容器的新 Grid 元素: <Grid FocusManager.FocusedElement="{Binding ElementName= dateScheduledPicker}"> <DatePicker x:Name=" dateScheduledPicker" SelectedDate="{Binding Path=DateScheduled, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" /> </Grid> 请注意,我为 DatePicker 控件提供了一个名称,并使用 FocusedElement Binding ElementName 指向了该名称。 如今请将注意力转到包含此 Date-Picker 的 DataGrid,注意,我添加了三个新属性(RowDetailsVisibilityMode、SelectionMode 和 SelectionUnit)和一个新的事件处理程序 (SelectedCellsChanged): <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True" ItemsSource="{Binding}" Margin="12,12,22,31" Name="scheduleItemsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" SelectionMode="Extended" SelectionUnit="Cell" SelectedCellsChanged="scheduleItemsDataGrid_SelectedCellsChanged"> 对 DataGrid 进行的更改会启用当用户选择该 DataGrid 中的新单元格进行通知的功能。 最后,须要确保当用户执行此操做时 DataGrid 确实进入编辑模式,这会在 DatePicker 中向用户提供必要的光标。 scheduleItemsDataGrid_SelectedCellsChanged 方法将提供这最后一部分逻辑: private void scheduleItemsDataGrid_SelectedCellsChanged (object sender, System.Windows.Controls.SelectedCellsChangedEventArgs e) { if (e.AddedCells.Count == 0) return; var currentCell = e.AddedCells[0]; string header = (string)currentCell.Column.Header; var currentCell = e.AddedCells[0]; if (currentCell.Column == scheduleItemsDataGrid.Columns[DateScheduledColumnIndex]) { scheduleItemsDataGrid.BeginEdit(); } } 请注意,在类声明中,我将常量 DateScheduledColumnIndex 定义为 1,即该列在网格中的位置。 完成这些更改后,最终用户会很满意。 我费了很大心思才找出使 DatePicker 在 DataGrid 内出色工做的正确 XAML 和代码元素组合,但愿这能够帮助您避免经历一样的困难。 如今,UI 以用户熟悉的方式工做了。 使受限 ComboBox 显示旧值 获取在 DataGridTemplateColumn 中对元素分层的价值以后,我再次考虑了另外一个我几乎已经放弃的 DataGrid-ComboBox 列相关问题。 编写这一特定应用程序的目的是为了用旧数据替换旧应用程序。 旧应用程序容许用户不经太多控制输入数据。 在新应用程序中,客户端要求经过下拉列表对一些数据输入进行限制。 经过使用字符串集合很容易提供下拉列表的内容。 难点在于仍要显示旧数据,即便此数据不包含在新的限制列表中也是如此。 我首先尝试使用 DataGridComboBoxColumn: <DataGridComboBoxColumn x:Name="frequencyCombo" MinWidth="100" Header="Frequency" ItemsSource="{Binding Source={StaticResource frequencyViewSource}}" SelectedValueBinding= "{Binding Path=Frequency, UpdateSourceTrigger=PropertyChanged}"> </DataGridComboBoxColumn> 在代码隐藏文件中定义源项: private void PopulateTrueFrequencyList() { _frequencyList = new List<String>{"", "Initial","2 Weeks", "1 Month", "2 Months", "3 Months", "4 Months", "5 Months", "6 Months", "7 Months", "8 Months", "9 Months", "10 Months", "11 Months", "12 Months" }; } 此 _frequencyList 绑定到另外一方法中的 frequencyViewSource.Source。 在无数种可能的 DataGridCombo-BoxColumn 配置中,我找不到任何办法来显示可能已经存储在数据库表的 Frequency 字段中的不一样值。 我就不一一列举我试过的全部解决方法了,其中一个是将这些额外的值动态添加到 _frequencyList 底部,而后根据须要删除它们。 我并不喜欢这个解决方法,但恐怕我不得不接受它。 我知道编写 UI 的 WPF 分层方法必须为此提供一种机制,而且已经解决了 Date-Picker 问题,所以我意识到能够对 ComboBox 使用类似的方法。 这个技巧的第一部分是避免使用华而不实的 DataGridComboBoxColumn,而是使用更经典的将 ComboBox 嵌入 DataGridTemplateColumn 内部的方法。 而后,利用 WPF 的组合性质,能够像使用 DateScheduled 列同样对此列使用两个元素。 第一个元素是 TextBlock,用来显示值;第二个元素是 ComboBox,用于编辑目的。 图 4 显示了同时使用这两个元素的方式。 图 4 组合使用显示值的 TextBlock 和用于编辑的 ComboBox <DataGridTemplateColumn x:Name="taskColumnFaster" Header="Task" Width="100" > <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Path=Task}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <Grid FocusManager.FocusedElement= "{Binding ElementName= taskCombo}" > <ComboBox x:Name="taskCombo" ItemsSource="{Binding Source={StaticResource taskViewSource}}" SelectedItem ="{Binding Path=Task}" IsSynchronizedWithCurrentItem="False"/> </Grid> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> </DataGridTemplateColumn> TextBlock 与限制列表不存在依赖关系,所以可以显示数据库中存储的任何值。 不过,在编辑时就会用到 ComboBox,输入将限制为 frequencyViewSource 中的值。 容许用户在单元格得到焦点时编辑 ComboBox 一样,由于 ComboBox 在用户在单元格中单击两次后方可以使用,所以,请注意我将 ComboBox 封装在一个 Grid 中以利用 FocusManager。 考虑到用户可能经过单击 Task 单元格而不是移至第一列的方式开始新行数据输入,我修改了 SelectedCellsChanged 方法。 惟一的更改是代码还检查当前单元格是否位于 Task 列中: private void scheduleItemsDataGrid_SelectedCellsChanged(object sender, System.Windows.Controls.SelectedCellsChangedEventArgs e) { if (e.AddedCells.Count == 0) return; var currentCell = e.AddedCells[0]; string header = (string)currentCell.Column.Header; if (currentCell.Column == scheduleItemsDataGrid.Columns[DateScheduledColumnIndex] || currentCell.Column == scheduleItemsDataGrid.Columns[TaskColumnIndex]) { scheduleItemsDataGrid.BeginEdit(); } }