NEWS

[DEVEXPRESS] Hỗ trợ tìm kiếm highlight không dấu và không khoảng cách trên Gridview Filter

[DEVEXPRESS] Hỗ trợ tìm kiếm highlight không dấu và không khoảng cách trên Gridview Filter
Đăng bởi: Thảo Meo - Lượt xem: 51 08:07:42, 22/09/2025DEVEXPRESS   In bài viết

Xin chào các bạn, bài viết hôm nay mình tiếp tục chia sẻ các bạn source code cách tìm kiếm không dấu và không khoảng cách trên Gridview Devexpress.

[DEVEXPRESS] Tìm kiếm không dấu và không khoảng cách trên Gridview Filter

Dưới đây là hình ảnh demo ứng dụng:

tim-kiem-khong-dau-gridview-devexpress

Như hình ảnh trên là mình demo 2 component: GridViewSearchLookupEdit, các bạn thấy mình gõ như sau:

  1. Từ khóa tìm kiếm: "nguphuc" -> "Ngũ phúc" 
  2. Từ khóa tìm kiếm: "honai3" -> "Hố Nai 3"

Với tính năng tìm kiếm này giúp cho người dùng tìm kiếm 1 cách nhanh chóng và dễ dàng trên main form các bạn chỉ cần gọi hàm enable hoặc disable tính năng này thôi, sử dụng rất linh động.

Trên hàm, mình cũng viết sẵn cho các bạn có thể truyền màu sắc highlight text: background color và foreground color.

Video Demo:

Source code Frmmain.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using DevExpress.Data;
using DevExpress.XtraEditors;
using GridViewFiltering;

namespace GridViewFiltering
{
    public partial class MainForm : XtraForm {
        public MainForm() {
            InitializeComponent();
        }

        private async void MainForm_Load(object sender, EventArgs e) {

            var data = await new EmployeeDataExample.EmployeeData().GetEmployeesAsync();

            this.GridControl.DataSource = data;

            searchLookUpEdit1.Properties.DataSource = data;
            searchLookUpEdit1.Properties.ValueMember = "Name";
            searchLookUpEdit1.Properties.DisplayMember = "Name";

        }

        private void btnEnable_Click(object sender, EventArgs e)
        {
            GridSearchHighlighter.EnableSmartHighlight(this.GridView, Color.Pink, Color.OrangeRed, 30);
            GridSearchHighlighter.EnableSmartHighlight(this.searchLookUpEdit1View, Color.Blue);

            XtraMessageBox.Show("Đã bật");
        }

        private void btnDisable_Click(object sender, EventArgs e)
        {
            GridSearchHighlighter.DisableSmartHighlight(this.GridView);
            GridSearchHighlighter.DisableSmartHighlight(this.searchLookUpEdit1View);
            XtraMessageBox.Show("Đã tắt");
        }
    }
}

Source code GridSearchHighlighter.cs

using System;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using DevExpress.XtraGrid.Views.Base;
using DevExpress.XtraEditors;
using DevExpress.XtraGrid.Views.Grid;
using DevExpress.Data;
using DevExpress.Data.Filtering;
using DevExpress.Data.Filtering.Helpers;
using DevExpress.Utils.Extensions;

namespace GridViewFiltering
{
    public enum HighlightPreset
    {
        Yellow,            
        LightBlue,         
        LightGreen,        
        LightPink,        
        LightOrange,      
        LightPurple,      
        LightCyan,         
        LightCoral,        
        LightSalmon,       
        LightSkyBlue,      
        LightSeaGreen,     
        LightSteelBlue,    
        Gold,             
        Silver,          
        Rose,             
        Lavender,         
        Mint,             
        Peach,           
        Apricot,         
        Cream            
    }

    public class HighlightColorScheme
    {
        public Color BackgroundColor { get; set; }
        public Color ForegroundColor { get; set; }
        public int BackgroundOpacity { get; set; } = 50;      

        public HighlightColorScheme(Color backgroundColor, Color foregroundColor, int backgroundOpacity = 50)
        {
            BackgroundColor = backgroundColor;
            ForegroundColor = foregroundColor;
            BackgroundOpacity = Math.Max(0, Math.Min(255, backgroundOpacity));
        }

        public Color GetBackgroundColor()
        {
            return Color.FromArgb(BackgroundOpacity, BackgroundColor);
        }
    }

    public static class GridSearchHighlighter
    {
        private static readonly Dictionary<object, HighlightColorScheme> _highlightSchemes = new Dictionary<object, HighlightColorScheme>();
        private static readonly Dictionary<HighlightPreset, HighlightColorScheme> _presetSchemes = new Dictionary<HighlightPreset, HighlightColorScheme>();
        private static bool _isCustomFunctionRegistered = false;

        static GridSearchHighlighter()
        {
            InitializePresetSchemes();
            RegisterCustomFunction();
        }

        private static void RegisterCustomFunction()
        {
            if (!_isCustomFunctionRegistered)
            {
                try
                {
                    CriteriaOperator.RegisterCustomFunction(new RemoveDiacriticsFunction());
                    _isCustomFunctionRegistered = true;
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine($"Failed to register RemoveDiacriticsFunction: {ex.Message}");
                }
            }
        }

        private static void InitializePresetSchemes()
        {
            _presetSchemes[HighlightPreset.Yellow] = new HighlightColorScheme(Color.Yellow, Color.Black, 60);
            _presetSchemes[HighlightPreset.LightBlue] = new HighlightColorScheme(Color.LightBlue, Color.DarkBlue, 70);
            _presetSchemes[HighlightPreset.LightGreen] = new HighlightColorScheme(Color.LightGreen, Color.DarkGreen, 70);
            _presetSchemes[HighlightPreset.LightPink] = new HighlightColorScheme(Color.LightPink, Color.DarkMagenta, 70);
            _presetSchemes[HighlightPreset.LightOrange] = new HighlightColorScheme(Color.Orange, Color.DarkOrange, 60);
            _presetSchemes[HighlightPreset.LightPurple] = new HighlightColorScheme(Color.Plum, Color.Purple, 70);
            _presetSchemes[HighlightPreset.LightCyan] = new HighlightColorScheme(Color.LightCyan, Color.DarkCyan, 70);
            _presetSchemes[HighlightPreset.LightCoral] = new HighlightColorScheme(Color.LightCoral, Color.DarkRed, 70);
            _presetSchemes[HighlightPreset.LightSalmon] = new HighlightColorScheme(Color.LightSalmon, Color.DarkRed, 70);
            _presetSchemes[HighlightPreset.LightSkyBlue] = new HighlightColorScheme(Color.LightSkyBlue, Color.DarkBlue, 70);
            _presetSchemes[HighlightPreset.LightSeaGreen] = new HighlightColorScheme(Color.LightSeaGreen, Color.DarkGreen, 70);
            _presetSchemes[HighlightPreset.LightSteelBlue] = new HighlightColorScheme(Color.LightSteelBlue, Color.DarkBlue, 70);
            _presetSchemes[HighlightPreset.Gold] = new HighlightColorScheme(Color.Gold, Color.DarkGoldenrod, 60);
            _presetSchemes[HighlightPreset.Silver] = new HighlightColorScheme(Color.Silver, Color.DarkSlateGray, 70);
            _presetSchemes[HighlightPreset.Rose] = new HighlightColorScheme(Color.MistyRose, Color.DarkRed, 70);
            _presetSchemes[HighlightPreset.Lavender] = new HighlightColorScheme(Color.Lavender, Color.Purple, 70);
            _presetSchemes[HighlightPreset.Mint] = new HighlightColorScheme(Color.MintCream, Color.DarkGreen, 70);
            _presetSchemes[HighlightPreset.Peach] = new HighlightColorScheme(Color.PeachPuff, Color.DarkOrange, 70);
            _presetSchemes[HighlightPreset.Apricot] = new HighlightColorScheme(Color.PeachPuff, Color.Orange, 70);
            _presetSchemes[HighlightPreset.Cream] = new HighlightColorScheme(Color.Crimson, Color.Black, 60);
        }

        public static void EnableSmartHighlight(object control, Color? highlightColor = null)
        {
            Color color = highlightColor ?? Color.Yellow;
            var scheme = new HighlightColorScheme(color, Color.Black, 60);
            EnableSmartHighlight(control, scheme);
        }

        public static void EnableSmartHighlight(object control, HighlightPreset preset)
        {
            if (!_presetSchemes.ContainsKey(preset))
                throw new ArgumentException($"Preset {preset} not found");

            var scheme = _presetSchemes[preset];
            EnableSmartHighlight(control, scheme);
        }

        public static void EnableSmartHighlight(object control, Color backgroundColor, Color foregroundColor, int backgroundOpacity = 50)
        {
            var scheme = new HighlightColorScheme(backgroundColor, foregroundColor, backgroundOpacity);
            EnableSmartHighlight(control, scheme);
        }

        public static void EnableSmartHighlight(object control, HighlightColorScheme scheme)
        {
            if (control is GridView gridView)
            {
                EnableForGridView(gridView, scheme);
            }
            else if (control is GridLookUpEdit gridLookUp)
            {
                EnableForGridView(gridLookUp.Properties.View, scheme);
            }
            else if (control is SearchLookUpEdit searchLookUp)
            {
                EnableForGridView(searchLookUp.Properties.View, scheme);
            }
            else
            {
                throw new ArgumentException("Control must be GridView, GridLookUpEdit, or SearchLookUpEdit");
            }
        }

        public static void DisableSmartHighlight(object control)
        {
            if (control is GridView gridView)
            {
                DisableForGridView(gridView);
            }
            else if (control is GridLookUpEdit gridLookUp)
            {
                DisableForGridView(gridLookUp.Properties.View);
            }
            else if (control is SearchLookUpEdit searchLookUp)
            {
                DisableForGridView(searchLookUp.Properties.View);
            }
        }

        private static void EnableForGridView(GridView gridView, HighlightColorScheme scheme)
        {
            if (gridView == null) return;

            gridView.OptionsFind.HighlightFindResults = false;
            gridView.CustomDrawCell -= GridView_CustomDrawCell;
            gridView.CustomDrawCell += GridView_CustomDrawCell;

            gridView.SubstituteFilter -= GridView_SubstituteFilter;
            gridView.SubstituteFilter += GridView_SubstituteFilter;

            _highlightSchemes[gridView] = scheme;
        }

        private static void DisableForGridView(GridView gridView)
        {
            if (gridView == null) return;

            gridView.CustomDrawCell -= GridView_CustomDrawCell;
            gridView.SubstituteFilter -= GridView_SubstituteFilter;
            gridView.OptionsFind.HighlightFindResults = true;
            _highlightSchemes.Remove(gridView);
        }

        private static void GridView_CustomDrawCell(object sender, RowCellCustomDrawEventArgs e)
        {
            var gridView = sender as GridView;
            if (gridView == null || !_highlightSchemes.ContainsKey(gridView)) return;

            if (gridView.IsFilterRow(e.RowHandle))
                return;

            string cellText = e.DisplayText;
            string searchText = null;

            if (e.Column != null && e.Column.FilterInfo != null && e.Column.FilterInfo.Value != null)
            {
                searchText = e.Column.FilterInfo.Value.ToString();
            }
            if (string.IsNullOrEmpty(searchText))
            {
                searchText = gridView.FindFilterText;
            }

            if (string.IsNullOrEmpty(searchText) || string.IsNullOrEmpty(cellText))
                return;

            var matchRanges = FindAllMatchPositions(cellText, searchText);
            if (matchRanges.Count == 0)
                return;

            e.Handled = true;
            var g = e.Graphics;
            var font = e.Appearance.Font;
            var textColor = e.Appearance.GetForeColor();
            var rect = e.Bounds;
            float x = rect.X;
            var stringFormat = new StringFormat(StringFormatFlags.MeasureTrailingSpaces);
            var scheme = _highlightSchemes[gridView];

            int lastIdx = 0;
            foreach (var (start, length) in matchRanges)
            {
                if (start > lastIdx)
                {
                    string before = cellText.Substring(lastIdx, start - lastIdx);
                    var beforeSize = g.MeasureString(before, font, int.MaxValue, stringFormat);
                    g.DrawString(before, font, new SolidBrush(textColor), x, rect.Y, stringFormat);
                    x += beforeSize.Width;
                }
                
                string match = cellText.Substring(start, length);
                
                var matchSize = g.MeasureString(match, font, int.MaxValue, stringFormat);
                
                var highlightRect = new RectangleF(x, rect.Y, matchSize.Width, rect.Height);
                g.FillRectangle(new SolidBrush(scheme.GetBackgroundColor()), highlightRect);
                
                g.DrawString(match, font, new SolidBrush(scheme.ForegroundColor), x, rect.Y, stringFormat);
                x += matchSize.Width;
                
                lastIdx = start + length;
            }
            if (lastIdx < cellText.Length)
            {
                string after = cellText.Substring(lastIdx);
                g.DrawString(after, font, new SolidBrush(textColor), x, rect.Y, stringFormat);
            }
        }

        private static List<(int start, int length)> FindAllMatchPositions(string original, string search)
        {
            if (string.IsNullOrEmpty(original) || string.IsNullOrEmpty(search))
                return new List<(int, int)>();

            var normOriginal = RemoveDiacriticsAndSpaces(original).ToLowerInvariant();
            var normSearch = RemoveDiacriticsAndSpaces(search).ToLowerInvariant();
            
            if (string.IsNullOrEmpty(normSearch))
                return new List<(int, int)>();

            var result = new List<(int, int)>();
            var mapping = CreateCharacterMapping(original);
            
            int normIdx = 0;
            while (normIdx <= normOriginal.Length - normSearch.Length)
            {
                if (normOriginal.Substring(normIdx, normSearch.Length) == normSearch)
                {
                    if (normIdx < mapping.Count)
                    {
                        int realStart = mapping[normIdx];
                        int normEnd = normIdx + normSearch.Length;
                        int realEnd = (normEnd < mapping.Count) ? mapping[normEnd] : original.Length;
                        
                        var matchRange = TrimWhitespaceFromMatch(original, realStart, realEnd - realStart);
                        if (matchRange.length > 0)
                        {
                            result.Add(matchRange);
                        }
                    }
                    normIdx += normSearch.Length;
                }
                else
                {
                    normIdx++;
                }
            }
            return result;
        }

        public static string RemoveDiacriticsAndSpaces(string src)
        {
            if (src == null) return string.Empty;
            var sb = new StringBuilder();
            foreach (var c in src.Normalize(NormalizationForm.FormKD))
            {
                var cat = CharUnicodeInfo.GetUnicodeCategory(c);
                if (cat != UnicodeCategory.NonSpacingMark &&
                    cat != UnicodeCategory.SpacingCombiningMark &&
                    cat != UnicodeCategory.EnclosingMark &&
                    !char.IsWhiteSpace(c))
                {
                    if (c == 'đ') sb.Append('d');
                    else if (c == 'Đ') sb.Append('D');
                    else sb.Append(c);
                }
            }
            return sb.ToString();
        }


        private static (int start, int length) TrimWhitespaceFromMatch(string original, int start, int length)
        {
            if (start < 0 || start >= original.Length || length <= 0)
                return (-1, 0);

            int end = start + length;
            if (end > original.Length)
                end = original.Length;

            int realStart = start;
            while (realStart < end && char.IsWhiteSpace(original[realStart]))
            {
                realStart++;
            }

            int realEnd = end;
            while (realEnd > realStart && char.IsWhiteSpace(original[realEnd - 1]))
            {
                realEnd--;
            }

            int realLength = realEnd - realStart;
            return realLength > 0 ? (realStart, realLength) : (-1, 0);
        }

        private static List<int> CreateCharacterMapping(string original)
        {
            var mapping = new List<int>();
            
            for (int i = 0; i < original.Length; i++)
            {
                char c = original[i];
                var cat = CharUnicodeInfo.GetUnicodeCategory(c);
                
                if (cat != UnicodeCategory.NonSpacingMark &&
                    cat != UnicodeCategory.SpacingCombiningMark &&
                    cat != UnicodeCategory.EnclosingMark &&
                    !char.IsWhiteSpace(c))
                {
                    mapping.Add(i);
                }
            }
            
            return mapping;
        }

        private static int FindRealIndex(string original, string normalized, int normIndex)
        {
            if (string.IsNullOrEmpty(original) || string.IsNullOrEmpty(normalized) || normIndex < 0)
                return -1;

            var mapping = CreateCharacterMapping(original);
            
            if (normIndex >= 0 && normIndex < mapping.Count)
                return mapping[normIndex];
            
            if (normIndex >= mapping.Count)
                return original.Length;

            return -1;
        }

        public static HighlightPreset[] GetAvailablePresets()
        {
            return (HighlightPreset[])Enum.GetValues(typeof(HighlightPreset));
        }

        public static HighlightColorScheme GetPresetScheme(HighlightPreset preset)
        {
            if (!_presetSchemes.ContainsKey(preset))
                throw new ArgumentException($"Preset {preset} not found");
            
            return _presetSchemes[preset];
        }

        public static void ChangeHighlightColor(object control, HighlightPreset preset)
        {
            if (!_highlightSchemes.ContainsKey(control))
                throw new InvalidOperationException("Control is not registered for highlighting");

            var scheme = GetPresetScheme(preset);
            _highlightSchemes[control] = scheme;
        }

        public static void ChangeHighlightColor(object control, Color backgroundColor, Color foregroundColor, int backgroundOpacity = 50)
        {
            if (!_highlightSchemes.ContainsKey(control))
                throw new InvalidOperationException("Control is not registered for highlighting");

            var scheme = new HighlightColorScheme(backgroundColor, foregroundColor, backgroundOpacity);
            _highlightSchemes[control] = scheme;
        }

        public static HighlightColorScheme GetCurrentScheme(object control)
        {
            if (!_highlightSchemes.ContainsKey(control))
                return null;
            
            return _highlightSchemes[control];
        }

        public static void EnsureCustomFunctionRegistered()
        {
            RegisterCustomFunction();
        }

        public static bool IsCustomFunctionRegistered()
        {
            return _isCustomFunctionRegistered;
        }

        private static void GridView_SubstituteFilter(object sender, SubstituteFilterEventArgs e)
        {
            e.Filter = GridFilterSubstitutor.Substitute(e.Filter);
        }
    }

    public class GridFilterSubstitutor : ClientCriteriaLazyPatcherBase.AggregatesCommonProcessingBase
    {
        private static CriteriaOperator WrapIntoCustomFunction(CriteriaOperator param)
        {
            return new FunctionOperator(FunctionOperatorType.Custom,
                new ConstantValue("RemoveDiacritics"), param);
        }

        public static CriteriaOperator Substitute(CriteriaOperator source)
        {
            return new GridFilterSubstitutor().Process(source);
        }

        public override CriteriaOperator Visit(FunctionOperator theOperator)
        {
            if (theOperator.OperatorType == FunctionOperatorType.StartsWith ||
                theOperator.OperatorType == FunctionOperatorType.EndsWith ||
                theOperator.OperatorType == FunctionOperatorType.Contains)
                return new FunctionOperator(theOperator.OperatorType,
                    WrapIntoCustomFunction(theOperator.Operands[0]),
                    WrapIntoCustomFunction(theOperator.Operands[1]));
            return base.Visit(theOperator);
        }
    }

    public sealed class RemoveDiacriticsFunction : ICustomFunctionOperator
    {
        public object Evaluate(params object[] operands)
        {
            var src = (string)operands[0];
            if (src == null)
                return string.Empty;

            var sb = new StringBuilder();

            foreach (var c in src.Normalize(NormalizationForm.FormKD))
            {
                if (!IsDiacritic(c) && !char.IsWhiteSpace(c))
                {
                    if (c == 'đ') sb.Append('d');
                    else if (c == 'Đ') sb.Append('D');
                    else sb.Append(c);
                }
            }

            return sb.ToString();
        }

        private bool IsDiacritic(char symbol)
        {
            var category = CharUnicodeInfo.GetUnicodeCategory(symbol);

            return category == UnicodeCategory.NonSpacingMark ||
                   category == UnicodeCategory.SpacingCombiningMark ||
                   category == UnicodeCategory.EnclosingMark;
        }

        public string Name => "RemoveDiacritics";

        public Type ResultType(params Type[] operands) => typeof(string);
    }
}

Thanks for watching!

DOWNLOAD SOURCE

THÔNG TIN TÁC GIẢ

BÀI VIẾT LIÊN QUAN

[DEVEXPRESS] Hỗ trợ tìm kiếm highlight không dấu và không khoảng cách trên Gridview Filter
Đăng bởi: Thảo Meo - Lượt xem: 51 08:07:42, 22/09/2025DEVEXPRESS   In bài viết

CÁC BÀI CÙNG CHỦ ĐỀ

Đọc tiếp
.