| File: System\Windows\Controls\DataGridHyperlinkColumn.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; namespace System.Windows.Controls { /// <summary> /// A column that displays a hyperlink. /// </summary> public class DataGridHyperlinkColumn : DataGridBoundColumn { static DataGridHyperlinkColumn() { ElementStyleProperty.OverrideMetadata(typeof(DataGridHyperlinkColumn), new FrameworkPropertyMetadata(DefaultElementStyle)); EditingElementStyleProperty.OverrideMetadata(typeof(DataGridHyperlinkColumn), new FrameworkPropertyMetadata(DefaultEditingElementStyle)); } #region Hyperlink Column Properties /// <summary> /// Dependecy property for TargetName Property /// </summary> public static readonly DependencyProperty TargetNameProperty = Hyperlink.TargetNameProperty.AddOwner( typeof(DataGridHyperlinkColumn), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(DataGridColumn.NotifyPropertyChangeForRefreshContent))); /// <summary> /// The property which determines the target name of the hyperlink /// </summary> public string TargetName { get { return (string)GetValue(TargetNameProperty); } set { SetValue(TargetNameProperty, value); } } /// <summary> /// The binding to the content to be display under hyperlink /// </summary> public BindingBase ContentBinding { get { return _contentBinding; } set { if (_contentBinding != value) { BindingBase oldValue = _contentBinding; _contentBinding = value; OnContentBindingChanged(oldValue, value); } } } /// <summary> /// Called when ContentBinding changes. /// </summary> /// <remarks> /// Default implementation notifies the DataGrid and its subtree about the change. /// </remarks> /// <param name="oldBinding">The old binding.</param> /// <param name="newBinding">The new binding.</param> protected virtual void OnContentBindingChanged(BindingBase oldBinding, BindingBase newBinding) { NotifyPropertyChanged(nameof(ContentBinding)); } /// <summary> /// Try applying ContentBinding. If it doesnt work out apply Binding. /// </summary> /// <param name="target"></param> /// <param name="property"></param> private void ApplyContentBinding(DependencyObject target, DependencyProperty property) { if (ContentBinding != null) { BindingOperations.SetBinding(target, property, ContentBinding); } else if (Binding != null) { BindingOperations.SetBinding(target, property, Binding); } else { BindingOperations.ClearBinding(target, property); } } #endregion #region Property Changed Handler /// <summary> /// Override which rebuilds the cell's visual tree for ContentBinding change /// and Modifies Hyperlink for TargetName change /// </summary> /// <param name="element"></param> /// <param name="propertyName"></param> protected internal override void RefreshCellContent(FrameworkElement element, string propertyName) { if (element is DataGridCell cell && !cell.IsEditing) { if (string.Equals(propertyName, "ContentBinding", StringComparison.Ordinal)) { cell.BuildVisualTree(); } else if (string.Equals(propertyName, "TargetName", StringComparison.Ordinal)) { TextBlock outerBlock = cell.Content as TextBlock; if (outerBlock != null && outerBlock.Inlines.Count > 0) { Hyperlink link = outerBlock.Inlines.FirstInline as Hyperlink; link?.TargetName = TargetName; } } } else { base.RefreshCellContent(element, propertyName); } } #endregion #region Styles /// <summary> /// The default value of the ElementStyle property. /// This value can be used as the BasedOn for new styles. /// </summary> public static Style DefaultElementStyle { get { return DataGridTextColumn.DefaultElementStyle; } } /// <summary> /// The default value of the EditingElementStyle property. /// This value can be used as the BasedOn for new styles. /// </summary> public static Style DefaultEditingElementStyle { get { return DataGridTextColumn.DefaultEditingElementStyle; } } #endregion #region Element Generation /// <summary> /// Creates the visual tree for cells. /// </summary> protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) { TextBlock outerBlock = new TextBlock(); Hyperlink link = new Hyperlink(); InlineUIContainer inlineContainer = new InlineUIContainer(); ContentPresenter innerContentPresenter = new ContentPresenter(); outerBlock.Inlines.Add(link); link.Inlines.Add(inlineContainer); inlineContainer.Child = innerContentPresenter; link.TargetName = TargetName; ApplyStyle(/* isEditing = */ false, /* defaultToElementStyle = */ false, outerBlock); ApplyBinding(link, Hyperlink.NavigateUriProperty); ApplyContentBinding(innerContentPresenter, ContentPresenter.ContentProperty); DataGridHelper.RestoreFlowDirection(outerBlock, cell); return outerBlock; } /// <summary> /// Creates the visual tree for cells. /// </summary> protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) { TextBox textBox = new TextBox(); ApplyStyle(/* isEditing = */ true, /* defaultToElementStyle = */ false, textBox); ApplyBinding(textBox, TextBox.TextProperty); DataGridHelper.RestoreFlowDirection(textBox, cell); return textBox; } #endregion #region Editing /// <summary> /// Called when a cell has just switched to edit mode. /// </summary> /// <param name="editingElement">A reference to element returned by GenerateEditingElement.</param> /// <param name="editingEventArgs">The event args of the input event that caused the cell to go into edit mode. May be null.</param> /// <returns>The unedited value of the cell.</returns> protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs) { TextBox textBox = editingElement as TextBox; if (textBox != null) { textBox.Focus(); string originalValue = textBox.Text; TextCompositionEventArgs textArgs = editingEventArgs as TextCompositionEventArgs; if (textArgs != null) { // If text input started the edit, then replace the text with what was typed. string inputText = textArgs.Text; textBox.Text = inputText; // Place the caret after the end of the text. textBox.Select(inputText.Length, 0); } else { // If something else started the edit, then select the text textBox.SelectAll(); } return originalValue; } return null; } /// <summary> /// Called when a cell's value is to be restored to its original value, /// just before it exits edit mode. /// </summary> /// <param name="editingElement">A reference to element returned by GenerateEditingElement.</param> /// <param name="uneditedValue">The original, unedited value of the cell.</param> protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue) { DataGridHelper.CacheFlowDirection(editingElement, editingElement != null ? editingElement.Parent as DataGridCell : null); base.CancelCellEdit(editingElement, uneditedValue); } /// <summary> /// Called when a cell's value is to be committed, just before it exits edit mode. /// </summary> /// <param name="editingElement">A reference to element returned by GenerateEditingElement.</param> /// <returns>false if there is a validation error. true otherwise.</returns> protected override bool CommitCellEdit(FrameworkElement editingElement) { DataGridHelper.CacheFlowDirection(editingElement, editingElement != null ? editingElement.Parent as DataGridCell : null); return base.CommitCellEdit(editingElement); } internal override void OnInput(InputEventArgs e) { // Text input will start an edit. // Escape is meant to be for CancelEdit. But DataGrid // may not handle KeyDown event for Escape if nothing // is cancelable. Such KeyDown if unhandled by others // will ultimately get promoted to TextInput and be handled // here. But BeginEdit on escape could be confusing to the user. // Hence escape key is special case and BeginEdit is performed if // there is atleast one non espace key character. if (DataGridHelper.HasNonEscapeCharacters(e as TextCompositionEventArgs)) { BeginEdit(e, true); } else if (DataGridHelper.IsImeProcessed(e as KeyEventArgs)) { if (DataGridOwner != null) { DataGridCell cell = DataGridOwner.CurrentCellContainer; if (cell != null && !cell.IsEditing) { Debug.Assert(e.RoutedEvent == Keyboard.PreviewKeyDownEvent, "We should only reach here on the PreviewKeyDown event because the TextBox within is expected to handle the preview event and hence trump the successive KeyDown event."); BeginEdit(e, false); // // The TextEditor for the TextBox establishes contact with the IME // engine lazily at background priority. However in this case we // want to IME engine to know about the TextBox in earnest before // PostProcessing this input event. Only then will the IME key be // recorded in the TextBox. Hence the call to synchronously drain // the Dispatcher queue. // Dispatcher.Invoke((Action)delegate(){}, System.Windows.Threading.DispatcherPriority.Background); } } } } #endregion #region Data private BindingBase _contentBinding = null; #endregion } }