Линии двойного цвета Silverlight

#silverlight #drawing #lines

#silverlight #рисование #линии

Вопрос:

Мне нужно нарисовать линию с двумя соседними цветами. Единственное решение, которое я нашел, основано на двух строках, с TranslateTransform на второй. Но значение перевода должно изменяться в зависимости от ориентации линии (угла).

Есть ли какой-нибудь способ сделать это проще?

Спасибо за помощь.

Ответ №1:

Двухцветную линию можно нарисовать с помощью LinearGradientBrush с четырьмя точками градиента. Например, следующий XAML рисует горизонтальную линию, которая наполовину красная, а наполовину желтая:

     <Line X1="0" Y1="20" X2="200" Y2="20" StrokeThickness="14">
        <Line.Stroke>
            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                <GradientStop Offset="0" Color="Red" />
                <GradientStop Offset="0.5" Color="Red" />
                <GradientStop Offset="0.5" Color="Yellow" />
                <GradientStop Offset="1" Color="Yellow" />
            </LinearGradientBrush>
        </Line.Stroke>
    </Line>
  

Если вы попытаетесь использовать ту же LinearGradientBrush с линией, которая не является горизонтальной, вы быстро обнаружите, что она не будет делать то, что вы хотите. Два цвета всегда разделяются горизонтальной линией, независимо от градиента линии, в то время как вы хотите, чтобы линия, разделяющая два цвета, проходила по середине вашего Line . Для достижения этого вам необходимо изменить StartPoint и EndPoint для LinearGradientBrush . Они зависят от градиента линии, и их немного неудобно вычислять.

Чтобы продемонстрировать, как это можно сделать, я собрал шаблонный элемент управления (ниже), который рисует двухцветные линии. Color1 это цвет, который был бы слева от вас, если бы вы стояли в ( X1 , Y1 ) и смотрели в сторону ( X2 , Y2 ), и Color2 был бы справа от вас.

Обратите внимание, что этот элемент управления предполагает, что начальная и конечная заглавные буквы строки равны Flat . Если вы хотите использовать другие типы начальной или конечной заглушки (например Square , или Round ), вам нужно будет настроить вычисление overallWidth и overallHeight .

TwoColorLine.cs:

 public class TwoColorLine : Control
{
    public static readonly DependencyProperty X1Property =
        DependencyProperty.Register("X1", typeof(double), typeof(TwoColorLine), new PropertyMetadata(Coordinates_Changed));

    public static readonly DependencyProperty Y1Property =
        DependencyProperty.Register("Y1", typeof(double), typeof(TwoColorLine), new PropertyMetadata(Coordinates_Changed));

    public static readonly DependencyProperty X2Property =
        DependencyProperty.Register("X2", typeof(double), typeof(TwoColorLine), new PropertyMetadata(Coordinates_Changed));

    public static readonly DependencyProperty Y2Property =
        DependencyProperty.Register("Y2", typeof(double), typeof(TwoColorLine), new PropertyMetadata(Coordinates_Changed));

    public static readonly DependencyProperty Color1Property =
        DependencyProperty.Register("Color1", typeof(Color), typeof(TwoColorLine), new PropertyMetadata(Colors_Changed));

    public static readonly DependencyProperty Color2Property =
        DependencyProperty.Register("Color2", typeof(Color), typeof(TwoColorLine), new PropertyMetadata(Colors_Changed));

    public static readonly DependencyProperty StrokeThicknessProperty =
        DependencyProperty.Register("StrokeThickness", typeof(double), typeof(TwoColorLine), null);

    private LinearGradientBrush lineBrush;

    public TwoColorLine()
    {
        this.DefaultStyleKey = typeof(TwoColorLine);
    }

    public double X1
    {
        get { return (double)GetValue(X1Property); }
        set { SetValue(X1Property, value); }
    }

    public double Y1
    {
        get { return (double)GetValue(Y1Property); }
        set { SetValue(Y1Property, value); }
    }

    public double X2
    {
        get { return (double)GetValue(X2Property); }
        set { SetValue(X2Property, value); }
    }

    public double Y2
    {
        get { return (double)GetValue(Y2Property); }
        set { SetValue(Y2Property, value); }
    }

    public Color Color1
    {
        get { return (Color)GetValue(Color1Property); }
        set { SetValue(Color1Property, value); }
    }

    public Color Color2
    {
        get { return (Color)GetValue(Color2Property); }
        set { SetValue(Color2Property, value); }
    }

    public double StrokeThickness
    {
        get { return (double)GetValue(StrokeThicknessProperty); }
        set { SetValue(StrokeThicknessProperty, value); }
    }

    private static void Coordinates_Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var line = obj as TwoColorLine;
        if (line != null)
        {
            line.OnCoordinatesChanged();
        }
    }

    private static void Colors_Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var line = obj as TwoColorLine;
        if (line != null)
        {
            line.OnColorsChanged();
        }
    }

    private void OnCoordinatesChanged()
    {
        if (lineBrush != null)
        {
            RecalculateEndPoints();
        }
    }

    public void OnColorsChanged()
    {
        if (lineBrush != null)
        {
            UpdateColors();
        }
    }

    public void UpdateColors()
    {
        lineBrush.GradientStops[0].Color = Color1;
        lineBrush.GradientStops[1].Color = Color1;
        lineBrush.GradientStops[2].Color = Color2;
        lineBrush.GradientStops[3].Color = Color2;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        Line line = GetTemplateChild("line") as Line;
        if (line == null)
        {
            throw new InvalidOperationException("No line found in the template");
        }

        lineBrush = line.Stroke as LinearGradientBrush;
        if (lineBrush == null)
        {
            throw new InvalidOperationException("Line does not have a LinearGradientBrush as its stroke");
        }

        UpdateColors();
        RecalculateEndPoints();
    }

    private void RecalculateEndPoints()
    {
        double cos, sin;
        if (X2 == X1)
        {
            cos = 0;
            sin = (Y2 > Y1) ? 1 : -1;
        }
        else
        {
            double gradient = (Y2 - Y1) / (X2 - X1);
            cos = Math.Sqrt(1 / (1   gradient * gradient));
            sin = gradient * cos;
        }

        // These two lines assume flat start and end cap.
        double overallWidth = Math.Abs(X2 - X1)   StrokeThickness * Math.Abs(sin);
        double overallHeight = Math.Abs(Y2 - Y1)   StrokeThickness * Math.Abs(cos);

        int sign = (X2 < X1) ? -1 : 1;

        double xOffset = (sign * StrokeThickness * sin / 2) / overallWidth;
        double yOffset = (sign * StrokeThickness * cos / 2) / overallHeight;

        lineBrush.StartPoint = new Point(0.5   xOffset, 0.5 - yOffset);
        lineBrush.EndPoint = new Point(0.5 - xOffset, 0.5   yOffset);
    }
}
  

Темы Generic.xaml:

 <Style TargetType="local:TwoColorLine">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:TwoColorLine">
                <Line x:Name="line" X1="{TemplateBinding X1}" Y1="{TemplateBinding Y1}" X2="{TemplateBinding X2}" Y2="{TemplateBinding Y2}" StrokeThickness="{TemplateBinding StrokeThickness}">
                    <Line.Stroke>
                        <LinearGradientBrush>
                            <GradientStop Offset="0" />
                            <GradientStop Offset="0.5" />
                            <GradientStop Offset="0.5" />
                            <GradientStop Offset="1" />
                        </LinearGradientBrush>
                    </Line.Stroke>
                </Line>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
  

Пример использования:

 <Grid>
    <local:TwoColorLine X1="190" Y1="170" X2="150" Y2="50" Color1="Red" Color2="Green" StrokeThickness="14" />
    <local:TwoColorLine X1="210" Y1="170" X2="250" Y2="50" Color1="Red" Color2="Green" StrokeThickness="14" />
    <local:TwoColorLine X1="230" Y1="190" X2="350" Y2="150" Color1="Red" Color2="Green" StrokeThickness="14" />
    <local:TwoColorLine X1="230" Y1="210" X2="350" Y2="250" Color1="Red" Color2="Green" StrokeThickness="14" />
    <local:TwoColorLine X1="210" Y1="230" X2="250" Y2="350" Color1="Red" Color2="Green" StrokeThickness="14" />
    <local:TwoColorLine X1="190" Y1="230" X2="150" Y2="350" Color1="Red" Color2="Green" StrokeThickness="14" />
    <local:TwoColorLine X1="170" Y1="210" X2="50" Y2="250" Color1="Red" Color2="Green" StrokeThickness="14" />
    <local:TwoColorLine X1="170" Y1="190" X2="50" Y2="150" Color1="Red" Color2="Green" StrokeThickness="14" />
</Grid>
  

РЕДАКТИРОВАТЬ: пересмотрите RecalculateEndPoints() метод, чтобы поместить начальную точку и конечную точку на край линии, а не на ограничивающий прямоугольник линии. Пересмотренный метод намного проще и упрощает использование элемента управления с более чем двумя цветами.

Комментарии:

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

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

3. @Nino: Я отредактировал элемент управления, чтобы упростить адаптацию к нескольким цветам. Начальная точка и конечная точка теперь находятся на краю линии, так что видимый диапазон значений смещения составляет от 0 до 1 вместо небольшого диапазона по обе стороны от 0,5, который зависит от ориентации. Чтобы добавить прозрачный цвет посередине, должно быть достаточно добавить две точки градиента в середину коллекции GradientStops LinearGradientBrush, а также обновить индексы в UpdateColors() методе.

Ответ №2:

Вы можете создать небольшой рисунок с использованием цветов и нарисовать с его помощью одну линию.

Комментарии:

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

2. Ха — похоже, другие тоже сталкивались с этим. Использование шейдера, по-видимому, является обходным решением, которое использовали другие, например silverscratch.blogspot.com/2010/09 /…