在.NET MAUI应用中,ObservableCollection项属性的更改未触发ContentView中的UI更新。

huangapple go评论81阅读模式
英文:

Change in ObservableCollection item property not triggering UI update in ContentView in .NET MAUI app

问题

在我的.NET MAUI应用程序中,我有一个显示一些数据和一个点赞按钮的动态更新的功能。由于我在同一应用程序中有不同的此功能变种,我创建了一个带有BindablePropertyContentView,以便我可以重复使用我的代码。

最初,一切似乎都正常工作,但当用户点击点赞按钮时,即使请求在父视图模型方法中得到正确处理,它也不会触发UI更新。

如果我只是将相同的XAML代码复制并粘贴到ContentPage中,而不使用ContentView,一切都正常工作,并且对IsLiked属性的任何更改都会触发UI更新。一切似乎都与ContentView正确连接。例如,点击按钮确实调用了父页面视图模型中的LikeButtonTapped。有什么可能引起这个问题的想法吗?

为了简洁起见,我在这里简化了一些事情。这是FeedItemComponent.xaml卡片的示例:

<ContentView...>
   <Grid
      RowDefinitions="20,*,30">

      <Label
         Grid.Row="0"
         x:Name="ItemTitle"
         Color="Black" />

      <Label
         Grid.Row="1"
         x:Name="ItemBody"
         Color="Black" />

      <Button
         Grid.Row="2"
         x:Name="LikeButton" />
      <Image
         Grid.Row="2"
         x:Name="LikedImage"
         Source="heart.png" />
   </Grid>
</ContentView>

这是FeedItemComponent.xaml.cs,即FeedItemComponent的代码:

public partial class FeedItemComponent : ContentView
{
   public static readonly BindableProperty FeedItemContentProperty =
        BindableProperty.Create(nameof(FeedItemContent),
			typeof(FeedItemModel),
			typeof(FeedItemComponent),
			defaultValue: null,
			defaultBindingMode: BindingMode.OneWay,
			propertyChanged: OnFeedItemContentPropertyChanged);

	public static readonly BindableProperty LikeCommandProperty =
		BindableProperty.Create(nameof(LikeCommand),
			typeof(ICommand),
			typeof(FeedItemComponent));

    private static void OnFeedItemContentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var control = (FeedItemComponent)bindable;
		var feedItemContent = (FeedItemModel)newValue;
		control.ItemTitle.Text = feedItemContent.Title;
		control.ItemBody.Text = feedItemContent.Body;
		control.LikeButton.Command = control.LikeCommand;
		control.LikeButton.CommandParameter = feedItemContent;
        control.LikeButton.IsVisible = feedItemContent.IsLiked ? false : true;
        control.LikedImage.IsVisible = feedItemContent.IsLiked ? true : false;
    }

    public FeedItemModel FeedItemContent
	{
		get => (FeedItemModel)GetValue(FeedItemContentProperty);
		set => SetValue(FeedItemContentProperty, value);
	}

	public ICommand LikeCommand
	{
		get => (ICommand)GetValue(LikeCommandProperty);
		set => SetValue(LikeCommandProperty, value);
	}

    public FeedItemComponent()
	{
		InitializeComponent();
	}
}

这是FeedItem模型的示例:

public partial class FeedItemModel : ObservableObject
{
   public Guid Id { get; set; }

   public string Title { get; set; }

   public string Body { get; set; }

   [ObservableProperty]
   bool isLiked;
}

这是MainPage的视图模型(MainPageViewModel.cs):

public partial class MainPageViewModel : ObservableObject
{
   public ObservableCollection<FeedItemModel> Feed { get; } = new();

   [RelayCommand]
   async Task LikeButtonTapped(FeedItemModel feedItem)
   {
      // 进行API调用
      await _myApiService.FeedItemLiked(feedItem.Id, userId);

      // 更新动态项的“已点赞”状态
      foreach(var item in Feed)
         if(item.Id == feedItem.Id)
            item.IsLiked = true;
   }

   internal async Task Init()
   {
      // 初始化并获取初始动态数据
      var data = await _myApiService.GetData();
      if(data != null && data.Count > 0)
         foreach(var item in data)
            Feed.Add(item);
   }
}

最后,这是MainPage.xaml的XAML:

<ContentPage...>
   <ContentPage.Resources>
      <ResourceDictionary>
         <DataTemplate
            x:Key="FeedItemTemplate"
            x:DataType="model:FeedItemModel">
               <controls:FeedItemComponent
                  FeedItemContent="{Binding .}"
                  LikeCommand="{Binding Source={x:Reference Name=Feed}, Path=BindingContext.LikeButtonTappedCommand}" />
         </DataTemplate>
      </ResourceDictionary>
   </ContentPage.Resources>
   <CollectionView
      x:Name="Feed"
      ItemsSource="{Binding Feed}"
      ItemTemplate="{StaticResource FeedItemTemplate}">
   </CollectionView>
</ContentPage>

有没有任何想法,为什么对FeedObservableCollection项的更新不会触发UI更新,如果我使用ContentView组件?

英文:

In my .NET MAUI app, I have a feed that displays some data along with a like button. Because I have different variations of this feed in the same app, I created a ContentView with BindableProperty's so that I can reuse my code.

Initially, things appear to work fine but when user taps the like button, even though the request gets processed correctly in parent view model method, it doesn't invoke a UI update.

If I simply copy and paste the same XAML code into the ContentPage and NOT use a ContentView, everything works fine and any change to IsLiked property triggers a UI update. Everything seems to be wired correctly with the ContentView. For example, tapping the button, does call the LikeButtonTapped in parent page's view model. Any idea what maybe causing this?

For brevity, I simplified things a bit here. Here's the FeedItemComponent.xaml card:

&lt;ContentView...&gt;
   &lt;Grid
      RowDefinitions=&quot;20,*,30&quot;&gt;

      &lt;Label
         Grid.Row=&quot;0&quot;
         x:Name=&quot;ItemTitle&quot;
         Color=&quot;Black&quot; /&gt;

      &lt;Label
         Grid.Row=&quot;1&quot;
         x:Name=&quot;ItemBody&quot;
         Color=&quot;Black&quot; /&gt;

      &lt;Button
         Grid.Row=&quot;2&quot;
         x:Name=&quot;LikeButton&quot; /&gt;
      &lt;Image
         Grid.Row=&quot;2&quot;
         x:Name=&quot;LikedImage&quot;
         Source=&quot;heart.png&quot; /&gt;
   &lt;/Grid&gt;
&lt;/ContentView&gt;

Here's the code behind for FeedItemComponent.xaml.cs i.e. FeedItemComponent:

public partial class FeedItemComponent : ContentView
{
   public static readonly BindableProperty FeedItemContentProperty =
        BindableProperty.Create(nameof(FeedItemContent),
			typeof(FeedItemModel),
			typeof(FeedItemComponent),
			defaultValue: null,
			defaultBindingMode: BindingMode.OneWay,
			propertyChanged: OnFeedItemContentPropertyChanged);

	public static readonly BindableProperty LikeCommandProperty =
		BindableProperty.Create(nameof(LikeCommand),
			typeof(ICommand),
			typeof(FeedItemComponent));

    private static void OnFeedItemContentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var control = (FeedItemComponent)bindable;
		var feedItemContent = (FeedItemModel)newValue;
		control.ItemTitle.Text = feedItemContent.Title;
		control.ItemBody.Text = feedItemContent.Body;
		control.LikeButton.Command = control.LikeCommand;
		control.LikeButton.CommandParameter = feedItemContent;
        control.LikeButton.IsVisible = feedItemContent.IsLiked ? false : true;
        control.LikedImage.IsVisible = feedItemContent.IsLiked ? true : false;
    }

    public FeedItemModel FeedItemContent
	{
		get =&gt; (FeedItemModel)GetValue(FeedItemContentProperty);
		set =&gt; SetValue(FeedItemContentProperty, value);
	}

	public ICommand LikeCommand
	{
		get =&gt; (ICommand)GetValue(LikeCommandProperty);
		set =&gt; SetValue(LikeCommandProperty, value);
	}

    public FeedItemComponent()
	{
		InitializeComponent();
	}
}

Here's the FeedItem model:

public partial class FeedItemModel : ObservableObject
{
   public Guid Id { get; set; }

   public string Title { get; set; }

   public string Body { get; set; }

   [ObservableProperty]
   bool isLiked;
}

Here's the view model (MainPageViewModel.cs) for the MainPage:

public partial class MainPageViewModel : ObservableObject
{
   public ObservableCollection&lt;FeedItemModel&gt; Feed { get; } = new();

   [RelayCommand]
   async Task LikeButtonTapped(FeedItemModel feedItem)
   {
      // Make API call
      await _myApiService.FeedItemLiked(feedItem.Id, userId);

      // Update feed item &quot;Liked&quot; status
      foreach(var item in Feed)
         if(item.Id == feedItem.Id)
            item.IsLiked = true;
   }

   internal async Task Init()
   {
      // Initialize and fetch initial feed data
      var data = await _myApiService.GetData();
      if(data != null &amp;&amp; data. Count &gt; 0)
         foreach(var item in data)
            Feed.Add(item);
   }
}

And finally, here's the XAML for MainPage.xaml:

&lt;ContentPage...&gt;
   &lt;ContentPage.Resources&gt;
      &lt;ResourceDictionary&gt;
         &lt;DataTemplate
            x:Key=&quot;FeedItemTemplate&quot;
            x:DataType=&quot;model:FeedItemModel&quot;&gt;
               &lt;controls:FeedItemComponent
                  FeedItemContent=&quot;{Binding .}&quot;
                  LikeCommand=&quot;{Binding Source={x:Reference Name=Feed}, Path=BindingContext.LikeButtonTappedCommand}&quot; /&gt;
         &lt;/DataTemplate&gt;
      &lt;/ResourceDictionary&gt;
   &lt;/ContentPage.Resources&gt;
   &lt;CollectionView
      x:Name=&quot;Feed&quot;
      ItemsSource=&quot;{Binding Feed}&quot;
      ItemTemplate=&quot;{StaticResource FeedItemTemplate}&quot;&gt;
   &lt;/CollectionView&gt;
&lt;/ContentPage&gt;

Any idea why updates to items of the ObservableCollection for Feed don't trigger a UI update IF I use ContentView component?

答案1

得分: 1

以下是翻译好的部分:

你所缺少的知识是如何将数据绑定到自定义组件的属性,因为该组件需要继承其父组件的 BindingContext(因此在绑定时需要进行一些特殊处理)。

<ContentView x:Name="me" ..>

  <Button x:Name="LikeButton"
    IsVisible="{Binding FeedItemContent.IsLiked, Source={x:Reference me}}" />
  • 在需要的地方使用反向布尔值转换器。我在上面没有提到这一点。
英文:

The knowledge you are missing, is how to data bind to properties of a custom component, since the component needs to inherit BindingContext from its parent (so you have to do something special when Binding).

&lt;ContentView x:Name=&quot;me&quot; ..&gt;

  &lt;Button x:Name=&quot;LikeButton&quot;
    IsVisible=&quot;{Binding FeedItemContent.IsLiked, Source={x:Reference me}}&quot; /&gt;
  • Use the inverse boolean value converter, where needed. I left that out above.

huangapple
  • 本文由 发表于 2023年7月20日 13:07:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76726813.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定