Привязка .NET MAUI к GraphicsView

У меня проблемы с пониманием того, как работает привязка в .NET MAUI. Моя цель состоит в том, чтобы иметь два способа отображения оценки внутри CollectionView: метка с числом и графическое представление. Привязка партитуры к метке работает нормально, а вот привязка к Drawable — нет. Если я пишу число вместо использования привязки, оно передается просто отлично.

Что я делаю не так?

ПроцедурыPage.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>

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
    }
}

Процедура.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;
}

ПроцедурыViewModel.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();
    }
}

Где определено свойство AvgScore? Я предполагаю, что это в ProceduresViewModel. Вы должны показать соответствующие биты этой ViewModel.

Julian 15.04.2023 08:50

Вы поставили точку останова в методе Draw(), чтобы увидеть, как часто он вызывается и каково значение Score?

Julian 15.04.2023 08:57

Я обновил модель ViewModel и процедуру. Когда я ставлю точку останова в Draw, она вызывается только тогда, когда я этого ожидал, т. е. когда IsScored имеет значение true (определено в procedure.cs), но значение равно 0.

KnightofniDK 15.04.2023 09:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
158
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я смог воспроизвести проблему. Кажется, что Drawables нельзя использовать с BindableProperty, по крайней мере, это не имеет никакого эффекта, значение свойства не обновляется.

Однако мне удалось найти обходной путь для этой проблемы. Вместо добавления свойства Score к ScoreGaugeDrawable вы можете добавить его к GraphicsView, расширив его через наследование.

Вы можете удалить базовый класс BindableObject, а также привязываемый ScoreProperty из ScoreGaugeDrawable и превратить свойство Score в обычное свойство с геттером и сеттером по умолчанию:

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
    }
}

Затем создайте 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();
    }
}

Таким образом, оценка должна быть передана в GraphicsView, который (к сожалению) теперь должен знать о ScoreGaugeDrawable. Что делает этот код, так это то, что он получает любые обновления привязываемого ScoreProperty и передает значение ScoreGaugeDrawable. Если значение изменилось и Drawable имеет тип ScoreGaugeDrawable, устанавливается новое значение, а затем представление становится недействительным, что вызывает перерисовку.

Вы можете использовать ScoreGraphicsView и ScoreGaugeDrawable вот так в своем XAML:

<drawables:ScoreGraphicsView
    Score = "{Binding AvgScore}">
    <drawables:ScoreGraphicsView.Drawable>
        <drawables:ScoreGaugeDrawable/>
    </drawables:ScoreGraphicsView.Drawable>
</drawables:ScoreGraphicsView>

Это не идеально, но должно решить вашу проблему на данный момент. Я проверил это сам в своем репозитории MAUI Samples, и он работает довольно хорошо.

ewerspej, ты гений! Большое спасибо, я был близок к тому, чтобы сойти с ума, и вы сделали мой день. Кстати, в вашем ответе есть несколько опечаток (RadiusProperty должен быть ScoreProperty в «наборе», а ScoreDrawable должен быть ScoreGaugeDrawable, чтобы соответствовать исходному названию).

KnightofniDK 15.04.2023 19:38

Спасибо, я исправил это. Это произошло потому, что я сам попробовал решение, а затем скопировал его в ответ, прежде чем корректировать имена.

Julian 15.04.2023 20:42

Другие вопросы по теме

Привязка данных InvHeader/InvItems с использованием Maui XAML во вложенном StackLayout, где InvHeader.CustId = InvItem.CustId?
Как изменить цвет переднего плана частичной строки внутри ячейки DataGrid в WPF?
Обработка элементов массива для ListView DataTemplate в UWP
Введите строку | номер | Дата» не может быть назначена типу «Дата» при попытке привязки к dx-date-box
Как я могу прочитать два значения из TextBox, разделенных некоторым разделителем, с помощью Converter, а затем привязать их к двум разным свойствам?
Форматирование текста в выражении привязки Xamarin
Как отображать изображения и текст с помощью нокаута?
Ошибки привязки при использовании CompositeCollection в качестве ItemSource с типами переменных в MenuItem
WPF - я пытаюсь привязать значение индикатора выполнения к методу, который вычисляет процент на основе двух текстовых полей - все внутри элемента дерева
YA - MAUI CollectionView не обновляется при изменении коллекции моделей представления