Справка XAML/MVVM для реализации экранной сетки

Как полный новичок в XAML/MVVM, я хотел бы попросить помощи в реализации экрана писем 24x14.
Реализация XAML довольно проста, я думаю, с чем-то вроде

<Grid x:Name = "ScreenGrid"
      RowDefinitions = "*, *, *, *, *, *, *, *, *, *, *, *, *, *"                  
      ColumnDefinitions = "*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*"/>

Однако то, с чем я борюсь, - это привязка данных. Я хотел бы написать что-то вроде метода «UpdateScreen()», который заполняет эту сетку данными, где

  • Каждая ячейка сетки имеет 1 букву
  • Каждая буква имеет цвет (красный, зеленый, ...)
  • Каждая буква большая или маленькая

My current ViewModel looks like this (with the help of Microsoft.Toolkit.Mvvm):
public partial class ScreenViewModel : ObservableObject
{
    [ObservableProperty]
    private ScreenText[] _screen; 
    //ScreenText consists of char value, Color color, bool isBig

    [ICommand]
    private void ButtonPressed(AppButton button)
    {
        System.Diagnostics.Debug.WriteLine($"Button {button} was pressed");
    }

    private void UpdateScreen()
    {
        [.?.]
    }
}

Который должен связываться с фактической логикой приложения, которое генерирует ScreenText[], который передается обратно в ViewModel.
Как подключить это к ScreenGrid?
Спасибо за вашу помощь.

🤔 А знаете ли вы, что...
C# поддерживает множество парадигм программирования, включая процедурное, объектно-ориентированное и функциональное программирование.


1
49
2

Ответы:

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

Дано:

public class ScreenText
{
    // I think binding to xaml requires string, not char.
    public string Value {
        get => value.ToString();
    }
    ...
}

и ScreenViewModel.cs:

public class ScreenViewModel : ObservableObject
{
    // List or ObservableCollection (not Array) usually used for Xamarin Forms.
    public ObservableCollection<ScreenText> Texts { get; } = new ObservableCollection<ScreenText>();
    ...
}

Главная страница.xaml:

<?xml version = "1.0" encoding = "utf-8" ?>
<ContentPage xmlns = "http://xamarin.com/schemas/2014/forms"
             xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class = "ManipulateGridChildren.MainPage">
    <Grid x:Name = "ScreenGrid"
        RowDefinitions = "*, *, *, *, *, *, *, *, *, *, *, *, *, *"                  
        ColumnDefinitions = "*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*"/>
</ContentPage>

MainPage.xaml.cs:

using System.Collections.Generic;
using Xamarin.Forms;

namespace ManipulateGridChildren
{
    public partial class MainPage : ContentPage
    {
        const int NRows = 14;
        static int NColumns = 24;

        public MainPage(ScreenViewModel vm)
        {
            InitializeComponent();
            // So you can access from any method.
            VM = vm;
            AddCells(vm);
            BindingContext = vm;
        }

        private ScreenViewModel VM;
        // Used a dictionary, so can add cells in any order.
        private Dictionary<int, Label> cells = new Dictionary<int, Label>();

        private void AddCells(ScreenViewModel vm)
        {
            // Grid rows and columns are numbered from "0".
            for (int row = 0; row < NRows; row++) {
                for (int column = 0; column < NColumns; column++) {
                    var cell = AddCell(row, column, vm);
                }
            }
        }

        private Label AddCell(int row, int column, ScreenViewModel vm)
        {
            var index = CellKey(row, column);
            
            var cell = new Label();
            // NOTE: I think "Value" has to be a "string" not a "char".
            cell.SetBinding(TextProperty, $"Texts[{index}].Value");
            cell.TextColor = Color.Black;
            cell.BackgroundColor = Color.White;
            cell.HorizontalTextAlignment = TextAlignment.Center;
            cell.VerticalTextAlignment = TextAlignment.Center;
            
            ScreenGrid.Children.Add(cell, column, row);
            // This dictionary makes it easier to access individual cells later.
            cells[index] = cell;
            return cell;
        }

        // Assign unique key to each cell.
        private int CellKey(int row, int column)
        {
            return row * NColumns + column;
        }

        // Can use this to access an individual cell later.
        // For example, you might change some property on that cell.
        private Label GetCell(int row, int column)
        {
            return cell[CellKey(row, column)];
    }
}

АЛЬТЕРНАТИВА:

Для тех, кто не использует модель представления, см. мой ответ здесь.


Решено

Решение для меня (минимальный пример) выглядит так:

В XAML сетка определяется следующим образом.

<Grid x:Name = "ScreenGrid"
              RowDefinitions = "*, *, *, *, *, *, *, *, *, *, *, *, *, *"
              ColumnDefinitions = "*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*"/>

ViewModel выглядит так (с помощью Инструментарий.Mvvm):

public partial class ScreenViewModel : ObservableObject
{
    const int LINES = 14;

    public ObservableCollection<ScreenText> Screen { get; } = new ObservableCollection<ScreenText>();

    public ScreenViewModel()
    {
        for (int i = 0; i < LINES; i++)
        {
            Screen.Add(new ScreenText());
        }
    }

    [ICommand]
    private void ButtonPressed(AppButton button)
    {
        System.Diagnostics.Debug.WriteLine($"Button {button} was pressed");

        //Do some business logic and manipulate the screen

        OnPropertyChanged(nameof(Screen));
    }
}

И, наконец, MainPage.Xaml.cs:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        //Setup Screen
        AddScreenCells();
    }

    private void AddScreenCells()
    {
        for (int row = 0; row < ScreenGrid.RowDefinitions.Count; row++)
        {
            for (int col = 0; col < ScreenGrid.ColumnDefinitions.Count; col++)
            {
                Label label = new Label();
                label.SetBinding(Label.TextProperty, $"{nameof(ScreenViewModel.Screen)}[{row}].{nameof(ScreenText.Letters)}[{col}]");

                ScreenGrid.Add(label, col, row);
            }
        }
    }
}