'XAML/MVVM help for implementing screen grid
As a complete beginner to XAML/MVVM I would like to request some help with implementing a 24x14 letter screen.
The XAML implementation is quite easy I think with something like
<Grid x:Name="ScreenGrid"
RowDefinitions="*, *, *, *, *, *, *, *, *, *, *, *, *, *"
ColumnDefinitions="*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*"/>
However what I am struggling with is the Data Binding.
I would like to write something like an "UpdateScreen()" Method, that populates that grid with data where
- Every Grid-Cell has 1 Letter
- Every letter has a color (red, green, ...)
- Every letter is either big or small
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()
{
[.?.]
}
}
Which is supposed to communicate with the actual logic of the app, which generates a ScreenText[], that is passed back to the ViewModel.
How do I wire this up to the ScreenGrid?
Thanks for your help.
Solution 1:[1]
The solution for me (minimal example) looks like this:
In the XAML the grid gets defined like this
<Grid x:Name="ScreenGrid"
RowDefinitions="*, *, *, *, *, *, *, *, *, *, *, *, *, *"
ColumnDefinitions="*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*,*"/>
The ViewModel looks like this (with the help of Toolkit.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));
}
}
And finally the 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);
}
}
}
}
Solution 2:[2]
Here is an approach that programmatically adds cells as children of grid, with each cell bound to an element of a list in viewmodel.
Given:
public class ScreenText
{
// I think binding to xaml requires string, not char.
public string Value {
get => value.ToString();
}
...
}
and ScreenViewModel.cs:
public class ScreenViewModel : ObservableObject
{
// List or ObservableCollection (not Array) usually used for Xamarin Forms.
public ObservableCollection<ScreenText> Texts { get; } = new ObservableCollection<ScreenText>();
...
}
MainPage.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)];
}
}
ALTERNATIVE:
For anyone not using a viewmodel, see my answer here.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Dokug |
Solution 2 | ToolmakerSteve |