|
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System.Activities.Presentation.View
{
using System;
using System.Activities.Presentation.Internal.PropertyEditing;
using System.Activities.Presentation.Internal.PropertyEditing.Model;
using System.Activities.Presentation.Model;
using System.Activities.Presentation.PropertyEditing;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Runtime;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Media.Effects;
[SuppressMessage(FxCop.Category.Xaml, FxCop.Rule.TypesShouldHavePublicParameterlessConstructors,
Justification = "This class is never supposed to be created in xaml directly.")]
sealed partial class DataGridHelper
{
public static readonly string PART_ButtonAdd = "PART_ButtonAdd";
static readonly string dynamicContentControlName = "PART_Dynamic";
//content of the Add new row button
public static readonly DependencyProperty AddNewRowContentProperty =
DependencyProperty.Register("AddNewRowContent", typeof(object), typeof(DataGridHelper), new UIPropertyMetadata("<Add new row>"));
//binding to the command, which gets executed when new row button is clicked
public static readonly DependencyProperty AddNewRowCommandProperty =
DependencyProperty.Register("AddNewRowCommand", typeof(ICommand), typeof(DataGridHelper), new UIPropertyMetadata(null));
//attached property - used to store reference to data grid helper within data grid instance
static readonly DependencyProperty DGHelperProperty =
DependencyProperty.RegisterAttached("DGHelper", typeof(DataGridHelper), typeof(DataGrid), new UIPropertyMetadata(null));
static readonly DependencyProperty ControlBehaviorProperty =
DependencyProperty.RegisterAttached("ControlBehavior", typeof(EditingControlBehavior), typeof(DataGridHelper), new UIPropertyMetadata(null));
static readonly DependencyProperty NewRowLoadedProperty =
DependencyProperty.RegisterAttached("NewRowLoaded", typeof(bool), typeof(DataGridHelper), new UIPropertyMetadata(false));
static readonly DependencyProperty IsCommitInProgressProperty =
DependencyProperty.RegisterAttached("IsCommitInProgress", typeof(bool), typeof(DataGridHelper), new UIPropertyMetadata(false));
public static readonly DependencyProperty ShowValidationErrorAsToolTipProperty =
DependencyProperty.Register("ShowValidationErrorAsToolTip", typeof(bool), typeof(DataGridHelper), new UIPropertyMetadata(false));
public static readonly DependencyProperty IsCustomEditorProperty =
DependencyProperty.RegisterAttached("IsCustomEditor", typeof(bool), typeof(DataGridHelper), new UIPropertyMetadata(false));
public event EventHandler<DataGridCellEditEndingEventArgs> DataGridCellEditEnding;
static DataTemplate dynamicCellContentTemplate;
static Dictionary<Type, Type> EditorBehaviorTypeMapping = new Dictionary<Type, Type>
{
{ typeof(ExpressionTextBox), typeof(ExpressionTextBoxBehavior) },
{ typeof(TextBox), typeof(TextBoxBehavior) },
{ typeof(TypePresenter), typeof(TypePresenterBehavior) },
{ typeof(VBIdentifierDesigner), typeof(VBIdentifierDesignerBehavior) },
};
DataGrid dataGrid;
bool isNewRowAdded;
Func<ResolveTemplateParams, bool> resolveDynamicTemplateCallback;
Dictionary<string, DataGridColumn> MemberPathToColumnDict = new Dictionary<string, DataGridColumn>();
[SuppressMessage(FxCop.Category.Usage, FxCop.Rule.ReviewUnusedParameters, Justification = "Existing code")]
public DataGridHelper(DataGrid instance, Control owner)
{
this.InitializeComponent();
this.dataGrid = instance;
//apply default cell style
this.ApplyCellStyle();
//apply default row style
this.ApplyRowStyle();
//apply default datagrid style
this.dataGrid.Style = (Style)this.FindResource("defaultDataGridStyle");
//handle data grid's loading event
this.dataGrid.LoadingRow += OnDataGridRowLoading;
//store reference to data grid helper within datagrid
DataGridHelper.SetDGHelper(this.dataGrid, this);
this.dataGrid.MouseDown += OnDataGridMouseDown;
this.dataGrid.Sorting += OnDataGridSorting;
this.dataGrid.CellEditEnding += OnDataGridCellEditEnding;
this.InitMemberPathToColumnDict();
}
public static bool GetIsCustomEditor(DependencyObject obj)
{
return (bool)obj.GetValue(IsCustomEditorProperty);
}
public static void SetIsCustomEditor(DependencyObject obj, bool value)
{
obj.SetValue(IsCustomEditorProperty, value);
}
void OnDataGridCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
if (this.DataGridCellEditEnding != null)
{
this.DataGridCellEditEnding(sender, e);
}
if ((!e.Cancel) && (!GetIsCommitInProgress(this.dataGrid)))
{
SetIsCommitInProgress(this.dataGrid, true);
//try to commit edit
bool commitSucceeded = false;
try
{
commitSucceeded = this.dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
}
catch (InvalidOperationException)
{
// Ignore and cancel edit
}
finally
{
//if commit fails - undo change
if (!commitSucceeded)
{
this.dataGrid.CancelEdit();
}
}
SetIsCommitInProgress(this.dataGrid, false);
}
}
void InitMemberPathToColumnDict()
{
MemberPathToColumnDict.Clear();
foreach (DataGridColumn column in this.dataGrid.Columns)
{
if (column.CanUserSort &&
!string.IsNullOrEmpty(column.SortMemberPath) &&
!MemberPathToColumnDict.ContainsKey(column.SortMemberPath))
{
MemberPathToColumnDict.Add(column.SortMemberPath, column);
}
}
}
void OnDataGridSorting(object sender, DataGridSortingEventArgs e)
{
bool primaryColumnSorted = false;
ListSortDirection direction = (e.Column.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending;
e.Column.SortDirection = null;
foreach (SortDescription description in this.dataGrid.Items.SortDescriptions.Reverse())
{
if (MemberPathToColumnDict[description.PropertyName].SortDirection == null)
{
this.dataGrid.Items.SortDescriptions.Remove(description);
}
else if (description.PropertyName == this.dataGrid.Columns[0].SortMemberPath)
{
primaryColumnSorted = true;
}
}
this.dataGrid.Items.SortDescriptions.Add(new SortDescription(e.Column.SortMemberPath, direction));
e.Column.SortDirection = direction;
if (e.Column != this.dataGrid.Columns[0] && !primaryColumnSorted)
{
this.dataGrid.Items.SortDescriptions.Add(new SortDescription(this.dataGrid.Columns[0].SortMemberPath, ListSortDirection.Ascending));
}
this.dataGrid.Items.Refresh();
e.Handled = true;
}
//Hook KeyDown event on DataGrid row to workaround DataGrid bug with customized NewItemPlaceHolder
void OnDataGridRowKeyDown(object sender, KeyEventArgs e)
{
if (e.Handled)
{
return;
}
if (e.Key == Key.Enter || e.Key == Key.Escape)
{
// If currentCell is the cell containing AddNewRowTemplate, its Column will be null since this cell
// spread across all columns in the grid. In this case, consume the event with no action.
if (dataGrid.CurrentCell.Column != null)
{
DataGridCellInfo currentCell = dataGrid.CurrentCell;
ObservableCollection<DataGridColumn> columns = dataGrid.Columns;
ItemCollection items = dataGrid.Items;
int currentColumnIndex = columns.IndexOf(dataGrid.ColumnFromDisplayIndex(currentCell.Column.DisplayIndex));
DataGridCell currentCellContainer = GetCell(dataGrid, items.IndexOf(currentCell.Item), currentColumnIndex);
if ((currentCellContainer != null) && (columns.Count > 0))
{
int numItems = items.Count;
bool shiftModifier = ((e.KeyboardDevice.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift);
int index = Math.Max(0, Math.Min(numItems - 1, items.IndexOf(currentCell.Item) + (shiftModifier ? -1 : 1)));
if (index < numItems)
{
if (items[index] == CollectionView.NewItemPlaceholder)
{
CommitAnyEdit(currentCellContainer);
e.Handled = true;
}
}
}
else
{
e.Handled = true;
}
}
else
{
e.Handled = true;
}
}
}
void CommitAnyEdit(DataGridCell currentCellContainer)
{
IEditableCollectionView editableItems = (IEditableCollectionView)(this.dataGrid.Items);
DataGridCell cell = currentCellContainer;
bool isCurrentCellEditing = false;
this.ExplicitCommit = true;
if (cell != null)
{
isCurrentCellEditing = cell.IsEditing;
}
if (editableItems.IsAddingNew || editableItems.IsEditingItem)
{
this.dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
}
else if (isCurrentCellEditing)
{
this.dataGrid.CommitEdit(DataGridEditingUnit.Cell, true);
}
this.ExplicitCommit = false;
}
//callback executed whenever user clicks AddNewRow
public Func<DataGrid, object, object> NotifyNewRowAddedCallback
{
get;
set;
}
//callback executed whenever users starts editing cell
public Action<Control, DataGridCell, bool> NotifyBeginCellEditCallback
{
get;
set;
}
//callback executed whenver cell edit is complete
public Action<Control, DataGridCell> NotifyEndCellEditCallback
{
get;
set;
}
public bool ExplicitCommit
{
get;
private set;
}
internal DataGrid DataGrid
{
get { return this.dataGrid; }
}
internal bool IsEditInProgress
{
get { return GetIsCommitInProgress(this.dataGrid); }
}
internal EditingContext Context
{
get;
set;
}
//callback executed whenever dynamic content cell is loaded
//parameters:
// - clicked data grid cell,
// - reference to data item in given dg row
// - boolean value indicating whether cell is beeing edited or viewewd
//returns:
// - data template to be applied
public Func<ResolveTemplateParams, bool> ResolveDynamicTemplateCallback
{
get { return this.resolveDynamicTemplateCallback; }
set
{
this.resolveDynamicTemplateCallback = value;
//if user adds dynamic template, we need to hook for EditMode button event - ShowDialogEditor;
//otherwise, clicking on that button wouldn't have any effect, since it is normally handled by property grid
bool containsBinding = this.dataGrid.CommandBindings
.Cast<CommandBinding>()
.Any(cb => ICommand.Equals(cb.Command, PropertyValueEditorCommands.ShowDialogEditor));
if (!containsBinding)
{
var cb = new CommandBinding(PropertyValueEditorCommands.ShowDialogEditor, this.OnShowPropertyValueEditor, this.OnCanShowPropertyValueEditor);
this.dataGrid.CommandBindings.Add(cb);
}
}
}
//callback executed whenever user clicks extended dialog property editor in the data grid -
//client has to specify reference to edited model property, which will be placed in extended editor dialog
//parameters:
// - clicked data grid cell
// - reference to data item in given dg row
//returns:
// - reference to model property which should be displayed
public Func<DataGridCell, object, ModelProperty> LoadDynamicContentDataCallback
{
get;
set;
}
//callback executed whenever user clicks extended dialog property editor in the data grid
//parameters:
// - clicked data grid cell
// - reference to data item in given dg row
//returns:
// - instance of dialog property value editor
public Func<DataGridCell, object, DialogPropertyValueEditor> LoadCustomPropertyValueEditorCallback
{
get;
set;
}
//default row template
ControlTemplate DefaultRowControlTemplate
{
get;
set;
}
ContentPresenter AddNewRowContentPresenter
{
get;
set;
}
//property containing content displayed on the Add new row button
public object AddNewRowContent
{
get { return (object)GetValue(AddNewRowContentProperty); }
set { SetValue(AddNewRowContentProperty, value); }
}
//command bound to add new row button
public ICommand AddNewRowCommand
{
get { return (ICommand)GetValue(AddNewRowCommandProperty); }
set { SetValue(AddNewRowCommandProperty, value); }
}
public bool ShowValidationErrorAsToolTip
{
get { return (bool)GetValue(ShowValidationErrorAsToolTipProperty); }
set { SetValue(ShowValidationErrorAsToolTipProperty, value); }
}
//helper method - returns selected data grid item casted to the target type
public T SelectedItem<T>() where T : class
{
return this.dataGrid.SelectedItem as T;
}
public T Source<T>() where T : class
{
return (T)this.dataGrid.ItemsSource;
}
public void BeginRowEdit(object value, DataGridColumn column)
{
if (null == value)
{
throw FxTrace.Exception.AsError(new ArgumentNullException("value"));
}
if (null == column)
{
throw FxTrace.Exception.AsError(new ArgumentNullException("column"));
}
int columnIndex = this.dataGrid.Columns.IndexOf(column);
if (columnIndex < 0)
{
throw FxTrace.Exception.AsError(new ArgumentOutOfRangeException("column"));
}
ICollectionView items = CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource);
if (null != items)
{
this.CommitDataGrid();
this.dataGrid.SelectedItem = null;
//lookup element in the collection
if (items.MoveCurrentTo(value))
{
//set the SelectedItem to passed value
this.dataGrid.SelectedItem = value;
//get the cell which contains given elemnt
DataGridCell cell = DataGridHelper.GetCell(this.dataGrid, items.CurrentPosition, columnIndex);
//and begin edit
if (null != cell)
{
cell.Focus();
dataGrid.BeginEdit();
}
}
else
{
throw FxTrace.Exception.AsError(new ArgumentOutOfRangeException("value"));
}
}
}
public void BeginRowEdit(object value)
{
var column = this.dataGrid.Columns[0];
int index = 1;
while (null != column && column.Visibility == Visibility.Hidden && this.dataGrid.Columns.Count > index)
{
column = this.dataGrid.Columns[index];
++index;
}
this.BeginRowEdit(value, column);
}
void OnDataGridRowLoading(object sender, DataGridRowEventArgs e)
{
if (this.DefaultRowControlTemplate == null)
{
this.DefaultRowControlTemplate = e.Row.Template;
}
if (e.Row.Item == CollectionView.NewItemPlaceholder)
{
e.Row.Style = (Style)this.FindResource("defaultNewRowStyle");
e.Row.UpdateLayout();
}
}
void OnAddNewRowContentPresenterLoaded(object sender, RoutedEventArgs args)
{
var presenter = (ContentPresenter)sender;
this.AddNewRowContentPresenter = presenter;
if (null != this.AddNewRowContent)
{
if (this.AddNewRowContent is DataTemplate)
{
presenter.ContentTemplate = (DataTemplate)this.AddNewRowContent;
presenter.ApplyTemplate();
}
else
{
presenter.ContentTemplate = (DataTemplate)this.FindResource("defaultAddNewRowTemplate");
presenter.ApplyTemplate();
presenter.Content = this.AddNewRowContent.ToString();
}
}
}
void OnAddNewRowClick(object sender, RoutedEventArgs args)
{
//user clicked on AddNew row - commit all pending changes
this.CommitDataGrid();
Button btn = (Button)sender;
//if there is callback registered
if (null != this.NotifyNewRowAddedCallback)
{
//execute it
object added = this.NotifyNewRowAddedCallback(this.dataGrid, btn.CommandParameter);
//if add was successfull, begin editing new row
this.isNewRowAdded = (null != added);
if (this.isNewRowAdded)
{
this.BeginRowEdit(added);
}
}
//if there is command registered
else if (null != this.AddNewRowCommand)
{
//try to invoke command as routed command, the as the interface command
RoutedCommand cmd = this.AddNewRowCommand as RoutedCommand;
if (null == cmd)
{
if (this.AddNewRowCommand.CanExecute(btn.CommandParameter))
{
this.AddNewRowCommand.Execute(btn.CommandParameter);
this.isNewRowAdded = true;
}
}
else
{
if (cmd.CanExecute(btn.CommandParameter, this.dataGrid))
{
cmd.Execute(btn.CommandParameter, this.dataGrid);
this.isNewRowAdded = true;
}
}
}
}
void OnAddNewRowGotFocus(object sender, RoutedEventArgs e)
{
//When tab over the last row, the last column won't get commit by default, which is a bug of DataGrid with
//customized new place holder template. Call commit explicitly here to workaround this issue
this.CommitDataGrid();
this.dataGrid.SelectedItem = null;
}
void CommitDataGrid()
{
if (!GetIsCommitInProgress(this.dataGrid))
{
SetIsCommitInProgress(this.dataGrid, true);
this.ExplicitCommit = true;
this.dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
this.ExplicitCommit = false;
SetIsCommitInProgress(this.dataGrid, false);
}
}
void NotifyEditingControlLoaded(Control control, DataGridCell cell, bool isNewRowLoaded)
{
Type controlType = control.GetType();
Type editorBehaviorType;
if (EditorBehaviorTypeMapping.ContainsKey(controlType))
{
editorBehaviorType = EditorBehaviorTypeMapping[controlType];
}
else
{
editorBehaviorType = typeof(DefaultControlBehavior);
}
EditingControlBehavior behavior = Activator.CreateInstance(editorBehaviorType, this.dataGrid) as EditingControlBehavior;
bool isHandled = behavior.HandleControlLoaded(control, cell, isNewRowLoaded);
if (isHandled)
{
SetControlBehavior(control, behavior);
}
if (null != this.NotifyBeginCellEditCallback)
{
this.NotifyBeginCellEditCallback(control, cell, isNewRowLoaded);
}
}
void NotifyEditingControlUnloaded(Control control, DataGridCell cell)
{
bool isHandled = false;
EditingControlBehavior behavior = GetControlBehavior(control);
if (null != behavior)
{
isHandled = behavior.ControlUnloaded(control, cell);
}
if (null != this.NotifyEndCellEditCallback)
{
this.NotifyEndCellEditCallback(control, cell);
}
}
void OnCanShowPropertyValueEditor(object sender, CanExecuteRoutedEventArgs args)
{
Fx.Assert(this.LoadCustomPropertyValueEditorCallback != null, "LoadCustomPropertyValueEditorCallback is not set!");
Fx.Assert(this.LoadDynamicContentDataCallback != null, "LoadDynamicContentDataCallback is not set!");
if (null != this.LoadDynamicContentDataCallback && null != this.LoadCustomPropertyValueEditorCallback)
{
var cell = VisualTreeUtils.FindVisualAncestor<DataGridCell>((DependencyObject)args.OriginalSource);
var row = VisualTreeUtils.FindVisualAncestor<DataGridRow>(cell);
args.CanExecute = null != this.LoadCustomPropertyValueEditorCallback(cell, row.Item);
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Propagating exceptions might lead to VS crash.")]
[SuppressMessage("Reliability", "Reliability108:IsFatalRule",
Justification = "Propagating exceptions might lead to VS crash.")]
void OnShowPropertyValueEditor(object sender, ExecutedRoutedEventArgs args)
{
//user clicked on dialog's property editor's button - now we need to show custom designer in dialog mode
var cell = VisualTreeUtils.FindVisualAncestor<DataGridCell>((DependencyObject)args.OriginalSource);
var row = VisualTreeUtils.FindVisualAncestor<DataGridRow>(cell);
//ask client for custom editor, given for currently selected row
var editor = this.LoadCustomPropertyValueEditorCallback(cell, row.Item);
Fx.Assert(editor != null, "Custom property value editor is not set or doesn't derive from DialogPropertyValueEditor!");
if (null != editor)
{
//out of currently selected row, get actual property which is beeing edited
var value = this.LoadDynamicContentDataCallback(cell, row.Item);
Fx.Assert(value != null, "ModelProperty shouldn't be null");
//create model property entry - it is required by dialog property editor
var propertyEntry = new ModelPropertyEntry(value, null);
try
{
editor.ShowDialog(propertyEntry.PropertyValue, (IInputElement)args.OriginalSource);
}
catch (Exception err)
{
ErrorReporting.ShowErrorMessage(err);
}
}
}
internal static void OnEditingControlLoaded(object sender, RoutedEventArgs args)
{
//editing control has been loaded - user starts editing the cell
Control ctrl = (Control)sender;
//get the data grid reference from control
DataGrid dg = VisualTreeUtils.FindVisualAncestor<DataGrid>(ctrl);
Fx.Assert(null != dg, string.Format(CultureInfo.CurrentCulture, "DataGrid is not in the visual tree of this control: {0}", ctrl));
if (null != dg)
{
//get the target instance of data grid helper
DataGridHelper helper = DataGridHelper.GetDGHelper(dg);
//store data grid helper in the control
DataGridHelper.SetDGHelper(ctrl, helper);
if (null != helper)
{
//notify user that given control is becoming acive one
DataGridCell cell = VisualTreeUtils.FindVisualAncestor<DataGridCell>(ctrl);
helper.NotifyEditingControlLoaded(ctrl, cell, helper.isNewRowAdded);
helper.isNewRowAdded = false;
}
}
}
internal static void OnEditingControlUnloaded(object sender, RoutedEventArgs args)
{
//editing control has been unloaded - user ends editing the cell
Control ctrl = (Control)sender;
//get data grid helper out of it
DataGridHelper helper = DataGridHelper.GetDGHelper(ctrl);
//notify user that edit is complete
if (null != helper)
{
DataGridCell cell = VisualTreeUtils.FindVisualAncestor<DataGridCell>(ctrl);
helper.NotifyEditingControlUnloaded(ctrl, cell);
}
}
void OnPreviewCellMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//When Ctrl of Shift is pressed, let DataGrid to handle multi-selection
//and DataGrid shouldn't enter editing mode in this case
if ((Keyboard.IsKeyDown(Key.RightShift)) || (Keyboard.IsKeyDown(Key.LeftShift)) ||
(Keyboard.IsKeyDown(Key.LeftCtrl)) || (Keyboard.IsKeyDown(Key.RightCtrl)))
{
return;
}
//support for single click edit
DataGridCell cell = sender as DataGridCell;
//enter this code only if cell is not beeing edited already and is not readonly
if (null != cell && !cell.IsEditing && !cell.IsReadOnly && null != this.dataGrid.SelectedItem)
{
bool shouldFocus = true;
//depending on the selection type - either select cell or row
if (this.dataGrid.SelectionUnit != DataGridSelectionUnit.FullRow)
{
if (!cell.IsSelected)
{
cell.IsSelected = true;
}
}
else
{
DataGridRow row = VisualTreeUtils.FindVisualAncestor<DataGridRow>(cell);
//if row was not selected - first click will select it, second will start editing cell's value
if (null != row && !row.IsSelected)
{
this.dataGrid.SelectedItem = row;
shouldFocus = false;
}
}
//if allowed - begin edit
if ((shouldFocus && !cell.IsFocused) && !GetIsCustomEditor(cell))
{
//attempt to set focus to the cell, and let DG start editing
if (cell.Focus() && !cell.IsEditing)
{
if (dataGrid.SelectionUnit == DataGridSelectionUnit.FullRow)
{
dataGrid.SelectedItems.Clear();
DataGridRow row = VisualTreeUtils.FindVisualAncestor<DataGridRow>(cell);
if (row != null)
{
dataGrid.SelectedItems.Add(dataGrid.ItemContainerGenerator.ItemFromContainer(row));
}
}
else
{
dataGrid.SelectedCells.Clear();
dataGrid.SelectedCells.Add(new DataGridCellInfo(cell));
}
this.dataGrid.BeginEdit();
}
}
}
}
void OnDataGridMouseDown(object sender, RoutedEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource);
if (null != this.dataGrid.SelectedItem && this.dataGrid.CurrentCell.IsValid && view.MoveCurrentTo(this.dataGrid.SelectedItem))
{
int rowIndex = view.CurrentPosition;
int columnIndex = this.dataGrid.Columns.IndexOf(this.dataGrid.CurrentCell.Column);
var cell = DataGridHelper.GetCell(this.dataGrid, rowIndex, columnIndex);
if (null != cell && cell.IsEditing)
{
this.CommitDataGrid();
cell.Focus();
}
}
else
{
this.dataGrid.Focus();
}
}
void OnDynamicContentColumnLoaded(DataGridCell cell, ContentControl contentContainer)
{
//user marked at least one column in data grid with DynamicContent - now, we have to query client for
//cell template for given row's property
if (null != this.ResolveDynamicTemplateCallback)
{
var resolveParams = new ResolveTemplateParams(cell, contentContainer.Content);
if (this.ResolveDynamicTemplateCallback(resolveParams) && null != resolveParams.Template)
{
if (!resolveParams.IsDefaultTemplate)
{
var content = this.LoadDynamicContentDataCallback(cell, contentContainer.Content);
var propertyEntry = new ModelPropertyEntry(content, null);
contentContainer.Content = propertyEntry.PropertyValue;
SetIsCustomEditor(cell, true);
}
else
{
contentContainer.Content = cell.DataContext;
SetIsCustomEditor(cell, false);
}
contentContainer.ContentTemplate = resolveParams.Template;
}
}
else
{
System.Diagnostics.Debug.WriteLine("ResolveDynamicTemplateCallback not registered for column " + cell.Column.Header);
}
}
public void UpdateDynamicContentColumns(object entry)
{
ICollectionView view = CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource);
int rowIndex = -1;
//get index of given entry
if (view.MoveCurrentTo(entry))
{
rowIndex = view.CurrentPosition;
}
if (-1 != rowIndex)
{
//pickup all dynamic columns in this data grid
var dynamicColumnsIndexes = this.dataGrid.Columns
.OfType<DataGridTemplateColumn>()
.Where(p => DataTemplate.Equals(p.CellEditingTemplate, DataGridHelper.DynamicCellContentTemplate) &&
DataTemplate.Equals(p.CellTemplate, DataGridHelper.DynamicCellContentTemplate))
.Select<DataGridColumn, int>(p => this.dataGrid.Columns.IndexOf(p));
//foreach dynamic column
foreach (var columnIndex in dynamicColumnsIndexes)
{
//get the cell
var cell = DataGridHelper.GetCell(this.dataGrid, rowIndex, columnIndex);
//get the content presenter within it
var dynamicContent = VisualTreeUtils.GetNamedChild<ContentControl>(cell, DataGridHelper.dynamicContentControlName, 5);
//reload the template
if (null != dynamicContent)
{
dynamicContent.ContentTemplate = null;
this.OnDynamicContentColumnLoaded(cell, dynamicContent);
}
}
}
}
void ApplyCellStyle()
{
//create default cell style
Style baseStyle = this.dataGrid.CellStyle;
//respect any user's base styles
Style style = null == baseStyle ? new Style(typeof(DataGridCell)) : new Style(typeof(DataGridCell), baseStyle);
//event handler for preview mouse down - single click edit
style.Setters.Add(new EventSetter(DataGridCell.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(this.OnPreviewCellMouseLeftButtonDown)));
//width binding - prevent columns from expanding while typing long texts
style.Setters.Add(new Setter(DataGridCell.WidthProperty, new Binding("Column.ActualWidth")
{
RelativeSource = new RelativeSource(RelativeSourceMode.Self),
Mode = BindingMode.OneWay
}));
//automation id - for cell it is always column name
style.Setters.Add(new Setter(AutomationProperties.AutomationIdProperty, new Binding("Column.Header")
{
RelativeSource = new RelativeSource(RelativeSourceMode.Self),
Mode = BindingMode.OneWay
}));
//apply style
this.dataGrid.CellStyle = style;
}
void ApplyRowStyle()
{
//create default row style
Style baseStyle = this.dataGrid.RowStyle;
//respect any user's base styles
Style style = null == baseStyle ? new Style(typeof(DataGridRow)) : new Style(typeof(DataGridRow), baseStyle);
EventSetter keyDownSetter = new EventSetter
{
Event = DataGridRow.KeyDownEvent,
Handler = new KeyEventHandler(this.OnDataGridRowKeyDown)
};
style.Setters.Add(keyDownSetter);
//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)
//first - create a binding and add ErrorToTooltipConverter, pass reference to owning data grid helper
var multiBinding = new MultiBinding() { Converter = new ErrorToTooltipConverter(this) };
//now define bindings
//first - bind to actual object behind the row - only DesignObjectWrapper is supported
var objectWrapperBinding = new Binding() { Mode = BindingMode.OneTime };
//second - bind to a HasError property change notifications - this will trigger tooltip to appear
var hasErrorsBinding = new Binding() { Mode = BindingMode.OneWay, Path = new PropertyPath("HasErrors") };
//finally - bind to a row which contains the data - this will be used as tooltip placement target
var rowBinding = new Binding() { Mode = BindingMode.OneTime, RelativeSource = new RelativeSource(RelativeSourceMode.Self) };
multiBinding.Bindings.Add(objectWrapperBinding);
multiBinding.Bindings.Add(hasErrorsBinding);
multiBinding.Bindings.Add(rowBinding);
var errorTooltipTrigger = new DataTrigger()
{
Binding = multiBinding,
Value = true
};
//define a dummy setter - it will never be executed anyway, but it is required for the binding to work
errorTooltipTrigger.Setters.Add(new Setter(DataGridRow.TagProperty, null));
//add trigger to the collection
style.Triggers.Add(errorTooltipTrigger);
//apply style
this.dataGrid.RowStyle = style;
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Propagating exceptions might lead to VS crash.")]
[SuppressMessage("Reliability", "Reliability108:IsFatalRule",
Justification = "Propagating exceptions might lead to VS crash.")]
static void OnDynamicCellContentLoaded(object sender, RoutedEventArgs e)
{
var container = (ContentControl)sender;
var dataGridCell = VisualTreeUtils.FindVisualAncestor<DataGridCell>(container);
var dataGrid = VisualTreeUtils.FindVisualAncestor<DataGrid>(dataGridCell);
if (dataGrid != null)
{
var dataGridHelper = DataGridHelper.GetDGHelper(dataGrid);
if (GetIsCustomEditor(dataGridCell) && (dataGridCell.IsEditing))
{
dataGridHelper.CommitDataGrid();
}
else
{
try
{
dataGridHelper.OnDynamicContentColumnLoaded(dataGridCell, container);
}
catch (Exception err)
{
container.Content = err.ToString();
container.ContentTemplate = (DataTemplate)dataGrid.Resources["dynamicContentErrorTemplate"];
System.Diagnostics.Debug.WriteLine(err.ToString());
}
}
}
}
internal static void DataGridCellGotFocus(DataGrid dataGrid, RoutedEventArgs e)
{
// Lookup for the source to be DataGridCell
if (null != e && null != e.OriginalSource && e.OriginalSource.GetType() == typeof(DataGridCell))
{
// Starts the Edit on the row;
dataGrid.BeginEdit(e);
}
}
public static DataTemplate DynamicCellContentTemplate
{
get
{
if (null == dynamicCellContentTemplate)
{
DataTemplate template = new DataTemplate();
FrameworkElementFactory contentControlFactory = new FrameworkElementFactory(typeof(ContentControl));
contentControlFactory.SetValue(ContentControl.NameProperty, DataGridHelper.dynamicContentControlName);
contentControlFactory.SetBinding(ContentControl.ContentProperty, new Binding());
contentControlFactory.AddHandler(ContentControl.LoadedEvent, new RoutedEventHandler(DataGridHelper.OnDynamicCellContentLoaded));
template.VisualTree = new FrameworkElementFactory(typeof(NoContextMenuGrid));
template.VisualTree.AppendChild(contentControlFactory);
template.Seal();
dynamicCellContentTemplate = template;
}
return dynamicCellContentTemplate;
}
}
static DataGridHelper GetDGHelper(DependencyObject obj)
{
return (DataGridHelper)obj.GetValue(DGHelperProperty);
}
static void SetDGHelper(DependencyObject obj, DataGridHelper value)
{
obj.SetValue(DGHelperProperty, value);
}
static EditingControlBehavior GetControlBehavior(DependencyObject obj)
{
return (EditingControlBehavior)obj.GetValue(ControlBehaviorProperty);
}
static void SetControlBehavior(DependencyObject obj, EditingControlBehavior value)
{
obj.SetValue(ControlBehaviorProperty, value);
}
static bool GetNewRowLoaded(DependencyObject obj)
{
return (bool)obj.GetValue(NewRowLoadedProperty);
}
static void SetNewRowLoaded(DependencyObject obj, bool value)
{
obj.SetValue(NewRowLoadedProperty, value);
}
static bool GetIsCommitInProgress(DependencyObject obj)
{
return (bool)obj.GetValue(IsCommitInProgressProperty);
}
static void SetIsCommitInProgress(DependencyObject obj, bool value)
{
obj.SetValue(IsCommitInProgressProperty, value);
}
public static DataGridCell GetCell(DataGrid dataGrid, int row, int column)
{
DataGridRow rowContainer = GetRow(dataGrid, row);
if (rowContainer != null)
{
DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
if (presenter != null)
{
// try to get the cell but it may possibly be virtualized
DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
if (cell == null)
{
// now try to bring into view and retreive the cell
dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
}
return cell;
}
}
return null;
}
internal static ModelItem GetSingleSelectedObject(DataGrid dataGrid)
{
if (dataGrid == null || dataGrid.SelectedItems == null || dataGrid.SelectedItems.Count != 1)
{
return null;
}
if (dataGrid.SelectedItems[0] == CollectionView.NewItemPlaceholder)
{
return null;
}
DesignObjectWrapper designObjectWrapper = dataGrid.SelectedItems[0] as DesignObjectWrapper;
if (designObjectWrapper != null)
{
return designObjectWrapper.ReflectedObject;
}
return null;
}
/// <summary>
/// Gets the DataGridRow based on the given index
/// </summary>
/// <param name="index">the index of the container to get</param>
public static DataGridRow GetRow(DataGrid dataGrid, int index)
{
DataGridRow row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
// may be virtualized, bring into view and try again
dataGrid.ScrollIntoView(dataGrid.Items[index]);
dataGrid.UpdateLayout();
row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
public static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
public static void CommitPendingEdits(DataGrid dataGrid)
{
if (null == dataGrid)
{
throw FxTrace.Exception.AsError(new ArgumentNullException("dataGrid"));
}
if (!GetIsCommitInProgress(dataGrid))
{
SetIsCommitInProgress(dataGrid, true);
//try to commit edit
bool commitSucceeded = false;
DataGridHelper helper = DataGridHelper.GetDGHelper(dataGrid) as DataGridHelper;
bool orginalExplicitCommit = helper.ExplicitCommit;
helper.ExplicitCommit = true;
try
{
commitSucceeded = dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
}
catch (InvalidOperationException)
{
// Ignore and cancel edit
}
finally
{
//if commit fails - undo change
if (!commitSucceeded)
{
dataGrid.CancelEdit();
}
helper.ExplicitCommit = orginalExplicitCommit;
}
SetIsCommitInProgress(dataGrid, false);
}
}
public static void OnDeleteSelectedItems(DataGrid dataGrid)
{
if (dataGrid == null
|| dataGrid.SelectedItems == null
|| dataGrid.SelectedItems.Count == 0
|| dataGrid.ItemsSource == null)
{
return;
}
if (!(dataGrid.ItemsSource is IList))
{
// if the item source is not a list
// we can't delete items from it.
return;
}
Int32 nextSelectedIndex = -1;
ICollection<object> toBeDeleted = new HashSet<object>();
foreach (object obj in dataGrid.SelectedItems)
{
toBeDeleted.Add(obj);
}
if (toBeDeleted.Count == 0)
{
return;
}
if (toBeDeleted.Count == 1)
{
// if it is single selection,
// set selected index to be the current selected index.
// Set nextSelectedIndex only in single selection to keep the behavior
// consistent with the "delete" button on key board, where if you
// select more than one items and push the "Delete" button on keyboard,
// nothing will be selected.
nextSelectedIndex = dataGrid.Items.IndexOf(toBeDeleted.ElementAt(0));
}
IList itemSource = (IList)dataGrid.ItemsSource;
foreach (object obj in toBeDeleted)
{
itemSource.Remove(obj);
}
if (nextSelectedIndex >= 0
&& nextSelectedIndex < dataGrid.Items.Count - 1)
{
// The last row, whose index is (dataGrid.Items.Count - 1), is
// the "add new item" row.
dataGrid.SelectedIndex = nextSelectedIndex;
}
}
internal abstract class EditingControlBehavior
{
protected DesignerView DesignerView
{
get;
private set;
}
protected DataGrid OwnerDataGrid
{
get;
set;
}
public EditingControlBehavior(DataGrid dataGrid)
{
this.OwnerDataGrid = dataGrid;
var helper = DataGridHelper.GetDGHelper(dataGrid);
if (null != helper && null != helper.Context)
{
this.DesignerView = helper.Context.Services.GetService<DesignerView>();
}
}
public abstract bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded);
public abstract bool ControlUnloaded(Control control, DataGridCell cell);
protected void ToggleDesignerViewAutoCommit(bool shouldIgnore)
{
if (null != this.DesignerView)
{
//enable/disable handling of lost keyboard focus events in designer view -
//if shouldIgnore is true, designer view should ignore keyboard focus events thus, not forcing DataGrid to
//commit any changes
this.DesignerView.ShouldIgnoreDataGridAutoCommit = shouldIgnore;
}
}
}
internal sealed class DefaultControlBehavior : EditingControlBehavior
{
public DefaultControlBehavior(DataGrid dataGrid)
: base(dataGrid)
{ }
public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
{
System.Diagnostics.Debug.WriteLine("DefaultControlBehavior.HandleControlLoaded");
control.Focus();
return true;
}
public override bool ControlUnloaded(Control control, DataGridCell cell)
{
System.Diagnostics.Debug.WriteLine("DefaultControlBehavior.ControlUnloaded");
return true;
}
}
internal sealed class TextBoxBehavior : EditingControlBehavior
{
public TextBoxBehavior(DataGrid dataGrid)
: base(dataGrid)
{ }
public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
{
System.Diagnostics.Debug.WriteLine("TextBoxBehavior.HandleControlLoaded");
bool handled = false;
TextBox tb = control as TextBox;
if (null != tb)
{
if (newRowLoaded)
{
tb.SelectAll();
}
else
{
tb.CaretIndex = tb.Text.Length;
}
tb.Focus();
handled = true;
}
return handled;
}
public override bool ControlUnloaded(Control control, DataGridCell cell)
{
System.Diagnostics.Debug.WriteLine("TextBoxBehavior.ControlUnloaded");
return true;
}
}
internal sealed class VBIdentifierDesignerBehavior : EditingControlBehavior
{
public VBIdentifierDesignerBehavior(DataGrid dataGrid)
: base(dataGrid)
{ }
public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
{
System.Diagnostics.Debug.WriteLine("VBIdentifierDesignerBehavior.HandleControlLoaded");
bool handled = false;
VBIdentifierDesigner identifierDesigner = control as VBIdentifierDesigner;
if ((null != identifierDesigner) && (!identifierDesigner.IsReadOnly))
{
if (newRowLoaded)
{
DataGridHelper.SetNewRowLoaded(identifierDesigner, true);
}
else
{
DataGridHelper.SetNewRowLoaded(identifierDesigner, false);
}
identifierDesigner.TextBoxPropertyChanged += this.OnIdentifierDesignerTextBoxChanged;
identifierDesigner.Focus();
handled = true;
}
return handled;
}
void OnIdentifierDesignerTextBoxChanged(object sender, PropertyChangedEventArgs e)
{
Fx.Assert(e.PropertyName == "IdentifierTextBox", "VBIdentifierDesignerBehavior.TextBoxPropertyChanged event should only be raised when IdentifierTextBox property is changed.");
VBIdentifierDesigner identifierDesigner = sender as VBIdentifierDesigner;
TextBox textBox = identifierDesigner.IdentifierTextBox;
if (textBox != null)
{
if (DataGridHelper.GetNewRowLoaded(identifierDesigner))
{
textBox.SelectAll();
DataGridHelper.SetNewRowLoaded(identifierDesigner, false);
}
else
{
textBox.CaretIndex = textBox.Text.Length;
}
textBox.Focus();
}
}
public override bool ControlUnloaded(Control control, DataGridCell cell)
{
System.Diagnostics.Debug.WriteLine("VBIdentifierDesignerBehavior.ControlUnloaded");
VBIdentifierDesigner identifierDesigner = control as VBIdentifierDesigner;
if (identifierDesigner != null)
{
identifierDesigner.TextBoxPropertyChanged -= this.OnIdentifierDesignerTextBoxChanged;
}
return true;
}
}
internal sealed class TypePresenterBehavior : EditingControlBehavior
{
DataGridCell cell;
TypePresenter typePresenter;
bool isTypeBrowserOpen = false;
bool isRegisteredForEvents = false;
public TypePresenterBehavior(DataGrid dataGrid)
: base(dataGrid)
{
DataGridHelper helper = DataGridHelper.GetDGHelper(dataGrid) as DataGridHelper;
helper.DataGridCellEditEnding += OnCellEditEnding;
}
public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
{
System.Diagnostics.Debug.WriteLine("TypePresenterBehavior.HandleControlLoaded");
bool handled = false;
this.cell = cell;
this.typePresenter = control as TypePresenter;
if (null != this.typePresenter)
{
this.isRegisteredForEvents = true;
this.typePresenter.TypeBrowserOpened += OnTypeBrowserOpened;
this.typePresenter.TypeBrowserClosed += OnTypeBrowserClosed;
handled = this.typePresenter.typeComboBox.Focus();
}
return handled;
}
public override bool ControlUnloaded(Control control, DataGridCell cell)
{
System.Diagnostics.Debug.WriteLine("TypePresenterBehavior.ControlUnloaded");
this.cell = null;
if (this.isRegisteredForEvents && null != this.typePresenter)
{
this.typePresenter.TypeBrowserOpened -= OnTypeBrowserOpened;
this.typePresenter.TypeBrowserClosed -= OnTypeBrowserClosed;
this.typePresenter = null;
this.isRegisteredForEvents = false;
DataGridHelper helper = DataGridHelper.GetDGHelper(this.OwnerDataGrid) as DataGridHelper;
helper.DataGridCellEditEnding -= this.OnCellEditEnding;
}
return true;
}
void OnCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
if (null != this.cell)
{
e.Cancel = this.isTypeBrowserOpen;
}
}
void OnTypeBrowserOpened(object sender, RoutedEventArgs e)
{
base.ToggleDesignerViewAutoCommit(true);
this.isTypeBrowserOpen = true;
}
void OnTypeBrowserClosed(object sender, RoutedEventArgs e)
{
base.ToggleDesignerViewAutoCommit(false);
this.isTypeBrowserOpen = false;
}
}
internal sealed class ExpressionTextBoxBehavior : EditingControlBehavior
{
bool isExpressionEditInProgress;
DataGridCell cell;
public ExpressionTextBoxBehavior(DataGrid dataGrid)
: base(dataGrid)
{
DataGridHelper helper = DataGridHelper.GetDGHelper(dataGrid) as DataGridHelper;
helper.DataGridCellEditEnding += OnCellEditEnding;
}
public override bool HandleControlLoaded(Control control, DataGridCell cell, bool newRowLoaded)
{
System.Diagnostics.Debug.WriteLine("ExpressionTextBoxBehavior.HandleControlLoaded");
bool handled = false;
this.cell = cell;
ExpressionTextBox etb = control as ExpressionTextBox;
if (null != etb)
{
etb.Tag = cell;
//register for logical lost focus events
etb.EditorLostLogicalFocus += OnExpressionEditComplete;
if (!etb.IsReadOnly)
{
//start editing expression
etb.BeginEdit();
//mark expression edit is in progress, so all CellEditEnding calls will be ignored by datagrid
this.isExpressionEditInProgress = true;
//disable forced keyboard focus lost events - intelisense window will trigger lost keyboard event,
//which eventualy will lead to commit edit
base.ToggleDesignerViewAutoCommit(true);
}
handled = true;
}
return handled;
}
public override bool ControlUnloaded(Control control, DataGridCell cell)
{
System.Diagnostics.Debug.WriteLine("ExpressionTextBoxBehavior.ControlUnloaded");
ExpressionTextBox etb = control as ExpressionTextBox;
if (null != etb)
{
//control is unloaded - unregister from the event
etb.EditorLostLogicalFocus -= OnExpressionEditComplete;
//if it happens that complete row is beeing unloaded, it is possible that expression edit was still in progress
if (this.isExpressionEditInProgress)
{
//force expression update before unload is complete
this.OnExpressionEditComplete(etb, null);
}
}
DataGridHelper helper = DataGridHelper.GetDGHelper(this.OwnerDataGrid) as DataGridHelper;
helper.DataGridCellEditEnding -= OnCellEditEnding;
this.cell = null;
return true;
}
void OnExpressionEditComplete(object sender, RoutedEventArgs e)
{
if (this.isExpressionEditInProgress)
{
ExpressionTextBox etb = (ExpressionTextBox)sender;
//commit the expression value
((RoutedCommand)DesignerView.CommitCommand).Execute(null, etb);
//allow data grid to consume cell editing events
this.isExpressionEditInProgress = false;
this.OwnerDataGrid.CommitEdit();
//restore keyboard focus handling for designer view
base.ToggleDesignerViewAutoCommit(false);
}
}
void OnCellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
DataGridHelper helper = DataGridHelper.GetDGHelper(this.OwnerDataGrid) as DataGridHelper;
if (this.isExpressionEditInProgress && (helper != null) && !helper.ExplicitCommit)
{
e.Cancel = true;
}
else if (this.isExpressionEditInProgress)
{
ExpressionTextBox etb = VisualTreeUtils.GetTemplateChild<ExpressionTextBox>(e.EditingElement);
this.OnExpressionEditComplete(etb, null);
}
}
}
}
sealed class ResolveTemplateParams
{
internal ResolveTemplateParams(DataGridCell cell, object instance)
{
this.Cell = cell;
this.Instance = instance;
this.IsDefaultTemplate = true;
}
public DataGridCell Cell { get; private set; }
public object Instance { get; private set; }
public bool IsDefaultTemplate { get; set; }
public DataTemplate Template { get; set; }
}
sealed class ErrorToTooltipConverter : IMultiValueConverter
{
DataGridHelper owner;
DataTemplate toolTipTemplate;
public ErrorToTooltipConverter(DataGridHelper owner)
{
this.owner = owner;
this.toolTipTemplate = (DataTemplate)this.owner.FindResource("errorToolTipTemplate");
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var entry = values.OfType<DesignObjectWrapper>().FirstOrDefault();
var row = values.OfType<DataGridRow>().FirstOrDefault();
if (this.owner.IsEditInProgress && null != entry && entry.HasErrors && null != row)
{
var invalidProperties = new List<string>();
if (this.owner.ShowValidationErrorAsToolTip)
{
var errorTip = new ToolTip()
{
PlacementTarget = row,
Placement = PlacementMode.Bottom,
ContentTemplate = this.toolTipTemplate,
Content = entry.GetValidationErrors(invalidProperties),
Effect = new DropShadowEffect() { ShadowDepth = 1 },
};
AutomationProperties.SetAutomationId(errorTip, "errorToolTip");
row.Dispatcher.BeginInvoke(new Action<ToolTip, DataGridRow>((tip, r) =>
{
tip.IsOpen = true;
var dt = new DispatcherTimer(TimeSpan.FromSeconds(6), DispatcherPriority.ApplicationIdle, (sender, e) => { tip.IsOpen = false; }, r.Dispatcher);
}), DispatcherPriority.ApplicationIdle, errorTip, row);
}
else
{
row.Dispatcher.BeginInvoke(new Action<string>((error) =>
{
//get currently focused element
var currentFocus = (UIElement)Keyboard.FocusedElement;
if (null != currentFocus)
{
//if focus was within datagrid's cell, after loosing focus most likely the editing control would be gone, so try to preserve
//reference to the cell itself
currentFocus = VisualTreeUtils.FindVisualAncestor<DataGridCell>(currentFocus) ?? currentFocus;
}
//show error message (this will result in KeyboardFocus changed
ErrorReporting.ShowErrorMessage(error);
//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)
if (null != currentFocus && null != VisualTreeUtils.FindVisualAncestor<DesignerView>(currentFocus))
{
Keyboard.Focus(currentFocus);
}
}), DispatcherPriority.ApplicationIdle, entry.GetValidationErrors(invalidProperties));
}
//clear the validation error messages - once the error is raised and displayed, i don't need it anymore in the collection
entry.ClearValidationErrors(invalidProperties);
}
//in case of property grid edit, the errors would be displayed by model item infrastructure,
//so just delegate the call to clear errors collection
if (!this.owner.IsEditInProgress && null != entry && entry.HasErrors)
{
Dispatcher.CurrentDispatcher.BeginInvoke(new Action<DesignObjectWrapper>((instance) =>
{
instance.ClearValidationErrors();
}), DispatcherPriority.ApplicationIdle, entry);
}
return Binding.DoNothing;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw FxTrace.Exception.AsError(new NotSupportedException());
}
}
}
|