DataGridTemplateColumn (ComboBox, DatePicker) Resets/Clears 并且不会触发 AddingNewItem

DataGridTemplateColumn (ComboBox, DatePicker) Resets/Clears and doesn't fire AddingNewItem

我已将问题缩小到以下具有三列 DataGrid 的示例。

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

如果您在新项目行中单击的第一列是 'DateWorks' 或 'Text',那么您将引发 AddingNewItem 事件。

如果您先单击 'DateDoesntWork' 列,则可以 select 一个日期,但在您移至其他列之一之前不会添加新项目,此时'DateDoesntWork' DatePicker 被清除。

这到底是怎么回事?


可以说(!)希望 DatePicker 已经对用户可见(因此 CellTemplate 和 CellEditingTemplate),而不是他们必须单击单元格才能 'reveal' 控件。

有没有什么方法可以通知 DataGrid 我的 DataGridTemplateColumn 控件刚刚在新行上设置了一个值?如果是这样,怎么会这样?!


编辑:

受此启发post:https://social.msdn.microsoft.com/Forums/vstudio/en-US/93d66047-1469-4bed-8fc8-fa5f9bdd2166/programmatically-beginning-edit-in-datagrid-cell?forum=wpf

我试图通过将以下内容添加到 'DateDoesntWork' 列 DatePicker 来解决问题,这确实会导致 AddingNewItem 事件触发,但 selected 日期仍然没有'不会添加到基础实体。

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

}

DatePicker 似乎仍在尝试以 NewItemPlaceholder 为目标,如果这有意义的话?!


还是陌生人,如果您 select 在新行的 DateDoesntWork 列中输入日期,然后开始编辑新行的 Text 列,然后不输入任何文本,select 该行上面...现在添加了另一个新行,新添加的行显示了我select为之前的行编辑的日期!!!

总计。疯狂。


正如 Maxime Tremblay-Savard 所提到的,似乎 CellTemplate 正在阻止下面的 'layer' 并停止触发 AddingNewItem 事件,尽管内置 DataGridColumn 类型不会遇到这个问题。

我对这个问题的看法。您在第二列中遇到的问题是 DataGridTemplateColumnDataGridTemplateColumn 是实际的列,因此您应该在此处单击以添加新行,当您将控件放在 DataGridCTemplateColumn.CellTemplate 中的 DataTemplate 中时,它会变成 "layer" 它上面。 "upper layer" 中的控件无需实际单击该行即可使用,这意味着它不会创建新行。


我做了一些测试来证明这一点,如果你这样创建一个复选框列:

<DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>

如果您单击复选框,它会触发添加新行的事件,因为这是实际的列,而不是对它的控制。

但是如果你用 DataGridTemplateColumn 做同样的事情,就像这样:

<DataGridTemplateColumn>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <CheckBox Content="Paid" IsChecked="{Binding Paid}" Margin="5"></CheckBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

注意边距,以便能够单击实际单元格而不是单元格上方的控件

这样的话,如果你点击单元格本身,它会触发添加一个新行事件,而如果你点击"above"单元格的复选框,它不会触发事件并且只会 check/uncheck 它。


在 msdn 文档中也有一条评论可能有助于您理解:

DataGridTemplateColumn 类型使您能够通过指定用于显示值和启用编辑的单元格模板来创建自己的列类型。设置 CellTemplate 属性 以指定显示值但不允许编辑的单元格内容。设置 CellEditingTemplate 属性 以在编辑模式下指定单元格的内容。如果将列 IsReadOnly 属性 设置为 true,则永远不会使用 CellEditingTemplate 属性 值。

我希望这能让您更好地了解您的 DataGrid

发生了什么

编辑

选择日期后单击 "Enter" 时,类似这样的操作将允许您手动添加该行。

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

如果您需要 InvoiceDate 的解决方案,可以通过如下创建 DataGridDateColumn 来实现您为 DateWorks 描述的行为:

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

然后我又在你的网格中添加了两列:

<custom:DataGridDateColumn Header="Custom" Binding="{Binding InvoiceDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>  
<DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>

如果我现在点击自定义字段,我会看到消息框,select 一个日期,然后跳出,值会被清除,直到我在 InvoiceDate 上实施 INPC:

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

现在,日期根据 DateFormatString 设置显示。

同样,我知道这并没有回答您原来的问题,但在我之前匆忙评论之后,我觉得有必要至少提出一个具体的解决方法。

如果您在 CellTemplate 中使用处理鼠标单击的控件,则 DataGrid 永远不会收到触发它切换到编辑模式的单击事件。所以就像eoinmullan提到的,解决方案是设置控件IsHitTestVisible=False。 下面是工作代码。我添加了 INotifyPropertyChanged,因此我们实际上可以看到 UI 中反映的更改值。我还为 DateDoes'tWork CellTemplate 添加了红色背景,因此您可以看到 DataGrid 何时从显示模式进入编辑模式。

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

EDIT - 添加了使一键编辑成为可能的代码。

  1. UpdateSourceTrigger=PropertyChanged 更改了所有列绑定 - 这是因为 LostFocus 的默认值在行级别而不是单元格级别工作,这意味着您必须在绑定生效。这适用于许多情况,但当您将两列绑定到同一列时则不行 属性,因为对其中一列所做的更改不会立即显示在另一列中。
  2. IsHitTestVisible="False"设置为中央栏的非编辑模板 - 我的第一种方法是将栏设置为只读并仅使用 CellTemplate...但这并没有触发 AddingNewItem 事件.您似乎需要从常规单元格更改为编辑单元格才能触发该事件,但由于您的非编辑模板不是您希望用户与之交互的模板,因此禁用命中测试很有意义。这样你就可以强制用户更改为编辑模式,从而触发事件,然后才能输入。
  3. 处理了 DataGrid 的 CurrentCellChanged 事件。在处理程序中,使用方法 CommitEdit() 确保先前选择的单元格离开编辑模式,并异步调用 BeginEdit() 以立即开始编辑当前单元格,而无需等待第二次点击.
  4. 处理了 CellEditingTemplates 中 DatePickers 的 Loaded 事件。在处理程序中,使用 Keyboard.Focus() 在 DatePicker 加载后立即将焦点置于 DatePicker 上,从而使用户无需第三次单击以将焦点置于控件上。

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>

代码隐藏:

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

代码的第一部分只是在“工作栏”中显示日期。 修复点击两次编辑,可以使用辅助class.

希望对您有所帮助...

<Window x:Class="WpfApplicationAnswerForWhosebug.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>

修复单击编辑:

用法:

<Window ...
        xmlns:WpfUtil="clr-namespace:HQ.Util.Wpf.WpfUtil;assembly=WpfUtil">

<DataGrid ... util:DataGridCellHelper.IsSingleClickInCell="True">

Class

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
    }
}