What i am doing is passing data through more than 2 pages. I assign viewmodel to next page while i am navigating. In second page i have a listview that is not refreshing/updating after adding a value.
Help me please!!
Here is my code
MyViewModel
public class MyViewModel : INotifyPropertyChanged
{
public string _userName { get; set; }
public List<family> familyList;
public List<family> FamilyList
{
get { return familyList; }
set
{
familyList = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MyViewModel()
{
_userName = "Mak";
familyList = new List<family>();
}
public void AddMember(string memberName)
{
FamilyList.Add(new family
{
name = memberName,
id = Guid.NewGuid().ToString(),
username=_userName
});
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
userdetails.xaml
<cl:BasePage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="familyinfo.userdetails" xmlns:cl="clr-namespace:familyinfo;assembly=familyinfo">
<Label Font="Roboto-Medium" FontSize="14" Text="{Bindinbg _userName}" />
<Button Clicked="Next_Step" HeightRequest="30" HorizontalOptions="FillAndExpand" BorderRadius="12" Text="NEXT" />
</cl:BasePage>
userdetails.xaml.cs
public partial class userdetails : BasePage
{
public MyViewModel _myViewModel { get; set; }
public userdetails()
{
InitializeComponent();
BindingContext = new MyViewModel();
}
void Next_Step(object sender, System.EventArgs e)
{
_myViewModel =(MyViewModel) this.BindingContext;
var familyMember = new FamilyMember();
familyMember.BindingContext = _myViewModel;
Application.Current.MainPage = new NavPage(registerCar);
}
}
FamilyMember.xaml
<cl:BasePage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="familyinfo.FamilyMember" xmlns:cl="clr-namespace:familyinfo;assembly=familyinfo">
<Label Font="Roboto-Medium" FontSize="14" Text="{Bindinbg _userName}" />
<cl:CustomEntry x:Name="txtMemberName" Placeholder="Member Name" FontSize="12" />
<Button Clicked="AddMember" HeightRequest="30" HorizontalOptions="FillAndExpand" BorderRadius="12" Text="Add" />
<ListView ItemsSource="{Binding FamilyList}" VerticalOptions="FillAndExpand" BackgroundColor="Transparent">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid Padding="20,10,0,0" ColumnSpacing="12" RowSpacing="0" BackgroundColor="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto">
</ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto">
</RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" Text="{Binding name}" Grid.Column="0" Font="Roboto-Medium" FontSize="14" TextColor="#000000" />
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</cl:BasePage>
FamilyMember.xaml.cs
public partial class FamilyMember : BasePage
{
public MyViewModel _myViewModel { get; set; }
public userdetails()
{
InitializeComponent();
}
void AddMember(object sender, System.EventArgs e)
{
_myViewModel = (MyViewModel)this.BindingContext;
_myViewModel.AddMember(txtMemberName.Text);
}
}
I agree with Atul: Using an ObservableCollection is the right way to do it.
A workaround - if you don't have a chance to change that - is to set the ListView's ItemSource to null and back to the list, whenever data changed and the UI needs to update:
void UpdateListView(ListView listView)
{
var itemsSource = listView.ItemsSource;
listView.ItemsSource = null;
listView.ItemsSource = itemsSource;
}
In fact, you must use a collection that implements INotifyCollectionChanged interface (instead of the well known INofifyPropertyChanged). And that's exactly what does ObservableCollection<T> for you. This is why it works like "magic".
I just used ObservableCollection instead of List and it works!!
Related
I'm trying to bind a Label to the result of the GetPlayCount() function call. The other bindings, for Name and Category, are working as expected, but there is no output for the third label
XAML:
<ListView ItemsSource="{Binding Games}"
HasUnevenRows="true"
HeightRequest="200"
SeparatorVisibility="Default">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid Margin="0" Padding="0" RowSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Margin="0" Text="{Binding Name}"/>
<Label Grid.Column="1" Margin="0" Text="{Binding Category}"/>
<!--This following Label is the one not binding -->
<Label Grid.Column="2" Margin="0" Text="{Binding GetPlayCount}" />
</Grid>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code Behind:
public partial class CollectionPage : ContentPage
{
CollectionViewModel collectionView = new CollectionViewModel();
public CollectionPage()
{
InitializeComponent();
BindingContext = collectionView;
}
}
ViewModel:
public class CollectionViewModel : INotifyPropertyChanged
{
private ObservableCollection<Game> games;
public ObservableCollection<Game> Games
{
get { return games; }
set
{
games = value;
OnPropertyChanged("Games");
}
}
public CollectionViewModel()
{
GetGames();
}
public async void GetGames()
{
var restService = new RestService();
Games = new ObservableCollection<Game>(await restService.GetGamesAsync());
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Model:
public class Game
{
public string Name { get; set; }
public string Category { get; set; }
public async Task<int> GetPlayCount()
{
return (await new RestService().GetGamesAsync()).Where(result => result.Name == this.Name).Count();
}
}
You can bind only to the property. You may call that function from the property getter. However it is not possible to bind to the function. The app doesn't know when your function is updated, so binding wouldn't make much sense. For the property you can call PropertyChanged to signal that the property has a new value.
I will talk with code:
[NotMapped]
public decimal TotalPrice { get =>GetTotalPrice(); }
private decimal GetTotalPrice()
{
decimal result = 0;
foreach(var dpo in DetailPurchaseOrder)
{
result = result + dpo.GetTotalPurchasePrice();
}
return result;
}
I have two XAML pages and one their common ViewModel page.I want to output data from one page to another from the collection of the selected item. It must be Label`s Text.
I have 2 problems
1)I can not bind text from label to the object field
2)If I bind Label`s Text to a variable.I can see data only on the current page. But if I go to another page and place the same label there, the information is not displayed.I do not understand why so because on the next page the same variable which already contains data
FIRST XAML PAGE
<?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="App1.TryPage">
<ContentPage.Content>
<StackLayout>
<CollectionView x:Name="AddCar" ItemsSource="{Binding Hearts}"
SelectionMode="None">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="135" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Frame CornerRadius="10" BorderColor="Black" Padding="0" >
<Button
CornerRadius="10" HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="135" WidthRequest="150"
BackgroundColor="{Binding CustButtonColor}" ImageSource="{Binding Image}"
Command="{ Binding BindingContext.ChangeColor,
Source={x:Reference Name=AddCar} }" CommandParameter="{Binding .}"/>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Label x:Name="small12" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Start" Text="{Binding tryHeart.TypeHeart}" />
<Button Text="Navigate" Command="{Binding navigateCommand }">
</StackLayout>
</ContentPage.Content>
</ContentPage>
CODE BEHIND
public partial class TryPage : ContentPage
{
public TryPage()
{
InitializeComponent();
BindingContext = new TryPageCS(this.Navigation);
}
}
VIEW MODEL PAGE
public class TryPageCS : INotifyPropertyChanged
{
public ObservableCollection<CircleColor> Hearts { get; set; }
public ICommand ChangeColor { protected set; get; }
public TryHeart tryHeart { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
INavigation Navigation { get; set; }
public Command navigateCommand { get; set; }
public async Task GotoPage2()
{
await Navigation.PushModalAsync(new MainPage());
}
public TryPageCS(INavigation navigation)
{
tryHeart = new TryHeart();
this.Navigation = navigation;
this.navigateCommand = new Command(async () => await GotoPage2());
Hearts = new ObservableCollection<CircleColor>();
Hearts.Add(new CircleColor() { Name = "one", Image = "heart", CustButtonColor = Color.White });
Hearts.Add(new CircleColor() { Name = "two", Image = "heart", CustButtonColor = Color.White });
Hearts.Add(new CircleColor() { Name = "three", Image = "heart", CustButtonColor = Color.White });
Hearts.Add(new CircleColor() { Name = "four", Image = "heart", CustButtonColor = Color.White });
var DefaultCars = new ObservableCollection<CircleColor>();
DefaultCars = Hearts;
ChangeColor = new Command<CircleColor>((key) =>
{
foreach (var item in Hearts)
{
item.CustButtonColor = Color.White;
item.Image = "heart";
}
var car = key as CircleColor;
car.CustButtonColor = Color.LightCoral;
tryHeart.TypeHeart = car.Name;
});
}
}
SECOND PAGE
<?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"
xmlns:controls="clr-namespace:App1"
x:Class="App1.MainPage">
<StackLayout>
<Label FontSize="Large" Text="{Binding tryHeart.TypeHeart}" />
</StackLayout>
</ContentPage>
CODE BEHIND
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new TryPageCS(this.Navigation);
}
}
Also I have a class
public class TryHeart : INotifyPropertyChanged
{
string typeHeart;
public string TypeHeart
{
set
{
if (typeHeart != value)
{
typeHeart = value;
OnPropertyChanged("TypeHeart");
}
}
get
{
return typeHeart;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I will explain why I need it. In my real project I have to collect information about the car from different pages. object of this class it will be my machine. Therefore I want to write down the collected data in object of a class and then on the last page to display data
On the SECOND XAML PAGE I write only THE SAME LABEL
<Label x:Name="small123" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Start" Text="{Binding Name}" />
Please,help me with my 2 problems
1)Why I can not to write
<Label x:Name="small12" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Start" Text="{Binding tryHeart.TypeHeart}" />
Information does not display
2)How I must display information from one page from collection view of selected item to another page
Navigation does not contain a definition for PushModalAsync
For question 1
TryHeart in the ViewModel is a private property in your case . You need to set it as public .
public TryHeart tryHeart {get;set;}
public TryPageCS()
{
//...
tryHeart = new TryHeart();
//...
}
For question 2
If you want to handle navigation logic in VM , you need to pass the current navigation from current page .
in ViewModel
Add a property
INavigation CurrentNavigation { get; set; }
public TryPageCS(INavigation navigation)
{
CurrentNavigation = navigation;
}
And now you can use the property in the method
await CurrentNavigation.PushModalAsync(new MainPage());
in ContentPage
Pass the Navigation as params
BindingContext = new TryPageCS(this.Navigation);
I have a problem with the listviewItem, is that when you change the data if they do it but they are not saved in the interface when you click on another item
This problem happens when binding the textbox to the listviewItem
MainPage.xaml
<Grid RequestedTheme="Light">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="818*" />
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBox
x:Name="titulo"
Grid.Row="0"
FontSize="40"
PlaceholderText="Ingresa tu titulo"
KeyDown="Titulo_KeyDown"
/>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<ListView
x:Name="listNotas"
Width="450"
Background="DimGray"
SelectionChanged="ListNotas_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate >
<StackPanel>
<TextBlock Text="{Binding title, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<RichEditBox
x:Name="editor"
Width="760"
HorizontalAlignment="Stretch" />
</StackPanel>
<GridView
Name="stpanel"
Grid.Row="2"
Height="50">
<TextBlock Text="" Name="Tester"/>
</GridView>
MainPage.xaml.cs
public string editpath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "Notas.json" );
public ObservableCollection<Notes> Mynotes;
public MainPage()
{
this.InitializeComponent();
// Load data of Notas.json to Listview
LoadUpdate();
}
private void LoadUpdate()
{
using (StreamReader file = File.OpenText(editpath))
{
var json = file.ReadToEnd();
baseNotes mainnotes = JsonConvert.DeserializeObject<baseNotes>(json);
Mynotes = new ObservableCollection<Notes>();
foreach (var item in mainnotes.notes)
{
Mynotes.Add(new Notes { title = item.title });
}
listNotas.ItemsSource = null;
listNotas.ItemsSource = Mynotes;
listNotas.SelectedIndex = 0;
}
}
private void ListNotas_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string json = File.ReadAllText(editpath);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
titulo.Text = jsonObj["notes"][listNotas.SelectedIndex]["title"];
}
private void Titulo_KeyDown(object sender, KeyRoutedEventArgs e)
{
#region
string json = File.ReadAllText(editpath);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
int indice = listNotas.SelectedIndex;
jsonObj["notes"][indice]["title"] = titulo.Text;
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj);
File.WriteAllText(editpath, output);
// Show json file text in RicheditBox
editor.TextDocument.SetText(Windows.UI.Text.TextSetOptions.None, output);
//Problem
Binding myBinding = new Binding();
myBinding.Source = Mynotes[indice];
myBinding.Path = new PropertyPath("title");
myBinding.Mode = BindingMode.TwoWay;
myBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(titulo, TextBox.TextProperty, myBinding);
#endregion
}
Model: Notes.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
namespace Realtimejsonedit
{
public class Notes : INotifyPropertyChanged
{
public int created { get; set; }
//public string title { get; set; }
private string Title;
public string title
{
get { return Title; }
set {
Title = value;
NotifyPropertyChanged("title");
}
}
public string text { get; set; }
public int id { get; set; }
public int updated { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class baseNotes
{
public List<Notes> notes { get; set; }
}
}
as I say the problem as I am doing the binding but when executing ListNotas.SelectionChanged the values that were saved in the json file are changed, but they do not remain in the listviewitem, although the binding is in the Keydown event and not in ListNotas. SelectionChanged.
the problem:
https://i.imgur.com/IGcd8iz.gif
What I want to achieve:
https://i.imgur.com/KnkbQw9.gif
UWP - How to save ListViewItem state if the data source has changed?
The problem is that you set bind repeatedly in Titulo_KeyDown event. For your requirement, you could bind ListView SelectItem once. For more please refer the following steps:
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public string editpath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "Notas.json");
public ObservableCollection<Notes> Mynotes { get; set; }
public ViewModel()
{
LoadUpdate();
SetSelectIndex(0);
}
private void SetSelectIndex(int index)
{
SelectItem = Mynotes[index];
}
private void LoadUpdate()
{
using (StreamReader file = File.OpenText(editpath))
{
var json = file.ReadToEnd();
baseNotes mainnotes = JsonConvert.DeserializeObject<baseNotes>(json);
Mynotes = new ObservableCollection<Notes>();
foreach (var item in mainnotes.notes)
{
Mynotes.Add(new Notes { title = item.title });
}
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private Notes _selectItem;
public event PropertyChangedEventHandler PropertyChanged;
public Notes SelectItem
{
get
{
return _selectItem;
}
set
{
_selectItem = value;
OnPropertyChanged();
}
}
}
Xaml
<Page.DataContext>
<local:ViewModel />
</Page.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="818*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBox
x:Name="titulo"
Grid.Row="0"
FontSize="40"
PlaceholderText="Ingresa tu titulo"
Text="{Binding SelectItem.title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextChanged="Titulo_TextChanged"
/>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<ListView
x:Name="listNotas"
Width="450"
Background="DimGray"
ItemsSource="{Binding Mynotes}"
SelectedItem="{Binding SelectItem, Mode=TwoWay}"
>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<RichEditBox
x:Name="editor"
Width="760"
HorizontalAlignment="Stretch"
/>
</StackPanel>
<GridView
Name="stpanel"
Grid.Row="2"
Height="50"
>
<TextBlock Name="Tester" Text="" />
</GridView>
</Grid>
Code behind (write the data to json)
public sealed partial class MainPage : Page
{
private dynamic jsonObj;
public string editpath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "Notas.json");
public ObservableCollection<Notes> Mynotes;
public MainPage()
{
this.InitializeComponent();
string json = File.ReadAllText(editpath);
jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
}
private void Titulo_TextChanged(object sender, TextChangedEventArgs e)
{
#region
int indice = listNotas.SelectedIndex;
jsonObj["notes"][indice]["title"] = titulo.Text;
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj);
editor.TextDocument.SetText(Windows.UI.Text.TextSetOptions.None, output);
File.WriteAllText(editpath, output);
#endregion
}
}
This is sample project.
I try to rewrite my UWP C# app for Windows10 to Xamarin app using XAML. But Binding (for example here in ListView ItemSource=...) is not working for me and I don´t know why.
Visual Studio tells me, Cannot Resolve Symbol Recording due to unknown Data Context.
Here is my XAML (MainPage.xaml) for testing purpose:
<?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:local="clr-namespace:XamarinTest;assembly=XamarinTest"
x:Class="XamarinTest.MainPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recording}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Image Source="Accept" WidthRequest="40" HeightRequest="40" />
<StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Text="TEST" HorizontalOptions="FillAndExpand" />
<Label Text="TEST" />
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
Here is C# (MainPage.xaml.cs):
namespace XamarinTest
{
public partial class MainPage : ContentPage
{
public MainPage()
{
this.InitializeComponent();
this.AllTestViewModel = new RecordingViewModel();
this.BindingContext = AllTestViewModel;
}
public RecordingViewModel AllTestViewModel { get; set; }
}
}
And finally ViewModel (RecordingViewModel.cs):
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using XamarinTest.Model;
namespace XamarinTest.ViewModel
{
public class RecordingViewModel : INotifyPropertyChanged
{
public ObservableCollection<Recording> Recordings { get; } = new TrulyObservableCollection<Recording>();
public RecordingViewModel()
{
Recordings.Add(new RecordingTest2()
{
TestName = "Test 1",
TestNote = "Vytvoreni DB",
TestTime = new TimeSpan(0, 0, 0)
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += FullObservableCollectionCollectionChanged;
}
public TrulyObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
}
Everything (models and viewmodels) are working in native UWP Windows 10 app. Only the Binding and making same view is problem in Xamarin. Could someone please help with binding?
Thx.
EDIT
Recording.cs is here:
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XamarinTest.Model
{
public abstract class Recording : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string TestName { get; set; }
private TimeSpan _testTime;
private string _testNote;
private string _actualIco = "Play";
private bool _isActive = false;
private bool _enabled = true;
public double IcoOpacity { get; private set; } = 1.0;
public string ActualIco
{
get => _actualIco;
set
{
if (_actualIco == null) _actualIco = "Admin";
_actualIco = value;
NotifyPropertyChanged("ActualIco");
}
}
public bool IsActive
{
get => _isActive;
set
{
if (_isActive == value) return;
_isActive = value;
IcoOpacity = !value ? 1.0 : 0.3;
NotifyPropertyChanged("IsActive");
NotifyPropertyChanged("IcoOpacity");
}
}
public bool Enabled
{
get => _enabled;
set
{
if (_enabled == value) return;
_enabled = value;
NotifyPropertyChanged("Enabled");
}
}
public string TestNote
{
get => _testNote;
set
{
if (_testNote == value) return;
_testNote = value;
NotifyPropertyChanged("TestNote");
}
}
public TimeSpan TestTime
{
get => _testTime;
set
{
if (_testTime == value) return;
_testTime = value;
NotifyPropertyChanged("TestTime");
}
}
protected Recording()
{
TestName = "Unkonwn";
TestNote = "";
_testTime = new TimeSpan(0, 0, 0);
}
protected Recording(string testName, string testNote, TimeSpan testTime)
{
TestName = testName;
TestNote = testNote;
_testTime = testTime;
}
public string OneLineSummary => $"{TestName}, finished: "
+ TestTime;
private void NotifyPropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public abstract bool playTest();
}
}
I tried add DataContext in XAML (postet in origin question), because of intellisence like this:
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:dvm="clr-namespace:XamarinTest.ViewModel"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
d:DataContext="{system:Type dvm:RecordingViewModel}"
and this to Grid:
<Label Text="{Binding Recordings[0].TestName}" Grid.Row="0" Grid.Column="2" />
IntelliSence is OK, but text doesn´t show in app.
Finally is working!
XAML should looks like code below.
Imporant is xmls:viewModel="..." and <ContentPage.BindingContext>...</>.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModel="clr-namespace:XamarinTest.ViewModel;assembly=XamarinTest"
x:Class="XamarinTest.MainPage"
>
<ContentPage.BindingContext>
<viewModel:RecordingViewModel/>
</ContentPage.BindingContext>
<ListView x:Name="listView" ItemsSource="{Binding Recordings}" Grid.Row="1" Grid.Column="1">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal">
<Image Source="Accept" WidthRequest="40" HeightRequest="40" />
<StackLayout Orientation="Vertical" HorizontalOptions="StartAndExpand">
<Label Text="{Binding TestName}" HorizontalOptions="FillAndExpand" />
<Label Text="{Binding TestNote}" />
</StackLayout>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
and MainPage.xaml.cs is okey
namespace XamarinTest
{
public partial class MainPage : ContentPage
{
public MainPage()
{
this.InitializeComponent();
this.AllTestViewModel = new RecordingViewModel();
this.BindingContext = AllTestViewModel;
}
public RecordingViewModel AllTestViewModel { get; set; }
}
}
looking at your ViewModel, it looks like there is no Recording member, but you do have a Recordings member.
EDIT
So you are adding your DataContext in the code behind so ignore the Xaml part.
Your View (MainPage.xaml) has a ViewModel(RecordingViewModel.cs). The ViewModel has a member called Recordings (a collection of type Recording). But in your Xaml, you are try to bind to Recording.
Change:
<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recording}">
to:
<ListView x:Name="listView" IsVisible="false" ItemsSource="{Binding Recordings}">
2nd EDIT
The only Labels in your example is the one inside of the ListView yes?
If so, you can access the Recordings children like TestNote by:
I have Observable collection with MarketPrices , This observable collection I have bind to an ItemsControl as Below .
1) Now I don't want to show all Items in Observable Collection , want to show only items that user click Add and selected Pair (GBPJPY, USDGBP..) needs to show in Items Control.
2) If user changed item in Comobobox from GBPJPY to USDGBP , then the price( DataTemplate) of GBPJPY need to update USDGBP.
How can I achieve both conditions. Please note that below code doesn't have real-time update but in project I have relatime price update as well, so observable collection updates on price changes.
Code So Far
public class PriceModel : INotifyPropertyChanged
{
private double _askPrice;
private double _offerPrice;
private string _selectedPair;
public PriceModel()
{
Pairs = new ObservableCollection<string> {"GBPUSD", "GBPEUR", "USDGBP", "GBPJPY"};
}
public double AskPrice
{
get { return _askPrice; }
set
{
_askPrice = value;
OnPropertyChanged("AskPrice");
}
}
public double OfferPrice
{
get { return _offerPrice; }
set
{
_offerPrice = value;
OnPropertyChanged("OfferPrice");
}
}
public string SelectedPair
{
get { return _selectedPair; }
set
{
_selectedPair = value;
OnPropertyChanged(SelectedPair);
}
}
public ObservableCollection<string> Pairs { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
MarketPrices = new ObservableCollection<PriceModel>
{
new PriceModel {AskPrice = 1.60345, OfferPrice = 1.60335, SelectedPair = "GBPUSD"},
new PriceModel {AskPrice = 1.71345, OfferPrice = 1.71335, SelectedPair = "GBPEUR"},
new PriceModel {AskPrice = 1.23345, OfferPrice = 1.23335, SelectedPair = "USDGBP"},
new PriceModel {AskPrice = 1.34345, OfferPrice = 1.34335, SelectedPair = "GBPJPY"}
};
}
public ObservableCollection<PriceModel> MarketPrices { get; set; }
}
XAML
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding MarketPrices}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="5" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel AllowDrop="True" ClipToBounds="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding Pairs}" SelectedItem="{Binding SelectedPair}" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Margin="2" Text="Ask Price" />
<TextBlock Margin="2" Text="{Binding AskPrice}" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Margin="2" Text="Offer Price" />
<TextBlock Margin="2" Text="{Binding OfferPrice}" />
</StackPanel>
</Grid>
</Grid>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
If I understand your question correctly, you want to display a list of pairs in a ComboBox and the details for a Selected pair only but not for all pairs?
If that's the case, there are couple problems with the code.
PriceModel
You do not need a collection of all available pairs in your PriceModel class. Also, You do not need SelectedPair property in this class, may be your intention was to indicate the name of the pair, you could update your PriceModel to:
public class PriceModel : INotifyPropertyChanged
{
private double _askPrice;
private double _offerPrice;
private string _pair;
public PriceModel(string pair)
{
_pair = pair;
}
public string Pair
{
get { return _pair; }
}
public double AskPrice
{
get { return _askPrice; }
set
{
_askPrice = value;
OnPropertyChanged("AskPrice");
}
}
public double OfferPrice
{
get { return _offerPrice; }
set
{
_offerPrice = value;
OnPropertyChanged("OfferPrice");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml.cs
You have a property named MarketPrices to hold the collection of Pairs, but no property to hold the Selected pair. Add a property named SelectedPair of type PriceModel. The Updated code would like this:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private PriceModel _selectedPair;
public MainWindow()
{
InitializeComponent();
DataContext = this;
MarketPrices = new ObservableCollection<PriceModel>
{
new PriceModel("GBPUSD") {AskPrice = 1.60345, OfferPrice = 1.60335, },
new PriceModel("GBPEUR") {AskPrice = 1.71345, OfferPrice = 1.71335, },
new PriceModel("USDGBP") {AskPrice = 1.23345, OfferPrice = 1.23335, },
new PriceModel("GBPJPY") {AskPrice = 1.34345, OfferPrice = 1.34335, }
};
}
public ObservableCollection<PriceModel> MarketPrices { get; set; }
public PriceModel SelectedPair
{
get { return _selectedPair; }
set
{
_selectedPair = value;
OnPropertyChanged("SelectedPair");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
You could just use ComboBox to display the list of available pairs and update your bindings for TextBoxes to refer SelectedPair.
Update XAML would look like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding Pairs}" SelectedItem="{Binding SelectedPair}" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Margin="2" Text="Ask Price" />
<TextBlock Margin="2" Text="{Binding SelectedPair.AskPrice}" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Margin="2" Text="Offer Price" />
<TextBlock Margin="2" Text="{Binding SelectedPair.OfferPrice}" />
</StackPanel>
</Grid>
</Grid>
Sample Output