You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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,这样它作为可视化元素就能正常继承绑定上下文:

  1. 创建自定义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();
    }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 20:30:00