WPF中。。DataGrid 實現時間控件和下拉框控件


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM