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