在WPF DataGrid中实现编辑和双击(MouseDoubleClick)。

时间:2014-05-21 22:32:57

标签: wpf events mvvm datagrid

我已经使用DataGrid编写了WPF UserControl。我添加了使用“单击编辑”编辑一列(RecordingName)的功能(请参阅:我的代码和http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing)。我也在为整个DataGrid处理MouseDoubleClick事件。

它有效......有点......你当然可以编辑有问题的列(RecordingName),当你双击该列以外的任何地方时,一切都很好。当你双击你遇到的那一列时,就是这样。这对我来说并不太令人惊讶。您正尝试捕获双击,但您也在查看单击(通过PreviewMouseLeftButtonDown事件)。

我认为这是一个常见问题。有人可以建议我处理这个问题的最佳方法吗?我绝对需要支持双击,但是也可以通过单击编辑来编辑RecordingName。

我还想支持通过右键单击并选择重命名来编辑RecordingName,并通过F2选择它。如果您转到Windows资源管理器,这是您看到的行为。如果选择该文件然后左键单击它,则表示您处于编辑(重命名)模式。如果您快速双击它,则会启动该文件。如果右键单击,或选择并按F2,则可以重命名。

感谢您的任何帮助或想法。我已粘贴下面的代码。我确实尝试将其截断到最低限度。它仍然是相当多的代码。无论好坏,我使用MVVM模型来控制自身。

这是控件的xaml:

<UserControl x:Class="StackOverFlowExample.RecordingListControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
          xmlns:local="clr-namespace:StackOverFlowExample"
         mc:Ignorable="d" 

         d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
    <ResourceDictionary>

    <Style TargetType="{x:Type DataGridCell}">
        <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown"></EventSetter>
    </Style>
        <Style x:Key="CellViewStyle" TargetType="{x:Type Label}" BasedOn="{StaticResource {x:Type Label}}">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={RelativeSource Self},
                                Path=(Validation.Errors)[0].ErrorContent}"/>
                <Setter Property="BorderBrush" Value="Red" />
                <Setter Property="BorderThickness" Value="1" />
            </Trigger>
        </Style.Triggers>
    </Style>



        <Style TargetType="{x:Type DataGrid}" >
        <Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RecordingListControl}}, Path=Foreground}" />

            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                </Trigger>
            </Style.Triggers>

    </Style>

    <!-- style to apply to DataGridTextColumn in edit mode  -->
    <Style x:Key="CellEditStyle" TargetType="{x:Type TextBox}">
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Padding" Value="0"/>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={RelativeSource Self},
                                Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="CellNonEditStyle" TargetType="{x:Type TextBlock}">

        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={RelativeSource Self},
                                Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    </ResourceDictionary>
</UserControl.Resources>
<Grid >
    <Grid Name="LayoutRoot">
        <DataGrid Name="MainDataGrid" IsEnabled="{Binding Path=IsEnabled}" ItemsSource="{Binding Path=Recordings}" Margin="5"  SelectionChanged="ListBox_SelectionChanged" MouseDoubleClick="DataGrid_MouseDoubleClick" AutoGenerateColumns="False" >
            <DataGrid.Columns>

                <DataGridTextColumn Header="#"  IsReadOnly="True"  Binding="{Binding RecordingNumber}">
                    <DataGridTextColumn.HeaderStyle>
                        <Style TargetType="DataGridColumnHeader">

                            <Setter Property="HorizontalAlignment"
             Value="Center" />
                        </Style>
                    </DataGridTextColumn.HeaderStyle>
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">

                            <Setter Property="HorizontalAlignment" Value="Center" />
                            <Setter Property="VerticalAlignment" Value="Center" />
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>

                <DataGridTemplateColumn SortMemberPath="RecordingName" Header="Recording Name" CanUserSort="True">
                    <DataGridTemplateColumn.HeaderStyle>
                        <Style TargetType="DataGridColumnHeader">
                            <Setter Property="HorizontalContentAlignment"
             Value="Center" />
                        </Style>
                    </DataGridTemplateColumn.HeaderStyle>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Label Content ="{Binding RecordingName, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" Foreground="Black"  Style="{StaticResource CellViewStyle}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding RecordingName, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>

                <DataGridTextColumn Header="Time" CanUserSort="False" IsReadOnly="True"  Binding="{Binding TotalTime, StringFormat=mm\\:ss}">
                    <DataGridTextColumn.HeaderStyle>
                        <Style TargetType="DataGridColumnHeader">
                            <Setter Property="HorizontalContentAlignment"
             Value="Center" />
                        </Style>
                    </DataGridTextColumn.HeaderStyle>
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="HorizontalAlignment" Value="Center" />
                            <Setter Property="VerticalAlignment" Value="Center" />
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>
                <DataGridTextColumn Header="End Time" IsReadOnly="True" SortMemberPath="EndTime" Binding="{Binding EndTime,StringFormat={}\{0:dd/MM/yyyy HH:mm\}}">
                    <DataGridTextColumn.HeaderStyle>
                        <Style TargetType="DataGridColumnHeader">
                            <Setter Property="HorizontalContentAlignment"
             Value="Center" />
                        </Style>
                    </DataGridTextColumn.HeaderStyle>
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="HorizontalAlignment" Value="Center" />
                            <Setter Property="VerticalAlignment" Value="Center" />
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>
            </DataGrid.Columns>
            <DataGrid.Resources>

            </DataGrid.Resources>
            <DataGrid.ContextMenu >
                <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">

                    <MenuItem 
                    Header="Delete Recording" 
                    Command="{Binding Path=DataContext.DeleteRecordingCommand}"
                    CommandParameter="{Binding Path=SelectedItem}"/>
                </ContextMenu>
            </DataGrid.ContextMenu>
    </DataGrid>
    </Grid>

</Grid>

这里是后面的代码(需要这个依赖属性。我不知道另一种方式)

using System;
using System.Windows;
 using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace StackOverFlowExample
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class RecordingListControl : UserControl
{
    public delegate void SelectionEventHandler(object sender, RecordingInfo info);

    public event SelectionEventHandler DoubleClickEvent;

    public RecordingListViewModel vm = new RecordingListViewModel();
    public RecordingListControl()
    {
        InitializeComponent();

       LayoutRoot.DataContext = vm;
    }

    #region Dependency property for SelectedItem
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(RecordingInfo), typeof(RecordingListControl));

    public RecordingInfo SelectedItem
    {
        get { return (RecordingInfo)GetValue(SelectedItemProperty); }
        set
        {
            SetValue(SelectedItemProperty, value);
        }
    }
    #endregion

    public static FrameworkPropertyMetadata md = new FrameworkPropertyMetadata(new PropertyChangedCallback(OnSomeCallback));

    public static readonly DependencyProperty SomeDependencyProperty =
   DependencyProperty.Register("SomeDependency", typeof(bool), typeof(RecordingListControl), md);


    public bool SomeDependency
    {  
    get { return (bool)GetValue(SomeDependencyProperty); }
        set { SetValue(SomeDependencyProperty, value); }
    }

    private static void OnSomeCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
       RecordingListControl ctrl = (RecordingListControl)d;

       ctrl.vm.PopulateGrid();

    }

    private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems != null && e.AddedItems.Count > 0)
            SelectedItem = e.AddedItems[0] as RecordingInfo;
        else
            SelectedItem = null;
    }

    private void DataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        if (MainDataGrid.SelectedItem == null || SelectedItem == null)
            return;

        if (DoubleClickEvent != null)
        {
            DoubleClickEvent(sender, SelectedItem);
        }
    }



    // from: http://wpf.codeplex.com/wikipage?title=Single-Click%20Editing
    // SINGLE CLICK EDITING
    //
    private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        DataGridCell cell = sender as DataGridCell;
        if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
        {
            if (!cell.IsFocused)
            {
                cell.Focus();
            }
            DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
            if (dataGrid != null)
            {
                if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
                {
                    if (!cell.IsSelected)
                        cell.IsSelected = true;
                }
                else
                {
                    DataGridRow row = FindVisualParent<DataGridRow>(cell);
                    if (row != null && !row.IsSelected)
                    {
                        row.IsSelected = true;
                    }
                }
            }
        }
    }

    static T FindVisualParent<T>(UIElement element) where T : UIElement
    {
        UIElement parent = element;
        while (parent != null)
        {
            T correctlyTyped = parent as T;
            if (correctlyTyped != null)
            {
                return correctlyTyped;
            }

            parent = VisualTreeHelper.GetParent(parent) as UIElement;
        }
        return null;
    }

}

}

ViewModel:

public class RecordingInfo
{
    public string RecordingName { get; set; }
    public int RecordingNumber { get; set; }
    public TimeSpan? TotalTime { get; set; }
    public DateTime? EndTime { get; set; }
}
public class RecordingListViewModel : ViewModelBase
{
    private ObservableCollection<RecordingInfo> _recordings = null;
    private string _patientId;
    private int _sessionNumber;
    BackgroundWorker _workerThread = new BackgroundWorker();


    public RecordingListViewModel()
    {
        _workerThread.DoWork += new DoWorkEventHandler(workerThread_DoWork);
        _workerThread.RunWorkerCompleted += new
            RunWorkerCompletedEventHandler(workerThread_RunWorkerCompleted);

    }
    public ObservableCollection<RecordingInfo> Recordings
    {
        get
        {
            return _recordings;
        }
    }

    bool _isEnabled = false;
    public bool IsEnabled
    {
        get
        {
            return _isEnabled;
        }
        private set
        {
            if (value != _isEnabled)
            {
                _isEnabled = value;
                OnPropertyChanged("IsEnabled");
            }
        }

    }

    public void PopulateGrid()
    {
        _workerThread.RunWorkerAsync();  // this is overkill in this demo project...
    }


    private void workerThread_DoWork(object sender, DoWorkEventArgs e)
    {
        _recordings = new ObservableCollection<RecordingInfo>();
        RecordingInfo info1 = new RecordingInfo() { TotalTime = new TimeSpan(100), EndTime = DateTime.Now, RecordingName = "recording 1", RecordingNumber = 1 };
        _recordings.Add(info1);

        RecordingInfo info2= new RecordingInfo() { TotalTime = new TimeSpan(10000), EndTime = new DateTime(1999,2,2), RecordingName = "recording 2", RecordingNumber = 2 };
        _recordings.Add(info2);

        RecordingInfo info3 = new RecordingInfo() { TotalTime = new TimeSpan(7000), EndTime = new DateTime(2008, 2, 2), RecordingName = "recording 3", RecordingNumber = 3};
        _recordings.Add(info3);
    }

    private void workerThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        OnPropertyChanged("Recordings");
        IsEnabled = true;
    }


    public void SaveRecording(RecordingInfo info)
    {

    }

    private RecordingInfo _selectedItem = null;
    public RecordingInfo SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            if (value == _selectedItem)
                return;
            // verify that selected item is actully in our collection of recordings!
            if (!_recordings.Contains(value))
                throw new ApplicationException("Selected item not in collection");

            _selectedItem = value;

            OnPropertyChanged("SelectedItem");

            // selection changed - do something special
        }
    }

    private ICommand _deleteRecordingCmd = null;
    public ICommand DeleteRecordingCommand
    {
        get
        {
            if (_deleteRecordingCmd == null)
            {
                _deleteRecordingCmd = new RelayCommand(param => DeleteRecordingCommandImplementation(param));
            }
            return _deleteRecordingCmd;
        }
    }

    /// <summary>
    /// I used ideas from this post to get Delete working:
    /// http://stackoverflow.com/questions/19447795/command-bind-to-contextmenu-which-on-listboxitem-in-listbox-dont-work
    /// </summary>
    /// <param name="note"></param>
    private void DeleteRecordingCommandImplementation(object recording)
    {

            if (_recordings != null && _recordings.Count > 0 && recording is RecordingInfo)
            {
                if (_recordings.Contains(recording as RecordingInfo))
                {
                    _recordings.Remove(recording as RecordingInfo);

                }
                OnPropertyChanged("Recordings");
            }

    }


}

MainWindow xaml和代码背后:

<Window x:Class="StackOverFlowExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:StackOverFlowExample"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <local:RecordingListControl x:Name="_ctrl"></local:RecordingListControl>
</Grid>

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        _ctrl.SomeDependency = true;
        _ctrl.DoubleClickEvent += _ctrl_DoubleClickEvent;
    }

    void _ctrl_DoubleClickEvent(object sender, RecordingInfo info)
    {
        MessageBox.Show("You double clicked me!");
    }
}

1 个答案:

答案 0 :(得分:4)

我不确定你真正想要实现的目标。单击是否仅更改标题,而双击是否可以使用DataGrid-Entry执行某些操作?

如果是这样的话,您可以轻松区分这两者,这样您就不会处理错误的案例(取决于您选择的处理程序):

MouseDown:

OnMouseDownDown(object sender, MouseButtonEventArgs e)
{
    UIElement uiElement = (UIElement) sender;
    Point hitPoint = e.GetPosition(uiElement);

    HitTestResult hitTestResult = VisualTreeHelper.HitTest(uiElement, hitPoint);
    if (hitTestResult == null)
    {
        return;
    }

     Visual DataGridHeader= hitTestResult.VisualHit.FindParent<DataGridHeader>();
}

单击:使用VisualTreeHelper获取eventArgs.OriginalSource

的[您寻找的任何类型。]的第一个父级。
OnHandleMouseClick(object sender, RoutedEventArgs e)
{
  var target = e.OriginalSource.FindVisualParent<DataGridHeader>();
}

如果您只想区分单击和双击(可能与hittesting结合使用,您仍然可以使用MouseDown并查看EventArgs中的ClickCount。此计数处理给定时间范围内的点击和可视矩形以区分单个和双击。

希望这有帮助。

EDIT ---------------------------------------------- --------&GT;

我评论后建了一个小样本。您可以在这里下载: http://1drv.ms/1kgnCeQ

当我在StackOverflow上发现另一个问题时,我正在寻找一个解决DispatcherTimer的解决方案。虽然我提出了正确的想法,但您可以找到此处使用的代码的来源:WPF: Button single click + double click issue

这应该是你要求的。如果您需要有关F2或ContextMenu的帮助,请发表评论。 :)

一切顺利 Gope

EDIT2 ---------------------------------------------- -------&GT;

我已经创建了一个处理您所有要求的新示例:http://1drv.ms/1kgnCeQ 它包含一个简单的ContextMenu,以及除前一个要求之外的F2编辑。它在ExceptionHandling等方面非常原始,但它显示了如何处理所有情况。应该这样做。我还在DoubleClick部件中添加了dataGrid.CancelEdit以重置editmode。 :)

以下是样本中的代码,以便其他人更轻松地阅读此答案:

MainWindow.xaml:

<Window x:Class="IDevign.DataGrids.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dataGrids="clr-namespace:IDevign.DataGrids"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid x:Name="dataGrid" AutoGenerateColumns="False" MouseDoubleClick="Button_MouseDoubleClick" PreviewKeyDown="DataGrid_OnPreviewKeyDown">
            <DataGrid.Resources>
                <Style TargetType="{x:Type DataGridCell}">
                    <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown"></EventSetter>
                </Style>
            </DataGrid.Resources>
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Edit" Click="MenuItem_OnClick"></MenuItem>
                </ContextMenu>
            </DataGrid.ContextMenu>
            <DataGrid.Columns>
                <DataGridTextColumn Header="Recording Name" Binding="{Binding Name}"/>
                <DataGridTextColumn Header="Duration"  Binding="{Binding Duration}" IsReadOnly="True"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

MainWindow.xaml.cs:

/// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private static DispatcherTimer myClickWaitTimer = new DispatcherTimer(
            new TimeSpan(0, 0, 0, 1),
            DispatcherPriority.Background,
            mouseWaitTimer_Tick,
            Dispatcher.CurrentDispatcher);

        public MainWindow()
        {
            InitializeComponent();
            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            ObservableCollection<Song> songs = new ObservableCollection<Song>
            {
                new Song {Duration = 3.11, Name = "Best song ever"},
                new Song {Duration = 3.33, Name = "2nd best song ever"},
                new Song {Duration = 3.02, Name = "3rd best song ever"}
            };

            dataGrid.ItemsSource = songs;
        }

        private void Button_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            // Stop the timer from ticking.
            myClickWaitTimer.Stop();
            dataGrid.CancelEdit();
            MessageBox.Show("DoubleClicked");
            e.Handled = true;
        }

        private static void mouseWaitTimer_Tick(object sender, EventArgs e)
        {
            myClickWaitTimer.Stop();

        }

        /// <summary>
        /// Handles the LeftButton Click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DataGridCell_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            myClickWaitTimer.Start();

            DataGridCell cell = sender as DataGridCell;
            if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
            {
                if (!cell.IsFocused)
                {
                    cell.Focus();
                }
                DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
                if (dataGrid != null)
                {
                    if (dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
                    {
                        if (!cell.IsSelected)
                            cell.IsSelected = true;
                    }
                    else
                    {
                        DataGridRow row = FindVisualParent<DataGridRow>(cell);
                        if (row != null && !row.IsSelected)
                        {
                            row.IsSelected = true;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// KeyDown Handler for F2 Key
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DataGrid_OnPreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (dataGrid.SelectedItem == null || e.Key != Key.F2)
            {
                return;
            }

            DataGridRow selectedRow = dataGrid.ItemContainerGenerator.ContainerFromItem(dataGrid.SelectedItem) as DataGridRow;
            if (selectedRow == null)
            {
                return;
            }

            DataGridCell cell = GetCell(dataGrid, selectedRow, 0);
            if (cell != null)
            {
                cell.Focus();
                dataGrid.BeginEdit();
                e.Handled = true;
            }

        }

        /// <summary>
        /// ContextMenu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MenuItem_OnClick(object sender, RoutedEventArgs e)
        {
            DataGridRow selectedRow = dataGrid.ItemContainerGenerator.ContainerFromItem(dataGrid.SelectedItem) as DataGridRow;
            if (selectedRow == null)
            {
                return;
            }

            DataGridCell cell = GetCell(dataGrid, selectedRow, 0);
            if (cell != null)
            {
                cell.Focus();
                dataGrid.BeginEdit();
                e.Handled = true;
            }
        }

        /// <summary>
        /// Helper Method to get a cell by columnIndex
        /// </summary>
        /// <param name="dataGrid"></param>
        /// <param name="rowContainer"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
        {
            if (rowContainer != null)
            {
                DataGridCellsPresenter presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
                if (presenter == null)
                {
                    /* if the row has been virtualized away, call its ApplyTemplate() method
                     * to build its visual tree in order for the DataGridCellsPresenter
                     * and the DataGridCells to be created */
                    rowContainer.ApplyTemplate();
                    presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
                }
                if (presenter != null)
                {
                    DataGridCell cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
                    if (cell == null)
                    {
                        /* bring the column into view
                         * in case it has been virtualized away */
                        dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
                        cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
                    }
                    return cell;
                }
            }
            return null;
        }

        #region VisualTreeHelper Methods 

        private static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child is T)
                    return (T) child;
                else
                {
                    T childOfChild = FindVisualChild<T>(child);
                    if (childOfChild != null)
                        return childOfChild;


     }
        }
        return null;
    }

    private static T FindVisualParent<T>(UIElement element) where T : UIElement
    {
        UIElement parent = element;
        while (parent != null)
        {
            T correctlyTyped = parent as T;
            if (correctlyTyped != null)
            {
                return correctlyTyped;
            }

            parent = VisualTreeHelper.GetParent(parent) as UIElement;
        }
        return null;
    }

    #endregion
}

Song课程的最后一课:

class Song : INotifyPropertyChanged
    {
        private string name;
        private double duration;

        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                OnPropertyChanged();
            }
        }

        public double Duration
        {
            get { return duration; }
            set
            {
                duration = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }