将 CornerRadius 绑定到两个布尔属性

发布于 2024-11-07 23:31:10 字数 2597 浏览 0 评论 0原文

假设我有一个 Border,其 DataContext 是 MyViewModel 类型的对象。 MyViewModel 具有名为 RoundLeft 和 RoundRight 的 bool 属性。当 RoundLeft 为 true 时,我希望边框的 CornerRadius 为 6,0,0,6。当 RoundRight 为 true 时,我想要 0,6,6,0。当两者都为真时,我想要 6,6,6,6。

我在下面描述了我的前两次尝试。我还没有放弃,但我想看看其他人是否有任何想法。

尝试#1

我通过绑定到 MyViewModel 实例本身(不是特定属性)并使用构建正确的 CornerRadius 对象的 IValueConverter 使其部分工作。这适用于初始负载。问题是绑定正在监视整个对象的更改,而不是监视特定 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

来自 Colin Eberhardt 的博客文章 看起来很有希望,但我得到了模糊的 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 && 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);
        }
    }

Let's say I have a Border whose DataContext is an object of type MyViewModel. MyViewModel has bool properties called RoundLeft and RoundRight. When RoundLeft is true, I want the CornerRadius of the border to be 6,0,0,6. When RoundRight is true, I want 0,6,6,0. When both are true, I want 6,6,6,6.

I've described my first two attempts below. I haven't given up yet, but I wanted to see if anyone else might have any ideas.

Attempt #1

I got it partially working by binding to the MyViewModel instance itself (not a specific property) and using an IValueConverter that builds the correct CornerRadius object. This works on initial load. The problem is that the binding is monitoring changes of the object as a whole rather than changes to the specific RoundLeft and RoundRight properties, e.g. if RoundLeft changes, the border's CornerRadius doesn't.

Binding:

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

Converter:

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);
        }
    }

Attempt #2

This blog post from Colin Eberhardt looked promising, but I'm getting vague XamlParseExceptions and ComExceptions. Here's my 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>

Here's my converter, although the execution never gets this far, i.e. my breakpoint is never hit.

public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values.Length == 2 && 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);
        }
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

ˉ厌 2024-11-14 23:31:10

SL 中缺乏内置的 MultiBinding 支持,这使得这有点痛苦,但是有一个更简单(尽管耦合程度更高)的方法怎么样。

由于您的 VM 中已经具有 roundLeft 和 roundRight 属性,因此它们已经在一定程度上与特定的 UI 范例耦合。那么为什么不只拥有一个返回 CornerRadius 值的计算属性并绑定到该属性呢?

例如,当您更改 roundLeft 时,您调用一个方法来更新计算的 CornerRadius 属性并引发该属性的更改通知,然后您的视图绑定到计算的 CornerRadius 属性。

Lack of built in MultiBinding support in SL makes this a bit of a pain but how about a simpler (albeit slightly more coupled) approach.

Since you already have roundLeft and roundRight properties in your VM that is already somewhat coupled to a specific UI paradigm. So why not just have a computed property that returns a CornerRadius value and just bind to that?

So for instance, when you change roundLeft you call a method to update the computed CornerRadius property and raise a change notification on that property and then your view binds to the computed CornerRadius property.

花开雨落又逢春i 2024-11-14 23:31:10

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>

What about DataTrigger and an appropriate model property:

<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>
相权↑美人 2024-11-14 23:31:10

我实现了 @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>

然后这是我将其绑定到视图模型属性的位置:

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

I implemented the approach @Foovanadil suggested, but then I got another idea: I created a new ContentControl that exposes RoundLeft and RoundRight dependency properties. It certainly involved more code, but now the CornerRadius stuff is all in the View layer.

[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
}

Then I created a ControlTemplate for it (some properties omitted for brevity):

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

Then here's where I bound it to the view-model properties:

<ce:CustomRoundedBorder
    DefaultCornerRadius="6"
    RoundLeft="{Binding RoundLeft}"
    RoundRight="{Binding RoundRight}"
    Template="{StaticResource MyBorderTemplate}"
    >
    <!-- Content -->
</ce:CustomRoundedBorder>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文