File: System\Windows\Controls\DataGridHyperlinkColumn.cs
Web Access
Project: src\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.
// See the LICENSE file in the project root for more information.
 
 
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("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;
                        if (link != null)
                        {
                            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
    }
}