Journey Man
Journey Man

Reputation: 143

Selecting A Series Of Text Boxes

I have a series(10) of selectable TextBoxes. I need to be able to click on one of them and select all the text boxes on which the mouse is moved until the click is released.

I used the following code but I am unable to hit the MouseMove on the other TextBoxes. It always hits the TextBox on which the Click was made.

class SelectableTextBox: TextBox
{
    public Boolean IsSelected { get; protected set; }

    public void select(Boolean value)
    {
        this.IsSelected = value;
        if (value)
        {
            this.Background = System.Windows.Media.Brushes.Aqua;
        }
        else
        {
            this.Background = System.Windows.Media.Brushes.White;
        }
    }
}

private void onPreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    SelectableTextBox textBox = (SelectableTextBox)sender;

    this.SelectionStartedRight = !textBox.IsSelected;
    textBox.select(!textBox.IsSelected);
}

private void onPreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
    SelectableTextBox textBox = (SelectableTextBox)sender;

    if (this.SelectionStartedRight)
    {
        textBox.select(true);
    }
}

private void onPreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    SelectableTextBox textBox = (SelectableTextBox)sender;

    this.SelectionStartedRight = false;
}

Upvotes: 0

Views: 87

Answers (1)

B.K.
B.K.

Reputation: 10162

Try using MouseEnter event instead of MouseMove. Attach MouseEnter to your selectable textboxes. That would ensure that only they trigger the desired event handler and its code.

If you decide to stay with the global handler, be careful when you convert sender to a specific control type. You need to account for those times when it's not the "expected" control:

SelectableTextBox textBox = sender as SelectableTextBox;

if (textBox != null)
{
    // The rest of the code here...
}

select is a Linq keyword, so you might want to rename that method to avoid any conflicts down the road. While I don't think it'd be causing any issues, I would change it.

You don't have to follow this convention, but in C# its customary to use an uppercase for the first letter of a method. Also note that the default access modifier for a class is internal... just want to make sure you're aware of that, as you continue your development.

Your last method, onPreviewMouseLeftButtonUp(...) has the following code in it:

SelectableTextBox textBox = (SelectableTextBox)sender;

Not only is it unsafe, as I've described above, but it also does absolutely nothing.

Lastly... and this is just me, I would probably move the code that handles changing the state of a selectable textbox (selected or not selected) into its class, since it belongs there. Why should anything else be in charge of how it handles its state. Keep things where they belong and you'll have a much easier time testing, debugging and maintaining your code. Don't fall into the "I'll refactor it later" trap... it'll rarely happen.

Here's my crude example. I let the class handle its MouseEnter event and simply check if the Mouse.LeftButton is down at that time. You would have to expand on it, but it should be a solid start:

Made some edits per OP's requests in the comments.

Preview:

enter image description here

XAML:

<Window x:Class="SelectableTextBoxes.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:SelectableTextBoxes"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <local:SelectableTextBox Height="20" Width="100" Margin="10"/>
        <local:SelectableTextBox Height="20" Width="100" Margin="10"/>
        <local:SelectableTextBox Height="20" Width="100" Margin="10"/>
        <local:SelectableTextBox Height="20" Width="100" Margin="10"/>
        <local:SelectableTextBox Height="20" Width="100" Margin="10"/>
    </StackPanel>
</Window>

C# (Pardon me for putting the SelectableTextBox into the same file...):

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace SelectableTextBoxes
{
    // Move this class into its own file (it's here for prototyping).
    public class SelectableTextBox : TextBox
    {
        private bool _isSelected;
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if (_isSelected != value)
                {
                    _isSelected = value;
                    // If the value changes, sets new color.
                    SetSelectionColor();
                }
            }
        }

        public SelectableTextBox()
        {
            // For handling an initial click if it happens in the textbox.
            PreviewMouseDown += SelectableTextBox_PreviewMouseDown;
            // For handling selection when mouse enters the textbox
            // and left mouse button is down.
            MouseEnter += SelectableTextBox_MouseEnter;    
            // To handle mouse capture (release it).     
            GotMouseCapture += SelectableTextBox_GotMouseCapture;
        }

        // Handles the mouse down event within the textbox.
        void SelectableTextBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!IsSelected)
            {
                IsSelected = true;
            }

            // If one of the Shift keys is down, return, since
            // we don't want to deselect others.
            if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
            {
                return;
            }

            // This part makes a poor assumption that the parent is
            // always going to be a Panel... expand on this code to
            // cover other types that may contain more than one element.
            var parent = VisualTreeHelper.GetParent(this) as Panel;

            if (parent != null)
            {
                foreach (var child in parent.Children)
                {
                    // If a child is not of a correct type, it'll be null.
                    var tbx = child as SelectableTextBox;
                    // This is where we check to see if it's null or this instance.
                    if (tbx != null && tbx != this)
                    {
                        tbx.IsSelected = false;
                    }
                }
            }
        }

        // When textbox receives focus, this event fires... we need to release
        // the mouse to continue selection.
        void SelectableTextBox_GotMouseCapture(object sender, MouseEventArgs e)
        {
            ReleaseMouseCapture();
        }

        // Sets selection state to true if the left mouse button is 
        // down while entering.
        void SelectableTextBox_MouseEnter(object sender, MouseEventArgs e)
        {
            if (Mouse.LeftButton == MouseButtonState.Pressed)
            {
                IsSelected = true;
            }
        }

        // Sets the background color based on selection state.
        private void SetSelectionColor()
        {
            if (IsSelected)
            {
                Background = Brushes.LightCyan;
            }
            else
            {
                Background = Brushes.White;
            }
        }
    }

    // Window code... should be on its own, but I placed the two
    // classes together while prototyping.
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Upvotes: 4

Related Questions