DataBindings not updating with OnPropertyChanged - c#

Building my first app with Xamarian.Forms. I have my basic menu and home page built with some labels and a button so that I can bind some data and a method and to make sure the logic code for my game is working. I got my data bindings working as far as the labels go, they appear on screen. However I was unsure if my bindings weren't updating or if my command wasn't binding. So I commented out the ICommand, removed the binding and put the method to advance a turn into the code behind my xaml. Even after this, the data is not updating when the button is clicked which leads me to believe it is a problem with my OnPropertyChanged and the data bindings. I've searched the web and related questions, I've implemented a couple different ways of writing the gets and sets for the bindings, wrote my OnPropertyChanged function a few different ways and still nothing happens when the toolbar button on the home page is clicked.
Here is my HomePageViewModel.cs containing INotifyPropertyChanged
***Edited to reflect changes made since getting the day value to update:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using Engine;
using Xamarin.Forms;
namespace TestApp
{
public class HomePageViewModel : INotifyPropertyChanged
{
public static Player _player = World.Player1;
public string Day = World.TrueDay.ToString();
public string MoneyValue = Convert.ToInt32(Math.Floor(World.Player1.PlayerMoney)).ToString();
public string CurrentLocation = _player.CurrentLocation.Name;
public HomePageViewModel()
{
OnTurn = new Command(execute: On1Turn);
}
public ICommand OnTurn { get; private set; }
public string CurrentDay
{
get { return Day; }
set { Day = value; OnPropertyChanged(); }
}
public string Money
{
get { return MoneyValue; }
set { MoneyValue = value; OnPropertyChanged(); }
}
public string PlayerLocation
{
get { return CurrentLocation; }
set { CurrentLocation = value; OnPropertyChanged(); }
}
void On1Turn()
{
World.TrueDay = World.TrueDay + 1;
CurrentDay = World.TrueDay.ToString();
World.Player1.PlayerMoney = World.Player1.PlayerMoney + 1000;
MoneyValue = Convert.ToInt32(Math.Floor(World.Player1.PlayerMoney)).ToString();
OnPropertyChanged(Money);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here is my HomePage.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"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local1="clr-namespace:TestApp"
mc:Ignorable="d"
x:Class="TestApp.HomePage">
<ContentPage.BindingContext>
<local1:HomePageViewModel/>
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="+24hrs" Clicked="ToolbarItem_Clicked" />
</ContentPage.ToolbarItems>
<StackLayout Padding="20">
<StackLayout Orientation="Horizontal">
<Label Text="Money:" HorizontalOptions="Start"/>
<Label x:Name="lblPlayerMoney" Text="{Binding Money, Mode=OneWay}" HorizontalOptions="FillAndExpand"/>
</StackLayout>
<StackLayout Orientation="Horizontal" VerticalOptions="EndAndExpand" Margin="0,-40,0,0">
<Label Text="Current Location:" HorizontalOptions="CenterAndExpand"/>
<Label x:Name="lblPlayerLocation" Text="{Binding PlayerLocation, Mode=OneWay}" HorizontalOptions="CenterAndExpand"/>
</StackLayout>
<StackLayout Orientation="Horizontal" VerticalOptions="StartAndExpand">
<Label Text="Current Day:" HorizontalOptions="CenterAndExpand" Margin="30,0,0,0"/>
<Label x:Name="lblCurrentDay" Text="{Binding CurrentDay, Mode=OneWay}" HorizontalOptions="CenterAndExpand"/>
</StackLayout>
</StackLayout>
</ContentPage>
And the HomePage.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Engine;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace TestApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
//BindingContext = new HomePageViewModel();
}
private void ToolbarItem_Clicked(object sender, EventArgs e)
{
World.TrueDay = World.TrueDay + 1;
World.Player1.PlayerMoney = World.Player1.PlayerMoney + 1000;
}
}
}
Any insight into the issue would be greatly appreciated as I'm new to Xamarian.Forms and the OnPropertyChanged feature in general. Thanks for the time!
EDIT******
Here is the World.cs were I set the properties if it helps
using System;
using System.Collections.Generic;
using System.Text;
namespace Engine
{
public class World
{
public static decimal TrueDay = 1;
//public string LocationText = Player1.CurrentLocation.Name.ToString();
public static Player Player1;
public static readonly List<Location> Locations = new List<Location>();
public const int LOCATION_ID_OSHAWA = 1;
public const int LOCATION_ID_TORONTO = 2;
public static void GenerateWorld()
{
PopulateLocations();
Player1 = new Player("Jordan", LocationByID(LOCATION_ID_OSHAWA), 5000);
}
private static void PopulateLocations()
{
Location oshawa = new Location(LOCATION_ID_OSHAWA, "Oshawa");
Location toronto = new Location(LOCATION_ID_TORONTO, "Toronto");
Locations.Add(oshawa);
Locations.Add(toronto);
}
public static Location LocationByID(int id)
{
foreach (Location location in Locations)
{
if (location.ID == id)
{
return location;
}
}
return null;
}
}
}

Not too understanding logic about project, but if want to change model data can do as follow
HomePage.xaml.cs:
namespace TestApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class HomePage : ContentPage
{
HomePageViewModel homePageViewModel = new HomePageViewModel();
public HomePage()
{
InitializeComponent();
BindingContext = homePageViewModel ;
}
private void ToolbarItem_Clicked(object sender, EventArgs e)
{
homePageViewModel.CurrentDay = xxx ;
homePageViewModel.xxxxx = xxxx;
//Something like this can change model data
}
}
}
Here is a sample data binding discussion can be refer to.

Related

How to add a countdown timer to a child element in Wpf

Im quite new to coding. So far I have a WPF application that when I press submit it creates the treeview but I wanted to add a countdown timer for each child item and have it display the time remaining next to the child item. The problem is the treeview doesn't update and I dont know how to assign a timer for each child item
using Microsoft.Azure.Cosmos.Core.Collections;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace Test_v2
{
public partial class MainWindow : Window
{
public int secondsCount = 100;
public MainWindow()
{
InitializeComponent();
DispatcherTimer disTmr = new DispatcherTimer();
disTmr.Tick += new EventHandler(disTmr_Tick);
disTmr.Interval = new TimeSpan(0, 0, 1);
disTmr.Start();
}
public void disTmr_Tick(object sender, EventArgs e)
{
secondsCount--;
}
List<TreeViewItem> folderList = new List<TreeViewItem>();
public void SubmitButton_Click(object sender, RoutedEventArgs e)
{
if (Folder.Text.Length == 0)
{
ErrorBlock.Text = "Please Enter Folder Name";
return;
}
if (Name.Text.Length == 0)
{
ErrorBlock.Text = "Please Enter a Name";
return;
}
TreeViewItem parent = new TreeViewItem();
for (int i = 0; i < folderList.Count; i++)
{
if (folderList[i].Header.ToString() == Folder.Text)
{
parent = folderList[i];
break;
}
}
if (folderList.Contains(parent))
{
FolderInArrayBlock.Text = "True";
TreeViewItem newChild = new TreeViewItem();
newChild.Header = Name.Text + secondsCount.ToString();
parent.Items.Add(newChild);
}
else
{
FolderInArrayBlock.Text = "false";
TreeViewItem? treeItem = null;
treeItem = new TreeViewItem();
treeItem.Header = Folder.Text;
folderList.Add(treeItem);
treeItem.Items.Add(new TreeViewItem() { Header = Name.Text + secondsCount.ToString()});
LearningItems.Items.Add(treeItem);
}
}
}
}
First of all, if you are using Wpf, you need to use MVVM approch if you want to make a sustainable and maintainable code. This means you need to seperate View funcionalities from Model funcionalities and use a ViewModel as a bridge to be able to communicate with those two things. In Wpf we should try to use Bindings and notifypropertychange to build the brige between View and ViewModel and not use control naming for later use in code behind .cs.(Code behind is the .cs file which belongs to .xaml file ex.: MainWindow.xaml.cs)
I recommend you to take a look at this page, which explains why its so important to use MVVM in your Wpf applications: MVVM pattern
I have created a sample project which demonstrate a proper approch for your task, in my opinion.
MainWindow.xaml
<Window x:Class="TreeViewWithCountDown.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TreeViewWithCountDown"
xmlns:localviewmodels="clr-namespace:TreeViewWithCountDown.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TreeView ItemsSource="{Binding Path=Items, Mode=OneWay}">
<!--We use TreeView Resources because we bind Items as ItemSource and Items is a List of StorageItems, which can be either FolderItem or FileItem.
TreeView can display the two types differently if we specify in the Resources-->
<TreeView.Resources>
<!--Here we specify how to display a FolderItem-->
<HierarchicalDataTemplate DataType="{x:Type localviewmodels:FolderItem}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"/>
</HierarchicalDataTemplate>
<!--Here we specify how to display a FileItem-->
<DataTemplate DataType="{x:Type localviewmodels:FileItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="FileNames"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Name}"
Margin="0 0 35 0"
Grid.Column="0"/>
<TextBlock Text="{Binding Path=CountdownTime}"
Margin="0 0 15 0"
Grid.Column="1">
</TextBlock>
</Grid>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
MainWindow.xaml.cs
using System.Windows;
namespace TreeViewWithCountDown
{
public partial class MainWindow : Window
{
private ViewModel _viewModel= new ViewModel();
public MainWindow()
{
InitializeComponent();
//Really important to define where to look for the binding properties
DataContext = _viewModel;
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Timers;
using TreeViewWithCountDown.ViewModels;
namespace TreeViewWithCountDown
{
public class ViewModel : INotifyPropertyChanged
{
private List<StorageItem> _items = new List<StorageItem>();
public List<StorageItem> Items
{
get => _items;
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged();
}
}
}
public ViewModel()
{
//Filling up our Items property which will be given to the View for display
Random random = new Random();
FileItem item0 = new FileItem("file0", random.Next(0,100));
FolderItem item1 = new FolderItem("folder1");
item1.Items.Add(item0);
FileItem item2 = new FileItem("file2", random.Next(0, 100));
FileItem item3 = new FileItem("file3", random.Next(0, 100));
Timer timer = new Timer(3000);
timer.Elapsed += Time_Elapsed;
timer.Start();
Items.Add(item1);
Items.Add(item2);
Items.Add(item3);
}
private void Time_Elapsed(object sender, ElapsedEventArgs e)
{
foreach (StorageItem item in Items)
{
if (item is FileItem fileItem)
{
fileItem.CountdownTime--;
}
else
{
//Reducing counters of Files in Folders
ReduceFileCountInFolders(item);
}
}
}
//A file can be nested in multiple folders so we can solve this with a recursive method
private void ReduceFileCountInFolders(StorageItem item)
{
if (item is FileItem fileItem)
{
fileItem.CountdownTime--;
}
else if (item is FolderItem folderItem)
{
if (folderItem.Items != null && folderItem.Items.Count > 0)
{
foreach (StorageItem storageItem in folderItem.Items)
{
ReduceFileCountInFolders(storageItem);
}
}
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
throw new Exception($"PropertyChanged event handler FAILED : {ex.Message}");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
StorageItem.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TreeViewWithCountDown.ViewModels
{
public class StorageItem : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
public StorageItem(string name)
{
Name = name;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
throw new Exception($"PropertyChanged event handler FAILED : {ex.Message}");
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
FileItem.cs
namespace TreeViewWithCountDown.ViewModels
{
public class FileItem : StorageItem
{
private int _countdownTime;
public int CountdownTime
{
get => _countdownTime;
set
{
if (_countdownTime != value && value > 0)
{
_countdownTime = value;
OnPropertyChanged();
}
}
}
public FileItem(string name, int num) : base(name)
{
CountdownTime = num;
}
}
}
FolderItem.cs
using System.Collections.Generic;
namespace TreeViewWithCountDown.ViewModels
{
public class FolderItem : StorageItem
{
private List<StorageItem> _items = new List<StorageItem>();
public List<StorageItem> Items
{
get => _items;
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged();
}
}
}
public FolderItem(string name) : base(name)
{
}
}
}
The final look: View
Hope this will help, if anything seems complicated, feel free to ask!

Items in collection view not displaying with MVVM while link is showing

I need help trying to figure out why my collection-view is not displaying the data that its binded to. When I run the application in debug mode the data is being populated into the Viewmodel and binded. When I go to the View.xaml and hover over the source where its binded, it displays.
I have provided the Model, ModelView, View and the code behind for the View and even a screen shot of the view running in the debugger showing that the bind seems to be working.
I have been stuck for a while any help will be truly appreciated.
What I see when I run in debug mode showing the view model is binded but just not showing.
ContactsPage.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"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Project_contact.Views.ItemsPage"
Title="{Binding Title}"
x:Name="BrowseItemsPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Clicked="AddItem_Clicked" />
</ContentPage.ToolbarItems>
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadDataCommand}">
<StackLayout>
<Label x:Name="TopBanner" Text="Welcome Please wait..." />
<StackLayout Orientation="Horizontal">
<Label Text= "{Binding StringFormat='Welcome You have' }" />
</StackLayout>
<CollectionView x:Name="ItemsCollectionView2"
ItemsSource="{Binding Contacts}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10">
<Label Text="{Binding name}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<Label Text="{Binding desc}"
d:Text="Item descripton"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="OnContactSelected_Tapped"></TapGestureRecognizer>
<SwipeGestureRecognizer Direction="Left" />
</StackLayout.GestureRecognizers>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</RefreshView>
</ContentPage>
ContactsPage.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Project_Contact.Models;
using Project_Contact.Views;
using Project_Contact.ViewModels;
using Project_Contact.Services;
using System.Data;
namespace Project_Contact.Views
{
// Learn more about making custom code visible in the Xamarin.Forms previewer
// by visiting https://aka.ms/xamarinforms-previewer
[DesignTimeVisible(false)]
public partial class ItemsPage : ContentPage
{
public ContactsViewModel viewModel { get; set; }
public ContactStore contactStore { get; set; }
public ContactsPage()
{
contactStore = new ContactStore(DependencyService.Get<Database>());
viewModel = new ContactsViewModel(contactStore);
viewModel.LoadDataCommand.Execute(null);
BindingContext = viewModel;
InitializeComponent();
}
async void OnItemSelected(object sender, EventArgs args)
{
var layout = (BindableObject)sender;
var item = (Item)layout.BindingContext;
await Navigation.PushAsync(new ItemDetailPage(new ItemDetailViewModel(item)));
}
async void AddItem_Clicked(object sender, EventArgs e)
{
await Navigation.PushModalAsync(new NavigationPage(new NewContactPage(contactStore)));
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.LoadDataCommand.Execute(true);
}
async void OnContactSelected_Tapped(object sender, EventArgs e)
{
var layout = (BindableObject)sender;
var contact = (Contact)layout.BindingContext;
await Navigation.PushAsync(new ContactDetailPage(new ContactDetailViewModel(contactStore,contact)));
}
}
}
ContactsPageViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
using Project_contact.Models;
using Project_contact.Views;
using Project_contact.Services;
namespace Project_contact.ViewModels
{
public class ContactsViewModel : BaseViewModel
{
public ObservableCollection<Contact> Contacts { get; set; } = new ObservableCollection<Contact>();
public Command LoadContacts { get; private set; }
public Command LoadDataCommand { get; private set; }
// public Command load
public ContactStore contactStore;
public int numberofContacts { get; set; }
public string TopBannerText { get; set; }
public ContactsViewModel(ContactStore contactStore)
{
Title = "Browse";
this.contactStore = contactStore;
LoadDataCommand = new Command(async () => await ExecuteLoadDataCommand());
}
public async Task ExecuteLoadDataCommand()
{
Contacts = new ObservableCollection<Contact>(await contactStore.GetContactsAsync());
LoadContacts = new Command(async () => await ExecuteLoadContactsCommand());
TopBannerText = String.Format("Welcome you have {0} contacts ",numberofContacts);
}
async Task ExecuteLoadContactsCommand()
{
if (!IsBusy)
{
IsBusy = true;
try
{
if (Contacts.Count > 0)
{
Contacts.Clear();
numberofContacts = 0;
}
var contacts = await contactStore.GetContactsAsync();
foreach (var contact in contacts)
{
Contacts.Add(contact);
numberofContacts++;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}
}
}
Contact.cs
using System;
using System.Collections.Generic;
using System.Text;
using SQLite;
namespace Project_Contact.Models
{
public class Contact
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
public string name { get; set; }
public string desc { get; set; }
public string number {get; set;}
}
}

I'm trying to scan barcodes and show the result in my front page but the binding process is failing Xamarin Forms

This is my main page in XAML, the project is a barcode scanner but it is not binding the result of the lecture, could you hel me please
first i hava a page with a button to activate the scanner and a label to show the result
<ContentPage.BindingContext>
<viewmodels:MainPageViewModel />
</ContentPage.BindingContext>
<StackLayout Padding="20">
<Button Command="{Binding ButtonCommand}" HorizontalOptions="FillAndExpand" VerticalOptions="EndAndExpand" BackgroundColor="Teal" TextColor="White" FontSize="Medium" Text="Click to open scanner" />
<Label Text="{Binding Result}" HorizontalOptions="Center" VerticalOptions="StartAndExpand" />
</StackLayout>
and this is the viewmodel than i'm using to read the barcode
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Xamarin.Forms;
using ZXing;
using ZXing.Mobile;
using ZXing.Net.Mobile.Forms;
using System.Text;
namespace pruebascan.viewmodels
{
class MainPageViewModel
{
private string _result;
public string Result
{
get => _result;
set
{
_result = value;
OnPropertyChanged(nameof(Result));
}
}
public ICommand ButtonCommand { get; private set; }
public MainPageViewModel()
{
ButtonCommand = new Command(OnButtomCommand);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnButtomCommand()
{
var options = new MobileBarcodeScanningOptions();
options.PossibleFormats = new List<BarcodeFormat>
{
BarcodeFormat.QR_CODE,
BarcodeFormat.CODE_128,
BarcodeFormat.EAN_13
};
var page = new ZXingScannerPage(options) { Title = "Scanner" };
var closeItem = new ToolbarItem { Text = "Close" };
closeItem.Clicked += (object sender, EventArgs e) =>
{
page.IsScanning = false;
Device.BeginInvokeOnMainThread(() =>
{
Application.Current.MainPage.Navigation.PopModalAsync();
});
};
page.ToolbarItems.Add(closeItem);
page.OnScanResult += (result) =>
{
page.IsScanning = false;
Device.BeginInvokeOnMainThread(() => {
Application.Current.MainPage.Navigation.PopModalAsync();
if (string.IsNullOrEmpty(result.Text))
{
Result = "No valid code has been scanned";
}
else
{
Result = $"Result: {result.Text}";
}
});
};
Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page) { BarTextColor = Color.White, BarBackgroundColor = Color.CadetBlue }, true);
}
}
}
everything is working good but the result of the binding is always empty
i found it the problem was the class definition
i changed this
class MainPageViewModel
for this
public class MainPageViewModel : INotifyPropertyChanged
and everything is working perfect right now

What's wrong with my Xamarin.Forms project?

I'm trying to make a Xamarin.Forms project where I have a BoxView and an Entry field and a Button. I want to enter the name of a color into my Entry field, press the button, and have my BoxView change to the color that I input. Here is the code I have written till now:
Views/MainView.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="TestGround.MainView">
<ContentPage.Content>
<StackLayout VerticalOptions="Center">
<Label
Text="Enter a color:"
VerticalOptions="Center"
HorizontalOptions="Center"
/>
<BoxView
Color="{Binding Color}"
/>
<Entry
Text="{Binding Name}"
/>
<Button
Text="Enter"
Command="{Binding SetColorCommand}"
/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
ViewModels/MainViewModel.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
namespace TestGround
{
public class MainViewModel :INotifyPropertyChanged
{
private string _color; //backing field for Greeting
public string Color //implementation for Greeting method
{
get { return _color; }
set
{
_color = value;
OnPropertyChanged ("Color"); //Notify view that change has taken place
}
}
public string Name { get; set; } //Name method for Entry field
public ICommand SetColorCommand { get; set; } //ICommand binds to buttons in XAML
public void SetColor() //Need a regular method to add to ICommand
{
Color = Name;
}
//Main VIEW MODEL
public MainViewModel ()
{
//Color = Name;
Name = "Enter color here";
SetColorCommand = new Command(SetColor); //Regular command added to ICommand
}
#region PropertyChangedRegion
public void OnPropertyChanged (string propertyName)
{
if (PropertyChanged != null)
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
Here is the error I get:
Java.Lang.RuntimeException: java.lang.reflect.InvocationTargetException
I want to know if my approach is wrong and how can I go about fixing it and making this pretty simple program.
According to the BoxView Documentation, the property "Color" must actually be a color... where as you have it defined as a string named color. Your types are mixed up. It should be something like Colors.Blue.
You can use class ColorTypeConverter for change string to Color.
I 've simplified your problem to this source code
//You simplified model
public class bModel : BindableObject
{
private Color _realColor;
public Color Color
{
get { return _realColor; }
set
{
_realColor = value;
OnPropertyChanged ("Color");
}
}
public string _stringColor;
public string StringColor {
get {
return _stringColor;
}
set {
_stringColor = value;
Color = (Color)(new ColorTypeConverter ()).ConvertFrom (_stringColor);
}
}
public bModel ()
{
StringColor = "Blue";
}
}
}
//Your simplified page 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="s2c.MyPage">
<ContentPage.Content>
<BoxView x:Name="box" Color="{Binding Color}"/>
</ContentPage.Content>
</ContentPage>
//Your simplified page csharp
public partial class MyPage : ContentPage
{
public MyPage ()
{
InitializeComponent ();
this.BindingContext = new bModel ();
}
}

Listivew with binding not updating

I've got a listview with binding and it's not updating. Can somebody find the bug? Wish I had some money, because I would offer a reward.
In this screen cap, the window on the right (Active Dinosaur List) is NOT updating when the status of a particular dinosaur is changing (note that when you click on the dinosaur (in this case, Nancy) it shows, correctly, that her status is, "Moving to food" while the Active Dinosaur List is showing her still Resting:
Here's all the relevant code, starting with the XAML for the window:
<Window x:Class="DinosaurIsland.ActiveDinosaurList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DinosaurIsland"
Title="ActiveDinosaurList" Height="850" Width="245" WindowStyle="SingleBorderWindow" Icon="/DinosaurIsland;component/Icon1.ico" ResizeMode="NoResize" >
<Window.Resources>
<local:EnergyBarColorConverter x:Key="EnergyBarColorConverter"/>
<local:DinoStatusConverter x:Key="DinoStatusConverter"/>
<DataTemplate x:Key="DinosaurInfo">
<StackPanel Orientation="Vertical" >
<Label Name="DinosaurName" Margin="0,0,0,-8" Content="{Binding Path=PersonalName}"/>
<Label Name="DinosaurSpecies" Margin="0,0,0,-8" FontStyle="Italic" Content="{Binding Path=Specie}"/>
<Label Name="DinosaurStatus" Margin="0,0,0,-8" Content="{Binding Path=State, Converter={StaticResource DinoStatusConverter}}"/>
<Label HorizontalAlignment="Center" Margin="0,0,0,-2" Content="Energy" />
<ProgressBar Name="Health" Margin="0,0,0,10" HorizontalAlignment="Center" VerticalAlignment="Top" Width="160" Height="15"
Foreground ="{Binding Path=Health, Converter={StaticResource EnergyBarColorConverter}}" Value="{Binding Path=Health}" />
<Separator/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Width="210">
<ListView x:Name="DinoListView" Width="207" ItemsSource="{Binding Path=Dinosaurs}" HorizontalAlignment="Left" Margin="3,0,0,0">
<ListView.View>
<GridView>
<GridViewColumn Width="180" Header="Select Dinosaur" CellTemplate="{StaticResource DinosaurInfo}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
Here's the Dinosaur class:
using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
namespace DinosaurIsland
{
public class Dinosaur : INotifyPropertyChanged
{
public string _specie;
public string Specie
{
get { return _specie; }
set{_specie = value; RaisePropertyChanged("Specie");}
}
public int Age { get; set; }
public int Weight { get; set; }
public double Height { get; set; }
public int _health;
public int Health
{
get { return _health; }
set{_health = value; RaisePropertyChanged("Health");}
}
public double Water { get; set; }
public double FoodConsumed { get; set; }
public bool Sex { get; set; }
public string PersonalName { get; set; }
public System.Windows.Point Head = new System.Windows.Point();
public List<System.Windows.Point> Location { get; set; }
public double Length { get; set; }
public double Speed { get; set; }
public byte _state;
public byte State
{
get { return _state; }
set{_state = value; RaisePropertyChanged("State");}
}
public System.Windows.Point Goal = new System.Windows.Point();
public System.Windows.Point[] FoodLocation = new System.Windows.Point[5]; // The last five locations that the dino found food
public System.Windows.Point[] WaterLocation = new System.Windows.Point[5]; // The last five locations that the dino found water
// Constructor
public Dinosaur()
{
}
public event PropertyChangedEventHandler PropertyChanged;
//called when a property is changed
protected void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
}
Here's the ViewModel class:
using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace DinosaurIsland
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Dinosaurs = new ObservableCollection<Dinosaur>();
for(int i = 0; i < MainWindow.Dinosaurs.Count; i++)
this.Dinosaurs.Add(new Dinosaur()
{
PersonalName = MainWindow.Dinosaurs[i].PersonalName,
Specie = MainWindow.Dinosaurs[i].Specie,
Health = MainWindow.Dinosaurs[i].Health,
State = MainWindow.Dinosaurs[i].State
});
}
public event PropertyChangedEventHandler PropertyChanged;
//called when a property is changed
public void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
private ObservableCollection<Dinosaur> _dinoList = new ObservableCollection<Dinosaur>();
public ObservableCollection<Dinosaur> Dinosaurs
{
get { return _dinoList; }
set { _dinoList = value; RaisePropertyChanged("Dinosaurs"); }
}
}
}
Here's how the window is invoked:
// This is a global
public ViewModel vm = new ViewModel();
// ....
// Instantiate window
ViewModel vm = new ViewModel();
DinoListDialogBox.DataContext = vm;
DinoListDialogBox.Show();
That should be all the pieces to the puzzle. What am I missing?
Thanks... and I'll name a dinosaur after you.
Ok having looked at your source could get a solution for your use-case. I do suggest checking into MVVM properly.As it stands right now, Your project goes against MVVM in quite a few areas as I mentioned in chat.
Putting that aside with your current implementation to get the Dinosaurs list to be in sync with the ActiveDinosaurList View, these are the changes I made:
MainWindow.xaml.cs:
1) Switch Dinosaurs to an ObservableCollection<T> and a property. Such as
public static List<Dinosaur> Dinosaurs = new List<Dinosaur>();
to
public static ObservableCollection<Dinosaur> Dinosaurs { get; set; }
2) Add a static constructor to the MainWindow class to initialize the Dinosaurs property
static MainWindow() {
Dinosaurs = new ObservableCollection<Dinosaur>();
}
ViewModel.cs
3) Switch the Dinosaurs property to be a pass-thru to the static property in MainWindow and remove the backing collection. Such as
private ObservableCollection<Dinosaur> _dinoList = new ObservableCollection<Dinosaur>();
public ObservableCollection<Dinosaur> Dinosaurs
{
get { return _dinoList; }
set { _dinoList = value; RaisePropertyChanged("Dinosaurs"); }
}
to
public ObservableCollection<Dinosaur> Dinosaurs {
get {
return MainWindow.Dinosaurs;
}
}
4) Finally add a hook to listen to CollectionChanged on MainWindow.Dinosaurs from ViewModel and RaisePropertyChanged on it's Dinosaurs property.
so switch:
public ViewModel()
{
this.Dinosaurs = new ObservableCollection<Dinosaur>();
for(int i = 0; i < MainWindow.Dinosaurs.Count; i++)
this.Dinosaurs.Add(new Dinosaur()
{
PersonalName = MainWindow.Dinosaurs[i].PersonalName,
Specie = MainWindow.Dinosaurs[i].Specie,
Health = MainWindow.Dinosaurs[i].Health,
State = MainWindow.Dinosaurs[i].State
});
}
to
public ViewModel() {
MainWindow.Dinosaurs.CollectionChanged += (sender, args) => RaisePropertyChanged("Dinosaurs");
}
That's it. Running your simulation now, When I forwarded the time, I could see the Status on the ActiveDinosaurs list getting updated fine.
Inside your bindings use UpdateSourceTrigger=PropertyChanged.
So your label would look like this: <Label Name="DinosaurStatus" Margin="0,0,0,-8" Content="{Binding Path=State, Converter={StaticResource DinoStatusConverter} UpdateSourceTrigger=PropertyChanged}" />.

Categories