DataGridTemplateColumn (ComboBox, DatePicker) réinitialise / efface et ne tire pas AddingNewItem
j'ai réduit le problème à l'exemple suivant qui a un DataGrid avec trois colonnes.
XAML:
<Window x:Class="DataGridColumnTemplate_NotFiringAddingNewItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dg" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="299" AutoGenerateColumns="False" Width="497" AddingNewItem="dg_AddingNewItem" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="DateWorks">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="DateDoesn'tWork">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<JobCostEntity> l = new List<JobCostEntity>()
{
new JobCostEntity() { Id = 0, InvoiceDate = DateTime.Now, Description = "A"},
new JobCostEntity() { Id = 0, InvoiceDate = DateTime.Now, Description = "B"}
};
dg.ItemsSource = l;
}
private void dg_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
MessageBox.Show("AddingNewItem");
}
}
public partial class JobCostEntity
{
public int Id { get; set; }
public int JobId { get; set; }
public Nullable<int> JobItemId { get; set; }
public Nullable<System.DateTime> InvoiceDate { get; set; }
public Nullable<System.DateTime> ProcessedDate { get; set; }
public int PackageId { get; set; }
public int DelegateId { get; set; }
public string Description { get; set; }
public Nullable<decimal> LabourCost { get; set; }
public Nullable<decimal> PlantOrMaterialCost { get; set; }
public Nullable<decimal> SubcontractorCost { get; set; }
public Nullable<decimal> TotalCost { get; set; }
public bool Paid { get; set; }
}
si la première colonne sur laquelle vous cliquez dans la rangée des nouveaux éléments est "DateWorks" ou "Text", alors vous relancerez L'événement AddingNewItem.
si au lieu de cela vous cliquez sur la colonne 'DateDoesntWork' en premier, vous pouvez sélectionner une date, mais aucun nouvel élément n'est ajouté jusqu'à ce que vous passez à l'une des autres colonnes, à quel point le la valeur dans le compteur' Datedoesnwork ' est effacée.
Ce qui sur terre se passe?
C'est sans doute(!) désirable d'avoir le curseur de données déjà visible à l'utilisateur (donc à la fois un CellTemplate et un CellEditingTemplate), plutôt que d'avoir à cliquer sur la cellule pour "révéler" le contrôle.
y a-t-il un moyen pour que je doive informer le DataGrid que mon contrôle DataGridTemplateColumn vient de définir une valeur sur une nouvelle ligne? Si oui, comment alors?!
EDIT:
Inspiré par ce post: https://social.msdn.microsoft.com/Forums/vstudio/en-US/93d66047-1469-4bed-8fc8-fa5f9bdd2166/programmatically-beginning-edit-in-datagrid-cell?forum=wpf
j'ai essayé de contourner le problème en ajoutant ce qui suit au curseur de données de la colonne 'DateDoesntWork', ce qui provoque le feu de L'événement AddingNewItem, mais la date sélectionnée n'obtient toujours pas ajouté à l'entité sous-jacente.
private void DatePicker_GotFocus(object sender, RoutedEventArgs e)
{
if (dg.SelectedIndex == dg.Items.Count - 1)
{
DataGridCellInfo dgci = dg.SelectedCells[0];
DataGridCell dgc = DataGridHelper.GetCell(dg, GetRowIndex(dg, dgci), GetColIndex(dg, dgci));
dgc.Focus();
dg.BeginEdit();
}
}
il semble que le lecteur de données essaye toujours de cibler le nouveau conteneur HTML, si cela a un sens?!
encore plus étrange, si vous sélectionnez une date dans la colonne Datedoesnwork sur la nouvelle ligne, puis commencez à éditer la colonne texte sur la nouvelle ligne, puis sans entrer de texte, sélectionnez la ligne ci-dessus ... maintenant une autre nouvelle ligne est ajoutée et cette nouvelle ligne montre la date que j'ai sélectionnée pour la ligne avant de!!!
Total. Folie.
Comme Maxime Tremblay-Savard a dessus décrite, il semble que l' CellTemplate
bloque la "couche" ci-dessous et arrête le AddingNewItem
événement tir, bien que le construit en DataGridColumn
types ne souffrent pas de ce problème.
5 réponses
si vous utilisez un contrôle qui gère le clic de souris dans CellTemplate, le DataGrid ne reçoit jamais l'événement de clic qui le déclenche pour passer en mode Édition. Ainsi comme eoinmullan mentionné, la solution est de mettre le contrôle IsHitTestVisible=False. Ci-dessous est le code de travail. J'ai ajouté INotifyPropertyChanged de sorte que nous pouvons réellement voir la valeur changée reflétée dans L'UI. J'ai aussi ajouté un fond rouge pour le CellTemplate de Datedoesn'wt, de sorte que vous pouvez voir quand le DataGrid passe du mode d'affichage à l'édition mode.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dg" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="299" AutoGenerateColumns="False" Width="497" AddingNewItem="dg_AddingNewItem" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="DateWorks" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding InvoiceDate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="DateDoesn'tWork">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate >
<!--Differentiate visually between CellTemplate and CellEditTemplate by using red background-->
<DatePicker Background="Red" IsHitTestVisible="False" SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<JobCostEntity> l = new List<JobCostEntity>()
{
new JobCostEntity() {Id = 0, InvoiceDate = DateTime.Now, Description = "A"},
new JobCostEntity() {Id = 0, InvoiceDate = DateTime.Now, Description = "B"}
};
dg.ItemsSource = l;
}
private void dg_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
//MessageBox.Show("AddingNewItem");
}
}
public partial class JobCostEntity : INotifyPropertyChanged
{
private string _description;
private DateTime? _invoiceDate;
public int Id { get; set; }
public int JobId { get; set; }
public Nullable<int> JobItemId { get; set; }
public Nullable<System.DateTime> InvoiceDate
{
get { return _invoiceDate; }
set
{
if (value.Equals(_invoiceDate)) return;
_invoiceDate = value;
OnPropertyChanged();
}
}
public Nullable<System.DateTime> ProcessedDate { get; set; }
public int PackageId { get; set; }
public int DelegateId { get; set; }
public string Description
{
get { return _description; }
set
{
if (value == _description) return;
_description = value;
OnPropertyChanged();
}
}
public Nullable<decimal> LabourCost { get; set; }
public Nullable<decimal> PlantOrMaterialCost { get; set; }
public Nullable<decimal> SubcontractorCost { get; set; }
public Nullable<decimal> TotalCost { get; set; }
public bool Paid { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Mon point de vue sur la question. Le problème que vous avez avec votre deuxième colonne est avec le DataGridTemplateColumn
. DataGridTemplateColumn
est la colonne réelle, donc c'est là où vous devez cliquer pour ajouter une nouvelle ligne, quand vous mettez un contrôle dans un DataTemplate
dans le DataGridCTemplateColumn.CellTemplate
, elle devient une "couche" au-dessus d'elle. Les commandes de cette "couche supérieure" sont alors utilisables sans cliquer réellement sur la ligne, ce qui signifie qu'elle ne crée pas de nouvelle ligne.
j'ai fait quelques tests pour le prouver, si vous créez une case à cocher colonne de cette façon:
<DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>
Si vous cliquez sur la case à cocher, il déclenche l'événement pour ajouter une nouvelle ligne parce que c'est la colonne réel, pas un contrôle sur elle.
Mais si vous faites la même chose mais avec l' DataGridTemplateColumn
, comme ceci:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Content="Paid" IsChecked="{Binding Paid}" Margin="5"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
notez la marge, pour pouvoir cliquer sur la cellule réelle et non sur le contrôle au-dessus de la cellule
de cette façon, si vous cliquez sur la cellule elle-même, cela déclenchera l'ajout d'un nouvel événement de ligne, alors que si vous cliquez sur la case qui est "au-dessus" de la cellule, il ne déclenchera pas l'événement et ne vérifiera/décochera que.
Il y a aussi une remarque sur la documentation msdn qui pourrait vous aider à comprendre aussi:
le type DataGridTemplateColumn vous permet de créer vos propres types de colonnes en spécifiant les modèles de cellules utilisés pour afficher les valeurs et permettre l'édition. Définissez la propriété CellTemplate pour spécifier le contenu des cellules qui afficher les valeurs, mais ne pas autoriser l'édition. Définissez la propriété CellEditingTemplate pour spécifier le contenu des cellules en mode édition. Si vous définissez la propriété IsReadOnly à true, La valeur de la propriété CellEditingTemplate n'est jamais utilisée.
j'espère que cela vous donne une meilleure idée de ce qui se passe avec votre DataGrid
EDIT
quelque chose comme ceci vous permettrait d'ajouter manuellement la ligne lorsque vous cliquez sur "Entrer" après selectionning votre date.
private void DatePicker_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
List<JobCostEntity> tempList = (List<JobCostEntity>)dg.ItemsSource;
tempList.Add(new JobCostEntity() { InvoiceDate = ((DatePicker)sender).DisplayDate });
dg.ItemsSource = tempList;
}
}
dans le cas où vous avez besoin d'une solution pour votre InvoiceDate, voici une façon d'avoir le comportement que vous décrivez pour DateWorks en créant un DataGridDateColumn comme ceci:
public class DataGridDateColumn : DataGridBoundColumn
{
public string DateFormatString { get; set; }
protected override void CancelCellEdit(FrameworkElement editingElement, object before)
{
var picker = editingElement as DatePicker;
if (picker != null)
{
picker.SelectedDate = DateTime.Parse(before.ToString());
}
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
var element = new DatePicker();
var binding = new Binding(((Binding)this.Binding).Path.Path) {Source = dataItem};
if (DateFormatString != null)
{
binding.Converter = new DateTimeConverter();
binding.ConverterParameter = DateFormatString;
}
element.SetBinding(DatePicker.SelectedDateProperty, this.Binding);
return element;
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var element = new TextBlock();
var b = new Binding(((Binding) Binding).Path.Path) {Source = dataItem};
if (DateFormatString != null)
{
b.Converter = new DateTimeConverter();
b.ConverterParameter = DateFormatString;
}
element.SetBinding(TextBlock.TextProperty, b);
return element;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var element = editingElement as DatePicker;
if (element != null)
{
if (element.SelectedDate.HasValue ) return element.SelectedDate.Value;
}
return DateTime.Now;
}
}
public class DateTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var date = (DateTime)value;
return date.ToString(parameter.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTime resultDateTime;
if (DateTime.TryParse(value.ToString(), out resultDateTime))
{
return resultDateTime;
}
return value;
}
}
j'ai ensuite ajouté deux autres colonnes de la grille:
<custom:DataGridDateColumn Header="Custom" Binding="{Binding InvoiceDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>
si je clique maintenant dans le champ personnalisé, j'obtiens la boîte de Message, je sélectionne une date et puis je sors, la valeur est effacée jusqu'à ce que J'implémente INPC sur InvoiceDate:
private Nullable<System.DateTime> _invoiceDate;
public Nullable<System.DateTime> InvoiceDate
{
get { return _invoiceDate; }
set
{
_invoiceDate = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
maintenant, la date est affichée selon le DateFormatString
définir.
encore une fois, je suis conscient que cela ne répond pas à votre question initiale, mais après mon commentaire précipité d'avant, je me suis senti obligé de trouver au moins une solution de contournement spécifique.
EDIT - ajout de code pour rendre l'édition en un clic possible.
- Changé toutes les liaisons de colonne
UpdateSourceTrigger=PropertyChanged
- C'est parce que la valeur par défaut deLostFocus
fonctionne au niveau de la rangée, pas au niveau de la cellule, ce qui signifie que vous devez laisser la rangée complètement avant que les fixations prennent effet. Cela fonctionne bien dans de nombreuses situations, mais pas lorsque vous avez deux colonnes liées à la même propriété, parce que les modifications faites à l'une de ces colonnes ne se verront pas immédiatement dans l'autre colonne. - Set
IsHitTestVisible="False"
au modèle de non-édition de la colonne centrale-ma première approche a été de faire la colonne en lecture seule et d'utiliser seulement le CellTemplate... Mais cela n'a pas déclenché L'événement AddingNewItem. Il semble que vous ayez besoin de passer de la cellule régulière à la cellule d'édition pour que cet événement se déclenche, mais puisque votre gabarit de non-édition n'est pas ce avec quoi vous voulez que l'utilisateur interagisse, désactiver le test de succès a tout son sens. De cette façon, vous forcer l'utilisateur à modifier le mode, donc déclenchant l'événement, avant de pouvoir entrer input. - manipulé le
CurrentCellChanged
cas de la grille de données. Dans le gestionnaire, utilisez la méthodeCommitEdit()
pour s'assurer que la cellule précédemment sélectionnée quitte le mode édition, et un appel asynchrone àBeginEdit()
pour commencer à éditer la cellule courante immédiatement, sans avoir à attendre un second clic. - manipulé le
Loaded
Evénement des pics de données à l'intérieur des templates CellEditingTemplates. Dans le gestionnaire, utiliséKeyboard.Focus()
pour donner concentrez - vous sur le lecteur de données dès qu'il est chargé, épargnant à l'utilisateur la nécessité de cliquer une troisième fois pour mettre l'accent sur le contrôle.
XAML:
<Grid>
<DataGrid x:Name="dg" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="299" AutoGenerateColumns="False" Width="497" AddingNewItem="dg_AddingNewItem" CanUserAddRows="True"
CurrentCellChanged="dg_CurrentCellChanged">
<DataGrid.Columns>
<DataGridTemplateColumn Header="DateWorks">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker Loaded="DatePicker_Loaded"
SelectedDate="{Binding InvoiceDate,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="DateDoesn'tWork">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker IsHitTestVisible="False"
SelectedDate="{Binding InvoiceDate,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker Loaded="DatePicker_Loaded"
SelectedDate="{Binding InvoiceDate,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text" Binding="{Binding Description,
UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
code-behind:
private void dg_CurrentCellChanged(object sender, EventArgs e)
{
var dataGrid = sender as DataGrid;
dataGrid.CommitEdit();
Dispatcher.BeginInvoke(new Action(() => dataGrid.BeginEdit()), System.Windows.Threading.DispatcherPriority.Loaded);
}
private void DatePicker_Loaded(object sender, RoutedEventArgs e)
{
Keyboard.Focus(sender as DatePicker);
}
la première partie du code n'indique que la date dans la"colonne de travail". Pour corriger le clic deux fois pour éditer, alors vous pouvez utiliser la classe helper.
j'Espère que ça aide...
<Window x:Class="WpfApplicationAnswerForStackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="dg" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="299" AutoGenerateColumns="False" Width="497" AddingNewItem="dg_AddingNewItem" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="DateWorks">
<!-- Here -->
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding InvoiceDate, StringFormat='d'}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="DateDoesn'tWork">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding InvoiceDate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
correction pour édition simple clic:
Utilisation:
<Window ...
xmlns:WpfUtil="clr-namespace:HQ.Util.Wpf.WpfUtil;assembly=WpfUtil">
<DataGrid ... util:DataGridCellHelper.IsSingleClickInCell="True">
Classe
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace HQ.Wpf.Util
{
public static class DataGridCellHelper
{
#region IsSingleClickInCell
public static readonly DependencyProperty IsSingleClickInCellProperty =
DependencyProperty.RegisterAttached("IsSingleClickInCell", typeof(bool), typeof(DataGrid), new FrameworkPropertyMetadata(false, OnIsSingleClickInCellSet)); public static void SetIsSingleClickInCell(UIElement element, bool value) { element.SetValue(IsSingleClickInCellProperty, value); }
public static bool GetIsSingleClickInCell(UIElement element)
{
return (bool)element.GetValue(IsSingleClickInCellProperty);
}
private static void OnIsSingleClickInCellSet(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (!(bool)(DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue))
{
if ((bool)e.NewValue)
{
var dataGrid = sender as DataGrid;
Debug.Assert(dataGrid != null);
EventManager.RegisterClassHandler(typeof(DataGridCell),
DataGridCell.PreviewMouseLeftButtonUpEvent,
new RoutedEventHandler(OnPreviewMouseLeftButtonDown));
}
}
}
private static void OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
{
var checkBoxes = ControlHelper.FindVisualChildren<CheckBox>(cell);
if (checkBoxes != null && checkBoxes.Count() > 0)
{
foreach (var checkBox in checkBoxes)
{
if (checkBox.IsEnabled)
{
checkBox.Focus();
checkBox.IsChecked = !checkBox.IsChecked;
var bindingExpression = checkBox.GetBindingExpression(CheckBox.IsCheckedProperty); if (bindingExpression != null) { bindingExpression.UpdateSource(); }
}
break;
}
}
}
}
#endregion
}
}