Привязка cornerRadius к двум логическим свойствам

#.net #silverlight #binding

#.net #silverlight #привязка

Вопрос:

Допустим, у меня есть граница, DataContext которой является объектом типа MyViewModel. MyViewModel имеет свойства bool, называемые RoundLeft и RoundRight. Когда значение RoundLeft равно true, я хочу, чтобы угловой радиус границы был равен 6,0,0,6. Когда RoundRight имеет значение true, я хочу 0,6,6,0. Когда оба значения истинны, я хочу 6,6,6,6.

Ниже я описал свои первые две попытки. Я еще не сдался, но я хотел посмотреть, могут ли у кого-нибудь еще быть какие-либо идеи.

Попытка # 1

Я заставил его частично работать, привязав к самому экземпляру MyViewModel (не к определенному свойству) и используя IValueConverter, который создает правильный объект cornerRadius. Это работает при начальной загрузке. Проблема в том, что привязка отслеживает изменения объекта в целом, а не изменения конкретных свойств RoundLeft и RoundRight, например, если RoundLeft изменяется, cornerRadius границы этого не делает.

Привязка:

 <Border CornerRadius="{Binding Converter={StaticResource myShiftCornerRadiusConverter}}" />
  

Конвертер:

 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var myViewModel = value as MyViewModel;
        if (myViewModel != null)
        {
            return new CornerRadius(
                myViewModel.RoundLeft ? 6 : 0,
                myViewModel.RoundRight ? 6 : 0,
                myViewModel.RoundRight ? 6 : 0,
                myViewModel.RoundLeft ? 6 : 0);
        }
        else
        {
            return new CornerRadius(6);
        }
    }
  

Попытка # 2

Это сообщение в блоге Колина Эберхардта выглядело многообещающе, но я получаю расплывчатые XamlParseExceptions и COMExceptions. Вот мой XAML:

 <Border>
<ce:MultiBindings>
    <ce:MultiBinding TargetProperty="CornerRadius" Converter="{StaticResource myCornerRadiusConverter}">
        <ce:MultiBinding.Bindings>
            <ce:BindingCollection>
                <Binding Path="RoundLeft" />
                <Binding Path="RoundRight" />
            </ce:BindingCollection>
        </ce:MultiBinding.Bindings>
    </ce:MultiBinding>
</ce:MultiBindings>
</Border>
  

Вот мой конвертер, хотя выполнение никогда не заходит так далеко, т. Е. моя точка останова никогда не достигается.

 public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values.Length == 2 amp;amp; values.All(v => v is bool))
        {
            var roundLeft = (bool)values[0];
            var roundRight = (bool)values[1];

            return new CornerRadius(
                roundLeft ? 6 : 0,
                roundRight ? 6 : 0,
                roundRight ? 6 : 0,
                roundLeft ? 6 : 0);
        }
        else
        {
            return new CornerRadius(6);
        }
    }
  

Ответ №1:

Отсутствие встроенной поддержки множественной привязки в SL делает это немного болезненным, но как насчет более простого (хотя и немного более связанного) подхода?

Поскольку у вас уже есть свойства roundLeft и roundRight в вашей виртуальной машине, которые уже в некоторой степени связаны с определенной парадигмой пользовательского интерфейса. Так почему бы просто не иметь вычисляемое свойство, которое возвращает значение cornerRadius, и просто привязать к нему?

Так, например, при изменении roundLeft вы вызываете метод для обновления вычисляемого свойства cornerRadius и вызываете уведомление об изменении этого свойства, а затем ваше представление привязывается к вычисляемому свойству cornerRadius.

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

1. 1 Спасибо за ответ; Я реализовал ваше предложение, и оно сработало. Однако затем я придумал другое решение, которое сохраняет все содержимое cornerRadius на уровне просмотра, поэтому я выбрал это.

Ответ №2:

Как насчет DataTrigger и соответствующего свойства модели:

 <Border Height="25" Width="45" BorderThickness="5" BorderBrush="Green">
    <Border.Style>
        <Style>
            <Style.Triggers>
                <DataTrigger Binding="{Binding Round}"  Value="Left">
                    <Setter Property="Border.CornerRadius" Value="6,0,0,6"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding Round}"  Value="Right">
                    <Setter Property="Border.CornerRadius" Value="0,6,6,0"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding Round}"  Value="Both">
                    <Setter Property="Border.CornerRadius" Value="6"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Border.Style>
    <TextBlock Text="{Binding Text}"/>
</Border>
  

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

1. AFAIK StyleTriggers не поддерживаются в Silverlight, и, более конкретно, я не верю, что DataTriggers также существуют.

2. @Foovandadil — Я этого не знал. Вау. Это отстой.

3. Существуют триггеры данных, но не триггеры стилей.

4. @Rich Melton да, да, это действительно отстой.

5. Спасибо за ваше предложение; Я хотел бы им воспользоваться!

Ответ №3:

Я реализовал подход, предложенный @Foovanadil, но затем мне пришла в голову другая идея: я создал новый ContentControl, который предоставляет свойства зависимости RoundLeft и RoundRight. Это, конечно, потребовало больше кода, но теперь все содержимое cornerRadius находится на уровне просмотра.

 [TemplatePart(Name = _borderPartName, Type = typeof(Border))]
public class CustomRoundedBorder : ContentControl
{
    #region Private Fields

    private const string _borderPartName = "PART_Border";
    private Border _borderPart;

    #endregion

    #region Dependency Properties

    #region DefaultCornerRadius

    //////////////////////////////////////////////////////////////////////////////
    /// <summary>
    /// Gets or sets the default corner radius, in pixels.
    /// </summary>
    //////////////////////////////////////////////////////////////////////////////
    public double DefaultCornerRadius
    {
        get { return (double)GetValue(DefaultCornerRadiusProperty); }
        set { SetValue(DefaultCornerRadiusProperty, value); }
    }

    public static readonly DependencyProperty DefaultCornerRadiusProperty = DependencyProperty.Register(
        "DefaultCornerRadius", typeof(double), typeof(CustomRoundedBorder),
        new PropertyMetadata(new PropertyChangedCallback(RoundingChanged)));

    #endregion

    #region RoundLeft

    //////////////////////////////////////////////////////////////////////////////
    /// <summary>
    /// Gets or sets a value indicating whether to round the corners on the left side of the border.
    /// </summary>
    //////////////////////////////////////////////////////////////////////////////
    public bool RoundLeft
    {
        get { return (bool)GetValue(RoundLeftProperty); }
        set { SetValue(RoundLeftProperty, value); }
    }

    public static readonly DependencyProperty RoundLeftProperty = DependencyProperty.Register(
        "RoundLeft", typeof(bool), typeof(CustomRoundedBorder),
        new PropertyMetadata(new PropertyChangedCallback(RoundingChanged)));

    #endregion

    #region RoundRight

    //////////////////////////////////////////////////////////////////////////////
    /// <summary>
    /// Gets or sets a value indicating whether to round the corners on the left side of the border.
    /// </summary>
    //////////////////////////////////////////////////////////////////////////////
    public bool RoundRight
    {
        get { return (bool)GetValue(RoundRightProperty); }
        set { SetValue(RoundRightProperty, value); }
    }

    public static readonly DependencyProperty RoundRightProperty = DependencyProperty.Register(
        "RoundRight", typeof(bool), typeof(CustomRoundedBorder),
        new PropertyMetadata(new PropertyChangedCallback(RoundingChanged)));

    #endregion

    #region EffectiveCornerRadius

    //////////////////////////////////////////////////////////////////////////////
    /// <summary>
    /// Gets the effective corner radius, based on DefaultCornerRadius and 
    /// RoundLeft and RoundRight.
    /// </summary>
    //////////////////////////////////////////////////////////////////////////////
    public double EffectiveCornerRadius
    {
        get { return (double)GetValue(EffectiveCornerRadiusProperty); }
        private set { SetValue(EffectiveCornerRadiusProperty, value); }
    }

    public static readonly DependencyProperty EffectiveCornerRadiusProperty = DependencyProperty.Register(
        "EffectiveCornerRadius", typeof(double), typeof(CustomRoundedBorder),
        new PropertyMetadata(new PropertyChangedCallback(RoundingChanged)));

    #endregion

    #endregion

    #region Overrides

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        this._borderPart = this.GetTemplateChild(_borderPartName) as Border;
        this.UpdateCornerRadius();
    }

    #endregion

    #region Private Methods

    private static void RoundingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as CustomRoundedBorder;
        if (control != null)
        {
            control.UpdateCornerRadius();
        }
    }

    private void UpdateCornerRadius()
    {
        if (this._borderPart != null)
        {
            this._borderPart.CornerRadius = new CornerRadius(
                this.RoundLeft ? this.DefaultCornerRadius : 0,
                this.RoundRight ? this.DefaultCornerRadius : 0,
                this.RoundRight ? this.DefaultCornerRadius : 0,
                this.RoundLeft ? this.DefaultCornerRadius : 0);
        }
    }

    #endregion
}
  

Затем я создал для него ControlTemplate (некоторые свойства опущены для краткости):

 <ControlTemplate x:Key="MyBorderTemplate" TargetType="ce:CustomRoundedBorder">
    <Border
        x:Name="PART_Border"
        CornerRadius="{TemplateBinding EffectiveCornerRadius}"
        >
        <ContentPresenter />
    </Border>
</ControlTemplate>
  

Затем вот где я привязал его к свойствам view-model:

 <ce:CustomRoundedBorder
    DefaultCornerRadius="6"
    RoundLeft="{Binding RoundLeft}"
    RoundRight="{Binding RoundRight}"
    Template="{StaticResource MyBorderTemplate}"
    >
    <!-- Content -->
</ce:CustomRoundedBorder>