Reputation: 99
I am creating phone app in .Net Maui and I have problem with data binding/properties.
Link to git : https://github.com/wojblaz/Clicer-Game---final
This part of my app is for choosing level you will play on. On swich you choose time or points. If you choose time timer will be set on some value and you will have to click as many buttons as you can(points startion from 0) and if you choose points you have to click given amount of times as fast as posible. The value of time/points you will choose from slider - values 5,10,15.
Problems When I am debbuging method OnSliderValueChanged is never used.
I have also no idea how to make transition - the closer you are to number 5 the bigger the font of lable 5 and lower of 10.
There is also second problem with swich. I want lables to change text based on what is turned on on swich. For example it is time - I want lable time to be set on based of what is set in slider and lable points to 0 and vice versa points 15 and time 0.
This is code in xaml.
<Switch
x:Name="ModeSelector"
IsEnabled="{Binding BindingContext.SwichTimeCommmand, Source={x:Reference ModeSelector}}"
IsToggled="{Binding BindingContext.SwichPointsCommmand, Source={x:Reference ModeSelector}}"
OnColor="LightBlue"
ThumbColor="Blue"
Grid.Row="1"
Grid.Column="0"
HorizontalOptions="Center"/>
<Label Text="{Binding lTime}"
Grid.Row="1"
Grid.Column="1"
HorizontalOptions="Center"
FontSize="25"/>
<Label Text="{Binding lPoints}"
Grid.Row="1"
Grid.Column="2"
HorizontalOptions="Center"/>
<Slider x:Name="ValueSelector"
Grid.Row="3"
Grid.ColumnSpan="3"
Minimum="5"
Maximum="15"
Value="{Binding BindingContext.OnSliderValueChangedCommmand, Source={x:Reference ValueSelector}}"/>
ViewModel
[RelayCommand]
public void SwichTime()
{
time = SelectedGameValue;
lTime = SelectedGameValue.ToString();
lPoints = "0";
points = 0;
}
[RelayCommand]
public void SwichPoints()
{
points = SelectedGameValue;
lPoints = SelectedGameValue.ToString();
lTime = "0";
time = 0;
}
[RelayCommand]
public void OnSliderValueChanged(ValueChangedEventArgs args)
{
double value = args.NewValue;
if (value <=7.5)
{
SelectedGameValue = 5;
}
else if(7.5 <= value && value <= 12.5)
{
SelectedGameValue = 10;
}
else if(12.5 <= value)
{
SelectedGameValue = 15;
}
}
Code after answer. Still there is no text displayed on lables and methods are not executed
Is there way to do all methods in View Model instead of in cs? And why there is no references to methods?
As always I updated git.
Xaml
<Switch
x:Name="ModeSelector"
IsToggled="False"
Toggled="ModeSelector_Toggled"
OnColor="LightBlue"
ThumbColor="Blue"
Grid.Row="1"
Grid.Column="0"
HorizontalOptions="Center"/>
<Label Text="{Binding timeLabel}"
Grid.Row="1"
Grid.Column="1"
HorizontalOptions="Center"
FontSize="25"
FontFamily="FAR"/>
<Label Text="{Binding pointsLabel}"
Grid.Row="1"
Grid.Column="2"
HorizontalOptions="Center"
FontSize="25"/>
<Slider x:Name="ValueSelector"
Grid.Row="3"
Grid.ColumnSpan="3"
Minimum="5"
Maximum="15"
ValueChanged="OnSliderValueChanged"/>
Xaml.cs
using Clicer_Game.ViewModels;
using System.Globalization;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace Clicer_Game.Views;
public partial class ClassicMode : ContentPage
{
ClassicModelViewModel vm;
public ClassicMode()
{
InitializeComponent();
vm = new ClassicModelViewModel();
this.BindingContext = vm;
}
private void OnSliderValueChanged(object sender, ValueChangedEventArgs e) // this methods will be triggered when the value of slider changes, for example, drag the slider
{
double value = e.NewValue;
if (value <= 7.5)
{
vm.SelectedGameValue = 5;
}
else if (7.5 <= value && value <= 12.5)
{
vm.SelectedGameValue = 10;
}
else if (12.5 <= value)
{
vm.SelectedGameValue = 15;
}
//And here you can set the text of your timeLabel and your pointLabel based on the switcher ( if IsTime is true which means OnTime, else means OnPoint), 'IsTime' property will be mentioned later
if (vm.IsTime)
{
vm.timeLabel = vm.SelectedGameValue.ToString();
vm.pointsLabel = "0";
}
else
{
vm.pointsLabel = vm.SelectedGameValue.ToString();
vm.timeLabel = "0";
}
}
private void ModeSelector_Toggled(object sender, ToggledEventArgs e)
{
if (ModeSelector.IsToggled)
{
vm.IsTime = false;
}
else
{
vm.IsTime = true;
}
}
}
ViewModel
public partial class ClassicModelViewModel : ObservableObject
{
[ObservableProperty]
public string timeLabel;
[ObservableProperty]
public string pointsLabel;
private int _selectedGameValue;
private bool _isTime;
public int SelectedGameValue
{
get { return _selectedGameValue; }
set { _selectedGameValue = value; }
}
public bool IsTime
{
get
{
return _isTime;
}
set
{
_isTime = value;
}
}
}
}
Upvotes: 1
Views: 1522
Reputation: 99
I spent hours to solve this problem and I finnaly found answer. Here is my code. But could it be better? Could I use CommunityToolkit.Mvvm?
Xaml:
<Switch
x:Name="ModeSelector"
IsToggled="{Binding GameType}"
OnColor="LightBlue"
ThumbColor="Blue"
Grid.Row="1"
Grid.Column="0"
HorizontalOptions="Center"/>
<Label x:Name="TimeLabel"
Text="{Binding timeLabel}"
Grid.Row="1"
Grid.Column="1"
HorizontalOptions="Center"
FontSize="20"
FontFamily="FAR"/>
<Label Text="{Binding pointsLabel }"
Grid.Row="1"
Grid.Column="2"
HorizontalOptions="Center"
FontSize="20"/>
<Slider x:Name="ValueSelector"
Grid.Row="3"
Grid.ColumnSpan="3"
Minimum="5"
Maximum="15"
ValueChanged="OnSliderValueChanged"/>
Xaml.cs
using Clicer_Game.ViewModels;
namespace Clicer_Game.Views;
public partial class ClassicModeView : ContentPage
{
ClassicModeViewModel vm;
public ClassicModeView(ClassicModeViewModel viewModel)
{
InitializeComponent();
vm = viewModel;
this.BindingContext = vm;
}
private void OnSliderValueChanged(object sender, ValueChangedEventArgs e)
{
double value = e.NewValue;
if (value <= 7.5)
{
vm.SelectedGameValue = 5;
}
else if (7.5 <= value && value <= 12.5)
{
vm.SelectedGameValue = 10;
}
else if (12.5 <= value)
{
vm.SelectedGameValue = 15;
}
((Slider)sender).Value = vm.SelectedGameValue;
}
}
ViewModel
using System.ComponentModel;
using Clicer_Game.Models;
namespace Clicer_Game.ViewModels
{
public partial class ClassicModeViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ClassicModeModel gameOptions;
public ClassicModeViewModel()
{
LoadGameOptions();
}
private void LoadGameOptions()
{
gameOptions = new ClassicModeModel(GameType: true, SelectedGameValue: 5, Points: 0, Time: 5, TimeLabel: "0", PointsLabel: "0");
}
public string timeLabel
{
get
{
return gameOptions.TimeLabel;
}
set
{
if (value != gameOptions.TimeLabel)
{
gameOptions.TimeLabel = value;
OnPropertyChanged(nameof(timeLabel));
}
}
}
public string pointsLabel
{
get
{
return gameOptions.PointsLabel;
}
set
{
if (value != gameOptions.PointsLabel)
{
gameOptions.PointsLabel = value;
OnPropertyChanged(nameof(pointsLabel));
}
}
}
public int SelectedGameValue
{
get { return gameOptions.SelectedGameValue; }
set
{
gameOptions.SelectedGameValue = value;
if (GameType)
{
timeLabel = SelectedGameValue.ToString();
pointsLabel = "0";
}
else
{
pointsLabel = SelectedGameValue.ToString();
timeLabel = "0";
}
OnPropertyChanged(nameof(pointsLabel));
}
}
public bool GameType
{
get
{
return gameOptions.GameType;
}
set
{
gameOptions.GameType = value;
if (value)
{
timeLabel = SelectedGameValue.ToString();
pointsLabel = "0";
}
else
{
pointsLabel = SelectedGameValue.ToString();
timeLabel = "0";
}
OnPropertyChanged(nameof(GameType));
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Model
namespace Clicer_Game.Models;
public record ClassicModeModel {
public bool GameType;
public int SelectedGameValue;
public int Points;
public int Time;
public string TimeLabel;
public string PointsLabel;
public string Napis;
public ClassicModeModel(bool GameType, int SelectedGameValue, int Points, int Time, string TimeLabel, string PointsLabel)
{
this.GameType = GameType;
this.SelectedGameValue = SelectedGameValue;
this.Points = Points;
this.Time = Time;
this.TimeLabel = TimeLabel;
this.PointsLabel = PointsLabel;
}
}
Upvotes: 2
Reputation: 8220
I make some changes based on your new code. In your ClassicModelViewModel:
//INotifyPropertyChanged is notification mechanism that allows the binding infrastructure to be notified when the value of a property changes.
public partial class ClassicModelViewModel : INotifyPropertyChanged
{
int time;
int points;
public string _timeLabel;
public string _pointsLabel;
private int _selectedGameValue;
private bool _isTime;
public event PropertyChangedEventHandler PropertyChanged;
public string timeLabel
{
get
{
return _timeLabel;
}
set
{
if (value != _timeLabel)
{
_timeLabel = value;
OnPropertyChanged(nameof(timeLabel));
}
}
}
public string pointsLabel
{
get
{
return _pointsLabel;
}
set
{
if (value != _pointsLabel)
{
_pointsLabel = value;
OnPropertyChanged(nameof(pointsLabel));
}
}
}
public int SelectedGameValue
{
get { return _selectedGameValue; }
set { _selectedGameValue = value; }
}
public bool IsTime
{
get
{
return _isTime;
}
set
{
_isTime = value;
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
To change font size, you could use a ValueConverter. Just take the '10' label for example. In your ClassicMode.xaml, let the FontSize bind to your slider and use ValueConverter:
<Label Text="10"
HorizontalOptions="Center"
FontSize="{Binding Source={x:Reference ValueSelector},
Path=Value,Converter={StaticResource fontSizeChanged}}" // here i use a converter to convert slider value to font size
FontFamily="FAS"
Grid.Column="1"/>
also add the following code in your ClassicMode.xaml:
<ContentPage
...
xmlns:local="clr-namespace:Clicer_Game"
...
>
<ContentPage.Resources>
<local:FontSizeChangedConverter x:Key="fontSizeChanged" />
</ContentPage.Resources>
In your FontSizeChangedConverter, you should implement IValueConverter Interface. Try the following code:
namespace Clicer_Game
{
public class FontSizeChangedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// just an example here
int newValue;
double b = 30 - (Math.Abs((double)value - 10)) * 1;
newValue = (int)Math.Round(b);
return newValue.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
For more information, you could refer to slider, switch, Data binding basics and Binding value converters.
I hope my answer could help you.
Upvotes: 1