对于'Binding'问题必须具有非空值



我在WPF中制作了一个自定义控件,这是一个星级。当有5颗星时,这些应该是金色的。

<UserControl x:Class="Lama.Wpf.Controls.RatingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:Lama.Wpf.Controls"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<ControlTemplate x:Key="RatingTemplate" TargetType="{x:Type ToggleButton}">
<Viewbox>
<Path Name="star" Fill="White" Opacity="0.2"
Data="F1 M 145.637,174.227L 127.619,110.39L 180.809,70.7577L 114.528,68.1664L 93.2725,5.33333L 70.3262,67.569L 4,68.3681L 56.0988,109.423L 36.3629,172.75L 91.508,135.888L 145.637,174.227 Z" />
</Viewbox>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Property="IsChecked" Value="True" />
<Condition
Binding="{Binding Rating, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:RatingControl}}"
Value="5" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="star" Property="Fill" Value="Gold" />
<Setter TargetName="star" Property="Opacity" Value="1" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0" Tag="1" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
<ToggleButton Grid.Column="1" Tag="2" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
<ToggleButton Grid.Column="2" Tag="3" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
<ToggleButton Grid.Column="3" Tag="4" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
<ToggleButton Grid.Column="4" Tag="5" Padding="2" Template="{StaticResource RatingTemplate}"
Click="ClickEventHandler" />
</Grid>
</UserControl>
public partial class RatingControl
{
private const int Max = 5;
public static readonly DependencyProperty RatingProperty = DependencyProperty.Register(nameof(Rating),
typeof(int),
typeof(RatingControl),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
RatingChanged));
public int Rating
{
get => (int)GetValue(RatingProperty);
set
{
if (value < 0)
{
SetValue(RatingProperty, 0);
}
else if (value > Max)
{
SetValue(RatingProperty, Max);
}
else
{
SetValue(RatingProperty, value);
}
}
}
public RatingControl()
{
InitializeComponent();
}
private static void RatingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = sender as RatingControl;
var newVal = (int)e.NewValue;
var children = ((Grid)(item.Content)).Children;
ToggleButton button;
for (var i = 0; i < newVal; i++)
{
button = children[i] as ToggleButton;
if (button != null)
button.IsChecked = true;
}
for (var i = newVal; i < children.Count; i++)
{
button = children[i] as ToggleButton;
if (button != null)
button.IsChecked = false;
}
}
private void ClickEventHandler(object sender, RoutedEventArgs args)
{
var button = sender as ToggleButton;
if (button == null)
{
return;
}
var newValue = int.Parse(button.Tag.ToString());
Rating = newValue;
}
}

如果我运行这个,我会得到这个异常:

InvalidOperationException:"Binding"必须具有非null值。

我的条件中绑定了错误的东西吗?因为如果我删除Rating-绑定,它是有效的,但我在这里没有看到我的错误。

您的Condition绑定中存在多个问题:

  • MultiDataTrigger中的第一个绑定使用了设置PropertyCondition,这是错误的。

    对于MultiDataTrigger,集合中的每个条件都必须设置Binding和Value属性。有关详细信息,请参见绑定。

    改为使用绑定到SelfRelativeSource设置Binding属性。

    <Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="True"/>
    
  • Rating的第二个绑定是错误的,因为只有当评级恰好为五星时,它才会将任何恒星设置为金色和不透明的。因此,没有选择任何其他评级的恒星。

为了解决这些问题,可以在IsChecked属性上使用一个简单的Trigger

<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="star" Property="Fill" Value="Gold" />
<Setter TargetName="star" Property="Opacity" Value="1" />
</Trigger>
</ControlTemplate.Triggers>

如果你真的要求只让星星变成金色和不透明的,如果评级正好是5,那么你可以如上所述更正MultiDataTrigger

<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="True"/>
<Condition Binding="{Binding Rating, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:RatingControl}}" Value="5"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="star" Property="Fill" Value="Gold" />
<Setter TargetName="star" Property="Opacity" Value="1" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</ControlTemplate.Triggers>

关于价值强制的另一个注意事项。正如@Clemens在评论中所说,在XAML中设置属性,例如下面这样的设置将绕过您的setter和直接呼叫CCD_ 14。您的setter应该只调用SetValue,因为在XAML中或通过属性设置属性时,行为会有所不同。

<local:RatingControl Rating="{Binding SomeProperty}"/>

您可以在依赖属性声明中指定值强制回调,而不是在setter中进行检查。

public static readonly DependencyProperty RatingProperty = DependencyProperty.Register(
nameof(Rating), typeof(int), typeof(RatingControl), new FrameworkPropertyMetadata(
0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, RatingChanged, CoerceRating));

然后创建一个CoerceRating方法,该方法包含您的检查并返回相应的值。

private static object CoerceRating(DependencyObject d, object baseValue)
{
var value = (int)baseValue;
if (value < 0)
{
return 0;
}
if (value > Max)
{
return Max;
}
return value;
}

最后,从Rating的setter中删除所有检查。

public int Rating
{
get => (int)GetValue(RatingProperty);
set => SetValue(RatingProperty, value);
}

当通过SetValue设置属性时,将自动调用值强制回调,从而确保Rating值在有效间隔内。

最新更新