picoe / Eto

Cross platform GUI framework for desktop and mobile applications in .NET
Other
3.64k stars 330 forks source link

Gtk: ComboBoxCell with ItemTextBinding not working #1578

Open ManuelHu opened 4 years ago

ManuelHu commented 4 years ago

ItemTextBinding on ComboBoxCell does not work on Gtk, like it works on windows WPF (expected behavior is met on WPF).

Expected Behavior

Actual Behavior

Code that Demonstrates the Problem

string[] dataStore =
            {
                "test",
                "hello",
                "lower",
            };

            var dataObject = new DataClass();
            dataObject.Testproperty = "test";

            var dataObject2 = new DataClass();
            dataObject2.Testproperty = "lower";

            var list = new[] {dataObject, dataObject2};

            var testForm = new Form();
            var gridView = new GridView();
            var stackLayout = new StackLayout(gridView);
            stackLayout.Padding = new Padding(100);
            testForm.Content = stackLayout;

            var dropDown = new ComboBoxCell();
            dropDown.ComboTextBinding = Binding.Delegate<string, string>(s => s.ToUpper());
            //dropDown.ComboKeyBinding = Binding.Delegate<string, string>(s => s);
            dropDown.DataStore = dataStore;

            dropDown.Binding = Binding.Property<DataClass, object>(o => o.Testproperty);

            gridView.AddColumn(dropDown, "Test", true);
            gridView.DataStore = list;

            App.Run(testForm);

public static GridColumn AddColumn(this GridView view, Cell cell, string header, bool editable = false)
        {
            var col = new GridColumn()
            {
                DataCell = cell,
                HeaderText = header,
                AutoSize = true,
                Sortable = false,
                Editable = editable,
            };
            view.Columns.Add(col);
            return col;
        }

        class DataClass
        {
            private string testproperty;

            public string Testproperty
            {
                get => testproperty;
                set
                {
                    MessageBox.Show(value);
                    testproperty = value;
                }
            }
        }

Specifications

cwensley commented 4 years ago

Thanks for reporting the issue!

ManuelHu commented 4 years ago

Oh I just saw, that AddColumn is a custom helper function of mine, so I added it to the code.

ManuelHu commented 4 years ago

I debugged a bit, and found out that the wrong (uppercase) value is already passed to ComboBoxCellEventConnector.HandleEdited by GtkSharp. So this looks like it may be something impossible to achieve directly with Gtk? (I never worked with Gtk before, so I might draw wrong conclusions...)

ManuelHu commented 4 years ago

I built a small workaround. It even works when multiple data items would yield the same text after applying textBinding, by adding invisible zero width spaces in front of the text, they encode the index. On my system nothing looks odd.

Edit: In the first version, it didn't work on WPF, but now I fixed this fix...

Here is the workaround wrapped in extension methods:

public static GridColumn AddDropDownColumn<T>(this GridView view, Expression<Func<T, object>> value, IEnumerable<object> dataStore, IIndirectBinding<string> textBinding, string header, bool editable = false)
{
    var internalBinding = Binding.Property(value);
    Cell cell;

    // hack for eto/gtk not supporting ItemTextBinding on ComboBoxCells
    if (Platform.Instance.IsGtk)
    {
        const char zws = '\u200B'; // This is a zero width (ZWS) space Unicode character.

        var lDataStore = dataStore.ToList();

        var tDataStore = lDataStore.Select(i => 
            string.Empty.PadLeft(lDataStore.IndexOf(i) + 1, zws) // Pad with index+1 ZWS chars to be able to check later. If no ZWS is there, this did not work.
            + textBinding.GetValue(i))
            .ToList();
        var hasNonUnique = tDataStore.Distinct().Count() != tDataStore.Count; // Check if textBinding produced the same string for more than one data item.

        var binding = Binding.Delegate<T, string>(
            (T s) => textBinding.GetValue(internalBinding.GetValue(s)),
            (T t, string s) =>
            {
                int idx = s.Split(zws).Length - 2;
                if (idx == -1)
                {
                    idx = tDataStore.IndexOf(s); // Fallback if ZWS is not supported.
                    if (hasNonUnique)
                        throw new Exception("ComboBoxCell ComboTextBinding Polyfill: Duplicate text entry encountered and Zero-Width-Space Hack not supported by platform!");
                }

                internalBinding.SetValue(t, lDataStore[idx]);
            }).Cast<object>();

        cell = new ComboBoxCell { Binding = binding, DataStore = tDataStore };
    }
    else
        cell = new ComboBoxCell { Binding = internalBinding, DataStore = dataStore, ComboTextBinding = textBinding };

    return view.AddColumn(cell, header, editable);
}
public static GridColumn AddColumn(this GridView view, Cell cell, string header, bool editable = false)
{
    var col = new GridColumn()
    {
        DataCell = cell,
        HeaderText = header,
        AutoSize = true,
        Sortable = false,
        Editable = editable,
    };
    view.Columns.Add(col);
    return col;
}