1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
5 namespace System.Activities.Presentation.View
8 using System.Activities.Presentation.Internal.PropertyEditing;
9 using System.Activities.Presentation.Internal.PropertyEditing.Model;
10 using System.Activities.Presentation.Model;
11 using System.Activities.Presentation.PropertyEditing;
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.Diagnostics.CodeAnalysis;
15 using System.Globalization;
19 using System.Windows.Automation;
20 using System.Windows.Controls;
21 using System.Windows.Controls.Primitives;
22 using System.Windows.Data;
23 using System.Windows.Input;
24 using System.Windows.Media;
25 using System.Windows.Threading;
26 using System.ComponentModel;
27 using System.Collections.ObjectModel;
28 using System.Windows.Media.Effects;
30 [SuppressMessage(FxCop.Category.Xaml, FxCop.Rule.TypesShouldHavePublicParameterlessConstructors,
31 Justification = "This class is never supposed to be created in xaml directly.")]
32 sealed partial class DataGridHelper
34 public static readonly string PART_ButtonAdd = "PART_ButtonAdd";
35 static readonly string dynamicContentControlName = "PART_Dynamic";
37 //content of the Add new row button
38 public static readonly DependencyProperty AddNewRowContentProperty =
39 DependencyProperty.Register("AddNewRowContent", typeof(object), typeof(DataGridHelper), new UIPropertyMetadata("<Add new row>"));
41 //binding to the command, which gets executed when new row button is clicked
42 public static readonly DependencyProperty AddNewRowCommandProperty =
43 DependencyProperty.Register("AddNewRowCommand", typeof(ICommand), typeof(DataGridHelper), new UIPropertyMetadata(null));
45 //attached property - used to store reference to data grid helper within data grid instance
46 static readonly DependencyProperty DGHelperProperty =
47 DependencyProperty.RegisterAttached("DGHelper", typeof(DataGridHelper), typeof(DataGrid), new UIPropertyMetadata(null));
49 static readonly DependencyProperty ControlBehaviorProperty =
50 DependencyProperty.RegisterAttached("ControlBehavior", typeof(EditingControlBehavior), typeof(DataGridHelper), new UIPropertyMetadata(null));
52 static readonly DependencyProperty NewRowLoadedProperty =
53 DependencyProperty.RegisterAttached("NewRowLoaded", typeof(bool), typeof(DataGridHelper), new UIPropertyMetadata(false));
55 static readonly DependencyProperty IsCommitInProgressProperty =
56 DependencyProperty.RegisterAttached("IsCommitInProgress", typeof(bool), typeof(DataGridHelper), new UIPropertyMetadata(false));
58 public static readonly DependencyProperty ShowValidationErrorAsToolTipProperty =
59 DependencyProperty.Register("ShowValidationErrorAsToolTip", typeof(bool), typeof(DataGridHelper), new UIPropertyMetadata(false));
61 public static readonly DependencyProperty IsCustomEditorProperty =
62 DependencyProperty.RegisterAttached("IsCustomEditor", typeof(bool), typeof(DataGridHelper), new UIPropertyMetadata(false));
64 public event EventHandler<DataGridCellEditEndingEventArgs> DataGridCellEditEnding;
66 static DataTemplate dynamicCellContentTemplate;
67 static Dictionary<Type, Type> EditorBehaviorTypeMapping = new Dictionary<Type, Type>
69 { typeof(ExpressionTextBox), typeof(ExpressionTextBoxBehavior) },
70 { typeof(TextBox), typeof(TextBoxBehavior) },
71 { typeof(TypePresenter), typeof(TypePresenterBehavior) },
72 { typeof(VBIdentifierDesigner), typeof(VBIdentifierDesignerBehavior) },
78 Func<ResolveTemplateParams, bool> resolveDynamicTemplateCallback;
79 Dictionary<string, DataGridColumn> MemberPathToColumnDict = new Dictionary<string, DataGridColumn>();
81 [SuppressMessage(FxCop.Category.Usage, FxCop.Rule.ReviewUnusedParameters, Justification = "Existing code")]
82 public DataGridHelper(DataGrid instance, Control owner)
84 this.InitializeComponent();
86 this.dataGrid = instance;
87 //apply default cell style
88 this.ApplyCellStyle();
89 //apply default row style
91 //apply default datagrid style
92 this.dataGrid.Style = (Style)this.FindResource("defaultDataGridStyle");
93 //handle data grid's loading event
94 this.dataGrid.LoadingRow += OnDataGridRowLoading;
95 //store reference to data grid helper within datagrid
96 DataGridHelper.SetDGHelper(this.dataGrid, this);
97 this.dataGrid.MouseDown += OnDataGridMouseDown;
98 this.dataGrid.Sorting += OnDataGridSorting;
99 this.dataGrid.CellEditEnding += OnDataGridCellEditEnding;
100 this.InitMemberPathToColumnDict();
103 public static bool GetIsCustomEditor(DependencyObject obj)
105 return (bool)obj.GetValue(IsCustomEditorProperty);
108 public static void SetIsCustomEditor(DependencyObject obj, bool value)
110 obj.SetValue(IsCustomEditorProperty, value);
113 void OnDataGridCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
115 if (this.DataGridCellEditEnding != null)
117 this.DataGridCellEditEnding(sender, e);
120 if ((!e.Cancel) && (!GetIsCommitInProgress(this.dataGrid)))
122 SetIsCommitInProgress(this.dataGrid, true);
124 bool commitSucceeded = false;
127 commitSucceeded = this.dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
129 catch (InvalidOperationException)
131 // Ignore and cancel edit
135 //if commit fails - undo change
136 if (!commitSucceeded)
138 this.dataGrid.CancelEdit();
141 SetIsCommitInProgress(this.dataGrid, false);
145 void InitMemberPathToColumnDict()
147 MemberPathToColumnDict.Clear();
148 foreach (DataGridColumn column in this.dataGrid.Columns)
150 if (column.CanUserSort &&
151 !string.IsNullOrEmpty(column.SortMemberPath) &&
152 !MemberPathToColumnDict.ContainsKey(column.SortMemberPath))
154 MemberPathToColumnDict.Add(column.SortMemberPath, column);
159 void OnDataGridSorting(object sender, DataGridSortingEventArgs e)
161 bool primaryColumnSorted = false;
162 ListSortDirection direction = (e.Column.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending;
163 e.Column.SortDirection = null;
165 foreach (SortDescription description in this.dataGrid.Items.SortDescriptions.Reverse())
167 if (MemberPathToColumnDict[description.PropertyName].SortDirection == null)
169 this.dataGrid.Items.SortDescriptions.Remove(description);
171 else if (description.PropertyName == this.dataGrid.Columns[0].SortMemberPath)
173 primaryColumnSorted = true;
177 this.dataGrid.Items.SortDescriptions.Add(new SortDescription(e.Column.SortMemberPath, direction));
178 e.Column.SortDirection = direction;
179 if (e.Column != this.dataGrid.Columns[0] && !primaryColumnSorted)
181 this.dataGrid.Items.SortDescriptions.Add(new SortDescription(this.dataGrid.Columns[0].SortMemberPath, ListSortDirection.Ascending));
183 this.dataGrid.Items.Refresh();
188 //Hook KeyDown event on DataGrid row to workaround DataGrid bug with customized NewItemPlaceHolder
189 void OnDataGridRowKeyDown(object sender, KeyEventArgs e)
195 if (e.Key == Key.Enter || e.Key == Key.Escape)
197 // If currentCell is the cell containing AddNewRowTemplate, its Column will be null since this cell
198 // spread across all columns in the grid. In this case, consume the event with no action.
199 if (dataGrid.CurrentCell.Column != null)
201 DataGridCellInfo currentCell = dataGrid.CurrentCell;
202 ObservableCollection<DataGridColumn> columns = dataGrid.Columns;
203 ItemCollection items = dataGrid.Items;
204 int currentColumnIndex = columns.IndexOf(dataGrid.ColumnFromDisplayIndex(currentCell.Column.DisplayIndex));
205 DataGridCell currentCellContainer = GetCell(dataGrid, items.IndexOf(currentCell.Item), currentColumnIndex);
206 if ((currentCellContainer != null) && (columns.Count > 0))
208 int numItems = items.Count;
209 bool shiftModifier = ((e.KeyboardDevice.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
210 int index = Math.Max(0, Math.Min(numItems - 1, items.IndexOf(currentCell.Item) + (shiftModifier ? -1 : 1)));
211 if (index < numItems)
213 if (items[index] == CollectionView.NewItemPlaceholder)
215 CommitAnyEdit(currentCellContainer);
232 void CommitAnyEdit(DataGridCell currentCellContainer)
234 IEditableCollectionView editableItems = (IEditableCollectionView)(this.dataGrid.Items);
235 DataGridCell cell = currentCellContainer;
236 bool isCurrentCellEditing = false;
237 this.ExplicitCommit = true;
240 isCurrentCellEditing = cell.IsEditing;
242 if (editableItems.IsAddingNew || editableItems.IsEditingItem)
244 this.dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
246 else if (isCurrentCellEditing)
248 this.dataGrid.CommitEdit(DataGridEditingUnit.Cell, true);
250 this.ExplicitCommit = false;
253 //callback executed whenever user clicks AddNewRow
254 public Func<DataGrid, object, object> NotifyNewRowAddedCallback
260 //callback executed whenever users starts editing cell
261 public Action<Control, DataGridCell, bool> NotifyBeginCellEditCallback
267 //callback executed whenver cell edit is complete
268 public Action<Control, DataGridCell> NotifyEndCellEditCallback
274 public bool ExplicitCommit
280 internal DataGrid DataGrid
282 get { return this.dataGrid; }
285 internal bool IsEditInProgress
287 get { return GetIsCommitInProgress(this.dataGrid); }
290 internal EditingContext Context
296 //callback executed whenever dynamic content cell is loaded
298 // - clicked data grid cell,
299 // - reference to data item in given dg row
300 // - boolean value indicating whether cell is beeing edited or viewewd
302 // - data template to be applied
303 public Func<ResolveTemplateParams, bool> ResolveDynamicTemplateCallback
305 get { return this.resolveDynamicTemplateCallback; }
308 this.resolveDynamicTemplateCallback = value;
309 //if user adds dynamic template, we need to hook for EditMode button event - ShowDialogEditor;
310 //otherwise, clicking on that button wouldn't have any effect, since it is normally handled by property grid
311 bool containsBinding = this.dataGrid.CommandBindings
312 .Cast<CommandBinding>()
313 .Any(cb => ICommand.Equals(cb.Command, PropertyValueEditorCommands.ShowDialogEditor));
315 if (!containsBinding)
317 var cb = new CommandBinding(PropertyValueEditorCommands.ShowDialogEditor, this.OnShowPropertyValueEditor, this.OnCanShowPropertyValueEditor);
318 this.dataGrid.CommandBindings.Add(cb);
323 //callback executed whenever user clicks extended dialog property editor in the data grid -
324 //client has to specify reference to edited model property, which will be placed in extended editor dialog
326 // - clicked data grid cell
327 // - reference to data item in given dg row
329 // - reference to model property which should be displayed
330 public Func<DataGridCell, object, ModelProperty> LoadDynamicContentDataCallback
336 //callback executed whenever user clicks extended dialog property editor in the data grid
338 // - clicked data grid cell
339 // - reference to data item in given dg row
341 // - instance of dialog property value editor
342 public Func<DataGridCell, object, DialogPropertyValueEditor> LoadCustomPropertyValueEditorCallback
348 //default row template
349 ControlTemplate DefaultRowControlTemplate
355 ContentPresenter AddNewRowContentPresenter
361 //property containing content displayed on the Add new row button
362 public object AddNewRowContent
364 get { return (object)GetValue(AddNewRowContentProperty); }
365 set { SetValue(AddNewRowContentProperty, value); }
368 //command bound to add new row button
369 public ICommand AddNewRowCommand
371 get { return (ICommand)GetValue(AddNewRowCommandProperty); }
372 set { SetValue(AddNewRowCommandProperty, value); }
375 public bool ShowValidationErrorAsToolTip
377 get { return (bool)GetValue(ShowValidationErrorAsToolTipProperty); }
378 set { SetValue(ShowValidationErrorAsToolTipProperty, value); }
381 //helper method - returns selected data grid item casted to the target type
382 public T SelectedItem<T>() where T : class
384 return this.dataGrid.SelectedItem as T;
387 public T Source<T>() where T : class
389 return (T)this.dataGrid.ItemsSource;
392 public void BeginRowEdit(object value, DataGridColumn column)
396 throw FxTrace.Exception.AsError(new ArgumentNullException("value"));
400 throw FxTrace.Exception.AsError(new ArgumentNullException("column"));
402 int columnIndex = this.dataGrid.Columns.IndexOf(column);
405 throw FxTrace.Exception.AsError(new ArgumentOutOfRangeException("column"));
407 ICollectionView items = CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource);
410 this.CommitDataGrid();
411 this.dataGrid.SelectedItem = null;
412 //lookup element in the collection
413 if (items.MoveCurrentTo(value))
415 //set the SelectedItem to passed value
416 this.dataGrid.SelectedItem = value;
417 //get the cell which contains given elemnt
418 DataGridCell cell = DataGridHelper.GetCell(this.dataGrid, items.CurrentPosition, columnIndex);
423 dataGrid.BeginEdit();
428 throw FxTrace.Exception.AsError(new ArgumentOutOfRangeException("value"));
433 public void BeginRowEdit(object value)
435 var column = this.dataGrid.Columns[0];
437 while (null != column && column.Visibility == Visibility.Hidden && this.dataGrid.Columns.Count > index)
439 column = this.dataGrid.Columns[index];
442 this.BeginRowEdit(value, column);
445 void OnDataGridRowLoading(object sender, DataGridRowEventArgs e)
447 if (this.DefaultRowControlTemplate == null)
449 this.DefaultRowControlTemplate = e.Row.Template;
452 if (e.Row.Item == CollectionView.NewItemPlaceholder)
454 e.Row.Style = (Style)this.FindResource("defaultNewRowStyle");
455 e.Row.UpdateLayout();
459 void OnAddNewRowContentPresenterLoaded(object sender, RoutedEventArgs args)
461 var presenter = (ContentPresenter)sender;
462 this.AddNewRowContentPresenter = presenter;
463 if (null != this.AddNewRowContent)
465 if (this.AddNewRowContent is DataTemplate)
467 presenter.ContentTemplate = (DataTemplate)this.AddNewRowContent;
468 presenter.ApplyTemplate();
472 presenter.ContentTemplate = (DataTemplate)this.FindResource("defaultAddNewRowTemplate");
473 presenter.ApplyTemplate();
474 presenter.Content = this.AddNewRowContent.ToString();
479 void OnAddNewRowClick(object sender, RoutedEventArgs args)
481 //user clicked on AddNew row - commit all pending changes
482 this.CommitDataGrid();
483 Button btn = (Button)sender;
484 //if there is callback registered
485 if (null != this.NotifyNewRowAddedCallback)
488 object added = this.NotifyNewRowAddedCallback(this.dataGrid, btn.CommandParameter);
489 //if add was successfull, begin editing new row
490 this.isNewRowAdded = (null != added);
491 if (this.isNewRowAdded)
493 this.BeginRowEdit(added);
496 //if there is command registered
497 else if (null != this.AddNewRowCommand)
499 //try to invoke command as routed command, the as the interface command
500 RoutedCommand cmd = this.AddNewRowCommand as RoutedCommand;
503 if (this.AddNewRowCommand.CanExecute(btn.CommandParameter))
505 this.AddNewRowCommand.Execute(btn.CommandParameter);
506 this.isNewRowAdded = true;
511 if (cmd.CanExecute(btn.CommandParameter, this.dataGrid))
513 cmd.Execute(btn.CommandParameter, this.dataGrid);
514 this.isNewRowAdded = true;
520 void OnAddNewRowGotFocus(object sender, RoutedEventArgs e)
522 //When tab over the last row, the last column won't get commit by default, which is a bug of DataGrid with
523 //customized new place holder template. Call commit explicitly here to workaround this issue
524 this.CommitDataGrid();
525 this.dataGrid.SelectedItem = null;
528 void CommitDataGrid()
530 if (!GetIsCommitInProgress(this.dataGrid))
532 SetIsCommitInProgress(this.dataGrid, true);
533 this.ExplicitCommit = true;
534 this.dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
535 this.ExplicitCommit = false;
536 SetIsCommitInProgress(this.dataGrid, false);
540 void NotifyEditingControlLoaded(Control control, DataGridCell cell, bool isNewRowLoaded)
542 Type controlType = control.GetType();
543 Type editorBehaviorType;
544 if (EditorBehaviorTypeMapping.ContainsKey(controlType))
546 editorBehaviorType = EditorBehaviorTypeMapping[controlType];
550 editorBehaviorType = typeof(DefaultControlBehavior);
553 EditingControlBehavior behavior = Activator.CreateInstance(editorBehaviorType, this.dataGrid) as EditingControlBehavior;
554 bool isHandled = behavior.HandleControlLoaded(control, cell, isNewRowLoaded);
557 SetControlBehavior(control, behavior);
560 if (null != this.NotifyBeginCellEditCallback)
562 this.NotifyBeginCellEditCallback(control, cell, isNewRowLoaded);
566 void NotifyEditingControlUnloaded(Control control, DataGridCell cell)
568 bool isHandled = false;
570 EditingControlBehavior behavior = GetControlBehavior(control);
572 if (null != behavior)
574 isHandled = behavior.ControlUnloaded(control, cell);
577 if (null != this.NotifyEndCellEditCallback)
579 this.NotifyEndCellEditCallback(control, cell);
583 void OnCanShowPropertyValueEditor(object sender, CanExecuteRoutedEventArgs args)
585 Fx.Assert(this.LoadCustomPropertyValueEditorCallback != null, "LoadCustomPropertyValueEditorCallback is not set!");
586 Fx.Assert(this.LoadDynamicContentDataCallback != null, "LoadDynamicContentDataCallback is not set!");
588 if (null != this.LoadDynamicContentDataCallback && null != this.LoadCustomPropertyValueEditorCallback)
590 var cell = VisualTreeUtils.FindVisualAncestor<DataGridCell>((DependencyObject)args.OriginalSource);
591 var row = VisualTreeUtils.FindVisualAncestor<DataGridRow>(cell);
592 args.CanExecute = null != this.LoadCustomPropertyValueEditorCallback(cell, row.Item);
596 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
597 Justification = "Propagating exceptions might lead to VS crash.")]
598 [SuppressMessage("Reliability", "Reliability108:IsFatalRule",
599 Justification = "Propagating exceptions might lead to VS crash.")]
600 void OnShowPropertyValueEditor(object sender, ExecutedRoutedEventArgs args)
602 //user clicked on dialog's property editor's button - now we need to show custom designer in dialog mode
603 var cell = VisualTreeUtils.FindVisualAncestor<DataGridCell>((DependencyObject)args.OriginalSource);
604 var row = VisualTreeUtils.FindVisualAncestor<DataGridRow>(cell);
605 //ask client for custom editor, given for currently selected row
606 var editor = this.LoadCustomPropertyValueEditorCallback(cell, row.Item);
608 Fx.Assert(editor != null, "Custom property value editor is not set or doesn't derive from DialogPropertyValueEditor!");
611 //out of currently selected row, get actual property which is beeing edited
612 var value = this.LoadDynamicContentDataCallback(cell, row.Item);
614 Fx.Assert(value != null, "ModelProperty shouldn't be null");
616 //create model property entry - it is required by dialog property editor
617 var propertyEntry = new ModelPropertyEntry(value, null);
620 editor.ShowDialog(propertyEntry.PropertyValue, (IInputElement)args.OriginalSource);
622 catch (Exception err)
624 ErrorReporting.ShowErrorMessage(err);
629 internal static void OnEditingControlLoaded(object sender, RoutedEventArgs args)
631 //editing control has been loaded - user starts editing the cell
632 Control ctrl = (Control)sender;
634 //get the data grid reference from control
635 DataGrid dg = VisualTreeUtils.FindVisualAncestor<DataGrid>(ctrl);
636 Fx.Assert(null != dg, string.Format(CultureInfo.CurrentCulture, "DataGrid is not in the visual tree of this control: {0}", ctrl));
639 //get the target instance of data grid helper
640 DataGridHelper helper = DataGridHelper.GetDGHelper(dg);
641 //store data grid helper in the control
642 DataGridHelper.SetDGHelper(ctrl, helper);
645 //notify user that given control is becoming acive one
646 DataGridCell cell = VisualTreeUtils.FindVisualAncestor<DataGridCell>(ctrl);
647 helper.NotifyEditingControlLoaded(ctrl, cell, helper.isNewRowAdded);
648 helper.isNewRowAdded = false;
653 internal static void OnEditingControlUnloaded(object sender, RoutedEventArgs args)
655 //editing control has been unloaded - user ends editing the cell
656 Control ctrl = (Control)sender;
658 //get data grid helper out of it
659 DataGridHelper helper = DataGridHelper.GetDGHelper(ctrl);
661 //notify user that edit is complete
664 DataGridCell cell = VisualTreeUtils.FindVisualAncestor<DataGridCell>(ctrl);
665 helper.NotifyEditingControlUnloaded(ctrl, cell);
669 void OnPreviewCellMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
671 //When Ctrl of Shift is pressed, let DataGrid to handle multi-selection
672 //and DataGrid shouldn't enter editing mode in this case
673 if ((Keyboard.IsKeyDown(Key.RightShift)) || (Keyboard.IsKeyDown(Key.LeftShift)) ||
674 (Keyboard.IsKeyDown(Key.LeftCtrl)) || (Keyboard.IsKeyDown(Key.RightCtrl)))
679 //support for single click edit
680 DataGridCell cell = sender as DataGridCell;
681 //enter this code only if cell is not beeing edited already and is not readonly
682 if (null != cell && !cell.IsEditing && !cell.IsReadOnly && null != this.dataGrid.SelectedItem)
684 bool shouldFocus = true;
685 //depending on the selection type - either select cell or row
686 if (this.dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
688 if (!cell.IsSelected)
690 cell.IsSelected = true;
695 DataGridRow row = VisualTreeUtils.FindVisualAncestor<DataGridRow>(cell);
696 //if row was not selected - first click will select it, second will start editing cell's value
697 if (null != row && !row.IsSelected)
699 this.dataGrid.SelectedItem = row;
704 //if allowed - begin edit
705 if ((shouldFocus && !cell.IsFocused) && !GetIsCustomEditor(cell))
707 //attempt to set focus to the cell, and let DG start editing
708 if (cell.Focus() && !cell.IsEditing)
710 if (dataGrid.SelectionUnit == DataGridSelectionUnit.FullRow)
712 dataGrid.SelectedItems.Clear();
713 DataGridRow row = VisualTreeUtils.FindVisualAncestor<DataGridRow>(cell);
716 dataGrid.SelectedItems.Add(dataGrid.ItemContainerGenerator.ItemFromContainer(row));
721 dataGrid.SelectedCells.Clear();
722 dataGrid.SelectedCells.Add(new DataGridCellInfo(cell));
724 this.dataGrid.BeginEdit();
730 void OnDataGridMouseDown(object sender, RoutedEventArgs e)
732 ICollectionView view = CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource);
733 if (null != this.dataGrid.SelectedItem && this.dataGrid.CurrentCell.IsValid && view.MoveCurrentTo(this.dataGrid.SelectedItem))
735 int rowIndex = view.CurrentPosition;
736 int columnIndex = this.dataGrid.Columns.IndexOf(this.dataGrid.CurrentCell.Column);
737 var cell = DataGridHelper.GetCell(this.dataGrid, rowIndex, columnIndex);
738 if (null != cell && cell.IsEditing)
740 this.CommitDataGrid();
746 this.dataGrid.Focus();
751 void OnDynamicContentColumnLoaded(DataGridCell cell, ContentControl contentContainer)
753 //user marked at least one column in data grid with DynamicContent - now, we have to query client for
754 //cell template for given row's property
755 if (null != this.ResolveDynamicTemplateCallback)
757 var resolveParams = new ResolveTemplateParams(cell, contentContainer.Content);
758 if (this.ResolveDynamicTemplateCallback(resolveParams) && null != resolveParams.Template)
760 if (!resolveParams.IsDefaultTemplate)
762 var content = this.LoadDynamicContentDataCallback(cell, contentContainer.Content);
763 var propertyEntry = new ModelPropertyEntry(content, null);
764 contentContainer.Content = propertyEntry.PropertyValue;
765 SetIsCustomEditor(cell, true);
769 contentContainer.Content = cell.DataContext;
770 SetIsCustomEditor(cell, false);
772 contentContainer.ContentTemplate = resolveParams.Template;
777 System.Diagnostics.Debug.WriteLine("ResolveDynamicTemplateCallback not registered for column " + cell.Column.Header);
781 public void UpdateDynamicContentColumns(object entry)
783 ICollectionView view = CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource);
785 //get index of given entry
786 if (view.MoveCurrentTo(entry))
788 rowIndex = view.CurrentPosition;
792 //pickup all dynamic columns in this data grid
793 var dynamicColumnsIndexes = this.dataGrid.Columns
794 .OfType<DataGridTemplateColumn>()
795 .Where(p => DataTemplate.Equals(p.CellEditingTemplate, DataGridHelper.DynamicCellContentTemplate) &&
796 DataTemplate.Equals(p.CellTemplate, DataGridHelper.DynamicCellContentTemplate))
797 .Select<DataGridColumn, int>(p => this.dataGrid.Columns.IndexOf(p));
799 //foreach dynamic column
800 foreach (var columnIndex in dynamicColumnsIndexes)
803 var cell = DataGridHelper.GetCell(this.dataGrid, rowIndex, columnIndex);
805 //get the content presenter within it
806 var dynamicContent = VisualTreeUtils.GetNamedChild<ContentControl>(cell, DataGridHelper.dynamicContentControlName, 5);
808 //reload the template
809 if (null != dynamicContent)
811 dynamicContent.ContentTemplate = null;
812 this.OnDynamicContentColumnLoaded(cell, dynamicContent);
819 void ApplyCellStyle()
821 //create default cell style
822 Style baseStyle = this.dataGrid.CellStyle;
823 //respect any user's base styles
824 Style style = null == baseStyle ? new Style(typeof(DataGridCell)) : new Style(typeof(DataGridCell), baseStyle);
826 //event handler for preview mouse down - single click edit
827 style.Setters.Add(new EventSetter(DataGridCell.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(this.OnPreviewCellMouseLeftButtonDown)));
828 //width binding - prevent columns from expanding while typing long texts
829 style.Setters.Add(new Setter(DataGridCell.WidthProperty, new Binding("Column.ActualWidth")
831 RelativeSource = new RelativeSource(RelativeSourceMode.Self),
832 Mode = BindingMode.OneWay
834 //automation id - for cell it is always column name
835 style.Setters.Add(new Setter(AutomationProperties.AutomationIdProperty, new Binding("Column.Header")
837 RelativeSource = new RelativeSource(RelativeSourceMode.Self),
838 Mode = BindingMode.OneWay
842 this.dataGrid.CellStyle = style;
847 //create default row style
848 Style baseStyle = this.dataGrid.RowStyle;
849 //respect any user's base styles
850 Style style = null == baseStyle ? new Style(typeof(DataGridRow)) : new Style(typeof(DataGridRow), baseStyle);
852 EventSetter keyDownSetter = new EventSetter
854 Event = DataGridRow.KeyDownEvent,
855 Handler = new KeyEventHandler(this.OnDataGridRowKeyDown)
857 style.Setters.Add(keyDownSetter);
859 //define a multibinding which displays a tooltip when cell validation fails (failure mean user's data was invalid and was not set in the target property)
860 //first - create a binding and add ErrorToTooltipConverter, pass reference to owning data grid helper
861 var multiBinding = new MultiBinding() { Converter = new ErrorToTooltipConverter(this) };
862 //now define bindings
863 //first - bind to actual object behind the row - only DesignObjectWrapper is supported
864 var objectWrapperBinding = new Binding() { Mode = BindingMode.OneTime };
865 //second - bind to a HasError property change notifications - this will trigger tooltip to appear
866 var hasErrorsBinding = new Binding() { Mode = BindingMode.OneWay, Path = new PropertyPath("HasErrors") };
867 //finally - bind to a row which contains the data - this will be used as tooltip placement target
868 var rowBinding = new Binding() { Mode = BindingMode.OneTime, RelativeSource = new RelativeSource(RelativeSourceMode.Self) };
869 multiBinding.Bindings.Add(objectWrapperBinding);
870 multiBinding.Bindings.Add(hasErrorsBinding);
871 multiBinding.Bindings.Add(rowBinding);
873 var errorTooltipTrigger = new DataTrigger()
875 Binding = multiBinding,
878 //define a dummy setter - it will never be executed anyway, but it is required for the binding to work
879 errorTooltipTrigger.Setters.Add(new Setter(DataGridRow.TagProperty, null));
880 //add trigger to the collection
881 style.Triggers.Add(errorTooltipTrigger);
883 this.dataGrid.RowStyle = style;
886 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
887 Justification = "Propagating exceptions might lead to VS crash.")]
888 [SuppressMessage("Reliability", "Reliability108:IsFatalRule",
889 Justification = "Propagating exceptions might lead to VS crash.")]
890 static void OnDynamicCellContentLoaded(object sender, RoutedEventArgs e)
892 var container = (ContentControl)sender;
893 var dataGridCell = VisualTreeUtils.FindVisualAncestor<DataGridCell>(container);
894 var dataGrid = VisualTreeUtils.FindVisualAncestor<DataGrid>(dataGridCell);
895 if (dataGrid != null)
897 var dataGridHelper = DataGridHelper.GetDGHelper(dataGrid);
899 if (GetIsCustomEditor(dataGridCell) && (dataGridCell.IsEditing))
901 dataGridHelper.CommitDataGrid();
907 dataGridHelper.OnDynamicContentColumnLoaded(dataGridCell, container);
909 catch (Exception err)
911 container.Content = err.ToString();
912 container.ContentTemplate = (DataTemplate)dataGrid.Resources["dynamicContentErrorTemplate"];
913 System.Diagnostics.Debug.WriteLine(err.ToString());
919 public static DataTemplate DynamicCellContentTemplate
923 if (null == dynamicCellContentTemplate)
925 DataTemplate template = new DataTemplate();
927 FrameworkElementFactory contentControlFactory = new FrameworkElementFactory(typeof(ContentControl));
928 contentControlFactory.SetValue(ContentControl.NameProperty, DataGridHelper.dynamicContentControlName);
929 contentControlFactory.SetBinding(ContentControl.ContentProperty, new Binding());
930 contentControlFactory.AddHandler(ContentControl.LoadedEvent, new RoutedEventHandler(DataGridHelper.OnDynamicCellContentLoaded));
932 template.VisualTree = new FrameworkElementFactory(typeof(NoContextMenuGrid));
933 template.VisualTree.AppendChild(contentControlFactory);
935 dynamicCellContentTemplate = template;
937 return dynamicCellContentTemplate;
941 static DataGridHelper GetDGHelper(DependencyObject obj)
943 return (DataGridHelper)obj.GetValue(DGHelperProperty);
946 static void SetDGHelper(DependencyObject obj, DataGridHelper value)
948 obj.SetValue(DGHelperProperty, value);
951 static EditingControlBehavior GetControlBehavior(DependencyObject obj)
953 return (EditingControlBehavior)obj.GetValue(ControlBehaviorProperty);
956 static void SetControlBehavior(DependencyObject obj, EditingControlBehavior value)
958 obj.SetValue(ControlBehaviorProperty, value);
961 static bool GetNewRowLoaded(DependencyObject obj)
963 return (bool)obj.GetValue(NewRowLoadedProperty);
966 static void SetNewRowLoaded(DependencyObject obj, bool value)
968 obj.SetValue(NewRowLoadedProperty, value);
971 static bool GetIsCommitInProgress(DependencyObject obj)
973 return (bool)obj.GetValue(IsCommitInProgressProperty);
976 static void SetIsCommitInProgress(DependencyObject obj, bool value)
978 obj.SetValue(IsCommitInProgressProperty, value);
981 public static DataGridCell GetCell(DataGrid dataGrid, int row, int column)
983 DataGridRow rowContainer = GetRow(dataGrid, row);
984 if (rowContainer != null)
986 DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
987 if (presenter != null)
989 // try to get the cell but it may possibly be virtualized
990 DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
993 // now try to bring into view and retreive the cell
994 dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
996 cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
1006 internal static ModelItem GetSingleSelectedObject(DataGrid dataGrid)
1008 if (dataGrid == null || dataGrid.SelectedItems == null || dataGrid.SelectedItems.Count != 1)
1013 if (dataGrid.SelectedItems[0] == CollectionView.NewItemPlaceholder)
1018 DesignObjectWrapper designObjectWrapper = dataGrid.SelectedItems[0] as DesignObjectWrapper;
1019 if (designObjectWrapper != null)
1021 return designObjectWrapper.ReflectedObject;
1028 /// Gets the DataGridRow based on the given index
1030 /// <param name="index">the index of the container to get</param>
1031 public static DataGridRow GetRow(DataGrid dataGrid, int index)
1033 DataGridRow row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index);
1036 // may be virtualized, bring into view and try again
1037 dataGrid.ScrollIntoView(dataGrid.Items[index]);
1038 dataGrid.UpdateLayout();
1040 row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index);
1046 public static T GetVisualChild<T>(DependencyObject parent) where T : Visual
1048 T child = default(T);
1050 int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
1051 for (int i = 0; i < numVisuals; i++)
1053 Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
1057 child = GetVisualChild<T>(v);
1068 public static void CommitPendingEdits(DataGrid dataGrid)
1070 if (null == dataGrid)
1072 throw FxTrace.Exception.AsError(new ArgumentNullException("dataGrid"));
1075 if (!GetIsCommitInProgress(dataGrid))
1077 SetIsCommitInProgress(dataGrid, true);
1079 //try to commit edit
1080 bool commitSucceeded = false;
1081 DataGridHelper helper = DataGridHelper.GetDGHelper(dataGrid) as DataGridHelper;
1082 bool orginalExplicitCommit = helper.ExplicitCommit;
1083 helper.ExplicitCommit = true;
1086 commitSucceeded = dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
1088 catch (InvalidOperationException)
1090 // Ignore and cancel edit
1094 //if commit fails - undo change
1095 if (!commitSucceeded)
1097 dataGrid.CancelEdit();
1099 helper.ExplicitCommit = orginalExplicitCommit;
1102 SetIsCommitInProgress(dataGrid, false);
1106 public static void OnDeleteSelectedItems(DataGrid dataGrid)
1108 if (dataGrid == null
1109 || dataGrid.SelectedItems == null
1110 || dataGrid.SelectedItems.Count == 0
1111 || dataGrid.ItemsSource == null)
1116 if (!(dataGrid.ItemsSource is IList))
1118 // if the item source is not a list
1119 // we can't delete items from it.
1123 Int32 nextSelectedIndex = -1;
1124 ICollection<object> toBeDeleted = new HashSet<object>();
1125 foreach (object obj in dataGrid.SelectedItems)
1127 toBeDeleted.Add(obj);
1130 if (toBeDeleted.Count == 0)
1135 if (toBeDeleted.Count == 1)
1137 // if it is single selection,
1138 // set selected index to be the current selected index.
1139 // Set nextSelectedIndex only in single selection to keep the behavior
1140 // consistent with the "delete" button on key board, where if you
1141 // select more than one items and push the "Delete" button on keyboard,
1142 // nothing will be selected.
1143 nextSelectedIndex = dataGrid.Items.IndexOf(toBeDeleted.ElementAt(0));
1146 IList itemSource = (IList)dataGrid.ItemsSource;
1147 foreach (object obj in toBeDeleted)
1149 itemSource.Remove(obj);
1152 if (nextSelectedIndex >= 0
1153 && nextSelectedIndex < dataGrid.Items.Count - 1)
1155 // The last row, whose index is (dataGrid.Items.Count - 1), is
1156 // the "add new item" row.
1157 dataGrid.SelectedIndex = nextSelectedIndex;
1161 internal abstract class EditingControlBehavior
1163 protected DesignerView DesignerView
1169 protected DataGrid OwnerDataGrid
1175 public EditingControlBehavior(DataGrid dataGrid)
1177 this.OwnerDataGrid = dataGrid;
1178 var helper = DataGridHelper.GetDGHelper(dataGrid);
1179 if (null != helper && null != helper.Context)
1181 this.DesignerView = helper.Context.Services.GetService<DesignerView>();
1185 public abstract bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded);
1186 public abstract bool ControlUnloaded(Control control, DataGridCell cell);
1188 protected void ToggleDesignerViewAutoCommit(bool shouldIgnore)
1190 if (null != this.DesignerView)
1192 //enable/disable handling of lost keyboard focus events in designer view -
1193 //if shouldIgnore is true, designer view should ignore keyboard focus events thus, not forcing DataGrid to
1194 //commit any changes
1195 this.DesignerView.ShouldIgnoreDataGridAutoCommit = shouldIgnore;
1200 internal sealed class DefaultControlBehavior : EditingControlBehavior
1202 public DefaultControlBehavior(DataGrid dataGrid)
1206 public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
1208 System.Diagnostics.Debug.WriteLine("DefaultControlBehavior.HandleControlLoaded");
1213 public override bool ControlUnloaded(Control control, DataGridCell cell)
1215 System.Diagnostics.Debug.WriteLine("DefaultControlBehavior.ControlUnloaded");
1220 internal sealed class TextBoxBehavior : EditingControlBehavior
1222 public TextBoxBehavior(DataGrid dataGrid)
1226 public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
1228 System.Diagnostics.Debug.WriteLine("TextBoxBehavior.HandleControlLoaded");
1229 bool handled = false;
1230 TextBox tb = control as TextBox;
1239 tb.CaretIndex = tb.Text.Length;
1247 public override bool ControlUnloaded(Control control, DataGridCell cell)
1249 System.Diagnostics.Debug.WriteLine("TextBoxBehavior.ControlUnloaded");
1254 internal sealed class VBIdentifierDesignerBehavior : EditingControlBehavior
1256 public VBIdentifierDesignerBehavior(DataGrid dataGrid)
1260 public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
1262 System.Diagnostics.Debug.WriteLine("VBIdentifierDesignerBehavior.HandleControlLoaded");
1263 bool handled = false;
1264 VBIdentifierDesigner identifierDesigner = control as VBIdentifierDesigner;
1265 if ((null != identifierDesigner) && (!identifierDesigner.IsReadOnly))
1269 DataGridHelper.SetNewRowLoaded(identifierDesigner, true);
1273 DataGridHelper.SetNewRowLoaded(identifierDesigner, false);
1276 identifierDesigner.TextBoxPropertyChanged += this.OnIdentifierDesignerTextBoxChanged;
1277 identifierDesigner.Focus();
1283 void OnIdentifierDesignerTextBoxChanged(object sender, PropertyChangedEventArgs e)
1285 Fx.Assert(e.PropertyName == "IdentifierTextBox", "VBIdentifierDesignerBehavior.TextBoxPropertyChanged event should only be raised when IdentifierTextBox property is changed.");
1286 VBIdentifierDesigner identifierDesigner = sender as VBIdentifierDesigner;
1287 TextBox textBox = identifierDesigner.IdentifierTextBox;
1288 if (textBox != null)
1290 if (DataGridHelper.GetNewRowLoaded(identifierDesigner))
1292 textBox.SelectAll();
1293 DataGridHelper.SetNewRowLoaded(identifierDesigner, false);
1297 textBox.CaretIndex = textBox.Text.Length;
1303 public override bool ControlUnloaded(Control control, DataGridCell cell)
1305 System.Diagnostics.Debug.WriteLine("VBIdentifierDesignerBehavior.ControlUnloaded");
1306 VBIdentifierDesigner identifierDesigner = control as VBIdentifierDesigner;
1307 if (identifierDesigner != null)
1309 identifierDesigner.TextBoxPropertyChanged -= this.OnIdentifierDesignerTextBoxChanged;
1315 internal sealed class TypePresenterBehavior : EditingControlBehavior
1318 TypePresenter typePresenter;
1319 bool isTypeBrowserOpen = false;
1320 bool isRegisteredForEvents = false;
1322 public TypePresenterBehavior(DataGrid dataGrid)
1325 DataGridHelper helper = DataGridHelper.GetDGHelper(dataGrid) as DataGridHelper;
1326 helper.DataGridCellEditEnding += OnCellEditEnding;
1329 public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
1331 System.Diagnostics.Debug.WriteLine("TypePresenterBehavior.HandleControlLoaded");
1332 bool handled = false;
1334 this.typePresenter = control as TypePresenter;
1335 if (null != this.typePresenter)
1337 this.isRegisteredForEvents = true;
1338 this.typePresenter.TypeBrowserOpened += OnTypeBrowserOpened;
1339 this.typePresenter.TypeBrowserClosed += OnTypeBrowserClosed;
1341 handled = this.typePresenter.typeComboBox.Focus();
1346 public override bool ControlUnloaded(Control control, DataGridCell cell)
1348 System.Diagnostics.Debug.WriteLine("TypePresenterBehavior.ControlUnloaded");
1350 if (this.isRegisteredForEvents && null != this.typePresenter)
1352 this.typePresenter.TypeBrowserOpened -= OnTypeBrowserOpened;
1353 this.typePresenter.TypeBrowserClosed -= OnTypeBrowserClosed;
1354 this.typePresenter = null;
1355 this.isRegisteredForEvents = false;
1356 DataGridHelper helper = DataGridHelper.GetDGHelper(this.OwnerDataGrid) as DataGridHelper;
1357 helper.DataGridCellEditEnding -= this.OnCellEditEnding;
1362 void OnCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
1364 if (null != this.cell)
1366 e.Cancel = this.isTypeBrowserOpen;
1370 void OnTypeBrowserOpened(object sender, RoutedEventArgs e)
1372 base.ToggleDesignerViewAutoCommit(true);
1373 this.isTypeBrowserOpen = true;
1376 void OnTypeBrowserClosed(object sender, RoutedEventArgs e)
1378 base.ToggleDesignerViewAutoCommit(false);
1379 this.isTypeBrowserOpen = false;
1383 internal sealed class ExpressionTextBoxBehavior : EditingControlBehavior
1385 bool isExpressionEditInProgress;
1388 public ExpressionTextBoxBehavior(DataGrid dataGrid)
1391 DataGridHelper helper = DataGridHelper.GetDGHelper(dataGrid) as DataGridHelper;
1392 helper.DataGridCellEditEnding += OnCellEditEnding;
1395 public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
1397 System.Diagnostics.Debug.WriteLine("ExpressionTextBoxBehavior.HandleControlLoaded");
1398 bool handled = false;
1400 ExpressionTextBox etb = control as ExpressionTextBox;
1404 //register for logical lost focus events
1405 etb.EditorLostLogicalFocus += OnExpressionEditComplete;
1407 if (!etb.IsReadOnly)
1409 //start editing expression
1411 //mark expression edit is in progress, so all CellEditEnding calls will be ignored by datagrid
1412 this.isExpressionEditInProgress = true;
1413 //disable forced keyboard focus lost events - intelisense window will trigger lost keyboard event,
1414 //which eventualy will lead to commit edit
1415 base.ToggleDesignerViewAutoCommit(true);
1422 public override bool ControlUnloaded(Control control, DataGridCell cell)
1424 System.Diagnostics.Debug.WriteLine("ExpressionTextBoxBehavior.ControlUnloaded");
1425 ExpressionTextBox etb = control as ExpressionTextBox;
1428 //control is unloaded - unregister from the event
1429 etb.EditorLostLogicalFocus -= OnExpressionEditComplete;
1430 //if it happens that complete row is beeing unloaded, it is possible that expression edit was still in progress
1431 if (this.isExpressionEditInProgress)
1433 //force expression update before unload is complete
1434 this.OnExpressionEditComplete(etb, null);
1437 DataGridHelper helper = DataGridHelper.GetDGHelper(this.OwnerDataGrid) as DataGridHelper;
1438 helper.DataGridCellEditEnding -= OnCellEditEnding;
1443 void OnExpressionEditComplete(object sender, RoutedEventArgs e)
1445 if (this.isExpressionEditInProgress)
1447 ExpressionTextBox etb = (ExpressionTextBox)sender;
1448 //commit the expression value
1449 ((RoutedCommand)DesignerView.CommitCommand).Execute(null, etb);
1450 //allow data grid to consume cell editing events
1451 this.isExpressionEditInProgress = false;
1452 this.OwnerDataGrid.CommitEdit();
1453 //restore keyboard focus handling for designer view
1454 base.ToggleDesignerViewAutoCommit(false);
1458 void OnCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
1460 DataGridHelper helper = DataGridHelper.GetDGHelper(this.OwnerDataGrid) as DataGridHelper;
1461 if (this.isExpressionEditInProgress && (helper != null) && !helper.ExplicitCommit)
1465 else if (this.isExpressionEditInProgress)
1467 ExpressionTextBox etb = VisualTreeUtils.GetTemplateChild<ExpressionTextBox>(e.EditingElement);
1468 this.OnExpressionEditComplete(etb, null);
1474 sealed class ResolveTemplateParams
1476 internal ResolveTemplateParams(DataGridCell cell, object instance)
1479 this.Instance = instance;
1480 this.IsDefaultTemplate = true;
1483 public DataGridCell Cell { get; private set; }
1484 public object Instance { get; private set; }
1485 public bool IsDefaultTemplate { get; set; }
1486 public DataTemplate Template { get; set; }
1489 sealed class ErrorToTooltipConverter : IMultiValueConverter
1491 DataGridHelper owner;
1492 DataTemplate toolTipTemplate;
1494 public ErrorToTooltipConverter(DataGridHelper owner)
1497 this.toolTipTemplate = (DataTemplate)this.owner.FindResource("errorToolTipTemplate");
1500 public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
1502 var entry = values.OfType<DesignObjectWrapper>().FirstOrDefault();
1503 var row = values.OfType<DataGridRow>().FirstOrDefault();
1504 if (this.owner.IsEditInProgress && null != entry && entry.HasErrors && null != row)
1506 var invalidProperties = new List<string>();
1507 if (this.owner.ShowValidationErrorAsToolTip)
1509 var errorTip = new ToolTip()
1511 PlacementTarget = row,
1512 Placement = PlacementMode.Bottom,
1513 ContentTemplate = this.toolTipTemplate,
1514 Content = entry.GetValidationErrors(invalidProperties),
1515 Effect = new DropShadowEffect() { ShadowDepth = 1 },
1517 AutomationProperties.SetAutomationId(errorTip, "errorToolTip");
1519 row.Dispatcher.BeginInvoke(new Action<ToolTip, DataGridRow>((tip, r) =>
1522 var dt = new DispatcherTimer(TimeSpan.FromSeconds(6), DispatcherPriority.ApplicationIdle, (sender, e) => { tip.IsOpen = false; }, r.Dispatcher);
1523 }), DispatcherPriority.ApplicationIdle, errorTip, row);
1527 row.Dispatcher.BeginInvoke(new Action<string>((error) =>
1529 //get currently focused element
1530 var currentFocus = (UIElement)Keyboard.FocusedElement;
1531 if (null != currentFocus)
1533 //if focus was within datagrid's cell, after loosing focus most likely the editing control would be gone, so try to preserve
1534 //reference to the cell itself
1535 currentFocus = VisualTreeUtils.FindVisualAncestor<DataGridCell>(currentFocus) ?? currentFocus;
1537 //show error message (this will result in KeyboardFocus changed
1538 ErrorReporting.ShowErrorMessage(error);
1539 //restore keyboard focus to stored element, but only if it is somewhere within DesignerView (i don't want to mess with focus in other windows)
1540 if (null != currentFocus && null != VisualTreeUtils.FindVisualAncestor<DesignerView>(currentFocus))
1542 Keyboard.Focus(currentFocus);
1544 }), DispatcherPriority.ApplicationIdle, entry.GetValidationErrors(invalidProperties));
1546 //clear the validation error messages - once the error is raised and displayed, i don't need it anymore in the collection
1547 entry.ClearValidationErrors(invalidProperties);
1549 //in case of property grid edit, the errors would be displayed by model item infrastructure,
1550 //so just delegate the call to clear errors collection
1551 if (!this.owner.IsEditInProgress && null != entry && entry.HasErrors)
1553 Dispatcher.CurrentDispatcher.BeginInvoke(new Action<DesignObjectWrapper>((instance) =>
1555 instance.ClearValidationErrors();
1556 }), DispatcherPriority.ApplicationIdle, entry);
1558 return Binding.DoNothing;
1561 public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
1563 throw FxTrace.Exception.AsError(new NotSupportedException());