# FTRTokenEdit

## Overview

`FTRTokenEdit` is a Windows Forms token/tag input control for entering and managing multiple text values.

Each value is displayed as a separate token chip. Users can add, remove, and edit tokens directly in the control, while the application can also manage tokens programmatically.

The control supports:

* Adding tokens by pressing **Enter**
* Adding multiple tokens by pasting comma- or line-separated values
* Removing the last token with **Backspace** when the input is empty
* Removing individual tokens using the remove button
* Editing an existing token by double-clicking it
* Maximum token limits
* Duplicate-token prevention
* Regular-expression validation
* Read-only mode
* Custom token and border colors
* Custom control and token corner radius
* Token appearance animation
* Automatic scrolling when many tokens are present
* FTR Controls theme integration

**Namespace:** `FTRControls`

**Base class:** `FTRControls.BaseClasses.FTRBaseUserControl`

**Control type:** Windows Forms control

---

# Getting Started

Add the `FTRTokenEdit` control to your Windows Forms application either through the Visual Studio Toolbox or programmatically.

### Basic example

```csharp
var tags = new FTRTokenEdit
{
    Placeholder = "Enter a tag and press Enter",
    MaxTokens = 10,
    AllowDuplicates = false
};

tags.TokenAdded += (_, token) =>
{
    Console.WriteLine($"Added: {token}");
};

tags.TokenRemoved += (_, token) =>
{
    Console.WriteLine($"Removed: {token}");
};
```

The control can then be added to a form or another Windows Forms container:

```csharp
Controls.Add(tags);
```

---

# Adding Tokens

There are two ways to add tokens.

## Add a token from the user interface

Type a value into the input area and press **Enter**.

For example:

```text
customer
```

Pressing Enter creates a token:

```text
[ customer ]
```

The input area is then ready for another value.

## Add a token programmatically

Use `AddToken(string)`:

```csharp
tags.AddToken("customer");
```

Leading and trailing whitespace is removed automatically.

For example:

```csharp
tags.AddToken("  customer  ");
```

creates the token:

```text
customer
```

Empty or whitespace-only values are ignored.

---

# Adding Multiple Tokens by Paste

The control supports pasting multiple values at once.

When multiple values are found in the clipboard, they are split using:

* Comma `,`
* Line feed `\n`
* Carriage return `\r`

For example, the following clipboard content:

```text
customer,admin,manager
```

creates three tokens:

```text
customer
admin
manager
```

The following also creates three tokens:

```text
customer
admin
manager
```

Each value is trimmed before it is added.

Each pasted value is processed independently using the same duplicate, maximum-count, and validation rules as a normally added token.

---

# Removing Tokens

## Remove a token using the UI

Click the remove (`X`) button displayed on a token.

## Remove the last token using Backspace

If the input area is empty, pressing **Backspace** removes the last token.

For example:

```text
[customer] [admin] [manager] |
```

Pressing Backspace removes:

```text
manager
```

## Remove a token programmatically

Use:

```csharp
bool removed = tags.RemoveToken("admin");
```

The comparison is case-insensitive.

For example:

```csharp
tags.RemoveToken("ADMIN");
```

can remove the token:

```text
admin
```

The method returns:

* `true` if a matching token was found and removed
* `false` if no matching token was found or the operation could not be performed

---

# Editing Tokens

Users can edit an existing token by **double-clicking the token**.

The selected token is removed from the token list and its value is placed back into the input field.

The user can then modify the value and press **Enter** to add it again.

For example:

```text
[customer] [admin]
```

Double-clicking `admin` changes the input area to:

```text
admin
```

The user can change it to:

```text
administrator
```

and press Enter.

The edited value is processed as a new token and is therefore subject to the normal validation, duplicate, and maximum-token rules.

---

# Token Limits

## MaxTokens

`MaxTokens` specifies the maximum number of tokens allowed.

Default:

```text
10
```

Example:

```csharp
tags.MaxTokens = 20;
```

The minimum supported value is `1`.

If the maximum number of tokens has already been reached, additional tokens cannot be added.

The `TokenAddFailed` event is raised with:

```csharp
TokenAddFailureReason.MaxTokensReached
```

---

# Duplicate Tokens

## AllowDuplicates

`AllowDuplicates` determines whether the same token can appear more than once.

Default:

```csharp
false
```

Example:

```csharp
tags.AllowDuplicates = false;
```

When duplicates are disabled, token comparison is **case-insensitive**.

Therefore:

```text
Customer
customer
CUSTOMER
```

are considered the same token.

If a duplicate is rejected, `TokenAddFailed` is raised with:

```csharp
TokenAddFailureReason.Duplicate
```

To allow duplicate values:

```csharp
tags.AllowDuplicates = true;
```

---

# Regular Expression Validation

## ValidationRegex

`ValidationRegex` allows the application to restrict which token values can be entered.

The expression uses the standard .NET regular-expression engine.

Example:

```csharp
tags.ValidationRegex = @"^[a-z0-9]+$";
```

With this expression, values such as:

```text
customer123
admin
user01
```

are accepted.

Values containing characters outside the expression are rejected.

For example:

```text
customer-name
customer name
```

would not match the example expression.

When a token does not match the configured expression, the control raises:

```csharp
TokenAddFailureReason.ValidationFailed
```

If the configured regular expression is invalid, the control raises:

```csharp
TokenAddFailureReason.InvalidRegex
```

If no validation is required, leave `ValidationRegex` empty:

```csharp
tags.ValidationRegex = "";
```

---

# Handling Token Addition Results

The `TokenAdded` event is raised after a token has been successfully added.

Example:

```csharp
tags.TokenAdded += (_, token) =>
{
    Console.WriteLine($"Token added: {token}");
};
```

The event provides the actual token text after trimming.

For example, adding:

```text
  customer
```

raises the event with:

```text
customer
```

---

# Handling Removed Tokens

The `TokenRemoved` event is raised when a token is removed.

Example:

```csharp
tags.TokenRemoved += (_, token) =>
{
    Console.WriteLine($"Token removed: {token}");
};
```

This event is raised when a token is removed through the UI or through the public removal APIs.

---

# Handling Failed Token Additions

Use `TokenAddFailed` to determine why a token could not be added.

Example:

```csharp
tags.TokenAddFailed += (_, reason) =>
{
    switch (reason)
    {
        case TokenAddFailureReason.Duplicate:
            MessageBox.Show("This token already exists.");
            break;

        case TokenAddFailureReason.MaxTokensReached:
            MessageBox.Show("The maximum number of tokens has been reached.");
            break;

        case TokenAddFailureReason.ValidationFailed:
            MessageBox.Show("The token format is not valid.");
            break;

        case TokenAddFailureReason.InvalidRegex:
            MessageBox.Show("The configured validation expression is invalid.");
            break;
    }
};
```

The available failure reasons are:

| Reason             | Description                                           |
| ------------------ | ----------------------------------------------------- |
| `Duplicate`        | The token already exists and duplicates are disabled. |
| `MaxTokensReached` | The maximum token count has been reached.             |
| `ValidationFailed` | The token does not match `ValidationRegex`.           |
| `InvalidRegex`     | The configured validation expression is invalid.      |

---

# Reading Current Tokens

Use the `Tokens` property to retrieve the current token values.

```csharp
List<string> currentTokens = tags.Tokens;
```

Example:

```csharp
foreach (string token in tags.Tokens)
{
    Console.WriteLine(token);
}
```

The returned collection represents the tokens currently displayed by the control.

---

# Checking Whether a Token Exists

Use `ContainsToken(string)`:

```csharp
bool exists = tags.ContainsToken("customer");
```

The comparison is case-insensitive.

For example:

```csharp
tags.AddToken("Customer");

bool exists = tags.ContainsToken("customer");
```

returns:

```text
true
```

Whitespace surrounding the value is ignored when checking for a token.

---

# Clearing All Tokens

Use:

```csharp
tags.ClearTokens();
```

This removes all existing tokens.

`TokenRemoved` is raised for each token that is removed.

Example:

```csharp
tags.ClearTokens();
```

---

# Replacing the Token List

Use `SetTokens(IEnumerable<string>)` when the application needs to replace the current token collection.

Example:

```csharp
tags.SetTokens(new[]
{
    "customer",
    "admin",
    "manager"
});
```

The existing tokens are removed first, and the supplied values are then processed using the control's normal token rules.

This means the following settings continue to apply:

* `MaxTokens`
* `AllowDuplicates`
* `ValidationRegex`

If more values are supplied than `MaxTokens` allows, only values that can successfully be added within the configured limit are retained.

If `null` is passed, the current tokens are cleared.

Example:

```csharp
tags.SetTokens(null);
```

---

# Read-Only Mode

Use `IsReadOnly` when tokens should be displayed without allowing the user to modify them.

```csharp
tags.IsReadOnly = true;
```

When read-only mode is enabled:

* The token input area is hidden.
* New tokens cannot be added.
* Existing tokens cannot be removed.
* Existing tokens cannot be edited.

To enable editing again:

```csharp
tags.IsReadOnly = false;
```

This is useful when the control is used to display a previously saved token collection without allowing modification.

---

# Placeholder

Use `Placeholder` to display a hint in the input area.

Example:

```csharp
tags.Placeholder = "Enter tags and press Enter";
```

The placeholder is displayed only while the input area is available for editing.

---

# Appearance

The control provides several appearance properties.

## TokenBackColor

Controls the background color of token chips.

```csharp
tags.TokenBackColor = Color.SteelBlue;
```

## TokenForeColor

Controls the text color of token chips.

```csharp
tags.TokenForeColor = Color.White;
```

## BorderColor

Controls the border color of the control.

```csharp
tags.BorderColor = Color.Gray;
```

Example:

```csharp
var tags = new FTRTokenEdit
{
    TokenBackColor = Color.SteelBlue,
    TokenForeColor = Color.White,
    BorderColor = Color.Gray
};
```

---

# Corner Radius

## BorderRadius

`BorderRadius` controls the corner radius of the main control.

```csharp
tags.BorderRadius = 8;
```

A value of `0` produces square corners.

This property is a **Premium Feature** and requires the appropriate FTR Controls license.

## TokenRadius

`TokenRadius` controls the corner radius of individual token chips.

```csharp
tags.TokenRadius = 12;
```

A value of `0` produces square token corners.

This property is a **Premium Feature** and requires the appropriate FTR Controls license.

---

# Token Animation

The control supports animation when token chips are displayed.

## EnableAnimation

Enable or disable token appearance animation.

```csharp
tags.EnableAnimation = true;
```

Default:

```text
true
```

## AnimationInterval

Controls the timer interval between animation frames, in milliseconds.

```csharp
tags.AnimationInterval = 15;
```

Lower values generally produce more frequent animation updates.

The value must be at least `1`.

## AnimationStep

Controls how much the token width increases during each animation frame.

```csharp
tags.AnimationStep = 25;
```

Higher values make the animation complete more quickly.

The value must be at least `1`.

> **Licensing note:** The current assembly implementation performs license checks for the underlying token animation settings. Verify the intended licensing behavior for `EnableAnimation`, `AnimationInterval`, and `AnimationStep` before publishing the final customer documentation.

---

# Themes

`FTRTokenEdit` integrates with the FTR Controls theme system.

When `ApplyTheme()` is called, the control applies the currently selected global FTR theme to:

* Control background
* Token background
* Token text
* Border
* Input area
* Scrollbar

Existing tokens are updated as well.

Example:

```csharp
tags.ApplyTheme();
```

Applications using the FTR Controls theme system can therefore use the same theme across the application without manually assigning every color.

---

# Scrolling

When the number of tokens exceeds the visible area, the control automatically provides a thin vertical scrollbar.

Users can:

* Drag the scrollbar thumb
* Click within the scrollbar
* Use the mouse wheel over the control

The scrollbar is shown automatically only when the token content exceeds the available display area.

No additional application code is required to enable this behavior.

---

# Complete Example

The following example demonstrates a typical customer integration:

```csharp
using System;
using System.Drawing;
using System.Windows.Forms;
using FTRControls;

public class TagForm : Form
{
    private readonly FTRTokenEdit tags;

    public TagForm()
    {
        tags = new FTRTokenEdit
        {
            Dock = DockStyle.Top,
            Height = 80,

            Placeholder = "Enter tags and press Enter",

            MaxTokens = 10,
            AllowDuplicates = false,

            ValidationRegex = @"^[a-zA-Z0-9_-]+$",

            TokenBackColor = Color.SteelBlue,
            TokenForeColor = Color.White,
            BorderColor = Color.Gray,

            EnableAnimation = true,
            AnimationInterval = 15,
            AnimationStep = 25
        };

        tags.TokenAdded += Tags_TokenAdded;
        tags.TokenRemoved += Tags_TokenRemoved;
        tags.TokenAddFailed += Tags_TokenAddFailed;

        Controls.Add(tags);
    }

    private void Tags_TokenAdded(object sender, string token)
    {
        Console.WriteLine($"Added: {token}");
    }

    private void Tags_TokenRemoved(object sender, string token)
    {
        Console.WriteLine($"Removed: {token}");
    }

    private void Tags_TokenAddFailed(
        object sender,
        TokenAddFailureReason reason)
    {
        switch (reason)
        {
            case TokenAddFailureReason.Duplicate:
                MessageBox.Show("The token already exists.");
                break;

            case TokenAddFailureReason.MaxTokensReached:
                MessageBox.Show("Maximum number of tokens reached.");
                break;

            case TokenAddFailureReason.ValidationFailed:
                MessageBox.Show("The token format is invalid.");
                break;

            case TokenAddFailureReason.InvalidRegex:
                MessageBox.Show("The configured token validation expression is invalid.");
                break;
        }
    }
}
```

---

# Public API Reference

## Properties

| Property            | Type           |       Default | Description                                                                            |
| ------------------- | -------------- | ------------: | -------------------------------------------------------------------------------------- |
| `AllowDuplicates`   | `bool`         |       `false` | Determines whether duplicate token values are allowed. Comparison is case-insensitive. |
| `MaxTokens`         | `int`          |          `10` | Maximum number of tokens. Minimum value is `1`.                                        |
| `ValidationRegex`   | `string`       |          `""` | .NET regular expression used to validate token values.                                 |
| `Placeholder`       | `string`       |          `""` | Text displayed as an input hint.                                                       |
| `IsReadOnly`        | `bool`         |       `false` | Disables token editing, removal, and addition when enabled.                            |
| `EnableAnimation`   | `bool`         |        `true` | Enables or disables token appearance animation.                                        |
| `AnimationInterval` | `int`          |          `15` | Animation frame interval in milliseconds.                                              |
| `AnimationStep`     | `int`          |          `25` | Width increment applied during each animation frame.                                   |
| `TokenBackColor`    | `Color`        | Theme/default | Token chip background color.                                                           |
| `TokenForeColor`    | `Color`        | Theme/default | Token chip text color.                                                                 |
| `BorderColor`       | `Color`        |      `Silver` | Control border color.                                                                  |
| `BorderRadius`      | `int`          |           `8` | Main control corner radius. Premium feature.                                           |
| `TokenRadius`       | `int`          |          `12` | Token chip corner radius. Premium feature.                                             |
| `Tokens`            | `List<string>` |             — | Current token values.                                                                  |

---

# Events

| Event            | Arguments               | Description                                 |
| ---------------- | ----------------------- | ------------------------------------------- |
| `TokenAdded`     | `string`                | Raised after a token is successfully added. |
| `TokenRemoved`   | `string`                | Raised after a token is removed.            |
| `TokenAddFailed` | `TokenAddFailureReason` | Raised when a token cannot be added.        |

---

# Methods

| Method                                  | Description                                               |
| --------------------------------------- | --------------------------------------------------------- |
| `AddToken(string text)`                 | Attempts to add a token programmatically.                 |
| `RemoveToken(string token)`             | Removes a token by text and returns whether it was found. |
| `ClearTokens()`                         | Removes all current tokens.                               |
| `ContainsToken(string token)`           | Checks whether a token exists.                            |
| `SetTokens(IEnumerable<string> tokens)` | Replaces the current token collection.                    |
| `ApplyTheme()`                          | Applies the currently selected FTR Controls theme.        |

---

# TokenAddFailureReason

```csharp
public enum TokenAddFailureReason
{
    Duplicate,
    MaxTokensReached,
    ValidationFailed,
    InvalidRegex
}
```

| Value              | Meaning                                                                 |
| ------------------ | ----------------------------------------------------------------------- |
| `Duplicate`        | A token with the same value already exists and duplicates are disabled. |
| `MaxTokensReached` | The configured maximum token count has already been reached.            |
| `ValidationFailed` | The token does not match `ValidationRegex`.                             |
| `InvalidRegex`     | `ValidationRegex` contains an invalid regular expression.               |

---

# Recommended Usage Pattern

For most applications, the following configuration is a good starting point:

```csharp
var tags = new FTRTokenEdit
{
    Placeholder = "Enter a value and press Enter",
    MaxTokens = 10,
    AllowDuplicates = false
};
```

If the application requires validation:

```csharp
tags.ValidationRegex = @"^[a-zA-Z0-9_-]+$";
```

If the application needs to react to user changes:

```csharp
tags.TokenAdded += (_, token) =>
{
    // Save or process the new token.
};

tags.TokenRemoved += (_, token) =>
{
    // Save or process the removed token.
};

tags.TokenAddFailed += (_, reason) =>
{
    // Show an appropriate validation or limit message.
};
```

---

# Important Behavior Notes

* Token values are trimmed before they are stored.
* Empty and whitespace-only values are ignored.
* Duplicate checking is case-insensitive.
* `ContainsToken` is case-insensitive.
* `RemoveToken` is case-insensitive.
* Pasted values are split by commas and line breaks.
* Each pasted value is processed independently.
* `MaxTokens` limits the number of tokens that can be added.
* `IsReadOnly` disables all user editing operations.
* Double-clicking a token moves its value back to the input area for editing.
* `TokenAddFailed` does not throw an exception for normal validation failures; the failure reason is reported through the event.
* An invalid `ValidationRegex` is reported through `TokenAddFailed`.
* The control automatically manages scrolling when the token content exceeds the visible area.

---

# Licensing

The following properties are explicitly implemented as Premium Features:

* `BorderRadius`
* `TokenRadius`

These properties require an appropriate FTR Controls license.

The current implementation also contains license checks in the underlying token animation component. Therefore, the licensing status of:

* `EnableAnimation`
* `AnimationInterval`
* `AnimationStep`

should be confirmed against the final distributed assembly and licensing policy before these properties are marketed as non-premium features.

---

# Summary

`FTRTokenEdit` is intended to provide a simple tag/token editing experience while giving the application full programmatic control over the token collection.

A typical integration only requires:

1. Add `FTRTokenEdit` to the form.
2. Configure `Placeholder`, `MaxTokens`, `AllowDuplicates`, and optionally `ValidationRegex`.
3. Subscribe to `TokenAdded`, `TokenRemoved`, and `TokenAddFailed` when application logic needs to react to changes.
4. Use `Tokens`, `AddToken`, `RemoveToken`, `ClearTokens`, `ContainsToken`, and `SetTokens` for programmatic management.
5. Configure appearance and premium properties as required.
