Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// ***********************************************************************
// Copyright (c) Charlie Poole and TestCentric contributors.
// Licensed under the MIT License. See LICENSE file in root directory.
// ***********************************************************************

using System.Drawing;
using System.Windows.Forms;
using System;

namespace TestCentric.Gui.Controls
{
/// <summary>
/// This class is required to stretch a ToolStripTextBox control within a ToolStrip to fill the available space and to resize when the control resizes.
/// The implementation is from the Microsoft Windows Forms documentation, but simplified to the current use case.
/// "How to: Stretch a ToolStripTextBox to Fill the Remaining Width of a ToolStrip"
/// https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/stretch-a-toolstriptextbox-to-fill-the-remaining-width-of-a-toolstrip-wf?view=netframeworkdesktop-4.8
/// </summary>
internal class StretchToolStripTextBox : ToolStripTextBox
{
public override Size GetPreferredSize(Size constrainingSize)
{
// Get width of the owning ToolStrip
int textBoxMargin = 2;
Int32 width = Owner.DisplayRectangle.Width - textBoxMargin;

// If the available width is less than the default width, use the default width
if (width < DefaultSize.Width) width = DefaultSize.Width;

// Retrieve the preferred size from the base class, but change the width to the calculated width.
Size size = base.GetPreferredSize(constrainingSize);
size.Width = width;
return size;
}
}
}
135 changes: 135 additions & 0 deletions src/TestCentric/testcentric.gui/Elements/TextBoxElement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// ***********************************************************************
// Copyright (c) Charlie Poole and TestCentric contributors.
// Licensed under the MIT License. See LICENSE file in root directory.
// ***********************************************************************


using System;
using System.Windows.Forms;

namespace TestCentric.Gui.Elements
{
/// <summary>
/// This class implements the ISelection interface for a TextBox control. It provides this additional functionality:
/// - show a PlaceHoder text if there's no text input
/// - Invoke the SelectionChanged event as soon as no further input is made within a short period of time.
/// </summary>
public class TextBoxElement : ISelection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISelection seems like an odd interface to use here, since there are no choices from which to select. Do we need an IChanged interface? Also, why not inherit from ToolStripElement, the base class for such elements?

{
private Timer _typingTimer;

public event CommandHandler SelectionChanged;

public TextBoxElement(Control textBox, string placeHolderText)
{
TextBox = textBox;
PlaceHolderText = placeHolderText;
TextBox.TextChanged += OnTextChanged;

TextBox.LostFocus += OnTextBoxLostFocus;
TextBox.GotFocus += OnTextBoxGotFocus;

// Call LostFocus to set initial text and color
OnTextBoxLostFocus(null, EventArgs.Empty);
}

public string SelectedItem
{
get => TextBox.Text;
set => TextBox.Text = value;
}

public int SelectedIndex
{
get => 0;
set => throw new NotImplementedException();
}

public bool Enabled
{
get => TextBox.Enabled;
set => TextBox.Enabled = value;
}

public bool Visible
{
get => TextBox.Visible;
set => TextBox.Visible = value;
}

public string Text
{
get => TextBox.Text;
set => TextBox.Text = value;
}

private string PlaceHolderText { get; set; }

private Control TextBox { get; }

private bool IsPlaceHolderTextShown { get; set; }

public void InvokeIfRequired(MethodInvoker _delegate)
{
throw new NotImplementedException();
}

public void Refresh()
{
throw new NotImplementedException();
}

private void OnTextBoxGotFocus(object sender, EventArgs e)
{
// If the PlaceHolderText is shown, replace it with an empty text
if (IsPlaceHolderTextShown)
{
TextBox.Text = "";
TextBox.ForeColor = System.Drawing.Color.Black;
IsPlaceHolderTextShown = false;
}
}

private void OnTextBoxLostFocus(object sender, EventArgs e)
{
// If there's no text input, show the PlaceHolderText instead
string searchText = TextBox.Text;
if (string.IsNullOrEmpty(searchText) && !string.IsNullOrEmpty(PlaceHolderText))
{
IsPlaceHolderTextShown = true;
TextBox.Text = PlaceHolderText;
TextBox.ForeColor = System.Drawing.Color.LightGray;
}
}

private void OnTextChanged(object sender, EventArgs e)
{
if (IsPlaceHolderTextShown)
return;

if (_typingTimer == null)
{
_typingTimer = new Timer();
_typingTimer.Interval = 600;
_typingTimer.Tick += TypingTimerTimeout;
}

_typingTimer.Stop();
_typingTimer.Start();
}

private void TypingTimerTimeout(object sender, EventArgs e)
{
var timer = sender as Timer;
if (timer == null)
return;

// The timer must be stopped!
timer.Stop();
if (SelectionChanged != null)
SelectionChanged();

TextBox.Focus();
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the timeout strictly necessary? I'm fairly accustomed to controls that do nothing until I tab away or otherwise change focus.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This 'timeout' feature is definitely just a gimmick and not absolutely necessary.
When I was working with the text filters, I found it helpful that the results were displayed quickly without additional key stroke. And that I can continue typing immediately if I wasn't satisfied with the result. That's why I had the idea of introducing this timer. Of course, this is not a brand new idea, but some other programs use this in their search as well. So from my point of view it's beneficial for the user and I prefer to keept this feature. But of course we can remove it, if users are annoying by this feature...

Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ private void WireUpEvents()
_model.TestCentricTestFilter.OutcomeFilter = filter;
};

_view.TextFilter.SelectionChanged += () =>
{
var text = _view.TextFilter.Text;
_model.TestCentricTestFilter.TextFilter = text;
};

// Node selected in tree
//_treeView.SelectedNodesChanged += (nodes) =>
//{
Expand Down
2 changes: 2 additions & 0 deletions src/TestCentric/testcentric.gui/Views/ITestTreeView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public interface ITestTreeView : IView
// Test Filter related properties / methods
IMultiSelection OutcomeFilter { get; }

ISelection TextFilter { get; }

void SetTestFilterVisibility(bool visible);

// Tree-related Methods
Expand Down
23 changes: 23 additions & 0 deletions src/TestCentric/testcentric.gui/Views/TestTreeView.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/TestCentric/testcentric.gui/Views/TestTreeView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public TestTreeView()
TestPropertiesCommand = new CommandMenuElement(testPropertiesMenuItem);
ViewAsXmlCommand = new CommandMenuElement(viewAsXmlMenuItem);
OutcomeFilter = new MultiCheckedToolStripButtonGroup(new[] { filterOutcomePassedButton, filterOutcomeFailedButton, filterOutcomeWarningButton, filterOutcomeNotRunButton });
TextFilter = new TextBoxElement(filterTextBox.Control, "Filter...");
TreeView = treeView;

// NOTE: We use MouseDown here rather than MouseUp because
Expand Down Expand Up @@ -127,6 +128,8 @@ public bool CheckBoxes

public IMultiSelection OutcomeFilter { get; private set; }

public ISelection TextFilter { get; private set; }

public TreeNode ContextNode { get; private set; }
public ContextMenuStrip TreeContextMenu => TreeView.ContextMenuStrip;

Expand Down Expand Up @@ -205,6 +208,7 @@ public void InvokeIfRequired(MethodInvoker _delegate)
public void SetTestFilterVisibility(bool isVisible)
{
filterToolStrip.Visible = isVisible;
filterTextToolStrip.Visible = isVisible;
}

public void LoadAlternateImages(string imageSet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,19 @@ public void OutcomeFilterChanged_ApplyFilter()
_model.TestCentricTestFilter.Received().OutcomeFilter = selectedItems;
}

[Test]
public void TextFilterChanged_ApplyFilter()
{
// 1. Arrange
_view.TextFilter.Text.Returns("TestA");

// 2. Act
_view.TextFilter.SelectionChanged += Raise.Event<CommandHandler>();

// 3. Assert
_model.TestCentricTestFilter.Received().TextFilter = "TestA";
}

// TODO: Version 1 Test - Make it work if needed.
//[Test]
//public void WhenContextNodeIsNotNull_RunCommandExecutesThatTest()
Expand Down