Настройки элемента Slider в WinRT

54

В рассмотренной в предыдущей статье программе ColorScroll, текущие значения Slider выводились в элементах TextBlock в шестнадцатеричном виде. Использовать передачу значений из файла отделенного кода не обязательно. Задачу можно решить посредством привязки данных из Slider в TextBlock. Все, что для этого нужно - преобразователь привязки (converter binding), способный преобразовать double в строку из двух шестнадцатеричных цифр.

К сожалению, класс FormattedStringConverter, описанный в статье «Элемент StackPanel в WinRT», в данном случае не подойдет. Если хотите, можете попробовать, но вы убедитесь, что шестнадцатеричная спецификация «X2» может использоваться только с целочисленными типами, а свойство Value элемента управления Slider относится к типу double.

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

Работая с элементами управления Slider в программе ColorScroll, можно заметить интересную деталь: Slider отображает текущее значение в маленьком окне подсказки. Это удобная функция, однако подсказка выводит значение в десятичном виде, в нашей программе используются шестнадцатеричные значения.

Если вы считаете, что видеть текущее значение Slider и в десятичной, и в шестнадцатеричной системе удобно - переходите к следующему разделу. Если вы предпочитаете, чтобы оба значения выводились по одному образцу, то есть в шестнадцатеричной системе - класс Slider определяет свойство ThumbToolTipValueConverter, в котором задается класс для выполнения нужного форматирования. Класс должен реализовать интерфейс IValueConverter, который также реализуется для написания преобразователей привязки.

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

Ниже показан класс для преобразования double в эквивалентную строку из двух шестнадцатеричных цифр. Имя класса оказывается едва ли не длиннее его кода:

using System;
using Windows.UI.Xaml.Data;

namespace WinRTTestApp
{
    public class DoubleToStringHexByteConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, 
            object parameter, string language)
        {
            return ((int)(double)value).ToString("X2");
        }

        public object ConvertBack(object value, Type targetType, 
            object parameter, string language)
        {
            return value;
        }
    }
}

Преобразователь может использоваться не только для форматирования текста подсказки, но и как преобразователь привязки для вывода значения Slider в TextBlock. Следующая разновидность программы ColorScroll показывает, как это делается. (Для простоты эта версия не реагирует на изменение пропорций.) Файл XAML создает экземпляр преобразователя в секции Resources:

<Page.Resources>
     <local:DoubleToStringHexByteConverter x:Key="hexConverter" />
        
     ...
</Page.Resources>

Ниже приведен первый набор TextBlock и Slider. Для ссылки на ресурс hexConverter в Slider используется простое расширение разметки StaticResource, a TextBlock использует конструкцию Binding, которую я для наглядности разбил на три строки:

<!-- Красный ползунок -->
<TextBlock Text="R"
           Grid.Column="0"
           Grid.Row="0"
           Foreground="Red" />

<Slider Name="redSlider"
        Grid.Column="0"
        Grid.Row="1"
        Foreground="Red"
        ThumbToolTipValueConverter="{StaticResource hexConverter}"
        ValueChanged="Slider_ValueChanged" />

<TextBlock Name="redValue"
           Grid.Column="0"
           Grid.Row="2"
           Foreground="Red"
           Text="{Binding ElementName=redSlider, Path=Value,
                          Converter={StaticResource hexConverter}}"/>
                          
...        

Поскольку обработчику ValueChanged уже не нужно обновлять надписи TextBlock, этот фрагмент кода был удален, но обработчику по-прежнему необходимо вычислить новый цвет:

private void Slider_ValueChanged(
   object sender, RangeBaseValueChangedEventArgs e)
{
    byte r = (byte)redSlider.Value;
    byte g = (byte)greenSlider.Value;
    byte b = (byte)blueSlider.Value;

    brushResult.Color = Color.FromArgb(255, r, g, b);
}

Определение ThumbToolTipValueConverter можно убрать из тегов элементов управления Slider и переместить его в стиль Slider:

<Page.Resources>
    ...

    <Style TargetType="Slider">
        <Setter Property="Maximum" Value="255" />
        <Setter Property="Orientation" Value="Vertical" />
        <Setter Property="IsDirectionReversed" Value="True" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="ThumbToolTipValueConverter" Value="{StaticResource hexConverter}" />
    </Style>
</Page.Resources>

А нельзя ли пойти еще дальше в направлении привязки данных и вообще исключить обработчик ValueChanged? Безусловно, это было бы возможно, если бы привязки можно было определять для отдельных свойств Color, как в следующем примере:

<!-- Работать не будет! -->
<Rectangle Name="rectangleResult"
           Grid.Column="1"
           Grid.Row="0">
    <Rectangle.Fill>
        <SolidColorBrush>
            <SolidColorBrush.Color>
                <Color A="255" B="{Binding ElementName=blueSlider, Path=Value}"
                       R="{Binding ElementName=redSlider, Path=Value}"
                       G="{Binding ElementName=greenSlider, Path=Value}"/>
            </SolidColorBrush.Color>
        </SolidColorBrush>
    </Rectangle.Fill>
</Rectangle>

К сожалению, приемники привязки должны поддерживаться свойствами зависимости, а у свойств Color такой поддержки нет. Да ее и не может быть, потому что свойства зависимости могут реализоваться только в классах, производных от DependencyObject, a Color вообще не является классом - это структура.

Свойство Color класса SolidColorBrush поддерживается свойством зависимости, и оно может быть приемником привязки данных. Однако в нашей программе свойству Color необходимы три значения, a Windows Runtime не поддерживает привязку данных с несколькими источниками.

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

Использование Slider для рисования

Следующая программа представляет собой версию популярной игрушки, изобретенную около 50 лет назад. Умело манипулируя двумя элементами управления Slider (вертикальным и горизонтальным), пользователь управляет воображаемым пером, двигающемся по непрерывной ломаной. В СССР эта игрушка была известна под названием «Волшебный экран».

Файл XAML определяет элемент Grid размером 2x2, но на экране большую часть места занимает одна ячейка с большим элементом Border и Polyline. Вертикальный элемент управления Slider находится у левого края, а горизонтальный - у нижнего. Ячейка в левом нижнем углу пуста:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Slider Name="ySlider"
                Grid.Row="0"
                Grid.Column="0"
                Orientation="Vertical"
                IsDirectionReversed="True"
                Margin="0 18"
                ValueChanged="OnSliderValueChanged" />

        <Slider Name="xSlider"
                Grid.Row="1"
                Grid.Column="1"
                Margin="18 0"
                ValueChanged="OnSliderValueChanged" />

        <Border Grid.Row="0"
                Grid.Column="1"
                BorderBrush="{StaticResource ApplicationForegroundThemeBrush}"
                BorderThickness="3 0 0 3"
                Background="#C0C0C0"
                Padding="24"
                SizeChanged="OnBorderSizeChanged">

            <Polyline Name="polyline"
                      Stroke="#404040"
                      StrokeThickness="3"
                      Points="0 0" />
        </Border>
</Grid>

При определении элементов Grid крайние строки и столбцы очень часто определяются со спецификацией Auto, а внутренняя область делается как можно большей, для чего используется спецификация *. В Windows 8 нет элемента DockPanel, но его поведение легко имитируется при использовании Grid.

Свойства Margin элементов управления Slider были подобраны экспериментальным путем. Чтобы программа работала интуитивно, диапазон значений Slider должен быть равен количеству пикселов между минимальной и максимальной позицией. Вычисление значений Minimum и Maximum для каждого элемента управления Slider происходит при изменении размеров области вывода:

public sealed partial class MainPage : Page
{
        public MainPage()
        {
            this.InitializeComponent();
        }

        private void OnBorderSizeChanged(object sender, SizeChangedEventArgs args)
        {
            Border border = sender as Border;
            xSlider.Maximum = args.NewSize.Width - border.Padding.Left
                                                 - border.Padding.Right
                                                 - polyline.StrokeThickness;

            ySlider.Maximum = args.NewSize.Height - border.Padding.Top
                                                  - border.Padding.Bottom
                                                  - polyline.StrokeThickness;
        }

        private void OnSliderValueChanged(object sender, RangeBaseValueChangedEventArgs args)
        {
            polyline.Points.Add(new Point(xSlider.Value, ySlider.Value));
        }
}

После всего, что было сделано, метод «рисования» в самом конце файла выглядит просто фантастически: всего одна строка кода, которая добавляет новый объект Point в Polyline.

Рисование графики с помощью элементов Slider

Только не пытайтесь перевернуть планшет и потрясти его, чтобы начать рисование заново. Эту функцию я еще не реализовал ;)

Пройди тесты
Лучший чат для C# программистов