'Binding' 问题必须有非空值
Must have non-null value for 'Binding' problem
我在 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: Must have non-null value for 'Binding'.
我的condition里面是不是绑定了什么错误的东西?因为如果我删除 Rating
-Binding,它会起作用,但我在这里看不到我的错误。
您的 Condition
绑定中存在多个问题:
MultiDataTrigger
中的第一个绑定使用设置 Property
、which is wrong.
的 Condition
For a MultiDataTrigger, each condition in the collection must set both the Binding and Value properties. For more information, see Binding.
使用 RelativeSource
绑定将 Binding
属性 设置为 Self
。
<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 和
直接调用 SetValue
。您的 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
方法,其中包含您的检查和 returns 相应的值。
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
值在有效区间内。
我在 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: Must have non-null value for 'Binding'.
我的condition里面是不是绑定了什么错误的东西?因为如果我删除 Rating
-Binding,它会起作用,但我在这里看不到我的错误。
您的 Condition
绑定中存在多个问题:
的MultiDataTrigger
中的第一个绑定使用设置Property
、which is wrong.Condition
For a MultiDataTrigger, each condition in the collection must set both the Binding and Value properties. For more information, see Binding.
使用
RelativeSource
绑定将Binding
属性 设置为Self
。<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 和
直接调用 SetValue
。您的 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
方法,其中包含您的检查和 returns 相应的值。
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
值在有效区间内。