Standardizing TextChanged Event - lucyberryhub/WPF.Tutorial GitHub Wiki

๐Ÿ’Standardizing TextChanged Event Handling ๐Ÿ“

๐Ÿ‡ Before the Improvement: Messy, Repetitive Code

In our initial approach, we repeatedly wrote similar logic for handling TextBox input validation and updating related data. This led to messy, repetitive, and hard-to-maintain code. Letโ€™s review the before scenario first.

Before Code: ๐Ÿฅด

private void BerryTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    try
    {
        if (sender is TextBox textBox)
        {
            string inputText = textBox.Text.Trim();

            // Validate the input
            if (inputText == "Cherry" || inputText == "Berry")
            {
                // Reset background color
                textBox.ClearValue(TextBox.BackgroundProperty);

                // Update the berry list
                var items = BerryDataGrid.ItemsSource as List<BerryModel>;
                if (items != null)
                {
                    BerryViewModel.UpdateBerryList(items);
                }
            }
            else
            {
                // Invalid input: Turn the background pink!
                textBox.Background = System.Windows.Media.Brushes.LightPink;
                MessageBox.Show("Only 'Berry' or 'Cherry' are valid inputs!", "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Oops! An error occurred: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

What's Wrong Here? ๐Ÿค”

  1. Repetition: Same logic for input validation and data updates is repeated in multiple handlers.
  2. Hard to Maintain: Any change to validation or update logic must be applied in multiple places.
  3. Error-Prone: Copy-pasting similar code increases the chance of mistakes.

๐Ÿ“ After the Improvement: Juicy, Reusable Code

Key Change: Centralize Logic into a Helper Method

By using a helper class, we can centralize the validation and update logic. This way, the code is clean, reusable, and easy to maintain. Now, letโ€™s explain this step-by-step.


Step 1: Create the BerryHelper Class

This class handles:

  1. Validation: Checks whether the input is valid.
  2. Data Update: Dynamically invokes an action (updateAction) to update the data.

Hereโ€™s the code:

public static class BerryHelper
{
    /// <summary>
    /// Validates the input and updates the berry list if valid.
    /// </summary>
    /// <typeparam name="T">The type of the model associated with the DataGrid.</typeparam>
    /// <param name="textBox">The TextBox being validated.</param>
    /// <param name="itemsSource">The collection bound to the DataGrid.</param>
    /// <param name="updateAction">The action to perform when updating the data source.</param>
    public static void HandleTextChanged<T>(TextBox textBox, IEnumerable<T> itemsSource, Action<IEnumerable<T>> updateAction)
    {
        if (textBox == null) return;

        string inputText = textBox.Text.Trim();

        // Validate input
        if (inputText == "Berry" || inputText == "Cherry")
        {
            // Reset background color to default
            textBox.ClearValue(TextBox.BackgroundProperty);

            if (itemsSource != null)
            {
                // Update the data source
                updateAction?.Invoke(itemsSource);
            }
        }
        else
        {
            // Highlight invalid input
            textBox.Background = System.Windows.Media.Brushes.LightPink;
        }
    }
}

๐Ÿ’ Detailed Explanation of updateAction (The Star of the Show!)

What is Action<IEnumerable<T>>?

  • Action<T>: Represents a delegate that takes one parameter and returns no value.
  • Action<IEnumerable<T>>: A specialized delegate where the parameter is an IEnumerable<T> (a collection of items like a list).

Why Use updateAction?

Using an Action<IEnumerable<T>> allows us to decouple the logic of what happens after the data is validated. This way:

  1. Flexibility: You can pass any custom logic for updating the data source.
  2. Reusability: The same method can handle updates for different types of models or data.

Setting updateAction

Letโ€™s dissect the code block:

if (itemsSource != null)
{
    // Update the data source
    updateAction?.Invoke(itemsSource);
}
  1. itemsSource != null:

    • This checks if the ItemsSource (i.e., the bound collection of the DataGrid) exists and is not null.
    • Without this, you risk invoking an update on a null object, which will throw an exception.
  2. updateAction?.Invoke(itemsSource):

    • The updateAction is the delegate passed to the helper method.
    • .Invoke(itemsSource) executes the custom update logic, passing the itemsSource as the parameter.
  3. Why the ?. Operator?

    • This ensures that if updateAction is null (not provided), the method will skip this step without throwing a NullReferenceException.

Example: Passing the updateAction

Hereโ€™s how you might call the HandleTextChanged method:

private void BerryTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    try
    {
        if (sender is TextBox textBox)
        {
            // Retrieve the data source
            var items = BerryDataGrid.ItemsSource as List<BerryModel>;

            // Pass the update logic as a lambda
            BerryHelper.HandleTextChanged(textBox, items, (updatedItems) =>
            {
                // Serialize and save the data to a JSON file
                string json = Newtonsoft.Json.JsonConvert.SerializeObject(updatedItems, Newtonsoft.Json.Formatting.Indented);
                File.WriteAllText("berries.json", json);
            });
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Oops! An error occurred: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

๐Ÿซ Benefits of updateAction

  1. Flexibility:

    • You can define different update logic for different scenarios or models.
    • For example, updating JSON, saving to a database, or logging changes can all be passed as updateAction.
  2. Readability:

    • The main handler becomes cleaner because the core logic is encapsulated in the helper.
  3. Error Handling:

    • Centralized logic makes debugging easier if something goes wrong.

๐Ÿ‡ Before vs After: A Berry-Tastic Comparison

Before Code (Messy):

if (inputText == "Cherry" || inputText == "Berry")
{
    textBox.ClearValue(TextBox.BackgroundProperty);

    var items = BerryDataGrid.ItemsSource as List<BerryModel>;
    if (items != null)
    {
        BerryViewModel.UpdateBerryList(items);
    }
}
else
{
    textBox.Background = System.Windows.Media.Brushes.LightPink;
}

After Code (Reusable):

BerryHelper.HandleTextChanged(textBox, items, (updatedItems) =>
{
    string json = Newtonsoft.Json.JsonConvert.SerializeObject(updatedItems, Newtonsoft.Json.Formatting.Indented);
    File.WriteAllText("berries.json", json);
});

๐Ÿ“ Conclusion

By using a centralized helper with Action<IEnumerable<T>>, weโ€™ve:

  1. Eliminated repetitive code.
  2. Made input handling flexible and reusable.
  3. Ensured better maintainability and scalability.

Now your TextChanged handlers will be as juicy and fresh as your favorite berries! ๐Ÿ’๐Ÿ“

โš ๏ธ **GitHub.com Fallback** โš ๏ธ