Как привязать пользовательский тип к TextBox.Text?

У меня есть собственный тип С#, например (просто пример):

public class MyVector
{ 
   public double X {get; set;} 
   public double Y {get; set;} 
   public double Z {get; set;} 
   //...
}

И я хочу, чтобы он привязывался к TextBox.Text:

TextBox textBox;
public MyVector MyVectorProperty { get; set;}
//...
textBox.DataBindings.Add("Text", this, "MyVectorProperty");

По сути, мне нужно преобразование в строку и обратно для моего пользовательского типа значения. В текстовом поле я хочу что-то вроде «x, y, z», которое можно редактировать, чтобы обновить тип вектора. Я предположил, что смогу сделать это, добавив производный класс TypeConverter:

public class MyVectorConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, 
                                        Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        //...
        return base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, 
                                      Type destinationType)
    {
        if (destinationType == typeof(string))
            return true;
        //...
        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, 
                                       System.Globalization.CultureInfo culture,
                                       object value)
    {
        if (value is string)
        {
            MyVector MyVector;
            //Parse MyVector from value
            return MyVector;
        }
        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context,
                                     System.Globalization.CultureInfo culture, 
                                     object value, 
                                     Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            string s;
            //serialize value to string s
            return s;
        }
        //...
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

и связывая его с моей структурой:

[TypeConverter(typeof(MyVectorConverter))]
public class MyVector { //... }

Кажется, на этом половина битвы завершена. Я вижу, что MyVectorConverter вызывают, но что-то не так. Он вызывается, чтобы узнать, знает ли он, как преобразовать в строку, а затем вызывается для преобразования в строку. Тем не менее, он никогда не запрашивается, может ли он преобразовать строку FROM или действительно выполнить преобразование. Кроме того, сразу после редактирования в текстовом поле старое значение немедленно заменяется (другая последовательность CanConvertTo и ConvertTo, восстанавливающая старое значение). Конечным результатом является то, что вновь введенная запись в текстовом поле возвращается сразу же после ее применения.

Я чувствую, как будто чего-то простого не хватает. Есть? Весь этот проект/подход обречен на провал? Кто-нибудь еще пытается такое безумие? Как можно двунаправленно связать пользовательский составной тип со строковым элементом управления?

Решение. Как ни странно, все, что нужно, — это включить «форматирование» для объекта Binding. (спасибо, Джон Скит):

textBox.DataBindings.Add("Text", this, "MyVectorProperty"); //FAILS
textBox.DataBindings.Add("Text", this, "MyVectorProperty", true); //WORKS!

Как ни странно, все, что мой MSDN упоминает об этом параметре (formattingEnabled), это:

"true для форматирования отображаемых данных; в противном случае - false"

В нем ничего не говорится о том, что данные должны возвращаться из системы управления (в этих условиях).


person el2iot2    schedule 22.01.2009    source источник


Ответы (1)


Понятно!

Установите для свойства Binding.FormattingEnabled значение true. Это, кажется, заставляет все это работать. Это можно сделать с помощью перегрузки метода ControlBindingsCollection.Add, который принимает логический параметр в конце. Странно, что раньше это работало так, а не иначе, но, конечно, мое тестовое приложение теперь работает...

(Старый ответ ниже)

Я бы совсем не удивился, если бы здесь был важен тот факт, что у вас есть структура вместо класса, а также то, как вы используете поля вместо свойств.

Вместо этого попробуйте класс, использующий автоматически реализуемые свойства:

public class MyClass
{ 
   public int IntPart { get; set; } 
   public string StringPart { get; set; }
   //...
}

Возможно, это не является корнем проблемы, но использование изменяемой структуры с общедоступными полями просто вызывает проблемы, IMO.

РЕДАКТИРОВАТЬ: Как упоминалось в комментариях, теперь у меня есть готовый пример. Binding.Parse поднимается с правильным значением. Теперь, чтобы узнать, почему TypeConverter не вызывается...

РЕДАКТИРОВАТЬ: я нашел полезную статью, в которой более подробно описывается связывание. Кажется, предполагается, что преобразователь типов используется только для преобразования «в» другой тип, поэтому вам понадобится преобразователь типов для string, чтобы знать, как преобразовать в пользовательский тип. Мне это кажется довольно странным, по общему признанию, но есть два других варианта:

  • Используйте события Format и Parse Binding для преобразования
  • Сделать тип реализующим IConvertible

Ни один из них не выглядит одинаково, но может быть достаточным обходным решением для вас. Я уверен, что есть способ заставить это работать с помощью TypeConverters, но я взорван, если увижу это в данный момент.

person Jon Skeet    schedule 22.01.2009
comment
Спасибо за отзыв, я уточнил пример и сделал вопрос более общим. Мой пример был структурным для простоты, но я изменил его на ссылочный тип, чтобы продемонстрировать более широкую проблему. - person el2iot2; 22.01.2009
comment
Создание структуры с общедоступными полями, конечно, не добавляет простоты :) Это в Windows Forms, WPF, ASP.NET или что-то в этом роде? - person Jon Skeet; 22.01.2009
comment
Зависит от того, какова ваша эвристика для простоты (простота кода, простота примера, простота использования типа данных в взаимодействии и т. д.), но я отвлекся. Я пытаюсь использовать решение в формах Windows, но я думаю, что вопрос имеет отношение к привязке данных в целом. - person el2iot2; 22.01.2009
comment
Изменяемые структуры очень редко означают простоту в любом контексте. Во всяком случае, я воспроизвел проблему, но я тоже не могу заставить ее работать. Как вы говорите, привязка, кажется, только на стороне дисплея. Все еще ищу. - person Jon Skeet; 22.01.2009
comment
Действительно, это сделало свое дело. Спасибо за помощь, я ценю это. - person el2iot2; 22.01.2009
comment
Я бы подумал, что использование событий Binding.Format и Binding.Parse является «стандартным» решением. Почему это «не совсем так»? - person Joe; 22.01.2009
comment
Потому что класс уже сказал, когда вам нужно сделать преобразование типов, используйте этот класс здесь! Почему каждая привязка должна повторять эту информацию? - person Jon Skeet; 23.01.2009
comment
Конечно, но преобразование типа в/из строки связано с сохранением информации. Format/Parse связан с пользовательским интерфейсом редактирования (обычно с учетом региональных параметров). Нет ничего плохого в преобразовании типов, если строка находится в удобном для редактирования формате. Но ничего плохого в Format/Parse тоже нет. - person Joe; 24.01.2009
comment
... также Format/Parse является двунаправленным, что и необходимо. С преобразователем типов вам все еще нужно что-то для парсинга обратно из строки в пользовательский тип. И Format/Parse не исключает наличия общего помощника, если привязка повторяется в нескольких местах. - person Joe; 24.01.2009
comment
@Joe: Если для преобразователя типов и FormattingEnabled установлено значение true, преобразователь tytpe также используется в двух направлениях. Это то, что раньше не работало, но работает с установленным FormattingEnabled. - person Jon Skeet; 24.01.2009