ValueConvertor’ы очень полезная и удобная штука, которая очень часто используется при биндинге (будем по народному)для преобразования одного типа данных в другой, например:
- Вы делаете компонент который отображает прогресс какого-то действия и вам надо менять цвет от зеленого до красного, в зависимости от того, на сколько прогресс движется к концу;
- Вам надо сделать какой-то расчет, например зарплату по количеству часов * рейт работника;
- Или более простые пример, это отформатировать дату или преобразовать цвет по #AABBCCDD в строковое представление;
- Инвертировать отображения элемента, например у вас биндится свойство на CheckBox типо IsDeclined и если оно true, то CheckBox не должен быть отмечен (пример не надуманый, всякое бывает). Если есть возможность, то можно переименовать в Allowed и будет логично, а если нет? Тогда делаем сами уже знаете что.
Конверторы инкапсулируют какую-то логику, которая может меняться, они также могут принимать параметры. Также конверторы используются для отладки биндинга (об этом позже).
Я думаю у многих возникала потребность в редактируемом тексте, например отображается текст в метке “Edit me”, вы щелкаете по тексту, он меняется на TextBox, вы его редактируете, нажимаете Enter и опять метка, круто.
Рассмотрим как это можно сделать с использованием конверторов. Определим правила игры:
- Метка может иметь начальный текст
- Метка переходит в состояние редактирования по щелчку мышки на ней
- TextBox переходит в состояние метки после нажатия клавиши Enter или когда пользователь уводит курсор с TextBox или теряется фокус.
Рассмотрим XAML нашего компонента:
<UserControl x:Class="EditableLabel.Control.EditableText"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converters="clr-namespace:EditableLabel.Converters"
x:Name="editableText">
<UserControl.Resources>
<Converters:BooleanToVisibleConverter x:Key="converter" />
</UserControl.Resources>
<Grid>
<Label x:Name="label"
Content="{Binding ElementName=editableTextBox, Path=Text}"
MouseLeftButtonDown="OnMouseLeftButtonClick"
Visibility="{Binding AllowEdit, ElementName=editableText,
Converter={StaticResource converter}, ConverterParameter=True}"/>
<TextBox x:Name="editableTextBox"
LostFocus="OnTextBoxLostFocus"
MouseLeave="OnTextBoxMouseLeave"
KeyDown="OnTextBoxKeyDown"
Text="{Binding Text, ElementName=editableText}"
Visibility="{Binding AllowEdit, ElementName=editableText,
Converter={StaticResource converter}, ConverterParameter=False}"/>
</Grid>
</UserControl>
У нас есть Label и TextBox. Когда AllowEdit свойство True отображается TextBox, в другом случае Label.
Текст для TextBox берется из свойства Text компонента, а текст для метки берется из TextBox. Зависимость выглядит так:
Label.Text < – TextBox.Text < – > UserControl.Text
В ресурсах у нас объявлен конвертер, который используется для свойства Visibility у Label и TextBox.
Visibility="{Binding AllowEdit, ElementName=editableText,
Converter={StaticResource converter}, ConverterParameter=False}
вот его код:namespace EditableLabel.Converters
{
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool flag = (bool) value;
bool inverted = false;
if (parameter != null)
{
inverted = Boolean.Parse(parameter.ToString());
}
return inverted ?
(flag ? Visibility.Hidden : Visibility.Visible)
: (flag ? Visibility.Visible : Visibility.Hidden);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Метод ConvertBack очень редко используется, в некоторых случаях при двунаправленном биндинге (расскажу в другой раз), поэтому всегда остается таким и не вызывается в данном случае.
В метод Convert, в параметр value приходит значение, которое мы биндим, в данном случае AllowEdit, а в parameter будет передано значение, которое мы указали в ConverterParameter (если не указали - null соответственно).
ConverterParameter не единственный способ передать параметры в класс конвертора. Если вам надо передать несколько параметров это можно сделать так:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibleConverter : IValueConverter
{
public string AdditionalValue { get; set; }
public int AnotherValue { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
}
и затем использовать в разметке или в коде так:
<Converters:BooleanToVisibleConverter
x:Key="converter"
AdditionalValue="asd"
AnotherValue="2" />
Единственная проблема, если у вас биндятся ListBoxItem’s, например, и у каждого свое значение для конвертора, то такой способ объявления конвертора со свойствами не прокатит. Этот способ хорошо когда конвертор используется 1 раз. В другом случае придется использовать IMultiValueConverter. Принцип тот же самый, только в Convert передается object[] values.
ValueConversion атрибут используется для того, чтоб быстрей было понять, из какого в какой тип происходит конвертация. Этот атрибут не обязателен, просто best practice + удобство чтения.
Код событий:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace EditableLabel.Control
{
/// <summary>
/// Interaction logic for EditableText.xaml
/// </summary>
public partial class EditableText : UserControl
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof (string), typeof (EditableText));
public static readonly DependencyProperty AllowEditProperty =
DependencyProperty.Register("AllowEdit", typeof (bool), typeof (EditableText));
public bool AllowEdit
{
get { return (bool) GetValue(AllowEditProperty); }
set { SetValue(AllowEditProperty, value); }
}
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public EditableText()
{
InitializeComponent();
}
private void OnMouseLeftButtonClick(object sender, MouseButtonEventArgs e)
{
AllowEdit = true;
editableTextBox.Focus();
}
private void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
{
AllowEdit = false;
}
private void OnTextBoxMouseLeave(object sender, MouseEventArgs e)
{
AllowEdit = false;
}
private void OnTextBoxKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
AllowEdit = false;
}
}
}
}
Варианты для усовершенствования:
- Cделать чтоб ширна TextBox’a была такой же как у метки;
- Возможно вынести в отдельную сборку для дальнейшего использования и сделать как Custom Control, может потом захочется вид изменить или эффекты прикрутить.