реализация IMultiValueConverter для преобразования между единицами - PullRequest
5 голосов
/ 16 августа 2011

Я работаю в системе выставления счетов и использую DataGrid для ввода товара. Я хочу, чтобы пользователи могли вводить количества в разных единицах (например, дюймах, футах, метрах) и чтобы это введенное количество конвертировалось в единицы складирования товара. Моей первой мыслью было реализовать IMultiValueConverter, но я пробовал со вчерашнего дня и не могу понять.

Мой метод Convert работает наверняка, я фактически взял тело, вставил его в другой метод и проверил его вывод. Моя программа дает сбой в методе ConvertBack, я новичок в C #, и я никогда не видел, чтобы кто-нибудь на самом деле реализовал этот метод, поэтому мне интересно, почему он заставляет меня его реализовывать (полагаю, это из-за моего количества привязка не односторонняя), но мне действительно нужно преобразованное значение, чтобы вернуться к исходным объектам. Свойство Количество.

У меня действительно нет проблем с реализацией метода ConvertBack, но параметр значения, который передается в метод, является значением, введенным пользователем, а не преобразованным значением, почему это так? Если это не конвертированное значение, по этой причине я не могу понять, как вернуться назад, потому что у меня есть доступ только к тому, что вводит пользователь, а не к требуемым единицам в этом методе.

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

Заранее спасибо!

Страницы / PurchaseInvoicePage.xaml

<DataGrid x:Name="ItemsGrid" Grid.Row="2" Grid.ColumnSpan="3" PreviewKeyDown="ItemsGrid_PreviewKeyDown" ItemsSource="{Binding Items}" FontSize="11" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" RowHeaderWidth="0" GridLinesVisibility="None" CanUserResizeRows="False" CanUserResizeColumns="False" CanUserReorderColumns="False">
    <DataGrid.Columns>

        <DataGridTemplateColumn x:Name="ItemNoColumn" Width="150" Header="Item No." IsReadOnly="True">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBox Cursor="Arrow" Width="130" BorderThickness="0" Background="Transparent" IsReadOnly="True" Text="{Binding Number}" />
                        <Image Cursor="Hand" MouseDown="ItemSelectionButton_Click" Width="12" Source="/Images/Icons/SearchBlack.png" />
                    </StackPanel>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

        <!-- question relative xaml starts here -->
        <DataGridTextColumn x:Name="QuantityColumn" Width="70" Header="Quantity">
            <DataGridTextColumn.Binding>
                <MultiBinding Converter="{StaticResource unitConverter}">
                    <Binding Path="Quantity" />
                    <Binding Path="Units" Mode="OneWay" />
                </MultiBinding>
            </DataGridTextColumn.Binding>
        </DataGridTextColumn>
        <!-- question relative xaml ends here -->

        <DataGridTextColumn x:Name="OrderColumn" Width="70" Header="Order" Binding="{Binding QuantityOrdered}" />
        <DataGridTextColumn x:Name="BackOrderColumn" Width="70" Header="B/O" Binding="{Binding QuantityBackOrdered}" />
        <DataGridTextColumn x:Name="UnitsColumn" Width="60" Header="Units" Binding="{Binding Units}" IsReadOnly="True" />
        <DataGridTextColumn x:Name="DescriptionColumn" Width="200" Header="Description" Binding="{Binding Description}" />
        <DataGridTextColumn x:Name="PriceColumn" Width="90" Header="Price" Binding="{Binding Price}" />
        <DataGridComboBoxColumn x:Name="TaxColumn" Width="50" Header="Tax" SelectedValueBinding="{Binding TaxCodeID}" DisplayMemberPath="Code" SelectedValuePath="ID" />
        <DataGridTextColumn x:Name="AmountColumn" Width="90" Header="Amount" Binding="{Binding Amount}" IsReadOnly="True" />
        <DataGridTextColumn x:Name="LinkedColumn" Width="90" Header="Linked" Binding="{Binding SalesOrderID}" />
    </DataGrid.Columns>
</DataGrid>

Fx / IMultiValueConverter.cs

public class UnitConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameters, CultureInfo culture)
    {
        double num = 0;
        // get the quantity value, and try parsing it
        string str = values[0].ToString().ToLower();
        bool parsed = double.TryParse(str, out num);

        // if it parses, no need to convert, return the value
        if (parsed)
            return num.ToString();

        // if it doesnt parse, get the last character in the value
        // this character indicates the units being entered
        // this will be either "(inches), f(eet), or m(eters) 
        string suffix = str.Substring(str.Length - 1);
        // get the value, without thhe last character
        str = str.Substring(0, str.Length - 1);
        // try parsing the value now
        parsed = double.TryParse(str, out num);

        // if it doesn't parse, the formats incorrect, return 0
        if (!parsed)
            return (0).ToString();

        // get the desired units, (the second value in my multibinding)
        string units = values[1].ToString().ToLower();

        // if either the entry suffix or the desired units are empty, just return
        // the number without converting
        if (string.IsNullOrEmpty(suffix) || string.IsNullOrEmpty(units))
            return num;

        // convert from inches to feet
        if (suffix == "\"" && units == "feet")
            return (num / 12).ToString();
        // convert from inches to meters
        else if (suffix == "\"" && units == "meters")
            return (num * 0.0254).ToString();
        // convert from feet to meters
        else if (suffix == "f" && units == "meters")
            return (num * 0.3048).ToString();
        // convert from meters to feet
        else if (suffix == "m" && units == "feet")
            return (num / 0.3048).ToString();

        // if we reachd this far, the user probably entered something random,
        // show an error and return 0
        MessageBox.Show("Failed to convert between the two units.");
        return (0).ToString();
    }

    public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
    {
        // now this is where my program is crashing, 
        // for some reason I need to actually implement the ConvertBack function
        // but when I try to popup the value being passed to this function,
        // it is the value the user entered e.g "20f" and not the converted value
        // should it not be the converted value? Why do I even need to implement this
        // how should i go backwards from here...
        string str = value.ToString();
        // MessageBox.Show(str);
        return new object[] { 0 }; // btw, I'm only returning one value, because the second binding is oneway

    }

Ответы [ 2 ]

8 голосов
/ 16 августа 2011

Забавно, я реализовал конвертер единиц измерения с помощью IMultiValueConverter всего несколько недель назад. Пока я выкапываю это, чтобы посмотреть, смогу ли я вам помочь, позвольте мне предложить теорию:

Может быть, вы перевернули Convert и ConvertBack:

Convert: преобразовывает значение из вашей модели в источник привязки (сетку данных)

ConvertBack: преобразовывает вводимые пользователем данные в модель

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

Теперь давайте посмотрим, смогу ли я найти этот конвертер ...

Здесь - документация MSDN

ОБНОВЛЕНИЕ: Итак, я нашел свой конвертер, и воспоминания мчались назад. Я столкнулся с той же самой проблемой, которую вы только что сделали: как бы передать нужные единицы в функцию преобразования назад Честно говоря, я не на 100% доволен своим решением, но я поделюсь им с вами.

Большинство реализаций ValueConverter, которые я видел, не имеют состояния, поэтому вы можете использовать их как StaticResource для множественных привязок. Я добавил поле юнитов в свою реализацию, чтобы отслеживать юниты, что жертвует его безгражданством:

public class UnitConverter : IMultiValueConverter
{
    private string _units;

    public object Convert(object[ ] values, Type targetType, object parameter, CultureInfo culture)
    {
         //Initialize
         _units = values[1];
         //Convert
    }

    public object Convert(object[ ] values, Type targetType, object parameter, CultureInfo culture)
    {
         //Use _units to convert back
    }
}

К сожалению, вам нужно создать конвертер для каждой используемой привязки:

<DataGridTextColumn.Binding>
   <MultiBinding>
      <Binding Path="Quantity" />
      <Binding Path="Units" Mode="OneWay" />
      <MultiBinding.Converter>
         <yourNameSpace:UnitConverter/>
      </MultiBinding.Converter>
   </MultiBinding>
</DataGridTextColumn.Binding>

Мне бы хотелось увидеть лучшее решение для себя, но это сработало для меня.

4 голосов
/ 06 февраля 2012

Я наткнулся на этот пост, пытаясь отладить мой собственный конвертер единиц, который использовал технику, аналогичную Джину С, для «запоминания» того, какие единицы были для преобразования обратно. В моем случае я конвертирую углы (RADIANS, DEGREES) и расстояния (NATIVE_DIST, MM, INCHES). В большинстве случаев все работало нормально, но в других случаях преобразование было бы совершенно неверным.

Оказывается, что преобразователи значений по умолчанию используются совместно. Это означает, что "_units" в примере Джина будет установлено на те единицы измерения, которые использовались последним вызовом "Convert". В моем случае, если я преобразовал расстояние из NATIVE_DIST-> MM (помня, что исходные единицы измерения были "NATIVE_DIST"), а затем немедленно вызвал "ConvertBack" для другого элемента управления, связанного с углом, "запомненными" единицами будет "NATIVE_DIST". "вместо угловых единиц, которые я намеревался.

Решение состояло в изменении объявления .xaml для конвертера:

Change this    <local:UnitsConverter x:Key="UnitsConverter"/>
To this        <local:UnitsConverter x:Key="UnitsConverter" x:Shared="false"/>
...