.NET MAUI:CollectionView内GraphicsView的Drawable绑定失效问题求助
.NET MAUI 数据绑定问题:自定义Drawable的Score属性绑定失效
问题描述
在CollectionView中展示分数时,Label绑定AvgScore可以正常显示,但自定义ScoreGaugeDrawable的Score属性绑定AvgScore无效,直接写入具体数值(如Score="5.0")则能正常生效。
相关代码
ProceduresPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:model="clr-namespace:MediSkillApp.Model" xmlns:drawables="clr-namespace:MediSkillApp.Drawables" xmlns:viewmodel="clr-namespace:MediSkillApp.ViewModel" x:Class="MediSkillApp.View.ProceduresPage" x:DataType="viewmodel:ProceduresViewModel" Title="Alle mine indgreb"> <Grid ColumnDefinitions="*" ColumnSpacing="5" RowSpacing="0"> <CollectionView BackgroundColor="Transparent" ItemsSource="{Binding Procedures}" RemainingItemsThresholdReachedCommand="{Binding GetProceduresCommand}" RemainingItemsThreshold="5"> <CollectionView.ItemTemplate> <DataTemplate x:DataType="model:Procedure"> <VerticalStackLayout> <Frame Margin="5"> <Grid ColumnDefinitions="64,*, 64"> <Image Grid.Column="0" Source="{Binding Icon}" HeightRequest="48" WidthRequest="48" HorizontalOptions="Start"/> <VerticalStackLayout Grid.Column="1"> <Label Text="{Binding ProcedureTypeString}" Style="{StaticResource Heading}"/> <Label Text="{Binding OpRoleString}" Style="{StaticResource NormalLabel}"/> <Label Text="{Binding Date, StringFormat='{0:dd/MM-yyyy}'}" Style="{StaticResource NormalLabel}"/> </VerticalStackLayout> <VerticalStackLayout Grid.Column="2" IsVisible="{Binding IsScored}"> <!-- 此绑定正常工作 --> <Label Text="{Binding AvgScore, StringFormat='{0:F2}'}" HorizontalOptions="Center"/> <Image Source="scoremeter.png"/> <GraphicsView> <GraphicsView.Drawable> <!-- 此绑定失效 --> <drawables:ScoreGaugeDrawable Score="{Binding AvgScore}"/> </GraphicsView.Drawable> </GraphicsView> </VerticalStackLayout> </Grid> </Frame> </VerticalStackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> <ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand"/> </Grid> </ContentPage>
ScoreGaugeDrawable.cs
namespace MediSkillApp.Drawables; public class ScoreGaugeDrawable : BindableObject, IDrawable { public static readonly BindableProperty ScoreProperty = BindableProperty.Create(nameof(Score), typeof(double), typeof(ScoreGaugeDrawable)); public double Score { get => (double)GetValue(ScoreProperty); set => SetValue(ScoreProperty, value); } public void Draw(ICanvas canvas, RectF dirtyRect) { var centerPoint = new PointF(32, 0); var circleRadius = 5; canvas.FillColor = Colors.Black; canvas.FillCircle(centerPoint, circleRadius); canvas.StrokeColor = Colors.Black; canvas.DrawLine(centerPoint, new Point(0, Score * 10)); //仅作测试绘制 } }
Procedure.cs
namespace MediSkillApp.Model; public class Procedure { public string Identifier { get; set; } public DateTime Date { get; set; } public string GetDate { get => Date.ToString("d/M-yyyy"); } public int ProcedureType { get; set; } public string ProcedureTypeString { get; set; } public double AvgScore { get; set; } public string GetAvgScore { get { if (AvgScore == 0) return ""; return AvgScore.ToString(); } } public int OpRole { get; set; } public string OpRoleString { get; set; } public string Icon { get { switch (ProcedureType) { case 7: return Icons.IconBleed; case 8: return Icons.IconTEA; case 18: return Icons.IconTEA; default: return Icons.IconSurgery; } } } public bool IsScored => AvgScore > 0; }
ProceduresViewModel.cs
using MediSkillApp.Model; using MediSkillApp.Services; namespace MediSkillApp.ViewModel; public partial class ProceduresViewModel : BaseViewModel { public ObservableCollection<Procedure> Procedures { get; } = new(); private APIService APIservice; public ProceduresViewModel(APIService aPIservice) { APIservice = aPIservice; } [RelayCommand] public async Task GetProceduresAsync() { if(IsBusy) return; try { IsBusy = true; var procedures = await APIservice.GetProceduresAsync("8", "dawda", Procedures.Count, 15); foreach (Procedure procedure in procedures) { Procedures.Add(procedure); } } catch(Exception ex) { Debug.WriteLine(ex); } finally { IsBusy = false; } } public void ClearProcedures() { Procedures.Clear(); } }
问题分析与解决方案
核心原因
GraphicsView.Drawable 属于非可视化属性,它不会自动继承所在DataTemplate的绑定上下文(也就是当前的Procedure对象)。而Label作为VisualElement,能正常继承上下文,所以绑定有效。
解决方法
方法一:显式指定绑定源
通过RelativeSource直接引用当前的Procedure对象,让绑定能找到正确的数据源:
<GraphicsView> <GraphicsView.Drawable> <drawables:ScoreGaugeDrawable Score="{Binding AvgScore, RelativeSource={RelativeSource AncestorType={x:Type model:Procedure}}}" /> </GraphicsView.Drawable> </GraphicsView>
方法二:封装为自定义VisualElement
创建一个继承自GraphicsView的自定义View,这样它作为可视化元素就能正常继承绑定上下文:
- 创建自定义View代码
namespace MediSkillApp.Views; public class ScoreGaugeView : GraphicsView { public static readonly BindableProperty ScoreProperty = BindableProperty.Create( nameof(Score), typeof(double), typeof(ScoreGaugeView), defaultValue: 0.0, propertyChanged: OnScoreChanged); public double Score { get => (double)GetValue(ScoreProperty); set => SetValue(ScoreProperty, value); } private static void OnScoreChanged(BindableObject bindable, object oldValue, object newValue) { if (bindable is ScoreGaugeView view && view.Drawable is ScoreGaugeDrawable drawable) { drawable.Score = (double)newValue; view.Invalidate(); // 分数变化时触发重绘 } } public ScoreGaugeView() { Drawable = new ScoreGaugeDrawable(); } }
- 在XAML中引用并使用
先添加命名空间:
xmlns:views="clr-namespace:MediSkillApp.Views"
然后替换原来的GraphicsView部分:
<views:ScoreGaugeView Score="{Binding AvgScore}" />
额外优化:触发重绘
当前的ScoreGaugeDrawable在Score属性变化时不会自动重绘,需要修改绑定属性定义,添加变更回调:
public static readonly BindableProperty ScoreProperty = BindableProperty.Create( nameof(Score), typeof(double), typeof(ScoreGaugeDrawable), defaultValue: 0.0, propertyChanged: (bindable, oldVal, newVal) => { if (bindable is ScoreGaugeDrawable drawable && drawable is IInvalidatable invalidatable) { invalidatable.Invalidate(); } });
如果使用方法二的自定义View,已经在View的属性变更中调用了Invalidate,则无需此步骤。
内容的提问来源于stack exchange,提问作者KnightofniDK
相关产品推荐
相关产品推荐

