BLOG

So, you want to add a right click menu to all of your textboxes in your Silverlight Application?

12 Nov 2014, by Sonny Nguyen in Developer

Here at BDCSoft, we like to use Telerik’s UI controls for Silverlight.

A while ago, I was tasked to create a right click context menu that allowed basic copy, cut, and paste functionality.

First, we need access to the clipboard.

We need to create a static class, let’s call it clipboard.

It needs three methods, void Copy(), string Paste() and bool HasContent.

Copy to copy. Paste to paste. And HasContent to check if there’s content.

public static class Clipboard {
        public static void Copy(string text) {
            var clipboardData = (ScriptObject)HtmlPage.Window.GetProperty("clipboardData");
            if (clipboardData != null) {
                clipboardData.Invoke("setData", "text", text);
            }
        }

        public static string Paste() {
            var clipboardData = (ScriptObject)HtmlPage.Window.GetProperty("clipboardData");
            if (clipboardData != null) {
                return (string)clipboardData.Invoke("getData", "text");
            }

            return string.Empty;
        }

        public static bool HasContent {
            get {
                return !string.IsNullOrEmpty(Paste());
            }
        }
    }

Now that we have access to the clipboard, we need to create a new Context Menu class. This Context Menu class will implement Behavior<TextBox>.

We’ll need some properties.

private RadContextMenu _contextMenu;
private RadMenuItem _copy;
private RadMenuItem _cut;
private RadMenuItem _paste;
private RadMenuItem _selectAll; 

private int _selectionStart;
private int _selectionLen;
private string _selectedText;

Now that we have some properties, we’ll need some methods to utilize these properties. Below is our implementation of cut, copy, paste, and select all.

        protected override void OnAttached() {
            base.OnAttached();
            CreateMenu();
            AssociatedObject.SetValue(RadContextMenu.ContextMenuProperty, _contextMenu);
        }

        protected override void OnDetaching() {
            base.OnDetaching();
        }

        #region Set Context Menu
        private void CreateMenu() {
            _contextMenu = new RadContextMenu();

            _cut = new RadMenuItem();
            _cut.Header = "Cut";
            _cut.Click += cut_Click;
            _cut.Icon = new Image() { Source = new BitmapImage(new Uri("cut.png", UriKind.RelativeOrAbsolute))};
            _contextMenu.Items.Add(_cut);

            _copy = new RadMenuItem();
            _copy.Header = "Copy";
            _copy.Click += copy_Click;
            _copy.Icon = new Image() { Source = new BitmapImage(new Uri("page_copy.png", UriKind.RelativeOrAbsolute)) };
            _contextMenu.Items.Add(_copy);

            _paste = new RadMenuItem();
            _paste.Header = "Paste";
            _paste.Click += paste_Click;
            _paste.Icon = new Image() { Source = new BitmapImage(new Uri("paste_plain.png", UriKind.RelativeOrAbsolute)) };
            _contextMenu.Items.Add(_paste);

            _selectAll = new RadMenuItem();
            _selectAll.Header = "Select All";
            _selectAll.Click += selectAll_Click;
            _contextMenu.Items.Add(_selectAll);
            _contextMenu.Opened += ContextMenuOpened;
            _contextMenu.Closed += ContextMenuClosed;
        }

        private void ContextMenuOpened(object sender, RoutedEventArgs e) {
            _selectionStart = AssociatedObject.SelectionStart;
            _selectionLen = AssociatedObject.SelectionLength;
            _selectedText = AssociatedObject.SelectedText;

            bool hasSelection = _selectionLen > 0;

            _cut.IsEnabled = hasSelection && AssociatedObject.IsEnabled && !AssociatedObject.IsReadOnly; // Cut
            _copy.IsEnabled = hasSelection && AssociatedObject.IsEnabled; // Copy
            _paste.IsEnabled = AssociatedObject.IsEnabled && !AssociatedObject.IsReadOnly && !string.IsNullOrEmpty(Clipboard.Paste()); // Paste
        }

        private void paste_Click(object sen, RoutedEventArgs e) {
            Paste(AssociatedObject, _selectionStart, _selectionLen);
        }

        private void cut_Click(object sen, RoutedEventArgs e) {
            Cut(AssociatedObject, _selectedText, _selectionStart, _selectionLen);
        }

        private void copy_Click(object sen, RoutedEventArgs e) {
            Copy(_selectedText);
        }

        private void selectAll_Click(object sen, RoutedEventArgs e) {
            AssociatedObject.SelectAll();
        }

        private void ContextMenuClosed(object sender, System.Windows.RoutedEventArgs e) {
            // RadMenu and RadContextMenu steal the focus in order to provide 
            // keyboard navigation. That's why we need to focus the text box 
            // again when the menu is closed.
            this.AssociatedObject.Focus();
        }
        #endregion

        #region Cut/Copy/Paste

        private static void Copy(string selectedText) {
            Clipboard.Copy(selectedText);
        }

        private static void Cut(TextBox textBox, string selectedText, int selectionStart, int selectionLen) {
            Copy(selectedText);
            DeleteSelection(textBox, selectionStart, selectionLen);
        }

        private static void DeleteSelection(TextBox textBox, int selectionStart, int selectionLen) {
            int cursorPosition = selectionStart;
            textBox.Text = textBox.Text.Remove(cursorPosition, selectionLen);
            textBox.SelectionStart = cursorPosition;
        }

        private static void Paste(TextBox textBox, int selectionStart, int selectionLen) {
            string clipboard = Clipboard.Paste();
            int cursorPosition = selectionStart;
            bool replaceText = selectionLen > 0;
            var text = textBox.Text;

            if (replaceText) {
                text = textBox.Text.Remove(cursorPosition, selectionLen);
            }
            textBox.Text = text.Insert(cursorPosition, clipboard);
            textBox.SelectionStart = cursorPosition + clipboard.Length;
        }

        #endregion

    }

 

Now that we have the context menu, we need to attach this behavior to all textboxes. We have a style.xaml within our project that is referenced everywhere.

We have two styles, a base style and a style that is based on the base style. The important setter is the int:InteractivityItems.Template setter.

Behaviors: namespace is just where we have our contextmenu behavior located.

Int: namespace is: xmlns:int=”clr-namespace:System.Windows.Interactivity”

<Style x:Key="TextboxBaseStyle" TargetType="TextBox">
    <Setter Property="TextWrapping" Value="Wrap" />
    <Setter Property="Cursor" Value="IBeam" />
    <Setter Property="int:InteractivityItems.Template">
        <Setter.Value>
            <int:InteractivityTemplate>
                <int:InteractivityItems>
                    <int:InteractivityItems.Behaviors>
                        <behaviors:ContextMenuTextbox />
                    </int:InteractivityItems.Behaviors>
                </int:InteractivityItems>
            </int:InteractivityTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="TextBox" BasedOn="{StaticResource TextboxBaseStyle}" />

 

That’s it folks. I believe you have everything you need to implement a custom right click menu for your textboxes.

 

About the Author

Sonny NguyenSonny Nguyen has three years experience in the technology industry.
He is a currently a .NET Developer and System Administrator at BDCSoft.
His specialty lies within Troubleshooting and Problem Solving.
With outside the box thinking, he can approach problems in new, innovative ways.
His broad range of technical skills enables him to tackle any issue that may arise.

You can reach the author of this post at sonny.nguyen@bdcsoft.com or on Twitter at @nguyenSONNY

LEAVE A COMENT