c# - DataGridTemplateColumn(ComboBox, DatePicker) se reinicia/borra y no se dispara
wpf (5)
En caso de que necesite una solución para su InvoiceDate, aquí hay una manera de tener el comportamiento que describe para DateWorks creando un DataGridDateColumn como:
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;
}
}
Luego agregué dos columnas más a su cuadrícula:
<custom:DataGridDateColumn Header="Custom" Binding="{Binding InvoiceDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>
Si hago clic en el campo Personalizado ahora, obtengo el Cuadro de mensaje, selecciono una fecha y luego salgo, el valor se borra hasta que implemento INPC en la Fecha de facturación:
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));
}
Ahora, la fecha se muestra de acuerdo con el conjunto DateFormatString
.
Una vez más, soy consciente de que no responde a su pregunta original, pero después de mi apresurado comentario de antes, me sentí obligado a al menos encontrar una solución específica.
He reducido el problema al siguiente ejemplo que tiene un DataGrid con tres columnas.
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>
DO#:
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 primera columna en la que hace clic en la nueva fila de elementos es ''DateWorks'' o ''Texto'', entonces levantará el evento AddingNewItem.
Si en su lugar, primero hace clic en la columna ''DateDoesntWork'', puede seleccionar una fecha, pero no se agrega ningún nuevo elemento hasta que se mueve a una de las otras columnas, en cuyo punto se borra el valor del DatePicker ''DateDoesntWork''.
¿Qué diablos está pasando?
Podría decirse que (!) Es deseable que el Selector de fechas ya sea visible para el usuario (de ahí que sea un CellTemplate y un CellEditingTemplate), en lugar de que ellos tengan que hacer clic en la celda para "revelar" el control.
¿Hay alguna manera de informar al DataGrid que mi control DataGridTemplateColumn acaba de establecer un valor en una nueva fila? Si es así, ¿cómo es eso?
EDITAR:
Inspirado en esta publicación: https://social.msdn.microsoft.com/Forums/vstudio/en-US/93d66047-1469-4bed-8fc8-fa5f9bdd2166/programmatically-beginning-edit-in-datagrid-cell?forum=wpf
Traté de solucionar el problema agregando lo siguiente a la columna de DatePoesntWork ''DatePicker'', que provoca el evento AddingNewItem, pero la fecha seleccionada aún no se agrega a la entidad subyacente.
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();
}
}
Parece que el DatePicker todavía está intentando apuntar al NewItemPlaceholder, ¡¿si eso tiene algún sentido ?!
Aún más extraño, si selecciona una fecha en la columna DateDoesntWork en la nueva fila, luego comienza a editar la columna de Texto en la nueva fila, luego, sin ingresar ningún texto, seleccione la fila de arriba ... ahora se agrega otra nueva fila y esa nueva fila agregada muestra la fecha que seleccioné para la fila anterior !!!
Total. Locura.
Como Maxime Tremblay-Savard ha mencionado, parece que CellTemplate
está bloqueando la "capa" a continuación y deteniendo la AddingNewItem
evento AddingNewItem
, aunque los tipos DataGridColumn
incorporados no tienen este problema.
La primera parte del código es solo para mostrar la fecha en ''Columna de trabajo ". Para corregir el clic dos veces para editar, puede usar la clase auxiliar.
Espero eso ayude...
<Window x:Class="WpfApplicationAnswerFor.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>
Corrección para la edición de un solo clic:
Uso:
<Window ...
xmlns:WpfUtil="clr-namespace:HQ.Util.Wpf.WpfUtil;assembly=WpfUtil">
<DataGrid ... util:DataGridCellHelper.IsSingleClickInCell="True">
Clase
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
}
}
Mi opinión sobre el tema. El problema que tiene con su segunda columna es con DataGridTemplateColumn
. DataGridTemplateColumn
es la columna real, por lo que es donde debe hacer clic para agregar una nueva línea, cuando coloca un control en una DataTemplate
en DataGridCTemplateColumn.CellTemplate
, se convierte en una "capa" sobre ella. Los controles en esta "capa superior" se pueden utilizar sin hacer clic en la fila, lo que significa que no crea una nueva línea.
Hice algunas pruebas para demostrar esto, si creas una columna de casilla de verificación de esta manera:
<DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>
Si hace clic en la casilla de verificación, activa el evento para agregar una nueva línea porque esta es la columna real, no un control sobre ella.
Pero si haces lo mismo pero con DataGridTemplateColumn
, así:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Content="Paid" IsChecked="{Binding Paid}" Margin="5"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Observe el margen, para poder hacer clic en la celda real y no en el control sobre la celda
De esta manera, si hace clic en la celda, activará el evento de agregar una nueva línea, mientras que si hace clic en la casilla que está "arriba" de la celda, no activará el evento y solo lo marcará / desactivará. .
También hay un comentario sobre la documentación de msdn que podría ayudarlo a comprender también:
El tipo DataGridTemplateColumn le permite crear sus propios tipos de columna especificando las plantillas de celda utilizadas para mostrar valores y habilitar la edición. Establezca la propiedad CellTemplate para especificar el contenido de las celdas que muestran valores, pero no permiten la edición. Establezca la propiedad CellEditingTemplate para especificar el contenido de las celdas en el modo de edición. Si establece la columna propiedad IsReadOnly en verdadero, el valor de la propiedad CellEditingTemplate nunca se usa.
Espero que esto le brinde una mejor idea de lo que está sucediendo con su DataGrid
EDITAR
Algo como esto le permitiría agregar la línea manualmente al hacer clic en "Entrar" después de seleccionar su fecha.
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;
}
}
Si usa un control que controla el clic del mouse dentro de CellTemplate, el DataGrid nunca recibe un evento de clic que lo active para cambiar al modo de edición. Entonces, como mencionó eoinmullan, la solución es establecer el control IsHitTestVisible = False. A continuación se muestra el código de trabajo. Agregué INotifyPropertyChanged para que podamos ver el valor cambiado reflejado en la interfaz de usuario. También agregué un fondo rojo para DateDoesn''tWork CellTemplate, para que puedas ver cuando el DataGrid pasa del modo de visualización al modo de edición.
<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));
}
}
EDITAR - Código agregado para hacer posible la edición con un solo clic.
- Se modificaron todos los enlaces de columna con
UpdateSourceTrigger=PropertyChanged
: esto se debe a que el valor predeterminado deLostFocus
funciona a nivel de fila, no a nivel de celda, lo que significa que debe dejar la fila completamente antes de que los enlaces entren en vigor. Esto funciona bien para muchas situaciones, pero no cuando tiene dos columnas vinculadas a la misma propiedad, porque los cambios realizados en una de esas columnas no se mostrarán inmediatamente en la otra columna. - Establezca
IsHitTestVisible="False"
en la plantilla de no edición de la columna central. Mi primer enfoque fue hacer que la columna fuera de solo lectura y usar solo la plantilla de la celda ... Pero esto no activó el evento AddingNewItem. Parece que NECESITA cambiar de la celda normal a la celda de edición para que se active ese evento, pero como la plantilla que no edita no es lo que usted quiere que el usuario interactúe, deshabilitar las pruebas de aciertos tiene todo el sentido. De esa manera, obliga al usuario a cambiar al modo de edición, lo que activa el evento, antes de poder ingresar una entrada. - Manejó el evento
CurrentCellChanged
de DataGrid. En el controlador, use los métodosCommitEdit()
para asegurarse de que la celda seleccionada anteriormente abandone el modo de edición y una llamada asíncrona aBeginEdit()
para comenzar a editar la celda actual de inmediato, sin tener que esperar un segundo clic. - Manejó el evento
Loaded
de los DatePickers dentro de CellEditingTemplates. En el controlador, se utilizóKeyboard.Focus()
para enfocar el Selector de fechas tan pronto como se carga, lo que evita que el usuario haga clic una tercera vez para poner el foco en el control.
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>
Código detrás:
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);
}