XAML / Uniconta LookupGridColumn

Date: 2026-03-03
<local:LookUpGridColumn x:Name="colInvoiceSelect" FieldName="InvoiceSurcharge" Header="Invoice"  Width="120" ReadOnly="false" AllowEditing="true"/>
colInvoiceSelect.SetOptions<CreditorInvoiceClient>(invoiceItemSource);
<dxg:GridColumn x:Class="Views.Controls.LookUpGridColumn"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
                xmlns:controls="clr-namespace:Views.Controls"
                x:Name="myColumn"
                >
    <dxg:GridColumn.Resources>
        <controls:DisplayTextConverter x:Key="DisplayTextConverter"/>
    </dxg:GridColumn.Resources>
    <dxg:GridColumn.EditSettings>
        <dxg:LookUpEditSettings x:Name="LookupCombo" ValueMember="KeyStr" DisplayMember="KeyName" AutoPopulateColumns="False" FindMode="Always" FilterCondition="Contains" ImmediatePopup="True"
                                IsCaseSensitiveFilter="False" 
                                PopupWidth="600"                               
                                ShowPopupIfReadOnly="False"
                                ItemsSource="{Binding ElementName=myColumn,Path=LookupItems}"
                                >
            <dxg:LookUpEditSettings.StyleSettings>
                <dxg:SearchLookUpEditStyleSettings AllowGrouping="False"/>
            </dxg:LookUpEditSettings.StyleSettings>
            <dxg:LookUpEditSettings.PopupContentTemplate>
                <ControlTemplate>
                    <dxg:GridControl Name="PART_GridControl">
                        <dxg:GridControl.Columns>
                            <dxg:GridColumn FieldName="KeyStr" Header="{Binding Converter={StaticResource GlobalLocalizationValueConverter},ConverterParameter=Key}" Width="150"/>
                            <dxg:GridColumn FieldName="KeyName" Header="{Binding Converter={StaticResource GlobalLocalizationValueConverter},ConverterParameter=ItemName}" Width="400" />
                        </dxg:GridControl.Columns>
                        <dxg:GridControl.View>
                            <dxg:TableView AutoWidth="False" SearchStringToFilterCriteria="CurrentTableView_SearchStringToFilterCriteria" SearchPanelHighlightResults="True" SearchDelay="50" x:Name="CurrentTableView"/>
                        </dxg:GridControl.View>
                    </dxg:GridControl>
                </ControlTemplate>
            </dxg:LookUpEditSettings.PopupContentTemplate>
        </dxg:LookUpEditSettings>
    </dxg:GridColumn.EditSettings>
</dxg:GridColumn>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using DevExpress.Data.Filtering;
using DevExpress.Xpf.Grid;
using ViewModels.Models;
using Uniconta.API.System;
using Uniconta.Common;

namespace Views.Controls
{
    public partial class LookUpGridColumn : GridColumn
    {
        public LookUpGridColumn() : base()
        {
            Loaded += LookUpGridColumn_Loaded;
            InitializeComponent();
        }

        public delegate CriteriaOperator FilterHandler(object row);
        public FilterHandler OnFilter { get; set; }

        public delegate void ChangeHandler(object row, string fieldName, object value, string text);
        public ChangeHandler OnChange { get; set; }

        private void LookUpGridColumn_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            if (View is TableView tableView)
            {
                tableView.EditorShowMode = DevExpress.Xpf.Core.EditorShowMode.MouseDown;
                tableView.CellValueChanged += TableView_CellValueChanged;

                if (tableView.Parent is GridControl grid)
                {
                    grid.CurrentColumnChanged += Grid_CurrentColumnChanged;
                    grid.CurrentItemChanged += Grid_CurrentItemChanged;
                }
            }
        }

        private void TableView_CellValueChanged(object sender, CellValueChangedEventArgs e)
        {
            OnChange?.Invoke(e.Row, e.Column.FieldName, e.Value, GetText(e.Value));
        }

        public ObservableCollection<object> LookupItems { get; set; } = new ObservableCollection<object>();
        public bool SelectionOnCurrenColum { get; private set; }
        public bool SearchAlsoInKey = true;

        private object currentRow;
        private List<IdKey> items;

        public void InitControl<T>(CrudAPI api, bool searchAlsoInKey = true) where T : class, UnicontaBaseEntity, IdKey, new()
        {
            SetOptions<T>(api.LoadCache<T>().Result);
            this.SearchAlsoInKey = searchAlsoInKey;
        }

        public void SetOptions<T>(IEnumerable<IdKey> options) where T : UnicontaBaseEntity
        {
            items = options.Select((x, i) => new SimpleIdKey<T>((T)x, x.KeyStr, x.KeyName, i)).Cast<IdKey>().ToList();

            LookupItems.Clear();
            items.ForEach(i => LookupItems.Add(i));
            var displayTextConverter = new DisplayTextConverter(GetText);
            this.LookupCombo.DisplayTextConverter = displayTextConverter;
        }

        public string GetText(object key)
        {
            if (key == null)
                return null;
            return items.FirstOrDefault(x => x.KeyStr == key.ToString())?.KeyName;
        }

        private void Grid_CurrentItemChanged(object sender, CurrentItemChangedEventArgs e)
        {
            currentRow = e.NewItem;
            SetFilter();
        }

        private void SetFilter()
        {
            if (SelectionOnCurrenColum)
            {
                LookupCombo.FilterCriteria = OnFilter?.Invoke(currentRow) ?? null;
            }
        }

        private void Grid_CurrentColumnChanged(object sender, CurrentColumnChangedEventArgs e)
        {
            SelectionOnCurrenColum = (e.NewColumn == this);
            currentRow = e.Source?.CurrentItem;
            SetFilter();
        }

        private void CurrentTableView_SearchStringToFilterCriteria(object sender, SearchStringToFilterCriteriaEventArgs e)
        {
            if (e.SearchString != null)
            {
                CriteriaOperator filter = null;
                var columns = new List<string> { "KeyName" };
                if (SearchAlsoInKey)
                    columns.Add("KeyStr");

                columns.ForEach(c =>
                {
                    filter |= CriteriaOperator.Or(
                        CriteriaOperator.Parse(string.Format("Contains([{0}], '{1}')", c, e.SearchString)),
                        CriteriaOperator.Parse(string.Format("Contains([{0}], '{1}')", c, e.SearchString.Replace(".", ","))),
                        CriteriaOperator.Parse(string.Format("Contains([{0}], '{1}')", c, e.SearchString.Replace("*", "x"))),
                        CriteriaOperator.Parse(string.Format("Contains([{0}], '{1}')", c, e.SearchString.Replace(".", ",").Replace("*", "x")))
                        );
                });

                e.Filter = filter;
            }
        }
    }

    public class DisplayTextConverter : IValueConverter
    {
        public delegate string KeyToTextHandler(object key);
        public KeyToTextHandler Converter;

        public DisplayTextConverter(KeyToTextHandler converter)
        {
            Converter = converter;
        }

        public DisplayTextConverter()
        {
        }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => Converter(value);
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) => value;
    }
}

Extended

<dxg:GridColumn x:Class="Views.Controls.ExtendedLookUpGridColumn"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
                xmlns:controls="clr-namespace:Views.Controls"
                x:Name="myColumn"
                >
    <dxg:GridColumn.Resources>
        <controls:DisplayTextConverter x:Key="DisplayTextConverter"/>
    </dxg:GridColumn.Resources>
    <dxg:GridColumn.EditSettings>
        <dxg:LookUpEditSettings x:Name="LookupCombo" ValueMember="KeyStr" DisplayMember="KeyName" AutoPopulateColumns="False" FindMode="Always" FilterCondition="Contains" ImmediatePopup="True"
                                IsCaseSensitiveFilter="False" 
                                PopupWidth="600"
                                ShowPopupIfReadOnly="False"
                                ItemsSource="{Binding ElementName=myColumn,Path=LookupItems}"
                                >
            <dxg:LookUpEditSettings.StyleSettings>
                <dxg:SearchLookUpEditStyleSettings AllowGrouping="False"/>
            </dxg:LookUpEditSettings.StyleSettings>
            <dxg:LookUpEditSettings.PopupContentTemplate>
                <ControlTemplate>
                    <dxg:GridControl Name="PART_GridControl" ColumnsSource="{Binding ElementName=myColumn,Path=PopupColumns}" AutoGenerateColumns="None">
                        <!--<dxg:GridControl.Columns>
                            <dxg:GridColumn FieldName="KeyStr" Header="{Binding Converter={StaticResource GlobalLocalizationValueConverter}, ConverterParameter=Key}" Width="150"/>
                            <dxg:GridColumn FieldName="KeyName" Header="{Binding Converter={StaticResource GlobalLocalizationValueConverter}, ConverterParameter=Description}" Width="400" />
                        </dxg:GridControl.Columns>-->
                        <dxg:GridControl.View>
                            <dxg:TableView AutoWidth="False" SearchStringToFilterCriteria="CurrentTableView_SearchStringToFilterCriteria" SearchPanelHighlightResults="True" SearchDelay="50" x:Name="CurrentTableView"/>
                        </dxg:GridControl.View>
                    </dxg:GridControl>
                </ControlTemplate>
            </dxg:LookUpEditSettings.PopupContentTemplate>
        </dxg:LookUpEditSettings>
    </dxg:GridColumn.EditSettings>
</dxg:GridColumn>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using DevExpress.Data.Filtering;
using DevExpress.Xpf.Grid;

namespace Views.Controls
{
    public partial class ExtendedLookUpGridColumn : GridColumn
    {
        public ExtendedLookUpGridColumn() : base()
        {
            Loaded += LookUpGridColumn_Loaded;
            InitializeComponent();
        }

        public delegate CriteriaOperator FilterHandler(object row);
        public FilterHandler OnFilter { get; set; }

        public delegate void ChangeHandler(object row, string fieldName, object value, string text);
        public event ChangeHandler OnChange;

        private void LookUpGridColumn_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            if (View is TableView tableView)
            {
                tableView.EditorShowMode = DevExpress.Xpf.Core.EditorShowMode.MouseDown;
                tableView.CellValueChanged += TableView_CellValueChanged;

                if (tableView.Parent is GridControl grid)
                {
                    grid.CurrentColumnChanged += Grid_CurrentColumnChanged;
                    grid.CurrentItemChanged += Grid_CurrentItemChanged;
                }
            }
        }

        private void TableView_CellValueChanged(object sender, CellValueChangedEventArgs e)
        {
            OnChange?.Invoke(e.Row, e.Column.FieldName, e.Value, GetText(e.Value));
        }

        public ObservableCollection<GridColumn> PopupColumns { get; set; } = new ObservableCollection<GridColumn>();

        public void SetColumns(IEnumerable<GridColumn> columns)
        {
            PopupColumns.Clear();
            foreach (var column in columns)
                PopupColumns.Add(column);
        }

        private ILookupDataSource DataSource;

        public ObservableCollection<object> LookupItems { get; set; } = new ObservableCollection<object>();
        public bool SelectionOnCurrenColum { get; private set; }

        private object currentRow;

        public void SetOptions<T>(IEnumerable<T> options, Func<T, string> keySelector, Func<T, string> nameSelector) where T : class
        {
            DataSource = new LookupDataSource<T>(keySelector, nameSelector, options);

            LookupItems.Clear();
            foreach (var option in options)
            {
                LookupItems.Add(option);
            }

            LookupCombo.DisplayTextConverter = new DisplayTextConverter(GetText);
        }

        public string GetText(object key)
        {
            if (key == null)
                return null;

            var itemByKey = DataSource.Items.Select(x => new { Key = DataSource.GetKey(x), Name = DataSource.GetName(x) }).ToList();
            var item = itemByKey.FirstOrDefault(x => x.Key?.ToString() == key?.ToString());
            if (item == null) return "";
            return item.Name;
        }

        private void Grid_CurrentItemChanged(object sender, CurrentItemChangedEventArgs e)
        {
            currentRow = e.NewItem;
            SetFilter();
        }

        private void SetFilter()
        {
            if (SelectionOnCurrenColum)
            {
                LookupCombo.FilterCriteria = OnFilter?.Invoke(currentRow) ?? null;
            }
        }

        private void Grid_CurrentColumnChanged(object sender, CurrentColumnChangedEventArgs e)
        {
            SelectionOnCurrenColum = (e.NewColumn == this);
            currentRow = e.Source?.CurrentItem;
            SetFilter();
        }

        private void CurrentTableView_SearchStringToFilterCriteria(object sender, SearchStringToFilterCriteriaEventArgs e)
        {
            if (e.SearchString != null && PopupColumns != null)
            {
                CriteriaOperator filter = null;
                var columns = PopupColumns.Select(x => x.FieldName).ToList();

                columns.ForEach(c =>
                {
                    filter |= CriteriaOperator.Or(
                        CriteriaOperator.Parse(string.Format("Contains([{0}], '{1}')", c, e.SearchString)),
                        CriteriaOperator.Parse(string.Format("Contains([{0}], '{1}')", c, e.SearchString.Replace(".", ","))),
                        CriteriaOperator.Parse(string.Format("Contains([{0}], '{1}')", c, e.SearchString.Replace("*", "x"))),
                        CriteriaOperator.Parse(string.Format("Contains([{0}], '{1}')", c, e.SearchString.Replace(".", ",").Replace("*", "x")))
                        );
                });

                e.Filter = filter;
            }
        }
    }

    public interface ILookupDataSource
    {
        ObservableCollection<object> Items { get; }
        object GetKey(object o);
        string GetName(object o);
    }

    public class LookupDataSource<T> : ILookupDataSource where T : class
    {
        Func<T, object> keySelector;
        Func<T, string> nameSelector;

        public LookupDataSource(Func<T, object> keySelector, Func<T, string> nameSelector, IEnumerable<T> items)
        {
            this.keySelector = keySelector;
            this.nameSelector = nameSelector;
            Items = new ObservableCollection<object>(items);
        }
        public ObservableCollection<object> Items { get; private set; }
        object ILookupDataSource.GetKey(object o) => o is T v ? keySelector(v) : null;
        string ILookupDataSource.GetName(object o) => o is T v ? nameSelector(v) : "";
    }
}
100950cookie-checkXAML / Uniconta LookupGridColumn