<View> 在 XAML 中设置内容的 IList 类型的可绑定属性永远不会更新



我正在为我在整个应用程序中使用的常见容器类型制作一个自定义ContentView。自定义视图有两个可绑定的属性:显示在左上角的标题(字符串(和显示在右上角的按钮列表(列表(。

header属性(和Contents属性(按预期工作,但buttons属性根本不工作。ButtonsPropertyChanged从不激发,并且输出控制台中没有绑定错误消息。我做错了什么?

主页.xaml:

<controls:SectionContainer Header="Text blah"> <!-- Header works -->
<controls:SectionContainer.Buttons>
<!-- This does NOT work - buttons are not shown -->
<ImageButton Source="{Binding SomeIcon}"></ImageButton>
<ImageButton Source="{Binding AnotherIcon}"></ImageButton>
</controls:SectionContainer.Buttons>
<!-- Actual contents here -- this works, the contents are shown -->
</controls:SectionContainer>

SectionContainer.xaml:

<ContentView.Content>
<Frame Style="{StaticResource SectionFrame}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Label x:Name="HeaderLabel" Grid.Column="0" Grid.Row="0" VerticalTextAlignment="Start" VerticalOptions="Start" Style="{StaticResource Header}"></Label>
<StackLayout x:Name="ButtonsContainer" Grid.Row="0" Grid.Column="1" Orientation="Horizontal" HorizontalOptions="End"></StackLayout>
<StackLayout Grid.Column="0" Grid.Row="1" x:Name="Container" Style="{StaticResource Section}"></StackLayout>
</Grid>
</Frame>
</ContentView.Content>

SectionContainer.xaml.cs:

[XamlCompilation(XamlCompilationOptions.Compile)]
[ContentProperty("Contents")]
public partial class SectionContainer : ContentView
{
public IList<View> Contents => Container.Children;
public string Header
{
get => (string)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public static readonly BindableProperty HeaderProperty = BindableProperty.Create(
nameof(Header),
typeof(string),
typeof(SectionContainer),
"",
propertyChanged: HeaderPropertyChanged);
private static void HeaderPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
((SectionContainer) bindable).HeaderLabel.Text = (string) newvalue;
}
public IList<View> Buttons
{
get => (IList<View>)GetValue(ButtonsProperty);
set => SetValue(ButtonsProperty, value);
}
public static readonly BindableProperty ButtonsProperty = BindableProperty.Create(
nameof(Buttons),
typeof(IList<View>),
typeof(SectionContainer),
new List<View>(),
BindingMode.OneWay,
propertyChanged: ButtonsPropertyChanged);
private static void ButtonsPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
Debug.WriteLine(newvalue); // This is never printed, and there are no error messages
var children= ((SectionContainer)bindable).ButtonsContainer.Children;
children.Clear();
foreach (var child in (IList<View>) newvalue)
children.Add(child);
}
public SectionContainer()
{
InitializeComponent();
}
}

Xamarin.Forms不设置属性,而是将元素添加到创建为默认值的对象中。

因此,使用defaultValueCreator(而不是defaultValue(创建的ObservableCollection(我们在其中侦听更改并填充用户控件(似乎可以工作。不确定我们是否需要propertyChanged事件,或者我们是否需要处理除单个元素添加之外的任何其他ObservableCollection事件,但我保留了它以备不时之需。

public static readonly BindableProperty ButtonsProperty = BindableProperty.Create(
nameof(Buttons),
typeof(IList<View>),
typeof(SectionContainer),
propertyChanged: ButtonsPropertyChanged,
defaultValueCreator: CreateObservableList);
private static object CreateObservableList(BindableObject bindable)
{
var oc = new ObservableCollection<View>();
// It appears Xamarin.Forms doesn't create a list containing the Views from the XAML, it uses the default value (list),
// and adds the Views one by one. Therefore, we create the default value as an ObservableCollection, then we listen
// for changes to this collection.
oc.CollectionChanged += (o, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// If this is an Add operation, simply add the view to the StackLayout.
var children = ((SectionContainer) bindable).ButtonsContainer.Children;
foreach (var child in e.NewItems.Cast<View>())
children.Add(child);
}
else
{
// Otherwise, recreate the StackLayout by clearing it, and adding all children all over again.
// (Won't bother handling all cases, since the normal one seems to be Add anyway.)
ButtonsPropertyChanged(bindable, null, o);
}
};
return oc;
}

最新更新