Binding CornerRadius to two boolean properties
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 targetT开发者_运维知识库ype, 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);
}
}
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.
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>
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>
精彩评论