borland-c++rad-studio

TEdit - ignore paste operation if clipboard contains specific string


I want to check clipboard string before paste happens into the TEdit control (using both Ctrl+V and context menu 'Paste'). If clipboard contains a specific string in it, then TEdit control should not paste that string. Clipboard text should remain as is, should not get cleared.


Solution

  • For CTRL-V you could create an OnKeyPress event handler to validate the text in the clipboard before it's pasted.

    I made a combination of checking one single character against a table of valid characters and also checking the complete paste against invalid strings.

    Note: The invalid strings may still be typed in manually since this is checking for invalid strings in the pasted text only. If you need to check so that a user doesn't type in bad strings one character at a time, call valid_string() in the OnChange event handler.

    #include <Clipbrd.hpp>
    #include <algorithm>
    #include <vector>
    
    // A function to validate a pasted string against a number of blacklisted strings
    bool valid_string(const UnicodeString& InStr) {
        static const std::vector<UnicodeString> BadStrings{"  ", "--"};
    
        return std::find_if(BadStrings.begin(), BadStrings.end(), [&](const auto& badstr) {
            // return true if the current badstr was found
            return 
                std::search(InStr.begin(),
                            InStr.end(),
                            badstr.begin(),
                            badstr.end()) != InStr.end();
        }) == BadStrings.end(); // true if a bad string was NOT found
    }
    
    // OnKeyPress event handler
    void __fastcall TForm1::Edit1KeyPress(TObject *Sender, System::WideChar &Key)
    {
        TEdit& se = *static_cast<TEdit*>(Sender);
        using StrType = decltype(se.Text);
    
        // A lambda to validate a single character:
        static const auto validkey = [](auto Ch) {
            // example of valid characters:
            static const StrType Accepted = "0123456789 -()";
            return std::find(Accepted.begin(), Accepted.end(), Ch) != Accepted.end();
        };
    
        if(Key >= ' ') {             // don't validate control characters
            // Single key validation
            if(not validkey(Key)) Key = 0;
    
        } else if(Key == 22) {       // CTRL-V - check that the whole clipboard buffer is ok
            auto& c = *Clipboard();
            if(c.HasFormat(CF_UNICODETEXT)) {
    
                // Extract the pasted string
                StrType paste = StrType(c.AsText.c_str());
    
                // Use the lambda on all characters
                bool all_chars_ok = std::all_of(paste.begin(), paste.end(), validkey);
    
                if(not (all_chars_ok && valid_string(paste))) { // reject the whole paste
                    Key = 0;
                }
            }
        }
    }
    

    Here's an example of doing it all in the OnChange handler instead. This should catch bad pastes from the context menu as well as if the user types in any illegal combinations (even if it consists of valid characters).

    #include <utility>
    
    void __fastcall TForm1::Edit1Change(TObject *Sender)
    {
        TEdit& se = *static_cast<TEdit*>(Sender);
        using StrType = decltype(se.Text);
    
        static StrType old_text;
    
        // A lambda to validate a single character:
        static const auto validkey = [](auto Ch) {
            // example of valid characters:
            static const StrType Accepted = "0123456789 -()";
            return std::find(Accepted.begin(), Accepted.end(), Ch) != Accepted.end();
        };
    
        // Making an unnecessary copy of the text.
        // Using se.Text's iterators directly fails for some reason.
        auto txt = se.Text;
    
        // Use the lambda on all characters
        bool all_chars_ok = std::all_of(txt.begin(), txt.end(), validkey);
    
        if(all_chars_ok && valid_string(txt)) {
            // All is ok, save this text
            old_text = std::move(txt);
    
        } else {
            // Revert back to the old text
            se.Text     = old_text;
            se.SelStart = old_text.Length();
            // se.Undo(); // May be a better idea to use instead.
        }
    }