Kni*_*iDK 3 c# data-binding maui
I have problems understanding how binding works in .NET MAUI. My goal is to have two ways of displaying a score inside a CollectionView, a label with the number and a graphical representation. The binding of the score to the label works just fine, but the binding to the Drawable does not. If I write a number instead of using a binding, it is passed just fine.
What am I doing wrong?
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}">
<!-- this binding works -->
<Label Text="{Binding AvgScore, StringFormat='{0:F2}'}"
HorizontalOptions="Center"/>
<Image Source="scoremeter.png"/>
<GraphicsView>
<GraphicsView.Drawable>
<!-- this binding does not -->
<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>
Run Code Online (Sandbox Code Playgroud)
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)); //Just draw something for testing
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
我能够重现该问题。看来Drawables不能与 一起使用BindableProperty,至少没有任何效果,属性的值没有得到更新。
不过,我设法找到了解决此问题的方法。您可以通过继承扩展属性来将其添加到 ,而不是将该Score属性添加到 。ScoreGaugeDrawableGraphicsView
您可以从 中删除BindableObject基类以及可绑定属性,并将属性转换为具有默认 getter 和 setter 的常规属性:ScorePropertyScoreGaugeDrawableScore
namespace MediSkillApp.Drawables;
public class ScoreGaugeDrawable : IDrawable
{
public double Score { get; set; }
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)); //Just draw something for testing
}
}
Run Code Online (Sandbox Code Playgroud)
然后,创建一个ScoreGraphicsView继承GraphicsView并添加可绑定的对象ScoreProperty:
namespace MediSkillApp.Drawables;
public class ScoreGraphicsView : GraphicsView
{
public double Score
{
get => (double)GetValue(ScoreProperty);
set => SetValue(ScoreProperty, value);
}
public static readonly BindableProperty ScoreProperty = BindableProperty.Create(nameof(Score), typeof(double), typeof(ScoreGraphicsView), propertyChanged: ScorePropertyChanged);
private static void ScorePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is not ScoreGraphicsView { Drawable: ScoreGaugeDrawable drawable } view)
{
return;
}
drawable.Score = (double)newValue;
view.Invalidate();
}
}
Run Code Online (Sandbox Code Playgroud)
这样,分数需要传递给 GraphicsView,(不幸的是)它现在必须知道ScoreGaugeDrawable. 此代码的作用是,它接收对可绑定的任何更新ScoreProperty并将值中继到ScoreGaugeDrawable. 如果值已更改且Drawable类型为ScoreGaugeDrawable,则设置新值,然后视图失效,从而触发重绘。
您可以在 XAML 中像这样使用ScoreGraphicsViewand :ScoreGaugeDrawable
<drawables:ScoreGraphicsView
Score="{Binding AvgScore}">
<drawables:ScoreGraphicsView.Drawable>
<drawables:ScoreGaugeDrawable/>
</drawables:ScoreGraphicsView.Drawable>
</drawables:ScoreGraphicsView>
Run Code Online (Sandbox Code Playgroud)
这并不理想,但应该暂时解决您的问题。我自己在我的MAUI Samples存储库中对此进行了测试,效果非常好。